mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-03 03:31:04 +03:00
feat(ui): add native multi-window application support
- let UIApplication create, manage, render, and destroy multiple native windows - add scoped scene and GL window-context switching - route input events to the scene belonging to each native window - share application fonts, themes, icons, styles, and graphics resources safely - add NewInApplicationWindow factories for UIWindow, UIFileDialog, and UIMessageBox - support native parent relationships, modality, minimum sizes, and optional positioning - close child windows when the primary application window closes - defer native context destruction until the end of the application tick - fall back to in-scene windows for Emscripten and terminal rendering - reapply renderer state caches when switching OpenGL contexts - fix SDL2/SDL3 display bounds and multi-window lifecycle handling - add a native file-dialog and message-box multi-window example - add integration coverage for context switching, input, resources, modality, and closing
This commit is contained in:
@@ -411,6 +411,12 @@
|
|||||||
"command": "${project_root}/bin/eepp-ui-data-handling-debug",
|
"command": "${project_root}/bin/eepp-ui-data-handling-debug",
|
||||||
"name": "eepp-ui-data-handling-debug",
|
"name": "eepp-ui-data-handling-debug",
|
||||||
"working_dir": "${project_root}/bin"
|
"working_dir": "${project_root}/bin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"args": "",
|
||||||
|
"command": "${project_root}/bin/eepp-ui-application-multi-window-debug",
|
||||||
|
"name": "eepp-ui-application-multi-window-debug",
|
||||||
|
"working_dir": "${project_root}/bin"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"var": {
|
"var": {
|
||||||
|
|||||||
@@ -170,6 +170,9 @@ class EE_API Renderer {
|
|||||||
|
|
||||||
virtual void enable( unsigned int cap );
|
virtual void enable( unsigned int cap );
|
||||||
|
|
||||||
|
/** Reapplies renderer state cached outside OpenGL after changing the current context. */
|
||||||
|
virtual void onContextChanged();
|
||||||
|
|
||||||
virtual GraphicsLibraryVersion version() = 0;
|
virtual GraphicsLibraryVersion version() = 0;
|
||||||
|
|
||||||
virtual std::string versionStr() = 0;
|
virtual std::string versionStr() = 0;
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ class EE_API RendererGL3 : public RendererGLShader {
|
|||||||
|
|
||||||
void enable( unsigned int cap );
|
void enable( unsigned int cap );
|
||||||
|
|
||||||
|
void onContextChanged();
|
||||||
|
|
||||||
void enableClientState( unsigned int array );
|
void enableClientState( unsigned int array );
|
||||||
|
|
||||||
void disableClientState( unsigned int array );
|
void disableClientState( unsigned int array );
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ class EE_API RendererGL3CP : public RendererGLShader {
|
|||||||
|
|
||||||
void enable( unsigned int cap );
|
void enable( unsigned int cap );
|
||||||
|
|
||||||
|
void onContextChanged();
|
||||||
|
|
||||||
void enableClientState( unsigned int array );
|
void enableClientState( unsigned int array );
|
||||||
|
|
||||||
void disableClientState( unsigned int array );
|
void disableClientState( unsigned int array );
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ class EE_API RendererGLES2 : public RendererGLShader {
|
|||||||
|
|
||||||
void enable( unsigned int cap );
|
void enable( unsigned int cap );
|
||||||
|
|
||||||
|
void onContextChanged();
|
||||||
|
|
||||||
void enableClientState( unsigned int array );
|
void enableClientState( unsigned int array );
|
||||||
|
|
||||||
void disableClientState( unsigned int array );
|
void disableClientState( unsigned int array );
|
||||||
|
|||||||
@@ -172,6 +172,9 @@ class EE_API TextureFactory : protected Mutex {
|
|||||||
*/
|
*/
|
||||||
void setCurrentTexture( const int& textureHandle, const Uint32& TextureUnit );
|
void setCurrentTexture( const int& textureHandle, const Uint32& TextureUnit );
|
||||||
|
|
||||||
|
/** Invalidates cached OpenGL binding state after changing the current graphics context. */
|
||||||
|
void invalidateTextureBindings();
|
||||||
|
|
||||||
/** Returns the number of currently live textures. */
|
/** Returns the number of currently live textures. */
|
||||||
Uint32 getTextureCount();
|
Uint32 getTextureCount();
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ using namespace EE::System;
|
|||||||
namespace EE { namespace UI {
|
namespace EE { namespace UI {
|
||||||
class UISceneNode;
|
class UISceneNode;
|
||||||
}} // namespace EE::UI
|
}} // namespace EE::UI
|
||||||
|
namespace EE { namespace Window {
|
||||||
|
class Window;
|
||||||
|
}} // namespace EE::Window
|
||||||
using namespace EE::UI;
|
using namespace EE::UI;
|
||||||
|
|
||||||
namespace EE { namespace Scene {
|
namespace EE { namespace Scene {
|
||||||
@@ -33,19 +36,35 @@ class EE_API SceneManager {
|
|||||||
|
|
||||||
void draw();
|
void draw();
|
||||||
|
|
||||||
|
/** Draws only scene nodes associated with @p window using its graphics context. */
|
||||||
|
void draw( EE::Window::Window* window );
|
||||||
|
|
||||||
void update( const Time& elapsed );
|
void update( const Time& elapsed );
|
||||||
|
|
||||||
void update();
|
void update();
|
||||||
|
|
||||||
UISceneNode* getUISceneNode();
|
UISceneNode* getUISceneNode();
|
||||||
|
|
||||||
|
/** Returns the UI scene associated with @p window, or nullptr if none is registered. */
|
||||||
|
UISceneNode* getUISceneNode( EE::Window::Window* window );
|
||||||
|
|
||||||
void setCurrentUISceneNode( UISceneNode* uiSceneNode );
|
void setCurrentUISceneNode( UISceneNode* uiSceneNode );
|
||||||
|
|
||||||
|
/** Removes and destroys all registered scene nodes associated with @p window. */
|
||||||
|
void destroyScenes( EE::Window::Window* window );
|
||||||
|
|
||||||
Time getElapsed() const;
|
Time getElapsed() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
friend class EE::UI::UISceneNode;
|
||||||
|
|
||||||
|
/** Installs the ambient UI scene for the current scope and returns the previously scoped scene.
|
||||||
|
* UI scene and graphics-context switching is restricted to the application's UI thread. */
|
||||||
|
UISceneNode* setScopedUISceneNode( UISceneNode* uiSceneNode );
|
||||||
|
|
||||||
Clock mClock;
|
Clock mClock;
|
||||||
UISceneNode* mUISceneNode;
|
UISceneNode* mUISceneNode;
|
||||||
|
UISceneNode* mScopedUISceneNode;
|
||||||
std::vector<SceneNode*> mSceneNodes;
|
std::vector<SceneNode*> mSceneNodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
#define EE_UI_UIAPPLICATION
|
#define EE_UI_UIAPPLICATION
|
||||||
|
|
||||||
#include <eepp/graphics/font.hpp>
|
#include <eepp/graphics/font.hpp>
|
||||||
|
#include <eepp/system/clock.hpp>
|
||||||
#include <eepp/window/window.hpp>
|
#include <eepp/window/window.hpp>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
using namespace EE::Window;
|
using namespace EE::Window;
|
||||||
|
|
||||||
@@ -19,6 +21,17 @@ class UIApplicationSystemFontState;
|
|||||||
|
|
||||||
class EE_API UIApplication {
|
class EE_API UIApplication {
|
||||||
public:
|
public:
|
||||||
|
/** Controls when UIApplication stops its main loop.
|
||||||
|
* @see setQuitPolicy() */
|
||||||
|
enum class QuitPolicy : Uint8 {
|
||||||
|
/** Stops after the last application-owned window closes. */
|
||||||
|
OnLastWindowClosed,
|
||||||
|
/** Stops when the primary window closes and closes all secondary windows. */
|
||||||
|
OnPrimaryWindowClosed,
|
||||||
|
/** Window closure never stops the loop; requestQuit() must be called explicitly. */
|
||||||
|
Explicit
|
||||||
|
};
|
||||||
|
|
||||||
struct EE_API Settings {
|
struct EE_API Settings {
|
||||||
Settings() {}
|
Settings() {}
|
||||||
|
|
||||||
@@ -35,6 +48,9 @@ class EE_API UIApplication {
|
|||||||
//! Must be set to true in order to initialize the basic UI resources (font and UI theme).
|
//! Must be set to true in order to initialize the basic UI resources (font and UI theme).
|
||||||
//! Otherwise it will initialize with an empty UI scene node
|
//! Otherwise it will initialize with an empty UI scene node
|
||||||
bool loadBaseResources{ true };
|
bool loadBaseResources{ true };
|
||||||
|
//! Loads the bundled icon fonts and initializes IconManager when base resources are
|
||||||
|
//! enabled.
|
||||||
|
bool loadIconResources{ true };
|
||||||
//! The default base font for the UI. If not provided it will load NotoSans-Regular ( will
|
//! The default base font for the UI. If not provided it will load NotoSans-Regular ( will
|
||||||
//! look at "assets/fonts/NotoSans-Regular.ttf" )
|
//! look at "assets/fonts/NotoSans-Regular.ttf" )
|
||||||
Font* baseFont{ nullptr };
|
Font* baseFont{ nullptr };
|
||||||
@@ -73,6 +89,39 @@ class EE_API UIApplication {
|
|||||||
//! Document
|
//! Document
|
||||||
UISceneNode* getUI() const;
|
UISceneNode* getUI() const;
|
||||||
|
|
||||||
|
/** Creates a secondary application-owned native window and its UISceneNode. The new scene
|
||||||
|
* inherits the primary scene's font, theme, icon theme, stylesheet, and rendering policies.
|
||||||
|
* The returned scene remains owned by UIApplication.
|
||||||
|
* @return The new UI scene, or nullptr if native window creation failed. */
|
||||||
|
UISceneNode* createWindow( const WindowSettings& windowSettings,
|
||||||
|
const ContextSettings& contextSettings = ContextSettings() );
|
||||||
|
|
||||||
|
/** Returns the application-owned UI scene associated with @p window, or nullptr when the
|
||||||
|
* window is not owned by this application. */
|
||||||
|
UISceneNode* getUI( EE::Window::Window* window ) const;
|
||||||
|
|
||||||
|
/** Returns the number of application-owned windows pending or participating in the loop. */
|
||||||
|
size_t getWindowCount() const;
|
||||||
|
|
||||||
|
/** Requests destruction of an application-owned window. The native window is hidden
|
||||||
|
* immediately and its scene and backend resources are destroyed at the safe point at the end
|
||||||
|
* of the current frame. */
|
||||||
|
void closeWindow( EE::Window::Window* window );
|
||||||
|
|
||||||
|
/** Requests termination of the application loop without changing the open state of its
|
||||||
|
* windows. Backend resources are released by UIApplication destruction. */
|
||||||
|
void requestQuit();
|
||||||
|
|
||||||
|
/** Returns whether the application main loop is currently running. */
|
||||||
|
bool isRunning() const;
|
||||||
|
|
||||||
|
/** Sets the condition under which window closure stops the application loop. The default is
|
||||||
|
* QuitPolicy::OnPrimaryWindowClosed. */
|
||||||
|
void setQuitPolicy( QuitPolicy policy );
|
||||||
|
|
||||||
|
/** Returns the current window-close policy. */
|
||||||
|
QuitPolicy getQuitPolicy() const;
|
||||||
|
|
||||||
//! Runs the application until window is closed
|
//! Runs the application until window is closed
|
||||||
//! @return EXIT_SUCCESS if application run successfully
|
//! @return EXIT_SUCCESS if application run successfully
|
||||||
int run();
|
int run();
|
||||||
@@ -91,11 +140,41 @@ class EE_API UIApplication {
|
|||||||
String::HashType getStyleSheetDefaultMarker() const { return mStyleSheetMarker; }
|
String::HashType getStyleSheetDefaultMarker() const { return mStyleSheetMarker; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
/** Associates an application-owned native window with its UI scene and deferred-destruction
|
||||||
|
* state. */
|
||||||
|
struct WindowEntry {
|
||||||
|
EE::Window::Window* window{ nullptr };
|
||||||
|
UISceneNode* ui{ nullptr };
|
||||||
|
bool primary{ false };
|
||||||
|
bool pendingDestroy{ false };
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Shared primary/secondary window creation implementation. */
|
||||||
|
WindowEntry* createWindowInternal( const WindowSettings& windowSettings,
|
||||||
|
const ContextSettings& contextSettings, bool primary );
|
||||||
|
|
||||||
|
/** Applies application-level resources and rendering policies to a newly created UI scene. */
|
||||||
|
void configureUIScene( UISceneNode* ui );
|
||||||
|
|
||||||
|
/** Processes one application frame across all open windows. */
|
||||||
|
void tick();
|
||||||
|
|
||||||
|
/** Marks windows closed externally for destruction and applies the active quit policy. */
|
||||||
|
void processClosedWindows();
|
||||||
|
|
||||||
|
/** Destroys scenes and native windows previously marked for deferred destruction. */
|
||||||
|
void processPendingWindowDestruction();
|
||||||
|
|
||||||
UISceneNode* mUISceneNode{ nullptr };
|
UISceneNode* mUISceneNode{ nullptr };
|
||||||
EE::Window::Window* mWindow{ nullptr };
|
EE::Window::Window* mWindow{ nullptr };
|
||||||
String::HashType mStyleSheetMarker{ 0 };
|
String::HashType mStyleSheetMarker{ 0 };
|
||||||
bool mDidRun{ false };
|
bool mDidRun{ false };
|
||||||
bool mShowMemoryManagerResult{ false };
|
bool mShowMemoryManagerResult{ false };
|
||||||
|
bool mRunning{ false };
|
||||||
|
QuitPolicy mQuitPolicy{ QuitPolicy::OnPrimaryWindowClosed };
|
||||||
|
std::vector<WindowEntry> mWindows;
|
||||||
|
Settings mSettings;
|
||||||
|
System::Clock mFrameClock;
|
||||||
std::unique_ptr<Private::UIApplicationSystemFontState> mSystemFontState;
|
std::unique_ptr<Private::UIApplicationSystemFontState> mSystemFontState;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
namespace EE { namespace UI {
|
namespace EE { namespace UI {
|
||||||
|
|
||||||
struct NativeFileDialogHandler;
|
struct NativeFileDialogHandler;
|
||||||
|
class UIApplication;
|
||||||
|
|
||||||
class EE_API UIFileDialog : public UIWindow {
|
class EE_API UIFileDialog : public UIWindow {
|
||||||
public:
|
public:
|
||||||
@@ -39,6 +40,19 @@ class EE_API UIFileDialog : public UIWindow {
|
|||||||
const std::string& defaultFilePattern = "*",
|
const std::string& defaultFilePattern = "*",
|
||||||
const std::string& defaultDirectory = Sys::getProcessPath() );
|
const std::string& defaultDirectory = Sys::getProcessPath() );
|
||||||
|
|
||||||
|
/** Creates an eepp file dialog that fills a new native UIApplication window. The dialog uses
|
||||||
|
* the eepp file browser while its host uses the platform window decoration. UseNativeFileDialog
|
||||||
|
* is ignored by this factory. Closing either dialog closes the application window. Native
|
||||||
|
* placement is left to the window manager by default. On Emscripten and in terminal runtime it
|
||||||
|
* creates a decorated in-application dialog instead of a native window. */
|
||||||
|
static UIFileDialog* NewInApplicationWindow(
|
||||||
|
UIApplication& application, const WindowSettings& windowSettings,
|
||||||
|
Uint32 dialogFlags = UIFileDialog::DefaultFlags,
|
||||||
|
const std::string& defaultFilePattern = "*",
|
||||||
|
const std::string& defaultDirectory = Sys::getProcessPath(),
|
||||||
|
const ContextSettings& contextSettings = ContextSettings(), bool modal = true,
|
||||||
|
ApplicationWindowPosition position = ApplicationWindowPosition::WindowManager );
|
||||||
|
|
||||||
virtual ~UIFileDialog();
|
virtual ~UIFileDialog();
|
||||||
|
|
||||||
virtual Uint32 getType() const;
|
virtual Uint32 getType() const;
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ class EE_API UIIconThemeManager {
|
|||||||
|
|
||||||
UIIconTheme* getCurrentTheme() const;
|
UIIconTheme* getCurrentTheme() const;
|
||||||
|
|
||||||
|
/** Returns a retaining handle to the current icon theme, or an empty handle if no owned theme
|
||||||
|
* is current. */
|
||||||
|
UIIconThemePtr getCurrentThemeHandle() const;
|
||||||
|
|
||||||
UIIconThemeManager* setCurrentTheme( UIIconThemePtr currentTheme );
|
UIIconThemeManager* setCurrentTheme( UIIconThemePtr currentTheme );
|
||||||
|
|
||||||
UIIconTheme* getFallbackTheme() const;
|
UIIconTheme* getFallbackTheme() const;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#define EE_UICUIMESSAGEBOX_HPP
|
#define EE_UICUIMESSAGEBOX_HPP
|
||||||
|
|
||||||
#include <eepp/ui/uiwindow.hpp>
|
#include <eepp/ui/uiwindow.hpp>
|
||||||
|
#include <eepp/window/window.hpp>
|
||||||
|
|
||||||
namespace EE { namespace UI {
|
namespace EE { namespace UI {
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ class UILayout;
|
|||||||
class UIPushButton;
|
class UIPushButton;
|
||||||
class UIDropDownList;
|
class UIDropDownList;
|
||||||
class UIComboBox;
|
class UIComboBox;
|
||||||
|
class UIApplication;
|
||||||
|
|
||||||
#define UI_MESSAGE_BOX_DEFAULT_FLAGS \
|
#define UI_MESSAGE_BOX_DEFAULT_FLAGS \
|
||||||
UI_WIN_CLOSE_BUTTON | UI_WIN_USE_DEFAULT_BUTTONS_ACTIONS | UI_WIN_MODAL | \
|
UI_WIN_CLOSE_BUTTON | UI_WIN_USE_DEFAULT_BUTTONS_ACTIONS | UI_WIN_MODAL | \
|
||||||
@@ -23,6 +25,16 @@ class EE_API UIMessageBox : public UIWindow {
|
|||||||
static UIMessageBox* New( const Type& type, const String& message,
|
static UIMessageBox* New( const Type& type, const String& message,
|
||||||
const Uint32& windowFlags = UI_MESSAGE_BOX_DEFAULT_FLAGS );
|
const Uint32& windowFlags = UI_MESSAGE_BOX_DEFAULT_FLAGS );
|
||||||
|
|
||||||
|
/** Creates an eepp message box in a separate native UIApplication window. On Emscripten and in
|
||||||
|
* terminal runtime it creates a decorated in-application message box instead. */
|
||||||
|
static UIMessageBox* NewInApplicationWindow(
|
||||||
|
UIApplication& application, const EE::Window::WindowSettings& windowSettings,
|
||||||
|
const Type& type, const String& message,
|
||||||
|
const Uint32& windowFlags = UI_MESSAGE_BOX_DEFAULT_FLAGS,
|
||||||
|
const EE::Window::ContextSettings& contextSettings = EE::Window::ContextSettings(),
|
||||||
|
bool modal = true,
|
||||||
|
ApplicationWindowPosition position = ApplicationWindowPosition::WindowManager );
|
||||||
|
|
||||||
virtual ~UIMessageBox();
|
virtual ~UIMessageBox();
|
||||||
|
|
||||||
virtual void setTheme( UITheme* theme );
|
virtual void setTheme( UITheme* theme );
|
||||||
@@ -69,7 +81,6 @@ class EE_API UIMessageBox : public UIWindow {
|
|||||||
virtual Uint32 onKeyUp( const KeyEvent& event );
|
virtual Uint32 onKeyUp( const KeyEvent& event );
|
||||||
|
|
||||||
virtual Uint32 onMessage( const NodeMessage* Msg );
|
virtual Uint32 onMessage( const NodeMessage* Msg );
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}} // namespace EE::UI
|
}} // namespace EE::UI
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <eepp/ui/keyboardshortcut.hpp>
|
#include <eepp/ui/keyboardshortcut.hpp>
|
||||||
#include <eepp/ui/layoutinvalidation.hpp>
|
#include <eepp/ui/layoutinvalidation.hpp>
|
||||||
#include <eepp/ui/webresourcecache.hpp>
|
#include <eepp/ui/webresourcecache.hpp>
|
||||||
|
#include <eepp/window/engine.hpp>
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@@ -51,6 +52,29 @@ struct NavigationRequest {
|
|||||||
|
|
||||||
class EE_API UISceneNode : public SceneNode {
|
class EE_API UISceneNode : public SceneNode {
|
||||||
public:
|
public:
|
||||||
|
/** Scoped binding of a UI scene and its native graphics context. Restores both previous
|
||||||
|
* bindings when destroyed. Context objects are movable but cannot be copied. */
|
||||||
|
class EE_API Context {
|
||||||
|
public:
|
||||||
|
explicit Context( UISceneNode* scene );
|
||||||
|
|
||||||
|
~Context();
|
||||||
|
|
||||||
|
Context( const Context& ) = delete;
|
||||||
|
Context& operator=( const Context& ) = delete;
|
||||||
|
|
||||||
|
Context( Context&& other ) noexcept;
|
||||||
|
Context& operator=( Context&& ) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
UISceneNode* mPreviousScene{ nullptr };
|
||||||
|
EE::Window::Engine::WindowContext mWindowContext;
|
||||||
|
bool mActive{ true };
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Makes this scene and its native window current for the returned scope. */
|
||||||
|
Context makeCurrent();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Creates a new UISceneNode instance.
|
* @brief Creates a new UISceneNode instance.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <eepp/thirdparty/nlohmann/json_fwd.hpp>
|
#include <eepp/thirdparty/nlohmann/json_fwd.hpp>
|
||||||
#include <eepp/ui/keyboardshortcut.hpp>
|
#include <eepp/ui/keyboardshortcut.hpp>
|
||||||
#include <eepp/ui/uiwidget.hpp>
|
#include <eepp/ui/uiwidget.hpp>
|
||||||
|
#include <eepp/window/window.hpp>
|
||||||
|
|
||||||
namespace EE { namespace Graphics {
|
namespace EE { namespace Graphics {
|
||||||
class FrameBuffer;
|
class FrameBuffer;
|
||||||
@@ -11,6 +12,7 @@ class FrameBuffer;
|
|||||||
|
|
||||||
namespace EE { namespace UI {
|
namespace EE { namespace UI {
|
||||||
|
|
||||||
|
class UIApplication;
|
||||||
class UITextView;
|
class UITextView;
|
||||||
class UISceneNode;
|
class UISceneNode;
|
||||||
|
|
||||||
@@ -61,6 +63,14 @@ class EE_API UIWindow : public UIWidget {
|
|||||||
RELATIVE_LAYOUT
|
RELATIVE_LAYOUT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Selects how a native host created by NewInApplicationWindow() is initially positioned. */
|
||||||
|
enum class ApplicationWindowPosition {
|
||||||
|
/** Leaves placement entirely to the desktop window manager. */
|
||||||
|
WindowManager,
|
||||||
|
/** Centers the host over the primary application window and clamps it to usable bounds. */
|
||||||
|
CenteredOnPrimary
|
||||||
|
};
|
||||||
|
|
||||||
static UIWindow* NewOpt( WindowBaseContainerType type,
|
static UIWindow* NewOpt( WindowBaseContainerType type,
|
||||||
const StyleConfig& windowStyleConfig = StyleConfig() );
|
const StyleConfig& windowStyleConfig = StyleConfig() );
|
||||||
|
|
||||||
@@ -72,6 +82,17 @@ class EE_API UIWindow : public UIWidget {
|
|||||||
|
|
||||||
static UIWindow* NewRelLay();
|
static UIWindow* NewRelLay();
|
||||||
|
|
||||||
|
/** Creates a UIWindow in a native application window. On Emscripten and in terminal runtime
|
||||||
|
* this transparently falls back to a regular in-application UIWindow. Native placement is left
|
||||||
|
* to the window manager unless CenteredOnPrimary is explicitly requested. */
|
||||||
|
static UIWindow* NewInApplicationWindow(
|
||||||
|
UIApplication& application, const EE::Window::WindowSettings& windowSettings,
|
||||||
|
WindowBaseContainerType type = SIMPLE_LAYOUT,
|
||||||
|
const StyleConfig& windowStyleConfig = StyleConfig(),
|
||||||
|
const EE::Window::ContextSettings& contextSettings = EE::Window::ContextSettings(),
|
||||||
|
bool modal = false,
|
||||||
|
ApplicationWindowPosition position = ApplicationWindowPosition::WindowManager );
|
||||||
|
|
||||||
virtual ~UIWindow();
|
virtual ~UIWindow();
|
||||||
|
|
||||||
virtual Uint32 getType() const;
|
virtual Uint32 getType() const;
|
||||||
@@ -210,6 +231,13 @@ class EE_API UIWindow : public UIWidget {
|
|||||||
virtual void unserialize( const nlohmann::json& json );
|
virtual void unserialize( const nlohmann::json& json );
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
/** Shared native-host factory used by UIWindow subclasses. On Emscripten and in terminal
|
||||||
|
* runtime it creates a regular in-scene window instead. */
|
||||||
|
static UIWindow* createInApplicationWindow( UIApplication& application,
|
||||||
|
const EE::Window::WindowSettings& windowSettings,
|
||||||
|
const std::function<UIWindow*()>& windowFactory,
|
||||||
|
const EE::Window::ContextSettings& contextSettings,
|
||||||
|
bool modal, ApplicationWindowPosition position );
|
||||||
enum UI_RESIZE_TYPE {
|
enum UI_RESIZE_TYPE {
|
||||||
RESIZE_NONE,
|
RESIZE_NONE,
|
||||||
RESIZE_LEFT,
|
RESIZE_LEFT,
|
||||||
@@ -256,6 +284,7 @@ class EE_API UIWindow : public UIWidget {
|
|||||||
KeyBindings mKeyBindings;
|
KeyBindings mKeyBindings;
|
||||||
std::map<std::string, KeyBindingCommand> mKeyBindingCommands;
|
std::map<std::string, KeyBindingCommand> mKeyBindingCommands;
|
||||||
std::function<bool( Node* focusNode )> mCheckEphemeralCloseFn;
|
std::function<bool( Node* focusNode )> mCheckEphemeralCloseFn;
|
||||||
|
std::function<void()> mApplicationWindowCloseCallback;
|
||||||
|
|
||||||
explicit UIWindow( WindowBaseContainerType type, const StyleConfig& windowStyleConfig );
|
explicit UIWindow( WindowBaseContainerType type, const StyleConfig& windowStyleConfig );
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,28 @@ class EE_API Engine {
|
|||||||
SINGLETON_DECLARE_HEADERS( Engine )
|
SINGLETON_DECLARE_HEADERS( Engine )
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/** Scoped binding of the current native window and graphics context. Restores the previous
|
||||||
|
* binding when destroyed. Context objects are movable but cannot be copied. */
|
||||||
|
class EE_API WindowContext {
|
||||||
|
public:
|
||||||
|
~WindowContext();
|
||||||
|
|
||||||
|
WindowContext( const WindowContext& ) = delete;
|
||||||
|
WindowContext& operator=( const WindowContext& ) = delete;
|
||||||
|
|
||||||
|
WindowContext( WindowContext&& other ) noexcept;
|
||||||
|
WindowContext& operator=( WindowContext&& ) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Engine;
|
||||||
|
|
||||||
|
WindowContext( Engine* engine, EE::Window::Window* window );
|
||||||
|
|
||||||
|
Engine* mEngine{ nullptr };
|
||||||
|
EE::Window::Window* mPreviousWindow{ nullptr };
|
||||||
|
bool mActive{ true };
|
||||||
|
};
|
||||||
|
|
||||||
~Engine();
|
~Engine();
|
||||||
|
|
||||||
static bool isEngineRunning();
|
static bool isEngineRunning();
|
||||||
@@ -54,6 +76,9 @@ class EE_API Engine {
|
|||||||
/** Set the window as the current. */
|
/** Set the window as the current. */
|
||||||
void setCurrentWindow( EE::Window::Window* window );
|
void setCurrentWindow( EE::Window::Window* window );
|
||||||
|
|
||||||
|
/** Makes @p window and its graphics context current for the returned scope. */
|
||||||
|
WindowContext makeWindowCurrent( EE::Window::Window* window );
|
||||||
|
|
||||||
/** @return The number of windows created. */
|
/** @return The number of windows created. */
|
||||||
Uint32 getWindowCount() const;
|
Uint32 getWindowCount() const;
|
||||||
|
|
||||||
@@ -67,6 +92,10 @@ class EE_API Engine {
|
|||||||
|
|
||||||
EE::Window::Window* getWindowID( const Uint32& winID );
|
EE::Window::Window* getWindowID( const Uint32& winID );
|
||||||
|
|
||||||
|
/** Begins an input frame for every window, pumps the backend event queue once, routes events by
|
||||||
|
* window ID, and then completes every input frame. */
|
||||||
|
void updateInput();
|
||||||
|
|
||||||
/** Constructs WindowSettings from an ini file
|
/** Constructs WindowSettings from an ini file
|
||||||
It will search for the following properties:
|
It will search for the following properties:
|
||||||
Width Window width
|
Width Window width
|
||||||
@@ -181,7 +210,7 @@ class EE_API Engine {
|
|||||||
EE::Window::Window* createSDL2Window( const WindowSettings& Settings,
|
EE::Window::Window* createSDL2Window( const WindowSettings& Settings,
|
||||||
const ContextSettings& Context );
|
const ContextSettings& Context );
|
||||||
|
|
||||||
#ifdef EE_BACKEND_SDL3
|
#if defined( EE_BACKEND_SDL3 ) || defined( EE_SDL_VERSION_3 )
|
||||||
Backend::WindowBackendLibrary* createSDL3Backend( const WindowSettings& Settings );
|
Backend::WindowBackendLibrary* createSDL3Backend( const WindowSettings& Settings );
|
||||||
|
|
||||||
EE::Window::Window* createSDL3Window( const WindowSettings& Settings,
|
EE::Window::Window* createSDL3Window( const WindowSettings& Settings,
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ class EE_API Input {
|
|||||||
/** Update the Input */
|
/** Update the Input */
|
||||||
virtual void update() = 0;
|
virtual void update() = 0;
|
||||||
|
|
||||||
|
/** Clears transient input state, advances the event-frame ID, and drains injected events. */
|
||||||
|
void beginInputFrame();
|
||||||
|
|
||||||
|
/** Emits the end-of-event-processing notification for this input frame. */
|
||||||
|
void endInputFrame();
|
||||||
|
|
||||||
/** If timeout is zero waits indefinitely for the next available event otherwise waits until the
|
/** If timeout is zero waits indefinitely for the next available event otherwise waits until the
|
||||||
* specified timeout for the next available event.
|
* specified timeout for the next available event.
|
||||||
*/
|
*/
|
||||||
@@ -251,6 +257,10 @@ class EE_API Input {
|
|||||||
/** Process an input event. Called by the input update. */
|
/** Process an input event. Called by the input update. */
|
||||||
void processEvent( InputEvent* Event );
|
void processEvent( InputEvent* Event );
|
||||||
|
|
||||||
|
/** Routes an input event to the Input instance identified by InputEvent::WinID, then processes
|
||||||
|
* it. Events without a window ID are processed by this instance. */
|
||||||
|
void processEventForWindow( InputEvent* Event );
|
||||||
|
|
||||||
/** Queues an event from any producer thread for processing during the next normal update cycle.
|
/** Queues an event from any producer thread for processing during the next normal update cycle.
|
||||||
* If the bounded queue is full, an older mouse-motion event can be discarded to make room.
|
* If the bounded queue is full, an older mouse-motion event can be discarded to make room.
|
||||||
* @return True if the event was queued.
|
* @return True if the event was queued.
|
||||||
|
|||||||
@@ -274,6 +274,14 @@ class EE_API Window {
|
|||||||
*/
|
*/
|
||||||
virtual void setSize( Uint32 Width, Uint32 Height, bool isWindowed ) = 0;
|
virtual void setSize( Uint32 Width, Uint32 Height, bool isWindowed ) = 0;
|
||||||
|
|
||||||
|
/** Sets the native minimum client size in screen coordinates. Backends without native support
|
||||||
|
* may leave the default no-op implementation. */
|
||||||
|
virtual void setMinimumSize( Uint32 Width, Uint32 Height );
|
||||||
|
|
||||||
|
/** Makes this window modal for @p parent. Passing nullptr clears modality where supported.
|
||||||
|
* @return True when the backend applied the requested relationship. */
|
||||||
|
virtual bool setModalFor( Window* parent );
|
||||||
|
|
||||||
/** @return The window size in pixels */
|
/** @return The window size in pixels */
|
||||||
virtual Sizei getSize() const;
|
virtual Sizei getSize() const;
|
||||||
|
|
||||||
@@ -311,6 +319,12 @@ class EE_API Window {
|
|||||||
*/
|
*/
|
||||||
virtual void display( bool clear = false );
|
virtual void display( bool clear = false );
|
||||||
|
|
||||||
|
/** Presents the rendered frame.
|
||||||
|
* @param clear Whether to clear the back buffer after presentation.
|
||||||
|
* @param limitFrameRate Whether to apply this window's frame-rate limiter. Multi-window loops
|
||||||
|
* disable this per window and throttle once after all windows have been presented. */
|
||||||
|
void display( bool clear, bool limitFrameRate );
|
||||||
|
|
||||||
/** @return The elapsed time for the last frame rendered */
|
/** @return The elapsed time for the last frame rendered */
|
||||||
virtual const System::Time& getElapsed() const;
|
virtual const System::Time& getElapsed() const;
|
||||||
|
|
||||||
@@ -351,6 +365,13 @@ class EE_API Window {
|
|||||||
/** Close the window if is running */
|
/** Close the window if is running */
|
||||||
virtual void close();
|
virtual void close();
|
||||||
|
|
||||||
|
/** Defers backend resource destruction until the Engine destroys this Window. Intended for
|
||||||
|
* owners that must release scene resources at a safe point after close(). */
|
||||||
|
void setDeferNativeResourceDestructionOnClose( bool defer );
|
||||||
|
|
||||||
|
/** Returns whether close() leaves native resources alive until Engine destruction. */
|
||||||
|
bool getDeferNativeResourceDestructionOnClose() const;
|
||||||
|
|
||||||
/** Set the current active view
|
/** Set the current active view
|
||||||
* @param view New view to use (pass GetDefaultView() to set the default view)
|
* @param view New view to use (pass GetDefaultView() to set the default view)
|
||||||
* @param forceRefresh Forces the view refresh even if is the same as the last one.
|
* @param forceRefresh Forces the view refresh even if is the same as the last one.
|
||||||
@@ -568,6 +589,7 @@ class EE_API Window {
|
|||||||
friend class Input;
|
friend class Input;
|
||||||
|
|
||||||
mutable WindowInfo mWindow;
|
mutable WindowInfo mWindow;
|
||||||
|
bool mDeferNativeResourceDestructionOnClose{ false };
|
||||||
Clipboard* mClipboard;
|
Clipboard* mClipboard;
|
||||||
Input* mInput;
|
Input* mInput;
|
||||||
CursorManager* mCursorManager;
|
CursorManager* mCursorManager;
|
||||||
|
|||||||
@@ -1737,6 +1737,12 @@ solution "eepp"
|
|||||||
files { "src/examples/ui_application_hello_world/*.cpp" }
|
files { "src/examples/ui_application_hello_world/*.cpp" }
|
||||||
build_link_configuration( "eepp-ui-application-hello-world", true )
|
build_link_configuration( "eepp-ui-application-hello-world", true )
|
||||||
|
|
||||||
|
project "eepp-ui-application-multi-window"
|
||||||
|
set_kind()
|
||||||
|
language "C++"
|
||||||
|
files { "src/examples/ui_application_multi_window/*.cpp" }
|
||||||
|
build_link_configuration( "eepp-ui-application-multi-window", true )
|
||||||
|
|
||||||
project "eepp-ui-font-picker"
|
project "eepp-ui-font-picker"
|
||||||
set_kind()
|
set_kind()
|
||||||
language "C++"
|
language "C++"
|
||||||
|
|||||||
@@ -1771,6 +1771,12 @@ workspace "eepp"
|
|||||||
files { "src/examples/ui_application_hello_world/*.cpp" }
|
files { "src/examples/ui_application_hello_world/*.cpp" }
|
||||||
build_link_configuration( "eepp-ui-application-hello-world", true )
|
build_link_configuration( "eepp-ui-application-hello-world", true )
|
||||||
|
|
||||||
|
project "eepp-ui-application-multi-window"
|
||||||
|
set_kind()
|
||||||
|
language "C++"
|
||||||
|
files { "src/examples/ui_application_multi_window/*.cpp" }
|
||||||
|
build_link_configuration( "eepp-ui-application-multi-window", true )
|
||||||
|
|
||||||
project "eepp-ui-font-picker"
|
project "eepp-ui-font-picker"
|
||||||
set_kind()
|
set_kind()
|
||||||
language "C++"
|
language "C++"
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
#include <SOIL2/src/SOIL2/SOIL2.h>
|
#include <SOIL2/src/SOIL2/SOIL2.h>
|
||||||
|
#include <eepp/graphics/blendmode.hpp>
|
||||||
#include <eepp/graphics/renderer/openglext.hpp>
|
#include <eepp/graphics/renderer/openglext.hpp>
|
||||||
#include <eepp/graphics/renderer/renderer.hpp>
|
#include <eepp/graphics/renderer/renderer.hpp>
|
||||||
#include <eepp/graphics/renderer/renderergl.hpp>
|
#include <eepp/graphics/renderer/renderergl.hpp>
|
||||||
#include <eepp/graphics/renderer/renderergl3.hpp>
|
#include <eepp/graphics/renderer/renderergl3.hpp>
|
||||||
#include <eepp/graphics/renderer/renderergl3cp.hpp>
|
#include <eepp/graphics/renderer/renderergl3cp.hpp>
|
||||||
#include <eepp/graphics/renderer/renderergles2.hpp>
|
#include <eepp/graphics/renderer/renderergles2.hpp>
|
||||||
|
#include <eepp/graphics/texturefactory.hpp>
|
||||||
#include <eepp/system/sys.hpp>
|
#include <eepp/system/sys.hpp>
|
||||||
|
|
||||||
#ifdef EE_GLES1_LATE_INCLUDE
|
#ifdef EE_GLES1_LATE_INCLUDE
|
||||||
@@ -483,6 +485,21 @@ void Renderer::enable( unsigned int cap ) {
|
|||||||
glEnable( cap );
|
glEnable( cap );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Renderer::onContextChanged() {
|
||||||
|
if ( TextureFactory::existsSingleton() )
|
||||||
|
TextureFactory::instance()->invalidateTextureBindings();
|
||||||
|
|
||||||
|
BlendMode::setMode( BlendMode::getPreBlendFunc(), true );
|
||||||
|
lineSmooth();
|
||||||
|
polygonSmooth();
|
||||||
|
polygonMode();
|
||||||
|
multisample( isMultisample() );
|
||||||
|
colorMask( mColorMask[0], mColorMask[1], mColorMask[2], mColorMask[3] );
|
||||||
|
const float lineWidth = mLineWidth;
|
||||||
|
mLineWidth = -1.f;
|
||||||
|
this->lineWidth( lineWidth );
|
||||||
|
}
|
||||||
|
|
||||||
const char* Renderer::getString( unsigned int name ) {
|
const char* Renderer::getString( unsigned int name ) {
|
||||||
return (const char*)glGetString( name );
|
return (const char*)glGetString( name );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,11 @@ void RendererGL3::reloadCurrentShader() {
|
|||||||
reloadShader( mCurShader );
|
reloadShader( mCurShader );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RendererGL3::onContextChanged() {
|
||||||
|
Renderer::onContextChanged();
|
||||||
|
reloadCurrentShader();
|
||||||
|
}
|
||||||
|
|
||||||
ShaderProgramPtr RendererGL3::createSubpixelDualSourceShader() {
|
ShaderProgramPtr RendererGL3::createSubpixelDualSourceShader() {
|
||||||
std::string vertexShader = mBaseVertexShader;
|
std::string vertexShader = mBaseVertexShader;
|
||||||
String::replaceAll( vertexShader, "#version 120", "#version 130" );
|
String::replaceAll( vertexShader, "#version 120", "#version 130" );
|
||||||
|
|||||||
@@ -147,6 +147,11 @@ void RendererGL3CP::reloadCurrentShader() {
|
|||||||
reloadShader( mCurShader );
|
reloadShader( mCurShader );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RendererGL3CP::onContextChanged() {
|
||||||
|
Renderer::onContextChanged();
|
||||||
|
reloadCurrentShader();
|
||||||
|
}
|
||||||
|
|
||||||
ShaderProgramPtr RendererGL3CP::createSubpixelDualSourceShader() {
|
ShaderProgramPtr RendererGL3CP::createSubpixelDualSourceShader() {
|
||||||
static const char fragmentShader[] = R"(#version 330
|
static const char fragmentShader[] = R"(#version 330
|
||||||
uniform sampler2D textureUnit0;
|
uniform sampler2D textureUnit0;
|
||||||
|
|||||||
@@ -148,6 +148,11 @@ void RendererGLES2::reloadCurrentShader() {
|
|||||||
reloadShader( mCurShader );
|
reloadShader( mCurShader );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RendererGLES2::onContextChanged() {
|
||||||
|
Renderer::onContextChanged();
|
||||||
|
reloadCurrentShader();
|
||||||
|
}
|
||||||
|
|
||||||
ShaderProgramPtr RendererGLES2::createSubpixelDualSourceShader() {
|
ShaderProgramPtr RendererGLES2::createSubpixelDualSourceShader() {
|
||||||
#ifdef EE_GLES2
|
#ifdef EE_GLES2
|
||||||
static const char fragmentShader[] = R"(#extension GL_EXT_blend_func_extended : require
|
static const char fragmentShader[] = R"(#extension GL_EXT_blend_func_extended : require
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <SOIL2/src/SOIL2/SOIL2.h>
|
#include <SOIL2/src/SOIL2/SOIL2.h>
|
||||||
#include <SOIL2/src/SOIL2/stb_image.h>
|
#include <SOIL2/src/SOIL2/stb_image.h>
|
||||||
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <eepp/graphics/renderer/openglext.hpp>
|
#include <eepp/graphics/renderer/openglext.hpp>
|
||||||
#include <eepp/graphics/renderer/renderer.hpp>
|
#include <eepp/graphics/renderer/renderer.hpp>
|
||||||
@@ -221,6 +222,11 @@ void TextureFactory::setCurrentTexture( const int& textureHandle, const Uint32&
|
|||||||
mCurrentTexture[TextureUnit] = textureHandle;
|
mCurrentTexture[TextureUnit] = textureHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextureFactory::invalidateTextureBindings() {
|
||||||
|
std::fill( mCurrentTexture.begin(), mCurrentTexture.end(), -1 );
|
||||||
|
mLastCoordinateType = static_cast<Texture::CoordinateType>( -1 );
|
||||||
|
}
|
||||||
|
|
||||||
TextureRegistrySnapshot TextureFactory::snapshotTextures() {
|
TextureRegistrySnapshot TextureFactory::snapshotTextures() {
|
||||||
struct LockedTextureRecord {
|
struct LockedTextureRecord {
|
||||||
LiveTextureRecord record;
|
LiveTextureRecord record;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <eepp/scene/eventdispatcher.hpp>
|
#include <eepp/scene/eventdispatcher.hpp>
|
||||||
#include <eepp/scene/scenenode.hpp>
|
#include <eepp/scene/scenenode.hpp>
|
||||||
|
#include <eepp/ui/uiscenenode.hpp>
|
||||||
#include <eepp/window/engine.hpp>
|
#include <eepp/window/engine.hpp>
|
||||||
#include <eepp/window/input.hpp>
|
#include <eepp/window/input.hpp>
|
||||||
#include <eepp/window/inputevent.hpp>
|
#include <eepp/window/inputevent.hpp>
|
||||||
@@ -24,10 +25,22 @@ EventDispatcher::EventDispatcher( SceneNode* sceneNode ) :
|
|||||||
mFirstPress( false ),
|
mFirstPress( false ),
|
||||||
mNodeWasDragging( NULL ),
|
mNodeWasDragging( NULL ),
|
||||||
mNodeDragging( NULL ) {
|
mNodeDragging( NULL ) {
|
||||||
mCbId = mInput->pushCallback( [this]( InputEvent* event ) { inputCallback( event ); } );
|
mCbId = mInput->pushCallback( [this]( InputEvent* event ) {
|
||||||
|
if ( mSceneNode->isUISceneNode() ) {
|
||||||
|
auto context = mSceneNode->asType<UI::UISceneNode>()->makeCurrent();
|
||||||
|
inputCallback( event );
|
||||||
|
} else {
|
||||||
|
inputCallback( event );
|
||||||
|
}
|
||||||
|
} );
|
||||||
mIMECbId = mWindow->getIME().addTextEditingCb(
|
mIMECbId = mWindow->getIME().addTextEditingCb(
|
||||||
[this]( const String& text, Int32 start, Int32 length ) {
|
[this]( const String& text, Int32 start, Int32 length ) {
|
||||||
|
if ( mSceneNode->isUISceneNode() ) {
|
||||||
|
auto context = mSceneNode->asType<UI::UISceneNode>()->makeCurrent();
|
||||||
sendTextEditing( text, start, length );
|
sendTextEditing( text, start, length );
|
||||||
|
} else {
|
||||||
|
sendTextEditing( text, start, length );
|
||||||
|
}
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
#include <eepp/scene/node.hpp>
|
#include <eepp/scene/node.hpp>
|
||||||
#include <eepp/scene/scenemanager.hpp>
|
#include <eepp/scene/scenemanager.hpp>
|
||||||
#include <eepp/scene/scenenode.hpp>
|
#include <eepp/scene/scenenode.hpp>
|
||||||
|
#include <eepp/ui/uinode.hpp>
|
||||||
|
#include <eepp/ui/uiscenenode.hpp>
|
||||||
#include <eepp/window/engine.hpp>
|
#include <eepp/window/engine.hpp>
|
||||||
|
|
||||||
namespace EE { namespace Scene {
|
namespace EE { namespace Scene {
|
||||||
@@ -227,6 +229,16 @@ void Node::unsubscribeScheduledUpdate() {
|
|||||||
|
|
||||||
Node* Node::setParent( Node* parent ) {
|
Node* Node::setParent( Node* parent ) {
|
||||||
eeASSERT( NULL != parent );
|
eeASSERT( NULL != parent );
|
||||||
|
#ifdef EE_DEBUG
|
||||||
|
if ( isUINode() && parent->isUINode() ) {
|
||||||
|
auto* childScene = asType<UI::UINode>()->getUISceneNode();
|
||||||
|
auto* parentScene = parent->asType<UI::UINode>()->getUISceneNode();
|
||||||
|
// Same-window scene rebinding is an existing supported mechanism for embedded documents.
|
||||||
|
// Cross-window rebinding cannot safely migrate input and native-window dependencies.
|
||||||
|
eeASSERT( !childScene || !parentScene || childScene == parentScene ||
|
||||||
|
childScene->getWindow() == parentScene->getWindow() );
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if ( parent == mParentNode )
|
if ( parent == mParentNode )
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ bool SceneManager::isActive() {
|
|||||||
!SceneManager::isShuttingDown();
|
!SceneManager::isShuttingDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
SceneManager::SceneManager() : mUISceneNode( NULL ) {}
|
SceneManager::SceneManager() : mUISceneNode( NULL ), mScopedUISceneNode( NULL ) {}
|
||||||
|
|
||||||
SceneManager::~SceneManager() {
|
SceneManager::~SceneManager() {
|
||||||
for ( auto& it : mSceneNodes ) {
|
for ( auto& it : mSceneNodes ) {
|
||||||
@@ -32,6 +32,10 @@ SceneNode* SceneManager::add( SceneNode* sceneNode ) {
|
|||||||
bool SceneManager::remove( SceneNode* sceneNode ) {
|
bool SceneManager::remove( SceneNode* sceneNode ) {
|
||||||
auto it = std::find( mSceneNodes.begin(), mSceneNodes.end(), sceneNode );
|
auto it = std::find( mSceneNodes.begin(), mSceneNodes.end(), sceneNode );
|
||||||
if ( it != mSceneNodes.end() ) {
|
if ( it != mSceneNodes.end() ) {
|
||||||
|
if ( mUISceneNode == sceneNode )
|
||||||
|
mUISceneNode = nullptr;
|
||||||
|
if ( mScopedUISceneNode == sceneNode )
|
||||||
|
mScopedUISceneNode = nullptr;
|
||||||
mSceneNodes.erase( it );
|
mSceneNodes.erase( it );
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -44,13 +48,39 @@ size_t SceneManager::count() const {
|
|||||||
|
|
||||||
void SceneManager::draw() {
|
void SceneManager::draw() {
|
||||||
for ( auto& sceneNode : mSceneNodes ) {
|
for ( auto& sceneNode : mSceneNodes ) {
|
||||||
|
if ( sceneNode->isUISceneNode() ) {
|
||||||
|
auto context = sceneNode->asType<UISceneNode>()->makeCurrent();
|
||||||
sceneNode->draw();
|
sceneNode->draw();
|
||||||
|
} else {
|
||||||
|
auto context = Engine::instance()->makeWindowCurrent( sceneNode->getWindow() );
|
||||||
|
sceneNode->draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneManager::draw( EE::Window::Window* window ) {
|
||||||
|
for ( auto& sceneNode : mSceneNodes ) {
|
||||||
|
if ( sceneNode->getWindow() != window )
|
||||||
|
continue;
|
||||||
|
if ( sceneNode->isUISceneNode() ) {
|
||||||
|
auto context = sceneNode->asType<UISceneNode>()->makeCurrent();
|
||||||
|
sceneNode->draw();
|
||||||
|
} else {
|
||||||
|
auto context = Engine::instance()->makeWindowCurrent( window );
|
||||||
|
sceneNode->draw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::update( const Time& elapsed ) {
|
void SceneManager::update( const Time& elapsed ) {
|
||||||
for ( auto& sceneNode : mSceneNodes ) {
|
for ( auto& sceneNode : mSceneNodes ) {
|
||||||
|
if ( sceneNode->isUISceneNode() ) {
|
||||||
|
auto context = sceneNode->asType<UISceneNode>()->makeCurrent();
|
||||||
sceneNode->update( elapsed );
|
sceneNode->update( elapsed );
|
||||||
|
} else {
|
||||||
|
auto context = Engine::instance()->makeWindowCurrent( sceneNode->getWindow() );
|
||||||
|
sceneNode->update( elapsed );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +89,16 @@ void SceneManager::update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UISceneNode* SceneManager::getUISceneNode() {
|
UISceneNode* SceneManager::getUISceneNode() {
|
||||||
|
if ( mScopedUISceneNode )
|
||||||
|
return mScopedUISceneNode;
|
||||||
|
|
||||||
|
if ( Engine::existsSingleton() ) {
|
||||||
|
if ( mUISceneNode && mUISceneNode->getWindow() == Engine::instance()->getCurrentWindow() )
|
||||||
|
return mUISceneNode;
|
||||||
|
if ( auto* scene = getUISceneNode( Engine::instance()->getCurrentWindow() ) )
|
||||||
|
return scene;
|
||||||
|
}
|
||||||
|
|
||||||
if ( NULL == mUISceneNode ) {
|
if ( NULL == mUISceneNode ) {
|
||||||
for ( auto& sceneNode : mSceneNodes ) {
|
for ( auto& sceneNode : mSceneNodes ) {
|
||||||
if ( sceneNode->isUISceneNode() ) {
|
if ( sceneNode->isUISceneNode() ) {
|
||||||
@@ -71,10 +111,40 @@ UISceneNode* SceneManager::getUISceneNode() {
|
|||||||
return mUISceneNode;
|
return mUISceneNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UISceneNode* SceneManager::getUISceneNode( EE::Window::Window* window ) {
|
||||||
|
for ( auto& sceneNode : mSceneNodes ) {
|
||||||
|
if ( sceneNode->isUISceneNode() && sceneNode->getWindow() == window )
|
||||||
|
return sceneNode->asType<UISceneNode>();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void SceneManager::setCurrentUISceneNode( UISceneNode* uiSceneNode ) {
|
void SceneManager::setCurrentUISceneNode( UISceneNode* uiSceneNode ) {
|
||||||
mUISceneNode = uiSceneNode;
|
mUISceneNode = uiSceneNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UISceneNode* SceneManager::setScopedUISceneNode( UISceneNode* uiSceneNode ) {
|
||||||
|
UISceneNode* previous = mScopedUISceneNode;
|
||||||
|
mScopedUISceneNode = uiSceneNode;
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneManager::destroyScenes( EE::Window::Window* window ) {
|
||||||
|
for ( auto it = mSceneNodes.begin(); it != mSceneNodes.end(); ) {
|
||||||
|
SceneNode* sceneNode = *it;
|
||||||
|
if ( sceneNode->getWindow() != window ) {
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( mUISceneNode == sceneNode )
|
||||||
|
mUISceneNode = nullptr;
|
||||||
|
if ( mScopedUISceneNode == sceneNode )
|
||||||
|
mScopedUISceneNode = nullptr;
|
||||||
|
it = mSceneNodes.erase( it );
|
||||||
|
eeSAFE_DELETE( sceneNode );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Time SceneManager::getElapsed() const {
|
Time SceneManager::getElapsed() const {
|
||||||
return mClock.getElapsedTime();
|
return mClock.getElapsedTime();
|
||||||
}
|
}
|
||||||
|
|||||||
+230
-18
@@ -5,7 +5,9 @@
|
|||||||
#include <eepp/scene/scenemanager.hpp>
|
#include <eepp/scene/scenemanager.hpp>
|
||||||
#include <eepp/system/filesystem.hpp>
|
#include <eepp/system/filesystem.hpp>
|
||||||
#include <eepp/system/thread.hpp>
|
#include <eepp/system/thread.hpp>
|
||||||
|
#include <eepp/ui/iconmanager.hpp>
|
||||||
#include <eepp/ui/uiapplication.hpp>
|
#include <eepp/ui/uiapplication.hpp>
|
||||||
|
#include <eepp/ui/uiiconthememanager.hpp>
|
||||||
#include <eepp/ui/uiscenenode.hpp>
|
#include <eepp/ui/uiscenenode.hpp>
|
||||||
#include <eepp/ui/uitheme.hpp>
|
#include <eepp/ui/uitheme.hpp>
|
||||||
#include <eepp/ui/uithememanager.hpp>
|
#include <eepp/ui/uithememanager.hpp>
|
||||||
@@ -17,6 +19,10 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#if EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN
|
||||||
|
#include <emscripten/emscripten.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace EE::Graphics;
|
using namespace EE::Graphics;
|
||||||
using namespace EE::System;
|
using namespace EE::System;
|
||||||
using namespace EE::Scene;
|
using namespace EE::Scene;
|
||||||
@@ -40,7 +46,8 @@ class UIApplicationSystemFontState {
|
|||||||
static std::atomic<bool> sSystemFontsEnabledByDefault{ true };
|
static std::atomic<bool> sSystemFontsEnabledByDefault{ true };
|
||||||
|
|
||||||
UIApplication::UIApplication( const WindowSettings& windowSettings, const Settings& appSettings,
|
UIApplication::UIApplication( const WindowSettings& windowSettings, const Settings& appSettings,
|
||||||
const ContextSettings& contextSettings ) {
|
const ContextSettings& contextSettings ) :
|
||||||
|
mSettings( appSettings ) {
|
||||||
const bool enableSystemFonts = appSettings.enableSystemFonts.value_or(
|
const bool enableSystemFonts = appSettings.enableSystemFonts.value_or(
|
||||||
sSystemFontsEnabledByDefault.load( std::memory_order_acquire ) );
|
sSystemFontsEnabledByDefault.load( std::memory_order_acquire ) );
|
||||||
if ( enableSystemFonts ) {
|
if ( enableSystemFonts ) {
|
||||||
@@ -67,6 +74,7 @@ UIApplication::UIApplication( const WindowSettings& windowSettings, const Settin
|
|||||||
std::cerr << "Could not create window, exiting" << std::endl;
|
std::cerr << "Could not create window, exiting" << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
mWindow->setDeferNativeResourceDestructionOnClose( true );
|
||||||
|
|
||||||
mDidRun = true;
|
mDidRun = true;
|
||||||
|
|
||||||
@@ -93,11 +101,12 @@ UIApplication::UIApplication( const WindowSettings& windowSettings, const Settin
|
|||||||
defaultFontService.setHinting( appSettings.fontHinting );
|
defaultFontService.setHinting( appSettings.fontHinting );
|
||||||
defaultFontService.setAntialiasing( appSettings.fontAntialiasing );
|
defaultFontService.setAntialiasing( appSettings.fontAntialiasing );
|
||||||
|
|
||||||
mUISceneNode = UISceneNode::New();
|
mUISceneNode = UISceneNode::New( mWindow );
|
||||||
FontService& uiFontService = mUISceneNode->getResourceScope()->getFontService();
|
FontService& uiFontService = mUISceneNode->getResourceScope()->getFontService();
|
||||||
uiFontService.setHinting( appSettings.fontHinting );
|
uiFontService.setHinting( appSettings.fontHinting );
|
||||||
uiFontService.setAntialiasing( appSettings.fontAntialiasing );
|
uiFontService.setAntialiasing( appSettings.fontAntialiasing );
|
||||||
SceneManager::instance()->add( mUISceneNode );
|
SceneManager::instance()->add( mUISceneNode );
|
||||||
|
mWindows.push_back( { mWindow, mUISceneNode, true, false } );
|
||||||
|
|
||||||
if ( !appSettings.loadBaseResources )
|
if ( !appSettings.loadBaseResources )
|
||||||
return;
|
return;
|
||||||
@@ -144,6 +153,24 @@ UIApplication::UIApplication( const WindowSettings& windowSettings, const Settin
|
|||||||
mUISceneNode->setStyleSheet( theme->getStyleSheet() );
|
mUISceneNode->setStyleSheet( theme->getStyleSheet() );
|
||||||
mUISceneNode->getStyleSheet().setMarker( mStyleSheetMarker );
|
mUISceneNode->getStyleSheet().setMarker( mStyleSheetMarker );
|
||||||
mUISceneNode->getUIThemeManager()->setDefaultTheme( std::move( theme ) );
|
mUISceneNode->getUIThemeManager()->setDefaultTheme( std::move( theme ) );
|
||||||
|
|
||||||
|
if ( appSettings.loadIconResources ) {
|
||||||
|
auto loadIconFont = []( const std::string& name,
|
||||||
|
const std::string& path ) -> FontTrueType* {
|
||||||
|
if ( auto font = defaultResourceScope().findFont( name ) )
|
||||||
|
return font->getType() == FontType::TTF ? static_cast<FontTrueType*>( font.get() )
|
||||||
|
: nullptr;
|
||||||
|
if ( !FileSystem::fileExists( path ) )
|
||||||
|
return nullptr;
|
||||||
|
return FontTrueType::New( name, path ).get();
|
||||||
|
};
|
||||||
|
auto* remixIconFont = loadIconFont( "icon", "assets/fonts/remixicon.ttf" );
|
||||||
|
auto* noniconsFont = loadIconFont( "nonicons", "assets/fonts/nonicons.ttf" );
|
||||||
|
auto* codIconFont = loadIconFont( "codicon", "assets/fonts/codicon.ttf" );
|
||||||
|
if ( remixIconFont || noniconsFont || codIconFont )
|
||||||
|
mUISceneNode->getUIIconThemeManager()->setCurrentTheme(
|
||||||
|
IconManager::init( "uiapplication", remixIconFont, noniconsFont, codIconFont ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UIApplication::~UIApplication() {
|
UIApplication::~UIApplication() {
|
||||||
@@ -161,31 +188,216 @@ UISceneNode* UIApplication::getUI() const {
|
|||||||
return mUISceneNode;
|
return mUISceneNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
int UIApplication::run() {
|
UIApplication::WindowEntry*
|
||||||
// Offscreen SDL windows do not receive an initial expose event. Ensure the first logical
|
UIApplication::createWindowInternal( const WindowSettings& windowSettings,
|
||||||
// framebuffer is rendered even when the scene was fully laid out before entering the loop.
|
const ContextSettings& contextSettings, bool primary ) {
|
||||||
if ( Runtime::isOffscreen() )
|
auto context = Engine::instance()->makeWindowCurrent( Engine::instance()->getCurrentWindow() );
|
||||||
mUISceneNode->invalidate( nullptr );
|
auto* window = Engine::instance()->createWindow( windowSettings, contextSettings );
|
||||||
|
if ( !window || !window->isOpen() )
|
||||||
|
return nullptr;
|
||||||
|
window->setDeferNativeResourceDestructionOnClose( true );
|
||||||
|
|
||||||
mWindow->runMainLoop( [this]() {
|
auto* ui = UISceneNode::New( window );
|
||||||
mWindow->getInput()->update();
|
SceneManager::instance()->add( ui );
|
||||||
SceneManager::instance()->update();
|
mWindows.push_back( { window, ui, primary, false } );
|
||||||
if ( mUISceneNode->invalidated() ) {
|
configureUIScene( ui );
|
||||||
mWindow->clear();
|
return &mWindows.back();
|
||||||
|
}
|
||||||
|
|
||||||
SceneManager::instance()->draw();
|
void UIApplication::configureUIScene( UISceneNode* ui ) {
|
||||||
|
FontService& uiFontService = ui->getResourceScope()->getFontService();
|
||||||
|
uiFontService.setHinting( mSettings.fontHinting );
|
||||||
|
uiFontService.setAntialiasing( mSettings.fontAntialiasing );
|
||||||
|
if ( !mUISceneNode || ui == mUISceneNode || !mSettings.loadBaseResources )
|
||||||
|
return;
|
||||||
|
|
||||||
mWindow->display();
|
auto* sourceThemeManager = mUISceneNode->getUIThemeManager();
|
||||||
} else {
|
ui->getUIThemeManager()->setDefaultFont( sourceThemeManager->getDefaultFont() );
|
||||||
#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN
|
ui->getUIThemeManager()->setDefaultEffectsEnabled(
|
||||||
mWindow->getInput()->waitEvent( Milliseconds( mWindow->hasFocus() ? 16 : 100 ) );
|
sourceThemeManager->getDefaultEffectsEnabled() );
|
||||||
|
ui->getUIThemeManager()->setDefaultTheme( sourceThemeManager->getDefaultThemeHandle() );
|
||||||
|
ui->getUIIconThemeManager()->setCurrentTheme(
|
||||||
|
mUISceneNode->getUIIconThemeManager()->getCurrentThemeHandle() );
|
||||||
|
ui->setStyleSheet( mUISceneNode->getStyleSheet() );
|
||||||
|
ui->getStyleSheet().setMarker( mStyleSheetMarker );
|
||||||
|
ui->getRoot()->addClass( "appbackground" );
|
||||||
|
}
|
||||||
|
|
||||||
|
UISceneNode* UIApplication::createWindow( const WindowSettings& windowSettings,
|
||||||
|
const ContextSettings& contextSettings ) {
|
||||||
|
auto* entry = createWindowInternal( windowSettings, contextSettings, false );
|
||||||
|
return entry ? entry->ui : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
UISceneNode* UIApplication::getUI( EE::Window::Window* window ) const {
|
||||||
|
for ( const auto& entry : mWindows ) {
|
||||||
|
if ( entry.window == window )
|
||||||
|
return entry.ui;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t UIApplication::getWindowCount() const {
|
||||||
|
return mWindows.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIApplication::closeWindow( EE::Window::Window* window ) {
|
||||||
|
if ( nullptr == window )
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool closeAllWindows = false;
|
||||||
|
for ( auto& entry : mWindows ) {
|
||||||
|
if ( entry.window != window )
|
||||||
|
continue;
|
||||||
|
entry.window->hide();
|
||||||
|
entry.window->close();
|
||||||
|
entry.pendingDestroy = true;
|
||||||
|
closeAllWindows = entry.primary && mQuitPolicy == QuitPolicy::OnPrimaryWindowClosed;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( closeAllWindows ) {
|
||||||
|
for ( auto& entry : mWindows ) {
|
||||||
|
if ( entry.window ) {
|
||||||
|
entry.window->hide();
|
||||||
|
entry.window->close();
|
||||||
|
}
|
||||||
|
entry.pendingDestroy = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIApplication::requestQuit() {
|
||||||
|
mRunning = false;
|
||||||
|
#if EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN
|
||||||
|
emscripten_cancel_main_loop();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
} );
|
|
||||||
|
bool UIApplication::isRunning() const {
|
||||||
|
return mRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIApplication::setQuitPolicy( QuitPolicy policy ) {
|
||||||
|
mQuitPolicy = policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIApplication::QuitPolicy UIApplication::getQuitPolicy() const {
|
||||||
|
return mQuitPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UIApplication::run() {
|
||||||
|
if ( !mDidRun )
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
// Offscreen SDL windows do not receive an initial expose event. Ensure the first logical
|
||||||
|
// framebuffer is rendered even when the scene was fully laid out before entering the loop.
|
||||||
|
if ( Runtime::isOffscreen() ) {
|
||||||
|
for ( auto& entry : mWindows )
|
||||||
|
entry.ui->invalidate( nullptr );
|
||||||
|
}
|
||||||
|
|
||||||
|
mRunning = true;
|
||||||
|
#if EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN
|
||||||
|
emscripten_set_main_loop_arg(
|
||||||
|
[]( void* application ) { static_cast<UIApplication*>( application )->tick(); }, this,
|
||||||
|
mWindow->getFrameRateLimit(), 1 );
|
||||||
|
#else
|
||||||
|
while ( mRunning )
|
||||||
|
tick();
|
||||||
|
#endif
|
||||||
|
|
||||||
return mDidRun ? EXIT_SUCCESS : EXIT_FAILURE;
|
return mDidRun ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UIApplication::processClosedWindows() {
|
||||||
|
bool primaryClosed = false;
|
||||||
|
for ( auto& entry : mWindows ) {
|
||||||
|
if ( entry.window && !entry.window->isOpen() ) {
|
||||||
|
entry.window->hide();
|
||||||
|
entry.pendingDestroy = true;
|
||||||
|
primaryClosed |= entry.primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( primaryClosed && mQuitPolicy == QuitPolicy::OnPrimaryWindowClosed ) {
|
||||||
|
for ( auto& entry : mWindows ) {
|
||||||
|
if ( entry.window && entry.window->isOpen() ) {
|
||||||
|
entry.window->hide();
|
||||||
|
entry.window->close();
|
||||||
|
}
|
||||||
|
entry.pendingDestroy = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIApplication::processPendingWindowDestruction() {
|
||||||
|
for ( auto it = mWindows.begin(); it != mWindows.end(); ) {
|
||||||
|
if ( !it->pendingDestroy ) {
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto* window = it->window;
|
||||||
|
const bool wasPrimary = it->primary;
|
||||||
|
const bool retainFinalContext = mWindows.size() == 1;
|
||||||
|
Engine::instance()->setCurrentWindow( window );
|
||||||
|
SceneManager::instance()->destroyScenes( window );
|
||||||
|
it = mWindows.erase( it );
|
||||||
|
// The engine needs one GL context alive while its shared GPU resources are released during
|
||||||
|
// shutdown. A closed final window no longer participates in the application loop, but its
|
||||||
|
// native resources remain engine-owned until Engine destruction.
|
||||||
|
if ( !retainFinalContext )
|
||||||
|
Engine::instance()->destroyWindow( window );
|
||||||
|
if ( wasPrimary ) {
|
||||||
|
mWindow = nullptr;
|
||||||
|
mUISceneNode = nullptr;
|
||||||
|
if ( mQuitPolicy == QuitPolicy::OnPrimaryWindowClosed )
|
||||||
|
mRunning = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( mWindows.empty() && mQuitPolicy == QuitPolicy::OnLastWindowClosed )
|
||||||
|
mRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIApplication::tick() {
|
||||||
|
Engine::instance()->updateInput();
|
||||||
|
processClosedWindows();
|
||||||
|
SceneManager::instance()->update();
|
||||||
|
|
||||||
|
bool presented = false;
|
||||||
|
for ( auto& entry : mWindows ) {
|
||||||
|
if ( entry.pendingDestroy || !entry.window->isOpen() || !entry.ui->invalidated() )
|
||||||
|
continue;
|
||||||
|
auto context = entry.ui->makeCurrent();
|
||||||
|
entry.window->clear();
|
||||||
|
SceneManager::instance()->draw( entry.window );
|
||||||
|
entry.window->display( false, false );
|
||||||
|
presented = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
processPendingWindowDestruction();
|
||||||
|
#if EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN
|
||||||
|
if ( !mRunning )
|
||||||
|
emscripten_cancel_main_loop();
|
||||||
|
#endif
|
||||||
|
#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN
|
||||||
|
if ( mRunning && !mWindows.empty() ) {
|
||||||
|
auto* window = mWindows.front().window;
|
||||||
|
Engine::instance()->setCurrentWindow( window );
|
||||||
|
Int64 waitMilliseconds = window->hasFocus() ? 16 : 100;
|
||||||
|
if ( presented && window->getFrameRateLimit() > 0 ) {
|
||||||
|
waitMilliseconds = eemax<Int64>( 0, 1000 / window->getFrameRateLimit() -
|
||||||
|
mFrameClock.getElapsedTime().asMilliseconds() );
|
||||||
|
}
|
||||||
|
if ( waitMilliseconds > 0 )
|
||||||
|
window->getInput()->waitEvent( Milliseconds( waitMilliseconds ) );
|
||||||
|
mFrameClock.restart();
|
||||||
|
} else if ( mRunning ) {
|
||||||
|
Sys::sleep( Milliseconds( 16 ) );
|
||||||
|
mFrameClock.restart();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
UIApplication::Settings::Settings( std::optional<std::string> basePath,
|
UIApplication::Settings::Settings( std::optional<std::string> basePath,
|
||||||
std::optional<Float> pixelDensity, bool loadBaseResources,
|
std::optional<Float> pixelDensity, bool loadBaseResources,
|
||||||
Font* baseFont, std::optional<std::string> baseStyleSheetPath,
|
Font* baseFont, std::optional<std::string> baseStyleSheetPath,
|
||||||
|
|||||||
@@ -10,7 +10,9 @@
|
|||||||
#include <eepp/system/log.hpp>
|
#include <eepp/system/log.hpp>
|
||||||
#include <eepp/ui/models/filesystemmodel.hpp>
|
#include <eepp/ui/models/filesystemmodel.hpp>
|
||||||
#include <eepp/ui/models/sortingproxymodel.hpp>
|
#include <eepp/ui/models/sortingproxymodel.hpp>
|
||||||
|
#include <eepp/ui/uiapplication.hpp>
|
||||||
#include <eepp/ui/uifiledialog.hpp>
|
#include <eepp/ui/uifiledialog.hpp>
|
||||||
|
#include <eepp/window/runtime.hpp>
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
@@ -46,6 +48,33 @@ UIFileDialog* UIFileDialog::New( Uint32 dialogFlags, const std::string& defaultF
|
|||||||
return eeNew( UIFileDialog, ( dialogFlags, defaultFilePattern, defaultDirectory ) );
|
return eeNew( UIFileDialog, ( dialogFlags, defaultFilePattern, defaultDirectory ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UIFileDialog* UIFileDialog::NewInApplicationWindow(
|
||||||
|
UIApplication& application, const WindowSettings& windowSettings, Uint32 dialogFlags,
|
||||||
|
const std::string& defaultFilePattern, const std::string& defaultDirectory,
|
||||||
|
const ContextSettings& contextSettings, bool modal, ApplicationWindowPosition position ) {
|
||||||
|
dialogFlags &= ~UIFileDialog::UseNativeFileDialog;
|
||||||
|
WindowSettings dialogWindowSettings( windowSettings );
|
||||||
|
#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN
|
||||||
|
if ( Runtime::mode() != RuntimeMode::Terminal ) {
|
||||||
|
// WindowSettings dimensions are screen coordinates, while the file dialog minimum is in dp.
|
||||||
|
// Create the native host at its final minimum size so the window manager never positions a
|
||||||
|
// smaller window that is immediately enlarged after the dialog has been laid out.
|
||||||
|
dialogWindowSettings.Width =
|
||||||
|
eemax( dialogWindowSettings.Width,
|
||||||
|
static_cast<Uint32>( PixelDensity::dpToPxI( FDLG_MIN_WIDTH ) ) );
|
||||||
|
dialogWindowSettings.Height =
|
||||||
|
eemax( dialogWindowSettings.Height,
|
||||||
|
static_cast<Uint32>( PixelDensity::dpToPxI( FDLG_MIN_HEIGHT ) ) );
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return static_cast<UIFileDialog*>( createInApplicationWindow(
|
||||||
|
application, dialogWindowSettings,
|
||||||
|
[dialogFlags, defaultFilePattern, defaultDirectory] {
|
||||||
|
return New( dialogFlags, defaultFilePattern, defaultDirectory );
|
||||||
|
},
|
||||||
|
contextSettings, modal, position ) );
|
||||||
|
}
|
||||||
|
|
||||||
UIFileDialog::UIFileDialog( Uint32 dialogFlags, const std::string& defaultFilePattern,
|
UIFileDialog::UIFileDialog( Uint32 dialogFlags, const std::string& defaultFilePattern,
|
||||||
const std::string& defaultDirectory ) :
|
const std::string& defaultDirectory ) :
|
||||||
UIWindow(),
|
UIWindow(),
|
||||||
|
|||||||
@@ -35,6 +35,13 @@ UIIconTheme* UIIconThemeManager::getCurrentTheme() const {
|
|||||||
return mCurrentTheme;
|
return mCurrentTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UIIconThemePtr UIIconThemeManager::getCurrentThemeHandle() const {
|
||||||
|
auto it =
|
||||||
|
std::find_if( mIconThemes.begin(), mIconThemes.end(),
|
||||||
|
[&]( const UIIconThemePtr& theme ) { return theme.get() == mCurrentTheme; } );
|
||||||
|
return it != mIconThemes.end() ? *it : UIIconThemePtr{};
|
||||||
|
}
|
||||||
|
|
||||||
UIIconThemeManager* UIIconThemeManager::setCurrentTheme( UIIconThemePtr currentTheme ) {
|
UIIconThemeManager* UIIconThemeManager::setCurrentTheme( UIIconThemePtr currentTheme ) {
|
||||||
if ( currentTheme.get() != mCurrentTheme && currentTheme.get() != mFallbackTheme ) {
|
if ( currentTheme.get() != mCurrentTheme && currentTheme.get() != mFallbackTheme ) {
|
||||||
mCurrentTheme = currentTheme.get();
|
mCurrentTheme = currentTheme.get();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#include <eepp/ui/uiapplication.hpp>
|
||||||
#include <eepp/ui/uicombobox.hpp>
|
#include <eepp/ui/uicombobox.hpp>
|
||||||
#include <eepp/ui/uidropdownlist.hpp>
|
#include <eepp/ui/uidropdownlist.hpp>
|
||||||
#include <eepp/ui/uilayout.hpp>
|
#include <eepp/ui/uilayout.hpp>
|
||||||
@@ -18,6 +19,17 @@ UIMessageBox* UIMessageBox::New( const Type& type, const String& message,
|
|||||||
return eeNew( UIMessageBox, ( type, message, windowFlags ) );
|
return eeNew( UIMessageBox, ( type, message, windowFlags ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UIMessageBox* UIMessageBox::NewInApplicationWindow(
|
||||||
|
UIApplication& application, const EE::Window::WindowSettings& windowSettings, const Type& type,
|
||||||
|
const String& message, const Uint32& windowFlags,
|
||||||
|
const EE::Window::ContextSettings& contextSettings, bool modal,
|
||||||
|
ApplicationWindowPosition position ) {
|
||||||
|
return static_cast<UIMessageBox*>( createInApplicationWindow(
|
||||||
|
application, windowSettings,
|
||||||
|
[type, message, windowFlags] { return New( type, message, windowFlags ); }, contextSettings,
|
||||||
|
modal, position ) );
|
||||||
|
}
|
||||||
|
|
||||||
UIMessageBox::UIMessageBox( const Type& type, const String& message, const Uint32& windowFlags ) :
|
UIMessageBox::UIMessageBox( const Type& type, const String& message, const Uint32& windowFlags ) :
|
||||||
UIWindow(), mMsgBoxType( type ), mCloseShortcut( KEY_UNKNOWN ) {
|
UIWindow(), mMsgBoxType( type ), mCloseShortcut( KEY_UNKNOWN ) {
|
||||||
mVisible = false;
|
mVisible = false;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ UINode::UINode() :
|
|||||||
mUISceneNode( SceneManager::instance()->getUISceneNode() ) {
|
mUISceneNode( SceneManager::instance()->getUISceneNode() ) {
|
||||||
mNodeFlags |= NODE_FLAG_UINODE | NODE_FLAG_OVER_FIND_ALLOWED;
|
mNodeFlags |= NODE_FLAG_UINODE | NODE_FLAG_OVER_FIND_ALLOWED;
|
||||||
|
|
||||||
if ( NULL != mUISceneNode )
|
if ( NULL != mUISceneNode && NULL != mUISceneNode->getRoot() )
|
||||||
setParent( (Node*)mUISceneNode->getRoot() );
|
setParent( (Node*)mUISceneNode->getRoot() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,28 @@ UISceneNode* UISceneNode::New( EE::Window::Window* window, bool importDefaultRes
|
|||||||
return eeNew( UISceneNode, ( window, importDefaultResources ) );
|
return eeNew( UISceneNode, ( window, importDefaultResources ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UISceneNode::Context::Context( UISceneNode* scene ) :
|
||||||
|
mPreviousScene( SceneManager::instance()->setScopedUISceneNode( scene ) ),
|
||||||
|
mWindowContext(
|
||||||
|
Engine::instance()->makeWindowCurrent( scene ? scene->getWindow() : nullptr ) ) {}
|
||||||
|
|
||||||
|
UISceneNode::Context::~Context() {
|
||||||
|
if ( !mActive )
|
||||||
|
return;
|
||||||
|
SceneManager::instance()->setScopedUISceneNode( mPreviousScene );
|
||||||
|
}
|
||||||
|
|
||||||
|
UISceneNode::Context::Context( Context&& other ) noexcept :
|
||||||
|
mPreviousScene( other.mPreviousScene ),
|
||||||
|
mWindowContext( std::move( other.mWindowContext ) ),
|
||||||
|
mActive( other.mActive ) {
|
||||||
|
other.mActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UISceneNode::Context UISceneNode::makeCurrent() {
|
||||||
|
return Context( this );
|
||||||
|
}
|
||||||
|
|
||||||
UISceneNode::UISceneNode( EE::Window::Window* window, bool importDefaultResources ) :
|
UISceneNode::UISceneNode( EE::Window::Window* window, bool importDefaultResources ) :
|
||||||
SceneNode( window ),
|
SceneNode( window ),
|
||||||
mRoot( NULL ),
|
mRoot( NULL ),
|
||||||
@@ -146,6 +168,7 @@ UISceneNode::UISceneNode( EE::Window::Window* window, bool importDefaultResource
|
|||||||
mDrawableResolver( *this ),
|
mDrawableResolver( *this ),
|
||||||
mWebResourceCache( WebResourceCache::New() ),
|
mWebResourceCache( WebResourceCache::New() ),
|
||||||
mKeyBindings( mWindow->getInput() ) {
|
mKeyBindings( mWindow->getInput() ) {
|
||||||
|
auto context = makeCurrent();
|
||||||
if ( mImportDefaultResources )
|
if ( mImportDefaultResources )
|
||||||
mResourceScope->importCatalog( defaultResourceScope().getLocalCatalog() );
|
mResourceScope->importCatalog( defaultResourceScope().getLocalCatalog() );
|
||||||
|
|
||||||
@@ -641,8 +664,7 @@ SmallVector<UIWidget*, 8> UISceneNode::loadNode( pugi::xml_node node, Node* pare
|
|||||||
|
|
||||||
UIWidget* UISceneNode::loadLayoutNodes( pugi::xml_node node, Node* parent, const Uint32& marker ) {
|
UIWidget* UISceneNode::loadLayoutNodes( pugi::xml_node node, Node* parent, const Uint32& marker ) {
|
||||||
Clock clock;
|
Clock clock;
|
||||||
UISceneNode* prevUISceneNode = SceneManager::instance()->getUISceneNode();
|
auto context = makeCurrent();
|
||||||
SceneManager::instance()->setCurrentUISceneNode( this );
|
|
||||||
std::string id( node.attribute( "id" ).as_string() );
|
std::string id( node.attribute( "id" ).as_string() );
|
||||||
mIsLoading = true;
|
mIsLoading = true;
|
||||||
Clock innerClock;
|
Clock innerClock;
|
||||||
@@ -684,8 +706,6 @@ UIWidget* UISceneNode::loadLayoutNodes( pugi::xml_node node, Node* parent, const
|
|||||||
|
|
||||||
mIsLoading = false;
|
mIsLoading = false;
|
||||||
|
|
||||||
SceneManager::instance()->setCurrentUISceneNode( prevUISceneNode );
|
|
||||||
|
|
||||||
if ( mVerbose ) {
|
if ( mVerbose ) {
|
||||||
Log::debug( "UISceneNode::loadLayoutNodes loaded in: %.2f ms",
|
Log::debug( "UISceneNode::loadLayoutNodes loaded in: %.2f ms",
|
||||||
clock.getElapsedTime().asMilliseconds() );
|
clock.getElapsedTime().asMilliseconds() );
|
||||||
@@ -1174,7 +1194,7 @@ void UISceneNode::flushDirtyStyleAndLayout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void UISceneNode::update( const Time& elapsed ) {
|
void UISceneNode::update( const Time& elapsed ) {
|
||||||
UISceneNode* uiSceneNode = SceneManager::instance()->getUISceneNode();
|
auto context = makeCurrent();
|
||||||
|
|
||||||
drainAsyncResourceMainThreadQueue();
|
drainAsyncResourceMainThreadQueue();
|
||||||
|
|
||||||
@@ -1182,8 +1202,6 @@ void UISceneNode::update( const Time& elapsed ) {
|
|||||||
mClock.restart();
|
mClock.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
SceneManager::instance()->setCurrentUISceneNode( this );
|
|
||||||
|
|
||||||
updateDirtyStyles();
|
updateDirtyStyles();
|
||||||
updateDirtyStyleStates();
|
updateDirtyStyleStates();
|
||||||
updateDirtyLayouts();
|
updateDirtyLayouts();
|
||||||
@@ -1221,8 +1239,6 @@ void UISceneNode::update( const Time& elapsed ) {
|
|||||||
invalidationDepth--;
|
invalidationDepth--;
|
||||||
}
|
}
|
||||||
|
|
||||||
SceneManager::instance()->setCurrentUISceneNode( uiSceneNode );
|
|
||||||
|
|
||||||
if ( mFirstUpdate && mVerbose ) {
|
if ( mFirstUpdate && mVerbose ) {
|
||||||
mFirstUpdate = false;
|
mFirstUpdate = false;
|
||||||
Log::debug( "UISceneNode::update first update took: %.2f ms",
|
Log::debug( "UISceneNode::update first update took: %.2f ms",
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
#include <eepp/scene/actions/actions.hpp>
|
#include <eepp/scene/actions/actions.hpp>
|
||||||
#include <eepp/scene/scenemanager.hpp>
|
#include <eepp/scene/scenemanager.hpp>
|
||||||
#include <eepp/scene/scenenode.hpp>
|
#include <eepp/scene/scenenode.hpp>
|
||||||
|
#include <eepp/system/log.hpp>
|
||||||
#include <eepp/ui/css/propertydefinition.hpp>
|
#include <eepp/ui/css/propertydefinition.hpp>
|
||||||
|
#include <eepp/ui/uiapplication.hpp>
|
||||||
#include <eepp/ui/uieventdispatcher.hpp>
|
#include <eepp/ui/uieventdispatcher.hpp>
|
||||||
#include <eepp/ui/uilinearlayout.hpp>
|
#include <eepp/ui/uilinearlayout.hpp>
|
||||||
#include <eepp/ui/uipushbutton.hpp>
|
#include <eepp/ui/uipushbutton.hpp>
|
||||||
@@ -17,6 +19,9 @@
|
|||||||
#include <eepp/ui/uitextview.hpp>
|
#include <eepp/ui/uitextview.hpp>
|
||||||
#include <eepp/ui/uithememanager.hpp>
|
#include <eepp/ui/uithememanager.hpp>
|
||||||
#include <eepp/ui/uiwindow.hpp>
|
#include <eepp/ui/uiwindow.hpp>
|
||||||
|
#include <eepp/window/displaymanager.hpp>
|
||||||
|
#include <eepp/window/engine.hpp>
|
||||||
|
#include <eepp/window/runtime.hpp>
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
@@ -46,6 +51,134 @@ UIWindow* UIWindow::NewRelLay() {
|
|||||||
return eeNew( UIWindow, ( RELATIVE_LAYOUT ) );
|
return eeNew( UIWindow, ( RELATIVE_LAYOUT ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UIWindow* UIWindow::NewInApplicationWindow( UIApplication& application,
|
||||||
|
const EE::Window::WindowSettings& windowSettings,
|
||||||
|
WindowBaseContainerType type,
|
||||||
|
const StyleConfig& windowStyleConfig,
|
||||||
|
const EE::Window::ContextSettings& contextSettings,
|
||||||
|
bool modal, ApplicationWindowPosition position ) {
|
||||||
|
return createInApplicationWindow(
|
||||||
|
application, windowSettings,
|
||||||
|
[type, windowStyleConfig] { return NewOpt( type, windowStyleConfig ); }, contextSettings,
|
||||||
|
modal, position );
|
||||||
|
}
|
||||||
|
|
||||||
|
UIWindow* UIWindow::createInApplicationWindow( UIApplication& application,
|
||||||
|
const EE::Window::WindowSettings& windowSettings,
|
||||||
|
const std::function<UIWindow*()>& windowFactory,
|
||||||
|
const EE::Window::ContextSettings& contextSettings,
|
||||||
|
bool modal, ApplicationWindowPosition position ) {
|
||||||
|
bool supportsMultipleNativeWindows = Runtime::mode() != RuntimeMode::Terminal;
|
||||||
|
#if EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN
|
||||||
|
supportsMultipleNativeWindows = false;
|
||||||
|
#endif
|
||||||
|
if ( !supportsMultipleNativeWindows ) {
|
||||||
|
(void)contextSettings;
|
||||||
|
(void)position;
|
||||||
|
auto* ui = application.getUI();
|
||||||
|
if ( nullptr == ui )
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto context = ui->makeCurrent();
|
||||||
|
auto* uiWindow = windowFactory();
|
||||||
|
if ( nullptr == uiWindow )
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
Uint32 windowFlags = uiWindow->getWinFlags();
|
||||||
|
uiWindow->setWindowFlags( modal ? windowFlags | UI_WIN_MODAL
|
||||||
|
: windowFlags & ~UI_WIN_MODAL );
|
||||||
|
if ( !windowSettings.Title.empty() )
|
||||||
|
uiWindow->setTitle( windowSettings.Title );
|
||||||
|
|
||||||
|
const Sizef requestedSize( windowSettings.Width, windowSettings.Height );
|
||||||
|
const Sizef minimumSize( uiWindow->getMinWindowSizeWithDecoration() );
|
||||||
|
uiWindow->setSizeWithDecoration(
|
||||||
|
eemax( requestedSize.getWidth(), minimumSize.getWidth() ),
|
||||||
|
eemax( requestedSize.getHeight(), minimumSize.getHeight() ) );
|
||||||
|
if ( !( windowSettings.Style & WindowStyle::Hidden ) )
|
||||||
|
uiWindow->showWhenReady();
|
||||||
|
return uiWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN
|
||||||
|
const bool showHostWindow = !( windowSettings.Style & WindowStyle::Hidden );
|
||||||
|
EE::Window::WindowSettings hiddenWindowSettings( windowSettings );
|
||||||
|
hiddenWindowSettings.Style |= WindowStyle::Hidden;
|
||||||
|
auto* ui = application.createWindow( hiddenWindowSettings, contextSettings );
|
||||||
|
if ( nullptr == ui )
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto context = ui->makeCurrent();
|
||||||
|
auto* layout = UIRelativeLayout::New();
|
||||||
|
layout->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent );
|
||||||
|
layout->setParent( ui->getRoot() );
|
||||||
|
auto* uiWindow = windowFactory();
|
||||||
|
if ( nullptr == uiWindow )
|
||||||
|
return nullptr;
|
||||||
|
uiWindow->setWindowFlags( ( uiWindow->getWinFlags() & ~UI_WIN_MODAL ) | UI_WIN_NO_DECORATION );
|
||||||
|
uiWindow->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent );
|
||||||
|
uiWindow->setPosition( 0, 0 );
|
||||||
|
uiWindow->setParent( layout );
|
||||||
|
|
||||||
|
auto* window = ui->getWindow();
|
||||||
|
// The application window provides the decoration. The UIWindow decoration was explicitly
|
||||||
|
// disabled above, so its border/title sizes must not affect the native client-area minimum.
|
||||||
|
const Sizef minimumSizePx = PixelDensity::dpToPx( uiWindow->getMinWindowSize() );
|
||||||
|
const Float windowScale = window->getScale();
|
||||||
|
const Sizei minimumSize( eeceil( minimumSizePx.getWidth() / windowScale ),
|
||||||
|
eeceil( minimumSizePx.getHeight() / windowScale ) );
|
||||||
|
window->setMinimumSize( minimumSize.getWidth(), minimumSize.getHeight() );
|
||||||
|
const Sizei windowSize = window->getSizeInScreenCoordinates();
|
||||||
|
if ( windowSize.getWidth() < minimumSize.getWidth() ||
|
||||||
|
windowSize.getHeight() < minimumSize.getHeight() ) {
|
||||||
|
window->setSize( eemax( windowSize.getWidth(), minimumSize.getWidth() ),
|
||||||
|
eemax( windowSize.getHeight(), minimumSize.getHeight() ) );
|
||||||
|
}
|
||||||
|
auto* primaryWindow = application.getWindow();
|
||||||
|
if ( modal && nullptr != primaryWindow && primaryWindow != window &&
|
||||||
|
!window->setModalFor( primaryWindow ) )
|
||||||
|
Log::warning( "UIWindow failed to make its application window modal" );
|
||||||
|
uiWindow->mApplicationWindowCloseCallback = [&application, window] {
|
||||||
|
application.closeWindow( window );
|
||||||
|
};
|
||||||
|
uiWindow->showWhenReady();
|
||||||
|
if ( showHostWindow )
|
||||||
|
window->show();
|
||||||
|
if ( position == ApplicationWindowPosition::CenteredOnPrimary && nullptr != primaryWindow &&
|
||||||
|
primaryWindow != window ) {
|
||||||
|
const Vector2i primaryPosition = primaryWindow->getPosition();
|
||||||
|
const Sizei primarySize = primaryWindow->getSizeInScreenCoordinates();
|
||||||
|
const Sizei dialogSize = window->getSizeInScreenCoordinates();
|
||||||
|
Vector2i dialogPosition(
|
||||||
|
primaryPosition.x + ( primarySize.getWidth() - dialogSize.getWidth() ) / 2,
|
||||||
|
primaryPosition.y + ( primarySize.getHeight() - dialogSize.getHeight() ) / 2 );
|
||||||
|
auto* displayManager = Engine::instance()->getDisplayManager();
|
||||||
|
const Vector2i primaryCenter( primaryPosition.x + primarySize.getWidth() / 2,
|
||||||
|
primaryPosition.y + primarySize.getHeight() / 2 );
|
||||||
|
for ( int i = 0; i < displayManager->getDisplayCount(); ++i ) {
|
||||||
|
auto* display = displayManager->getDisplayIndex( i );
|
||||||
|
if ( nullptr == display || !display->getBounds().contains( primaryCenter ) )
|
||||||
|
continue;
|
||||||
|
const Rect usableBounds = display->getUsableBounds();
|
||||||
|
const Rect border = window->getBorderSize();
|
||||||
|
const int minimumX = usableBounds.Left + border.Left;
|
||||||
|
const int minimumY = usableBounds.Top + border.Top;
|
||||||
|
const int maximumX = usableBounds.Right - dialogSize.getWidth() - border.Right;
|
||||||
|
const int maximumY = usableBounds.Bottom - dialogSize.getHeight() - border.Bottom;
|
||||||
|
dialogPosition.x =
|
||||||
|
maximumX < minimumX ? minimumX : eeclamp( dialogPosition.x, minimumX, maximumX );
|
||||||
|
dialogPosition.y =
|
||||||
|
maximumY < minimumY ? minimumY : eeclamp( dialogPosition.y, minimumY, maximumY );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
window->setPosition( dialogPosition.x, dialogPosition.y );
|
||||||
|
}
|
||||||
|
return uiWindow;
|
||||||
|
#else
|
||||||
|
return nullptr;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
UIWindow::UIWindow( UIWindow::WindowBaseContainerType type ) : UIWindow( type, StyleConfig() ) {}
|
UIWindow::UIWindow( UIWindow::WindowBaseContainerType type ) : UIWindow( type, StyleConfig() ) {}
|
||||||
|
|
||||||
UIWindow::UIWindow( UIWindow::WindowBaseContainerType type, const StyleConfig& windowStyleConfig ) :
|
UIWindow::UIWindow( UIWindow::WindowBaseContainerType type, const StyleConfig& windowStyleConfig ) :
|
||||||
@@ -322,6 +455,13 @@ void UIWindow::updateWinFlags() {
|
|||||||
|
|
||||||
if ( isModal() && NULL == mModalNode ) {
|
if ( isModal() && NULL == mModalNode ) {
|
||||||
createModalNode();
|
createModalNode();
|
||||||
|
} else if ( !isModal() && NULL != mModalNode ) {
|
||||||
|
// The modal blocker is parented to the scene, not to the window. It must be removed when
|
||||||
|
// UI_WIN_MODAL is cleared or it will continue winning hit tests over the entire scene.
|
||||||
|
mModalNode->setEnabled( false );
|
||||||
|
mModalNode->setVisible( false );
|
||||||
|
mModalNode->close();
|
||||||
|
mModalNode = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( needsUpdate ) {
|
if ( needsUpdate ) {
|
||||||
@@ -452,6 +592,7 @@ bool UIWindow::isType( const Uint32& type ) const {
|
|||||||
void UIWindow::closeWindow() {
|
void UIWindow::closeWindow() {
|
||||||
if ( mClosing )
|
if ( mClosing )
|
||||||
return;
|
return;
|
||||||
|
mClosing = true;
|
||||||
|
|
||||||
if ( NULL != mButtonClose )
|
if ( NULL != mButtonClose )
|
||||||
mButtonClose->setEnabled( false );
|
mButtonClose->setEnabled( false );
|
||||||
@@ -473,8 +614,17 @@ void UIWindow::closeWindow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void UIWindow::close() {
|
void UIWindow::close() {
|
||||||
|
mClosing = true;
|
||||||
UIWidget::close();
|
UIWidget::close();
|
||||||
|
|
||||||
|
// An application-hosted UIWindow owns the lifetime of its native host. OnWindowClose is
|
||||||
|
// emitted from the destructor and is therefore too late for controls such as a message-box
|
||||||
|
// OK button, which close the UIWindow itself.
|
||||||
|
if ( mApplicationWindowCloseCallback ) {
|
||||||
|
auto closeApplicationWindow = std::move( mApplicationWindowCloseCallback );
|
||||||
|
closeApplicationWindow();
|
||||||
|
}
|
||||||
|
|
||||||
if ( NULL != mModalNode ) {
|
if ( NULL != mModalNode ) {
|
||||||
mModalNode->setEnabled( false );
|
mModalNode->setEnabled( false );
|
||||||
mModalNode->setVisible( false );
|
mModalNode->setVisible( false );
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ std::string DisplaySDL2::getName() const {
|
|||||||
Rect DisplaySDL2::getBounds() const {
|
Rect DisplaySDL2::getBounds() const {
|
||||||
SDL_Rect r;
|
SDL_Rect r;
|
||||||
if ( SDL_GetDisplayBounds( index, &r ) == 0 )
|
if ( SDL_GetDisplayBounds( index, &r ) == 0 )
|
||||||
return Rect( r.x, r.y, r.w, r.h );
|
return Rect( Vector2i( r.x, r.y ), Sizei( r.w, r.h ) );
|
||||||
return Rect();
|
return Rect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ DisplayMode DisplaySDL2::getClosestDisplayMode( DisplayMode wantedMode ) const {
|
|||||||
Rect DisplaySDL2::getUsableBounds() const {
|
Rect DisplaySDL2::getUsableBounds() const {
|
||||||
SDL_Rect r;
|
SDL_Rect r;
|
||||||
if ( SDL_GetDisplayUsableBounds( index, &r ) == 0 )
|
if ( SDL_GetDisplayUsableBounds( index, &r ) == 0 )
|
||||||
return Rect( r.x, r.y, r.w, r.h );
|
return Rect( Vector2i( r.x, r.y ), Sizei( r.w, r.h ) );
|
||||||
return Rect();
|
return Rect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,14 +21,8 @@ InputSDL::InputSDL( EE::Window::Window* window ) :
|
|||||||
InputSDL::~InputSDL() {}
|
InputSDL::~InputSDL() {}
|
||||||
|
|
||||||
void InputSDL::update() {
|
void InputSDL::update() {
|
||||||
|
beginInputFrame();
|
||||||
SDL_Event SDLEvent;
|
SDL_Event SDLEvent;
|
||||||
cleanStates();
|
|
||||||
|
|
||||||
++mEventsSentId;
|
|
||||||
if ( mEventsSentId == std::numeric_limits<Uint64>::max() )
|
|
||||||
mEventsSentId = 0;
|
|
||||||
|
|
||||||
drainQueuedEvents();
|
|
||||||
|
|
||||||
if ( !mQueuedEvents.empty() ) {
|
if ( !mQueuedEvents.empty() ) {
|
||||||
for ( const auto& prevEvent : mQueuedEvents )
|
for ( const auto& prevEvent : mQueuedEvents )
|
||||||
@@ -37,9 +31,7 @@ void InputSDL::update() {
|
|||||||
}
|
}
|
||||||
while ( SDL_PollEvent( &SDLEvent ) )
|
while ( SDL_PollEvent( &SDLEvent ) )
|
||||||
sendEvent( SDLEvent );
|
sendEvent( SDLEvent );
|
||||||
InputEvent endProcessingEvent;
|
endInputFrame();
|
||||||
endProcessingEvent.Type = InputEvent::EventsSent;
|
|
||||||
processEvent( &endProcessingEvent );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputSDL::waitEvent( const Time& timeout ) {
|
void InputSDL::waitEvent( const Time& timeout ) {
|
||||||
@@ -244,14 +236,16 @@ void InputSDL::sendEvent( const SDL_Event& SDLEvent ) {
|
|||||||
}
|
}
|
||||||
case SDL_TEXTINPUT: {
|
case SDL_TEXTINPUT: {
|
||||||
String txt = String::fromUtf8( std::string_view{ SDLEvent.text.text } );
|
String txt = String::fromUtf8( std::string_view{ SDLEvent.text.text } );
|
||||||
|
if ( txt.empty() )
|
||||||
|
break;
|
||||||
event.Type = InputEvent::TextInput;
|
event.Type = InputEvent::TextInput;
|
||||||
event.text.timestamp = SDLEvent.text.timestamp;
|
event.text.timestamp = SDLEvent.text.timestamp;
|
||||||
event.WinID = SDLEvent.text.windowID;
|
event.WinID = SDLEvent.text.windowID;
|
||||||
for ( size_t i = 0; i < txt.size() - 1; i++ ) {
|
for ( const auto& character : txt ) {
|
||||||
event.text.text = txt[i];
|
event.text.text = character;
|
||||||
processEvent( &event );
|
processEventForWindow( &event );
|
||||||
}
|
}
|
||||||
event.text.text = txt[txt.size() - 1];
|
event.Type = InputEvent::NoEvent;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SDL_TEXTEDITING: {
|
case SDL_TEXTEDITING: {
|
||||||
@@ -357,11 +351,11 @@ void InputSDL::sendEvent( const SDL_Event& SDLEvent ) {
|
|||||||
|
|
||||||
event.Type = InputEvent::MouseButtonDown;
|
event.Type = InputEvent::MouseButtonDown;
|
||||||
event.button.state = 1;
|
event.button.state = 1;
|
||||||
processEvent( &event );
|
processEventForWindow( &event );
|
||||||
|
|
||||||
event.Type = InputEvent::MouseButtonUp;
|
event.Type = InputEvent::MouseButtonUp;
|
||||||
event.button.state = 0;
|
event.button.state = 0;
|
||||||
processEvent( &event );
|
processEventForWindow( &event );
|
||||||
|
|
||||||
event.Type = InputEvent::MouseWheel;
|
event.Type = InputEvent::MouseWheel;
|
||||||
event.wheel.which = SDLEvent.wheel.which;
|
event.wheel.which = SDLEvent.wheel.which;
|
||||||
@@ -376,7 +370,7 @@ void InputSDL::sendEvent( const SDL_Event& SDLEvent ) {
|
|||||||
event.wheel.x = SDLEvent.wheel.x;
|
event.wheel.x = SDLEvent.wheel.x;
|
||||||
event.wheel.y = SDLEvent.wheel.y;
|
event.wheel.y = SDLEvent.wheel.y;
|
||||||
#endif
|
#endif
|
||||||
processEvent( &event );
|
processEventForWindow( &event );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SDL_FINGERMOTION: {
|
case SDL_FINGERMOTION: {
|
||||||
@@ -496,17 +490,8 @@ void InputSDL::sendEvent( const SDL_Event& SDLEvent ) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EE::Window::Window* win;
|
if ( InputEvent::NoEvent != event.Type )
|
||||||
|
processEventForWindow( &event );
|
||||||
if ( InputEvent::NoEvent != event.Type ) {
|
|
||||||
if ( event.WinID == mWindow->getWindowID() || event.WinID == 0 ) {
|
|
||||||
processEvent( &event );
|
|
||||||
} else if ( ( win = Engine::instance()->getWindowID( event.WinID ) ) ) {
|
|
||||||
win->getInput()->processEvent( &event );
|
|
||||||
} else {
|
|
||||||
processEvent( &event );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( InputEvent::FileDropped == event.Type || InputEvent::TextDropped == event.Type )
|
if ( InputEvent::FileDropped == event.Type || InputEvent::TextDropped == event.Type )
|
||||||
SDL_free( SDLEvent.drop.file );
|
SDL_free( SDLEvent.drop.file );
|
||||||
|
|||||||
@@ -316,6 +316,7 @@ void WindowSDL::makeCurrent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WindowSDL::close() {
|
void WindowSDL::close() {
|
||||||
|
if ( !getDeferNativeResourceDestructionOnClose() )
|
||||||
destroySDLResources();
|
destroySDLResources();
|
||||||
Window::close();
|
Window::close();
|
||||||
}
|
}
|
||||||
@@ -540,6 +541,17 @@ void WindowSDL::setSize( Uint32 width, Uint32 height, bool windowed ) {
|
|||||||
sendVideoResizeCb();
|
sendVideoResizeCb();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WindowSDL::setMinimumSize( Uint32 width, Uint32 height ) {
|
||||||
|
SDL_SetWindowMinimumSize( mSDLWindow, static_cast<int>( width ), static_cast<int>( height ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowSDL::setModalFor( Window* parent ) {
|
||||||
|
auto* parentWindow = dynamic_cast<WindowSDL*>( parent );
|
||||||
|
if ( nullptr == parentWindow )
|
||||||
|
return false;
|
||||||
|
return 0 == SDL_SetWindowModalFor( mSDLWindow, parentWindow->mSDLWindow );
|
||||||
|
}
|
||||||
|
|
||||||
void WindowSDL::swapBuffers() {
|
void WindowSDL::swapBuffers() {
|
||||||
SDL_GL_SwapWindow( mSDLWindow );
|
SDL_GL_SwapWindow( mSDLWindow );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,12 @@ class EE_API WindowSDL : public Window {
|
|||||||
|
|
||||||
void setSize( Uint32 width, Uint32 height, bool windowed );
|
void setSize( Uint32 width, Uint32 height, bool windowed );
|
||||||
|
|
||||||
|
/** @copydoc EE::Window::Window::setMinimumSize() */
|
||||||
|
void setMinimumSize( Uint32 width, Uint32 height );
|
||||||
|
|
||||||
|
/** @copydoc EE::Window::Window::setModalFor() */
|
||||||
|
bool setModalFor( Window* parent );
|
||||||
|
|
||||||
std::vector<DisplayMode> getDisplayModes() const;
|
std::vector<DisplayMode> getDisplayModes() const;
|
||||||
|
|
||||||
void setGamma( Float Red, Float Green, Float Blue );
|
void setGamma( Float Red, Float Green, Float Blue );
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ std::string DisplaySDL3::getName() const {
|
|||||||
Rect DisplaySDL3::getBounds() const {
|
Rect DisplaySDL3::getBounds() const {
|
||||||
SDL_Rect r{};
|
SDL_Rect r{};
|
||||||
if ( mDisplayId && SDL_GetDisplayBounds( mDisplayId, &r ) == 0 )
|
if ( mDisplayId && SDL_GetDisplayBounds( mDisplayId, &r ) == 0 )
|
||||||
return Rect( r.x, r.y, r.w, r.h );
|
return Rect( Vector2i( r.x, r.y ), Sizei( r.w, r.h ) );
|
||||||
return Rect();
|
return Rect();
|
||||||
}
|
}
|
||||||
|
|
||||||
Rect DisplaySDL3::getUsableBounds() const {
|
Rect DisplaySDL3::getUsableBounds() const {
|
||||||
SDL_Rect r{};
|
SDL_Rect r{};
|
||||||
if ( mDisplayId && SDL_GetDisplayUsableBounds( mDisplayId, &r ) == 0 )
|
if ( mDisplayId && SDL_GetDisplayUsableBounds( mDisplayId, &r ) == 0 )
|
||||||
return Rect( r.x, r.y, r.w, r.h );
|
return Rect( Vector2i( r.x, r.y ), Sizei( r.w, r.h ) );
|
||||||
return Rect();
|
return Rect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,14 +17,8 @@ InputSDL::InputSDL( Window* window ) :
|
|||||||
InputSDL::~InputSDL() {}
|
InputSDL::~InputSDL() {}
|
||||||
|
|
||||||
void InputSDL::update() {
|
void InputSDL::update() {
|
||||||
|
beginInputFrame();
|
||||||
SDL_Event SDLEvent;
|
SDL_Event SDLEvent;
|
||||||
cleanStates();
|
|
||||||
|
|
||||||
++mEventsSentId;
|
|
||||||
if ( mEventsSentId == std::numeric_limits<Uint64>::max() )
|
|
||||||
mEventsSentId = 0;
|
|
||||||
|
|
||||||
drainQueuedEvents();
|
|
||||||
|
|
||||||
if ( !mQueuedEvents.empty() ) {
|
if ( !mQueuedEvents.empty() ) {
|
||||||
for ( const auto& prevEvent : mQueuedEvents )
|
for ( const auto& prevEvent : mQueuedEvents )
|
||||||
@@ -33,9 +27,7 @@ void InputSDL::update() {
|
|||||||
}
|
}
|
||||||
while ( SDL_PollEvent( &SDLEvent ) )
|
while ( SDL_PollEvent( &SDLEvent ) )
|
||||||
sendEvent( SDLEvent );
|
sendEvent( SDLEvent );
|
||||||
InputEvent endProcessingEvent;
|
endInputFrame();
|
||||||
endProcessingEvent.Type = InputEvent::EventsSent;
|
|
||||||
processEvent( &endProcessingEvent );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputSDL::waitEvent( const Time& timeout ) {
|
void InputSDL::waitEvent( const Time& timeout ) {
|
||||||
@@ -237,7 +229,7 @@ void InputSDL::sendEvent( const SDL_Event& SDLEvent ) {
|
|||||||
event.WinID = SDLEvent.text.windowID;
|
event.WinID = SDLEvent.text.windowID;
|
||||||
for ( const auto& character : txt ) {
|
for ( const auto& character : txt ) {
|
||||||
event.text.text = character;
|
event.text.text = character;
|
||||||
processEvent( &event );
|
processEventForWindow( &event );
|
||||||
}
|
}
|
||||||
event.Type = InputEvent::NoEvent; // Already processed all characters
|
event.Type = InputEvent::NoEvent; // Already processed all characters
|
||||||
break;
|
break;
|
||||||
@@ -334,11 +326,11 @@ void InputSDL::sendEvent( const SDL_Event& SDLEvent ) {
|
|||||||
|
|
||||||
event.Type = InputEvent::MouseButtonDown;
|
event.Type = InputEvent::MouseButtonDown;
|
||||||
event.button.state = 1;
|
event.button.state = 1;
|
||||||
processEvent( &event );
|
processEventForWindow( &event );
|
||||||
|
|
||||||
event.Type = InputEvent::MouseButtonUp;
|
event.Type = InputEvent::MouseButtonUp;
|
||||||
event.button.state = 0;
|
event.button.state = 0;
|
||||||
processEvent( &event );
|
processEventForWindow( &event );
|
||||||
|
|
||||||
event.Type = InputEvent::MouseWheel;
|
event.Type = InputEvent::MouseWheel;
|
||||||
event.wheel.which = SDLEvent.wheel.windowID;
|
event.wheel.which = SDLEvent.wheel.windowID;
|
||||||
@@ -347,7 +339,7 @@ void InputSDL::sendEvent( const SDL_Event& SDLEvent ) {
|
|||||||
: InputEvent::WheelEvent::Flipped;
|
: InputEvent::WheelEvent::Flipped;
|
||||||
event.wheel.x = SDLEvent.wheel.x;
|
event.wheel.x = SDLEvent.wheel.x;
|
||||||
event.wheel.y = SDLEvent.wheel.y;
|
event.wheel.y = SDLEvent.wheel.y;
|
||||||
processEvent( &event );
|
processEventForWindow( &event );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SDL_EVENT_JOYSTICK_AXIS_MOTION: {
|
case SDL_EVENT_JOYSTICK_AXIS_MOTION: {
|
||||||
@@ -476,17 +468,8 @@ void InputSDL::sendEvent( const SDL_Event& SDLEvent ) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EE::Window::Window* win = nullptr;
|
if ( InputEvent::NoEvent != event.Type )
|
||||||
|
processEventForWindow( &event );
|
||||||
if ( InputEvent::NoEvent != event.Type ) {
|
|
||||||
if ( event.WinID == mWindow->getWindowID() || event.WinID == 0 ) {
|
|
||||||
processEvent( &event );
|
|
||||||
} else if ( ( win = EE::Window::Engine::instance()->getWindowID( event.WinID ) ) ) {
|
|
||||||
win->getInput()->processEvent( &event );
|
|
||||||
} else {
|
|
||||||
processEvent( &event );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In SDL3, drop event data is managed by SDL, do not free
|
// In SDL3, drop event data is managed by SDL, do not free
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,6 +291,7 @@ void WindowSDL::makeCurrent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WindowSDL::close() {
|
void WindowSDL::close() {
|
||||||
|
if ( !getDeferNativeResourceDestructionOnClose() )
|
||||||
destroySDLResources();
|
destroySDLResources();
|
||||||
Window::close();
|
Window::close();
|
||||||
}
|
}
|
||||||
@@ -513,6 +514,22 @@ void WindowSDL::setSize( Uint32 width, Uint32 height, bool windowed ) {
|
|||||||
sendVideoResizeCb();
|
sendVideoResizeCb();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WindowSDL::setMinimumSize( Uint32 width, Uint32 height ) {
|
||||||
|
SDL_SetWindowMinimumSize( mSDLWindow, static_cast<int>( width ), static_cast<int>( height ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowSDL::setModalFor( Window* parent ) {
|
||||||
|
auto* parentWindow = dynamic_cast<WindowSDL*>( parent );
|
||||||
|
if ( nullptr == parent ) {
|
||||||
|
if ( !SDL_SetWindowModal( mSDLWindow, false ) )
|
||||||
|
return false;
|
||||||
|
return SDL_SetWindowParent( mSDLWindow, nullptr );
|
||||||
|
}
|
||||||
|
if ( nullptr == parentWindow || !SDL_SetWindowParent( mSDLWindow, parentWindow->mSDLWindow ) )
|
||||||
|
return false;
|
||||||
|
return SDL_SetWindowModal( mSDLWindow, true );
|
||||||
|
}
|
||||||
|
|
||||||
void WindowSDL::swapBuffers() {
|
void WindowSDL::swapBuffers() {
|
||||||
SDL_GL_SwapWindow( mSDLWindow );
|
SDL_GL_SwapWindow( mSDLWindow );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ class EE_API WindowSDL : public Window {
|
|||||||
|
|
||||||
void setSize( Uint32 width, Uint32 height, bool windowed );
|
void setSize( Uint32 width, Uint32 height, bool windowed );
|
||||||
|
|
||||||
|
/** @copydoc EE::Window::Window::setMinimumSize() */
|
||||||
|
void setMinimumSize( Uint32 width, Uint32 height );
|
||||||
|
|
||||||
|
/** @copydoc EE::Window::Window::setModalFor() */
|
||||||
|
bool setModalFor( Window* parent );
|
||||||
|
|
||||||
std::vector<DisplayMode> getDisplayModes() const;
|
std::vector<DisplayMode> getDisplayModes() const;
|
||||||
|
|
||||||
void setGamma( Float Red, Float Green, Float Blue );
|
void setGamma( Float Red, Float Green, Float Blue );
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include <eepp/window/backend/SDL3/platformhelpersdl3.hpp>
|
#include <eepp/window/backend/SDL3/platformhelpersdl3.hpp>
|
||||||
#endif
|
#endif
|
||||||
#include <eepp/window/engine.hpp>
|
#include <eepp/window/engine.hpp>
|
||||||
|
#include <eepp/window/input.hpp>
|
||||||
#include <eepp/window/runtime.hpp>
|
#include <eepp/window/runtime.hpp>
|
||||||
#include <eepp/window/terminal/terminalruntime.hpp>
|
#include <eepp/window/terminal/terminalruntime.hpp>
|
||||||
|
|
||||||
@@ -54,6 +55,21 @@ using namespace EE::Graphics;
|
|||||||
|
|
||||||
namespace EE { namespace Window {
|
namespace EE { namespace Window {
|
||||||
|
|
||||||
|
Engine::WindowContext::WindowContext( Engine* engine, EE::Window::Window* window ) :
|
||||||
|
mEngine( engine ), mPreviousWindow( engine->getCurrentWindow() ) {
|
||||||
|
mEngine->setCurrentWindow( window );
|
||||||
|
}
|
||||||
|
|
||||||
|
Engine::WindowContext::~WindowContext() {
|
||||||
|
if ( mActive )
|
||||||
|
mEngine->setCurrentWindow( mPreviousWindow );
|
||||||
|
}
|
||||||
|
|
||||||
|
Engine::WindowContext::WindowContext( WindowContext&& other ) noexcept :
|
||||||
|
mEngine( other.mEngine ), mPreviousWindow( other.mPreviousWindow ), mActive( other.mActive ) {
|
||||||
|
other.mActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
void configureRuntimeVideoDriver() {
|
void configureRuntimeVideoDriver() {
|
||||||
@@ -245,6 +261,7 @@ EE::Window::Window* Engine::createDefaultWindow( const WindowSettings& Settings,
|
|||||||
}
|
}
|
||||||
|
|
||||||
EE::Window::Window* Engine::createWindow( WindowSettings Settings, ContextSettings Context ) {
|
EE::Window::Window* Engine::createWindow( WindowSettings Settings, ContextSettings Context ) {
|
||||||
|
const bool firstWindow = mWindows.empty();
|
||||||
if ( Runtime::mode() == RuntimeMode::Terminal && !mWindows.empty() ) {
|
if ( Runtime::mode() == RuntimeMode::Terminal && !mWindows.empty() ) {
|
||||||
Log::error( "Terminal runtime currently supports one top-level Window" );
|
Log::error( "Terminal runtime currently supports one top-level Window" );
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -287,7 +304,7 @@ EE::Window::Window* Engine::createWindow( WindowSettings Settings, ContextSettin
|
|||||||
|
|
||||||
mWindows.insert( { mWindow->getWindowID(), mWindow } );
|
mWindows.insert( { mWindow->getWindowID(), mWindow } );
|
||||||
|
|
||||||
if ( Settings.PixelDensity > 0 )
|
if ( firstWindow && Settings.PixelDensity > 0 )
|
||||||
PixelDensity::setPixelDensity( Settings.PixelDensity );
|
PixelDensity::setPixelDensity( Settings.PixelDensity );
|
||||||
|
|
||||||
return window;
|
return window;
|
||||||
@@ -297,11 +314,9 @@ void Engine::destroyWindow( EE::Window::Window* window ) {
|
|||||||
mWindows.erase( window->getWindowID() );
|
mWindows.erase( window->getWindowID() );
|
||||||
|
|
||||||
if ( window == mWindow ) {
|
if ( window == mWindow ) {
|
||||||
if ( mWindows.size() > 0 ) {
|
|
||||||
mWindow = mWindows.begin()->second;
|
|
||||||
} else {
|
|
||||||
mWindow = NULL;
|
mWindow = NULL;
|
||||||
}
|
if ( !mWindows.empty() )
|
||||||
|
setCurrentWindow( mWindows.begin()->second );
|
||||||
}
|
}
|
||||||
|
|
||||||
eeSAFE_DELETE( window );
|
eeSAFE_DELETE( window );
|
||||||
@@ -329,17 +344,40 @@ EE::Window::Window* Engine::getWindowID( const Uint32& winID ) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Engine::updateInput() {
|
||||||
|
if ( !mWindow )
|
||||||
|
return;
|
||||||
|
for ( auto& window : mWindows ) {
|
||||||
|
if ( window.second != mWindow )
|
||||||
|
window.second->getInput()->beginInputFrame();
|
||||||
|
}
|
||||||
|
mWindow->getInput()->update();
|
||||||
|
for ( auto& window : mWindows ) {
|
||||||
|
if ( window.second != mWindow )
|
||||||
|
window.second->getInput()->endInputFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EE::Window::Window* Engine::getCurrentWindow() const {
|
EE::Window::Window* Engine::getCurrentWindow() const {
|
||||||
return mWindow;
|
return mWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::setCurrentWindow( EE::Window::Window* window ) {
|
void Engine::setCurrentWindow( EE::Window::Window* window ) {
|
||||||
if ( NULL != window && window != mWindow ) {
|
if ( window != mWindow ) {
|
||||||
mWindow = window;
|
mWindow = window;
|
||||||
|
if ( mWindow ) {
|
||||||
mWindow->setCurrent();
|
mWindow->setCurrent();
|
||||||
|
// Renderer state caches are shared by all windows, while the OpenGL bindings and state
|
||||||
|
// they mirror belong to each context. Reapply that cached state to the new context.
|
||||||
|
if ( Renderer::existsSingleton() )
|
||||||
|
Renderer::instance()->onContextChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Engine::WindowContext Engine::makeWindowCurrent( EE::Window::Window* window ) {
|
||||||
|
return WindowContext( this, window );
|
||||||
|
}
|
||||||
|
|
||||||
Uint32 Engine::getWindowCount() const {
|
Uint32 Engine::getWindowCount() const {
|
||||||
return mWindows.size();
|
return mWindows.size();
|
||||||
@@ -468,7 +506,7 @@ void Engine::disableSharedGLContext() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Engine::isSharedGLContextEnabled() {
|
bool Engine::isSharedGLContextEnabled() {
|
||||||
return mSharedGLContext && mWindow->isThreadedGLContext();
|
return mSharedGLContext && mWindow && mWindow->isThreadedGLContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Engine::isThreaded() {
|
bool Engine::isThreaded() {
|
||||||
|
|||||||
@@ -31,6 +31,18 @@ Input::~Input() {
|
|||||||
eeSAFE_DELETE( mJoystickManager );
|
eeSAFE_DELETE( mJoystickManager );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Input::beginInputFrame() {
|
||||||
|
cleanStates();
|
||||||
|
++mEventsSentId;
|
||||||
|
drainQueuedEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Input::endInputFrame() {
|
||||||
|
InputEvent endProcessingEvent;
|
||||||
|
endProcessingEvent.Type = InputEvent::EventsSent;
|
||||||
|
processEvent( &endProcessingEvent );
|
||||||
|
}
|
||||||
|
|
||||||
void Input::cleanStates() {
|
void Input::cleanStates() {
|
||||||
memset( mScancodeUp, 0, EE_KEYS_SPACE );
|
memset( mScancodeUp, 0, EE_KEYS_SPACE );
|
||||||
|
|
||||||
@@ -229,6 +241,16 @@ void Input::processEvent( InputEvent* Event ) {
|
|||||||
sendEvent( Event );
|
sendEvent( Event );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Input::processEventForWindow( InputEvent* Event ) {
|
||||||
|
if ( Event->WinID == 0 || Event->WinID == mWindow->getWindowID() ) {
|
||||||
|
processEvent( Event );
|
||||||
|
} else if ( auto* window = Engine::instance()->getWindowID( Event->WinID ) ) {
|
||||||
|
window->getInput()->processEvent( Event );
|
||||||
|
} else {
|
||||||
|
processEvent( Event );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Input::enqueueEvent( InputEvent event ) {
|
bool Input::enqueueEvent( InputEvent event ) {
|
||||||
static constexpr size_t MaxInjectedEvents = 4096;
|
static constexpr size_t MaxInjectedEvents = 4096;
|
||||||
std::lock_guard<std::mutex> lock( mInjectedEventsMutex );
|
std::lock_guard<std::mutex> lock( mInjectedEventsMutex );
|
||||||
|
|||||||
@@ -75,6 +75,12 @@ void Window::setSize( Uint32 Width, Uint32 Height ) {
|
|||||||
setSize( Width, Height, isWindowed() );
|
setSize( Width, Height, isWindowed() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::setMinimumSize( Uint32, Uint32 ) {}
|
||||||
|
|
||||||
|
bool Window::setModalFor( Window* ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool Window::isWindowed() const {
|
bool Window::isWindowed() const {
|
||||||
return 0 != !( mWindow.WindowConfig.Style & WindowStyle::Fullscreen );
|
return 0 != !( mWindow.WindowConfig.Style & WindowStyle::Fullscreen );
|
||||||
}
|
}
|
||||||
@@ -390,6 +396,14 @@ void Window::close() {
|
|||||||
mWindow.Created = false;
|
mWindow.Created = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::setDeferNativeResourceDestructionOnClose( bool defer ) {
|
||||||
|
mDeferNativeResourceDestructionOnClose = defer;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Window::getDeferNativeResourceDestructionOnClose() const {
|
||||||
|
return mDeferNativeResourceDestructionOnClose;
|
||||||
|
}
|
||||||
|
|
||||||
void Window::setFrameRateLimit( Uint32 FrameRateLimit ) {
|
void Window::setFrameRateLimit( Uint32 FrameRateLimit ) {
|
||||||
if ( FrameRateLimit == ContextSettings::FrameRateLimitScreenRefreshRate ) {
|
if ( FrameRateLimit == ContextSettings::FrameRateLimitScreenRefreshRate ) {
|
||||||
Display* currentDisplay = nullptr;
|
Display* currentDisplay = nullptr;
|
||||||
@@ -504,6 +518,10 @@ void Window::clear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Window::display( bool clear ) {
|
void Window::display( bool clear ) {
|
||||||
|
display( clear, true );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::display( bool clear, bool limitFrameRate ) {
|
||||||
GlobalBatchRenderer::instance()->draw();
|
GlobalBatchRenderer::instance()->draw();
|
||||||
if ( mFramePresenter )
|
if ( mFramePresenter )
|
||||||
mFramePresenter->present( *this );
|
mFramePresenter->present( *this );
|
||||||
@@ -526,6 +544,7 @@ void Window::display( bool clear ) {
|
|||||||
|
|
||||||
updateElapsedTime();
|
updateElapsedTime();
|
||||||
|
|
||||||
|
if ( limitFrameRate )
|
||||||
limitFps();
|
limitFps();
|
||||||
|
|
||||||
calculateFps();
|
calculateFps();
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#include <eepp/ee.hpp>
|
||||||
|
#include <eepp/ui/uifiledialog.hpp>
|
||||||
|
#include <eepp/ui/uimessagebox.hpp>
|
||||||
|
|
||||||
|
EE_MAIN_FUNC int main( int, char** ) {
|
||||||
|
UIApplication app( { 640, 480, "eepp - UIApplication Multi-window" } );
|
||||||
|
auto* layout = app.getUI()->loadLayoutFromString( R"xml(
|
||||||
|
<RelativeLayout layout_width="match_parent" layout_height="match_parent">
|
||||||
|
<PushButton id="open_file" layout_width="wrap_content" layout_height="wrap_content"
|
||||||
|
layout_gravity="center" text="Open File..." />
|
||||||
|
</RelativeLayout>
|
||||||
|
)xml" );
|
||||||
|
|
||||||
|
auto* openFile = layout->querySelector<UIPushButton>( "#open_file" );
|
||||||
|
openFile->on( Event::MouseClick, [&app]( const Event* event ) {
|
||||||
|
if ( !( event->asMouseEvent()->getFlags() & EE_BUTTON_LMASK ) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto* dialog = UIFileDialog::NewInApplicationWindow(
|
||||||
|
app, { 640, 400, "Open File" }, UIFileDialog::DefaultFlags, "*",
|
||||||
|
FileSystem::getCurrentWorkingDirectory() );
|
||||||
|
if ( nullptr == dialog )
|
||||||
|
return;
|
||||||
|
|
||||||
|
dialog->on( Event::OpenFile, [&app]( const Event* event ) {
|
||||||
|
const auto path = event->getNode()->asType<UIFileDialog>()->getFullPath();
|
||||||
|
UIMessageBox::NewInApplicationWindow( app, { 520, 100, "File Opened" },
|
||||||
|
UIMessageBox::OK,
|
||||||
|
String::format( "File %s opened", path ) );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
return app.run();
|
||||||
|
}
|
||||||
@@ -2,19 +2,25 @@
|
|||||||
|
|
||||||
#include <eepp/graphics/fontfamily.hpp>
|
#include <eepp/graphics/fontfamily.hpp>
|
||||||
#include <eepp/graphics/fonttruetype.hpp>
|
#include <eepp/graphics/fonttruetype.hpp>
|
||||||
|
#include <eepp/graphics/texturefactory.hpp>
|
||||||
#include <eepp/scene/eventdispatcher.hpp>
|
#include <eepp/scene/eventdispatcher.hpp>
|
||||||
#include <eepp/scene/scenemanager.hpp>
|
#include <eepp/scene/scenemanager.hpp>
|
||||||
#include <eepp/system/filesystem.hpp>
|
#include <eepp/system/filesystem.hpp>
|
||||||
#include <eepp/system/sys.hpp>
|
#include <eepp/system/sys.hpp>
|
||||||
|
#include <eepp/ui/uiapplication.hpp>
|
||||||
#include <eepp/ui/uifiledialog.hpp>
|
#include <eepp/ui/uifiledialog.hpp>
|
||||||
|
#include <eepp/ui/uiiconthememanager.hpp>
|
||||||
|
#include <eepp/ui/uimessagebox.hpp>
|
||||||
#include <eepp/ui/uiroot.hpp>
|
#include <eepp/ui/uiroot.hpp>
|
||||||
#include <eepp/ui/uiscenenode.hpp>
|
#include <eepp/ui/uiscenenode.hpp>
|
||||||
|
#include <eepp/ui/uitextinput.hpp>
|
||||||
#include <eepp/ui/uithememanager.hpp>
|
#include <eepp/ui/uithememanager.hpp>
|
||||||
#include <eepp/ui/uiwidget.hpp>
|
#include <eepp/ui/uiwidget.hpp>
|
||||||
#include <eepp/ui/uiwindow.hpp>
|
#include <eepp/ui/uiwindow.hpp>
|
||||||
#include <eepp/window/cursor.hpp>
|
#include <eepp/window/cursor.hpp>
|
||||||
#include <eepp/window/engine.hpp>
|
#include <eepp/window/engine.hpp>
|
||||||
#include <eepp/window/input.hpp>
|
#include <eepp/window/input.hpp>
|
||||||
|
#include <eepp/window/runtime.hpp>
|
||||||
|
|
||||||
using namespace EE;
|
using namespace EE;
|
||||||
using namespace EE::Graphics;
|
using namespace EE::Graphics;
|
||||||
@@ -29,6 +35,231 @@ UTEST( UISceneNode, CssPointerCursorUsesHandCursor ) {
|
|||||||
EXPECT_STREQ( Cursor::toName( Cursor::Arrow ), "arrow" );
|
EXPECT_STREQ( Cursor::toName( Cursor::Arrow ), "arrow" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UTEST( UISceneNode, ScopedContextBindsNodesAndRestoresNestedScene ) {
|
||||||
|
auto* engine = Engine::instance();
|
||||||
|
auto* window = engine->getCurrentWindow();
|
||||||
|
if ( !window ) {
|
||||||
|
window =
|
||||||
|
engine->createWindow( WindowSettings( 320, 240, "UI Context Test", WindowStyle::Default,
|
||||||
|
WindowBackend::Default, 32, {}, 1, false, true ),
|
||||||
|
ContextSettings( false, 0, 0, GLv_default, true, false ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* sceneA = UISceneNode::New( window );
|
||||||
|
auto* sceneB = UISceneNode::New( window );
|
||||||
|
auto* sceneManager = SceneManager::instance();
|
||||||
|
sceneManager->add( sceneA );
|
||||||
|
sceneManager->add( sceneB );
|
||||||
|
sceneManager->setCurrentUISceneNode( sceneA );
|
||||||
|
|
||||||
|
{
|
||||||
|
auto contextA = sceneA->makeCurrent();
|
||||||
|
EXPECT_EQ( sceneManager->getUISceneNode(), sceneA );
|
||||||
|
auto* nodeA = UIWidget::New();
|
||||||
|
EXPECT_EQ( nodeA->getUISceneNode(), sceneA );
|
||||||
|
|
||||||
|
{
|
||||||
|
auto contextB = sceneB->makeCurrent();
|
||||||
|
EXPECT_EQ( sceneManager->getUISceneNode(), sceneB );
|
||||||
|
auto* nodeB = UIWidget::New();
|
||||||
|
EXPECT_EQ( nodeB->getUISceneNode(), sceneB );
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ( sceneManager->getUISceneNode(), sceneA );
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ( sceneManager->getUISceneNode(), sceneA );
|
||||||
|
sceneManager->remove( sceneB );
|
||||||
|
sceneManager->remove( sceneA );
|
||||||
|
eeDelete( sceneB );
|
||||||
|
eeDelete( sceneA );
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestUIApplication : public UIApplication {
|
||||||
|
public:
|
||||||
|
using UIApplication::UIApplication;
|
||||||
|
void tickOnce() { tick(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
UTEST( UIApplication, CreatesSecondaryWindowWithoutChangingAmbientScene ) {
|
||||||
|
TestUIApplication app(
|
||||||
|
WindowSettings( 320, 240, "Primary UI Context", WindowStyle::Default,
|
||||||
|
WindowBackend::Default, 32, {}, 1, false, true ),
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1.5f,
|
||||||
|
true ),
|
||||||
|
ContextSettings( false, 0, 0, GLv_default, true, false ) );
|
||||||
|
ASSERT_TRUE( app.getWindow() != nullptr );
|
||||||
|
ASSERT_TRUE( app.getUI() != nullptr );
|
||||||
|
EXPECT_EQ( PixelDensity::getPixelDensity(), 1.5f );
|
||||||
|
ASSERT_TRUE( app.getUI()->getUIIconThemeManager()->getCurrentTheme() != nullptr );
|
||||||
|
EXPECT_TRUE( app.getUI()->getUIIconThemeManager()->findIcon( "go-up" ) != nullptr );
|
||||||
|
EXPECT_EQ( app.getQuitPolicy(), UIApplication::QuitPolicy::OnPrimaryWindowClosed );
|
||||||
|
if ( Runtime::mode() == RuntimeMode::Terminal ) {
|
||||||
|
auto* inApplicationWindow = UIWindow::NewInApplicationWindow(
|
||||||
|
app, WindowSettings( 240, 180, "Terminal In-Application Window" ),
|
||||||
|
UIWindow::RELATIVE_LAYOUT, UIWindow::StyleConfig(), ContextSettings(), true );
|
||||||
|
ASSERT_TRUE( inApplicationWindow != nullptr );
|
||||||
|
EXPECT_EQ( app.getWindowCount(), 1u );
|
||||||
|
EXPECT_EQ( inApplicationWindow->getUISceneNode(), app.getUI() );
|
||||||
|
EXPECT_TRUE( inApplicationWindow->isModal() );
|
||||||
|
EXPECT_FALSE( inApplicationWindow->getWinFlags() & UI_WIN_NO_DECORATION );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* primaryWindow = app.getWindow();
|
||||||
|
auto* secondaryUI =
|
||||||
|
app.createWindow( WindowSettings( 240, 180, "Secondary UI Context", WindowStyle::Default,
|
||||||
|
WindowBackend::Default, 32, {}, 1, false, true ),
|
||||||
|
ContextSettings( false, 0, 0, GLv_default, true, false ) );
|
||||||
|
ASSERT_TRUE( secondaryUI != nullptr );
|
||||||
|
EXPECT_EQ( PixelDensity::getPixelDensity(), 1.5f );
|
||||||
|
EXPECT_EQ( app.getWindowCount(), 2u );
|
||||||
|
EXPECT_EQ( app.getUI( secondaryUI->getWindow() ), secondaryUI );
|
||||||
|
EXPECT_EQ( secondaryUI->getUIIconThemeManager()->getCurrentTheme(),
|
||||||
|
app.getUI()->getUIIconThemeManager()->getCurrentTheme() );
|
||||||
|
EXPECT_EQ( Engine::instance()->getCurrentWindow(), primaryWindow );
|
||||||
|
EXPECT_EQ( SceneManager::instance()->getUISceneNode(), app.getUI() );
|
||||||
|
TextureFactory::instance()->setCurrentTexture( 123, 0 );
|
||||||
|
Engine::instance()->setCurrentWindow( secondaryUI->getWindow() );
|
||||||
|
EXPECT_EQ( TextureFactory::instance()->getCurrentTexture( 0 ), -1 );
|
||||||
|
Engine::instance()->setCurrentWindow( primaryWindow );
|
||||||
|
|
||||||
|
{
|
||||||
|
auto context = secondaryUI->makeCurrent();
|
||||||
|
auto* widget = UIWidget::New();
|
||||||
|
EXPECT_EQ( widget->getUISceneNode(), secondaryUI );
|
||||||
|
|
||||||
|
auto* textInput = UITextInput::New();
|
||||||
|
textInput->setParent( secondaryUI->getRoot() );
|
||||||
|
textInput->setFocus();
|
||||||
|
InputEvent textEvent;
|
||||||
|
textEvent.Type = InputEvent::TextInput;
|
||||||
|
textEvent.WinID = secondaryUI->getWindow()->getWindowID();
|
||||||
|
textEvent.text.text = 'x';
|
||||||
|
textEvent.text.timestamp = 1;
|
||||||
|
secondaryUI->getWindow()->getInput()->beginInputFrame();
|
||||||
|
primaryWindow->getInput()->processEventForWindow( &textEvent );
|
||||||
|
secondaryUI->getWindow()->getInput()->endInputFrame();
|
||||||
|
EXPECT_STREQ( textInput->getText().toUtf8().c_str(), "x" );
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ( SceneManager::instance()->getUISceneNode(), app.getUI() );
|
||||||
|
|
||||||
|
const Vector2i primaryPosition = primaryWindow->getPosition();
|
||||||
|
const Sizei primaryScreenSize = primaryWindow->getSizeInScreenCoordinates();
|
||||||
|
auto* fileDialog = UIFileDialog::NewInApplicationWindow(
|
||||||
|
app,
|
||||||
|
WindowSettings( 640, 400, "File Dialog UI Context",
|
||||||
|
WindowStyle::Titlebar | WindowStyle::Resize, WindowBackend::Default, 32, {},
|
||||||
|
1, false, true ),
|
||||||
|
UIFileDialog::DefaultFlags, "*", FileSystem::getCurrentWorkingDirectory(),
|
||||||
|
ContextSettings( false, 0, 0, GLv_default, true, false ), true,
|
||||||
|
UIWindow::ApplicationWindowPosition::CenteredOnPrimary );
|
||||||
|
ASSERT_TRUE( fileDialog != nullptr );
|
||||||
|
EXPECT_EQ( app.getWindowCount(), 3u );
|
||||||
|
EXPECT_NE( fileDialog->getUISceneNode(), app.getUI() );
|
||||||
|
EXPECT_NE( fileDialog->getUISceneNode(), secondaryUI );
|
||||||
|
EXPECT_TRUE( fileDialog->getWinFlags() & UI_WIN_NO_DECORATION );
|
||||||
|
EXPECT_EQ( fileDialog->getLayoutWidthPolicy(), SizePolicy::MatchParent );
|
||||||
|
EXPECT_EQ( fileDialog->getLayoutHeightPolicy(), SizePolicy::MatchParent );
|
||||||
|
EXPECT_EQ( Engine::instance()->getCurrentWindow(), primaryWindow );
|
||||||
|
EXPECT_EQ( SceneManager::instance()->getUISceneNode(), app.getUI() );
|
||||||
|
EXPECT_TRUE( fileDialog->getParent()->isType( UI_TYPE_RELATIVE_LAYOUT ) );
|
||||||
|
const Sizei dialogWindowSize =
|
||||||
|
fileDialog->getUISceneNode()->getWindow()->getSizeInScreenCoordinates();
|
||||||
|
const Sizef dialogMinimumSizePx = PixelDensity::dpToPx( fileDialog->getMinWindowSize() );
|
||||||
|
const Float dialogWindowScale = fileDialog->getUISceneNode()->getWindow()->getScale();
|
||||||
|
EXPECT_TRUE( dialogWindowSize.getWidth() >=
|
||||||
|
eeceil( dialogMinimumSizePx.getWidth() / dialogWindowScale ) );
|
||||||
|
EXPECT_TRUE( dialogWindowSize.getHeight() >=
|
||||||
|
eeceil( dialogMinimumSizePx.getHeight() / dialogWindowScale ) );
|
||||||
|
const Vector2i dialogPosition = fileDialog->getUISceneNode()->getWindow()->getPosition();
|
||||||
|
const Vector2i primaryCenter( primaryPosition.x + primaryScreenSize.getWidth() / 2,
|
||||||
|
primaryPosition.y + primaryScreenSize.getHeight() / 2 );
|
||||||
|
auto* displayManager = Engine::instance()->getDisplayManager();
|
||||||
|
for ( int i = 0; i < displayManager->getDisplayCount(); ++i ) {
|
||||||
|
auto* display = displayManager->getDisplayIndex( i );
|
||||||
|
if ( nullptr == display || !display->getBounds().contains( primaryCenter ) )
|
||||||
|
continue;
|
||||||
|
const Rect usableBounds = display->getUsableBounds();
|
||||||
|
const Rect border = fileDialog->getUISceneNode()->getWindow()->getBorderSize();
|
||||||
|
if ( dialogWindowSize.getWidth() + border.Left + border.Right <= usableBounds.getWidth() ) {
|
||||||
|
EXPECT_TRUE( dialogPosition.x - border.Left >= usableBounds.Left );
|
||||||
|
EXPECT_TRUE( dialogPosition.x + dialogWindowSize.getWidth() + border.Right <=
|
||||||
|
usableBounds.Right );
|
||||||
|
}
|
||||||
|
if ( dialogWindowSize.getHeight() + border.Top + border.Bottom <=
|
||||||
|
usableBounds.getHeight() ) {
|
||||||
|
EXPECT_TRUE( dialogPosition.y - border.Top >= usableBounds.Top );
|
||||||
|
EXPECT_TRUE( dialogPosition.y + dialogWindowSize.getHeight() + border.Bottom <=
|
||||||
|
usableBounds.Bottom );
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* primaryFont = app.getUI()->getUIThemeManager()->getDefaultFont();
|
||||||
|
ASSERT_TRUE( primaryFont != nullptr );
|
||||||
|
fileDialog->getUISceneNode()->getWindow()->close();
|
||||||
|
app.tickOnce();
|
||||||
|
EXPECT_EQ( app.getWindowCount(), 2u );
|
||||||
|
EXPECT_EQ( app.getUI()->getUIThemeManager()->getDefaultFont(), primaryFont );
|
||||||
|
auto* genericWindow = UIWindow::NewInApplicationWindow(
|
||||||
|
app,
|
||||||
|
WindowSettings( 400, 240, "Generic UI Window", WindowStyle::Default, WindowBackend::Default,
|
||||||
|
32, {}, 1, false, true ),
|
||||||
|
UIWindow::RELATIVE_LAYOUT, UIWindow::StyleConfig(),
|
||||||
|
ContextSettings( false, 0, 0, GLv_default, true, false ), true );
|
||||||
|
ASSERT_TRUE( genericWindow != nullptr );
|
||||||
|
EXPECT_EQ( app.getWindowCount(), 3u );
|
||||||
|
genericWindow->getUISceneNode()->getWindow()->close();
|
||||||
|
app.tickOnce();
|
||||||
|
EXPECT_EQ( app.getWindowCount(), 2u );
|
||||||
|
auto* messageBox = UIMessageBox::NewInApplicationWindow(
|
||||||
|
app,
|
||||||
|
WindowSettings( 400, 180, "Message Box UI Context", WindowStyle::Default,
|
||||||
|
WindowBackend::Default, 32, {}, 1, false, true ),
|
||||||
|
UIMessageBox::OK, "Native modal message", UI_MESSAGE_BOX_DEFAULT_FLAGS,
|
||||||
|
ContextSettings( false, 0, 0, GLv_default, true, false ) );
|
||||||
|
ASSERT_TRUE( messageBox != nullptr );
|
||||||
|
EXPECT_EQ( app.getWindowCount(), 3u );
|
||||||
|
EXPECT_FALSE( messageBox->isModal() );
|
||||||
|
EXPECT_TRUE( messageBox->getModalWidget() == nullptr );
|
||||||
|
const Sizei messageBoxWindowSize =
|
||||||
|
messageBox->getUISceneNode()->getWindow()->getSizeInScreenCoordinates();
|
||||||
|
const Uint32 messageBoxWindowId = messageBox->getUISceneNode()->getWindow()->getWindowID();
|
||||||
|
EXPECT_EQ( messageBoxWindowSize.getWidth(), 400 );
|
||||||
|
EXPECT_EQ( messageBoxWindowSize.getHeight(), 180 );
|
||||||
|
bool messageBoxConfirmed = false;
|
||||||
|
messageBox->on( Event::OnConfirm,
|
||||||
|
[&messageBoxConfirmed]( const Event* ) { messageBoxConfirmed = true; } );
|
||||||
|
messageBox->getUISceneNode()->getUIThemeManager()->setDefaultEffectsEnabled( true );
|
||||||
|
messageBox->getEventDispatcher()->sendMsg( messageBox->getButtonOK(), NodeMessage::MouseClick,
|
||||||
|
EE_BUTTON_LMASK );
|
||||||
|
EXPECT_TRUE( messageBoxConfirmed );
|
||||||
|
EXPECT_FALSE( messageBox->getUISceneNode()->getWindow()->isVisible() );
|
||||||
|
for ( int i = 0; i < 20 && app.getWindowCount() == 3u; ++i ) {
|
||||||
|
Sys::sleep( Milliseconds( 5 ) );
|
||||||
|
app.tickOnce();
|
||||||
|
}
|
||||||
|
EXPECT_EQ( app.getWindowCount(), 2u );
|
||||||
|
EXPECT_TRUE( Engine::instance()->getWindowID( messageBoxWindowId ) == nullptr );
|
||||||
|
|
||||||
|
auto* reopenedDialog = UIFileDialog::NewInApplicationWindow(
|
||||||
|
app,
|
||||||
|
WindowSettings( 640, 400, "Reopened File Dialog", WindowStyle::Default,
|
||||||
|
WindowBackend::Default, 32, {}, 1, false, true ),
|
||||||
|
UIFileDialog::DefaultFlags, "*", FileSystem::getCurrentWorkingDirectory(),
|
||||||
|
ContextSettings( false, 0, 0, GLv_default, true, false ) );
|
||||||
|
ASSERT_TRUE( reopenedDialog != nullptr );
|
||||||
|
EXPECT_EQ( app.getWindowCount(), 3u );
|
||||||
|
EXPECT_EQ( reopenedDialog->getUISceneNode()->getUIThemeManager()->getDefaultFont(),
|
||||||
|
primaryFont );
|
||||||
|
|
||||||
|
primaryWindow->close();
|
||||||
|
app.tickOnce();
|
||||||
|
EXPECT_EQ( app.getWindowCount(), 0u );
|
||||||
|
}
|
||||||
|
|
||||||
UTEST( Node, DescendantWorldBoundsRefreshAfterDirtyAncestorMoves ) {
|
UTEST( Node, DescendantWorldBoundsRefreshAfterDirtyAncestorMoves ) {
|
||||||
Node* parent = Node::New();
|
Node* parent = Node::New();
|
||||||
Node* child = Node::New();
|
Node* child = Node::New();
|
||||||
@@ -79,9 +310,7 @@ class InvalidationTestSceneNode : public UISceneNode {
|
|||||||
|
|
||||||
size_t processedStyleRootCount() const { return mDirtyStylesSnapshot.size(); }
|
size_t processedStyleRootCount() const { return mDirtyStylesSnapshot.size(); }
|
||||||
|
|
||||||
UIWidget* processedStyleRoot( size_t index ) const {
|
UIWidget* processedStyleRoot( size_t index ) const { return mDirtyStylesSnapshot[index].first; }
|
||||||
return mDirtyStylesSnapshot[index].first;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool processedStyleRootDisablesAnimations( size_t index ) const {
|
bool processedStyleRootDisablesAnimations( size_t index ) const {
|
||||||
return mDirtyStylesSnapshot[index].second;
|
return mDirtyStylesSnapshot[index].second;
|
||||||
|
|||||||
Reference in New Issue
Block a user