Files
2026-04-17 20:09:41 +03:00

691 lines
35 KiB
C++

#pragma once
// Native UI runtime - window abstraction
// Part of the AC6 Recompilation native presenter/window layer
#include <cstddef>
#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <rex/platform.h>
#include <rex/ui/menu_item.h>
#include <native/ui/presenter.h>
#include <native/ui/surface.h>
#include <rex/ui/ui_event.h>
#include <rex/ui/virtual_key.h>
#include <rex/ui/window_listener.h>
#include <native/ui/windowed_app_context.h>
namespace rex {
namespace ui {
class Window {
public:
// Transitions between these phases is sequential and looped (and >= and <=
// can be used for openness checks, for closedness checks ! of >= and <= is
// needed due to looping), with the exception of kDeleting that may be entered
// from any other state (with an assertion for that not being done during
// kOpening though as that's extremely dangerous and would require a lot of
// handling in OpenImpl on all platforms) as the Window object may be deleted
// externally at any moment, and the Window will have no control anymore when
// that happens. Another exception is that the window may be closed in the
// platform during kOpening - in this case, it will skip kOpenBeforeClosing
// and go directly to kClosing and beyond.
enum class Phase {
// The window hasn't been opened yet, or has been fully closed and can be
// reopened.
// No native window - external state updates change only the desired state,
// don't go to the implementation immediately.
// No actual state.
// Listeners are not called.
kClosedOpenable,
// OpenImpl is being invoked, the implementation is performing initial
// setup.
// External state changes are functionally near-impossible as OpenImpl
// mostly isn't able to communicate with external Xenia code that may have
// any effect on the Window, to avoid interference during the native window
// setup which may be pretty complex - and also, this is the phase in which
// the native window is being created, so there's no guarantee that the
// native window exists throughout this phase.
// However, it's still not strictly enforceable that OpenImpl will not cause
// any interaction with the Window - it may happen, for instance, if
// OpenImpl causes the OS to execute the pending functions in the
// WindowedAppContext that might have been, for example, left over from when
// the window was still open last time. Therefore, for the reason of state
// consistency within OpenImpl, all external state changes are simply
// dropped in this phase.
// However, the desired state may be updated by the implementation during
// OpenImpl still, but via the Update functions (if the implementation or
// the OS, for instance, clamps the size of the window during creation, or
// refuses to enter fullscreen).
// Actual state is first populated during this phase, and it's readable
// (with its level of completeness at the specific point in time) for the
// internal purposes of the implementation.
// Listeners are not called so the implementation can perform all the setup
// without outer interference (the common code calls some to let the
// listeners be aware of the initial state when the window enters kOpen
// anyway).
// Note: Closing may occur in the platform during kOpening - skip
// kOpenBeforeClosing in this case while closing.
kOpening,
// Fully interactive.
// The native window exists, external state changes are applied immediately
// (or at least immediately requested to be applied shortly after, but the
// implementation must make sure that, for instance, if SetFullscreen is
// successfully called, but the window will actually enter fullscreen only
// at the next platform event loop tick, IsFullscreen will start returning
// true immediately).
// The desired state can be updated as feedback from the implementation.
// Actual state can be retrieved.
// Listeners are called.
// The only state in which a non-null Surface can be created (it's destroyed
// after entering kClosing).
kOpen,
// OnBeforeClose is being invoked.
// The native window still exists, this is mostly like kOpen, but with no
// way of creating a Surface (it's becoming destroyed in this state so the
// native window can be destroyed safely in the next phase) or recursively
// requesting closing - for consistency during closing within the platform,
// state changes also behave like in kOpen.
// Listeners are still called as normal, primarily because this is where the
// OnClosing listener function is invoked, but reopening the window from a
// listener is not possible for state consistency during closing.
kOpenBeforeClosing,
// OnBeforeClose has completed, but OnAfterClose hasn't been called yet for
// the implementation to confirm that it has finished destroying the native
// window being closed. This state exists to prevent the situation in which
// the Window is somehow reopened in the middle of the implementation's
// internal work in closing.
// The implementation must detach from the native window and destroy it in
// this phase.
// The native window is being destroyed - external state updates change only
// the desired state, don't go to the implementation immediately.
// Actual state is still queryable for internal purposes of the
// implementation.
// Listeners are not called.
kClosing,
// OnBeforeClose has completed, but the close has occurred from a listener,
// and the call stack of listeners still hasn't been exited.
// This state exists to prevent the situation in which the Window is being
// closed and then reopened in the middle of the implementation's internal
// work in event handlers. For example, let's assume putting a window (HWND)
// in the fullscreen state requires removing window decorations
// (SetWindowLong) and resizing the window (SetWindowPos), both being able
// to invoke the resize handler (WM_SIZE) and thus the resize listener. In
// this case, consider the following situation:
// - SetWindowLong called for HWND.
// - WM_SIZE arrives.
// - Listener's OnResize called.
// - Listener's OnResize does RequestClose, destroying the current HWND.
// - Listener's OnResize does Open, creating a new HWND.
// - SetWindowPos called for HWND, which is different now.
// Here, SetWindowLong will be called for one native window, but then it
// will be replaced, and SetWindowPos will be called for a different one.
// No native window - external state updates change only the desired state,
// don't go to the implementation immediately.
// No actual state.
// Listeners are not called.
kClosedLeavingListeners,
// The destructor has been called - should transition into this before doing
// anything not only in the common, but in the implementation's destructor
// as well. The transition must be done regardless of the previous phase as
// there's no way the Window can stop its destruction from now on. This is
// mostly similar to kClosedLeavingListeners, except there's no way to leave
// this phase.
kDeleting,
};
enum class CursorVisibility {
kVisible,
// Temporarily revealed, hidden if not interacting with the mouse.
kAutoHidden,
kHidden,
};
static std::unique_ptr<Window> Create(WindowedAppContext& app_context,
const std::string_view title,
uint32_t desired_logical_width,
uint32_t desired_logical_height);
virtual ~Window();
WindowedAppContext& app_context() const { return app_context_; }
Phase phase() const { return phase_; }
bool HasActualState() const {
// kOpening - for internal use by the implementation.
// kOpen, kOpenBeforeClosing - for both external and internal use.
// kClosing - for internal use by the implementation.
return phase_ >= Phase::kOpening && phase_ <= Phase::kClosing;
}
void AddListener(WindowListener* listener);
void RemoveListener(WindowListener* listener);
void AddInputListener(WindowInputListener* listener, size_t z_order);
void RemoveInputListener(WindowInputListener* listener);
// `false` is returned only in case of an error while trying to perform the
// platform window opening. If the window is already open, or just can't be
// reopened in the current phase (as in this case it's assumed that the outer
// will handle this situation properly and won't, for instance, leave a
// process without windows - it will quit the application in OnBeforeClose of
// the window closing of which was initiated before or even during opening,
// for instance), `true` is returned. The functions of WindowListeners will be
// called only for a newly opened platform window - and the listeners may
// close or even destroy the window, in which case this function will still
// return `true` to differentiate from an actual error - if it's really
// necessary that the platform window is open after the call, check phase()
// after calling.
bool Open();
// The call may or may not close the window immediately, depending on the
// platform (phase() may still return an open phase, and events may still be
// sent after the call). Use phase() to check if closing has actually
// happened immediately.
void RequestClose() {
// Don't allow external close requests during opening for state consistency
// inside OpenImpl (if an internal close happens during OpenImpl, the
// implementation will be aware of that at least), and don't allow closing
// twice.
if (phase_ != Phase::kOpen) {
return;
}
RequestCloseImpl();
// Must not doing anything else with *this as callbacks might have been
// triggered during closing (if it has actually even happened), and the
// Window might have been deleted.
}
// The `public` state setters are for calling from outside.
// The implementation must use the public getters to obtain the desired state
// while applying, but for updating the actual state, or for overriding the
// desired state, the `protected` On*Update functions must be used (overall
// the On* functions are for the implementation's feedback).
virtual uint32_t GetMediumDpi() const { return 96; }
uint32_t GetDpi() const {
uint32_t dpi = GetLatestDpiImpl();
return dpi ? dpi : GetMediumDpi();
}
// Round trips are not guaranteed to return the same results.
static constexpr uint32_t ConvertSizeDpi(uint32_t size, uint32_t new_dpi, uint32_t old_dpi) {
// Always rounding up to prevent zero sizes (unless the input is zero) as
// well as gaps at the edge.
return uint32_t((uint64_t(size) * new_dpi + (old_dpi - 1)) / old_dpi);
}
uint32_t SizeToLogical(uint32_t size) const {
return ConvertSizeDpi(size, GetMediumDpi(), GetDpi());
}
uint32_t SizeToPhysical(uint32_t size) const {
return ConvertSizeDpi(size, GetDpi(), GetMediumDpi());
}
static constexpr int32_t ConvertPositionDpi(int32_t position, uint32_t new_dpi,
uint32_t old_dpi) {
// Rounding to the nearest mostly similar to Windows MulDiv.
// Plus old_dpi / 2 for positive values, minus old_dpi / 2 for negative
// values for consistent rounding for both positive and negative values (as
// the `/` operator rounds towards zero).
// Example:
// (-3 - 1) / 3 == -1
// (-2 - 1) / 3 == -1
// (-1 - 1) / 3 == 0
// ---
// (0 + 1) / 3 == 0
// (1 + 1) / 3 == 0
// (2 + 1) / 3 == 1
return int32_t((int64_t(position) * new_dpi + int32_t(old_dpi >> 1) * (position < 0 ? -1 : 1)) /
old_dpi);
}
int32_t PositionToLogical(int32_t position) const {
return ConvertPositionDpi(position, GetMediumDpi(), GetDpi());
}
int32_t PositionToPhysical(int32_t position) const {
return ConvertPositionDpi(position, GetDpi(), GetMediumDpi());
}
// The desired logical size of the window when it's not maximized, regardless
// of the current state of the window (maximized, fullscreen, etc.)
// The implementation may update it, for instance, to clamp it, or when the
// user resizes a non-maximized window.
uint32_t GetDesiredLogicalWidth() const { return desired_logical_width_; }
uint32_t GetDesiredLogicalHeight() const { return desired_logical_height_; }
// 0 width or height may be returned even in case of an open window with a
// valid non-zero-area surface depending on the platform.
uint32_t GetActualPhysicalWidth() const { return HasActualState() ? actual_physical_width_ : 0; }
uint32_t GetActualPhysicalHeight() const {
return HasActualState() ? actual_physical_height_ : 0;
}
uint32_t GetActualLogicalWidth() const { return SizeToLogical(GetActualPhysicalWidth()); }
uint32_t GetActualLogicalHeight() const { return SizeToLogical(GetActualPhysicalHeight()); }
// Desired state stored by the common Window, modifiable both externally and
// by the implementation (including from SetFullscreen itself).
bool IsFullscreen() const { return fullscreen_; }
void SetFullscreen(bool new_fullscreen);
// Desired state stored by the common Window, externally modifiable, read-only
// in the implementation.
const std::string& GetTitle() const { return title_; }
void SetTitle(const std::string_view new_title);
// Desired state stored in a platform-dependent way in the implementation,
// externally modifiable, read-only by the implementation unless from the
// LoadAndApplyIcon implementation. The icon is in Windows .ico format.
// Provide null buffer and / or zero size to reset the icon.
void SetIcon(const void* buffer, size_t size);
void ResetIcon() { SetIcon(nullptr, 0); }
// Desired state stored by the common Window, externally modifiable, read-only
// in the implementation.
void SetMainMenu(std::unique_ptr<MenuItem> new_main_menu);
void CompleteMainMenuItemsUpdate();
void SetMainMenuEnabled(bool enabled);
// Desired state stored by the common Window, externally modifiable, read-only
// in the implementation.
bool IsMouseCaptureRequested() const { return mouse_capture_request_count_ != 0; }
void CaptureMouse();
void ReleaseMouse();
// Desired state stored by the common Window, externally modifiable, read-only
// in the implementation.
CursorVisibility GetCursorVisibility() const { return cursor_visibility_; }
// Setting this to kAutoHidden from any _other_ visibility should hide the
// cursor immediately - for instance, if the external code wants to auto-hide
// the cursor in fullscreen, to allow going into the fullscreen mode to hide
// the cursor instantly.
void SetCursorVisibility(CursorVisibility new_cursor_visibility);
bool HasFocus() const { return HasActualState() ? has_focus_ : false; }
// May be applied in a delayed way or dropped at all, HasFocus will not
// necessarily be true immediately.
void Focus();
// TODO(Triang3l): A resize function, primarily for snapping externally to
// 1280x720, 1920x1080, and other 1:1 resolutions. It will need to resize the
// window (to a desired logical size - the actual physical size is entirely
// the feedback of the implementation) in the normal state, and possibly also
// un-maximize (and possibly un-fullscreen) it (but this choice will possibly
// need to be exposed to the caller). Because it's currently not needed, it's
// not implemented to avoid platform-specific complexities regarding
// maximization, DPI, etc.
void SetPresenter(Presenter* presenter);
// Request repainting of the surface. Can be called from non-UI threads as
// long as they know the Surface exists and isn't in the middle of being
// changed to another (the synchronization of this fact between the UI thread
// and the caller thread must be done externally through OnSurfaceChanged).
void RequestPaint() {
if (presenter_surface_) {
RequestPaintImpl();
}
}
void RequestPresenterUIPaintFromUIThread() {
if (presenter_) {
presenter_->RequestUIPaintFromUIThread();
}
}
protected:
// The receiver, which must never be instantiated in the Window object itself
// (rather, usually it should be created as a local variable, because only
// LIFO-ordered creation and deletion of these is supported), that allows
// deletion of the Window from within an event handler (which may invoke a
// WindowListener, and window listeners are allowed to destroy windows; also
// they may execute, for instance, the functions requested to be executed in
// the UI thread in the WindowedAppContext, which are also allowed to destroy
// windows - because if the former wasn't allowed, the latter would be
// required to destroy windows as a result of UI interaction) to be caught by
// functions inside the Window in order to stop interacting with `*this` and
// returning after this happens.
// Note that the receivers are signaled in the *end* of the destruction of the
// common Window, when truly nothing can be done with it anymore, so it's safe
// to assume that right after the creation of the WindowDestructionReceiver,
// it will still be in an unsignaled state even if it's used somewhere in the
// destructor. The reason is that the users of the WindowDestructionReceiver
// are expected to stop accessing the Window *immediately* once
// IsWindowDestroyed becomes `true`, and to leave it in a potentially
// indeterminate state - but the code executed subsequently in the destructor
// may still use that state meaningfully.
class WindowDestructionReceiver {
public:
explicit WindowDestructionReceiver(Window* window) : window_(window) {
if (window_) {
outer_receiver_ = window_->innermost_destruction_receiver_;
window_->innermost_destruction_receiver_ = this;
}
}
~WindowDestructionReceiver() {
// If the window is not null, removal from the stack must happen
// regardless of `phase_ == Phase::kDeleting`, because the window
// destructor iterates the receivers after EnterDestructor(), and if the
// receiver is not removed in this case, the destructor will do
// use-after-free.
if (window_) {
// Only LIFO order is supported (normally through RAII).
assert_true(window_->innermost_destruction_receiver_ == this);
window_->innermost_destruction_receiver_ = outer_receiver_;
}
}
bool IsWindowDestroyed() const { return window_ == nullptr; }
// Helper functions for common usages of the receiver. Unlike
// IsWindowDestroyed, these, however, may return false immediately on
// creation.
// Primarily for the implementation (most importantly its native event
// handler), to stop interacting with the native window given that it was
// possible before the function call it's guarded with.
bool IsWindowDestroyedOrClosed() const { return IsWindowDestroyed() || window_->IsClosed(); }
// For guarding Apply* calls if one state setter needs to make multiple of
// them (or just detecting if it's okay to call Apply*).
bool IsWindowDestroyedOrStateInapplicable() const {
return IsWindowDestroyed() || !window_->CanApplyState();
}
bool IsWindowDestroyedOrListenersUncallable() const {
return IsWindowDestroyed() || !window_->CanSendEventsToListeners();
}
private:
// The Window must set window_ to nullptr in its destructor.
friend Window;
Window* window_;
WindowDestructionReceiver* outer_receiver_ = nullptr;
};
// Like in the Windows Media Player.
// A more modern Windows example, Movies & TV in Windows 11 21H2, has 5000,
// but it's too long especially for highly dynamic games.
// Implementations may use different values according to the platform's UX
// conventions.
static constexpr uint32_t kDefaultCursorAutoHideMilliseconds = 3333;
Window(WindowedAppContext& app_context, const std::string_view title,
uint32_t desired_logical_width, uint32_t desired_logical_height);
// If implementation-specific destruction happens, should be called in the
// beginning of the implementation's destructor so the implementation can
// destroy the platform window without doing something unsafe in destruction.
void EnterDestructor() {
phase_ = Phase::kDeleting;
// Disconnect from the surface before destroying the window behind it.
OnSurfaceChanged(false);
}
// For an open window, the implementation should return the current DPI for
// the window. For a non-open one, it should be the closest approximation,
// such as the last DPI from an existing window, the system DPI, or just the
// medium DPI (0 returned from it will also be treated as medium DPI).
virtual uint32_t GetLatestDpiImpl() const { return GetMediumDpi(); }
// Deletion of the window may (and must) not happen in OpenImpl, the listeners
// are deferred, so there's no need to use WindowDestructionReceiver in it.
// In case of failure, the implementation must not leave itself in an
// indeterminate state, so another attempt to open the window can be made.
// The implementation must apply the following desired state if it needs it,
// directly (not via Set* methods as they will be dropped during OpenImpl
// since the window is not fully open yet):
// - Title (GetTitle()).
// - Icon (from the last LoadAndApplyIcon call).
// - Main menu (GetMainMenu()) and its enablement.
// - Desired logical size (GetDesiredLogicalWidth() / Height(), taking into
// account that the main menu, during the initial opening, should not be
// included in this size - it specifies the client area that painting will
// be done to), within the capabilities of the platform (may be clamped by
// the OS, for instance - in this case, OnDesiredLogicalSizeUpdate may be
// called from within OpenImpl to store the clamped size for later).
// - Fullscreen (GetFullscreen()) - however, if possible, the calculations
// that would normally be done for a non-fullscreen window in OpenImpl
// should also be done if entering fullscreen, including the menu-related
// ones - first, the usual windowed geometry calculations should be done,
// and then fullscreen should be entered; but preferably still entering
// fullscreen before actually showing a visible window to the user for a
// seamless transition.
// - Mouse capture (IsMouseCaptureRequested()).
// - Cursor visibility (GetCursorVisibility()).
// Also, as a result of the OpenImpl call, these function should be called
// (immediately from within OpenImpl directly or indirectly through the native
// event handling, or shortly after during the UI main loop) to provide the
// initial actual state to the common Window code so it returns the correct
// values:
// - OnActualSizeUpdate, at least if the window isn't opened as not yet
// visible on screen (otherwise the size will be assumed to be 0x0 until the
// next size update caused by something likely requiring user interaction).
// - OnFocusUpdate, at least if the focus has been obtained (otherwise the
// window will be assumed to be not in focus until it goes into the focus
// again).
// Also, if some of the desired state that may be updated by the
// implementation could not be applied (such as the fullscreen mode), and
// certain values of the implementation state are either normally
// differentiated within the implementation or are just meaningful considering
// the platform's defaults (for instance, the platform inherently supports
// only fullscreen, possibly doesn't have a concept of windows at all), it's
// recommended to call the appropriate On*Update functions to update the
// desired state to the actual one.
virtual bool OpenImpl() = 0;
virtual void RequestCloseImpl() = 0;
// Apply* are called only if CanApplyState() is true (unless the function
// should do more than just applying, such as also updating the desired state
// in a platform-dependent way - see each individual function).
// ApplyNew* means that the value has actually been changed to something
// different than it was previously.
virtual void ApplyNewFullscreen() {}
virtual void ApplyNewTitle() {}
// can_apply_state_in_current_phase whether the window is in a life cycle
// phase that would normally accept Apply calls (the native window surely
// exists), since this function may be called in closed states too to update
// the desired icon (since it's stored in the implementation) - the
// implementation may, however, ignore it and use a more granular check of the
// existence of the native window and the safety of updating the icon for
// better internal state consistency. The icon is in Windows .ico format. If
// the buffer is null or the size is 0, the icon should be reset to the
// default one. Returns whether the icon has been updated successfully.
virtual void LoadAndApplyIcon(const void* buffer, size_t size,
bool can_apply_state_in_current_phase) {
(void)buffer;
(void)size;
(void)can_apply_state_in_current_phase;
}
MenuItem* GetMainMenu() const { return main_menu_.get(); }
// May be called to add, replace or remove the main menu.
virtual void ApplyNewMainMenu(MenuItem* old_main_menu) { (void)old_main_menu; }
// If there's main menu, and state can be applied, will be called to make the
// implementation's state consistent with the new state of the MenuItems of
// the main menu after changes have been made to them.
virtual void CompleteMainMenuItemsUpdateImpl() {}
// Will be called even if capturing while the mouse is already assumed to be
// captured, in case something has released it in the OS.
virtual void ApplyNewMouseCapture() {}
virtual void ApplyNewMouseRelease() {}
virtual void ApplyNewCursorVisibility(CursorVisibility old_cursor_visibility) {
(void)old_cursor_visibility;
}
// If state can be applied, this is called to request bringing the window into
// focus (and once that's done by the OS, update the actual focus state). Does
// nothing otherwise (focus can't be requested before the window is open, a
// closed window is always assumed to be not in focus).
virtual void FocusImpl() {}
Presenter* presenter() const { return presenter_; }
bool HasSurface() const { return presenter_surface_ != nullptr; }
// If new_surface_potentially_exists is false, creation of the new surface for
// the window won't be updated, and it may be called from the destructor (via
// EnterDestructor to destroy the surface before destroying what it depends
// on) as no virtual functions (including CreateSurface) will be called.
// This function is nonvirtual itself for this reason as well.
void OnSurfaceChanged(bool new_surface_potentially_exists);
// Called only for an open window.
virtual std::unique_ptr<Surface> CreateSurfaceImpl(Surface::TypeFlags allowed_types) = 0;
// Called only if the Surface exists.
virtual void RequestPaintImpl() = 0;
// Will also disconnect the surface if needed.
void OnBeforeClose(WindowDestructionReceiver& destruction_receiver);
void OnAfterClose();
// These functions may usually also be called as part of the opening process
// from within OpenImpl (directly or through the platform event handler
// invoked during it) to actualize the state for the newly createad window,
// especially if it's different than the desired one.
void OnDpiChanged(UISetupEvent& e, WindowDestructionReceiver& destruction_receiver);
void OnMonitorUpdate(MonitorUpdateEvent& e);
// For calling when the platform changes something in the non-maximized,
// non-fullscreen size of the window.
void OnDesiredLogicalSizeUpdate(uint32_t new_desired_logical_width,
uint32_t new_desired_logical_height) {
desired_logical_width_ = new_desired_logical_width;
desired_logical_height_ = new_desired_logical_height;
}
// If the size of the client area is the same as the currently assumed one
// (the desired / last size for a newly opened / reopened window, the last
// actual size for an already open window), does nothing and returns false.
// Otherwise, updates the size, notifies what depends on the size about the
// change, and returns true. Not storing the new size in the UISetupEvent
// because a resize listener may request another resize, in which case it will
// be outdated - listeners must query the new physical size from the window
// explicitly.
bool OnActualSizeUpdate(uint32_t new_physical_width, uint32_t new_physical_height,
WindowDestructionReceiver& destruction_receiver);
void OnDesiredFullscreenUpdate(bool new_fullscreen) { fullscreen_ = new_fullscreen; }
void OnFocusUpdate(bool new_has_focus, WindowDestructionReceiver& destruction_receiver);
// Pass true as force_paint in case the platform can't retain the image from
// the previous paint so it won't be skipped if there are no content updates.
void OnPaint(bool force_paint = false);
void OnFileDrop(FileDropEvent& e, WindowDestructionReceiver& destruction_receiver);
void OnKeyDown(KeyEvent& e, WindowDestructionReceiver& destruction_receiver);
void OnKeyUp(KeyEvent& e, WindowDestructionReceiver& destruction_receiver);
void OnKeyChar(KeyEvent& e, WindowDestructionReceiver& destruction_receiver);
void OnMouseDown(MouseEvent& e, WindowDestructionReceiver& destruction_receiver);
void OnMouseMove(MouseEvent& e, WindowDestructionReceiver& destruction_receiver);
void OnMouseUp(MouseEvent& e, WindowDestructionReceiver& destruction_receiver);
void OnMouseWheel(MouseEvent& e, WindowDestructionReceiver& destruction_receiver);
void OnTouchEvent(TouchEvent& e, WindowDestructionReceiver& destruction_receiver);
private:
struct ListenerIterationContext {
explicit ListenerIterationContext(ListenerIterationContext* outer_context,
size_t first_index = 0)
: outer_context(outer_context), next_index(first_index) {}
// To support nested listener calls, in case a listener does some
// interaction with the window that results in more events being triggered
// (such as calling Windows API functions that return a result from a
// message handled by a window, rather that simply enqueueing the message).
ListenerIterationContext* outer_context;
// Using indices, not iterators, because after the erasure, the adjustment
// must be done for the vector element indices that would be in the iterator
// range that would be invalidated.
size_t next_index;
};
struct InputListenerIterationContext {
explicit InputListenerIterationContext(
InputListenerIterationContext* outer_context,
std::multimap<size_t, WindowInputListener*>::const_reverse_iterator first_iterator,
size_t first_z_order = SIZE_MAX)
: outer_context(outer_context),
next_iterator(first_iterator),
current_z_order(first_z_order) {}
// To support nested listener calls.
InputListenerIterationContext* outer_context;
// Reverse iterator because input handlers with a higher Z order index may
// correspond to what's displayed on top of what has a lower Z order index,
// so what's higher may consum the event.
std::multimap<size_t, WindowInputListener*>::const_reverse_iterator next_iterator;
size_t current_z_order;
};
// If the window is closed, the platform native window is either being
// destroyed, or doesn't exist anymore, and thus it's in a non-interactive
// state.
bool IsClosed() const { return phase_ < Phase::kOpening || phase_ > Phase::kOpenBeforeClosing; }
bool CanApplyState() const {
// In kOpening, OpenImpl itself pulls the desired state itself and applies
// it, the Apply* functions can't be called and are unsafe to call because
// the implementation is an incomplete state, with the platform window
// potentially not existing. In kOpenBeforeClosing, as the native window
// still hasn't been destroyed and it can receive new state, still allowing
// applying new state for more consistency between the desired and the
// actual state during the final listener invocation.
return phase_ >= Phase::kOpen && phase_ <= Phase::kOpenBeforeClosing;
}
bool CanSendEventsToListeners() const {
return phase_ >= Phase::kOpen && phase_ <= Phase::kOpenBeforeClosing;
}
// The listeners may delete the Window - check the destruction receiver after
// calling and stop doing anything accessing *this if that happens.
void SendEventToListeners(std::function<void(WindowListener*)> fn,
WindowDestructionReceiver& destruction_receiver);
void PropagateEventThroughInputListeners(std::function<bool(WindowInputListener*)> fn,
WindowDestructionReceiver& destruction_receiver);
std::unique_ptr<Surface> CreateSurface(Surface::TypeFlags allowed_types) {
// If opening, surface creation is deferred until all the initial setup has
// completed. Destruction of the surface is also a part of the closing
// process in OnBeforeClose.
if (phase_ != Phase::kOpen) {
return nullptr;
}
return CreateSurfaceImpl(allowed_types);
}
WindowedAppContext& app_context_;
Phase phase_ = Phase::kClosedOpenable;
WindowDestructionReceiver* innermost_destruction_receiver_ = nullptr;
// All currently-attached listeners that get event notifications.
std::vector<WindowListener*> listeners_;
// Ordered by the Z order, and then by the time of addition (but executed in
// reverse order).
// Note: All the iteration logic involving this Z ordering must be the same as
// in drawing (in the UI drawers in the Presenter), but in reverse.
std::multimap<size_t, WindowInputListener*> input_listeners_;
// Linked list-based stacks of the contexts of the listener iterations
// currently being done, usually allocated on the stack.
ListenerIterationContext* innermost_listener_iteration_context_ = nullptr;
InputListenerIterationContext* innermost_input_listener_iteration_context_ = nullptr;
uint32_t desired_logical_width_ = 0;
uint32_t desired_logical_height_ = 0;
// Set by the implementation via OnActualSizeUpdate (from OpenImpl or from the
// platform resize handler).
uint32_t actual_physical_width_ = 0;
uint32_t actual_physical_height_ = 0;
bool fullscreen_ = false;
std::string title_;
std::unique_ptr<MenuItem> main_menu_;
uint32_t mouse_capture_request_count_ = 0;
CursorVisibility cursor_visibility_ = CursorVisibility::kVisible;
bool has_focus_ = false;
Presenter* presenter_ = nullptr;
std::unique_ptr<Surface> presenter_surface_;
// Whether currently in InPaint to prevent recursive painting in case it's
// triggered somehow from within painting again, because painting is much more
// complex than just a small state update, and recursive painting is
// completely unsupported by the Presenter.
bool is_painting_ = false;
};
} // namespace ui
} // namespace rex