Files
ppsspp/Common/UI/Screen.h
T
Henrik RydgårdandClaude Opus 5.5 d491fc90cf Secondary outputs, and --game-window on Mac to show the game in a second window
The ScreenManager keeps a list of secondary outputs, each with its own
swapchain, recreated after a lost device. Platform code adds, resizes and
removes them from any thread through Native_AddOutput() and friends, which
take effect at the start of the next frame.

Each frame, every output is drawn by the topmost screen that claims it
(Screen::renderOutput). EmuScreen claims Game outputs: it presents the
game there, laid out for that output, and leaves it out of the backbuffer,
where the touch controls and the menus stay. Not in non-buffered mode,
which draws straight into the backbuffer.

--game-window opens such an output on Mac, with Metal. It's a smoke test
for multiple swapchains, and a stand-in for an AirPlay display.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:42:53 +02:00

170 lines
4.9 KiB
C++

// Super basic screen manager. Let's you, well, switch between screens. Can also be used
// to pop one screen in front for a bit while keeping another one running, it's basically
// a native "activity stack". Well actually that part is still a TODO.
//
// Semantics
//
// switchScreen: When you call this, on a newed screen, the ScreenManager takes ownership.
// On the next update, it switches to the new screen and deletes the previous screen.
//
// TODO: A way to do smooth transitions between screens. Will probably involve screenshotting
// the previous screen and then animating it on top of the current screen with transparency
// and/or other similar effects.
#pragma once
#include <cstdint>
#include <string>
#include "Common/Common.h"
#include "Common/Input/InputState.h"
#include "Common/System/NativeApp.h"
#include "Common/System/System.h"
namespace UI {
class View;
}
namespace Draw {
class DrawContext;
class Swapchain;
}
// A display other than the primary one, like a second window or a TV. See docs/multi-swapchain.md.
struct OutputDisplay {
int id;
OutputRole role;
Draw::Swapchain *swapchain; // nullptr while there's no draw context.
};
enum DialogResult {
DR_NONE,
DR_OK,
DR_CANCEL,
DR_YES,
DR_NO,
DR_BACK,
};
const char *DialogResultToString(DialogResult result);
class ScreenManager;
class UIContext;
enum class ScreenFocusChange {
FOCUS_LOST_TOP, // Another screen was pushed on top
FOCUS_BECAME_TOP, // Became the top screen again
};
enum class ScreenRenderMode {
DEFAULT = 0,
FIRST = 1,
BEHIND = 4,
TOP = 8,
};
ENUM_CLASS_BITOPS(ScreenRenderMode);
enum class ScreenRenderRole {
NONE = 0,
CAN_BE_BACKGROUND = 1,
MUST_BE_FIRST = 2,
};
ENUM_CLASS_BITOPS(ScreenRenderRole);
enum class ViewLayoutMode {
ApplyInsets = 0, // For gameplay, option #1
IgnoreInsets, // For gameplay, option #2
IgnoreBottomInset,
};
enum class QueuedEventType : u8 {
KEY,
AXIS,
TOUCH,
};
struct QueuedEvent {
QueuedEventType type;
union {
TouchInput touch;
KeyInput key;
AxisInput axis;
};
};
enum class ScreenRenderFlags {
NONE = 0,
HANDLED_THROTTLING = 1,
};
ENUM_CLASS_BITOPS(ScreenRenderFlags);
enum class InputMode {
None = 0,
Keyboard = 1,
Mouse = 2,
Other = 4,
ImDebuggerToggle = 8, // ugly hack. debugger and pause goes through.
AllKeys = 16, // The screen gets keys that are normally left to the OS, like volume. For key mapping.
};
ENUM_CLASS_BITOPS(InputMode);
class Screen {
public:
Screen() = default;
virtual ~Screen();
virtual void onFinish(DialogResult reason) {}
virtual void update() {}
virtual ScreenRenderFlags PreRender(ScreenRenderMode mode) { return ScreenRenderFlags::NONE; }
virtual ScreenRenderFlags render(ScreenRenderMode mode) = 0;
// Draws this screen's content for a secondary output, whose swapchain is bound. Returns false if the
// screen has nothing for it. The topmost screen that returns true is the one shown.
virtual bool renderOutput(const OutputDisplay &output) { return false; }
virtual void resized() {}
virtual void dialogFinished(const Screen *dialog, DialogResult result) {}
virtual void sendMessage(UIMessage message, const char *value) {}
virtual void deviceLost() {}
virtual void deviceRestored(Draw::DrawContext *draw) {}
virtual ScreenRenderRole renderRole(bool isTop) const { return ScreenRenderRole::NONE; }
virtual bool wantBrightBackground() const { return false; } // special hack for DisplayLayoutScreen.
virtual void focusChanged(ScreenFocusChange focusChange);
virtual bool touch(const TouchInput &touch) = 0;
virtual bool key(const KeyInput &key) = 0;
virtual void axis(const AxisInput &axis) = 0;
virtual void RecreateViews() {}
// NOTE: This is polled every frame, not called on every input event.
virtual InputMode PassInputToMapper() const { return InputMode::None; }
ScreenManager *screenManager() { return screenManager_; }
const ScreenManager *screenManager() const { return screenManager_; }
void setScreenManager(ScreenManager *sm) { screenManager_ = sm; }
virtual const char *tag() const = 0;
virtual bool isTransparent() const { return false; }
virtual bool isTopLevel() const { return false; }
virtual TouchInput transformTouch(const TouchInput &touch) { return touch; }
virtual bool WantsTextInput() const { return false; }
protected:
// Requests made while creating views (by the views themselves, or with callbacks that capture them) get a separate
// token, so they can be forgotten when the views are deleted, while the screen's own requests stay alive.
int GetRequesterToken();
void SetCreatingViews(bool creating) { creatingViews_ = creating; }
bool IsCreatingViews() const { return creatingViews_; }
// Drops the callbacks of pending requests made by the views, which are about to be deleted.
void ForgetViewRequests();
private:
ScreenManager *screenManager_ = nullptr;
int token_ = -1;
int viewsToken_ = -1;
bool creatingViews_ = false;
DISALLOW_COPY_AND_ASSIGN(Screen);
};