diff --git a/libraries/ZWidget/CMakeLists.txt b/libraries/ZWidget/CMakeLists.txt index 0959cc7f9..d65e5ccea 100644 --- a/libraries/ZWidget/CMakeLists.txt +++ b/libraries/ZWidget/CMakeLists.txt @@ -1,6 +1,22 @@ cmake_minimum_required(VERSION 3.11) project(zwidget) +if (UNIX AND NOT APPLE) + include(FindPkgConfig) + pkg_check_modules(DBUS dbus-1) + if (!DBUS_FOUND) + pkg_check_modules(DBUS REQUIRED dbus) # Fedora Linux looks for dbus instead + endif() + pkg_check_modules(WAYLANDPP + wayland-client + wayland-client++ + wayland-client-extra++ + wayland-client-unstable++ + wayland-cursor++ + xkbcommon>=0.5.0 + ) +endif() + set(ZWIDGET_SOURCES src/core/canvas.cpp src/core/font.cpp @@ -33,7 +49,15 @@ set(ZWIDGET_SOURCES src/widgets/listview/listview.cpp src/widgets/tabwidget/tabwidget.cpp src/window/window.cpp - src/systemdialogs/folder_browse_dialog.cpp + src/window/stub/stub_open_folder_dialog.cpp + src/window/stub/stub_open_folder_dialog.h + src/window/stub/stub_open_file_dialog.cpp + src/window/stub/stub_open_file_dialog.h + src/window/stub/stub_save_file_dialog.cpp + src/window/stub/stub_save_file_dialog.h + src/window/ztimer/ztimer.h + src/window/ztimer/ztimer.cpp + src/systemdialogs/open_folder_dialog.cpp src/systemdialogs/open_file_dialog.cpp src/systemdialogs/save_file_dialog.cpp ) @@ -66,22 +90,62 @@ set(ZWIDGET_INCLUDES include/zwidget/widgets/listview/listview.h include/zwidget/widgets/tabwidget/tabwidget.h include/zwidget/window/window.h - include/zwidget/systemdialogs/folder_browse_dialog.h + include/zwidget/window/x11nativehandle.h + include/zwidget/window/waylandnativehandle.h + include/zwidget/window/win32nativehandle.h + include/zwidget/window/sdl2nativehandle.h + include/zwidget/systemdialogs/open_folder_dialog.h include/zwidget/systemdialogs/open_file_dialog.h include/zwidget/systemdialogs/save_file_dialog.h ) set(ZWIDGET_WIN32_SOURCES - src/window/win32/win32displaywindow.cpp - src/window/win32/win32displaywindow.h + src/window/win32/win32_display_backend.cpp + src/window/win32/win32_display_backend.h + src/window/win32/win32_display_window.cpp + src/window/win32/win32_display_window.h + src/window/win32/win32_open_folder_dialog.cpp + src/window/win32/win32_open_folder_dialog.h + src/window/win32/win32_open_file_dialog.cpp + src/window/win32/win32_open_file_dialog.h + src/window/win32/win32_save_file_dialog.cpp + src/window/win32/win32_save_file_dialog.h + src/window/win32/win32_util.h +) + +set(ZWIDGET_DBUS_SOURCES + src/window/dbus/dbus_open_folder_dialog.cpp + src/window/dbus/dbus_open_folder_dialog.h + src/window/dbus/dbus_open_file_dialog.cpp + src/window/dbus/dbus_open_file_dialog.h + src/window/dbus/dbus_save_file_dialog.cpp + src/window/dbus/dbus_save_file_dialog.h ) set(ZWIDGET_COCOA_SOURCES ) set(ZWIDGET_SDL2_SOURCES - src/window/sdl2/sdl2displaywindow.cpp - src/window/sdl2/sdl2displaywindow.h + src/window/sdl2/sdl2_display_backend.cpp + src/window/sdl2/sdl2_display_backend.h + src/window/sdl2/sdl2_display_window.cpp + src/window/sdl2/sdl2_display_window.h +) + +set(ZWIDGET_X11_SOURCES + src/window/x11/x11_display_backend.cpp + src/window/x11/x11_display_backend.h + src/window/x11/x11_display_window.cpp + src/window/x11/x11_display_window.h +) + +set(ZWIDGET_WAYLAND_SOURCES + src/window/wayland/wayland_display_backend.cpp + src/window/wayland/wayland_display_backend.h + src/window/wayland/wayland_display_window.cpp + src/window/wayland/wayland_display_window.h + src/window/wayland/wl_fractional_scaling_protocol.cpp + src/window/wayland/wl_fractional_scaling_protocol.hpp ) source_group("src" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/.+") @@ -103,6 +167,12 @@ source_group("src\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_S source_group("src\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/listview/.+") source_group("src\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/tabwidget/.+") source_group("src\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/.+") +source_group("src\\window\\stub" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/stub/.+") +source_group("src\\window\\win32" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/win32/.+") +source_group("src\\window\\sdl2" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/sdl2/.+") +source_group("src\\window\\x11" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/x11/.+") +source_group("src\\window\\wayland" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/wayland/.+") +source_group("src\\window\\dbus" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/dbus/.+") source_group("src\\systemdialogs" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/systemdialogs/.+") source_group("include" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/.+") source_group("include\\core" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/core/.+") @@ -121,39 +191,62 @@ source_group("include\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRE source_group("include\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/listview/.+") source_group("include\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/tabwidget/.+") source_group("include\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/.+") -source_group("include\\window\\win32" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/win32/.+") -source_group("include\\window\\sdl2" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/sdl2/.+") source_group("include\\systemdialogs" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/systemdialogs/.+") -include_directories(include include/zwidget src) +# Include directory to external projects using zwidget +include_directories(include) + +# Internal include dirs for building zwidget +set(ZWIDGET_INCLUDE_DIRS include/zwidget src) + +set(ZWIDGET_COMPILE_OPTIONS) if(WIN32) set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_WIN32_SOURCES}) - add_definitions(-DUNICODE -D_UNICODE) - if(MINGW) - add_definitions(-DMINGW) + set(ZWIDGET_DEFINES -DUNICODE -D_UNICODE) + set(ZWIDGET_LINK_OPTIONS) + if(MSVC) + # Use all cores for compilation + set(ZWIDGET_COMPILE_OPTIONS ${ZWIDGET_COMPILE_OPTIONS} /MP) + + # Ignore specific warnings + #set(ZWIDGET_COMPILE_OPTIONS ${ZWIDGET_COMPILE_OPTIONS} /wd4244 /wd4267 /wd4005 /wd4018) + + # Don't slow down std containers in debug builds + set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES}) + + # Ignore warning about legacy CRT functions + #set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES} -D_CRT_SECURE_NO_WARNINGS) endif() elseif(APPLE) set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_COCOA_SOURCES}) set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl) - add_definitions(-DUNIX -D_UNIX) - add_link_options(-pthread) + set(ZWIDGET_DEFINES -DUNIX -D_UNIX) + set(ZWIDGET_LINK_OPTIONS -pthread) else() - set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_SDL2_SOURCES}) - set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl) - add_definitions(-DUNIX -D_UNIX) - add_link_options(-pthread) -endif() - -if(MSVC) - # Use all cores for compilation - set(CMAKE_CXX_FLAGS "/MP ${CMAKE_CXX_FLAGS}") - - # Ignore warnings in third party code - #set_source_files_properties(${ZWIDGET_SOURCES} PROPERTIES COMPILE_FLAGS "/wd4244 /wd4267 /wd4005 /wd4018 -D_CRT_SECURE_NO_WARNINGS") + set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_SDL2_SOURCES} ${ZWIDGET_X11_SOURCES}) + set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl -lX11) + set(ZWIDGET_DEFINES -DUNIX -D_UNIX -DUSE_SDL2 -DUSE_X11) + set(ZWIDGET_LINK_OPTIONS -pthread) + if (DBUS_FOUND) + set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_DBUS_SOURCES}) + set(ZWIDGET_INCLUDE_DIRS ${ZWIDGET_INCLUDE_DIRS} ${DBUS_INCLUDE_DIRS}) + set(ZWIDGET_LIBS ${ZWIDGET_LIBS} ${DBUS_LDFLAGS}) + set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES} -DUSE_DBUS) + endif() + if (WAYLANDPP_FOUND) + set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_WAYLAND_SOURCES}) + set(ZWIDGET_INCLUDE_DIRS ${ZWIDGET_INCLUDE_DIRS} ${WAYLANDPP_INCLUDE_DIRS}) + set(ZWIDGET_LIBS ${ZWIDGET_LIBS} ${WAYLANDPP_LDFLAGS}) + set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES} -DUSE_WAYLAND) + endif() endif() add_library(zwidget STATIC ${ZWIDGET_SOURCES} ${ZWIDGET_INCLUDES}) +target_compile_options(zwidget PRIVATE ${ZWIDGET_COMPILE_OPTIONS}) +target_compile_definitions(zwidget PRIVATE ${ZWIDGET_DEFINES}) +target_include_directories(zwidget PRIVATE ${ZWIDGET_INCLUDE_DIRS}) +target_link_options(zwidget PRIVATE ${ZWIDGET_LINK_OPTIONS}) target_link_libraries(zwidget ${ZWIDGET_LIBS}) set_target_properties(zwidget PROPERTIES CXX_STANDARD 17) diff --git a/libraries/ZWidget/include/zwidget/core/canvas.h b/libraries/ZWidget/include/zwidget/core/canvas.h index a546e62cc..c3ae7fea8 100644 --- a/libraries/ZWidget/include/zwidget/core/canvas.h +++ b/libraries/ZWidget/include/zwidget/core/canvas.h @@ -2,14 +2,26 @@ #include #include +#include +#include "image.h" +#include "rect.h" +#include class Font; -class Image; class Point; class Rect; class Colorf; class DisplayWindow; -struct VerticalTextPosition; +class CanvasFontGroup; + +class CanvasTexture +{ +public: + virtual ~CanvasTexture() = default; + + int Width = 0; + int Height = 0; +}; class FontMetrics { @@ -20,44 +32,85 @@ public: double height = 0.0; }; -class Canvas -{ -public: - static std::unique_ptr create(DisplayWindow* window); - - virtual ~Canvas() = default; - - virtual void begin(const Colorf& color) = 0; - virtual void end() = 0; - - virtual void begin3d() = 0; - virtual void end3d() = 0; - - virtual Point getOrigin() = 0; - virtual void setOrigin(const Point& origin) = 0; - - virtual void pushClip(const Rect& box) = 0; - virtual void popClip() = 0; - - virtual void fillRect(const Rect& box, const Colorf& color) = 0; - virtual void line(const Point& p0, const Point& p1, const Colorf& color) = 0; - - virtual void drawText(const Point& pos, const Colorf& color, const std::string& text) = 0; - virtual Rect measureText(const std::string& text) = 0; - virtual VerticalTextPosition verticalTextAlign() = 0; - - virtual void drawText(const std::shared_ptr& font, const Point& pos, const std::string& text, const Colorf& color) = 0; - virtual void drawTextEllipsis(const std::shared_ptr& font, const Point& pos, const Rect& clipBox, const std::string& text, const Colorf& color) = 0; - virtual Rect measureText(const std::shared_ptr& font, const std::string& text) = 0; - virtual FontMetrics getFontMetrics(const std::shared_ptr& font) = 0; - virtual int getCharacterIndex(const std::shared_ptr& font, const std::string& text, const Point& hitPoint) = 0; - - virtual void drawImage(const std::shared_ptr& image, const Point& pos) = 0; -}; - struct VerticalTextPosition { double top = 0.0; double baseline = 0.0; double bottom = 0.0; }; + +class Canvas +{ +public: + static std::unique_ptr create(); + + Canvas(); + virtual ~Canvas(); + + virtual void attach(DisplayWindow* window); + virtual void detach(); + + virtual void begin(const Colorf& color); + virtual void end() { } + + virtual void begin3d() { } + virtual void end3d() { } + + Point getOrigin(); + void setOrigin(const Point& origin); + + void pushClip(const Rect& box); + void popClip(); + + void fillRect(const Rect& box, const Colorf& color); + void line(const Point& p0, const Point& p1, const Colorf& color); + + void drawText(const Point& pos, const Colorf& color, const std::string& text); + Rect measureText(const std::string& text); + VerticalTextPosition verticalTextAlign(); + + void drawText(const std::shared_ptr& font, const Point& pos, const std::string& text, const Colorf& color); + void drawTextEllipsis(const std::shared_ptr& font, const Point& pos, const Rect& clipBox, const std::string& text, const Colorf& color); + Rect measureText(const std::shared_ptr& font, const std::string& text); + FontMetrics getFontMetrics(const std::shared_ptr& font); + int getCharacterIndex(const std::shared_ptr& font, const std::string& text, const Point& hitPoint); + + void drawImage(const std::shared_ptr& image, const Point& pos); + void drawImage(const std::shared_ptr& image, const Rect& box); + +protected: + virtual std::unique_ptr createTexture(int width, int height, const void* pixels, ImageFormat format = ImageFormat::B8G8R8A8) = 0; + virtual void drawLineAntialiased(float x0, float y0, float x1, float y1, Colorf color) = 0; + virtual void fillTile(float x, float y, float width, float height, Colorf color) = 0; + virtual void drawTile(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) = 0; + virtual void drawGlyph(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) = 0; + + int getClipMinX() const; + int getClipMinY() const; + int getClipMaxX() const; + int getClipMaxY() const; + + template + static T clamp(T val, T minval, T maxval) { return std::max(std::min(val, maxval), minval); } + + DisplayWindow* window = nullptr; + int width = 0; + int height = 0; + double uiscale = 1.0f; + + std::unique_ptr whiteTexture; + +private: + void setLanguage(const char* lang) { language = lang; } + void drawLineUnclipped(const Point& p0, const Point& p1, const Colorf& color); + + std::unique_ptr font; + + Point origin; + std::vector clipStack; + + std::unordered_map, std::unique_ptr> imageTextures; + std::string language; + + friend class CanvasFont; +}; diff --git a/libraries/ZWidget/include/zwidget/core/rect.h b/libraries/ZWidget/include/zwidget/core/rect.h index ad2a4e09c..3b655cd2e 100644 --- a/libraries/ZWidget/include/zwidget/core/rect.h +++ b/libraries/ZWidget/include/zwidget/core/rect.h @@ -1,5 +1,7 @@ #pragma once +#include + class Point { public: diff --git a/libraries/ZWidget/include/zwidget/core/widget.h b/libraries/ZWidget/include/zwidget/core/widget.h index 2c17507fd..6d87f72a0 100644 --- a/libraries/ZWidget/include/zwidget/core/widget.h +++ b/libraries/ZWidget/include/zwidget/core/widget.h @@ -22,7 +22,7 @@ enum class WidgetType class Widget : DisplayWindowHost { public: - Widget(Widget* parent = nullptr, WidgetType type = WidgetType::Child); + Widget(Widget* parent = nullptr, WidgetType type = WidgetType::Child, RenderAPI api = RenderAPI::Unspecified); virtual ~Widget(); void SetParent(Widget* parent); @@ -104,6 +104,7 @@ public: bool IsEnabled(); bool IsVisible(); bool IsHidden(); + bool IsFullscreen(); void SetFocus(); void SetEnabled(bool value); @@ -113,8 +114,12 @@ public: void LockCursor(); void UnlockCursor(); void SetCursor(StandardCursor cursor); - void CaptureMouse(); - void ReleaseMouseCapture(); + + void SetPointerCapture(); + void ReleasePointerCapture(); + + void SetModalCapture(); + void ReleaseModalCapture(); bool GetKeyState(InputKey key); @@ -126,6 +131,9 @@ public: Widget* ChildAt(double x, double y) { return ChildAt(Point(x, y)); } Widget* ChildAt(const Point& pos); + bool IsParent(const Widget* w) const; + bool IsChild(const Widget* w) const; + Widget* Parent() const { return ParentObj; } Widget* PrevSibling() const { return PrevSiblingObj; } Widget* NextSibling() const { return NextSiblingObj; } @@ -142,7 +150,14 @@ public: static Size GetScreenSize(); + void SetCanvas(std::unique_ptr canvas); void* GetNativeHandle(); + int GetNativePixelWidth(); + int GetNativePixelHeight(); + + // Vulkan support: + std::vector GetVulkanInstanceExtensions() { return Window()->DispWindow->GetVulkanInstanceExtensions(); } + VkSurfaceKHR CreateVulkanSurface(VkInstance instance) { return Window()->DispWindow->CreateVulkanSurface(instance); } protected: virtual void OnPaintFrame(Canvas* canvas); @@ -220,4 +235,7 @@ private: Widget& operator=(const Widget&) = delete; friend class Timer; + friend class OpenFileDialog; + friend class OpenFolderDialog; + friend class SaveFileDialog; }; diff --git a/libraries/ZWidget/include/zwidget/systemdialogs/folder_browse_dialog.h b/libraries/ZWidget/include/zwidget/systemdialogs/open_folder_dialog.h similarity index 83% rename from libraries/ZWidget/include/zwidget/systemdialogs/folder_browse_dialog.h rename to libraries/ZWidget/include/zwidget/systemdialogs/open_folder_dialog.h index 991869a5a..9727f353b 100644 --- a/libraries/ZWidget/include/zwidget/systemdialogs/folder_browse_dialog.h +++ b/libraries/ZWidget/include/zwidget/systemdialogs/open_folder_dialog.h @@ -7,13 +7,13 @@ class Widget; /// \brief Displays the system folder browsing dialog -class BrowseFolderDialog +class OpenFolderDialog { public: /// \brief Constructs an browse folder dialog. - static std::unique_ptr Create(Widget*owner); + static std::unique_ptr Create(Widget*owner); - virtual ~BrowseFolderDialog() = default; + virtual ~OpenFolderDialog() = default; /// \brief Get the full path of the directory selected. virtual std::string SelectedPath() const = 0; diff --git a/libraries/ZWidget/include/zwidget/widgets/imagebox/imagebox.h b/libraries/ZWidget/include/zwidget/widgets/imagebox/imagebox.h index 7d3aa520e..7cae08c24 100644 --- a/libraries/ZWidget/include/zwidget/widgets/imagebox/imagebox.h +++ b/libraries/ZWidget/include/zwidget/widgets/imagebox/imagebox.h @@ -4,12 +4,20 @@ #include "../../core/widget.h" #include "../../core/image.h" +enum ImageBoxMode +{ + Center, + Contain, + Cover +}; + class ImageBox : public Widget { public: ImageBox(Widget* parent); void SetImage(std::shared_ptr newImage); + void SetImageMode(ImageBoxMode mode); double GetPreferredWidth() const; double GetPreferredHeight() const; @@ -19,4 +27,5 @@ protected: private: std::shared_ptr image; + ImageBoxMode mode = ImageBoxMode::Center; }; diff --git a/libraries/ZWidget/include/zwidget/widgets/listview/listview.h b/libraries/ZWidget/include/zwidget/widgets/listview/listview.h index 3d3170391..fc6a82391 100644 --- a/libraries/ZWidget/include/zwidget/widgets/listview/listview.h +++ b/libraries/ZWidget/include/zwidget/widgets/listview/listview.h @@ -13,6 +13,7 @@ public: ListView(Widget* parent = nullptr); void AddItem(const std::string& text); + void RemoveItem(int index); int GetSelectedItem() const { return selectedItem; } void SetSelectedItem(int index); void ScrollToItem(int index); diff --git a/libraries/ZWidget/include/zwidget/widgets/mainwindow/mainwindow.h b/libraries/ZWidget/include/zwidget/widgets/mainwindow/mainwindow.h index abba23d6c..13498e56d 100644 --- a/libraries/ZWidget/include/zwidget/widgets/mainwindow/mainwindow.h +++ b/libraries/ZWidget/include/zwidget/widgets/mainwindow/mainwindow.h @@ -10,11 +10,12 @@ class Statusbar; class MainWindow : public Widget { public: - MainWindow(); + MainWindow(RenderAPI api = RenderAPI::Unspecified); ~MainWindow(); Menubar* GetMenubar() const { return MenubarWidget; } - Toolbar* GetToolbar() const { return ToolbarWidget; } + Toolbar* GetTopToolbar() const { return TopToolbarWidget; } + Toolbar* GetLeftToolbar() const { return LeftToolbarWidget; } Statusbar* GetStatusbar() const { return StatusbarWidget; } Widget* GetCentralWidget() const { return CentralWidget; } @@ -25,7 +26,8 @@ protected: private: Menubar* MenubarWidget = nullptr; - Toolbar* ToolbarWidget = nullptr; + Toolbar* TopToolbarWidget = nullptr; + Toolbar* LeftToolbarWidget = nullptr; Widget* CentralWidget = nullptr; Statusbar* StatusbarWidget = nullptr; }; diff --git a/libraries/ZWidget/include/zwidget/widgets/menubar/menubar.h b/libraries/ZWidget/include/zwidget/widgets/menubar/menubar.h index 572d2a2f9..1881111f4 100644 --- a/libraries/ZWidget/include/zwidget/widgets/menubar/menubar.h +++ b/libraries/ZWidget/include/zwidget/widgets/menubar/menubar.h @@ -21,13 +21,22 @@ public: protected: void OnGeometryChanged() override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; + void OnMouseMove(const Point& pos) override; + void OnKeyDown(InputKey key) override; private: void ShowMenu(MenubarItem* item); void CloseMenu(); + MenubarItem* GetMenubarItemAt(const Point& pos); + int GetItemIndex(MenubarItem* item); + std::vector menuItems; + int currentMenubarItem = -1; Menu* openMenu = nullptr; + bool modalMode = false; friend class MenubarItem; }; @@ -70,10 +79,13 @@ public: double GetPreferredWidth() const; double GetPreferredHeight() const; + void SetSelected(MenuItem* item); + protected: void OnGeometryChanged() override; std::function onCloseMenu; + MenuItem* selectedItem = nullptr; friend class Menubar; }; @@ -81,19 +93,29 @@ protected: class MenuItem : public Widget { public: - MenuItem(Widget* parent); + MenuItem(Menu* menu, std::function onClick); + void Click(); + + Menu* menu = nullptr; ImageBox* icon = nullptr; TextLabel* text = nullptr; protected: + bool OnMouseUp(const Point& pos, InputKey key) override; void OnMouseMove(const Point& pos) override; void OnMouseLeave() override; void OnGeometryChanged() override; + +private: + std::function onClick; }; class MenuItemSeparator : public Widget { public: MenuItemSeparator(Widget* parent); + +protected: + void OnPaint(Canvas* canvas) override; }; diff --git a/libraries/ZWidget/include/zwidget/widgets/textlabel/textlabel.h b/libraries/ZWidget/include/zwidget/widgets/textlabel/textlabel.h index 45515dfcb..18cfcbf5f 100644 --- a/libraries/ZWidget/include/zwidget/widgets/textlabel/textlabel.h +++ b/libraries/ZWidget/include/zwidget/widgets/textlabel/textlabel.h @@ -3,7 +3,7 @@ #include "../../core/widget.h" -enum TextLabelAlignment +enum class TextLabelAlignment { Left, Center, diff --git a/libraries/ZWidget/include/zwidget/widgets/toolbar/toolbar.h b/libraries/ZWidget/include/zwidget/widgets/toolbar/toolbar.h index ca0a89c0d..fac66b97d 100644 --- a/libraries/ZWidget/include/zwidget/widgets/toolbar/toolbar.h +++ b/libraries/ZWidget/include/zwidget/widgets/toolbar/toolbar.h @@ -3,9 +3,29 @@ #include "../../core/widget.h" +enum class ToolbarDirection +{ + Horizontal, + Vertical +}; + +class ToolbarButton; + class Toolbar : public Widget { public: Toolbar(Widget* parent); ~Toolbar(); + + void SetDirection(ToolbarDirection direction); + ToolbarDirection GetDirection(ToolbarDirection) const { return direction; } + + ToolbarButton* AddButton(std::string icon, std::string text, std::function onClicked = {}); + +protected: + void OnGeometryChanged() override; + +private: + ToolbarDirection direction = ToolbarDirection::Horizontal; + std::vector buttons; }; diff --git a/libraries/ZWidget/include/zwidget/widgets/toolbar/toolbarbutton.h b/libraries/ZWidget/include/zwidget/widgets/toolbar/toolbarbutton.h index 933a6517e..f6e1557a1 100644 --- a/libraries/ZWidget/include/zwidget/widgets/toolbar/toolbarbutton.h +++ b/libraries/ZWidget/include/zwidget/widgets/toolbar/toolbarbutton.h @@ -2,6 +2,8 @@ #pragma once #include "../../core/widget.h" +#include "../textlabel/textlabel.h" +#include "../imagebox/imagebox.h" class ToolbarButton : public Widget { @@ -9,6 +11,24 @@ public: ToolbarButton(Widget* parent); ~ToolbarButton(); + void SetIcon(std::string icon); + void SetText(std::string text); + + void Click(); + + double GetPreferredWidth(); + double GetPreferredHeight(); + + std::function OnClick; + protected: - void OnPaint(Canvas* canvas) override; + void OnMouseMove(const Point& pos) override; + void OnMouseLeave() override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; + void OnGeometryChanged() override; + +private: + ImageBox* image = nullptr; + TextLabel* label = nullptr; }; diff --git a/libraries/ZWidget/include/zwidget/window/sdl2nativehandle.h b/libraries/ZWidget/include/zwidget/window/sdl2nativehandle.h new file mode 100644 index 000000000..9e396a80b --- /dev/null +++ b/libraries/ZWidget/include/zwidget/window/sdl2nativehandle.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +struct SDL_Window; + +class SDL2NativeHandle +{ +public: + SDL_Window* window = nullptr; +}; diff --git a/libraries/ZWidget/include/zwidget/window/waylandnativehandle.h b/libraries/ZWidget/include/zwidget/window/waylandnativehandle.h new file mode 100644 index 000000000..b7358d782 --- /dev/null +++ b/libraries/ZWidget/include/zwidget/window/waylandnativehandle.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class WaylandNativeHandle +{ +public: + wl_display* display = nullptr; + wl_surface* surface = nullptr; +}; diff --git a/libraries/ZWidget/include/zwidget/window/win32nativehandle.h b/libraries/ZWidget/include/zwidget/window/win32nativehandle.h new file mode 100644 index 000000000..979ac63a1 --- /dev/null +++ b/libraries/ZWidget/include/zwidget/window/win32nativehandle.h @@ -0,0 +1,9 @@ +#pragma once + +typedef struct HWND__* HWND; + +class Win32NativeHandle +{ +public: + HWND hwnd = {}; +}; diff --git a/libraries/ZWidget/include/zwidget/window/window.h b/libraries/ZWidget/include/zwidget/window/window.h index a51519af4..ad59f1f02 100644 --- a/libraries/ZWidget/include/zwidget/window/window.h +++ b/libraries/ZWidget/include/zwidget/window/window.h @@ -4,9 +4,28 @@ #include #include #include +#include #include "../core/rect.h" -class Engine; +#ifndef VULKAN_H_ + +#define VK_DEFINE_HANDLE(object) typedef struct object##_T* object; + +#if defined(__LP64__) || defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) +#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object; +#else +#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object; +#endif + +VK_DEFINE_HANDLE(VkInstance) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSurfaceKHR) + +#endif + +class Widget; +class OpenFileDialog; +class SaveFileDialog; +class OpenFolderDialog; enum class StandardCursor { @@ -93,6 +112,17 @@ enum class InputKey : uint32_t NoName, PA1, OEMClear }; +enum class RenderAPI +{ + Unspecified, + Bitmap, + Vulkan, + OpenGL, + D3D11, + D3D12, + Metal +}; + class DisplayWindow; class DisplayWindowHost @@ -119,7 +149,7 @@ public: class DisplayWindow { public: - static std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner); + static std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI); static void ProcessEvents(); static void RunLoop(); @@ -140,6 +170,7 @@ public: virtual void ShowMaximized() = 0; virtual void ShowMinimized() = 0; virtual void ShowNormal() = 0; + virtual bool IsWindowFullscreen() = 0; virtual void Hide() = 0; virtual void Activate() = 0; virtual void ShowCursor(bool enable) = 0; @@ -171,4 +202,42 @@ public: virtual void SetClipboardText(const std::string& text) = 0; virtual void* GetNativeHandle() = 0; + + virtual std::vector GetVulkanInstanceExtensions() = 0; + virtual VkSurfaceKHR CreateVulkanSurface(VkInstance instance) = 0; +}; + +class DisplayBackend +{ +public: + static DisplayBackend* Get(); + static void Set(std::unique_ptr instance); + + static std::unique_ptr TryCreateWin32(); + static std::unique_ptr TryCreateSDL2(); + static std::unique_ptr TryCreateX11(); + static std::unique_ptr TryCreateWayland(); + + static std::unique_ptr TryCreateBackend(); + + virtual ~DisplayBackend() = default; + + virtual bool IsWin32() { return false; } + virtual bool IsSDL2() { return false; } + virtual bool IsX11() { return false; } + virtual bool IsWayland() { return false; } + + virtual std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) = 0; + virtual void ProcessEvents() = 0; + virtual void RunLoop() = 0; + virtual void ExitLoop() = 0; + + virtual void* StartTimer(int timeoutMilliseconds, std::function onTimer) = 0; + virtual void StopTimer(void* timerID) = 0; + + virtual Size GetScreenSize() = 0; + + virtual std::unique_ptr CreateOpenFileDialog(DisplayWindow* owner); + virtual std::unique_ptr CreateSaveFileDialog(DisplayWindow* owner); + virtual std::unique_ptr CreateOpenFolderDialog(DisplayWindow* owner); }; diff --git a/libraries/ZWidget/include/zwidget/window/x11nativehandle.h b/libraries/ZWidget/include/zwidget/window/x11nativehandle.h new file mode 100644 index 000000000..f78247b2f --- /dev/null +++ b/libraries/ZWidget/include/zwidget/window/x11nativehandle.h @@ -0,0 +1,13 @@ +#pragma once + +typedef struct _XDisplay Display; +typedef unsigned long XID; +typedef unsigned long VisualID; +typedef XID Window; + +class X11NativeHandle +{ +public: + Display* display = nullptr; + Window window = 0; +}; diff --git a/libraries/ZWidget/src/core/canvas.cpp b/libraries/ZWidget/src/core/canvas.cpp index 437c25f02..bc72b8f19 100644 --- a/libraries/ZWidget/src/core/canvas.cpp +++ b/libraries/ZWidget/src/core/canvas.cpp @@ -18,13 +18,7 @@ #define USE_SSE2 #endif -class CanvasTexture -{ -public: - int Width = 0; - int Height = 0; - std::vector Data; -}; +//////////////////////////////////////////////////////////////////////////// class CanvasGlyph { @@ -46,78 +40,12 @@ public: class CanvasFont { public: - CanvasFont(const std::string& fontname, double height, std::vector data) : fontname(fontname), height(height) - { - auto tdata = std::make_shared(std::move(data)); - ttf = std::make_unique(tdata); - textmetrics = ttf->GetTextMetrics(height); - } + CanvasFont(const std::string& fontname, double height, std::vector data); + ~CanvasFont(); - ~CanvasFont() - { - } - - CanvasGlyph* getGlyph(uint32_t utfchar) - { - uint32_t glyphIndex = ttf->GetGlyphIndex(utfchar); - if (glyphIndex == 0) return nullptr; - - auto& glyph = glyphs[glyphIndex]; - if (glyph) - return glyph.get(); - - glyph = std::make_unique(); - - TrueTypeGlyph ttfglyph = ttf->LoadGlyph(glyphIndex, height); - - // Create final subpixel version - int w = ttfglyph.width; - int h = ttfglyph.height; - int destwidth = (w + 2) / 3; - auto texture = std::make_shared(); - texture->Width = destwidth; - texture->Height = h; - texture->Data.resize(destwidth * h); - - uint8_t* grayscale = ttfglyph.grayscale.get(); - uint32_t* dest = (uint32_t*)texture->Data.data(); - for (int y = 0; y < h; y++) - { - uint8_t* sline = grayscale + y * w; - uint32_t* dline = dest + y * destwidth; - for (int x = 0; x < w; x += 3) - { - uint32_t values[5] = - { - x > 0 ? sline[x - 1] : 0U, - sline[x], - x + 1 < w ? sline[x + 1] : 0U, - x + 2 < w ? sline[x + 2] : 0U, - x + 3 < w ? sline[x + 3] : 0U - }; - - uint32_t red = (values[0] + values[1] + values[1] + values[2] + 2) >> 2; - uint32_t green = (values[1] + values[2] + values[2] + values[3] + 2) >> 2; - uint32_t blue = (values[2] + values[3] + values[3] + values[4] + 2) >> 2; - uint32_t alpha = (red | green | blue) ? 255 : 0; - - *(dline++) = (alpha << 24) | (red << 16) | (green << 8) | blue; - } - } - - glyph->u = 0.0; - glyph->v = 0.0; - glyph->uvwidth = destwidth; - glyph->uvheight = h; - glyph->texture = std::move(texture); - - glyph->metrics.advanceWidth = (ttfglyph.advanceWidth + 2) / 3; - glyph->metrics.leftSideBearing = (ttfglyph.leftSideBearing + 2) / 3; - glyph->metrics.yOffset = ttfglyph.yOffset; - - return glyph.get(); - } + CanvasGlyph* getGlyph(Canvas* canvas, uint32_t utfchar); +private: std::unique_ptr ttf; std::string fontname; @@ -125,6 +53,8 @@ public: TrueTypeTextMetrics textmetrics; std::unordered_map> glyphs; + + friend class CanvasFontGroup; }; class CanvasFontGroup @@ -135,144 +65,164 @@ public: std::unique_ptr font; std::string language; }; - CanvasFontGroup(const std::string& fontname, double height) : height(height) - { - auto fontdata = LoadWidgetFontData(fontname); - fonts.resize(fontdata.size()); - for (size_t i = 0; i < fonts.size(); i++) - { - fonts[i].font = std::make_unique(fontname, height, fontdata[i].fontdata); - fonts[i].language = fontdata[i].language; - } - } - CanvasGlyph* getGlyph(uint32_t utfchar, const char* lang = nullptr) - { - for (int i = 0; i < 2; i++) - { - for (auto& fd : fonts) - { - if (i == 1 || lang == nullptr || *lang == 0 || fd.language.empty() || fd.language == lang) - { - auto g = fd.font->getGlyph(utfchar); - if (g) return g; - } - } - } + CanvasFontGroup(const std::string& fontname, double height); - return nullptr; - } - - TrueTypeTextMetrics& GetTextMetrics() - { - return fonts[0].font->textmetrics; - } + CanvasGlyph* getGlyph(Canvas* canvas, uint32_t utfchar, const char* lang = nullptr); + TrueTypeTextMetrics& GetTextMetrics(); double height; std::vector fonts; - }; -class BitmapCanvas : public Canvas +//////////////////////////////////////////////////////////////////////////// + +CanvasFont::CanvasFont(const std::string& fontname, double height, std::vector data) : fontname(fontname), height(height) { -public: - BitmapCanvas(DisplayWindow* window); - ~BitmapCanvas(); + auto tdata = std::make_shared(std::move(data)); + ttf = std::make_unique(tdata); + textmetrics = ttf->GetTextMetrics(height); +} - void begin(const Colorf& color) override; - void end() override; +CanvasFont::~CanvasFont() +{ +} - void begin3d() override; - void end3d() override; +CanvasGlyph* CanvasFont::getGlyph(Canvas* canvas, uint32_t utfchar) +{ + uint32_t glyphIndex = ttf->GetGlyphIndex(utfchar); + if (glyphIndex == 0) return nullptr; - Point getOrigin() override; - void setOrigin(const Point& origin) override; + auto& glyph = glyphs[glyphIndex]; + if (glyph) + return glyph.get(); - void pushClip(const Rect& box) override; - void popClip() override; + glyph = std::make_unique(); - void fillRect(const Rect& box, const Colorf& color) override; - void line(const Point& p0, const Point& p1, const Colorf& color) override; + TrueTypeGlyph ttfglyph = ttf->LoadGlyph(glyphIndex, height); - void drawText(const Point& pos, const Colorf& color, const std::string& text) override; - Rect measureText(const std::string& text) override; - VerticalTextPosition verticalTextAlign() override; + // Create final subpixel version + int w = ttfglyph.width; + int h = ttfglyph.height; + int destwidth = (w + 2) / 3; + auto texture = std::make_shared(); + std::vector data(destwidth * h); - void drawText(const std::shared_ptr& font, const Point& pos, const std::string& text, const Colorf& color) override { drawText(pos, color, text); } - void drawTextEllipsis(const std::shared_ptr& font, const Point& pos, const Rect& clipBox, const std::string& text, const Colorf& color) override { drawText(pos, color, text); } - Rect measureText(const std::shared_ptr& font, const std::string& text) override { return measureText(text); } - FontMetrics getFontMetrics(const std::shared_ptr& font) override + uint8_t* grayscale = ttfglyph.grayscale.get(); + uint32_t* dest = data.data(); + for (int y = 0; y < h; y++) { - VerticalTextPosition vtp = verticalTextAlign(); - FontMetrics metrics; - metrics.external_leading = vtp.top; - metrics.ascent = vtp.baseline - vtp.top; - metrics.descent = vtp.bottom - vtp.baseline; - metrics.height = metrics.ascent + metrics.descent; - return metrics; + uint8_t* sline = grayscale + y * w; + uint32_t* dline = dest + y * destwidth; + for (int x = 0; x < w; x += 3) + { + uint32_t values[5] = + { + x > 0 ? sline[x - 1] : 0U, + sline[x], + x + 1 < w ? sline[x + 1] : 0U, + x + 2 < w ? sline[x + 2] : 0U, + x + 3 < w ? sline[x + 3] : 0U + }; + + uint32_t red = (values[0] + values[1] + values[1] + values[2] + 2) >> 2; + uint32_t green = (values[1] + values[2] + values[2] + values[3] + 2) >> 2; + uint32_t blue = (values[2] + values[3] + values[3] + values[4] + 2) >> 2; + uint32_t alpha = (red | green | blue) ? 255 : 0; + + *(dline++) = (alpha << 24) | (red << 16) | (green << 8) | blue; + } } - int getCharacterIndex(const std::shared_ptr& font, const std::string& text, const Point& hitPoint) override { return 0; } - void drawImage(const std::shared_ptr& image, const Point& pos) override; + glyph->u = 0.0; + glyph->v = 0.0; + glyph->uvwidth = destwidth; + glyph->uvheight = h; + glyph->texture = canvas->createTexture(destwidth, h, data.data(), ImageFormat::B8G8R8A8); - void drawLineUnclipped(const Point& p0, const Point& p1, const Colorf& color); + glyph->metrics.advanceWidth = (ttfglyph.advanceWidth + 2) / 3; + glyph->metrics.leftSideBearing = (ttfglyph.leftSideBearing + 2) / 3; + glyph->metrics.yOffset = ttfglyph.yOffset; - void fillTile(float x, float y, float width, float height, Colorf color); - void drawTile(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color); - void drawGlyph(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color); + return glyph.get(); +} - int getClipMinX() const; - int getClipMinY() const; - int getClipMaxX() const; - int getClipMaxY() const; +//////////////////////////////////////////////////////////////////////////// - void setLanguage(const char* lang) { language = lang; } - - std::unique_ptr createTexture(int width, int height, const void* pixels, ImageFormat format = ImageFormat::B8G8R8A8); - - template - static T clamp(T val, T minval, T maxval) { return std::max(std::min(val, maxval), minval); } - - DisplayWindow* window = nullptr; - - std::unique_ptr font; - std::unique_ptr whiteTexture; - - Point origin; - double uiscale = 1.0f; - std::vector clipStack; - - int width = 0; - int height = 0; - std::vector pixels; - - std::unordered_map, std::unique_ptr> imageTextures; - std::string language; -}; - -BitmapCanvas::BitmapCanvas(DisplayWindow* window) : window(window) +CanvasFontGroup::CanvasFontGroup(const std::string& fontname, double height) : height(height) { + auto fontdata = LoadWidgetFontData(fontname); + fonts.resize(fontdata.size()); + for (size_t i = 0; i < fonts.size(); i++) + { + fonts[i].font = std::make_unique(fontname, height, fontdata[i].fontdata); + fonts[i].language = fontdata[i].language; + } +} + +CanvasGlyph* CanvasFontGroup::getGlyph(Canvas* canvas, uint32_t utfchar, const char* lang) +{ + for (int i = 0; i < 2; i++) + { + for (auto& fd : fonts) + { + if (i == 1 || lang == nullptr || *lang == 0 || fd.language.empty() || fd.language == lang) + { + auto g = fd.font->getGlyph(canvas, utfchar); + if (g) return g; + } + } + } + + return nullptr; +} + +TrueTypeTextMetrics& CanvasFontGroup::GetTextMetrics() +{ + return fonts[0].font->textmetrics; +} + +//////////////////////////////////////////////////////////////////////////// + +Canvas::Canvas() +{ +} + +Canvas::~Canvas() +{ +} + +void Canvas::attach(DisplayWindow* newWindow) +{ + window = newWindow; uiscale = window->GetDpiScale(); uint32_t white = 0xffffffff; whiteTexture = createTexture(1, 1, &white); font = std::make_unique("NotoSans", 13.0 * uiscale); } -BitmapCanvas::~BitmapCanvas() +void Canvas::detach() { } -Point BitmapCanvas::getOrigin() +void Canvas::begin(const Colorf& color) +{ + uiscale = window->GetDpiScale(); + width = window->GetPixelWidth(); + height = window->GetPixelHeight(); +} + +Point Canvas::getOrigin() { return origin; } -void BitmapCanvas::setOrigin(const Point& newOrigin) +void Canvas::setOrigin(const Point& newOrigin) { origin = newOrigin; } -void BitmapCanvas::pushClip(const Rect& box) +void Canvas::pushClip(const Rect& box) { if (!clipStack.empty()) { @@ -299,17 +249,17 @@ void BitmapCanvas::pushClip(const Rect& box) } } -void BitmapCanvas::popClip() +void Canvas::popClip() { clipStack.pop_back(); } -void BitmapCanvas::fillRect(const Rect& box, const Colorf& color) +void Canvas::fillRect(const Rect& box, const Colorf& color) { fillTile((float)((origin.x + box.x) * uiscale), (float)((origin.y + box.y) * uiscale), (float)(box.width * uiscale), (float)(box.height * uiscale), color); } -void BitmapCanvas::drawImage(const std::shared_ptr& image, const Point& pos) +void Canvas::drawImage(const std::shared_ptr& image, const Point& pos) { auto& texture = imageTextures[image]; if (!texture) @@ -320,7 +270,18 @@ void BitmapCanvas::drawImage(const std::shared_ptr& image, const Point& p drawTile(texture.get(), (float)((origin.x + pos.x) * uiscale), (float)((origin.y + pos.y) * uiscale), (float)(texture->Width * uiscale), (float)(texture->Height * uiscale), 0.0, 0.0, (float)texture->Width, (float)texture->Height, color); } -void BitmapCanvas::line(const Point& p0, const Point& p1, const Colorf& color) +void Canvas::drawImage(const std::shared_ptr& image, const Rect& box) +{ + auto& texture = imageTextures[image]; + if (!texture) + { + texture = createTexture(image->GetWidth(), image->GetHeight(), image->GetData(), image->GetFormat()); + } + Colorf color(1.0f, 1.0f, 1.0f); + drawTile(texture.get(), (float)((origin.x + box.x) * uiscale), (float)((origin.y + box.y) * uiscale), (float)(box.width * uiscale), (float)(box.height * uiscale), 0.0, 0.0, (float)texture->Width, (float)texture->Height, color); +} + +void Canvas::line(const Point& p0, const Point& p1, const Colorf& color) { double x0 = origin.x + p0.x; double y0 = origin.y + p0.y; @@ -407,7 +368,7 @@ void BitmapCanvas::line(const Point& p0, const Point& p1, const Colorf& color) } } -void BitmapCanvas::drawText(const Point& pos, const Colorf& color, const std::string& text) +void Canvas::drawText(const Point& pos, const Colorf& color, const std::string& text) { double x = std::round((origin.x + pos.x) * uiscale); double y = std::round((origin.y + pos.y) * uiscale); @@ -415,10 +376,10 @@ void BitmapCanvas::drawText(const Point& pos, const Colorf& color, const std::st UTF8Reader reader(text.data(), text.size()); while (!reader.is_end()) { - CanvasGlyph* glyph = font->getGlyph(reader.character(), language.c_str()); + CanvasGlyph* glyph = font->getGlyph(this, reader.character(), language.c_str()); if (!glyph || !glyph->texture) { - glyph = font->getGlyph(32); + glyph = font->getGlyph(this, 32); } if (glyph->texture) @@ -433,7 +394,22 @@ void BitmapCanvas::drawText(const Point& pos, const Colorf& color, const std::st } } -Rect BitmapCanvas::measureText(const std::string& text) +void Canvas::drawText(const std::shared_ptr& font, const Point& pos, const std::string& text, const Colorf& color) +{ + drawText(pos, color, text); +} + +void Canvas::drawTextEllipsis(const std::shared_ptr& font, const Point& pos, const Rect& clipBox, const std::string& text, const Colorf& color) +{ + drawText(pos, color, text); +} + +Rect Canvas::measureText(const std::shared_ptr& font, const std::string& text) +{ + return measureText(text); +} + +Rect Canvas::measureText(const std::string& text) { double x = 0.0; double y = font->GetTextMetrics().ascender - font->GetTextMetrics().descender; @@ -441,10 +417,10 @@ Rect BitmapCanvas::measureText(const std::string& text) UTF8Reader reader(text.data(), text.size()); while (!reader.is_end()) { - CanvasGlyph* glyph = font->getGlyph(reader.character(), language.c_str()); + CanvasGlyph* glyph = font->getGlyph(this, reader.character(), language.c_str()); if (!glyph || !glyph->texture) { - glyph = font->getGlyph(32); + glyph = font->getGlyph(this, 32); } x += std::round(glyph->metrics.advanceWidth); @@ -454,7 +430,23 @@ Rect BitmapCanvas::measureText(const std::string& text) return Rect::xywh(0.0, 0.0, x / uiscale, y / uiscale); } -VerticalTextPosition BitmapCanvas::verticalTextAlign() +FontMetrics Canvas::getFontMetrics(const std::shared_ptr& font) +{ + VerticalTextPosition vtp = verticalTextAlign(); + FontMetrics metrics; + metrics.external_leading = vtp.top; + metrics.ascent = vtp.baseline - vtp.top; + metrics.descent = vtp.bottom - vtp.baseline; + metrics.height = metrics.ascent + metrics.descent; + return metrics; +} + +int Canvas::getCharacterIndex(const std::shared_ptr& font, const std::string& text, const Point& hitPoint) +{ + return 0; +} + +VerticalTextPosition Canvas::verticalTextAlign() { VerticalTextPosition align; align.top = 0.0f; @@ -464,9 +456,70 @@ VerticalTextPosition BitmapCanvas::verticalTextAlign() return align; } +void Canvas::drawLineUnclipped(const Point& p0, const Point& p1, const Colorf& color) +{ + if (p0.x == p1.x) + { + fillTile((float)((p0.x - 0.5) * uiscale), (float)(p0.y * uiscale), (float)uiscale, (float)((p1.y - p0.y) * uiscale), color); + } + else if (p0.y == p1.y) + { + fillTile((float)(p0.x * uiscale), (float)((p0.y - 0.5) * uiscale), (float)((p1.x - p0.x) * uiscale), (float)uiscale, color); + } + else + { + drawLineAntialiased((float)(p0.x * uiscale), (float)(p0.y * uiscale), (float)(p1.x * uiscale), (float)(p1.y * uiscale), color); + } +} + +int Canvas::getClipMinX() const +{ + return clipStack.empty() ? 0 : (int)std::round(std::max(clipStack.back().x * uiscale, 0.0)); +} + +int Canvas::getClipMinY() const +{ + return clipStack.empty() ? 0 : (int)std::round(std::max(clipStack.back().y * uiscale, 0.0)); +} + +int Canvas::getClipMaxX() const +{ + return clipStack.empty() ? width : (int)std::round(std::min((clipStack.back().x + clipStack.back().width) * uiscale, (double)width)); +} + +int Canvas::getClipMaxY() const +{ + return clipStack.empty() ? height : (int)std::round(std::min((clipStack.back().y + clipStack.back().height) * uiscale, (double)height)); +} + +///////////////////////////////////////////////////////////////////////////// + +class BitmapTexture : public CanvasTexture +{ +public: + std::vector Data; +}; + +class BitmapCanvas : public Canvas +{ +public: + void begin(const Colorf& color) override; + void end() override; + + void fillTile(float x, float y, float width, float height, Colorf color) override; + void drawTile(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) override; + void drawGlyph(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) override; + void drawLineAntialiased(float x0, float y0, float x1, float y1, Colorf color) override; + void plot(float x, float y, float alpha, const Colorf& color); + + std::unique_ptr createTexture(int width, int height, const void* pixels, ImageFormat format = ImageFormat::B8G8R8A8) override; + + std::vector pixels; +}; + std::unique_ptr BitmapCanvas::createTexture(int width, int height, const void* pixels, ImageFormat format) { - auto texture = std::make_unique(); + auto texture = std::make_unique(); texture->Width = width; texture->Height = height; texture->Data.resize(width * height); @@ -491,40 +544,119 @@ std::unique_ptr BitmapCanvas::createTexture(int width, int height return texture; } -void BitmapCanvas::drawLineUnclipped(const Point& p0, const Point& p1, const Colorf& color) +void BitmapCanvas::plot(float x, float y, float alpha, const Colorf& color) { - if (p0.x == p1.x) + int xx = (int)x; + int yy = (int)y; + + int dwidth = width; + int dheight = height; + uint32_t* dest = pixels.data() + xx + yy * dwidth; + + uint32_t cred = (int32_t)clamp(color.r * 256.0f, 0.0f, 256.0f); + uint32_t cgreen = (int32_t)clamp(color.g * 256.0f, 0.0f, 256.0f); + uint32_t cblue = (int32_t)clamp(color.b * 256.0f, 0.0f, 256.0f); + uint32_t calpha = (int32_t)clamp(color.a * alpha * 256.0f, 0.0f, 256.0f); + uint32_t invalpha = 256 - calpha; + + uint32_t dpixel = *dest; + uint32_t dalpha = dpixel >> 24; + uint32_t dred = (dpixel >> 16) & 0xff; + uint32_t dgreen = (dpixel >> 8) & 0xff; + uint32_t dblue = dpixel & 0xff; + + // dest.rgba = color.rgba + dest.rgba * (1-color.a) + uint32_t a = (calpha * calpha + dalpha * invalpha + 127) >> 8; + uint32_t r = (cred * calpha + dred * invalpha + 127) >> 8; + uint32_t g = (cgreen * calpha + dgreen * invalpha + 127) >> 8; + uint32_t b = (cblue * calpha + dblue * invalpha + 127) >> 8; + *dest = (a << 24) | (r << 16) | (g << 8) | b; +} + +static float fpart(float x) +{ + return x - std::floor(x); +} + +static float rfpart(float x) +{ + return 1 - fpart(x); +} + +void BitmapCanvas::drawLineAntialiased(float x0, float y0, float x1, float y1, Colorf color) +{ + bool steep = std::abs(y1 - y0) > std::abs(x1 - x0); + + if (steep) { - fillTile((float)((p0.x - 0.5) * uiscale), (float)(p0.y * uiscale), (float)uiscale, (float)((p1.y - p0.y) * uiscale), color); + std::swap(x0, y0); + std::swap(x1, y1); } - else if (p0.y == p1.y) + + if (x0 > x1) { - fillTile((float)(p0.x * uiscale), (float)((p0.y - 0.5) * uiscale), (float)((p1.x - p0.x) * uiscale), (float)uiscale, color); + std::swap(x0, x1); + std::swap(y0, y1); + } + + float dx = x1 - x0; + float dy = y1 - y0; + float gradient = (dx == 0.0f) ? 1.0f : dy / dx; + + // handle first endpoint + float xend = std::round(x0); + float yend = y0 + gradient * (xend - x0); + float xgap = rfpart(x0 + 0.5f); + float xpxl1 = xend; // this will be used in the main loop + float ypxl1 = std::floor(yend); + if (steep) + { + plot(ypxl1, xpxl1, rfpart(yend) * xgap, color); + plot(ypxl1 + 1, xpxl1, fpart(yend) * xgap, color); } else { - // To do: draw line using bresenham + plot(xpxl1, ypxl1, rfpart(yend) * xgap, color); + plot(xpxl1, ypxl1 + 1, fpart(yend) * xgap, color); } -} + float intery = yend + gradient; // first y-intersection for the main loop -int BitmapCanvas::getClipMinX() const -{ - return clipStack.empty() ? 0 : (int)std::max(clipStack.back().x * uiscale, 0.0); -} + // handle second endpoint + xend = std::floor(x1 + 0.5f); + yend = y1 + gradient * (xend - x1); + xgap = fpart(x1 + 0.5f); + float xpxl2 = xend; // this will be used in the main loop + float ypxl2 = std::floor(yend); + if (steep) + { + plot(ypxl2, xpxl2, rfpart(yend) * xgap, color); + plot(ypxl2 + 1.0f, xpxl2, fpart(yend) * xgap, color); + } + else + { + plot(xpxl2, ypxl2, rfpart(yend) * xgap, color); + plot(xpxl2, ypxl2 + 1.0f, fpart(yend) * xgap, color); + } -int BitmapCanvas::getClipMinY() const -{ - return clipStack.empty() ? 0 : (int)std::max(clipStack.back().y * uiscale, 0.0); -} - -int BitmapCanvas::getClipMaxX() const -{ - return clipStack.empty() ? width : (int)std::min((clipStack.back().x + clipStack.back().width) * uiscale, (double)width); -} - -int BitmapCanvas::getClipMaxY() const -{ - return clipStack.empty() ? height : (int)std::min((clipStack.back().y + clipStack.back().height) * uiscale, (double)height); + // main loop + if (steep) + { + for (float x = xpxl1 + 1.0f; x <= xpxl2 - 1.0f; x++) + { + plot(std::floor(intery), x, rfpart(intery), color); + plot(std::floor(intery) + 1.0f, x, fpart(intery), color); + intery = intery + gradient; + } + } + else + { + for (float x = xpxl1 + 1.0f; x <= xpxl2 - 1.0f; x++) + { + plot(x, std::floor(intery), rfpart(intery), color); + plot(x, std::floor(intery) + 1, fpart(intery), color); + intery = intery + gradient; + } + } } void BitmapCanvas::fillTile(float left, float top, float width, float height, Colorf color) @@ -632,11 +764,12 @@ void BitmapCanvas::fillTile(float left, float top, float width, float height, Co } } -void BitmapCanvas::drawTile(CanvasTexture* texture, float left, float top, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) +void BitmapCanvas::drawTile(CanvasTexture* tex, float left, float top, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) { if (width <= 0.0f || height <= 0.0f || color.a <= 0.0f) return; + auto texture = static_cast(tex); int swidth = texture->Width; int sheight = texture->Height; const uint32_t* src = texture->Data.data(); @@ -740,11 +873,12 @@ void BitmapCanvas::drawTile(CanvasTexture* texture, float left, float top, float } } -void BitmapCanvas::drawGlyph(CanvasTexture* texture, float left, float top, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) +void BitmapCanvas::drawGlyph(CanvasTexture* tex, float left, float top, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) { if (width <= 0.0f || height <= 0.0f) return; + auto texture = static_cast(tex); int swidth = texture->Width; int sheight = texture->Height; const uint32_t* src = texture->Data.data(); @@ -926,15 +1060,13 @@ void BitmapCanvas::drawGlyph(CanvasTexture* texture, float left, float top, floa void BitmapCanvas::begin(const Colorf& color) { - uiscale = window->GetDpiScale(); + Canvas::begin(color); uint32_t r = (int32_t)clamp(color.r * 255.0f, 0.0f, 255.0f); uint32_t g = (int32_t)clamp(color.g * 255.0f, 0.0f, 255.0f); uint32_t b = (int32_t)clamp(color.b * 255.0f, 0.0f, 255.0f); uint32_t a = (int32_t)clamp(color.a * 255.0f, 0.0f, 255.0f); uint32_t bgcolor = (a << 24) | (r << 16) | (g << 8) | b; - width = window->GetPixelWidth(); - height = window->GetPixelHeight(); pixels.clear(); pixels.resize(width * height, bgcolor); } @@ -944,17 +1076,9 @@ void BitmapCanvas::end() window->PresentBitmap(width, height, pixels.data()); } -void BitmapCanvas::begin3d() -{ -} - -void BitmapCanvas::end3d() -{ -} - ///////////////////////////////////////////////////////////////////////////// -std::unique_ptr Canvas::create(DisplayWindow* window) +std::unique_ptr Canvas::create() { - return std::make_unique(window); + return std::make_unique(); } diff --git a/libraries/ZWidget/src/core/nanosvg/nanosvgrast.h b/libraries/ZWidget/src/core/nanosvg/nanosvgrast.h index 89fae3834..a3295098c 100644 --- a/libraries/ZWidget/src/core/nanosvg/nanosvgrast.h +++ b/libraries/ZWidget/src/core/nanosvg/nanosvgrast.h @@ -331,7 +331,7 @@ static float nsvg__normalize(float *x, float* y) } static float nsvg__absf(float x) { return x < 0 ? -x : x; } -static float nsvg__roundf(float x) { return (x >= 0) ? floorf(x + 0.5) : ceilf(x - 0.5); } +static float nsvg__roundf(float x) { return (x >= 0) ? (float)floorf(x + 0.5f) : (float)ceilf(x - 0.5f); } static void nsvg__flattenCubicBez(NSVGrasterizer* r, float x1, float y1, float x2, float y2, diff --git a/libraries/ZWidget/src/core/pathfill.cpp b/libraries/ZWidget/src/core/pathfill.cpp index bac3dfb50..cb604ef3f 100644 --- a/libraries/ZWidget/src/core/pathfill.cpp +++ b/libraries/ZWidget/src/core/pathfill.cpp @@ -146,7 +146,7 @@ void PathFillRasterizer::Rasterize(const PathFillDesc& path, uint8_t* dest, int height = block_height; scanlines.resize(block_height); - first_scanline = scanlines.size(); + first_scanline = (int)scanlines.size(); last_scanline = 0; } @@ -264,7 +264,7 @@ void PathFillRasterizer::Clear() } } - first_scanline = scanlines.size(); + first_scanline = (int)scanlines.size(); last_scanline = 0; } diff --git a/libraries/ZWidget/src/core/theme.cpp b/libraries/ZWidget/src/core/theme.cpp index b92839989..415224429 100644 --- a/libraries/ZWidget/src/core/theme.cpp +++ b/libraries/ZWidget/src/core/theme.cpp @@ -156,9 +156,13 @@ DarkWidgetTheme::DarkWidgetTheme() auto tabbar_tab = RegisterStyle(std::make_unique(widget), "tabbar-tab"); auto tabwidget_stack = RegisterStyle(std::make_unique(widget), "tabwidget-stack"); auto checkbox_label = RegisterStyle(std::make_unique(widget), "checkbox-label"); + auto menubar = RegisterStyle(std::make_unique(widget), "menubar"); auto menubaritem = RegisterStyle(std::make_unique(widget), "menubaritem"); auto menu = RegisterStyle(std::make_unique(widget), "menu"); auto menuitem = RegisterStyle(std::make_unique(widget), "menuitem"); + auto toolbar = RegisterStyle(std::make_unique(widget), "toolbar"); + auto toolbarbutton = RegisterStyle(std::make_unique(widget), "toolbarbutton"); + auto statusbar = RegisterStyle(std::make_unique(widget), "statusbar"); widget->SetString("font-family", "NotoSans"); widget->SetColor("color", Colorf::fromRgba8(226, 223, 219)); @@ -235,8 +239,18 @@ DarkWidgetTheme::DarkWidgetTheme() checkbox_label->SetColor("unchecked-outer-border-color", Colorf::fromRgba8(99, 99, 99)); checkbox_label->SetColor("unchecked-inner-border-color", Colorf::fromRgba8(51, 51, 51)); + menubar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33)); + toolbar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33)); + statusbar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33)); + + toolbarbutton->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78)); + toolbarbutton->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88)); + + menubaritem->SetColor("color", Colorf::fromRgba8(226, 223, 219)); menubaritem->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78)); + menubaritem->SetColor("hover", "color", Colorf::fromRgba8(0, 0, 0)); menubaritem->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88)); + menubaritem->SetColor("down", "color", Colorf::fromRgba8(0, 0, 0)); menu->SetDouble("noncontent-left", 5.0); menu->SetDouble("noncontent-top", 5.0); @@ -266,6 +280,7 @@ LightWidgetTheme::LightWidgetTheme() auto tabbar_tab = RegisterStyle(std::make_unique(widget), "tabbar-tab"); auto tabwidget_stack = RegisterStyle(std::make_unique(widget), "tabwidget-stack"); auto checkbox_label = RegisterStyle(std::make_unique(widget), "checkbox-label"); + auto menubar = RegisterStyle(std::make_unique(widget), "menubar"); auto menubaritem = RegisterStyle(std::make_unique(widget), "menubaritem"); auto menu = RegisterStyle(std::make_unique(widget), "menu"); auto menuitem = RegisterStyle(std::make_unique(widget), "menuitem"); @@ -345,13 +360,19 @@ LightWidgetTheme::LightWidgetTheme() checkbox_label->SetColor("unchecked-outer-border-color", Colorf::fromRgba8(156, 156, 156)); checkbox_label->SetColor("unchecked-inner-border-color", Colorf::fromRgba8(200, 200, 200)); + menubar->SetColor("background-color", Colorf::fromRgba8(70, 70, 70)); + + menubaritem->SetColor("color", Colorf::fromRgba8(226, 223, 219)); menubaritem->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200)); + menubaritem->SetColor("hover", "color", Colorf::fromRgba8(0, 0, 0)); menubaritem->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190)); + menubaritem->SetColor("down", "color", Colorf::fromRgba8(0, 0, 0)); menu->SetDouble("noncontent-left", 5.0); menu->SetDouble("noncontent-top", 5.0); menu->SetDouble("noncontent-right", 5.0); menu->SetDouble("noncontent-bottom", 5.0); + menu->SetColor("background-color", Colorf::fromRgba8(255, 255, 255)); menu->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); menu->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); menu->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); diff --git a/libraries/ZWidget/src/core/truetypefont.cpp b/libraries/ZWidget/src/core/truetypefont.cpp index ccc65a26c..757929e3e 100644 --- a/libraries/ZWidget/src/core/truetypefont.cpp +++ b/libraries/ZWidget/src/core/truetypefont.cpp @@ -23,7 +23,7 @@ TrueTypeFont::TrueTypeFont(std::shared_ptr initdata, int t if (memcmp(versionTag.data(), "ttcf", 4) == 0) // TTC header { ttcHeader.Load(reader); - if (ttcFontIndex >= ttcHeader.numFonts) + if (ttcFontIndex >= (int)ttcHeader.numFonts) throw std::runtime_error("TTC font index out of bounds"); reader.Seek(ttcHeader.tableDirectoryOffsets[ttcFontIndex]); } @@ -122,7 +122,7 @@ TrueTypeGlyph TrueTypeFont::LoadGlyph(uint32_t glyphIndex, double height) const path.fill_mode = PathFillMode::winding; int startPoint = 0; - int numberOfContours = g.endPtsOfContours.size(); + int numberOfContours = (int)g.endPtsOfContours.size(); for (int i = 0; i < numberOfContours; i++) { int endPoint = g.endPtsOfContours[i]; @@ -310,7 +310,7 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos if (numberOfContours > 0) // Simple glyph { - int pointsOffset = g.points.size(); + int pointsOffset = (int)g.points.size(); for (ttf_uint16 i = 0; i < numberOfContours; i++) g.endPtsOfContours.push_back(pointsOffset + reader.ReadUInt16()); @@ -339,15 +339,15 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos if (g.flags[i] & TTF_X_SHORT_VECTOR) { ttf_int16 x = reader.ReadUInt8(); - g.points[i].x = (g.flags[i] & TTF_X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) ? x : -x; + g.points[i].x = (float)((g.flags[i] & TTF_X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) ? x : -x); } else if (g.flags[i] & TTF_X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) { - g.points[i].x = 0; + g.points[i].x = 0.0f; } else { - g.points[i].x = reader.ReadInt16(); + g.points[i].x = (float)reader.ReadInt16(); } } @@ -356,15 +356,15 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos if (g.flags[i] & TTF_Y_SHORT_VECTOR) { ttf_int16 y = reader.ReadUInt8(); - g.points[i].y = (g.flags[i] & TTF_Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) ? y : -y; + g.points[i].y = (float)((g.flags[i] & TTF_Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) ? y : -y); } else if (g.flags[i] & TTF_Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) { - g.points[i].y = 0; + g.points[i].y = 0.0f; } else { - g.points[i].y = reader.ReadInt16(); + g.points[i].y = (float)reader.ReadInt16(); } } @@ -380,7 +380,7 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos if (compositeDepth == 8) throw std::runtime_error("Composite glyph recursion exceeded"); - int parentPointsOffset = g.points.size(); + int parentPointsOffset = (int)g.points.size(); bool weHaveInstructions = false; while (true) @@ -420,7 +420,7 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos bool transform = true; if (flags & TTF_WE_HAVE_A_SCALE) { - ttf_F2DOT14 scale = F2DOT14_ToFloat(reader.ReadF2DOT14()); + float scale = F2DOT14_ToFloat(reader.ReadF2DOT14()); mat2x2[0] = scale; mat2x2[1] = 0; mat2x2[2] = 0; @@ -445,7 +445,7 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos transform = false; } - int childPointsOffset = g.points.size(); + int childPointsOffset = (int)g.points.size(); LoadGlyph(g, childGlyphIndex, compositeDepth + 1); if (transform) @@ -463,8 +463,8 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos if (flags & TTF_ARGS_ARE_XY_VALUES) { - dx = argument1; - dy = argument2; + dx = (float)argument1; + dy = (float)argument2; // Spec states we must fall back to TTF_UNSCALED_COMPONENT_OFFSET if both flags are set if ((flags & (TTF_SCALED_COMPONENT_OFFSET | TTF_UNSCALED_COMPONENT_OFFSET)) == TTF_SCALED_COMPONENT_OFFSET) @@ -976,8 +976,8 @@ void TTF_NamingTable::Load(TrueTypeFileReader& reader) for (ttf_uint16 i = 0; i < langTagCount; i++) { LangTagRecord record; - ttf_uint16 length; - ttf_Offset16 langTagOffset; + record.length = reader.ReadUInt16(); + record.langTagOffset = reader.ReadOffset16(); langTagRecord.push_back(record); } } diff --git a/libraries/ZWidget/src/core/widget.cpp b/libraries/ZWidget/src/core/widget.cpp index 9d380917c..e0c637f08 100644 --- a/libraries/ZWidget/src/core/widget.cpp +++ b/libraries/ZWidget/src/core/widget.cpp @@ -7,13 +7,17 @@ #include #include -Widget::Widget(Widget* parent, WidgetType type) : Type(type) +Widget::Widget(Widget* parent, WidgetType type, RenderAPI renderAPI) : Type(type) { if (type != WidgetType::Child) { Widget* owner = parent ? parent->Window() : nullptr; - DispWindow = DisplayWindow::Create(this, type == WidgetType::Popup, owner ? owner->DispWindow.get() : nullptr); - DispCanvas = Canvas::create(DispWindow.get()); + DispWindow = DisplayWindow::Create(this, type == WidgetType::Popup, owner ? owner->DispWindow.get() : nullptr, renderAPI); + if (renderAPI == RenderAPI::Unspecified || renderAPI == RenderAPI::Bitmap) + { + DispCanvas = Canvas::create(); + DispCanvas->attach(DispWindow.get()); + } SetStyleState("root"); SetWindowBackground(GetStyleColor("window-background")); @@ -30,6 +34,9 @@ Widget::Widget(Widget* parent, WidgetType type) : Type(type) Widget::~Widget() { + if (DispCanvas) + DispCanvas->detach(); + while (LastChildObj) delete LastChildObj; @@ -39,6 +46,17 @@ Widget::~Widget() DetachFromParent(); } +void Widget::SetCanvas(std::unique_ptr canvas) +{ + if (DispWindow) + { + if (DispCanvas) + DispCanvas->detach(); + DispCanvas = std::move(canvas); + DispCanvas->attach(DispWindow.get()); + } +} + void Widget::SetParent(Widget* newParent) { if (ParentObj != newParent) @@ -204,6 +222,15 @@ void Widget::ShowFullscreen() } } +bool Widget::IsFullscreen() +{ + if (Type != WidgetType::Child) + { + return DispWindow->IsWindowFullscreen(); + } + return false; +} + void Widget::ShowMaximized() { if (Type != WidgetType::Child) @@ -307,9 +334,12 @@ void Widget::Update() void Widget::Repaint() { Widget* w = Window(); - w->DispCanvas->begin(WindowBackground); - w->Paint(w->DispCanvas.get()); - w->DispCanvas->end(); + if (w->DispCanvas) + { + w->DispCanvas->begin(WindowBackground); + w->Paint(w->DispCanvas.get()); + w->DispCanvas->end(); + } } void Widget::Paint(Canvas* canvas) @@ -429,7 +459,7 @@ void Widget::SetCursor(StandardCursor cursor) } } -void Widget::CaptureMouse() +void Widget::SetPointerCapture() { Widget* w = Window(); if (w && w->CaptureWidget != this) @@ -439,7 +469,7 @@ void Widget::CaptureMouse() } } -void Widget::ReleaseMouseCapture() +void Widget::ReleasePointerCapture() { Widget* w = Window(); if (w && w->CaptureWidget != nullptr) @@ -449,6 +479,24 @@ void Widget::ReleaseMouseCapture() } } +void Widget::SetModalCapture() +{ + Widget* w = Window(); + if (w && w->CaptureWidget != this) + { + w->CaptureWidget = this; + } +} + +void Widget::ReleaseModalCapture() +{ + Widget* w = Window(); + if (w && w->CaptureWidget != nullptr) + { + w->CaptureWidget = nullptr; + } +} + std::string Widget::GetClipboardText() { Widget* w = Window(); @@ -485,6 +533,24 @@ Canvas* Widget::GetCanvas() const return nullptr; } +bool Widget::IsParent(const Widget* w) const +{ + while (w) + { + w = w->Parent(); + if (w == this) + return true; + } + return false; +} + +bool Widget::IsChild(const Widget* w) const +{ + if (!w) + return false; + return w->IsParent(this); +} + Widget* Widget::ChildAt(const Point& pos) { for (Widget* cur = LastChild(); cur != nullptr; cur = cur->PrevSibling()) @@ -790,6 +856,18 @@ void* Widget::GetNativeHandle() return w ? w->DispWindow->GetNativeHandle() : nullptr; } +int Widget::GetNativePixelWidth() +{ + Widget* w = Window(); + return w ? w->DispWindow->GetPixelWidth() : 0; +} + +int Widget::GetNativePixelHeight() +{ + Widget* w = Window(); + return w ? w->DispWindow->GetPixelHeight() : 0; +} + void Widget::SetStyleClass(const std::string& themeClass) { if (StyleClass != themeClass) diff --git a/libraries/ZWidget/src/systemdialogs/folder_browse_dialog.cpp b/libraries/ZWidget/src/systemdialogs/folder_browse_dialog.cpp deleted file mode 100644 index 754e397e2..000000000 --- a/libraries/ZWidget/src/systemdialogs/folder_browse_dialog.cpp +++ /dev/null @@ -1,188 +0,0 @@ - -#include "systemdialogs/folder_browse_dialog.h" -#include "core/widget.h" -#include "window/window.h" -#include - -#if defined(WIN32) -#include - -namespace -{ - static std::string from_utf16(const std::wstring& str) - { - if (str.empty()) return {}; - int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr); - if (needed == 0) - throw std::runtime_error("WideCharToMultiByte failed"); - std::string result; - result.resize(needed); - needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr); - if (needed == 0) - throw std::runtime_error("WideCharToMultiByte failed"); - return result; - } - - static std::wstring to_utf16(const std::string& str) - { - if (str.empty()) return {}; - int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); - if (needed == 0) - throw std::runtime_error("MultiByteToWideChar failed"); - std::wstring result; - result.resize(needed); - needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size()); - if (needed == 0) - throw std::runtime_error("MultiByteToWideChar failed"); - return result; - } - - template - class ComPtr - { - public: - ComPtr() { Ptr = nullptr; } - ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } - ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; } - ~ComPtr() { reset(); } - ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; } - void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; } - T* get() { return Ptr; } - static IID GetIID() { return __uuidof(T); } - void** InitPtr() { return (void**)TypedInitPtr(); } - T** TypedInitPtr() { reset(); return &Ptr; } - operator T* () const { return Ptr; } - T* operator ->() const { return Ptr; } - T* Ptr; - }; -} - -class BrowseFolderDialogImpl : public BrowseFolderDialog -{ -public: - BrowseFolderDialogImpl(Widget *owner) : owner(owner) - { - } - - Widget *owner = nullptr; - - std::string selected_path; - std::string initial_directory; - std::string title; - - bool Show() override - { - std::wstring title16 = to_utf16(title); - std::wstring initial_directory16 = to_utf16(initial_directory); - - HRESULT result; - ComPtr open_dialog; - - result = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, open_dialog.GetIID(), open_dialog.InitPtr()); - throw_if_failed(result, "CoCreateInstance(FileOpenDialog) failed"); - - result = open_dialog->SetTitle(title16.c_str()); - throw_if_failed(result, "IFileOpenDialog.SetTitle failed"); - - result = open_dialog->SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST); - throw_if_failed(result, "IFileOpenDialog.SetOptions((FOS_PICKFOLDERS) failed"); - - if (initial_directory16.length() > 0) - { - LPITEMIDLIST item_id_list = nullptr; - SFGAOF flags = 0; - result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags); - throw_if_failed(result, "SHParseDisplayName failed"); - - ComPtr folder_item; - result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr()); - ILFree(item_id_list); - throw_if_failed(result, "SHCreateItemFromParsingName failed"); - - /* This code requires Windows Vista or newer: - ComPtr folder_item; - result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr()); - throw_if_failed(result, "SHCreateItemFromParsingName failed"); - */ - - if (folder_item) - { - result = open_dialog->SetFolder(folder_item); - throw_if_failed(result, "IFileOpenDialog.SetFolder failed"); - } - } - - if (owner && owner->Window()) - result = open_dialog->Show((HWND)owner->Window()->GetNativeHandle()); - else - result = open_dialog->Show(0); - - if (SUCCEEDED(result)) - { - ComPtr chosen_folder; - result = open_dialog->GetResult(chosen_folder.TypedInitPtr()); - throw_if_failed(result, "IFileOpenDialog.GetResult failed"); - - WCHAR *buffer = nullptr; - result = chosen_folder->GetDisplayName(SIGDN_FILESYSPATH, &buffer); - throw_if_failed(result, "IShellItem.GetDisplayName failed"); - - std::wstring output_directory16; - if (buffer) - { - try - { - output_directory16 = buffer; - } - catch (...) - { - CoTaskMemFree(buffer); - throw; - } - } - - CoTaskMemFree(buffer); - selected_path = from_utf16(output_directory16); - return true; - } - else - { - return false; - } - } - - std::string BrowseFolderDialog::SelectedPath() const override - { - return selected_path; - } - - void BrowseFolderDialog::SetInitialDirectory(const std::string& path) override - { - initial_directory = path; - } - - void BrowseFolderDialog::SetTitle(const std::string& newtitle) override - { - title = newtitle; - } - - void throw_if_failed(HRESULT result, const std::string &error) - { - if (FAILED(result)) - throw std::runtime_error(error); - } -}; - -std::unique_ptr BrowseFolderDialog::Create(Widget* owner) -{ - return std::make_unique(owner); -} - -#else - -std::unique_ptr BrowseFolderDialog::Create(Widget* owner) -{ - return {}; -} - -#endif diff --git a/libraries/ZWidget/src/systemdialogs/open_file_dialog.cpp b/libraries/ZWidget/src/systemdialogs/open_file_dialog.cpp index ca7bc8f30..6d0ce3931 100644 --- a/libraries/ZWidget/src/systemdialogs/open_file_dialog.cpp +++ b/libraries/ZWidget/src/systemdialogs/open_file_dialog.cpp @@ -1,286 +1,12 @@ #include "systemdialogs/open_file_dialog.h" -#include "core/widget.h" #include "window/window.h" -#include - -#if defined(WIN32) -#include - -namespace -{ - static std::string from_utf16(const std::wstring& str) - { - if (str.empty()) return {}; - int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr); - if (needed == 0) - throw std::runtime_error("WideCharToMultiByte failed"); - std::string result; - result.resize(needed); - needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr); - if (needed == 0) - throw std::runtime_error("WideCharToMultiByte failed"); - return result; - } - - static std::wstring to_utf16(const std::string& str) - { - if (str.empty()) return {}; - int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); - if (needed == 0) - throw std::runtime_error("MultiByteToWideChar failed"); - std::wstring result; - result.resize(needed); - needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size()); - if (needed == 0) - throw std::runtime_error("MultiByteToWideChar failed"); - return result; - } - - template - class ComPtr - { - public: - ComPtr() { Ptr = nullptr; } - ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } - ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; } - ~ComPtr() { reset(); } - ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; } - void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; } - T* get() { return Ptr; } - static IID GetIID() { return __uuidof(T); } - void** InitPtr() { return (void**)TypedInitPtr(); } - T** TypedInitPtr() { reset(); return &Ptr; } - operator T* () const { return Ptr; } - T* operator ->() const { return Ptr; } - T* Ptr; - }; -} - -class OpenFileDialogImpl : public OpenFileDialog -{ -public: - OpenFileDialogImpl(Widget* owner) : owner(owner) - { - } - - Widget* owner = nullptr; - - std::string initial_directory; - std::string initial_filename; - std::string title; - std::vector filenames; - bool multi_select = false; - - struct Filter - { - std::string description; - std::string extension; - }; - std::vector filters; - int filterindex = 0; - std::string defaultext; - - bool Show() override - { - std::wstring title16 = to_utf16(title); - std::wstring initial_directory16 = to_utf16(initial_directory); - - HRESULT result; - ComPtr open_dialog; - - result = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, open_dialog.GetIID(), open_dialog.InitPtr()); - throw_if_failed(result, "CoCreateInstance(FileOpenDialog) failed"); - - result = open_dialog->SetTitle(title16.c_str()); - throw_if_failed(result, "IFileOpenDialog.SetTitle failed"); - - if (!initial_filename.empty()) - { - result = open_dialog->SetFileName(to_utf16(initial_filename).c_str()); - throw_if_failed(result, "IFileOpenDialog.SetFileName failed"); - } - - FILEOPENDIALOGOPTIONS options = FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST; - if (multi_select) - options |= FOS_ALLOWMULTISELECT; - result = open_dialog->SetOptions(options); - throw_if_failed(result, "IFileOpenDialog.SetOptions() failed"); - - if (!filters.empty()) - { - std::vector filterspecs(filters.size()); - std::vector descriptions(filters.size()); - std::vector extensions(filters.size()); - for (size_t i = 0; i < filters.size(); i++) - { - descriptions[i] = to_utf16(filters[i].description); - extensions[i] = to_utf16(filters[i].extension); - COMDLG_FILTERSPEC& spec = filterspecs[i]; - spec.pszName = descriptions[i].c_str(); - spec.pszSpec = extensions[i].c_str(); - } - result = open_dialog->SetFileTypes((UINT)filterspecs.size(), filterspecs.data()); - throw_if_failed(result, "IFileOpenDialog.SetFileTypes() failed"); - - if ((size_t)filterindex < filters.size()) - { - result = open_dialog->SetFileTypeIndex(filterindex); - throw_if_failed(result, "IFileOpenDialog.SetFileTypeIndex() failed"); - } - } - - if (!defaultext.empty()) - { - result = open_dialog->SetDefaultExtension(to_utf16(defaultext).c_str()); - throw_if_failed(result, "IFileOpenDialog.SetDefaultExtension() failed"); - } - - if (initial_directory16.length() > 0) - { - LPITEMIDLIST item_id_list = nullptr; - SFGAOF flags = 0; - result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags); - throw_if_failed(result, "SHParseDisplayName failed"); - - ComPtr folder_item; - result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr()); - ILFree(item_id_list); - throw_if_failed(result, "SHCreateItemFromParsingName failed"); - - /* This code requires Windows Vista or newer: - ComPtr folder_item; - result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr()); - throw_if_failed(result, "SHCreateItemFromParsingName failed"); - */ - - if (folder_item) - { - result = open_dialog->SetFolder(folder_item); - throw_if_failed(result, "IFileOpenDialog.SetFolder failed"); - } - } - - if (owner && owner->Window()) - result = open_dialog->Show((HWND)owner->Window()->GetNativeHandle()); - else - result = open_dialog->Show(0); - - if (SUCCEEDED(result)) - { - ComPtr items; - result = open_dialog->GetSelectedItems(items.TypedInitPtr()); - throw_if_failed(result, "IFileOpenDialog.GetSelectedItems failed"); - - DWORD num_items = 0; - result = items->GetCount(&num_items); - throw_if_failed(result, "IShellItemArray.GetCount failed"); - - for (DWORD i = 0; i < num_items; i++) - { - ComPtr item; - result = items->GetItemAt(i, item.TypedInitPtr()); - throw_if_failed(result, "IShellItemArray.GetItemAt failed"); - - WCHAR* buffer = nullptr; - result = item->GetDisplayName(SIGDN_FILESYSPATH, &buffer); - throw_if_failed(result, "IShellItem.GetDisplayName failed"); - - std::wstring output16; - if (buffer) - { - try - { - output16 = buffer; - } - catch (...) - { - CoTaskMemFree(buffer); - throw; - } - } - - CoTaskMemFree(buffer); - filenames.push_back(from_utf16(output16)); - } - return true; - } - else - { - return false; - } - } - - std::string Filename() const override - { - return !filenames.empty() ? filenames.front() : std::string(); - } - - std::vector Filenames() const override - { - return filenames; - } - - void SetMultiSelect(bool new_multi_select) override - { - multi_select = new_multi_select; - } - - void SetFilename(const std::string& filename) override - { - initial_filename = filename; - } - - void AddFilter(const std::string& filter_description, const std::string& filter_extension) override - { - Filter f; - f.description = filter_description; - f.extension = filter_extension; - filters.push_back(std::move(f)); - } - - void ClearFilters() override - { - filters.clear(); - } - - void SetFilterIndex(int filter_index) override - { - filterindex = filter_index; - } - - void SetInitialDirectory(const std::string& path) override - { - initial_directory = path; - } - - void SetTitle(const std::string& newtitle) override - { - title = newtitle; - } - - void SetDefaultExtension(const std::string& extension) override - { - defaultext = extension; - } - - void throw_if_failed(HRESULT result, const std::string& error) - { - if (FAILED(result)) - throw std::runtime_error(error); - } -}; +#include "core/widget.h" std::unique_ptr OpenFileDialog::Create(Widget* owner) { - return std::make_unique(owner); + DisplayWindow* windowOwner = nullptr; + if (owner && owner->Window()) + windowOwner = owner->Window()->DispWindow.get(); + return DisplayBackend::Get()->CreateOpenFileDialog(windowOwner); } - -#else - -std::unique_ptr OpenFileDialog::Create(Widget* owner) -{ - return {}; -} - -#endif diff --git a/libraries/ZWidget/src/systemdialogs/open_folder_dialog.cpp b/libraries/ZWidget/src/systemdialogs/open_folder_dialog.cpp new file mode 100644 index 000000000..8ccf66237 --- /dev/null +++ b/libraries/ZWidget/src/systemdialogs/open_folder_dialog.cpp @@ -0,0 +1,12 @@ + +#include "systemdialogs/open_folder_dialog.h" +#include "window/window.h" +#include "core/widget.h" + +std::unique_ptr OpenFolderDialog::Create(Widget* owner) +{ + DisplayWindow* windowOwner = nullptr; + if (owner && owner->Window()) + windowOwner = owner->Window()->DispWindow.get(); + return DisplayBackend::Get()->CreateOpenFolderDialog(windowOwner); +} diff --git a/libraries/ZWidget/src/systemdialogs/save_file_dialog.cpp b/libraries/ZWidget/src/systemdialogs/save_file_dialog.cpp index 7bd8c00cd..0163fe7cb 100644 --- a/libraries/ZWidget/src/systemdialogs/save_file_dialog.cpp +++ b/libraries/ZWidget/src/systemdialogs/save_file_dialog.cpp @@ -1,263 +1,12 @@ #include "systemdialogs/save_file_dialog.h" -#include "core/widget.h" #include "window/window.h" -#include - -#if defined(WIN32) -#include - -namespace -{ - static std::string from_utf16(const std::wstring& str) - { - if (str.empty()) return {}; - int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr); - if (needed == 0) - throw std::runtime_error("WideCharToMultiByte failed"); - std::string result; - result.resize(needed); - needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr); - if (needed == 0) - throw std::runtime_error("WideCharToMultiByte failed"); - return result; - } - - static std::wstring to_utf16(const std::string& str) - { - if (str.empty()) return {}; - int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); - if (needed == 0) - throw std::runtime_error("MultiByteToWideChar failed"); - std::wstring result; - result.resize(needed); - needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size()); - if (needed == 0) - throw std::runtime_error("MultiByteToWideChar failed"); - return result; - } - - template - class ComPtr - { - public: - ComPtr() { Ptr = nullptr; } - ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } - ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; } - ~ComPtr() { reset(); } - ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; } - void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; } - T* get() { return Ptr; } - static IID GetIID() { return __uuidof(T); } - void** InitPtr() { return (void**)TypedInitPtr(); } - T** TypedInitPtr() { reset(); return &Ptr; } - operator T* () const { return Ptr; } - T* operator ->() const { return Ptr; } - T* Ptr; - }; -} - -class SaveFileDialogImpl : public SaveFileDialog -{ -public: - SaveFileDialogImpl(Widget* owner) : owner(owner) - { - } - - Widget* owner = nullptr; - - std::string filename; - std::string initial_directory; - std::string initial_filename; - std::string title; - std::vector filenames; - - struct Filter - { - std::string description; - std::string extension; - }; - std::vector filters; - int filterindex = 0; - std::string defaultext; - - bool Show() override - { - std::wstring title16 = to_utf16(title); - std::wstring initial_directory16 = to_utf16(initial_directory); - - HRESULT result; - ComPtr save_dialog; - - result = CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_ALL, save_dialog.GetIID(), save_dialog.InitPtr()); - throw_if_failed(result, "CoCreateInstance(FileSaveDialog) failed"); - - result = save_dialog->SetTitle(title16.c_str()); - throw_if_failed(result, "IFileSaveDialog.SetTitle failed"); - - if (!initial_filename.empty()) - { - result = save_dialog->SetFileName(to_utf16(initial_filename).c_str()); - throw_if_failed(result, "IFileSaveDialog.SetFileName failed"); - } - - FILEOPENDIALOGOPTIONS options = FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST; - result = save_dialog->SetOptions(options); - throw_if_failed(result, "IFileSaveDialog.SetOptions() failed"); - - if (!filters.empty()) - { - std::vector filterspecs(filters.size()); - std::vector descriptions(filters.size()); - std::vector extensions(filters.size()); - for (size_t i = 0; i < filters.size(); i++) - { - descriptions[i] = to_utf16(filters[i].description); - extensions[i] = to_utf16(filters[i].extension); - COMDLG_FILTERSPEC& spec = filterspecs[i]; - spec.pszName = descriptions[i].c_str(); - spec.pszSpec = extensions[i].c_str(); - } - result = save_dialog->SetFileTypes((UINT)filterspecs.size(), filterspecs.data()); - throw_if_failed(result, "IFileOpenDialog.SetFileTypes() failed"); - - if ((size_t)filterindex < filters.size()) - { - result = save_dialog->SetFileTypeIndex(filterindex); - throw_if_failed(result, "IFileOpenDialog.SetFileTypeIndex() failed"); - } - } - - if (!defaultext.empty()) - { - result = save_dialog->SetDefaultExtension(to_utf16(defaultext).c_str()); - throw_if_failed(result, "IFileOpenDialog.SetDefaultExtension() failed"); - } - - if (initial_directory16.length() > 0) - { - LPITEMIDLIST item_id_list = nullptr; - SFGAOF flags = 0; - result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags); - throw_if_failed(result, "SHParseDisplayName failed"); - - ComPtr folder_item; - result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr()); - ILFree(item_id_list); - throw_if_failed(result, "SHCreateItemFromParsingName failed"); - - /* This code requires Windows Vista or newer: - ComPtr folder_item; - result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr()); - throw_if_failed(result, "SHCreateItemFromParsingName failed"); - */ - - if (folder_item) - { - result = save_dialog->SetFolder(folder_item); - throw_if_failed(result, "IFileSaveDialog.SetFolder failed"); - } - } - - if (owner && owner->Window()) - result = save_dialog->Show((HWND)owner->Window()->GetNativeHandle()); - else - result = save_dialog->Show(0); - - if (SUCCEEDED(result)) - { - ComPtr chosen_folder; - result = save_dialog->GetResult(chosen_folder.TypedInitPtr()); - throw_if_failed(result, "IFileSaveDialog.GetResult failed"); - - WCHAR* buffer = nullptr; - result = chosen_folder->GetDisplayName(SIGDN_FILESYSPATH, &buffer); - throw_if_failed(result, "IShellItem.GetDisplayName failed"); - - std::wstring output16; - if (buffer) - { - try - { - output16 = buffer; - } - catch (...) - { - CoTaskMemFree(buffer); - throw; - } - } - - CoTaskMemFree(buffer); - filename = from_utf16(output16); - return true; - } - else - { - return false; - } - } - - std::string Filename() const override - { - return filename; - } - - void SetFilename(const std::string& filename) override - { - initial_filename = filename; - } - - void AddFilter(const std::string& filter_description, const std::string& filter_extension) override - { - Filter f; - f.description = filter_description; - f.extension = filter_extension; - filters.push_back(std::move(f)); - } - - void ClearFilters() override - { - filters.clear(); - } - - void SetFilterIndex(int filter_index) override - { - filterindex = filter_index; - } - - void SetInitialDirectory(const std::string& path) override - { - initial_directory = path; - } - - void SetTitle(const std::string& newtitle) override - { - title = newtitle; - } - - void SetDefaultExtension(const std::string& extension) override - { - defaultext = extension; - } - - void throw_if_failed(HRESULT result, const std::string& error) - { - if (FAILED(result)) - throw std::runtime_error(error); - } -}; +#include "core/widget.h" std::unique_ptr SaveFileDialog::Create(Widget* owner) { - return std::make_unique(owner); + DisplayWindow* windowOwner = nullptr; + if (owner && owner->Window()) + windowOwner = owner->Window()->DispWindow.get(); + return DisplayBackend::Get()->CreateSaveFileDialog(windowOwner); } - -#else - -std::unique_ptr SaveFileDialog::Create(Widget* owner) -{ - return {}; -} - -#endif diff --git a/libraries/ZWidget/src/widgets/imagebox/imagebox.cpp b/libraries/ZWidget/src/widgets/imagebox/imagebox.cpp index 37253c857..70323cc89 100644 --- a/libraries/ZWidget/src/widgets/imagebox/imagebox.cpp +++ b/libraries/ZWidget/src/widgets/imagebox/imagebox.cpp @@ -30,10 +30,44 @@ void ImageBox::SetImage(std::shared_ptr newImage) } } +void ImageBox::SetImageMode(ImageBoxMode newMode) +{ + if (mode != newMode) + { + mode = newMode; + Update(); + } +} + void ImageBox::OnPaint(Canvas* canvas) { if (image) { - canvas->drawImage(image, Point((GetWidth() - (double)image->GetWidth()) * 0.5, (GetHeight() - (double)image->GetHeight()) * 0.5)); + if (mode == ImageBoxMode::Center) + { + canvas->drawImage(image, Point((GetWidth() - (double)image->GetWidth()) * 0.5, (GetHeight() - (double)image->GetHeight()) * 0.5)); + } + else if (mode == ImageBoxMode::Contain) + { + double bw = GetWidth(); + double bh = GetHeight(); + double iw = image->GetWidth(); + double ih = image->GetHeight(); + double xscale = bw / iw; + double yscale = bh / ih; + double scale = std::min(xscale, yscale); + canvas->drawImage(image, Rect::xywh((bw - iw * scale) * 0.5, (bh - ih * scale) * 0.5, iw * scale, ih * scale)); + } + else if (mode == ImageBoxMode::Cover) + { + double bw = GetWidth(); + double bh = GetHeight(); + double iw = image->GetWidth(); + double ih = image->GetHeight(); + double xscale = bw / iw; + double yscale = bh / ih; + double scale = std::max(xscale, yscale); + canvas->drawImage(image, Rect::xywh((bw - iw * scale) * 0.5, (bh - ih * scale) * 0.5, iw * scale, ih * scale)); + } } } diff --git a/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp b/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp index d2eb62f0a..b34a18c5e 100644 --- a/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp +++ b/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp @@ -1,4 +1,4 @@ -#include + #include "widgets/lineedit/lineedit.h" #include "core/utf8reader.h" #include "core/colorf.h" @@ -19,8 +19,6 @@ LineEdit::LineEdit(Widget* parent) : Widget(parent) LineEdit::~LineEdit() { - delete timer; - delete scroll_timer; } bool LineEdit::IsReadOnly() const @@ -310,7 +308,7 @@ bool LineEdit::OnMouseDown(const Point& pos, InputKey key) { if (HasFocus()) { - CaptureMouse(); + SetPointerCapture(); mouse_selecting = true; cursor_pos = GetCharacterIndex(pos.x); SetTextSelection(cursor_pos, 0); @@ -335,14 +333,14 @@ bool LineEdit::OnMouseUp(const Point& pos, InputKey key) { if (ignore_mouse_events) // This prevents text selection from changing from what was set when focus was gained. { - ReleaseMouseCapture(); + ReleasePointerCapture(); ignore_mouse_events = false; mouse_selecting = false; } else { scroll_timer->Stop(); - ReleaseMouseCapture(); + ReleasePointerCapture(); mouse_selecting = false; int sel_end = GetCharacterIndex(pos.x); SetSelectionLength(sel_end - selection_start); diff --git a/libraries/ZWidget/src/widgets/listview/listview.cpp b/libraries/ZWidget/src/widgets/listview/listview.cpp index 9c9704f4c..54f48552d 100644 --- a/libraries/ZWidget/src/widgets/listview/listview.cpp +++ b/libraries/ZWidget/src/widgets/listview/listview.cpp @@ -16,6 +16,19 @@ void ListView::AddItem(const std::string& text) Update(); } +void ListView::RemoveItem(int index) +{ + if (index >= 0 && index < items.size()) + { + items.erase(items.begin() + index); + } + + if (selectedItem == items.size()) + { + selectedItem = !items.empty() ? (int)items.size() - 1 : 0; + } +} + void ListView::Activate() { if (OnActivated) diff --git a/libraries/ZWidget/src/widgets/mainwindow/mainwindow.cpp b/libraries/ZWidget/src/widgets/mainwindow/mainwindow.cpp index 7a98a2ce5..a59680a4e 100644 --- a/libraries/ZWidget/src/widgets/mainwindow/mainwindow.cpp +++ b/libraries/ZWidget/src/widgets/mainwindow/mainwindow.cpp @@ -4,10 +4,13 @@ #include "widgets/toolbar/toolbar.h" #include "widgets/statusbar/statusbar.h" -MainWindow::MainWindow() : Widget(nullptr, WidgetType::Window) +MainWindow::MainWindow(RenderAPI api) : Widget(nullptr, WidgetType::Window, api) { MenubarWidget = new Menubar(this); - // ToolbarWidget = new Toolbar(this); + TopToolbarWidget = new Toolbar(this); + TopToolbarWidget->SetDirection(ToolbarDirection::Horizontal); + LeftToolbarWidget = new Toolbar(this); + LeftToolbarWidget->SetDirection(ToolbarDirection::Vertical); StatusbarWidget = new Statusbar(this); } @@ -32,9 +35,10 @@ void MainWindow::OnGeometryChanged() Size s = GetSize(); MenubarWidget->SetFrameGeometry(0.0, 0.0, s.width, 32.0); - // ToolbarWidget->SetFrameGeometry(0.0, 32.0, s.width, 36.0); + TopToolbarWidget->SetFrameGeometry(0.0, 32.0, s.width, 32.0); + LeftToolbarWidget->SetFrameGeometry(0.0, 64.0, 32.0, s.height - 64.0); StatusbarWidget->SetFrameGeometry(0.0, s.height - 32.0, s.width, 32.0); if (CentralWidget) - CentralWidget->SetFrameGeometry(0.0, 32.0, s.width, s.height - 32.0 - 32.0); + CentralWidget->SetFrameGeometry(32.0, 64.0, s.width - 32.0, s.height - 64.0 - 32.0); } diff --git a/libraries/ZWidget/src/widgets/menubar/menubar.cpp b/libraries/ZWidget/src/widgets/menubar/menubar.cpp index 517128508..37353756f 100644 --- a/libraries/ZWidget/src/widgets/menubar/menubar.cpp +++ b/libraries/ZWidget/src/widgets/menubar/menubar.cpp @@ -22,7 +22,17 @@ MenubarItem* Menubar::AddItem(std::string text, std::function void Menubar::ShowMenu(MenubarItem* item) { + int index = GetItemIndex(item); + if (index == currentMenubarItem) + return; + CloseMenu(); + SetFocus(); + SetModalCapture(); + currentMenubarItem = index; + if (currentMenubarItem != -1) + menuItems[currentMenubarItem]->SetStyleState("hover"); + modalMode = true; if (item->GetOpenCallback()) { openMenu = new Menu(this); @@ -38,8 +48,13 @@ void Menubar::ShowMenu(MenubarItem* item) void Menubar::CloseMenu() { - //delete openMenu; - //openMenu = nullptr; + if (currentMenubarItem != -1) + menuItems[currentMenubarItem]->SetStyleState(""); + currentMenubarItem = -1; + delete openMenu; + openMenu = nullptr; + ReleaseModalCapture(); + modalMode = false; } void Menubar::OnGeometryChanged() @@ -65,6 +80,139 @@ void Menubar::OnGeometryChanged() } } +MenubarItem* Menubar::GetMenubarItemAt(const Point& pos) +{ + Widget* widget = ChildAt(pos); + for (MenubarItem* item : menuItems) + { + if (widget == item) + return item; + } + return nullptr; +} + +bool Menubar::OnMouseDown(const Point& pos, InputKey key) +{ + if (!modalMode) + return Widget::OnMouseDown(pos, key); + + MenubarItem* item = GetMenubarItemAt(pos); + if (item) + ShowMenu(item); + else + CloseMenu(); + return true; +} + +bool Menubar::OnMouseUp(const Point& pos, InputKey key) +{ + if (!modalMode) + return Widget::OnMouseUp(pos, key); + + MenubarItem* item = GetMenubarItemAt(pos); + if (!item) + CloseMenu(); + return true; +} + +void Menubar::OnMouseMove(const Point& pos) +{ + if (!modalMode) + return Widget::OnMouseMove(pos); + + MenubarItem* item = GetMenubarItemAt(pos); + if (item) + ShowMenu(item); +} + +int Menubar::GetItemIndex(MenubarItem* item) +{ + int i = 0; + for (MenubarItem* cur : menuItems) + { + if (cur == item) + return i; + i++; + } + return -1; +} + +void Menubar::OnKeyDown(InputKey key) +{ + if (!modalMode) + return Widget::OnKeyDown(key); + + if (key == InputKey::Left) + { + if (!menuItems.empty()) + { + int index = currentMenubarItem - 1; + if (index < 0) + index = (int)menuItems.size() - 1; + ShowMenu(menuItems[index]); + } + } + else if (key == InputKey::Right) + { + if (!menuItems.empty()) + { + int index = currentMenubarItem + 1; + if (index == (int)menuItems.size()) + index = 0; + ShowMenu(menuItems[index]); + } + } + else if (key == InputKey::Up) + { + if (openMenu) + { + // Keep trying until we don't find a separator + for (int i = 0; i < 10; i++) + { + if (!openMenu->selectedItem || !openMenu->selectedItem->PrevSibling()) + { + if (openMenu->LastChild()) + openMenu->SetSelected(static_cast(openMenu->LastChild())); + } + else + { + openMenu->SetSelected(static_cast(openMenu->selectedItem->PrevSibling())); + } + + if (openMenu->selectedItem && openMenu->selectedItem->GetStyleClass() != "menuitemseparator") + break; + } + } + } + else if (key == InputKey::Down) + { + if (openMenu) + { + // Keep trying until we don't find a separator + for (int i = 0; i < 10; i++) + { + if (!openMenu->selectedItem || !openMenu->selectedItem->NextSibling()) + { + if (openMenu->FirstChild()) + openMenu->SetSelected(static_cast(openMenu->FirstChild())); + } + else + { + openMenu->SetSelected(static_cast(openMenu->selectedItem->NextSibling())); + } + + if (openMenu->selectedItem && openMenu->selectedItem->GetStyleClass() != "menuitemseparator") + break; + } + } + } + else if (key == InputKey::Enter) + { + if (openMenu && openMenu->selectedItem) + openMenu->selectedItem->Click(); + } +} + ///////////////////////////////////////////////////////////////////////////// MenubarItem::MenubarItem(Menubar* menubar, std::string text, bool alignRight) : Widget(menubar), menubar(menubar), text(text), AlignRight(alignRight) @@ -127,20 +275,10 @@ void Menu::SetRightPosition(const Point& pos) MenuItem* Menu::AddItem(std::shared_ptr icon, std::string text, std::function onClick) { - auto item = new MenuItem(this); + auto item = new MenuItem(this, [this, onClick]() { if (onCloseMenu) onCloseMenu(); if (onClick) onClick(); }); if (icon) item->icon->SetImage(icon); item->text->SetText(text); - /* - item->element->addEventListener("click", [=](Event* event) - { - event->stopPropagation(); - if (onCloseMenu) - onCloseMenu(); - if (onClick) - onClick(); - }); - */ return item; } @@ -152,7 +290,7 @@ MenuItemSeparator* Menu::AddSeparator() double Menu::GetPreferredWidth() const { - return 200.0; + return GridFitSize(200.0); } double Menu::GetPreferredHeight() const @@ -160,7 +298,8 @@ double Menu::GetPreferredHeight() const double h = 0.0; for (Widget* item = FirstChild(); item != nullptr; item = item->NextSibling()) { - h += 20.0; + double itemheight = GridFitSize((item->GetStyleClass() == "menuitemseparator") ? 7.0 : 30.0); + h += itemheight; } return h; } @@ -168,18 +307,31 @@ double Menu::GetPreferredHeight() const void Menu::OnGeometryChanged() { double w = GetWidth(); - double h = GetHeight(); double y = 0.0; for (Widget* item = FirstChild(); item != nullptr; item = item->NextSibling()) { - item->SetFrameGeometry(Rect::xywh(0.0, y, w, 20.0)); - y += 20.0; + double itemheight = GridFitSize((item->GetStyleClass() == "menuitemseparator") ? 7.0 : 30.0); + item->SetFrameGeometry(Rect::xywh(0.0, y, w, itemheight)); + y += itemheight; + } +} + +void Menu::SetSelected(MenuItem* item) +{ + if (selectedItem) + { + selectedItem->SetStyleState(""); + } + selectedItem = item; + if (selectedItem) + { + selectedItem->SetStyleState("hover"); } } ///////////////////////////////////////////////////////////////////////////// -MenuItem::MenuItem(Widget* parent) : Widget(parent) +MenuItem::MenuItem(Menu* menu, std::function onClick) : Widget(menu), menu(menu), onClick(onClick) { SetStyleClass("menuitem"); icon = new ImageBox(this); @@ -188,25 +340,41 @@ MenuItem::MenuItem(Widget* parent) : Widget(parent) void MenuItem::OnMouseMove(const Point& pos) { - if (GetStyleState().empty()) + menu->SetSelected(this); +} + +bool MenuItem::OnMouseUp(const Point& pos, InputKey key) +{ + Click(); + return true; +} + +void MenuItem::Click() +{ + if (onClick) { - SetStyleState("hover"); + // We have to make a copy of the handler as it may delete 'this' + auto handler = onClick; + handler(); } } void MenuItem::OnMouseLeave() { - SetStyleState(""); + menu->SetSelected(nullptr); } void MenuItem::OnGeometryChanged() { - double iconwidth = icon->GetPreferredWidth(); - double iconheight = icon->GetPreferredHeight(); + double iconwidth = GridFitSize(icon->GetPreferredWidth()); + double iconheight = GridFitSize(icon->GetPreferredHeight()); double w = GetWidth(); double h = GetHeight(); - icon->SetFrameGeometry(Rect::xywh(0.0, (h - iconheight) * 0.5, iconwidth, iconheight)); - text->SetFrameGeometry(Rect::xywh(iconwidth, 0.0, w - iconwidth, h)); + double textheight = 19.0; + double x0 = GridFitPoint(5.0); + double x1 = GridFitPoint(5.0 + iconwidth); + icon->SetFrameGeometry(Rect::xywh(x0, GridFitPoint((h - iconheight) * 0.5), iconwidth, iconheight)); + text->SetFrameGeometry(Rect::xywh(x1, GridFitPoint((h - textheight) * 0.5), w - x1, textheight)); } ///////////////////////////////////////////////////////////////////////////// @@ -215,3 +383,8 @@ MenuItemSeparator::MenuItemSeparator(Widget* parent) : Widget(parent) { SetStyleClass("menuitemseparator"); } + +void MenuItemSeparator::OnPaint(Canvas* canvas) +{ + canvas->fillRect(Rect::xywh(0.0, GridFitPoint(GetHeight() * 0.5), GetWidth(), GridFitSize(1.0)), Colorf::fromRgba8(75, 75, 75)); +} diff --git a/libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp b/libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp index 634403fa7..c4e29329a 100644 --- a/libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp +++ b/libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp @@ -254,7 +254,7 @@ bool Scrollbar::OnMouseDown(const Point& pos, InputKey key) UpdatePartPositions(); Update(); - CaptureMouse(); + SetPointerCapture(); return true; } @@ -270,7 +270,7 @@ bool Scrollbar::OnMouseUp(const Point& pos, InputKey key) mouse_down_timer->Stop(); Update(); - ReleaseMouseCapture(); + ReleasePointerCapture(); return true; } diff --git a/libraries/ZWidget/src/widgets/statusbar/statusbar.cpp b/libraries/ZWidget/src/widgets/statusbar/statusbar.cpp index fd914298c..33faa15c3 100644 --- a/libraries/ZWidget/src/widgets/statusbar/statusbar.cpp +++ b/libraries/ZWidget/src/widgets/statusbar/statusbar.cpp @@ -5,6 +5,8 @@ Statusbar::Statusbar(Widget* parent) : Widget(parent) { + SetStyleClass("statusbar"); + CommandEdit = new LineEdit(this); CommandEdit->SetFrameGeometry(Rect::xywh(90.0, 4.0, 400.0, 23.0)); } @@ -15,5 +17,5 @@ Statusbar::~Statusbar() void Statusbar::OnPaint(Canvas* canvas) { - canvas->drawText(Point(16.0, 21.0), Colorf::fromRgba8(0, 0, 0), "Command:"); + canvas->drawText(Point(16.0, 21.0), Colorf::fromRgba8(226, 223, 219), "Command:"); } diff --git a/libraries/ZWidget/src/widgets/textedit/textedit.cpp b/libraries/ZWidget/src/widgets/textedit/textedit.cpp index 27a2d0230..338efb512 100644 --- a/libraries/ZWidget/src/widgets/textedit/textedit.cpp +++ b/libraries/ZWidget/src/widgets/textedit/textedit.cpp @@ -3,6 +3,7 @@ #include "widgets/scrollbar/scrollbar.h" #include "core/utf8reader.h" #include "core/colorf.h" +#include #ifdef _MSC_VER #pragma warning(disable: 4267) // warning C4267: 'initializing': conversion from 'size_t' to 'int', possible loss of data @@ -290,7 +291,7 @@ bool TextEdit::OnMouseDown(const Point& pos, InputKey key) { if (key == InputKey::LeftMouse) { - CaptureMouse(); + SetPointerCapture(); mouse_selecting = true; cursor_pos = GetCharacterIndex(pos); selection_start = cursor_pos; @@ -312,14 +313,14 @@ bool TextEdit::OnMouseUp(const Point& pos, InputKey key) { if (ignore_mouse_events) // This prevents text selection from changing from what was set when focus was gained. { - ReleaseMouseCapture(); + ReleasePointerCapture(); ignore_mouse_events = false; mouse_selecting = false; } else { scroll_timer->Stop(); - ReleaseMouseCapture(); + ReleasePointerCapture(); mouse_selecting = false; ivec2 sel_end = GetCharacterIndex(pos); selection_length = ToOffset(sel_end) - ToOffset(selection_start); diff --git a/libraries/ZWidget/src/widgets/toolbar/toolbar.cpp b/libraries/ZWidget/src/widgets/toolbar/toolbar.cpp index d028dd2d2..4629f3a1e 100644 --- a/libraries/ZWidget/src/widgets/toolbar/toolbar.cpp +++ b/libraries/ZWidget/src/widgets/toolbar/toolbar.cpp @@ -1,10 +1,63 @@ #include "widgets/toolbar/toolbar.h" +#include "widgets/toolbar/toolbarbutton.h" Toolbar::Toolbar(Widget* parent) : Widget(parent) { + SetStyleClass("toolbar"); } Toolbar::~Toolbar() { } + +void Toolbar::SetDirection(ToolbarDirection newDirection) +{ + if (direction != newDirection) + { + direction = newDirection; + Update(); + } +} + +ToolbarButton* Toolbar::AddButton(std::string icon, std::string text, std::function onClicked) +{ + ToolbarButton* button = new ToolbarButton(this); + if (!icon.empty()) + button->SetIcon(icon); + if (!text.empty()) + button->SetText(text); + button->OnClick = std::move(onClicked); + buttons.push_back(button); + return button; +} + +void Toolbar::OnGeometryChanged() +{ + if (direction == ToolbarDirection::Horizontal) + { + double x = 7.0; + double barHeight = GetHeight(); + double gap = 7.0; + for (ToolbarButton* button : buttons) + { + double width = button->GetPreferredWidth(); + double height = button->GetPreferredHeight(); + button->SetFrameGeometry(Rect::xywh(x, (barHeight - height) * 0.5, width, height)); + x += width + gap; + } + } + else + { + double y = 7.0; + double barWidth = GetWidth(); + double gap = 7.0; + for (ToolbarButton* button : buttons) + { + double width = button->GetPreferredWidth(); + double height = button->GetPreferredHeight(); + button->SetFrameGeometry(Rect::xywh((barWidth - width) * 0.5, y, width, height)); + y += height + gap; + } + } +} diff --git a/libraries/ZWidget/src/widgets/toolbar/toolbarbutton.cpp b/libraries/ZWidget/src/widgets/toolbar/toolbarbutton.cpp index adb6d08bf..5eda9a13d 100644 --- a/libraries/ZWidget/src/widgets/toolbar/toolbarbutton.cpp +++ b/libraries/ZWidget/src/widgets/toolbar/toolbarbutton.cpp @@ -3,12 +3,112 @@ ToolbarButton::ToolbarButton(Widget* parent) : Widget(parent) { + SetStyleClass("toolbarbutton"); } ToolbarButton::~ToolbarButton() { } -void ToolbarButton::OnPaint(Canvas* canvas) +void ToolbarButton::SetIcon(std::string icon) { + if (!icon.empty()) + { + if (!image) + image = new ImageBox(this); + image->SetImage(Image::LoadResource(icon, GetDpiScale())); + image->SetImageMode(ImageBoxMode::Contain); + } + else + { + delete image; + image = nullptr; + } +} + +void ToolbarButton::SetText(std::string text) +{ + if (!text.empty()) + { + if (!label) + label = new TextLabel(this); + label->SetText(text); + } + else + { + delete label; + label = nullptr; + } +} + +void ToolbarButton::Click() +{ + if (OnClick) + OnClick(); +} + +double ToolbarButton::GetPreferredWidth() +{ + double w = 0.0; + if (image) + w = 26.0; + if (label) + w += label->GetPreferredWidth(); + return w; +} + +double ToolbarButton::GetPreferredHeight() +{ + double h = 0.0; + if (image) + h = 24.0; + if (label) + h = std::max(h, label->GetPreferredHeight()); + return h; +} + +void ToolbarButton::OnMouseMove(const Point& pos) +{ + if (GetStyleState().empty()) + { + SetStyleState("hover"); + } +} + +void ToolbarButton::OnMouseLeave() +{ + SetStyleState(""); +} + +bool ToolbarButton::OnMouseDown(const Point& pos, InputKey key) +{ + SetStyleState("down"); + return true; +} + +bool ToolbarButton::OnMouseUp(const Point& pos, InputKey key) +{ + if (GetStyleState() == "down") + { + SetStyleState(""); + Repaint(); + Click(); + } + return true; +} + +void ToolbarButton::OnGeometryChanged() +{ + double totalHeight = GetPreferredHeight(); + double x = 0.0; + if (image) + { + image->SetFrameGeometry(Rect::xywh(0.0, (totalHeight - 24.0) * 0.5, 24.0, 24.0)); + x += 26.0; + } + if (label) + { + double labelHeight = label->GetPreferredHeight(); + label->SetFrameGeometry(Rect::xywh(x, (totalHeight - labelHeight) * 0.5, GetWidth() - x, labelHeight)); + } } diff --git a/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.cpp b/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.cpp new file mode 100644 index 000000000..1ebc0668f --- /dev/null +++ b/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.cpp @@ -0,0 +1,288 @@ + +#include "dbus_open_file_dialog.h" +#include + +DBusOpenFileDialog::DBusOpenFileDialog(std::string ownerHandle) : ownerHandle(ownerHandle) +{ +} + +std::string DBusOpenFileDialog::Filename() const +{ + return outputFilenames.empty() ? std::string() : outputFilenames.front(); +} + +std::vector DBusOpenFileDialog::Filenames() const +{ + return outputFilenames; +} + +void DBusOpenFileDialog::SetMultiSelect(bool multiselect) +{ + this->multiSelect = multiSelect; +} + +void DBusOpenFileDialog::SetFilename(const std::string &filename) +{ + inputFilename = filename; +} + +void DBusOpenFileDialog::SetDefaultExtension(const std::string& extension) +{ + defaultExt = extension; +} + +void DBusOpenFileDialog::AddFilter(const std::string &filter_description, const std::string &filter_extension) +{ + filters.push_back({ filter_description, filter_extension }); +} + +void DBusOpenFileDialog::ClearFilters() +{ + filters.clear(); +} + +void DBusOpenFileDialog::SetFilterIndex(int filter_index) +{ + this->filter_index = filter_index; +} + +void DBusOpenFileDialog::SetInitialDirectory(const std::string &path) +{ + initialDirectory = path; +} + +void DBusOpenFileDialog::SetTitle(const std::string &title) +{ + this->title = title; +} + +bool DBusOpenFileDialog::Show() +{ + // https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.FileChooser.html + + dbus_bool_t bresult = {}; + DBusError error = {}; + dbus_error_init(&error); + + DBusConnection* connection = dbus_bus_get(DBUS_BUS_SESSION, &error); + if (!connection) + { + dbus_error_free(&error); + return false; + } + + std::string busname = dbus_bus_get_unique_name(connection); + + DBusMessage* request = dbus_message_new_method_call("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.FileChooser", "OpenFile"); + if (!request) + { + dbus_connection_unref(connection); + dbus_error_free(&error); + return false; + } + + const char* parentWindow = ownerHandle.c_str(); + const char* title = this->title.c_str(); + + DBusMessageIter requestArgs = {}; + dbus_message_iter_init_append(request, &requestArgs); + + bresult = dbus_message_iter_append_basic(&requestArgs, DBUS_TYPE_STRING, &parentWindow); + bresult = dbus_message_iter_append_basic(&requestArgs, DBUS_TYPE_STRING, &title); + + DBusMessageIter requestOptions = {}; + bresult = dbus_message_iter_open_container(&requestArgs, DBUS_TYPE_ARRAY, "{sv}", &requestOptions); + + // handle_token - s race condition prevention for signal (/org/freedesktop/portal/desktop/request/SENDER/TOKEN) + // accept_label - s text label for the OK button + // modal - b makes dialog modal. Defaults to true. Not really sure what it means since nothing stayed modal on my computer + // directory - b open folder mode, added in version 3 + // filters - a(sa(us)) [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])] + // current_filter - sa(us) filter from filters that should be current filter + // choices - a(ssa(ss)s) list of serialized combo boxes to add to the file chooser + // current_name - s suggested name + + // current_folder - ay + if (!initialDirectory.empty()) + { + // Note: the docs unfortunately says "The portal implementation is free to ignore this option" + + const char* key = "current_folder"; + const char* value = initialDirectory.c_str(); + int valueCount = (int)initialDirectory.size() + 1; + + DBusMessageIter entry = {}; + bresult = dbus_message_iter_open_container(&requestOptions, DBUS_TYPE_DICT_ENTRY, nullptr, &entry); + bresult = dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); + + DBusMessageIter variant = {}; + DBusMessageIter array = {}; + bresult = dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "ay", &variant); + bresult = dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &array); + bresult = dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &value, valueCount); + bresult = dbus_message_iter_close_container(&variant, &array); + bresult = dbus_message_iter_close_container(&entry, &variant); + + bresult = dbus_message_iter_close_container(&requestOptions, &entry); + } + + // multiple - b + { + const char* key = "multiple"; + dbus_bool_t value = multiSelect; + + DBusMessageIter entry = {}; + bresult = dbus_message_iter_open_container(&requestOptions, DBUS_TYPE_DICT_ENTRY, nullptr, &entry); + bresult = dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); + + DBusMessageIter variant = {}; + bresult = dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &variant); + bresult = dbus_message_iter_append_basic(&variant, DBUS_TYPE_BOOLEAN, &value); + bresult = dbus_message_iter_close_container(&entry, &variant); + + bresult = dbus_message_iter_close_container(&requestOptions, &entry); + } + + bresult = dbus_message_iter_close_container(&requestArgs, &requestOptions); + + DBusMessage* response = dbus_connection_send_with_reply_and_block(connection, request, DBUS_TIMEOUT_USE_DEFAULT, &error); + if (!response) + { + dbus_message_unref(request); + dbus_connection_unref(connection); + dbus_error_free(&error); + return false; + } + + const char* handle = nullptr; + bresult = dbus_message_get_args(response, &error, DBUS_TYPE_OBJECT_PATH, &handle, DBUS_TYPE_INVALID); + if (!bresult) + { + dbus_message_unref(response); + dbus_message_unref(request); + dbus_connection_unref(connection); + dbus_error_free(&error); + return false; + } + + std::string signalObjectPath = handle; + + dbus_message_unref(response); + dbus_message_unref(request); + + std::string rule = "type='signal',interface='org.freedesktop.portal.Request',member='Response',path='" + signalObjectPath + "'"; + dbus_bus_add_match(connection, rule.c_str(), &error); + + // Wait for the response signal + // + // To do: process the run loop while we wait + // + DBusMessage* signalmsg = nullptr; + while (!signalmsg && dbus_connection_read_write(connection, -1)) + { + while (true) + { + DBusMessage* message = dbus_connection_pop_message(connection); + if (!message) + break; + + if (dbus_message_is_signal(message, "org.freedesktop.portal.Request", "Response") && dbus_message_get_path(message) == signalObjectPath) + { + signalmsg = message; + break; + } + else + { + dbus_message_unref(message); + } + } + } + dbus_bus_remove_match(connection, rule.c_str(), &error); + + // Read the response + + dbus_uint32_t responseCode = 0; + std::vector uris; + + DBusMessageIter signalArgs; + bresult = dbus_message_iter_init(signalmsg, &signalArgs); + + // response code - u + if (dbus_message_iter_get_arg_type(&signalArgs) == DBUS_TYPE_UINT32) + { + dbus_message_iter_get_basic(&signalArgs, &responseCode); + } + dbus_message_iter_next(&signalArgs); + + // results - a{sv} + if (dbus_message_iter_get_arg_type(&signalArgs) == DBUS_TYPE_ARRAY) + { + DBusMessageIter resultsArray = {}; + dbus_message_iter_recurse(&signalArgs, &resultsArray); + while (true) + { + int type = dbus_message_iter_get_arg_type(&resultsArray); + if (type != DBUS_TYPE_DICT_ENTRY) + break; + + DBusMessageIter entry = {}; + dbus_message_iter_recurse(&resultsArray, &entry); + + const char* key = nullptr; + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + + DBusMessageIter value = {}; + dbus_message_iter_recurse(&entry, &value); + + std::string k = key; + if (k == "uris" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_ARRAY) // as + { + DBusMessageIter uriArray = {}; + dbus_message_iter_recurse(&value, &uriArray); + while (true) + { + int type = dbus_message_iter_get_arg_type(&uriArray); + if (type != DBUS_TYPE_STRING) + break; + + const char* uri = nullptr; + dbus_message_iter_get_basic(&uriArray, &uri); + uris.push_back(uri); + + dbus_message_iter_next(&uriArray); + } + } + else if (k == "choices" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_ARRAY) // a(ss) + { + // An array of pairs of strings, + // the first string being the ID of a combobox that was passed into this call, + // the second string being the selected option. + } + else if (k == "current_filter" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_STRUCT) // sa(us) + { + // The filter that was selected. + // This may match a filter in the filter list or another filter that was applied unconditionally. + } + + dbus_message_iter_next(&entry); + dbus_message_iter_next(&resultsArray); + } + } + dbus_message_iter_next(&signalArgs); + + dbus_message_unref(signalmsg); + dbus_connection_unref(connection); + dbus_error_free(&error); + + if (responseCode != 0) // User cancelled + return false; + + for (const std::string& uri : uris) + { + if (uri.size() > 7 && uri.substr(0, 7) == "file://") + outputFilenames.push_back(uri.substr(7)); + } + + return !uris.empty(); +} diff --git a/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.h b/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.h new file mode 100644 index 000000000..f4efd2663 --- /dev/null +++ b/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.h @@ -0,0 +1,38 @@ +#pragma once + +#include "systemdialogs/open_file_dialog.h" + +class DBusOpenFileDialog : public OpenFileDialog +{ +public: + DBusOpenFileDialog(std::string ownerHandle); + + std::string Filename() const override; + std::vector Filenames() const override; + void SetMultiSelect(bool multiselect) override; + void SetFilename(const std::string &filename) override; + void SetDefaultExtension(const std::string& extension) override; + void AddFilter(const std::string &filter_description, const std::string &filter_extension) override; + void ClearFilters() override; + void SetFilterIndex(int filter_index) override; + void SetInitialDirectory(const std::string &path) override; + void SetTitle(const std::string& newtitle) override; + bool Show() override; + +private: + std::string ownerHandle; + std::string title; + std::string initialDirectory; + std::string inputFilename; + std::string defaultExt; + std::vector outputFilenames; + bool multiSelect = false; + + struct Filter + { + std::string description; + std::string extension; + }; + std::vector filters; + int filter_index = 0; +}; diff --git a/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.cpp b/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.cpp new file mode 100644 index 000000000..7bcd94953 --- /dev/null +++ b/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.cpp @@ -0,0 +1,26 @@ + +#include "dbus_open_folder_dialog.h" + +DBusOpenFolderDialog::DBusOpenFolderDialog(std::string ownerHandle) : ownerHandle(ownerHandle) +{ +} + +bool DBusOpenFolderDialog::Show() +{ + return false; +} + +std::string DBusOpenFolderDialog::SelectedPath() const +{ + return selected_path; +} + +void DBusOpenFolderDialog::SetInitialDirectory(const std::string& path) +{ + initial_directory = path; +} + +void DBusOpenFolderDialog::SetTitle(const std::string& newtitle) +{ + title = newtitle; +} diff --git a/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.h b/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.h new file mode 100644 index 000000000..3e40cc7bc --- /dev/null +++ b/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.h @@ -0,0 +1,21 @@ +#pragma once + +#include "systemdialogs/open_folder_dialog.h" + +class DBusOpenFolderDialog : public OpenFolderDialog +{ +public: + DBusOpenFolderDialog(std::string ownerHandle); + + bool Show() override; + std::string SelectedPath() const override; + void SetInitialDirectory(const std::string& path) override; + void SetTitle(const std::string& newtitle) override; + +private: + std::string ownerHandle; + + std::string selected_path; + std::string initial_directory; + std::string title; +}; diff --git a/libraries/ZWidget/src/window/dbus/dbus_save_file_dialog.cpp b/libraries/ZWidget/src/window/dbus/dbus_save_file_dialog.cpp new file mode 100644 index 000000000..ba6b6d1f4 --- /dev/null +++ b/libraries/ZWidget/src/window/dbus/dbus_save_file_dialog.cpp @@ -0,0 +1,54 @@ + +#include "dbus_save_file_dialog.h" + +DBusSaveFileDialog::DBusSaveFileDialog(std::string ownerHandle) : ownerHandle(ownerHandle) +{ +} + +bool DBusSaveFileDialog::Show() +{ + return false; +} + +std::string DBusSaveFileDialog::Filename() const +{ + return filename; +} + +void DBusSaveFileDialog::SetFilename(const std::string& filename) +{ + initial_filename = filename; +} + +void DBusSaveFileDialog::AddFilter(const std::string& filter_description, const std::string& filter_extension) +{ + Filter f; + f.description = filter_description; + f.extension = filter_extension; + filters.push_back(std::move(f)); +} + +void DBusSaveFileDialog::ClearFilters() +{ + filters.clear(); +} + +void DBusSaveFileDialog::SetFilterIndex(int filter_index) +{ + filterindex = filter_index; +} + +void DBusSaveFileDialog::SetInitialDirectory(const std::string& path) +{ + initial_directory = path; +} + +void DBusSaveFileDialog::SetTitle(const std::string& newtitle) +{ + title = newtitle; +} + +void DBusSaveFileDialog::SetDefaultExtension(const std::string& extension) +{ + defaultext = extension; +} diff --git a/libraries/ZWidget/src/window/dbus/dbus_save_file_dialog.h b/libraries/ZWidget/src/window/dbus/dbus_save_file_dialog.h new file mode 100644 index 000000000..835789b2c --- /dev/null +++ b/libraries/ZWidget/src/window/dbus/dbus_save_file_dialog.h @@ -0,0 +1,37 @@ +#pragma once + +#include "systemdialogs/save_file_dialog.h" + +class DBusSaveFileDialog : public SaveFileDialog +{ +public: + DBusSaveFileDialog(std::string ownerHandle); + + bool Show() override; + std::string Filename() const override; + void SetFilename(const std::string& filename) override; + void AddFilter(const std::string& filter_description, const std::string& filter_extension) override; + void ClearFilters() override; + void SetFilterIndex(int filter_index) override; + void SetInitialDirectory(const std::string& path) override; + void SetTitle(const std::string& newtitle) override; + void SetDefaultExtension(const std::string& extension) override; + +private: + std::string ownerHandle; + + std::string filename; + std::string initial_directory; + std::string initial_filename; + std::string title; + std::vector filenames; + + struct Filter + { + std::string description; + std::string extension; + }; + std::vector filters; + int filterindex = 0; + std::string defaultext; +}; diff --git a/libraries/ZWidget/src/window/sdl2/sdl2_display_backend.cpp b/libraries/ZWidget/src/window/sdl2/sdl2_display_backend.cpp new file mode 100644 index 000000000..a5b40f743 --- /dev/null +++ b/libraries/ZWidget/src/window/sdl2/sdl2_display_backend.cpp @@ -0,0 +1,38 @@ + +#include "sdl2_display_backend.h" +#include "sdl2_display_window.h" + +std::unique_ptr SDL2DisplayBackend::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) +{ + return std::make_unique(windowHost, popupWindow, static_cast(owner), renderAPI); +} + +void SDL2DisplayBackend::ProcessEvents() +{ + SDL2DisplayWindow::ProcessEvents(); +} + +void SDL2DisplayBackend::RunLoop() +{ + SDL2DisplayWindow::RunLoop(); +} + +void SDL2DisplayBackend::ExitLoop() +{ + SDL2DisplayWindow::ExitLoop(); +} + +Size SDL2DisplayBackend::GetScreenSize() +{ + return SDL2DisplayWindow::GetScreenSize(); +} + +void* SDL2DisplayBackend::StartTimer(int timeoutMilliseconds, std::function onTimer) +{ + return SDL2DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer)); +} + +void SDL2DisplayBackend::StopTimer(void* timerID) +{ + SDL2DisplayWindow::StopTimer(timerID); +} diff --git a/libraries/ZWidget/src/window/sdl2/sdl2_display_backend.h b/libraries/ZWidget/src/window/sdl2/sdl2_display_backend.h new file mode 100644 index 000000000..e80b8352e --- /dev/null +++ b/libraries/ZWidget/src/window/sdl2/sdl2_display_backend.h @@ -0,0 +1,19 @@ +#pragma once + +#include "window/window.h" + +class SDL2DisplayBackend : public DisplayBackend +{ +public: + std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) override; + void ProcessEvents() override; + void RunLoop() override; + void ExitLoop() override; + + void* StartTimer(int timeoutMilliseconds, std::function onTimer) override; + void StopTimer(void* timerID) override; + + Size GetScreenSize() override; + + bool IsSDL2() override { return true; } +}; diff --git a/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.cpp b/libraries/ZWidget/src/window/sdl2/sdl2_display_window.cpp similarity index 85% rename from libraries/ZWidget/src/window/sdl2/sdl2displaywindow.cpp rename to libraries/ZWidget/src/window/sdl2/sdl2_display_window.cpp index b863aad02..b5d2ffad5 100644 --- a/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.cpp +++ b/libraries/ZWidget/src/window/sdl2/sdl2_display_window.cpp @@ -1,6 +1,7 @@ -#include "sdl2displaywindow.h" +#include "sdl2_display_window.h" #include +#include Uint32 SDL2DisplayWindow::PaintEventNumber = 0xffffffff; bool SDL2DisplayWindow::ExitRunLoop; @@ -24,23 +25,43 @@ static void CheckInitSDL() static InitSDL initsdl; } -SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, SDL2DisplayWindow* owner) : WindowHost(windowHost) +SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, SDL2DisplayWindow* owner, RenderAPI renderAPI) : WindowHost(windowHost) { CheckInitSDL(); unsigned int flags = SDL_WINDOW_HIDDEN /*| SDL_WINDOW_ALLOW_HIGHDPI*/; + if (renderAPI == RenderAPI::Vulkan) + flags |= SDL_WINDOW_VULKAN; + else if (renderAPI == RenderAPI::OpenGL) + flags |= SDL_WINDOW_OPENGL; +#if defined(__APPLE__) + else if (renderAPI == RenderAPI::Metal) + flags |= SDL_WINDOW_METAL; +#endif if (popupWindow) flags |= SDL_WINDOW_BORDERLESS; - int result = SDL_CreateWindowAndRenderer(320, 200, flags, &WindowHandle, &RendererHandle); - if (result != 0) - throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError()); + + if (renderAPI == RenderAPI::Vulkan || renderAPI == RenderAPI::OpenGL || renderAPI == RenderAPI::Metal) + { + Handle.window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 320, 200, flags); + if (!Handle.window) + throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError()); + } + else + { + int result = SDL_CreateWindowAndRenderer(320, 200, flags, &Handle.window, &RendererHandle); + if (result != 0) + throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError()); + } - WindowList[SDL_GetWindowID(WindowHandle)] = this; + WindowList[SDL_GetWindowID(Handle.window)] = this; } SDL2DisplayWindow::~SDL2DisplayWindow() { - WindowList.erase(WindowList.find(SDL_GetWindowID(WindowHandle))); + UnlockCursor(); + + WindowList.erase(WindowList.find(SDL_GetWindowID(Handle.window))); if (BackBufferTexture) { @@ -48,15 +69,39 @@ SDL2DisplayWindow::~SDL2DisplayWindow() BackBufferTexture = nullptr; } - SDL_DestroyRenderer(RendererHandle); - SDL_DestroyWindow(WindowHandle); + if (RendererHandle) + SDL_DestroyRenderer(RendererHandle); + SDL_DestroyWindow(Handle.window); RendererHandle = nullptr; - WindowHandle = nullptr; + Handle.window = nullptr; +} + +std::vector SDL2DisplayWindow::GetVulkanInstanceExtensions() +{ + unsigned int extCount = 0; + SDL_Vulkan_GetInstanceExtensions(Handle.window, &extCount, nullptr); + std::vector extNames(extCount); + SDL_Vulkan_GetInstanceExtensions(Handle.window, &extCount, extNames.data()); + + std::vector result; + result.reserve(extNames.size()); + for (const char* ext : extNames) + result.emplace_back(ext); + return result; +} + +VkSurfaceKHR SDL2DisplayWindow::CreateVulkanSurface(VkInstance instance) +{ + VkSurfaceKHR surfaceHandle = {}; + SDL_Vulkan_CreateSurface(Handle.window, instance, &surfaceHandle); + if (surfaceHandle) + throw std::runtime_error("Could not create vulkan surface"); + return surfaceHandle; } void SDL2DisplayWindow::SetWindowTitle(const std::string& text) { - SDL_SetWindowTitle(WindowHandle, text.c_str()); + SDL_SetWindowTitle(Handle.window, text.c_str()); } void SDL2DisplayWindow::SetWindowFrame(const Rect& box) @@ -76,46 +121,54 @@ void SDL2DisplayWindow::SetClientFrame(const Rect& box) int w = (int)std::round(box.width * uiscale); int h = (int)std::round(box.height * uiscale); - SDL_SetWindowPosition(WindowHandle, x, y); - SDL_SetWindowSize(WindowHandle, w, h); + SDL_SetWindowPosition(Handle.window, x, y); + SDL_SetWindowSize(Handle.window, w, h); } void SDL2DisplayWindow::Show() { - SDL_ShowWindow(WindowHandle); + SDL_ShowWindow(Handle.window); } void SDL2DisplayWindow::ShowFullscreen() { - SDL_SetWindowFullscreen(WindowHandle, SDL_WINDOW_FULLSCREEN_DESKTOP); + SDL_ShowWindow(Handle.window); + SDL_SetWindowFullscreen(Handle.window, SDL_WINDOW_FULLSCREEN_DESKTOP); + isFullscreen = true; } void SDL2DisplayWindow::ShowMaximized() { - SDL_ShowWindow(WindowHandle); - SDL_MaximizeWindow(WindowHandle); + SDL_ShowWindow(Handle.window); + SDL_MaximizeWindow(Handle.window); } void SDL2DisplayWindow::ShowMinimized() { - SDL_ShowWindow(WindowHandle); - SDL_MinimizeWindow(WindowHandle); + SDL_ShowWindow(Handle.window); + SDL_MinimizeWindow(Handle.window); } void SDL2DisplayWindow::ShowNormal() { - SDL_ShowWindow(WindowHandle); - SDL_SetWindowFullscreen(WindowHandle, 0); + SDL_ShowWindow(Handle.window); + SDL_SetWindowFullscreen(Handle.window, 0); + isFullscreen = false; +} + +bool SDL2DisplayWindow::IsWindowFullscreen() +{ + return isFullscreen; } void SDL2DisplayWindow::Hide() { - SDL_HideWindow(WindowHandle); + SDL_HideWindow(Handle.window); } void SDL2DisplayWindow::Activate() { - SDL_RaiseWindow(WindowHandle); + SDL_RaiseWindow(Handle.window); } void SDL2DisplayWindow::ShowCursor(bool enable) @@ -125,14 +178,20 @@ void SDL2DisplayWindow::ShowCursor(bool enable) void SDL2DisplayWindow::LockCursor() { - SDL_SetWindowGrab(WindowHandle, SDL_TRUE); - SDL_ShowCursor(0); + if (!CursorLocked) + { + SDL_SetRelativeMouseMode(SDL_TRUE); + CursorLocked = true; + } } void SDL2DisplayWindow::UnlockCursor() { - SDL_SetWindowGrab(WindowHandle, SDL_FALSE); - SDL_ShowCursor(1); + if (CursorLocked) + { + SDL_SetRelativeMouseMode(SDL_FALSE); + CursorLocked = false; + } } void SDL2DisplayWindow::CaptureMouse() @@ -151,7 +210,7 @@ void SDL2DisplayWindow::Update() { SDL_Event event = {}; event.type = PaintEventNumber; - event.user.windowID = SDL_GetWindowID(WindowHandle); + event.user.windowID = SDL_GetWindowID(Handle.window); SDL_PushEvent(&event); } @@ -172,8 +231,8 @@ Rect SDL2DisplayWindow::GetWindowFrame() const int w = 0; int h = 0; double uiscale = GetDpiScale(); - SDL_GetWindowPosition(WindowHandle, &x, &y); - SDL_GetWindowSize(WindowHandle, &w, &h); + SDL_GetWindowPosition(Handle.window, &x, &y); + SDL_GetWindowSize(Handle.window, &w, &h); return Rect::xywh(x / uiscale, y / uiscale, w / uiscale, h / uiscale); } @@ -182,7 +241,7 @@ Point SDL2DisplayWindow::MapFromGlobal(const Point& pos) const int x = 0; int y = 0; double uiscale = GetDpiScale(); - SDL_GetWindowPosition(WindowHandle, &x, &y); + SDL_GetWindowPosition(Handle.window, &x, &y); return Point(pos.x - x / uiscale, pos.y - y / uiscale); } @@ -191,7 +250,7 @@ Point SDL2DisplayWindow::MapToGlobal(const Point& pos) const int x = 0; int y = 0; double uiscale = GetDpiScale(); - SDL_GetWindowPosition(WindowHandle, &x, &y); + SDL_GetWindowPosition(Handle.window, &x, &y); return Point(pos.x + x / uiscale, pos.y + y / uiscale); } @@ -200,7 +259,7 @@ Size SDL2DisplayWindow::GetClientSize() const int w = 0; int h = 0; double uiscale = GetDpiScale(); - SDL_GetWindowSize(WindowHandle, &w, &h); + SDL_GetWindowSize(Handle.window, &w, &h); return Size(w / uiscale, h / uiscale); } @@ -208,7 +267,10 @@ int SDL2DisplayWindow::GetPixelWidth() const { int w = 0; int h = 0; - int result = SDL_GetRendererOutputSize(RendererHandle, &w, &h); + if (RendererHandle) + SDL_GetRendererOutputSize(RendererHandle, &w, &h); + else + SDL_GL_GetDrawableSize(Handle.window, &w, &h); return w; } @@ -216,7 +278,10 @@ int SDL2DisplayWindow::GetPixelHeight() const { int w = 0; int h = 0; - int result = SDL_GetRendererOutputSize(RendererHandle, &w, &h); + if (RendererHandle) + SDL_GetRendererOutputSize(RendererHandle, &w, &h); + else + SDL_GL_GetDrawableSize(Handle.window, &w, &h); return h; } @@ -228,6 +293,9 @@ double SDL2DisplayWindow::GetDpiScale() const void SDL2DisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels) { + if (!RendererHandle) + return; + if (!BackBufferTexture || BackBufferWidth != width || BackBufferHeight != height) { if (BackBufferTexture) @@ -305,6 +373,7 @@ void SDL2DisplayWindow::RunLoop() { CheckInitSDL(); + ExitRunLoop = false; while (!ExitRunLoop) { SDL_Event event = {}; @@ -463,7 +532,14 @@ void SDL2DisplayWindow::OnMouseWheel(const SDL_MouseWheelEvent& event) void SDL2DisplayWindow::OnMouseMotion(const SDL_MouseMotionEvent& event) { - WindowHost->OnWindowMouseMove(GetMousePos(event)); + if (CursorLocked) + { + WindowHost->OnWindowRawMouseMove(event.xrel, event.yrel); + } + else + { + WindowHost->OnWindowMouseMove(GetMousePos(event)); + } } void SDL2DisplayWindow::OnPaintEvent() diff --git a/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.h b/libraries/ZWidget/src/window/sdl2/sdl2_display_window.h similarity index 88% rename from libraries/ZWidget/src/window/sdl2/sdl2displaywindow.h rename to libraries/ZWidget/src/window/sdl2/sdl2_display_window.h index 81494dc70..f891c6720 100644 --- a/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.h +++ b/libraries/ZWidget/src/window/sdl2/sdl2_display_window.h @@ -3,12 +3,13 @@ #include #include #include +#include #include class SDL2DisplayWindow : public DisplayWindow { public: - SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, SDL2DisplayWindow* owner); + SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, SDL2DisplayWindow* owner, RenderAPI renderAPI); ~SDL2DisplayWindow(); void SetWindowTitle(const std::string& text) override; @@ -19,6 +20,7 @@ public: void ShowMaximized() override; void ShowMinimized() override; void ShowNormal() override; + bool IsWindowFullscreen() override; void Hide() override; void Activate() override; void ShowCursor(bool enable) override; @@ -48,7 +50,10 @@ public: Point MapFromGlobal(const Point& pos) const override; Point MapToGlobal(const Point& pos) const override; - void* GetNativeHandle() override { return WindowHandle; } + void* GetNativeHandle() override { return &Handle; } + + std::vector GetVulkanInstanceExtensions() override; + VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override; static void DispatchEvent(const SDL_Event& event); static SDL2DisplayWindow* FindEventWindow(const SDL_Event& event); @@ -84,12 +89,15 @@ public: static void StopTimer(void* timerID); DisplayWindowHost* WindowHost = nullptr; - SDL_Window* WindowHandle = nullptr; + SDL2NativeHandle Handle; SDL_Renderer* RendererHandle = nullptr; SDL_Texture* BackBufferTexture = nullptr; int BackBufferWidth = 0; int BackBufferHeight = 0; + bool CursorLocked = false; + bool isFullscreen = false; + static bool ExitRunLoop; static Uint32 PaintEventNumber; static std::unordered_map WindowList; diff --git a/libraries/ZWidget/src/window/stub/stub_open_file_dialog.cpp b/libraries/ZWidget/src/window/stub/stub_open_file_dialog.cpp new file mode 100644 index 000000000..b3c61fe0b --- /dev/null +++ b/libraries/ZWidget/src/window/stub/stub_open_file_dialog.cpp @@ -0,0 +1,64 @@ + +#include "stub_open_file_dialog.h" + +StubOpenFileDialog::StubOpenFileDialog(DisplayWindow* owner) : owner(owner) +{ +} + +bool StubOpenFileDialog::Show() +{ + return false; +} + +std::string StubOpenFileDialog::Filename() const +{ + return !filenames.empty() ? filenames.front() : std::string(); +} + +std::vector StubOpenFileDialog::Filenames() const +{ + return filenames; +} + +void StubOpenFileDialog::SetMultiSelect(bool new_multi_select) +{ + multi_select = new_multi_select; +} + +void StubOpenFileDialog::SetFilename(const std::string& filename) +{ + initial_filename = filename; +} + +void StubOpenFileDialog::AddFilter(const std::string& filter_description, const std::string& filter_extension) +{ + Filter f; + f.description = filter_description; + f.extension = filter_extension; + filters.push_back(std::move(f)); +} + +void StubOpenFileDialog::ClearFilters() +{ + filters.clear(); +} + +void StubOpenFileDialog::SetFilterIndex(int filter_index) +{ + filterindex = filter_index; +} + +void StubOpenFileDialog::SetInitialDirectory(const std::string& path) +{ + initial_directory = path; +} + +void StubOpenFileDialog::SetTitle(const std::string& newtitle) +{ + title = newtitle; +} + +void StubOpenFileDialog::SetDefaultExtension(const std::string& extension) +{ + defaultext = extension; +} diff --git a/libraries/ZWidget/src/window/stub/stub_open_file_dialog.h b/libraries/ZWidget/src/window/stub/stub_open_file_dialog.h new file mode 100644 index 000000000..9ffcead2e --- /dev/null +++ b/libraries/ZWidget/src/window/stub/stub_open_file_dialog.h @@ -0,0 +1,41 @@ +#pragma once + +#include "systemdialogs/open_file_dialog.h" + +class DisplayWindow; + +class StubOpenFileDialog : public OpenFileDialog +{ +public: + StubOpenFileDialog(DisplayWindow* owner); + + bool Show() override; + std::string Filename() const override; + std::vector Filenames() const override; + void SetMultiSelect(bool new_multi_select) override; + void SetFilename(const std::string& filename) override; + void AddFilter(const std::string& filter_description, const std::string& filter_extension) override; + void ClearFilters() override; + void SetFilterIndex(int filter_index) override; + void SetInitialDirectory(const std::string& path) override; + void SetTitle(const std::string& newtitle) override; + void SetDefaultExtension(const std::string& extension) override; + +private: + DisplayWindow* owner = nullptr; + + std::string initial_directory; + std::string initial_filename; + std::string title; + std::vector filenames; + bool multi_select = false; + + struct Filter + { + std::string description; + std::string extension; + }; + std::vector filters; + int filterindex = 0; + std::string defaultext; +}; diff --git a/libraries/ZWidget/src/window/stub/stub_open_folder_dialog.cpp b/libraries/ZWidget/src/window/stub/stub_open_folder_dialog.cpp new file mode 100644 index 000000000..83ca24469 --- /dev/null +++ b/libraries/ZWidget/src/window/stub/stub_open_folder_dialog.cpp @@ -0,0 +1,26 @@ + +#include "stub_open_folder_dialog.h" + +StubOpenFolderDialog::StubOpenFolderDialog(DisplayWindow* owner) : owner(owner) +{ +} + +bool StubOpenFolderDialog::Show() +{ + return false; +} + +std::string StubOpenFolderDialog::SelectedPath() const +{ + return selected_path; +} + +void StubOpenFolderDialog::SetInitialDirectory(const std::string& path) +{ + initial_directory = path; +} + +void StubOpenFolderDialog::SetTitle(const std::string& newtitle) +{ + title = newtitle; +} diff --git a/libraries/ZWidget/src/window/stub/stub_open_folder_dialog.h b/libraries/ZWidget/src/window/stub/stub_open_folder_dialog.h new file mode 100644 index 000000000..4d18708be --- /dev/null +++ b/libraries/ZWidget/src/window/stub/stub_open_folder_dialog.h @@ -0,0 +1,23 @@ +#pragma once + +#include "systemdialogs/open_folder_dialog.h" + +class DisplayWindow; + +class StubOpenFolderDialog : public OpenFolderDialog +{ +public: + StubOpenFolderDialog(DisplayWindow* owner); + + bool Show() override; + std::string SelectedPath() const override; + void SetInitialDirectory(const std::string& path) override; + void SetTitle(const std::string& newtitle) override; + +private: + DisplayWindow* owner = nullptr; + + std::string selected_path; + std::string initial_directory; + std::string title; +}; diff --git a/libraries/ZWidget/src/window/stub/stub_save_file_dialog.cpp b/libraries/ZWidget/src/window/stub/stub_save_file_dialog.cpp new file mode 100644 index 000000000..64e96a2ff --- /dev/null +++ b/libraries/ZWidget/src/window/stub/stub_save_file_dialog.cpp @@ -0,0 +1,54 @@ + +#include "stub_save_file_dialog.h" + +StubSaveFileDialog::StubSaveFileDialog(DisplayWindow* owner) : owner(owner) +{ +} + +bool StubSaveFileDialog::Show() +{ + return false; +} + +std::string StubSaveFileDialog::Filename() const +{ + return filename; +} + +void StubSaveFileDialog::SetFilename(const std::string& filename) +{ + initial_filename = filename; +} + +void StubSaveFileDialog::AddFilter(const std::string& filter_description, const std::string& filter_extension) +{ + Filter f; + f.description = filter_description; + f.extension = filter_extension; + filters.push_back(std::move(f)); +} + +void StubSaveFileDialog::ClearFilters() +{ + filters.clear(); +} + +void StubSaveFileDialog::SetFilterIndex(int filter_index) +{ + filterindex = filter_index; +} + +void StubSaveFileDialog::SetInitialDirectory(const std::string& path) +{ + initial_directory = path; +} + +void StubSaveFileDialog::SetTitle(const std::string& newtitle) +{ + title = newtitle; +} + +void StubSaveFileDialog::SetDefaultExtension(const std::string& extension) +{ + defaultext = extension; +} diff --git a/libraries/ZWidget/src/window/stub/stub_save_file_dialog.h b/libraries/ZWidget/src/window/stub/stub_save_file_dialog.h new file mode 100644 index 000000000..c57ce2e5e --- /dev/null +++ b/libraries/ZWidget/src/window/stub/stub_save_file_dialog.h @@ -0,0 +1,39 @@ +#pragma once + +#include "systemdialogs/save_file_dialog.h" + +class DisplayWindow; + +class StubSaveFileDialog : public SaveFileDialog +{ +public: + StubSaveFileDialog(DisplayWindow* owner); + + bool Show() override; + std::string Filename() const override; + void SetFilename(const std::string& filename) override; + void AddFilter(const std::string& filter_description, const std::string& filter_extension) override; + void ClearFilters() override; + void SetFilterIndex(int filter_index) override; + void SetInitialDirectory(const std::string& path) override; + void SetTitle(const std::string& newtitle) override; + void SetDefaultExtension(const std::string& extension) override; + +private: + DisplayWindow* owner = nullptr; + + std::string filename; + std::string initial_directory; + std::string initial_filename; + std::string title; + std::vector filenames; + + struct Filter + { + std::string description; + std::string extension; + }; + std::vector filters; + int filterindex = 0; + std::string defaultext; +}; diff --git a/libraries/ZWidget/src/window/wayland/wayland_display_backend.cpp b/libraries/ZWidget/src/window/wayland/wayland_display_backend.cpp new file mode 100644 index 000000000..ffb394853 --- /dev/null +++ b/libraries/ZWidget/src/window/wayland/wayland_display_backend.cpp @@ -0,0 +1,1150 @@ +#include "wayland_display_backend.h" +#include "wayland_display_window.h" + +#ifdef USE_DBUS +#include "window/dbus/dbus_open_file_dialog.h" +#include "window/dbus/dbus_save_file_dialog.h" +#include "window/dbus/dbus_open_folder_dialog.h" +#endif + +WaylandDisplayBackend::WaylandDisplayBackend() +{ + if (!s_waylandDisplay) + throw std::runtime_error("Wayland Display initialization failed!"); + + s_waylandRegistry = s_waylandDisplay.get_registry(); + + s_waylandRegistry.on_global() = [&](uint32_t name, std::string interface, uint32_t version) { + if (interface == wayland::compositor_t::interface_name) + s_waylandRegistry.bind(name, m_waylandCompositor, 3); + if (interface == wayland::shm_t::interface_name) + s_waylandRegistry.bind(name, m_waylandSHM, version); + if (interface == wayland::output_t::interface_name) + s_waylandRegistry.bind(name, m_waylandOutput, version); + if (interface == wayland::seat_t::interface_name) + s_waylandRegistry.bind(name, m_waylandSeat, 8); + if (interface == wayland::data_device_manager_t::interface_name) + s_waylandRegistry.bind(name, m_DataDeviceManager, 3); + if (interface == wayland::xdg_wm_base_t::interface_name) + s_waylandRegistry.bind(name, m_XDGWMBase, 4); + if (interface == wayland::zxdg_output_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_XDGOutputManager, version); + if (interface == wayland::zxdg_exporter_v2_t::interface_name) + s_waylandRegistry.bind(name, m_XDGExporter, 1); + if (interface == wayland::zwp_pointer_constraints_v1_t::interface_name) + s_waylandRegistry.bind(name, m_PointerConstraints, 1); + if (interface == wayland::xdg_activation_v1_t::interface_name) + s_waylandRegistry.bind(name, m_XDGActivation, 1); + if (interface == wayland::zxdg_decoration_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_XDGDecorationManager, 1); + if (interface == wayland::fractional_scale_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_FractionalScaleManager, 1); + if (interface == wayland::zwp_relative_pointer_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_RelativePointerManager, 1); + }; + + s_waylandDisplay.roundtrip(); + + if (!m_XDGWMBase) + throw std::runtime_error("WaylandDisplayBackend: XDG-Shell is required!"); + + if (!m_XDGOutputManager) + throw std::runtime_error("WaylandDisplayBackend: xdg-output-manager-v1 is required!"); + + if (!m_XDGExporter) + throw std::runtime_error("WaylandDisplayBackend: xdg-foreign-unstable-v2 is required!"); + + if (!m_PointerConstraints) + throw std::runtime_error("WaylandDisplayBackend: pointer-constrains-unstable-v1 is required!"); + + if (!m_RelativePointerManager) + throw std::runtime_error("WaylandDisplayBackend: relative-pointer-unstable-v1 is required!"); + + m_waylandOutput.on_mode() = [this] (wayland::output_mode flags, int32_t width, int32_t height, int32_t refresh) { + s_ScreenSize = Size(width, height); + }; + + m_XDGWMBase.on_ping() = [this] (uint32_t serial) { + m_XDGWMBase.pong(serial); + }; + + m_waylandSeat.on_capabilities() = [this] (uint32_t capabilities) { + hasKeyboard = capabilities & wayland::seat_capability::keyboard; + hasPointer = capabilities & wayland::seat_capability::pointer; + }; + + m_XDGOutput = m_XDGOutputManager.get_xdg_output(m_waylandOutput); + + m_XDGOutput.on_logical_position() = [this] (int32_t x, int32_t y) { + //m_WindowGlobalPos = Point(x, y); + }; + + m_XDGOutput.on_logical_size() = [this] (int32_t width, int32_t height) { + s_ScreenSize = Size(width, height); + }; + + if (!m_FractionalScaleManager) + { + m_waylandOutput.on_scale() = [this] (int32_t scale) { + for (WaylandDisplayWindow* w : s_Windows) + { + w->m_ScaleFactor = scale; + w->m_NeedsUpdate = true; + w->windowHost->OnWindowDpiScaleChanged(); + } + }; + } + + s_waylandDisplay.roundtrip(); + + // To do: this shouldn't really be fatal. The user might have forgotten to plug in their keyboard or mouse. + if (!hasKeyboard) + throw std::runtime_error("No keyboard detected!"); + if (!hasPointer) + throw std::runtime_error("No pointer device detected!"); + + m_waylandKeyboard = m_waylandSeat.get_keyboard(); + m_waylandPointer = m_waylandSeat.get_pointer(); + m_RelativePointer = m_RelativePointerManager.get_relative_pointer(m_waylandPointer); + + ConnectDeviceEvents(); + + m_cursorSurface = m_waylandCompositor.create_surface(); + SetCursor(StandardCursor::arrow); + +/* + m_DataDevice = m_DataDeviceManager.get_data_device(m_waylandSeat); + + m_DataSource = m_DataDeviceManager.create_data_source(); + m_DataSource.offer("text/plain"); + + m_DataSource.on_send() = [&] (std::string mime_type, int fd) { + if (mime_type != "text/plain") + return; + + if (!m_ClipboardContents.empty()) + write(fd, m_ClipboardContents.data(), m_ClipboardContents.size()); + close(fd); + }; + + m_DataDevice.on_selection() = [&] (wayland::data_offer_t dataOffer) { + m_ClipboardContents.clear(); + + if (!dataOffer) + // Clipboard is empty + return; + + int fds[2]; + pipe(fds); + + dataOffer.receive("text/plain", fds[1]); + close(fds[1]); + + m_waylandDisplay.roundtrip(); + + while (true) + { + char buf[1024]; + + ssize_t n = read(fds[0], buf, sizeof(buf)); + + if (n <= 0) + break; + + m_ClipboardContents += buf; + } + + close(fds[0]); + + dataOffer.proxy_release(); + }; +*/ +} + +WaylandDisplayBackend::~WaylandDisplayBackend() +{ + if (m_KeymapContext) + xkb_context_unref(m_KeymapContext); + + if (m_KeyboardState) + xkb_state_unref(m_KeyboardState); +} + +void WaylandDisplayBackend::ConnectDeviceEvents() +{ + m_KeymapContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + m_waylandKeyboard.on_keymap() = [this] (wayland::keyboard_keymap_format format, int fd, uint32_t size) { + if (format != wayland::keyboard_keymap_format::xkb_v1) + throw std::runtime_error("WaylandDisplayBackend: Unrecognized keymap format!"); + + char* mapSHM = (char*)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (mapSHM == MAP_FAILED) + throw std::runtime_error("WaylandDisplayBackend: Keymap shared memory allocation failed!"); + + if (m_Keymap) + xkb_keymap_unref(m_Keymap); + + m_Keymap = xkb_keymap_new_from_string(m_KeymapContext, mapSHM, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(mapSHM, size); + close(fd); + + if (m_KeyboardState) + xkb_state_unref(m_KeyboardState); + + m_KeyboardState = xkb_state_new(m_Keymap); + }; + + m_waylandKeyboard.on_modifiers() = [this] (uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { + xkb_state_update_mask(m_KeyboardState, mods_depressed, mods_latched, mods_locked, 0, 0, group); + }; + + m_waylandKeyboard.on_enter() = [this] (uint32_t serial, wayland::surface_t surfaceEntered, wayland::array_t keys) { + std::vector keysVec = keys; + + m_KeyboardSerial = serial; + + // Find the window to focus on by checking the surface window owns. + if (!m_FocusWindow || m_FocusWindow->GetWindowSurface() != surfaceEntered) + { + for (auto win: s_Windows) + { + if (win->GetWindowSurface() == surfaceEntered) + m_FocusWindow = win; + } + } + + + for (auto key: keysVec) + { + // keys parameter represents the keys pressed when entering the surface + // key variable is Linux evdev scancode, to translate it to XKB keycode, we must add 8 + xkb_keysym_t sym = xkb_state_key_get_one_sym(m_KeyboardState, key + 8); + OnKeyboardKeyEvent(sym, wayland::keyboard_key_state::pressed); + + // Also cause a Char event + char buf[128]; + xkb_state_key_get_utf8(m_KeyboardState, key + 8, buf, sizeof(buf)); + + OnKeyboardCharEvent(buf, wayland::keyboard_key_state::pressed); + } + }; + + m_waylandKeyboard.on_key() = [this] (uint32_t serial, uint32_t time, uint32_t key, wayland::keyboard_key_state state) { + // key is Linux evdev scancode, to translate it to XKB keycode, we must add 8 + xkb_keysym_t sym = xkb_state_key_get_one_sym(m_KeyboardState, key + 8); + OnKeyboardKeyEvent(sym, state); + + // Also cause a Char event + char buf[128]; + xkb_state_key_get_utf8(m_KeyboardState, key + 8, buf, sizeof(buf)); + + OnKeyboardCharEvent(buf, state); + + //m_DataDevice.set_selection(m_DataSource, m_KeyboardSerial); + }; + + m_waylandPointer.on_enter() = [this](uint32_t serial, wayland::surface_t surfaceEntered, double surfaceX, double surfaceY) { + // Find the window to focus on by checking the surface window owns. + if (!m_MouseFocusWindow || m_MouseFocusWindow->GetWindowSurface() != surfaceEntered) + { + for (auto win: s_Windows) + { + if (win->GetWindowSurface() == surfaceEntered) + { + m_MouseFocusWindow = win; + } + } + } + + currentPointerEvent.event_mask |= POINTER_EVENT_ENTER; + currentPointerEvent.serial = serial; + currentPointerEvent.surfaceX = surfaceX; + currentPointerEvent.surfaceY = surfaceY; + }; + + m_waylandPointer.on_leave() = [this](uint32_t serial, wayland::surface_t surfaceLeft) { + currentPointerEvent.event_mask |= POINTER_EVENT_LEAVE; + currentPointerEvent.serial = serial; + }; + + m_waylandPointer.on_motion() = [this] (uint32_t serial, double surfaceX, double surfaceY) { + currentPointerEvent.event_mask |= POINTER_EVENT_MOTION; + currentPointerEvent.serial = serial; + currentPointerEvent.surfaceX = surfaceX; + currentPointerEvent.surfaceY = surfaceY; + }; + + m_waylandPointer.on_button() = [this] (uint32_t serial, uint32_t time, uint32_t button, wayland::pointer_button_state state) { + currentPointerEvent.event_mask |= POINTER_EVENT_BUTTON; + currentPointerEvent.serial = serial; + currentPointerEvent.time = time; + currentPointerEvent.button = button; + currentPointerEvent.state = state; + }; + + m_waylandPointer.on_axis() = [this] (uint32_t serial, wayland::pointer_axis axis, double value) { + currentPointerEvent.event_mask |= POINTER_EVENT_AXIS; + currentPointerEvent.serial = serial; + currentPointerEvent.axes[uint32_t(axis)].value = value; + currentPointerEvent.axes[uint32_t(axis)].valid = true; + }; + + // High resolution scroll event + m_waylandPointer.on_axis_value120() = [this] (wayland::pointer_axis axis, int32_t value) { + currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_120; + currentPointerEvent.axes[uint32_t(axis)].valid = true; + currentPointerEvent.axes[uint32_t(axis)].value120 = value; + }; + + m_waylandPointer.on_axis_source() = [this] (wayland::pointer_axis_source axis) { + currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_SOURCE; + currentPointerEvent.axis_source = axis; + }; + + m_waylandPointer.on_axis_stop() = [this] (uint32_t time, wayland::pointer_axis axis) { + currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_STOP; + currentPointerEvent.time = time; + currentPointerEvent.axes[uint32_t(axis)].valid = true; + }; + + m_RelativePointer.on_relative_motion() = [this] (uint32_t utime_hi, uint32_t utime_lo, + double dx, double dy, + double dx_unaccel, double dy_unaccel) { + currentPointerEvent.event_mask |= POINTER_EVENT_RELATIVE_MOTION; + currentPointerEvent.time = utime_lo; + currentPointerEvent.dx = dx; + currentPointerEvent.dy = dy; + }; + + m_waylandPointer.on_frame() = [this] () { + if (currentPointerEvent.event_mask & POINTER_EVENT_ENTER) + OnMouseEnterEvent(currentPointerEvent.serial); + + if (currentPointerEvent.event_mask & POINTER_EVENT_LEAVE) + OnMouseLeaveEvent(); + + if (currentPointerEvent.event_mask & POINTER_EVENT_MOTION) + { + OnMouseMoveEvent(Point(currentPointerEvent.surfaceX, currentPointerEvent.surfaceY)); + } + + if (currentPointerEvent.event_mask & POINTER_EVENT_RELATIVE_MOTION) + { + if (hasMouseLock) + OnMouseMoveRawEvent(int(currentPointerEvent.dx), int(currentPointerEvent.dy)); + } + + if (currentPointerEvent.event_mask & POINTER_EVENT_BUTTON) + { + InputKey ikey = LinuxInputEventCodeToInputKey(currentPointerEvent.button); + if (currentPointerEvent.state == wayland::pointer_button_state::pressed) + OnMousePressEvent(ikey); + else // released + OnMouseReleaseEvent(ikey); + } + + uint32_t axisevents = POINTER_EVENT_AXIS | POINTER_EVENT_AXIS_120 | POINTER_EVENT_AXIS_SOURCE | POINTER_EVENT_AXIS_STOP; + + if (currentPointerEvent.event_mask & axisevents) + { + for (size_t idx = 0 ; idx < 2 ; idx++) + { + if (!currentPointerEvent.axes[idx].valid) + continue; + + if (currentPointerEvent.event_mask & POINTER_EVENT_AXIS_120) + { + if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value > 0) + OnMouseWheelEvent(InputKey::MouseWheelDown); + if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value < 0) + OnMouseWheelEvent(InputKey::MouseWheelUp); + } + else if (currentPointerEvent.event_mask & POINTER_EVENT_AXIS) + { + if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value120 > 0) + OnMouseWheelEvent(InputKey::MouseWheelDown); + if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value120 < 0) + OnMouseWheelEvent(InputKey::MouseWheelUp); + } + } + } + + // Reset everything once all the events are processed + currentPointerEvent = {0}; + }; + + m_keyboardDelayTimer = ZTimer(); + m_keyboardRepeatTimer = ZTimer(); + + m_waylandKeyboard.on_repeat_info() = [this] (int32_t rate, int32_t delay) { + // rate is characters per second, delay is in milliseconds + m_keyboardDelayTimer.SetDuration(ZTimer::Duration(delay)); + m_keyboardRepeatTimer.SetDuration(ZTimer::Duration(1000.0 / rate)); + }; + + m_keyboardDelayTimer.SetCallback([this] () { OnKeyboardDelayEnd(); }); + m_keyboardRepeatTimer.SetCallback([this] () { OnKeyboardRepeat(); }); + + m_keyboardRepeatTimer.SetRepeating(true); + + m_previousTime = ZTimer::Clock::now(); + m_currentTime = ZTimer::Clock::now(); +} + +void WaylandDisplayBackend::OnKeyboardKeyEvent(xkb_keysym_t xkbKeySym, wayland::keyboard_key_state state) +{ + InputKey inputKey = XKBKeySymToInputKey(xkbKeySym); + + if (state == wayland::keyboard_key_state::pressed) + { + inputKeyStates[inputKey] = true; + m_FocusWindow->windowHost->OnWindowKeyDown(inputKey); + if (inputKey != previousKey) + { + previousKey = inputKey; + m_keyboardDelayTimer.Stop(); + m_keyboardRepeatTimer.Stop(); + } + m_keyboardDelayTimer.Start(); + } + if (state == wayland::keyboard_key_state::released) + { + inputKeyStates[inputKey] = false; + if (m_FocusWindow) + m_FocusWindow->windowHost->OnWindowKeyUp(inputKey); + m_keyboardDelayTimer.Stop(); + m_keyboardRepeatTimer.Stop(); + } + +} + +void WaylandDisplayBackend::OnKeyboardCharEvent(const char* ch, wayland::keyboard_key_state state) +{ + if (state == wayland::keyboard_key_state::pressed) + { + previousChars = std::string(ch); + m_FocusWindow->windowHost->OnWindowKeyChar(previousChars); + } +} + +void WaylandDisplayBackend::OnKeyboardDelayEnd() +{ + if (inputKeyStates[previousKey]) + m_keyboardRepeatTimer.Start(); +} + +void WaylandDisplayBackend::OnKeyboardRepeat() +{ + if (inputKeyStates[previousKey]) + { + m_FocusWindow->windowHost->OnWindowKeyDown(previousKey); + m_FocusWindow->windowHost->OnWindowKeyChar(previousChars); + } +} + +void WaylandDisplayBackend::OnMouseEnterEvent(uint32_t serial) +{ + m_cursorSurface.attach(!hasMouseLock ? m_cursorBuffer : nullptr, 0, 0); + if (!hasMouseLock) + m_cursorSurface.damage(0, 0, m_cursorImage.width(), m_cursorImage.height()); + m_cursorSurface.commit(); + m_waylandPointer.set_cursor(serial, m_cursorSurface, 0, 0); +} + +void WaylandDisplayBackend::OnMouseLeaveEvent() +{ + if (m_MouseFocusWindow) + { + m_MouseFocusWindow->windowHost->OnWindowMouseLeave(); + //m_MouseFocusWindow = nullptr; // Borks up the menus + } +} + +void WaylandDisplayBackend::OnMousePressEvent(InputKey button) +{ + if (m_MouseFocusWindow) + m_MouseFocusWindow->windowHost->OnWindowMouseDown(m_MouseFocusWindow->m_SurfaceMousePos, button); +} + +void WaylandDisplayBackend::OnMouseReleaseEvent(InputKey button) +{ + if (m_MouseFocusWindow) + m_MouseFocusWindow->windowHost->OnWindowMouseUp(m_MouseFocusWindow->m_SurfaceMousePos, button); +} + +void WaylandDisplayBackend::OnMouseMoveEvent(Point surfacePos) +{ + if (m_MouseFocusWindow) + { + m_MouseFocusWindow->m_SurfaceMousePos = surfacePos / m_MouseFocusWindow->m_ScaleFactor; + m_MouseFocusWindow->windowHost->OnWindowMouseMove(m_MouseFocusWindow->m_SurfaceMousePos); + } +} + +void WaylandDisplayBackend::OnMouseMoveRawEvent(int dx, int dy) +{ + if (m_MouseFocusWindow) + m_MouseFocusWindow->windowHost->OnWindowRawMouseMove(dx, dy); +} + +void WaylandDisplayBackend::OnMouseWheelEvent(InputKey button) +{ + if (m_MouseFocusWindow) + m_MouseFocusWindow->windowHost->OnWindowMouseWheel(m_MouseFocusWindow->m_SurfaceMousePos, button); +} + +void WaylandDisplayBackend::SetCursor(StandardCursor cursor) +{ + std::string cursorName = GetWaylandCursorName(cursor); + + // Perhaps the cursor size can be inferred from the user prefs as well? + wayland::cursor_theme_t cursorTheme = wayland::cursor_theme_t("default", 16, m_waylandSHM); + wayland::cursor_t obtainedCursor = cursorTheme.get_cursor(cursorName); + m_cursorImage = obtainedCursor.image(0); + m_cursorBuffer = m_cursorImage.get_buffer(); +} + +void WaylandDisplayBackend::ShowCursor(bool enable) +{ + m_cursorSurface.attach(enable ? m_cursorBuffer : nullptr, 0, 0); + m_cursorSurface.commit(); +} + +std::unique_ptr WaylandDisplayBackend::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) +{ + return std::make_unique(this, windowHost, popupWindow, static_cast(owner), renderAPI); +} + +void WaylandDisplayBackend::OnWindowCreated(WaylandDisplayWindow* window) +{ + s_Windows.push_back(window); + if (!window->m_PopupWindow) + { + m_FocusWindow = window; + m_MouseFocusWindow = window; + } + +} + +void WaylandDisplayBackend::OnWindowDestroyed(WaylandDisplayWindow* window) +{ + auto it = std::find(s_Windows.begin(), s_Windows.end(), window); + if (it != s_Windows.end()) + s_Windows.erase(it); + + if (m_FocusWindow == window) + m_FocusWindow = nullptr; + + if (m_MouseFocusWindow == window) + m_MouseFocusWindow = nullptr; +} + +void WaylandDisplayBackend::ProcessEvents() +{ + while (wl_display_prepare_read(s_waylandDisplay)) + s_waylandDisplay.dispatch_pending(); + + while (wl_display_flush(s_waylandDisplay) < 0 && errno == EAGAIN) + poll_single(s_waylandDisplay.get_fd(), POLLOUT, -1); + + if (poll_single(s_waylandDisplay.get_fd(), POLLIN, 0) & POLLIN) + { + wl_display_read_events(s_waylandDisplay); + s_waylandDisplay.dispatch_pending(); + } + else + wl_display_cancel_read(s_waylandDisplay); + + if (s_waylandDisplay.get_error()) + throw std::runtime_error("Wayland Protocol Error"); +} + +void WaylandDisplayBackend::RunLoop() +{ + exitRunLoop = false; + + while (!exitRunLoop) + { + CheckNeedsUpdate(); + UpdateTimers(); + + ProcessEvents(); + } +} + +void WaylandDisplayBackend::UpdateTimers() +{ + m_currentTime = ZTimer::Clock::now(); + + m_keyboardDelayTimer.Update(m_currentTime - m_previousTime); + m_keyboardRepeatTimer.Update(m_currentTime - m_previousTime); + + m_previousTime = m_currentTime; +} + +void WaylandDisplayBackend::CheckNeedsUpdate() +{ + for (auto window: s_Windows) + { + if (window->m_NeedsUpdate) + { + window->m_NeedsUpdate = false; + window->windowHost->OnWindowPaint(); + } + } +} + +void WaylandDisplayBackend::ExitLoop() +{ + exitRunLoop = true; +} + +Size WaylandDisplayBackend::GetScreenSize() +{ + return s_ScreenSize; +} + +void* WaylandDisplayBackend::StartTimer(int timeoutMilliseconds, std::function onTimer) +{ + return nullptr; +} + +void WaylandDisplayBackend::StopTimer(void* timerID) +{ +} + +#ifdef USE_DBUS +std::unique_ptr WaylandDisplayBackend::CreateOpenFileDialog(DisplayWindow* owner) +{ + std::string ownerHandle; + if (owner) + ownerHandle = "wayland:" + static_cast(owner)->GetWaylandWindowID(); + return std::make_unique(ownerHandle); +} + +std::unique_ptr WaylandDisplayBackend::CreateSaveFileDialog(DisplayWindow* owner) +{ + std::string ownerHandle; + if (owner) + ownerHandle = "wayland:" + static_cast(owner)->GetWaylandWindowID(); + return std::make_unique(ownerHandle); +} + +std::unique_ptr WaylandDisplayBackend::CreateOpenFolderDialog(DisplayWindow* owner) +{ + std::string ownerHandle; + if (owner) + ownerHandle = "wayland:" + static_cast(owner)->GetWaylandWindowID(); + return std::make_unique(ownerHandle); +} +#endif + +bool WaylandDisplayBackend::GetKeyState(InputKey key) +{ + auto it = inputKeyStates.find(key); + + // if the key isn't "registered", then it is not pressed. + if (it == inputKeyStates.end()) + return false; + + return it->second; +} + +InputKey WaylandDisplayBackend::XKBKeySymToInputKey(xkb_keysym_t keySym) +{ + switch (keySym) + { + case XKB_KEY_Escape: + return InputKey::Escape; + case XKB_KEY_1: + return InputKey::_1; + case XKB_KEY_2: + return InputKey::_2; + case XKB_KEY_3: + return InputKey::_3; + case XKB_KEY_4: + return InputKey::_4; + case XKB_KEY_5: + return InputKey::_5; + case XKB_KEY_6: + return InputKey::_6; + case XKB_KEY_7: + return InputKey::_7; + case XKB_KEY_8: + return InputKey::_8; + case XKB_KEY_9: + return InputKey::_9; + case XKB_KEY_0: + return InputKey::_0; + case XKB_KEY_KP_1: + return InputKey::NumPad1; + case XKB_KEY_KP_2: + return InputKey::NumPad2; + case XKB_KEY_KP_3: + return InputKey::NumPad3; + case XKB_KEY_KP_4: + return InputKey::NumPad4; + case XKB_KEY_KP_5: + return InputKey::NumPad5; + case XKB_KEY_KP_6: + return InputKey::NumPad6; + case XKB_KEY_KP_7: + return InputKey::NumPad7; + case XKB_KEY_KP_8: + return InputKey::NumPad8; + case XKB_KEY_KP_9: + return InputKey::NumPad9; + case XKB_KEY_KP_0: + return InputKey::NumPad0; + case XKB_KEY_F1: + return InputKey::F1; + case XKB_KEY_F2: + return InputKey::F2; + case XKB_KEY_F3: + return InputKey::F3; + case XKB_KEY_F4: + return InputKey::F4; + case XKB_KEY_F5: + return InputKey::F5; + case XKB_KEY_F6: + return InputKey::F6; + case XKB_KEY_F7: + return InputKey::F7; + case XKB_KEY_F8: + return InputKey::F8; + case XKB_KEY_F9: + return InputKey::F9; + case XKB_KEY_F10: + return InputKey::F10; + case XKB_KEY_F11: + return InputKey::F11; + case XKB_KEY_F12: + return InputKey::F12; + case XKB_KEY_F13: + return InputKey::F13; + case XKB_KEY_F14: + return InputKey::F14; + case XKB_KEY_F15: + return InputKey::F15; + case XKB_KEY_F16: + return InputKey::F16; + case XKB_KEY_F17: + return InputKey::F17; + case XKB_KEY_F18: + return InputKey::F18; + case XKB_KEY_F19: + return InputKey::F19; + case XKB_KEY_F20: + return InputKey::F20; + case XKB_KEY_F21: + return InputKey::F21; + case XKB_KEY_F22: + return InputKey::F22; + case XKB_KEY_F23: + return InputKey::F23; + case XKB_KEY_F24: + return InputKey::F24; + case XKB_KEY_minus: + case XKB_KEY_KP_Subtract: + return InputKey::Minus; + case XKB_KEY_equal: + return InputKey::Equals; + case XKB_KEY_BackSpace: + return InputKey::Backspace; + case XKB_KEY_backslash: + return InputKey::Backslash; + case XKB_KEY_Tab: + return InputKey::Tab; + case XKB_KEY_braceleft: + return InputKey::LeftBracket; + case XKB_KEY_braceright: + return InputKey::RightBracket; + case XKB_KEY_Control_L: + case XKB_KEY_Control_R: + return InputKey::Ctrl; + case XKB_KEY_Alt_L: + case XKB_KEY_Alt_R: + return InputKey::Alt; + case XKB_KEY_Delete: + return InputKey::Delete; + case XKB_KEY_semicolon: + return InputKey::Semicolon; + case XKB_KEY_comma: + return InputKey::Comma; + case XKB_KEY_period: + return InputKey::Period; + case XKB_KEY_Num_Lock: + return InputKey::NumLock; + case XKB_KEY_Caps_Lock: + return InputKey::CapsLock; + case XKB_KEY_Scroll_Lock: + return InputKey::ScrollLock; + case XKB_KEY_Shift_L: + return InputKey::LShift; + case XKB_KEY_Shift_R: + return InputKey::RShift; + case XKB_KEY_grave: + return InputKey::Tilde; + case XKB_KEY_apostrophe: + return InputKey::SingleQuote; + case XKB_KEY_KP_Enter: + case XKB_KEY_Return: + return InputKey::Enter; + case XKB_KEY_space: + return InputKey::Space; + + case XKB_KEY_Up: + return InputKey::Up; + case XKB_KEY_Down: + return InputKey::Down; + case XKB_KEY_Left: + return InputKey::Left; + case XKB_KEY_Right: + return InputKey::Right; + + case XKB_KEY_A: + case XKB_KEY_a: + return InputKey::A; + case XKB_KEY_B: + case XKB_KEY_b: + return InputKey::B; + case XKB_KEY_C: + case XKB_KEY_c: + return InputKey::C; + case XKB_KEY_D: + case XKB_KEY_d: + return InputKey::D; + case XKB_KEY_E: + case XKB_KEY_e: + return InputKey::E; + case XKB_KEY_F: + case XKB_KEY_f: + return InputKey::F; + case XKB_KEY_G: + case XKB_KEY_g: + return InputKey::G; + case XKB_KEY_H: + case XKB_KEY_h: + return InputKey::H; + case XKB_KEY_I: + case XKB_KEY_i: + return InputKey::I; + case XKB_KEY_J: + case XKB_KEY_j: + return InputKey::J; + case XKB_KEY_K: + case XKB_KEY_k: + return InputKey::K; + case XKB_KEY_L: + case XKB_KEY_l: + return InputKey::L; + case XKB_KEY_M: + case XKB_KEY_m: + return InputKey::M; + case XKB_KEY_N: + case XKB_KEY_n: + return InputKey::N; + case XKB_KEY_O: + case XKB_KEY_o: + return InputKey::O; + case XKB_KEY_P: + case XKB_KEY_p: + return InputKey::P; + case XKB_KEY_Q: + case XKB_KEY_q: + return InputKey::Q; + case XKB_KEY_R: + case XKB_KEY_r: + return InputKey::R; + case XKB_KEY_S: + case XKB_KEY_s: + return InputKey::S; + case XKB_KEY_T: + case XKB_KEY_t: + return InputKey::T; + case XKB_KEY_U: + case XKB_KEY_u: + return InputKey::U; + case XKB_KEY_V: + case XKB_KEY_v: + return InputKey::V; + case XKB_KEY_W: + case XKB_KEY_w: + return InputKey::W; + case XKB_KEY_X: + case XKB_KEY_x: + return InputKey::X; + case XKB_KEY_Y: + case XKB_KEY_y: + return InputKey::Y; + case XKB_KEY_Z: + case XKB_KEY_z: + return InputKey::Z; + + case XKB_KEY_NoSymbol: + case XKB_KEY_VoidSymbol: + return InputKey::None; + default: + return InputKey::None; + } +} + +InputKey WaylandDisplayBackend::LinuxInputEventCodeToInputKey(uint32_t inputCode) +{ + switch (inputCode) + { + // Keyboard + // Probably not needed due to the existence of XKBKeySym + case KEY_ESC: + return InputKey::Escape; + case KEY_1: + return InputKey::_1; + case KEY_2: + return InputKey::_2; + case KEY_3: + return InputKey::_3; + case KEY_4: + return InputKey::_4; + case KEY_5: + return InputKey::_5; + case KEY_6: + return InputKey::_6; + case KEY_7: + return InputKey::_7; + case KEY_8: + return InputKey::_8; + case KEY_9: + return InputKey::_9; + case KEY_0: + return InputKey::_0; + case KEY_KP1: + return InputKey::NumPad1; + case KEY_KP2: + return InputKey::NumPad2; + case KEY_KP3: + return InputKey::NumPad3; + case KEY_KP4: + return InputKey::NumPad4; + case KEY_KP5: + return InputKey::NumPad5; + case KEY_KP6: + return InputKey::NumPad6; + case KEY_KP7: + return InputKey::NumPad7; + case KEY_KP8: + return InputKey::NumPad8; + case KEY_KP9: + return InputKey::NumPad9; + case KEY_KP0: + return InputKey::NumPad0; + case KEY_F1: + return InputKey::F1; + case KEY_F2: + return InputKey::F2; + case KEY_F3: + return InputKey::F3; + case KEY_F4: + return InputKey::F4; + case KEY_F5: + return InputKey::F5; + case KEY_F6: + return InputKey::F6; + case KEY_F7: + return InputKey::F7; + case KEY_F8: + return InputKey::F8; + case KEY_F9: + return InputKey::F9; + case KEY_F10: + return InputKey::F10; + case KEY_F11: + return InputKey::F11; + case KEY_F12: + return InputKey::F12; + case KEY_F13: + return InputKey::F13; + case KEY_F14: + return InputKey::F14; + case KEY_F15: + return InputKey::F15; + case KEY_F16: + return InputKey::F16; + case KEY_F17: + return InputKey::F17; + case KEY_F18: + return InputKey::F18; + case KEY_F19: + return InputKey::F19; + case KEY_F20: + return InputKey::F20; + case KEY_F21: + return InputKey::F21; + case KEY_F22: + return InputKey::F22; + case KEY_F23: + return InputKey::F23; + case KEY_F24: + return InputKey::F24; + case KEY_MINUS: + case KEY_KPMINUS: + return InputKey::Minus; + case KEY_EQUAL: + return InputKey::Equals; + case KEY_BACKSPACE: + return InputKey::Backspace; + case KEY_BACKSLASH: + return InputKey::Backslash; + case KEY_TAB: + return InputKey::Tab; + case KEY_LEFTBRACE: + return InputKey::LeftBracket; + case KEY_RIGHTBRACE: + return InputKey::RightBracket; + case KEY_LEFTCTRL: + return InputKey::LControl; + case KEY_RIGHTCTRL: + return InputKey::RControl; + case KEY_LEFTALT: + case KEY_RIGHTALT: + return InputKey::Alt; + case KEY_DELETE: + return InputKey::Delete; + case KEY_SEMICOLON: + return InputKey::Semicolon; + case KEY_COMMA: + return InputKey::Comma; + case KEY_DOT: + return InputKey::Period; + case KEY_NUMLOCK: + return InputKey::NumLock; + case KEY_CAPSLOCK: + return InputKey::CapsLock; + case KEY_SCROLLDOWN: + return InputKey::ScrollLock; + case KEY_LEFTSHIFT: + return InputKey::LShift; + case KEY_RIGHTSHIFT: + return InputKey::RShift; + case KEY_GRAVE: + return InputKey::Tilde; + case KEY_APOSTROPHE: + return InputKey::SingleQuote; + case KEY_SPACE: + return InputKey::Space; + case KEY_ENTER: + case KEY_KPENTER: + return InputKey::Enter; + + case KEY_UP: + return InputKey::Up; + case KEY_DOWN: + return InputKey::Down; + case KEY_LEFT: + return InputKey::Left; + case KEY_RIGHT: + return InputKey::Right; + + case KEY_A: + return InputKey::A; + case KEY_B: + return InputKey::B; + case KEY_C: + return InputKey::C; + case KEY_D: + return InputKey::D; + case KEY_E: + return InputKey::E; + case KEY_F: + return InputKey::F; + case KEY_G: + return InputKey::G; + case KEY_H: + return InputKey::H; + case KEY_I: + return InputKey::I; + case KEY_J: + return InputKey::J; + case KEY_K: + return InputKey::K; + case KEY_L: + return InputKey::L; + case KEY_M: + return InputKey::M; + case KEY_N: + return InputKey::N; + case KEY_O: + return InputKey::O; + case KEY_P: + return InputKey::P; + case KEY_Q: + return InputKey::Q; + case KEY_R: + return InputKey::R; + case KEY_S: + return InputKey::S; + case KEY_T: + return InputKey::T; + case KEY_U: + return InputKey::U; + case KEY_V: + return InputKey::V; + case KEY_W: + return InputKey::W; + case KEY_X: + return InputKey::X; + case KEY_Y: + return InputKey::Y; + case KEY_Z: + return InputKey::Z; + + // Mouse + case BTN_LEFT: + return InputKey::LeftMouse; + case BTN_RIGHT: + return InputKey::RightMouse; + case BTN_MIDDLE: + return InputKey::MiddleMouse; + default: + return InputKey::None; + } +} + +std::string WaylandDisplayBackend::GetWaylandCursorName(StandardCursor cursor) +{ + // Checked out Adwaita and Breeze cursors for the names. + // Other cursor themes should adhere to the names these two have. + switch (cursor) + { + case StandardCursor::arrow: + return "default"; + case StandardCursor::appstarting: + return "progress"; + case StandardCursor::cross: + return "crosshair"; + case StandardCursor::hand: + return "pointer"; + case StandardCursor::ibeam: + return "text"; + case StandardCursor::no: + return "not-allowed"; + case StandardCursor::size_all: + return "fleur"; + case StandardCursor::size_nesw: + return "nesw-resize"; + case StandardCursor::size_ns: + return "ns-resize"; + case StandardCursor::size_nwse: + return "nwse-resize"; + case StandardCursor::size_we: + return "ew-resize"; + case StandardCursor::uparrow: + // Breeze actually has an up-arrow cursor, but Adwaita doesn't, so the default cursor it is + return "default"; + case StandardCursor::wait: + return "wait"; + default: + return "default"; + } +} + diff --git a/libraries/ZWidget/src/window/wayland/wayland_display_backend.h b/libraries/ZWidget/src/window/wayland/wayland_display_backend.h new file mode 100644 index 000000000..26e3e7f64 --- /dev/null +++ b/libraries/ZWidget/src/window/wayland/wayland_display_backend.h @@ -0,0 +1,168 @@ +#pragma once + +#include "window/window.h" +#include "window/ztimer/ztimer.h" + +#include +#include +#include +#include +#include +#include "wl_fractional_scaling_protocol.hpp" +#include +#include +#include +#include + +static short poll_single(int fd, short events, int timeout) { + pollfd pfd { .fd = fd, .events = events, .revents = 0 }; + if (0 > poll(&pfd, 1, timeout)) { + throw std::runtime_error("poll() failed"); + } + + return pfd.revents; +} + +enum pointer_event_mask { + POINTER_EVENT_ENTER = 1 << 0, + POINTER_EVENT_LEAVE = 1 << 1, + POINTER_EVENT_MOTION = 1 << 2, + POINTER_EVENT_BUTTON = 1 << 3, + POINTER_EVENT_AXIS = 1 << 4, + POINTER_EVENT_AXIS_SOURCE = 1 << 5, + POINTER_EVENT_AXIS_STOP = 1 << 6, + POINTER_EVENT_AXIS_DISCRETE = 1 << 7, + POINTER_EVENT_AXIS_120 = 1 << 8, + POINTER_EVENT_RELATIVE_MOTION = 1 << 9, +}; + +struct WaylandPointerEvent +{ + uint32_t event_mask; + double surfaceX, surfaceY; + double dx, dy; + uint32_t button; + wayland::pointer_button_state state; + uint32_t time; + uint32_t serial; + struct { + bool valid; + double value; + int32_t discrete; + int32_t value120; + } axes[2]; + wayland::pointer_axis_source axis_source; +}; + +class WaylandDisplayWindow; + +class WaylandDisplayBackend : public DisplayBackend +{ +public: + WaylandDisplayBackend(); + ~WaylandDisplayBackend(); + + std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) override; + void ProcessEvents() override; + void RunLoop() override; + void ExitLoop() override; + + void* StartTimer(int timeoutMilliseconds, std::function onTimer) override; + void StopTimer(void* timerID) override; + + Size GetScreenSize() override; + + bool IsWayland() override { return true; } + + void OnWindowCreated(WaylandDisplayWindow* window); + void OnWindowDestroyed(WaylandDisplayWindow* window); + + void SetCursor(StandardCursor cursor); + void ShowCursor(bool enable); + bool GetKeyState(InputKey key); + +#ifdef USE_DBUS + std::unique_ptr CreateOpenFileDialog(DisplayWindow* owner) override; + std::unique_ptr CreateSaveFileDialog(DisplayWindow* owner) override; + std::unique_ptr CreateOpenFolderDialog(DisplayWindow* owner) override; +#endif + + bool exitRunLoop = false; + Size s_ScreenSize = Size(0, 0); + wayland::display_t s_waylandDisplay = wayland::display_t(); + wayland::registry_t s_waylandRegistry; + std::vector s_Windows; + WaylandDisplayWindow* m_FocusWindow = nullptr; + WaylandDisplayWindow* m_MouseFocusWindow = nullptr; // Mouse focus should be tracked separately. + + wayland::compositor_t m_waylandCompositor; + wayland::shm_t m_waylandSHM; + wayland::seat_t m_waylandSeat; + wayland::output_t m_waylandOutput; + wayland::data_device_manager_t m_DataDeviceManager; + wayland::xdg_wm_base_t m_XDGWMBase; + wayland::zwp_pointer_constraints_v1_t m_PointerConstraints; + wayland::xdg_activation_v1_t m_XDGActivation; + wayland::zxdg_decoration_manager_v1_t m_XDGDecorationManager; + wayland::fractional_scale_manager_v1_t m_FractionalScaleManager; + wayland::zxdg_output_manager_v1_t m_XDGOutputManager; + wayland::zxdg_output_v1_t m_XDGOutput; + wayland::zxdg_exporter_v2_t m_XDGExporter; + + wayland::keyboard_t m_waylandKeyboard; + wayland::pointer_t m_waylandPointer; + + wayland::zwp_relative_pointer_manager_v1_t m_RelativePointerManager; + wayland::zwp_relative_pointer_v1_t m_RelativePointer; + + wayland::cursor_image_t m_cursorImage; + wayland::surface_t m_cursorSurface; + wayland::buffer_t m_cursorBuffer; + + std::map inputKeyStates; // True when the key is pressed, false when isn't + + bool IsMouseLocked() { return hasMouseLock; } + void SetMouseLocked(bool val) { hasMouseLock = val; } + +private: + void CheckNeedsUpdate(); + void UpdateTimers(); + void ConnectDeviceEvents(); + void OnKeyboardKeyEvent(xkb_keysym_t xkbKeySym, wayland::keyboard_key_state state); + void OnKeyboardCharEvent(const char* ch, wayland::keyboard_key_state state); + void OnKeyboardDelayEnd(); + void OnKeyboardRepeat(); + void OnMouseEnterEvent(uint32_t serial); + void OnMouseLeaveEvent(); + void OnMousePressEvent(InputKey button); + void OnMouseReleaseEvent(InputKey button); + void OnMouseMoveEvent(Point surfacePos); + void OnMouseMoveRawEvent(int surfaceX, int surfaceY); + void OnMouseWheelEvent(InputKey button); + + InputKey XKBKeySymToInputKey(xkb_keysym_t keySym); + InputKey LinuxInputEventCodeToInputKey(uint32_t inputCode); + + std::string GetWaylandCursorName(StandardCursor cursor); + + bool hasKeyboard = false; + bool hasPointer = false; + bool hasMouseLock = false; + + ZTimer::TimePoint m_previousTime; + ZTimer::TimePoint m_currentTime; + + ZTimer m_keyboardDelayTimer; + ZTimer m_keyboardRepeatTimer; + + InputKey previousKey = {}; + std::string previousChars; + + uint32_t m_KeyboardSerial = 0; + + xkb_context* m_KeymapContext = nullptr; + xkb_keymap* m_Keymap = nullptr; + xkb_state* m_KeyboardState = nullptr; + + WaylandPointerEvent currentPointerEvent = {0}; +}; diff --git a/libraries/ZWidget/src/window/wayland/wayland_display_window.cpp b/libraries/ZWidget/src/window/wayland/wayland_display_window.cpp new file mode 100644 index 000000000..f41b3317e --- /dev/null +++ b/libraries/ZWidget/src/window/wayland/wayland_display_window.cpp @@ -0,0 +1,472 @@ +#include "wayland_display_window.h" + +#include +#include + +WaylandDisplayWindow::WaylandDisplayWindow(WaylandDisplayBackend* backend, DisplayWindowHost* windowHost, bool popupWindow, WaylandDisplayWindow* owner, RenderAPI renderAPI) + : backend(backend), m_owner(owner), windowHost(windowHost), m_PopupWindow(popupWindow), m_renderAPI(renderAPI) +{ + m_AppSurface = backend->m_waylandCompositor.create_surface(); + + m_NativeHandle.display = backend->s_waylandDisplay; + m_NativeHandle.surface = m_AppSurface; + + if (backend->m_FractionalScaleManager) + { + m_FractionalScale = backend->m_FractionalScaleManager.get_fractional_scale(m_AppSurface); + + m_FractionalScale.on_preferred_scale() = [&](uint32_t scale_numerator) { + // parameter is the numerator of a fraction with the denominator of 120 + m_ScaleFactor = scale_numerator / 120.0; + + m_NeedsUpdate = true; + windowHost->OnWindowDpiScaleChanged(); + }; + } + + m_XDGSurface = backend->m_XDGWMBase.get_xdg_surface(m_AppSurface); + m_XDGSurface.on_configure() = [&] (uint32_t serial) { + m_XDGSurface.ack_configure(serial); + }; + + if (popupWindow) + { + InitializePopup(); + } + else + { + InitializeToplevel(); + } + + backend->OnWindowCreated(this); + + this->DrawSurface(); +} + +WaylandDisplayWindow::~WaylandDisplayWindow() +{ + backend->OnWindowDestroyed(this); +} + +void WaylandDisplayWindow::InitializeToplevel() +{ + m_XDGToplevel = m_XDGSurface.get_toplevel(); + m_XDGToplevel.set_title("ZWidget Window"); + + if (m_owner) + m_XDGToplevel.set_parent(m_owner->m_XDGToplevel); + + if (backend->m_XDGDecorationManager) + { + // Force server side decorations if possible + m_XDGToplevelDecoration = backend->m_XDGDecorationManager.get_toplevel_decoration(m_XDGToplevel); + m_XDGToplevelDecoration.set_mode(wayland::zxdg_toplevel_decoration_v1_mode::server_side); + } + + m_AppSurface.commit(); + + backend->s_waylandDisplay.roundtrip(); + + // These have to be added after the roundtrip + m_XDGToplevel.on_configure() = [&] (int32_t width, int32_t height, wayland::array_t states) { + OnXDGToplevelConfigureEvent(width, height); + }; + + m_XDGToplevel.on_close() = [&] () { + OnExitEvent(); + }; + + m_XDGToplevel.on_configure_bounds() = [this] (int32_t width, int32_t height) + { + + }; + + m_XDGExported = backend->m_XDGExporter.export_toplevel(m_AppSurface); + + m_XDGExported.on_handle() = [&] (std::string handleStr) { + OnExportHandleEvent(handleStr); + }; +} + +void WaylandDisplayWindow::InitializePopup() +{ + if (!m_owner) + throw std::runtime_error("Popup window must have an owner!"); + + wayland::xdg_positioner_t popupPositioner = backend->m_XDGWMBase.create_positioner(); + + popupPositioner.set_anchor(wayland::xdg_positioner_anchor::bottom); + popupPositioner.set_anchor_rect(0, 0, 1, 30); + popupPositioner.set_size(1, 1); + + m_XDGPopup = m_XDGSurface.get_popup(m_owner->m_XDGSurface, popupPositioner); + + m_XDGPopup.on_configure() = [&] (int32_t x, int32_t y, int32_t width, int32_t height) { + SetClientFrame(Rect::xywh(x, y, width, height)); + }; + + //m_XDGPopup.on_repositioned() + + m_XDGPopup.on_popup_done() = [&] () { + OnExitEvent(); + }; + + m_AppSurface.commit(); + + backend->s_waylandDisplay.roundtrip(); +} + + +void WaylandDisplayWindow::SetWindowTitle(const std::string& text) +{ + if (m_XDGToplevel) + m_XDGToplevel.set_title(text); +} + +void WaylandDisplayWindow::SetWindowFrame(const Rect& box) +{ + // Resizing will be shown on the next commit + CreateBuffers(box.width, box.height); + windowHost->OnWindowGeometryChanged(); + m_NeedsUpdate = true; + m_AppSurface.commit(); +} + +void WaylandDisplayWindow::SetClientFrame(const Rect& box) +{ + SetWindowFrame(box); +} + +void WaylandDisplayWindow::Show() +{ + m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0); + m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height); + m_AppSurface.commit(); +} + +void WaylandDisplayWindow::ShowFullscreen() +{ + if (m_XDGToplevel) + { + m_XDGToplevel.set_fullscreen(backend->m_waylandOutput); + isFullscreen = true; + } +} + +void WaylandDisplayWindow::ShowMaximized() +{ + if (m_XDGToplevel) + m_XDGToplevel.set_maximized(); +} + +void WaylandDisplayWindow::ShowMinimized() +{ + if (m_XDGToplevel) + m_XDGToplevel.set_minimized(); +} + +void WaylandDisplayWindow::ShowNormal() +{ + if (m_XDGToplevel) + m_XDGToplevel.unset_fullscreen(); +} + +bool WaylandDisplayWindow::IsWindowFullscreen() +{ + return isFullscreen; +} + +void WaylandDisplayWindow::Hide() +{ + // Apparently this is how hiding a window works + // By attaching a null buffer to the surface + // See: https://lists.freedesktop.org/archives/wayland-devel/2017-November/035963.html + m_AppSurface.attach(nullptr, 0, 0); + m_AppSurface.commit(); +} + +void WaylandDisplayWindow::Activate() +{ + // To do: this needs to be in the backend instance if all windows share the activation token + wayland::xdg_activation_token_v1_t xdgActivationToken = backend->m_XDGActivation.get_activation_token(); + + std::string tokenString; + + xdgActivationToken.on_done() = [&tokenString] (std::string obtainedString) { + tokenString = obtainedString; + }; + + xdgActivationToken.set_surface(m_AppSurface); + xdgActivationToken.commit(); // This will set our token string + + backend->m_XDGActivation.activate(tokenString, m_AppSurface); + backend->m_FocusWindow = this; + backend->m_MouseFocusWindow = this; + windowHost->OnWindowActivated(); +} + +void WaylandDisplayWindow::ShowCursor(bool enable) +{ + backend->ShowCursor(enable); +} + +void WaylandDisplayWindow::LockCursor() +{ + m_LockedPointer = backend->m_PointerConstraints.lock_pointer(m_AppSurface, backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent); + backend->SetMouseLocked(true); + ShowCursor(false); +} + +void WaylandDisplayWindow::UnlockCursor() +{ + if (m_LockedPointer) + m_LockedPointer.proxy_release(); + backend->SetMouseLocked(false); + ShowCursor(true); +} + +void WaylandDisplayWindow::CaptureMouse() +{ + m_ConfinedPointer = backend->m_PointerConstraints.confine_pointer(GetWindowSurface(), backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent); + ShowCursor(false); +} + +void WaylandDisplayWindow::ReleaseMouseCapture() +{ + if (m_ConfinedPointer) + m_ConfinedPointer.proxy_release(); + ShowCursor(true); +} + +void WaylandDisplayWindow::Update() +{ + m_NeedsUpdate = true; +} + +bool WaylandDisplayWindow::GetKeyState(InputKey key) +{ + return backend->GetKeyState(key); +} + +void WaylandDisplayWindow::SetCursor(StandardCursor cursor) +{ + backend->SetCursor(cursor); +} + +Rect WaylandDisplayWindow::GetWindowFrame() const +{ + return Rect(m_WindowGlobalPos.x, m_WindowGlobalPos.y, m_WindowSize.width, m_WindowSize.height); +} + +Size WaylandDisplayWindow::GetClientSize() const +{ + return m_WindowSize / m_ScaleFactor; +} + +int WaylandDisplayWindow::GetPixelWidth() const +{ + return m_WindowSize.width; +} + +int WaylandDisplayWindow::GetPixelHeight() const +{ + return m_WindowSize.height; +} + +double WaylandDisplayWindow::GetDpiScale() const +{ + return m_ScaleFactor; +} + +void WaylandDisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels) +{ + // Make new buffers if the sizes don't match + if (width != m_WindowSize.width || height != m_WindowSize.height) + CreateBuffers(width, height); + + std::memcpy(shared_mem->get_mem(), (void*)pixels, width * height * 4); +} + +void WaylandDisplayWindow::SetBorderColor(uint32_t bgra8) +{ + +} + +void WaylandDisplayWindow::SetCaptionColor(uint32_t bgra8) +{ + +} + +void WaylandDisplayWindow::SetCaptionTextColor(uint32_t bgra8) +{ + +} + +std::string WaylandDisplayWindow::GetClipboardText() +{ + return m_ClipboardContents; +} + +void WaylandDisplayWindow::SetClipboardText(const std::string& text) +{ + m_ClipboardContents = text; +} + +Point WaylandDisplayWindow::MapFromGlobal(const Point& pos) const +{ + return (pos - m_WindowGlobalPos) / m_ScaleFactor; +} + +Point WaylandDisplayWindow::MapToGlobal(const Point& pos) const +{ + return (m_WindowGlobalPos + pos) / m_ScaleFactor; +} + +void WaylandDisplayWindow::OnXDGToplevelConfigureEvent(int32_t width, int32_t height) +{ + Rect rect = GetWindowFrame(); + rect.width = width / m_ScaleFactor; + rect.height = height / m_ScaleFactor; + SetWindowFrame(rect); + windowHost->OnWindowGeometryChanged(); +} + +void WaylandDisplayWindow::OnExportHandleEvent(std::string exportedHandle) +{ + m_windowID = exportedHandle; +} + +void WaylandDisplayWindow::OnExitEvent() +{ + windowHost->OnWindowClose(); +} + +void WaylandDisplayWindow::DrawSurface(uint32_t serial) +{ + m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0); + m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height); + + if (m_renderAPI == RenderAPI::Unspecified || m_renderAPI == RenderAPI::Bitmap) + { + m_FrameCallback = m_AppSurface.frame(); + + m_FrameCallback.on_done() = bind_mem_fn(&WaylandDisplayWindow::DrawSurface, this); + } + m_AppSurface.commit(); +} + +void WaylandDisplayWindow::CreateBuffers(int32_t width, int32_t height) +{ + if (width == 0 || height == 0) + return; + + if (shared_mem) + shared_mem.reset(); + + int scaled_width = width * m_ScaleFactor; + int scaled_height = height * m_ScaleFactor; + + shared_mem = std::make_shared(scaled_width * scaled_height * 4); + auto pool = backend->m_waylandSHM.create_pool(shared_mem->get_fd(), scaled_width * scaled_height * 4); + + m_AppSurfaceBuffer = pool.create_buffer(0, scaled_width, scaled_height, scaled_width * 4, wayland::shm_format::xrgb8888); + + m_WindowSize = Size(scaled_width, scaled_height); +} + +std::string WaylandDisplayWindow::GetWaylandWindowID() +{ + return m_windowID; +} + +// This is to avoid needing all the Vulkan headers and the volk binding library just for this: +#ifndef VK_VERSION_1_0 + +#define VKAPI_CALL +#define VKAPI_PTR VKAPI_CALL + +typedef uint32_t VkFlags; +typedef enum VkStructureType { VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000, VK_OBJECT_TYPE_MAX_ENUM = 0x7FFFFFFF } VkStructureType; +typedef enum VkResult { VK_SUCCESS = 0, VK_RESULT_MAX_ENUM = 0x7FFFFFFF } VkResult; +typedef struct VkAllocationCallbacks VkAllocationCallbacks; + +typedef void (VKAPI_PTR* PFN_vkVoidFunction)(void); +typedef PFN_vkVoidFunction(VKAPI_PTR* PFN_vkGetInstanceProcAddr)(VkInstance instance, const char* pName); + +#ifndef VK_KHR_wayland_surface + +typedef VkFlags VkWaylandSurfaceCreateFlagsKHR; +typedef struct VkWaylandSurfaceCreateInfoKHR +{ + VkStructureType sType; + const void* pNext; + VkWaylandSurfaceCreateFlagsKHR flags; + struct wl_display* display; + struct wl_surface* surface; +} VkWaylandSurfaceCreateInfoKHR; + +typedef VkResult(VKAPI_PTR* PFN_vkCreateWaylandSurfaceKHR)(VkInstance instance, const VkWaylandSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface); + +#endif +#endif + +class ZWidgetWaylandVulkanLoader +{ +public: + ZWidgetWaylandVulkanLoader() + { +#if defined(__APPLE__) + module = dlopen("libvulkan.dylib", RTLD_NOW | RTLD_LOCAL); + if (!module) + module = dlopen("libvulkan.1.dylib", RTLD_NOW | RTLD_LOCAL); + if (!module) + module = dlopen("libMoltenVK.dylib", RTLD_NOW | RTLD_LOCAL); +#else + module = dlopen("libvulkan.so.1", RTLD_NOW | RTLD_LOCAL); + if (!module) + module = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL); +#endif + + if (!module) + throw std::runtime_error("Could not load vulkan"); + + vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym(module, "vkGetInstanceProcAddr"); + if (!vkGetInstanceProcAddr) + { + dlclose(module); + throw std::runtime_error("vkGetInstanceProcAddr not found"); + } + } + + ~ZWidgetWaylandVulkanLoader() + { + dlclose(module); + } + + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr; + void* module = nullptr; +}; + +VkSurfaceKHR WaylandDisplayWindow::CreateVulkanSurface(VkInstance instance) +{ + static ZWidgetWaylandVulkanLoader loader; + + auto vkCreateWaylandSurfaceKHR = (PFN_vkCreateWaylandSurfaceKHR)loader.vkGetInstanceProcAddr(instance, "vkCreateWaylandSurfaceKHR"); + if (!vkCreateWaylandSurfaceKHR) + throw std::runtime_error("Could not create vulkan surface"); + + VkWaylandSurfaceCreateInfoKHR createInfo = { VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR }; + createInfo.display = m_NativeHandle.display; + createInfo.surface = m_NativeHandle.surface; + + VkSurfaceKHR surface = {}; + VkResult result = vkCreateWaylandSurfaceKHR(instance, &createInfo, nullptr, &surface); + if (result != VK_SUCCESS) + throw std::runtime_error("Could not create vulkan surface"); + return surface; +} + +std::vector WaylandDisplayWindow::GetVulkanInstanceExtensions() +{ + return { "VK_KHR_surface", "VK_KHR_wayland_surface" }; +} diff --git a/libraries/ZWidget/src/window/wayland/wayland_display_window.h b/libraries/ZWidget/src/window/wayland/wayland_display_window.h new file mode 100644 index 000000000..5553ece84 --- /dev/null +++ b/libraries/ZWidget/src/window/wayland/wayland_display_window.h @@ -0,0 +1,198 @@ +#pragma once + +#include "wayland_display_backend.h" +#include "window/ztimer/ztimer.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "zwidget/window/window.h" +#include "zwidget/window/waylandnativehandle.h" + +#include +#include +#include +#include + +template +std::function bind_mem_fn(R(T::* func)(Args...), T *t) +{ + return [func, t] (Args... args) + { + return (t->*func)(args...); + }; +} + +class SharedMemHelper +{ +public: + SharedMemHelper(size_t size) + : len(size) + { + std::stringstream ss; + std::random_device device; + std::default_random_engine engine(device()); + std::uniform_int_distribution distribution(0, std::numeric_limits::max()); + ss << distribution(engine); + name = ss.str(); + + fd = memfd_create(name.c_str(), 0); + if(fd < 0) + throw std::runtime_error("shm_open failed."); + + if(ftruncate(fd, size) < 0) + throw std::runtime_error("ftruncate failed."); + + mem = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if(mem == MAP_FAILED) // NOLINT + throw std::runtime_error("mmap failed with len " + std::to_string(len) + "."); + } + + ~SharedMemHelper() noexcept + { + if(fd) + { + munmap(mem, len); + close(fd); + shm_unlink(name.c_str()); + } + } + + int get_fd() const + { + return fd; + } + + void *get_mem() + { + return mem; + } + +private: + std::string name; + int fd = 0; + size_t len = 0; + void *mem = nullptr; +}; + +class WaylandDisplayWindow : public DisplayWindow +{ +public: + WaylandDisplayWindow(WaylandDisplayBackend* backend, DisplayWindowHost* windowHost, bool popupWindow, WaylandDisplayWindow* owner, RenderAPI renderAPI); + ~WaylandDisplayWindow(); + + void SetWindowTitle(const std::string& text) override; + void SetWindowFrame(const Rect& box) override; + void SetClientFrame(const Rect& box) override; + void Show() override; + void ShowFullscreen() override; + void ShowMaximized() override; + void ShowMinimized() override; + void ShowNormal() override; + void Hide() override; + void Activate() override; + void ShowCursor(bool enable) override; + void LockCursor() override; + void UnlockCursor() override; + void CaptureMouse() override; + void ReleaseMouseCapture() override; + void Update() override; + bool GetKeyState(InputKey key) override; + void SetCursor(StandardCursor cursor) override; + + Rect GetWindowFrame() const override; + Size GetClientSize() const override; + int GetPixelWidth() const override; + int GetPixelHeight() const override; + double GetDpiScale() const override; + + void PresentBitmap(int width, int height, const uint32_t* pixels) override; + + void SetBorderColor(uint32_t bgra8) override; + void SetCaptionColor(uint32_t bgra8) override; + void SetCaptionTextColor(uint32_t bgra8) override; + + std::string GetClipboardText() override; + void SetClipboardText(const std::string& text) override; + + Point MapFromGlobal(const Point& pos) const override; + Point MapToGlobal(const Point& pos) const override; + + void* GetNativeHandle() override { return (void*)&m_NativeHandle; } + + std::vector GetVulkanInstanceExtensions() override; + VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override; + + wayland::surface_t GetWindowSurface() { return m_AppSurface; } + + bool IsWindowFullscreen() override; + +private: + // Event handlers as otherwise linking DisplayWindowHost On...() functions with Wayland events directly crashes the app + // Alternatively to avoid crashes one can capture by value ([=]) instead of reference ([&]) + void OnXDGToplevelConfigureEvent(int32_t width, int32_t height); + void OnExportHandleEvent(std::string exportedHandle); + void OnExitEvent(); + + void DrawSurface(uint32_t serial = 0); + + void InitializeToplevel(); + void InitializePopup(); + + WaylandDisplayBackend* backend = nullptr; + WaylandDisplayWindow* m_owner = nullptr; + DisplayWindowHost* windowHost = nullptr; + bool m_PopupWindow = false; + + bool m_NeedsUpdate = true; + + Point m_WindowGlobalPos = Point(0, 0); + Size m_WindowSize = Size(0, 0); + double m_ScaleFactor = 1.0; + + Point m_SurfaceMousePos = Point(0, 0); + + WaylandNativeHandle m_NativeHandle; + RenderAPI m_renderAPI; + + wayland::data_device_t m_DataDevice; + wayland::data_source_t m_DataSource; + + wayland::zxdg_toplevel_decoration_v1_t m_XDGToplevelDecoration; + + wayland::fractional_scale_v1_t m_FractionalScale; + + wayland::surface_t m_AppSurface; + wayland::buffer_t m_AppSurfaceBuffer; + + wayland::xdg_surface_t m_XDGSurface; + wayland::xdg_toplevel_t m_XDGToplevel; + wayland::xdg_popup_t m_XDGPopup; + + wayland::zxdg_exported_v2_t m_XDGExported; + + wayland::zwp_locked_pointer_v1_t m_LockedPointer; + wayland::zwp_confined_pointer_v1_t m_ConfinedPointer; + + wayland::callback_t m_FrameCallback; + + std::string m_windowID; + std::string m_ClipboardContents; + + std::shared_ptr shared_mem; + + bool isFullscreen = false; + + // Helper functions + void CreateBuffers(int32_t width, int32_t height); + std::string GetWaylandWindowID(); + + friend WaylandDisplayBackend; +}; diff --git a/libraries/ZWidget/src/window/wayland/wl_fractional_scaling_protocol.cpp b/libraries/ZWidget/src/window/wayland/wl_fractional_scaling_protocol.cpp new file mode 100644 index 000000000..536ae765d --- /dev/null +++ b/libraries/ZWidget/src/window/wayland/wl_fractional_scaling_protocol.cpp @@ -0,0 +1,205 @@ +#include "wl_fractional_scaling_protocol.hpp" + +using namespace wayland; +using namespace wayland::detail; + +const wl_interface* fractional_scale_manager_v1_interface_destroy_request[0] = { +}; + +const wl_interface* fractional_scale_manager_v1_interface_get_fractional_scale_request[2] = { + &fractional_scale_v1_interface, + &surface_interface, +}; + +const wl_message fractional_scale_manager_v1_interface_requests[2] = { + { + "destroy", + "", + fractional_scale_manager_v1_interface_destroy_request, + }, + { + "get_fractional_scale", + "no", + fractional_scale_manager_v1_interface_get_fractional_scale_request, + }, +}; + +const wl_message fractional_scale_manager_v1_interface_events[0] = { +}; + +const wl_interface wayland::detail::fractional_scale_manager_v1_interface = + { + "wp_fractional_scale_manager_v1", + 1, + 2, + fractional_scale_manager_v1_interface_requests, + 0, + fractional_scale_manager_v1_interface_events, + }; + +const wl_interface* fractional_scale_v1_interface_destroy_request[0] = { +}; + +const wl_interface* fractional_scale_v1_interface_preferred_scale_event[1] = { + nullptr, +}; + +const wl_message fractional_scale_v1_interface_requests[1] = { + { + "destroy", + "", + fractional_scale_v1_interface_destroy_request, + }, +}; + +const wl_message fractional_scale_v1_interface_events[1] = { + { + "preferred_scale", + "u", + fractional_scale_v1_interface_preferred_scale_event, + }, +}; + +const wl_interface wayland::detail::fractional_scale_v1_interface = + { + "wp_fractional_scale_v1", + 1, + 1, + fractional_scale_v1_interface_requests, + 1, + fractional_scale_v1_interface_events, + }; + +fractional_scale_manager_v1_t::fractional_scale_manager_v1_t(const proxy_t &p) + : proxy_t(p) +{ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&fractional_scale_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return fractional_scale_manager_v1_t(p); }); +} + +fractional_scale_manager_v1_t::fractional_scale_manager_v1_t() +{ + set_interface(&fractional_scale_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return fractional_scale_manager_v1_t(p); }); +} + +fractional_scale_manager_v1_t::fractional_scale_manager_v1_t(wp_fractional_scale_manager_v1 *p, wrapper_type t) + : proxy_t(reinterpret_cast (p), t){ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&fractional_scale_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return fractional_scale_manager_v1_t(p); }); +} + +fractional_scale_manager_v1_t::fractional_scale_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/) + : proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){ + set_interface(&fractional_scale_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return fractional_scale_manager_v1_t(p); }); +} + +fractional_scale_manager_v1_t fractional_scale_manager_v1_t::proxy_create_wrapper() +{ + return {*this, construct_proxy_wrapper_tag()}; +} + +const std::string fractional_scale_manager_v1_t::interface_name = "wp_fractional_scale_manager_v1"; + +fractional_scale_manager_v1_t::operator wp_fractional_scale_manager_v1*() const +{ + return reinterpret_cast (c_ptr()); +} + +fractional_scale_v1_t fractional_scale_manager_v1_t::get_fractional_scale(surface_t const& surface) +{ + proxy_t p = marshal_constructor(1U, &fractional_scale_v1_interface, nullptr, surface.proxy_has_object() ? reinterpret_cast(surface.c_ptr()) : nullptr); + return fractional_scale_v1_t(p); +} + + +int fractional_scale_manager_v1_t::dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e) +{ + return 0; +} + + +fractional_scale_v1_t::fractional_scale_v1_t(const proxy_t &p) + : proxy_t(p) +{ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&fractional_scale_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return fractional_scale_v1_t(p); }); +} + +fractional_scale_v1_t::fractional_scale_v1_t() +{ + set_interface(&fractional_scale_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return fractional_scale_v1_t(p); }); +} + +fractional_scale_v1_t::fractional_scale_v1_t(wp_fractional_scale_v1 *p, wrapper_type t) + : proxy_t(reinterpret_cast (p), t){ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&fractional_scale_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return fractional_scale_v1_t(p); }); +} + +fractional_scale_v1_t::fractional_scale_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/) + : proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){ + set_interface(&fractional_scale_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return fractional_scale_v1_t(p); }); +} + +fractional_scale_v1_t fractional_scale_v1_t::proxy_create_wrapper() +{ + return {*this, construct_proxy_wrapper_tag()}; +} + +const std::string fractional_scale_v1_t::interface_name = "wp_fractional_scale_v1"; + +fractional_scale_v1_t::operator wp_fractional_scale_v1*() const +{ + return reinterpret_cast (c_ptr()); +} + +std::function &fractional_scale_v1_t::on_preferred_scale() +{ + return std::static_pointer_cast(get_events())->preferred_scale; +} + +int fractional_scale_v1_t::dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e) +{ + std::shared_ptr events = std::static_pointer_cast(e); + switch(opcode) + { + case 0: + if(events->preferred_scale) events->preferred_scale(args[0].get()); + break; + } + return 0; +} + + diff --git a/libraries/ZWidget/src/window/wayland/wl_fractional_scaling_protocol.hpp b/libraries/ZWidget/src/window/wayland/wl_fractional_scaling_protocol.hpp new file mode 100644 index 000000000..859530671 --- /dev/null +++ b/libraries/ZWidget/src/window/wayland/wl_fractional_scaling_protocol.hpp @@ -0,0 +1,126 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +struct wp_fractional_scale_manager_v1; +struct wp_fractional_scale_v1; + +namespace wayland +{ +class fractional_scale_manager_v1_t; +enum class fractional_scale_manager_v1_error : uint32_t; +class fractional_scale_v1_t; + +namespace detail +{ + extern const wl_interface fractional_scale_manager_v1_interface; + extern const wl_interface fractional_scale_v1_interface; +} + +/** \brief fractional surface scale information + + A global interface for requesting surfaces to use fractional scales. + +*/ +class fractional_scale_manager_v1_t : public proxy_t +{ +private: + struct events_t : public detail::events_base_t + { + }; + + static int dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e); + + fractional_scale_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/); + +public: + fractional_scale_manager_v1_t(); + explicit fractional_scale_manager_v1_t(const proxy_t &proxy); + fractional_scale_manager_v1_t(wp_fractional_scale_manager_v1 *p, wrapper_type t = wrapper_type::standard); + + fractional_scale_manager_v1_t proxy_create_wrapper(); + + static const std::string interface_name; + + operator wp_fractional_scale_manager_v1*() const; + + /** \brief extend surface interface for scale information + \return the new surface scale info interface id + \param surface the surface + + Create an add-on object for the the wl_surface to let the compositor + request fractional scales. If the given wl_surface already has a + wp_fractional_scale_v1 object associated, the fractional_scale_exists + protocol error is raised. + + */ + fractional_scale_v1_t get_fractional_scale(surface_t const& surface); + + /** \brief Minimum protocol version required for the \ref get_fractional_scale function + */ + static constexpr std::uint32_t get_fractional_scale_since_version = 1; + +}; + +/** \brief + + */ +enum class fractional_scale_manager_v1_error : uint32_t + { + /** \brief the surface already has a fractional_scale object associated */ + fractional_scale_exists = 0 +}; + + +/** \brief fractional scale interface to a wl_surface + + An additional interface to a wl_surface object which allows the compositor + to inform the client of the preferred scale. + +*/ +class fractional_scale_v1_t : public proxy_t +{ +private: + struct events_t : public detail::events_base_t + { + std::function preferred_scale; + }; + + static int dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e); + + fractional_scale_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/); + +public: + fractional_scale_v1_t(); + explicit fractional_scale_v1_t(const proxy_t &proxy); + fractional_scale_v1_t(wp_fractional_scale_v1 *p, wrapper_type t = wrapper_type::standard); + + fractional_scale_v1_t proxy_create_wrapper(); + + static const std::string interface_name; + + operator wp_fractional_scale_v1*() const; + + /** \brief notify of new preferred scale + \param scale the new preferred scale + + Notification of a new preferred scale for this surface that the + compositor suggests that the client should use. + + The sent scale is the numerator of a fraction with a denominator of 120. + + */ + std::function &on_preferred_scale(); + +}; + + + +} diff --git a/libraries/ZWidget/src/window/win32/win32_display_backend.cpp b/libraries/ZWidget/src/window/win32/win32_display_backend.cpp new file mode 100644 index 000000000..0934da042 --- /dev/null +++ b/libraries/ZWidget/src/window/win32/win32_display_backend.cpp @@ -0,0 +1,56 @@ + +#include "win32_display_backend.h" +#include "win32_display_window.h" +#include "win32_open_file_dialog.h" +#include "win32_save_file_dialog.h" +#include "win32_open_folder_dialog.h" + +std::unique_ptr Win32DisplayBackend::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) +{ + return std::make_unique(windowHost, popupWindow, static_cast(owner), renderAPI); +} + +void Win32DisplayBackend::ProcessEvents() +{ + Win32DisplayWindow::ProcessEvents(); +} + +void Win32DisplayBackend::RunLoop() +{ + Win32DisplayWindow::RunLoop(); +} + +void Win32DisplayBackend::ExitLoop() +{ + Win32DisplayWindow::ExitLoop(); +} + +Size Win32DisplayBackend::GetScreenSize() +{ + return Win32DisplayWindow::GetScreenSize(); +} + +void* Win32DisplayBackend::StartTimer(int timeoutMilliseconds, std::function onTimer) +{ + return Win32DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer)); +} + +void Win32DisplayBackend::StopTimer(void* timerID) +{ + Win32DisplayWindow::StopTimer(timerID); +} + +std::unique_ptr Win32DisplayBackend::CreateOpenFileDialog(DisplayWindow* owner) +{ + return std::make_unique(static_cast(owner)); +} + +std::unique_ptr Win32DisplayBackend::CreateSaveFileDialog(DisplayWindow* owner) +{ + return std::make_unique(static_cast(owner)); +} + +std::unique_ptr Win32DisplayBackend::CreateOpenFolderDialog(DisplayWindow* owner) +{ + return std::make_unique(static_cast(owner)); +} diff --git a/libraries/ZWidget/src/window/win32/win32_display_backend.h b/libraries/ZWidget/src/window/win32/win32_display_backend.h new file mode 100644 index 000000000..f641249c2 --- /dev/null +++ b/libraries/ZWidget/src/window/win32/win32_display_backend.h @@ -0,0 +1,23 @@ +#pragma once + +#include "window/window.h" + +class Win32DisplayBackend : public DisplayBackend +{ +public: + std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) override; + void ProcessEvents() override; + void RunLoop() override; + void ExitLoop() override; + + void* StartTimer(int timeoutMilliseconds, std::function onTimer) override; + void StopTimer(void* timerID) override; + + Size GetScreenSize() override; + + std::unique_ptr CreateOpenFileDialog(DisplayWindow* owner) override; + std::unique_ptr CreateSaveFileDialog(DisplayWindow* owner) override; + std::unique_ptr CreateOpenFolderDialog(DisplayWindow* owner) override; + + bool IsWin32() override { return true; } +}; diff --git a/libraries/ZWidget/src/window/win32/win32displaywindow.cpp b/libraries/ZWidget/src/window/win32/win32_display_window.cpp similarity index 69% rename from libraries/ZWidget/src/window/win32/win32displaywindow.cpp rename to libraries/ZWidget/src/window/win32/win32_display_window.cpp index 21d58a815..9a5043a9a 100644 --- a/libraries/ZWidget/src/window/win32/win32displaywindow.cpp +++ b/libraries/ZWidget/src/window/win32/win32_display_window.cpp @@ -1,5 +1,5 @@ -#include "win32displaywindow.h" +#include "win32_display_window.h" #include #include #include @@ -28,40 +28,12 @@ #define RIDEV_INPUTSINK (0x100) #endif -static std::string from_utf16(const std::wstring& str) -{ - if (str.empty()) return {}; - int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr); - if (needed == 0) - throw std::runtime_error("WideCharToMultiByte failed"); - std::string result; - result.resize(needed); - needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr); - if (needed == 0) - throw std::runtime_error("WideCharToMultiByte failed"); - return result; -} - -static std::wstring to_utf16(const std::string& str) -{ - if (str.empty()) return {}; - int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); - if (needed == 0) - throw std::runtime_error("MultiByteToWideChar failed"); - std::wstring result; - result.resize(needed); - needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size()); - if (needed == 0) - throw std::runtime_error("MultiByteToWideChar failed"); - return result; -} - -Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner) : WindowHost(windowHost) +Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner, RenderAPI renderAPI) : WindowHost(windowHost), PopupWindow(popupWindow) { Windows.push_front(this); WindowsIterator = Windows.begin(); - WNDCLASSEXW classdesc = {}; + WNDCLASSEX classdesc = {}; classdesc.cbSize = sizeof(WNDCLASSEX); classdesc.hInstance = GetModuleHandle(0); classdesc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS; @@ -78,6 +50,7 @@ Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popup DWORD style = 0, exstyle = 0; if (popupWindow) { + exstyle = WS_EX_NOACTIVATE; style = WS_POPUP; } else @@ -85,24 +58,15 @@ Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popup exstyle = WS_EX_APPWINDOW | WS_EX_DLGMODALFRAME; style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX; } - CreateWindowEx(exstyle, L"ZWidgetWindow", L"", style, 0, 0, 100, 100, owner ? owner->WindowHandle : 0, 0, GetModuleHandle(0), this); - - /* - RAWINPUTDEVICE rid; - rid.usUsagePage = HID_USAGE_PAGE_GENERIC; - rid.usUsage = HID_USAGE_GENERIC_MOUSE; - rid.dwFlags = RIDEV_INPUTSINK; - rid.hwndTarget = WindowHandle; - BOOL result = RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)); - */ + CreateWindowEx(exstyle, L"ZWidgetWindow", L"", style, 0, 0, 100, 100, owner ? owner->WindowHandle.hwnd : 0, 0, GetModuleHandle(0), this); } Win32DisplayWindow::~Win32DisplayWindow() { - if (WindowHandle) + if (WindowHandle.hwnd) { - DestroyWindow(WindowHandle); - WindowHandle = 0; + DestroyWindow(WindowHandle.hwnd); + WindowHandle.hwnd = 0; } Windows.erase(WindowsIterator); @@ -110,31 +74,31 @@ Win32DisplayWindow::~Win32DisplayWindow() void Win32DisplayWindow::SetWindowTitle(const std::string& text) { - SetWindowText(WindowHandle, to_utf16(text).c_str()); + SetWindowText(WindowHandle.hwnd, to_utf16(text).c_str()); } void Win32DisplayWindow::SetBorderColor(uint32_t bgra8) { bgra8 = bgra8 & 0x00ffffff; - DwmSetWindowAttribute(WindowHandle, 34/*DWMWA_BORDER_COLOR*/, &bgra8, sizeof(uint32_t)); + DwmSetWindowAttribute(WindowHandle.hwnd, 34/*DWMWA_BORDER_COLOR*/, &bgra8, sizeof(uint32_t)); } void Win32DisplayWindow::SetCaptionColor(uint32_t bgra8) { bgra8 = bgra8 & 0x00ffffff; - DwmSetWindowAttribute(WindowHandle, 35/*DWMWA_CAPTION_COLOR*/, &bgra8, sizeof(uint32_t)); + DwmSetWindowAttribute(WindowHandle.hwnd, 35/*DWMWA_CAPTION_COLOR*/, &bgra8, sizeof(uint32_t)); } void Win32DisplayWindow::SetCaptionTextColor(uint32_t bgra8) { bgra8 = bgra8 & 0x00ffffff; - DwmSetWindowAttribute(WindowHandle, 36/*DWMWA_TEXT_COLOR*/, &bgra8, sizeof(uint32_t)); + DwmSetWindowAttribute(WindowHandle.hwnd, 36/*DWMWA_TEXT_COLOR*/, &bgra8, sizeof(uint32_t)); } void Win32DisplayWindow::SetWindowFrame(const Rect& box) { double dpiscale = GetDpiScale(); - SetWindowPos(WindowHandle, nullptr, (int)std::round(box.x * dpiscale), (int)std::round(box.y * dpiscale), (int)std::round(box.width * dpiscale), (int)std::round(box.height * dpiscale), SWP_NOACTIVATE | SWP_NOZORDER); + SetWindowPos(WindowHandle.hwnd, nullptr, (int)std::round(box.x * dpiscale), (int)std::round(box.y * dpiscale), (int)std::round(box.width * dpiscale), (int)std::round(box.height * dpiscale), SWP_NOACTIVATE | SWP_NOZORDER); } void Win32DisplayWindow::SetClientFrame(const Rect& box) @@ -147,16 +111,16 @@ void Win32DisplayWindow::SetClientFrame(const Rect& box) rect.right = rect.left + (int)std::round(box.width * dpiscale); rect.bottom = rect.top + (int)std::round(box.height * dpiscale); - DWORD style = (DWORD)GetWindowLongPtr(WindowHandle, GWL_STYLE); - DWORD exstyle = (DWORD)GetWindowLongPtr(WindowHandle, GWL_EXSTYLE); - AdjustWindowRectExForDpi(&rect, style, FALSE, exstyle, GetDpiForWindow(WindowHandle)); + DWORD style = (DWORD)GetWindowLongPtr(WindowHandle.hwnd, GWL_STYLE); + DWORD exstyle = (DWORD)GetWindowLongPtr(WindowHandle.hwnd, GWL_EXSTYLE); + AdjustWindowRectExForDpi(&rect, style, FALSE, exstyle, GetDpiForWindow(WindowHandle.hwnd)); - SetWindowPos(WindowHandle, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER); + SetWindowPos(WindowHandle.hwnd, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER); } void Win32DisplayWindow::Show() { - ShowWindow(WindowHandle, SW_SHOW); + ShowWindow(WindowHandle.hwnd, PopupWindow ? SW_SHOWNA : SW_SHOW); } void Win32DisplayWindow::ShowFullscreen() @@ -165,35 +129,47 @@ void Win32DisplayWindow::ShowFullscreen() int width = GetDeviceCaps(screenDC, HORZRES); int height = GetDeviceCaps(screenDC, VERTRES); ReleaseDC(0, screenDC); - SetWindowLongPtr(WindowHandle, GWL_EXSTYLE, WS_EX_APPWINDOW); - SetWindowLongPtr(WindowHandle, GWL_STYLE, WS_OVERLAPPED); - SetWindowPos(WindowHandle, HWND_TOP, 0, 0, width, height, SWP_FRAMECHANGED | SWP_SHOWWINDOW); + DWORD dwStyle = GetWindowLong(WindowHandle.hwnd, GWL_STYLE); + SetWindowLongPtr(WindowHandle.hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW); + SetWindowLongPtr(WindowHandle.hwnd, GWL_STYLE, dwStyle & ~WS_OVERLAPPEDWINDOW); + SetWindowPos(WindowHandle.hwnd, HWND_TOP, 0, 0, width, height, SWP_FRAMECHANGED | SWP_SHOWWINDOW); Fullscreen = true; } void Win32DisplayWindow::ShowMaximized() { - ShowWindow(WindowHandle, SW_SHOWMAXIMIZED); + ShowWindow(WindowHandle.hwnd, SW_SHOWMAXIMIZED); } void Win32DisplayWindow::ShowMinimized() { - ShowWindow(WindowHandle, SW_SHOWMINIMIZED); + ShowWindow(WindowHandle.hwnd, SW_SHOWMINIMIZED); } void Win32DisplayWindow::ShowNormal() { - ShowWindow(WindowHandle, SW_NORMAL); + if (Fullscreen) + { + SetWindowLongPtr(WindowHandle.hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW); + Fullscreen = false; + } + ShowWindow(WindowHandle.hwnd, SW_NORMAL); +} + +bool Win32DisplayWindow::IsWindowFullscreen() +{ + return Fullscreen; } void Win32DisplayWindow::Hide() { - ShowWindow(WindowHandle, SW_HIDE); + ShowWindow(WindowHandle.hwnd, SW_HIDE); } void Win32DisplayWindow::Activate() { - SetFocus(WindowHandle); + if (!PopupWindow) + SetFocus(WindowHandle.hwnd); } void Win32DisplayWindow::ShowCursor(bool enable) @@ -207,6 +183,13 @@ void Win32DisplayWindow::LockCursor() MouseLocked = true; GetCursorPos(&MouseLockPos); ::ShowCursor(FALSE); + + RAWINPUTDEVICE rid = {}; + rid.usUsagePage = HID_USAGE_PAGE_GENERIC; + rid.usUsage = HID_USAGE_GENERIC_MOUSE; + rid.dwFlags = RIDEV_INPUTSINK; + rid.hwndTarget = WindowHandle.hwnd; + RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)); } } @@ -214,6 +197,13 @@ void Win32DisplayWindow::UnlockCursor() { if (MouseLocked) { + RAWINPUTDEVICE rid = {}; + rid.usUsagePage = HID_USAGE_PAGE_GENERIC; + rid.usUsage = HID_USAGE_GENERIC_MOUSE; + rid.dwFlags = RIDEV_REMOVE; + rid.hwndTarget = 0; + RegisterRawInputDevices(&rid, 1, sizeof(rid)); + MouseLocked = false; SetCursorPos(MouseLockPos.x, MouseLockPos.y); ::ShowCursor(TRUE); @@ -222,7 +212,7 @@ void Win32DisplayWindow::UnlockCursor() void Win32DisplayWindow::CaptureMouse() { - SetCapture(WindowHandle); + SetCapture(WindowHandle.hwnd); } void Win32DisplayWindow::ReleaseMouseCapture() @@ -232,7 +222,7 @@ void Win32DisplayWindow::ReleaseMouseCapture() void Win32DisplayWindow::Update() { - InvalidateRect(WindowHandle, nullptr, FALSE); + InvalidateRect(WindowHandle.hwnd, nullptr, FALSE); } bool Win32DisplayWindow::GetKeyState(InputKey key) @@ -252,9 +242,9 @@ void Win32DisplayWindow::SetCursor(StandardCursor cursor) Rect Win32DisplayWindow::GetWindowFrame() const { RECT box = {}; - GetWindowRect(WindowHandle, &box); + GetWindowRect(WindowHandle.hwnd, &box); double dpiscale = GetDpiScale(); - return Rect(box.left / dpiscale, box.top / dpiscale, box.right / dpiscale, box.bottom / dpiscale); + return Rect(box.left / dpiscale, box.top / dpiscale, (box.right - box.left) / dpiscale, (box.bottom - box.top) / dpiscale); } Point Win32DisplayWindow::MapFromGlobal(const Point& pos) const @@ -263,7 +253,7 @@ Point Win32DisplayWindow::MapFromGlobal(const Point& pos) const POINT point = {}; point.x = (LONG)std::round(pos.x / dpiscale); point.y = (LONG)std::round(pos.y / dpiscale); - ScreenToClient(WindowHandle, &point); + ScreenToClient(WindowHandle.hwnd, &point); return Point(point.x * dpiscale, point.y * dpiscale); } @@ -273,14 +263,14 @@ Point Win32DisplayWindow::MapToGlobal(const Point& pos) const POINT point = {}; point.x = (LONG)std::round(pos.x * dpiscale); point.y = (LONG)std::round(pos.y * dpiscale); - ClientToScreen(WindowHandle, &point); + ClientToScreen(WindowHandle.hwnd, &point); return Point(point.x / dpiscale, point.y / dpiscale); } Size Win32DisplayWindow::GetClientSize() const { RECT box = {}; - GetClientRect(WindowHandle, &box); + GetClientRect(WindowHandle.hwnd, &box); double dpiscale = GetDpiScale(); return Size(box.right / dpiscale, box.bottom / dpiscale); } @@ -288,25 +278,25 @@ Size Win32DisplayWindow::GetClientSize() const int Win32DisplayWindow::GetPixelWidth() const { RECT box = {}; - GetClientRect(WindowHandle, &box); + GetClientRect(WindowHandle.hwnd, &box); return box.right; } int Win32DisplayWindow::GetPixelHeight() const { RECT box = {}; - GetClientRect(WindowHandle, &box); + GetClientRect(WindowHandle.hwnd, &box); return box.bottom; } double Win32DisplayWindow::GetDpiScale() const { - return GetDpiForWindow(WindowHandle) / 96.0; + return GetDpiForWindow(WindowHandle.hwnd) / 96.0; } std::string Win32DisplayWindow::GetClipboardText() { - BOOL result = OpenClipboard(WindowHandle); + BOOL result = OpenClipboard(WindowHandle.hwnd); if (result == FALSE) throw std::runtime_error("Unable to open clipboard"); @@ -334,7 +324,7 @@ void Win32DisplayWindow::SetClipboardText(const std::string& text) { std::wstring text16 = to_utf16(text); - BOOL result = OpenClipboard(WindowHandle); + BOOL result = OpenClipboard(WindowHandle.hwnd); if (result == FALSE) throw std::runtime_error("Unable to open clipboard"); @@ -395,14 +385,14 @@ void Win32DisplayWindow::PresentBitmap(int width, int height, const uint32_t* pi if (dc != 0) { int result = SetDIBitsToDevice(dc, 0, 0, width, height, 0, 0, 0, height, pixels, (const BITMAPINFO*)&header, BI_RGB); - ReleaseDC(WindowHandle, dc); + ReleaseDC(WindowHandle.hwnd, dc); } } LRESULT Win32DisplayWindow::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lparam) { LPARAM result = 0; - if (DwmDefWindowProc(WindowHandle, msg, wparam, lparam, &result)) + if (DwmDefWindowProc(WindowHandle.hwnd, msg, wparam, lparam, &result)) return result; if (msg == WM_INPUT) @@ -427,16 +417,16 @@ LRESULT Win32DisplayWindow::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lpar } } } - return DefWindowProc(WindowHandle, msg, wparam, lparam); + return DefWindowProc(WindowHandle.hwnd, msg, wparam, lparam); } else if (msg == WM_PAINT) { PAINTSTRUCT paintStruct = {}; - PaintDC = BeginPaint(WindowHandle, &paintStruct); + PaintDC = BeginPaint(WindowHandle.hwnd, &paintStruct); if (PaintDC) { WindowHost->OnWindowPaint(); - EndPaint(WindowHandle, &paintStruct); + EndPaint(WindowHandle.hwnd, &paintStruct); PaintDC = 0; } return 0; @@ -445,17 +435,23 @@ LRESULT Win32DisplayWindow::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lpar { WindowHost->OnWindowActivated(); } + else if (msg == WM_MOUSEACTIVATE) + { + // We don't want to activate the window on mouse clicks as that changes the focus from the popup owner to the popup itself + if (PopupWindow) + return MA_NOACTIVATE; + } else if (msg == WM_MOUSEMOVE) { if (MouseLocked && GetFocus() != 0) { RECT box = {}; - GetClientRect(WindowHandle, &box); + GetClientRect(WindowHandle.hwnd, &box); POINT center = {}; center.x = box.right / 2; center.y = box.bottom / 2; - ClientToScreen(WindowHandle, ¢er); + ClientToScreen(WindowHandle.hwnd, ¢er); SetCursorPos(center.x, center.y); } @@ -468,7 +464,7 @@ LRESULT Win32DisplayWindow::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lpar { TRACKMOUSEEVENT eventTrack = {}; eventTrack.cbSize = sizeof(TRACKMOUSEEVENT); - eventTrack.hwndTrack = WindowHandle; + eventTrack.hwndTrack = WindowHandle.hwnd; eventTrack.dwFlags = TME_LEAVE; if (TrackMouseEvent(&eventTrack)) TrackMouseActive = true; @@ -526,7 +522,7 @@ LRESULT Win32DisplayWindow::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lpar POINT pos; pos.x = GET_X_LPARAM(lparam); pos.y = GET_Y_LPARAM(lparam); - ScreenToClient(WindowHandle, &pos); + ScreenToClient(WindowHandle.hwnd, &pos); WindowHost->OnWindowMouseWheel(Point(pos.x / dpiscale, pos.y / dpiscale), delta < 0.0 ? InputKey::MouseWheelDown : InputKey::MouseWheelUp); } @@ -573,7 +569,7 @@ LRESULT Win32DisplayWindow::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lpar return WVR_REDRAW; }*/ - return DefWindowProc(WindowHandle, msg, wparam, lparam); + return DefWindowProc(WindowHandle.hwnd, msg, wparam, lparam); } void Win32DisplayWindow::UpdateCursor() @@ -612,7 +608,7 @@ LRESULT Win32DisplayWindow::WndProc(HWND windowhandle, UINT msg, WPARAM wparam, { CREATESTRUCT* createstruct = (CREATESTRUCT*)lparam; Win32DisplayWindow* viewport = (Win32DisplayWindow*)createstruct->lpCreateParams; - viewport->WindowHandle = windowhandle; + viewport->WindowHandle.hwnd = windowhandle; SetWindowLongPtr(windowhandle, GWLP_USERDATA, (LONG_PTR)viewport); return viewport->OnWindowMessage(msg, wparam, lparam); } @@ -625,7 +621,7 @@ LRESULT Win32DisplayWindow::WndProc(HWND windowhandle, UINT msg, WPARAM wparam, if (msg == WM_DESTROY) { SetWindowLongPtr(windowhandle, GWLP_USERDATA, 0); - viewport->WindowHandle = 0; + viewport->WindowHandle.hwnd = 0; } return result; } @@ -677,6 +673,87 @@ Size Win32DisplayWindow::GetScreenSize() return Size(screenWidth / dpiScale, screenHeight / dpiScale); } +// This is to avoid needing all the Vulkan headers and the volk binding library just for this: +#ifndef VK_VERSION_1_0 + +#define VKAPI_CALL __stdcall +#define VKAPI_PTR VKAPI_CALL + +typedef uint32_t VkFlags; +typedef enum VkStructureType { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000, VK_OBJECT_TYPE_MAX_ENUM = 0x7FFFFFFF } VkStructureType; +typedef enum VkResult { VK_SUCCESS = 0, VK_RESULT_MAX_ENUM = 0x7FFFFFFF } VkResult; +typedef struct VkAllocationCallbacks VkAllocationCallbacks; + +typedef void (VKAPI_PTR* PFN_vkVoidFunction)(void); +typedef PFN_vkVoidFunction(VKAPI_PTR* PFN_vkGetInstanceProcAddr)(VkInstance instance, const char* pName); + +#ifndef VK_KHR_win32_surface + +typedef VkFlags VkWin32SurfaceCreateFlagsKHR; +typedef struct VkWin32SurfaceCreateInfoKHR +{ + VkStructureType sType; + const void* pNext; + VkWin32SurfaceCreateFlagsKHR flags; + HINSTANCE hinstance; + HWND hwnd; +} VkWin32SurfaceCreateInfoKHR; + +typedef VkResult(VKAPI_PTR* PFN_vkCreateWin32SurfaceKHR)(VkInstance instance, const VkWin32SurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface); + +#endif +#endif + +class ZWidgetVulkanLoader +{ +public: + ZWidgetVulkanLoader() + { + module = LoadLibraryA("vulkan-1.dll"); + if (!module) + throw std::runtime_error("Could not load vulkan-1.dll"); + + vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)(void(*)(void))GetProcAddress(module, "vkGetInstanceProcAddr"); + if (!vkGetInstanceProcAddr) + { + FreeLibrary(module); + throw std::runtime_error("vkGetInstanceProcAddr not found in vulkan-1.dll"); + } + } + + ~ZWidgetVulkanLoader() + { + FreeLibrary(module); + } + + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr; + HMODULE module = {}; +}; + +VkSurfaceKHR Win32DisplayWindow::CreateVulkanSurface(VkInstance instance) +{ + static ZWidgetVulkanLoader loader; + + auto vkCreateWin32SurfaceKHR = (PFN_vkCreateWin32SurfaceKHR)loader.vkGetInstanceProcAddr(instance, "vkCreateWin32SurfaceKHR"); + if (!vkCreateWin32SurfaceKHR) + throw std::runtime_error("Could not create vulkan surface"); + + VkWin32SurfaceCreateInfoKHR createInfo = { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR }; + createInfo.hwnd = WindowHandle.hwnd; + createInfo.hinstance = GetModuleHandle(nullptr); + + VkSurfaceKHR surface = {}; + VkResult result = vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface); + if (result != VK_SUCCESS) + throw std::runtime_error("Could not create vulkan surface"); + return surface; +} + +std::vector Win32DisplayWindow::GetVulkanInstanceExtensions() +{ + return { "VK_KHR_surface", "VK_KHR_win32_surface" }; +} + static void CALLBACK Win32TimerCallback(HWND handle, UINT message, UINT_PTR timerID, DWORD timestamp) { auto it = Win32DisplayWindow::Timers.find(timerID); diff --git a/libraries/ZWidget/src/window/win32/win32displaywindow.h b/libraries/ZWidget/src/window/win32/win32_display_window.h similarity index 85% rename from libraries/ZWidget/src/window/win32/win32displaywindow.h rename to libraries/ZWidget/src/window/win32/win32_display_window.h index 85ed92b64..7666274bd 100644 --- a/libraries/ZWidget/src/window/win32/win32displaywindow.h +++ b/libraries/ZWidget/src/window/win32/win32_display_window.h @@ -1,20 +1,16 @@ #pragma once -#define NOMINMAX -#define WIN32_MEAN_AND_LEAN -#ifndef WINVER -#define WINVER 0x0605 -#endif -#include +#include "win32_util.h" #include #include #include +#include class Win32DisplayWindow : public DisplayWindow { public: - Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner); + Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner, RenderAPI renderAPI); ~Win32DisplayWindow(); void SetWindowTitle(const std::string& text) override; @@ -25,6 +21,7 @@ public: void ShowMaximized() override; void ShowMinimized() override; void ShowNormal() override; + bool IsWindowFullscreen() override; void Hide() override; void Activate() override; void ShowCursor(bool enable) override; @@ -58,7 +55,10 @@ public: Point GetLParamPos(LPARAM lparam) const; - void* GetNativeHandle() override { return reinterpret_cast(WindowHandle); } + void* GetNativeHandle() override { return &WindowHandle; } + + std::vector GetVulkanInstanceExtensions() override; + VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override; static void ProcessEvents(); static void RunLoop(); @@ -78,8 +78,9 @@ public: static LRESULT CALLBACK WndProc(HWND windowhandle, UINT msg, WPARAM wparam, LPARAM lparam); DisplayWindowHost* WindowHost = nullptr; + bool PopupWindow = false; - HWND WindowHandle = 0; + Win32NativeHandle WindowHandle; bool Fullscreen = false; bool MouseLocked = false; diff --git a/libraries/ZWidget/src/window/win32/win32_open_file_dialog.cpp b/libraries/ZWidget/src/window/win32/win32_open_file_dialog.cpp new file mode 100644 index 000000000..092a78da1 --- /dev/null +++ b/libraries/ZWidget/src/window/win32/win32_open_file_dialog.cpp @@ -0,0 +1,227 @@ + +#include "win32_open_file_dialog.h" +#include "win32_display_window.h" +#include "core/widget.h" +#include +#include +#include +#include +#include + +Win32OpenFileDialog::Win32OpenFileDialog(Win32DisplayWindow* owner) : owner(owner) +{ +} + +bool Win32OpenFileDialog::Show() +{ + std::wstring title16 = to_utf16(title); + std::wstring initial_directory16 = to_utf16(initial_directory); + + HRESULT result; + ComPtr open_dialog; + + result = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, open_dialog.GetIID(), open_dialog.InitPtr()); + throw_if_failed(result, "CoCreateInstance(FileOpenDialog) failed"); + + result = open_dialog->SetTitle(title16.c_str()); + throw_if_failed(result, "IFileOpenDialog.SetTitle failed"); + + if (!initial_filename.empty()) + { + result = open_dialog->SetFileName(to_utf16(initial_filename).c_str()); + throw_if_failed(result, "IFileOpenDialog.SetFileName failed"); + } + + FILEOPENDIALOGOPTIONS options = FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST; + if (multi_select) + options |= FOS_ALLOWMULTISELECT; + result = open_dialog->SetOptions(options); + throw_if_failed(result, "IFileOpenDialog.SetOptions() failed"); + + if (!filters.empty()) + { + std::vector filterspecs(filters.size()); + std::vector descriptions(filters.size()); + std::vector extensions(filters.size()); + for (size_t i = 0; i < filters.size(); i++) + { + descriptions[i] = to_utf16(filters[i].description); + extensions[i] = to_utf16(filters[i].extension); + COMDLG_FILTERSPEC& spec = filterspecs[i]; + spec.pszName = descriptions[i].c_str(); + spec.pszSpec = extensions[i].c_str(); + } + result = open_dialog->SetFileTypes((UINT)filterspecs.size(), filterspecs.data()); + throw_if_failed(result, "IFileOpenDialog.SetFileTypes() failed"); + + if ((size_t)filterindex < filters.size()) + { + result = open_dialog->SetFileTypeIndex(filterindex); + throw_if_failed(result, "IFileOpenDialog.SetFileTypeIndex() failed"); + } + } + + if (!defaultext.empty()) + { + result = open_dialog->SetDefaultExtension(to_utf16(defaultext).c_str()); + throw_if_failed(result, "IFileOpenDialog.SetDefaultExtension() failed"); + } + + if (initial_directory16.length() > 0) + { + LPITEMIDLIST item_id_list = nullptr; + SFGAOF flags = 0; + result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags); + throw_if_failed(result, "SHParseDisplayName failed"); + + ComPtr folder_item; + result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr()); + ILFree(item_id_list); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + + /* This code requires Windows Vista or newer: + ComPtr folder_item; + result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr()); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + */ + + if (folder_item) + { + result = open_dialog->SetFolder(folder_item); + throw_if_failed(result, "IFileOpenDialog.SetFolder failed"); + } + } + + // For some reason this can hang deep inside Win32 if we do it on the calling thread! + { + bool done = false; + std::mutex mutex; + std::condition_variable condvar; + + std::thread thread([&]() { + + if (owner) + result = open_dialog->Show(owner->WindowHandle.hwnd); + else + result = open_dialog->Show(0); + + std::unique_lock lock(mutex); + done = true; + condvar.notify_all(); + + }); + + std::unique_lock lock(mutex); + while (!done) + { + DisplayBackend::Get()->ProcessEvents(); + using namespace std::chrono_literals; + condvar.wait_for(lock, 50ms, [&]() { return done; }); + } + lock.unlock(); + thread.join(); + } + + if (SUCCEEDED(result)) + { + ComPtr items; + result = open_dialog->GetResults(items.TypedInitPtr()); + throw_if_failed(result, "IFileOpenDialog.GetSelectedItems failed"); + + DWORD num_items = 0; + result = items->GetCount(&num_items); + throw_if_failed(result, "IShellItemArray.GetCount failed"); + + for (DWORD i = 0; i < num_items; i++) + { + ComPtr item; + result = items->GetItemAt(i, item.TypedInitPtr()); + throw_if_failed(result, "IShellItemArray.GetItemAt failed"); + + WCHAR* buffer = nullptr; + result = item->GetDisplayName(SIGDN_FILESYSPATH, &buffer); + throw_if_failed(result, "IShellItem.GetDisplayName failed"); + + std::wstring output16; + if (buffer) + { + try + { + output16 = buffer; + } + catch (...) + { + CoTaskMemFree(buffer); + throw; + } + } + + CoTaskMemFree(buffer); + filenames.push_back(from_utf16(output16)); + } + return true; + } + else + { + return false; + } +} + +std::string Win32OpenFileDialog::Filename() const +{ + return !filenames.empty() ? filenames.front() : std::string(); +} + +std::vector Win32OpenFileDialog::Filenames() const +{ + return filenames; +} + +void Win32OpenFileDialog::SetMultiSelect(bool new_multi_select) +{ + multi_select = new_multi_select; +} + +void Win32OpenFileDialog::SetFilename(const std::string& filename) +{ + initial_filename = filename; +} + +void Win32OpenFileDialog::AddFilter(const std::string& filter_description, const std::string& filter_extension) +{ + Filter f; + f.description = filter_description; + f.extension = filter_extension; + filters.push_back(std::move(f)); +} + +void Win32OpenFileDialog::ClearFilters() +{ + filters.clear(); +} + +void Win32OpenFileDialog::SetFilterIndex(int filter_index) +{ + filterindex = filter_index; +} + +void Win32OpenFileDialog::SetInitialDirectory(const std::string& path) +{ + initial_directory = path; +} + +void Win32OpenFileDialog::SetTitle(const std::string& newtitle) +{ + title = newtitle; +} + +void Win32OpenFileDialog::SetDefaultExtension(const std::string& extension) +{ + defaultext = extension; +} + +void Win32OpenFileDialog::throw_if_failed(HRESULT result, const std::string& error) +{ + if (FAILED(result)) + throw std::runtime_error(error); +} diff --git a/libraries/ZWidget/src/window/win32/win32_open_file_dialog.h b/libraries/ZWidget/src/window/win32/win32_open_file_dialog.h new file mode 100644 index 000000000..52ac858f0 --- /dev/null +++ b/libraries/ZWidget/src/window/win32/win32_open_file_dialog.h @@ -0,0 +1,44 @@ +#pragma once + +#include "systemdialogs/open_file_dialog.h" +#include "win32_util.h" + +class Win32DisplayWindow; + +class Win32OpenFileDialog : public OpenFileDialog +{ +public: + Win32OpenFileDialog(Win32DisplayWindow* owner); + + bool Show() override; + std::string Filename() const override; + std::vector Filenames() const override; + void SetMultiSelect(bool new_multi_select) override; + void SetFilename(const std::string& filename) override; + void AddFilter(const std::string& filter_description, const std::string& filter_extension) override; + void ClearFilters() override; + void SetFilterIndex(int filter_index) override; + void SetInitialDirectory(const std::string& path) override; + void SetTitle(const std::string& newtitle) override; + void SetDefaultExtension(const std::string& extension) override; + +private: + void throw_if_failed(HRESULT result, const std::string& error); + + Win32DisplayWindow* owner = nullptr; + + std::string initial_directory; + std::string initial_filename; + std::string title; + std::vector filenames; + bool multi_select = false; + + struct Filter + { + std::string description; + std::string extension; + }; + std::vector filters; + int filterindex = 0; + std::string defaultext; +}; diff --git a/libraries/ZWidget/src/window/win32/win32_open_folder_dialog.cpp b/libraries/ZWidget/src/window/win32/win32_open_folder_dialog.cpp new file mode 100644 index 000000000..f579f0ce7 --- /dev/null +++ b/libraries/ZWidget/src/window/win32/win32_open_folder_dialog.cpp @@ -0,0 +1,111 @@ + +#include "win32_open_folder_dialog.h" +#include "win32_display_window.h" +#include "core/widget.h" +#include + +Win32OpenFolderDialog::Win32OpenFolderDialog(Win32DisplayWindow* owner) : owner(owner) +{ +} + +bool Win32OpenFolderDialog::Show() +{ + std::wstring title16 = to_utf16(title); + std::wstring initial_directory16 = to_utf16(initial_directory); + + HRESULT result; + ComPtr open_dialog; + + result = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, open_dialog.GetIID(), open_dialog.InitPtr()); + throw_if_failed(result, "CoCreateInstance(FileOpenDialog) failed"); + + result = open_dialog->SetTitle(title16.c_str()); + throw_if_failed(result, "IFileOpenDialog.SetTitle failed"); + + result = open_dialog->SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST); + throw_if_failed(result, "IFileOpenDialog.SetOptions((FOS_PICKFOLDERS) failed"); + + if (initial_directory16.length() > 0) + { + LPITEMIDLIST item_id_list = nullptr; + SFGAOF flags = 0; + result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags); + throw_if_failed(result, "SHParseDisplayName failed"); + + ComPtr folder_item; + result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr()); + ILFree(item_id_list); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + + /* This code requires Windows Vista or newer: + ComPtr folder_item; + result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr()); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + */ + + if (folder_item) + { + result = open_dialog->SetFolder(folder_item); + throw_if_failed(result, "IFileOpenDialog.SetFolder failed"); + } + } + + if (owner) + result = open_dialog->Show(owner->WindowHandle.hwnd); + else + result = open_dialog->Show(0); + + if (SUCCEEDED(result)) + { + ComPtr chosen_folder; + result = open_dialog->GetResult(chosen_folder.TypedInitPtr()); + throw_if_failed(result, "IFileOpenDialog.GetResult failed"); + + WCHAR* buffer = nullptr; + result = chosen_folder->GetDisplayName(SIGDN_FILESYSPATH, &buffer); + throw_if_failed(result, "IShellItem.GetDisplayName failed"); + + std::wstring output_directory16; + if (buffer) + { + try + { + output_directory16 = buffer; + } + catch (...) + { + CoTaskMemFree(buffer); + throw; + } + } + + CoTaskMemFree(buffer); + selected_path = from_utf16(output_directory16); + return true; + } + else + { + return false; + } +} + +std::string Win32OpenFolderDialog::SelectedPath() const +{ + return selected_path; +} + +void Win32OpenFolderDialog::SetInitialDirectory(const std::string& path) +{ + initial_directory = path; +} + +void Win32OpenFolderDialog::SetTitle(const std::string& newtitle) +{ + title = newtitle; +} + +void Win32OpenFolderDialog::throw_if_failed(HRESULT result, const std::string& error) +{ + if (FAILED(result)) + throw std::runtime_error(error); +} diff --git a/libraries/ZWidget/src/window/win32/win32_open_folder_dialog.h b/libraries/ZWidget/src/window/win32/win32_open_folder_dialog.h new file mode 100644 index 000000000..45f05a26b --- /dev/null +++ b/libraries/ZWidget/src/window/win32/win32_open_folder_dialog.h @@ -0,0 +1,26 @@ +#pragma once + +#include "systemdialogs/open_folder_dialog.h" +#include "win32_util.h" + +class Win32DisplayWindow; + +class Win32OpenFolderDialog : public OpenFolderDialog +{ +public: + Win32OpenFolderDialog(Win32DisplayWindow* owner); + + bool Show() override; + std::string SelectedPath() const override; + void SetInitialDirectory(const std::string& path) override; + void SetTitle(const std::string& newtitle) override; + +private: + void throw_if_failed(HRESULT result, const std::string& error); + + Win32DisplayWindow* owner = nullptr; + + std::string selected_path; + std::string initial_directory; + std::string title; +}; diff --git a/libraries/ZWidget/src/window/win32/win32_save_file_dialog.cpp b/libraries/ZWidget/src/window/win32/win32_save_file_dialog.cpp new file mode 100644 index 000000000..02568f1ff --- /dev/null +++ b/libraries/ZWidget/src/window/win32/win32_save_file_dialog.cpp @@ -0,0 +1,174 @@ + +#include "win32_save_file_dialog.h" +#include "win32_display_window.h" +#include "core/widget.h" + +Win32SaveFileDialog::Win32SaveFileDialog(Win32DisplayWindow* owner) : owner(owner) +{ +} + +bool Win32SaveFileDialog::Show() +{ + std::wstring title16 = to_utf16(title); + std::wstring initial_directory16 = to_utf16(initial_directory); + + HRESULT result; + ComPtr save_dialog; + + result = CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_ALL, save_dialog.GetIID(), save_dialog.InitPtr()); + throw_if_failed(result, "CoCreateInstance(FileSaveDialog) failed"); + + result = save_dialog->SetTitle(title16.c_str()); + throw_if_failed(result, "IFileSaveDialog.SetTitle failed"); + + if (!initial_filename.empty()) + { + result = save_dialog->SetFileName(to_utf16(initial_filename).c_str()); + throw_if_failed(result, "IFileSaveDialog.SetFileName failed"); + } + + FILEOPENDIALOGOPTIONS options = FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST; + result = save_dialog->SetOptions(options); + throw_if_failed(result, "IFileSaveDialog.SetOptions() failed"); + + if (!filters.empty()) + { + std::vector filterspecs(filters.size()); + std::vector descriptions(filters.size()); + std::vector extensions(filters.size()); + for (size_t i = 0; i < filters.size(); i++) + { + descriptions[i] = to_utf16(filters[i].description); + extensions[i] = to_utf16(filters[i].extension); + COMDLG_FILTERSPEC& spec = filterspecs[i]; + spec.pszName = descriptions[i].c_str(); + spec.pszSpec = extensions[i].c_str(); + } + result = save_dialog->SetFileTypes((UINT)filterspecs.size(), filterspecs.data()); + throw_if_failed(result, "IFileOpenDialog.SetFileTypes() failed"); + + if ((size_t)filterindex < filters.size()) + { + result = save_dialog->SetFileTypeIndex(filterindex); + throw_if_failed(result, "IFileOpenDialog.SetFileTypeIndex() failed"); + } + } + + if (!defaultext.empty()) + { + result = save_dialog->SetDefaultExtension(to_utf16(defaultext).c_str()); + throw_if_failed(result, "IFileOpenDialog.SetDefaultExtension() failed"); + } + + if (initial_directory16.length() > 0) + { + LPITEMIDLIST item_id_list = nullptr; + SFGAOF flags = 0; + result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags); + throw_if_failed(result, "SHParseDisplayName failed"); + + ComPtr folder_item; + result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr()); + ILFree(item_id_list); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + + /* This code requires Windows Vista or newer: + ComPtr folder_item; + result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr()); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + */ + + if (folder_item) + { + result = save_dialog->SetFolder(folder_item); + throw_if_failed(result, "IFileSaveDialog.SetFolder failed"); + } + } + + if (owner) + result = save_dialog->Show(owner->WindowHandle.hwnd); + else + result = save_dialog->Show(0); + + if (SUCCEEDED(result)) + { + ComPtr chosen_folder; + result = save_dialog->GetResult(chosen_folder.TypedInitPtr()); + throw_if_failed(result, "IFileSaveDialog.GetResult failed"); + + WCHAR* buffer = nullptr; + result = chosen_folder->GetDisplayName(SIGDN_FILESYSPATH, &buffer); + throw_if_failed(result, "IShellItem.GetDisplayName failed"); + + std::wstring output16; + if (buffer) + { + try + { + output16 = buffer; + } + catch (...) + { + CoTaskMemFree(buffer); + throw; + } + } + + CoTaskMemFree(buffer); + filename = from_utf16(output16); + return true; + } + else + { + return false; + } +} + +std::string Win32SaveFileDialog::Filename() const +{ + return filename; +} + +void Win32SaveFileDialog::SetFilename(const std::string& filename) +{ + initial_filename = filename; +} + +void Win32SaveFileDialog::AddFilter(const std::string& filter_description, const std::string& filter_extension) +{ + Filter f; + f.description = filter_description; + f.extension = filter_extension; + filters.push_back(std::move(f)); +} + +void Win32SaveFileDialog::ClearFilters() +{ + filters.clear(); +} + +void Win32SaveFileDialog::SetFilterIndex(int filter_index) +{ + filterindex = filter_index; +} + +void Win32SaveFileDialog::SetInitialDirectory(const std::string& path) +{ + initial_directory = path; +} + +void Win32SaveFileDialog::SetTitle(const std::string& newtitle) +{ + title = newtitle; +} + +void Win32SaveFileDialog::SetDefaultExtension(const std::string& extension) +{ + defaultext = extension; +} + +void Win32SaveFileDialog::throw_if_failed(HRESULT result, const std::string& error) +{ + if (FAILED(result)) + throw std::runtime_error(error); +} diff --git a/libraries/ZWidget/src/window/win32/win32_save_file_dialog.h b/libraries/ZWidget/src/window/win32/win32_save_file_dialog.h new file mode 100644 index 000000000..5a5071423 --- /dev/null +++ b/libraries/ZWidget/src/window/win32/win32_save_file_dialog.h @@ -0,0 +1,42 @@ +#pragma once + +#include "systemdialogs/save_file_dialog.h" +#include "win32_util.h" + +class Win32DisplayWindow; + +class Win32SaveFileDialog : public SaveFileDialog +{ +public: + Win32SaveFileDialog(Win32DisplayWindow* owner); + + bool Show() override; + std::string Filename() const override; + void SetFilename(const std::string& filename) override; + void AddFilter(const std::string& filter_description, const std::string& filter_extension) override; + void ClearFilters() override; + void SetFilterIndex(int filter_index) override; + void SetInitialDirectory(const std::string& path) override; + void SetTitle(const std::string& newtitle) override; + void SetDefaultExtension(const std::string& extension) override; + +private: + void throw_if_failed(HRESULT result, const std::string& error); + + Win32DisplayWindow* owner = nullptr; + + std::string filename; + std::string initial_directory; + std::string initial_filename; + std::string title; + std::vector filenames; + + struct Filter + { + std::string description; + std::string extension; + }; + std::vector filters; + int filterindex = 0; + std::string defaultext; +}; diff --git a/libraries/ZWidget/src/window/win32/win32_util.h b/libraries/ZWidget/src/window/win32/win32_util.h new file mode 100644 index 000000000..06f89540e --- /dev/null +++ b/libraries/ZWidget/src/window/win32/win32_util.h @@ -0,0 +1,60 @@ +#pragma once + +#define NOMINMAX +#define WIN32_MEAN_AND_LEAN +#ifndef WINVER +#define WINVER 0x0605 +#endif +#include +#include +#include + +namespace +{ + static std::string from_utf16(const std::wstring& str) + { + if (str.empty()) return {}; + int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr); + if (needed == 0) + throw std::runtime_error("WideCharToMultiByte failed"); + std::string result; + result.resize(needed); + needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr); + if (needed == 0) + throw std::runtime_error("WideCharToMultiByte failed"); + return result; + } + + static std::wstring to_utf16(const std::string& str) + { + if (str.empty()) return {}; + int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + std::wstring result; + result.resize(needed); + needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size()); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + return result; + } + + template + class ComPtr + { + public: + ComPtr() { Ptr = nullptr; } + ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } + ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; } + ~ComPtr() { reset(); } + ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; } + void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; } + T* get() { return Ptr; } + static IID GetIID() { return __uuidof(T); } + void** InitPtr() { return (void**)TypedInitPtr(); } + T** TypedInitPtr() { reset(); return &Ptr; } + operator T* () const { return Ptr; } + T* operator ->() const { return Ptr; } + T* Ptr; + }; +} diff --git a/libraries/ZWidget/src/window/window.cpp b/libraries/ZWidget/src/window/window.cpp index 207a9540a..5fc35b230 100644 --- a/libraries/ZWidget/src/window/window.cpp +++ b/libraries/ZWidget/src/window/window.cpp @@ -1,120 +1,201 @@ #include "window/window.h" +#include "window/stub/stub_open_folder_dialog.h" +#include "window/stub/stub_open_file_dialog.h" +#include "window/stub/stub_save_file_dialog.h" +#include "window/sdl2nativehandle.h" +#include "core/widget.h" #include -#ifdef _WIN32 - -#include "win32/win32displaywindow.h" - -std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner) +std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) { - return std::make_unique(windowHost, popupWindow, static_cast(owner)); + return DisplayBackend::Get()->Create(windowHost, popupWindow, owner, renderAPI); } void DisplayWindow::ProcessEvents() { - Win32DisplayWindow::ProcessEvents(); + DisplayBackend::Get()->ProcessEvents(); } void DisplayWindow::RunLoop() { - Win32DisplayWindow::RunLoop(); + DisplayBackend::Get()->RunLoop(); } void DisplayWindow::ExitLoop() { - Win32DisplayWindow::ExitLoop(); -} - -Size DisplayWindow::GetScreenSize() -{ - return Win32DisplayWindow::GetScreenSize(); + DisplayBackend::Get()->ExitLoop(); } void* DisplayWindow::StartTimer(int timeoutMilliseconds, std::function onTimer) { - return Win32DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer)); + return DisplayBackend::Get()->StartTimer(timeoutMilliseconds, onTimer); } void DisplayWindow::StopTimer(void* timerID) { - Win32DisplayWindow::StopTimer(timerID); -} - -#elif defined(__APPLE__) - -std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner) -{ - throw std::runtime_error("DisplayWindow::Create not implemented"); -} - -void DisplayWindow::ProcessEvents() -{ - throw std::runtime_error("DisplayWindow::ProcessEvents not implemented"); -} - -void DisplayWindow::RunLoop() -{ - throw std::runtime_error("DisplayWindow::RunLoop not implemented"); -} - -void DisplayWindow::ExitLoop() -{ - throw std::runtime_error("DisplayWindow::ExitLoop not implemented"); + DisplayBackend::Get()->StopTimer(timerID); } Size DisplayWindow::GetScreenSize() { - throw std::runtime_error("DisplayWindow::GetScreenSize not implemented"); + return DisplayBackend::Get()->GetScreenSize(); } -void* DisplayWindow::StartTimer(int timeoutMilliseconds, std::function onTimer) +///////////////////////////////////////////////////////////////////////////// + +static std::unique_ptr& GetBackendVar() { - throw std::runtime_error("DisplayWindow::StartTimer not implemented"); + // In C++, static variables in functions are constructed on first encounter and is destructed in the reverse order when main() ends. + static std::unique_ptr p; + return p; } -void DisplayWindow::StopTimer(void* timerID) +DisplayBackend* DisplayBackend::Get() { - throw std::runtime_error("DisplayWindow::StopTimer not implemented"); + return GetBackendVar().get(); +} + +void DisplayBackend::Set(std::unique_ptr instance) +{ + GetBackendVar() = std::move(instance); +} + +std::unique_ptr DisplayBackend::CreateOpenFileDialog(DisplayWindow* owner) +{ + return std::make_unique(owner); +} + +std::unique_ptr DisplayBackend::CreateSaveFileDialog(DisplayWindow* owner) +{ + return std::make_unique(owner); +} + +std::unique_ptr DisplayBackend::CreateOpenFolderDialog(DisplayWindow* owner) +{ + return std::make_unique(owner); +} + +#ifdef _MSC_VER +#pragma warning(disable: 4996) // warning C4996 : 'getenv' : This function or variable may be unsafe.Consider using _dupenv_s instead.To disable deprecation, use _CRT_SECURE_NO_WARNINGS.See online help for details. +#endif + +std::unique_ptr DisplayBackend::TryCreateBackend() +{ + std::unique_ptr backend; + + // Check if there is an environment variable specified for the desired backend + const char* backendSelectionEnv = std::getenv("ZWIDGET_DISPLAY_BACKEND"); + if (backendSelectionEnv) + { + std::string backendSelectionStr(backendSelectionEnv); + if (backendSelectionStr == "Win32") + { + backend = TryCreateWin32(); + } + else if (backendSelectionStr == "X11") + { + backend = TryCreateX11(); + } + else if (backendSelectionStr == "SDL2") + { + backend = TryCreateSDL2(); + } + } + + if (!backend) + { + backend = TryCreateWin32(); + if (!backend) backend = TryCreateWayland(); + if (!backend) backend = TryCreateX11(); + if (!backend) backend = TryCreateSDL2(); + } + + return backend; +} + +#ifdef WIN32 + +#include "win32/win32_display_backend.h" + +std::unique_ptr DisplayBackend::TryCreateWin32() +{ + return std::make_unique(); } #else -#include "sdl2/sdl2displaywindow.h" - -std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner) +std::unique_ptr DisplayBackend::TryCreateWin32() { - return std::make_unique(windowHost, popupWindow, static_cast(owner)); + return nullptr; } -void DisplayWindow::ProcessEvents() +#endif + +#ifdef USE_SDL2 + +#include "sdl2/sdl2_display_backend.h" + +std::unique_ptr DisplayBackend::TryCreateSDL2() { - SDL2DisplayWindow::ProcessEvents(); + return std::make_unique(); } -void DisplayWindow::RunLoop() +#else + +std::unique_ptr DisplayBackend::TryCreateSDL2() { - SDL2DisplayWindow::RunLoop(); + return nullptr; } + +#endif + +#ifdef USE_X11 -void DisplayWindow::ExitLoop() +#include "x11/x11_display_backend.h" + +std::unique_ptr DisplayBackend::TryCreateX11() { - SDL2DisplayWindow::ExitLoop(); + try + { + return std::make_unique(); + } + catch (...) + { + return nullptr; + } } + +#else -Size DisplayWindow::GetScreenSize() +std::unique_ptr DisplayBackend::TryCreateX11() { - return SDL2DisplayWindow::GetScreenSize(); + return nullptr; } + +#endif -void* DisplayWindow::StartTimer(int timeoutMilliseconds, std::function onTimer) +#ifdef USE_WAYLAND + +#include "wayland/wayland_display_backend.h" + +std::unique_ptr DisplayBackend::TryCreateWayland() { - return SDL2DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer)); + try + { + return std::make_unique(); + } + catch (...) + { + return nullptr; + } } + +#else -void DisplayWindow::StopTimer(void* timerID) +std::unique_ptr DisplayBackend::TryCreateWayland() { - SDL2DisplayWindow::StopTimer(timerID); + return nullptr; } #endif diff --git a/libraries/ZWidget/src/window/x11/x11_display_backend.cpp b/libraries/ZWidget/src/window/x11/x11_display_backend.cpp new file mode 100644 index 000000000..b97760b3a --- /dev/null +++ b/libraries/ZWidget/src/window/x11/x11_display_backend.cpp @@ -0,0 +1,70 @@ + +#include "x11_display_backend.h" +#include "x11_display_window.h" + +#ifdef USE_DBUS +#include "window/dbus/dbus_open_file_dialog.h" +#include "window/dbus/dbus_save_file_dialog.h" +#include "window/dbus/dbus_open_folder_dialog.h" +#endif + +std::unique_ptr X11DisplayBackend::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) +{ + return std::make_unique(windowHost, popupWindow, static_cast(owner), renderAPI); +} + +void X11DisplayBackend::ProcessEvents() +{ + X11DisplayWindow::ProcessEvents(); +} + +void X11DisplayBackend::RunLoop() +{ + X11DisplayWindow::RunLoop(); +} + +void X11DisplayBackend::ExitLoop() +{ + X11DisplayWindow::ExitLoop(); +} + +Size X11DisplayBackend::GetScreenSize() +{ + return X11DisplayWindow::GetScreenSize(); +} + +void* X11DisplayBackend::StartTimer(int timeoutMilliseconds, std::function onTimer) +{ + return X11DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer)); +} + +void X11DisplayBackend::StopTimer(void* timerID) +{ + X11DisplayWindow::StopTimer(timerID); +} + +#ifdef USE_DBUS +std::unique_ptr X11DisplayBackend::CreateOpenFileDialog(DisplayWindow* owner) +{ + std::string ownerHandle; + if (owner) + ownerHandle = "x11:" + std::to_string(static_cast(owner)->window); + return std::make_unique(ownerHandle); +} + +std::unique_ptr X11DisplayBackend::CreateSaveFileDialog(DisplayWindow* owner) +{ + std::string ownerHandle; + if (owner) + ownerHandle = "x11:" + std::to_string(static_cast(owner)->window); + return std::make_unique(ownerHandle); +} + +std::unique_ptr X11DisplayBackend::CreateOpenFolderDialog(DisplayWindow* owner) +{ + std::string ownerHandle; + if (owner) + ownerHandle = "x11:" + std::to_string(static_cast(owner)->window); + return std::make_unique(ownerHandle); +} +#endif diff --git a/libraries/ZWidget/src/window/x11/x11_display_backend.h b/libraries/ZWidget/src/window/x11/x11_display_backend.h new file mode 100644 index 000000000..9d8046353 --- /dev/null +++ b/libraries/ZWidget/src/window/x11/x11_display_backend.h @@ -0,0 +1,25 @@ +#pragma once + +#include "window/window.h" + +class X11DisplayBackend : public DisplayBackend +{ +public: + std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) override; + void ProcessEvents() override; + void RunLoop() override; + void ExitLoop() override; + + void* StartTimer(int timeoutMilliseconds, std::function onTimer) override; + void StopTimer(void* timerID) override; + + Size GetScreenSize() override; + + bool IsX11() override { return true; } + +#ifdef USE_DBUS + std::unique_ptr CreateOpenFileDialog(DisplayWindow* owner) override; + std::unique_ptr CreateSaveFileDialog(DisplayWindow* owner) override; + std::unique_ptr CreateOpenFolderDialog(DisplayWindow* owner) override; +#endif +}; diff --git a/libraries/ZWidget/src/window/x11/x11_display_window.cpp b/libraries/ZWidget/src/window/x11/x11_display_window.cpp new file mode 100644 index 000000000..cd3fe2734 --- /dev/null +++ b/libraries/ZWidget/src/window/x11/x11_display_window.cpp @@ -0,0 +1,1161 @@ + +#include "x11_display_window.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class X11Connection +{ +public: + X11Connection() + { + // If we ever want to support windows on multiple threads: + // XInitThreads(); + + display = XOpenDisplay(nullptr); + if (!display) + throw std::runtime_error("Could not open X11 display"); + + // Make auto-repeat keys detectable + Bool supports_detectable_autorepeat = {}; + XkbSetDetectableAutoRepeat(display, True, &supports_detectable_autorepeat); + + // Loads the XMODIFIERS environment variable to see what IME to use + XSetLocaleModifiers(""); + xim = XOpenIM(display, 0, 0, 0); + if (!xim) + { + // fallback to internal input method + XSetLocaleModifiers("@im=none"); + xim = XOpenIM(display, 0, 0, 0); + } + } + + ~X11Connection() + { + for (auto& it : standardCursors) + XFreeCursor(display, it.second); + if (xim) + XCloseIM(xim); + XCloseDisplay(display); + } + + Display* display = nullptr; + std::map atoms; + std::map windows; + std::map standardCursors; + bool ExitRunLoop = false; + + XIM xim = nullptr; +}; + +static X11Connection* GetX11Connection() +{ + static X11Connection connection; + return &connection; +} + +static Atom GetAtom(const std::string& name) +{ + auto connection = GetX11Connection(); + auto it = connection->atoms.find(name); + if (it != connection->atoms.end()) + return it->second; + + Atom atom = XInternAtom(connection->display, name.c_str(), True); + connection->atoms[name] = atom; + return atom; +} + +X11DisplayWindow::X11DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, X11DisplayWindow* owner, RenderAPI renderAPI) : windowHost(windowHost), owner(owner) +{ + display = GetX11Connection()->display; + + screen = XDefaultScreen(display); + depth = XDefaultDepth(display, screen); + visual = XDefaultVisual(display, screen); + colormap = XDefaultColormap(display, screen); + + int disp_width_px = XDisplayWidth(display, screen); + int disp_height_px = XDisplayHeight(display, screen); + int disp_width_mm = XDisplayWidthMM(display, screen); + double ppi = (disp_width_mm < 24) ? 96.0 : (25.4 * static_cast(disp_width_px) / static_cast(disp_width_mm)); + dpiScale = std::round(ppi / 96.0 * 4.0) / 4.0; // 100%, 125%, 150%, 175%, 200%, etc. + + XSetWindowAttributes attributes = {}; + attributes.backing_store = Always; + attributes.override_redirect = popupWindow ? True : False; + attributes.save_under = popupWindow ? True : False; + attributes.colormap = colormap; + attributes.event_mask = + KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | + EnterWindowMask | LeaveWindowMask | PointerMotionMask | KeymapStateMask | + ExposureMask | StructureNotifyMask | FocusChangeMask | PropertyChangeMask; + + unsigned long mask = CWBackingStore | CWSaveUnder | CWEventMask | CWOverrideRedirect; + + window = XCreateWindow(display, XRootWindow(display, screen), 0, 0, 100, 100, 0, depth, InputOutput, visual, mask, &attributes); + GetX11Connection()->windows[window] = this; + + if (owner) + { + XSetTransientForHint(display, window, owner->window); + } + + // Tell window manager which process this window came from + if (GetAtom("_NET_WM_PID") != None) + { + int32_t pid = getpid(); + if (pid != 0) + { + XChangeProperty(display, window, GetAtom("_NET_WM_PID"), XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1); + } + } + + // Tell window manager which machine this window came from + if (GetAtom("WM_CLIENT_MACHINE") != None) + { + std::vector hostname(256); + if (gethostname(hostname.data(), hostname.size()) >= 0) + { + hostname.push_back(0); + XChangeProperty(display, window, GetAtom("WM_CLIENT_MACHINE"), XA_STRING, 8, PropModeReplace, (unsigned char *)hostname.data(), strlen(hostname.data())); + } + } + + // Tell window manager we want to listen to close events + if (GetAtom("WM_DELETE_WINDOW") != None) + { + Atom protocol = GetAtom("WM_DELETE_WINDOW"); + XSetWMProtocols(display, window, &protocol, 1); + } + + // Tell window manager what type of window we are + if (GetAtom("_NET_WM_WINDOW_TYPE") != None) + { + Atom type = None; + if (popupWindow) + { + type = GetAtom("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"); + if (type == None) + type = GetAtom("_NET_WM_WINDOW_TYPE_POPUP_MENU"); + if (type == None) + type = GetAtom("_NET_WM_WINDOW_TYPE_COMBO"); + } + if (type == None) + type = GetAtom("_NET_WM_WINDOW_TYPE_NORMAL"); + + if (type != None) + { + XChangeProperty(display, window, GetAtom("_NET_WM_WINDOW_TYPE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&type, 1); + } + } + + // Create input context + if (GetX11Connection()->xim) + { + xic = XCreateIC( + GetX11Connection()->xim, + XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, window, + XNFocusWindow, window, + nullptr); + } +} + +X11DisplayWindow::~X11DisplayWindow() +{ + if (hidden_cursor != None) + { + XFreeCursor(display, hidden_cursor); + XFreePixmap(display, cursor_bitmap); + } + + DestroyBackbuffer(); + XDestroyWindow(display, window); + GetX11Connection()->windows.erase(GetX11Connection()->windows.find(window)); +} + +void X11DisplayWindow::SetWindowTitle(const std::string& text) +{ + XSetStandardProperties(display, window, text.c_str(), text.c_str(), None, nullptr, 0, nullptr); +} + +void X11DisplayWindow::SetWindowFrame(const Rect& box) +{ + // To do: this requires cooperation with the window manager + + SetClientFrame(box); +} + +void X11DisplayWindow::SetClientFrame(const Rect& box) +{ + double dpiscale = GetDpiScale(); + int x = (int)std::round(box.x * dpiscale); + int y = (int)std::round(box.y * dpiscale); + int width = (int)std::round(box.width * dpiscale); + int height = (int)std::round(box.height * dpiscale); + + XWindowChanges changes = {}; + changes.x = x; + changes.y = y; + changes.width = width; + changes.height = height; + unsigned int mask = CWX | CWY | CWWidth | CWHeight; + + XConfigureWindow(display, window, mask, &changes); +} + +void X11DisplayWindow::Show() +{ + if (!isMapped) + { + XMapRaised(display, window); + isMapped = true; + } +} + +void X11DisplayWindow::ShowFullscreen() +{ + Show(); + + if (GetAtom("_NET_WM_STATE") != None && GetAtom("_NET_WM_STATE_FULLSCREEN") != None) + { + Atom state = GetAtom("_NET_WM_STATE_FULLSCREEN"); + XChangeProperty(display, window, GetAtom("_NET_WM_STATE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&state, 1); + isFullscreen = true; + } +} + +bool X11DisplayWindow::IsWindowFullscreen() +{ + return isFullscreen; +} + +void X11DisplayWindow::ShowMaximized() +{ + Show(); +} + +void X11DisplayWindow::ShowMinimized() +{ + if (!isMinimized) + { + Show(); // To do: can this be avoided? WMHints has an initial state that can make it show minimized + XIconifyWindow(display, window, screen); + isMinimized = true; + } +} + +void X11DisplayWindow::ShowNormal() +{ + Show(); + isFullscreen = false; +} + +void X11DisplayWindow::Hide() +{ + if (isMapped) + { + XUnmapWindow(display, window); + isMapped = false; + } +} + +void X11DisplayWindow::Activate() +{ + XRaiseWindow(display, window); +} + +void X11DisplayWindow::ShowCursor(bool enable) +{ + if (isCursorEnabled != enable) + { + isCursorEnabled = enable; + UpdateCursor(); + } +} + +void X11DisplayWindow::LockCursor() +{ +} + +void X11DisplayWindow::UnlockCursor() +{ +} + +void X11DisplayWindow::CaptureMouse() +{ +} + +void X11DisplayWindow::ReleaseMouseCapture() +{ +} + +void X11DisplayWindow::Update() +{ + needsUpdate = true; +} + +bool X11DisplayWindow::GetKeyState(InputKey key) +{ + auto it = keyState.find(key); + return it != keyState.end() ? it->second : false; +} + +void X11DisplayWindow::SetCursor(StandardCursor newcursor) +{ + if (cursor != newcursor) + { + cursor = newcursor; + UpdateCursor(); + } +} + +void X11DisplayWindow::UpdateCursor() +{ + if (isCursorEnabled) + { + Cursor& x11cursor = GetX11Connection()->standardCursors[cursor]; + if (x11cursor == None) + { + unsigned int index = XC_left_ptr; + switch (cursor) + { + default: + case StandardCursor::arrow: index = XC_left_ptr; break; + case StandardCursor::appstarting: index = XC_watch; break; + case StandardCursor::cross: index = XC_cross; break; + case StandardCursor::hand: index = XC_hand2; break; + case StandardCursor::ibeam: index = XC_xterm; break; + case StandardCursor::size_all: index = XC_fleur; break; + case StandardCursor::size_ns: index = XC_double_arrow; break; + case StandardCursor::size_we: index = XC_sb_h_double_arrow; break; + case StandardCursor::uparrow: index = XC_sb_up_arrow; break; + case StandardCursor::wait: index = XC_watch; break; + case StandardCursor::no: index = XC_X_cursor; break; + case StandardCursor::size_nesw: break; // To do: need to map this + case StandardCursor::size_nwse: break; + } + x11cursor = XCreateFontCursor(display, index); + } + XDefineCursor(display, window, x11cursor); + } + else + { + if (hidden_cursor == None) + { + char data[64] = {}; + XColor black_color = {}; + cursor_bitmap = XCreateBitmapFromData(display, window, data, 8, 8); + hidden_cursor = XCreatePixmapCursor(display, cursor_bitmap, cursor_bitmap, &black_color, &black_color, 0, 0); + } + XDefineCursor(display, window, hidden_cursor); + } +} + +Rect X11DisplayWindow::GetWindowFrame() const +{ + // To do: this needs to include the window manager frame + + double dpiscale = GetDpiScale(); + + Window root = {}; + int x = 0; + int y = 0; + unsigned int width = 0; + unsigned int height = 0; + unsigned int borderwidth = 0; + unsigned int depth = 0; + Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth); + + return Rect::xywh(x / dpiscale, y / dpiscale, width / dpiscale, height / dpiscale); +} + +Size X11DisplayWindow::GetClientSize() const +{ + double dpiscale = GetDpiScale(); + + Window root = {}; + int x = 0; + int y = 0; + unsigned int width = 0; + unsigned int height = 0; + unsigned int borderwidth = 0; + unsigned int depth = 0; + Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth); + + return Size(width / dpiscale, height / dpiscale); +} + +int X11DisplayWindow::GetPixelWidth() const +{ + Window root = {}; + int x = 0; + int y = 0; + unsigned int width = 0; + unsigned int height = 0; + unsigned int borderwidth = 0; + unsigned int depth = 0; + Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth); + return width; +} + +int X11DisplayWindow::GetPixelHeight() const +{ + Window root = {}; + int x = 0; + int y = 0; + unsigned int width = 0; + unsigned int height = 0; + unsigned int borderwidth = 0; + unsigned int depth = 0; + Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth); + return height; +} + +double X11DisplayWindow::GetDpiScale() const +{ + return dpiScale; +} + +void X11DisplayWindow::CreateBackbuffer(int width, int height) +{ + backbuffer.pixels = malloc(width * height * sizeof(uint32_t)); + backbuffer.image = XCreateImage(display, DefaultVisual(display, screen), depth, ZPixmap, 0, (char*)backbuffer.pixels, width, height, 32, 0); + backbuffer.pixmap = XCreatePixmap(display, window, width, height, depth); + backbuffer.width = width; + backbuffer.height = height; +} + +void X11DisplayWindow::DestroyBackbuffer() +{ + if (backbuffer.width > 0 && backbuffer.height > 0) + { + XDestroyImage(backbuffer.image); + XFreePixmap(display, backbuffer.pixmap); + backbuffer.width = 0; + backbuffer.height = 0; + backbuffer.pixmap = None; + backbuffer.image = nullptr; + backbuffer.pixels = nullptr; + } +} + +void X11DisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels) +{ + if (backbuffer.width != width || backbuffer.height != height) + { + DestroyBackbuffer(); + if (width > 0 && height > 0) + CreateBackbuffer(width, height); + } + + if (backbuffer.width == width && backbuffer.height == height) + { + memcpy(backbuffer.pixels, pixels, width * height * sizeof(uint32_t)); + GC gc = XDefaultGC(display, screen); + XPutImage(display, backbuffer.pixmap, gc, backbuffer.image, 0, 0, 0, 0, width, height); + XCopyArea(display, backbuffer.pixmap, window, gc, 0, 0, width, height, BlackPixel(display, screen), WhitePixel(display, screen)); + } +} + +void X11DisplayWindow::SetBorderColor(uint32_t bgra8) +{ +} + +void X11DisplayWindow::SetCaptionColor(uint32_t bgra8) +{ +} + +void X11DisplayWindow::SetCaptionTextColor(uint32_t bgra8) +{ +} + +std::vector X11DisplayWindow::GetWindowProperty(Atom property, Atom &actual_type, int &actual_format, unsigned long &item_count) +{ + long read_bytes = 0; + Atom _actual_type = actual_type; + int _actual_format = actual_format; + unsigned long _item_count = item_count; + unsigned long bytes_remaining = 0; + unsigned char *read_data = nullptr; + do + { + int result = XGetWindowProperty( + display, window, property, 0ul, read_bytes, + False, AnyPropertyType, &actual_type, &actual_format, + &_item_count, &bytes_remaining, &read_data); + if (result != Success) + { + actual_type = None; + actual_format = 0; + item_count = 0; + return {}; + } + } while (bytes_remaining > 0); + + item_count = _item_count; + if (!read_data) + return {}; + std::vector buffer(read_data, read_data + read_bytes); + XFree(read_data); + return buffer; +} + +std::string X11DisplayWindow::GetClipboardText() +{ + Atom clipboard = GetAtom("CLIPBOARD"); + if (clipboard == None) + return {}; + + XConvertSelection(display, clipboard, XA_STRING, clipboard, window, CurrentTime); + XFlush(display); + + // Wait 500 ms for a response + XEvent event = {}; + while (true) + { + if (XCheckTypedWindowEvent(display, window, SelectionNotify, &event)) + break; + if (!WaitForEvents(500)) + return {}; + } + + Atom type = None; + int format = 0; + unsigned long count = 0; + std::vector data = GetWindowProperty(clipboard, type, format, count); + if (type != XA_STRING || format != 8 || count <= 0 || data.empty()) + return {}; + + data.push_back(0); + return (char*)data.data(); +} + +void X11DisplayWindow::SetClipboardText(const std::string& text) +{ + clipboardText = text; + + Atom clipboard = GetAtom("CLIPBOARD"); + if (clipboard == None) + return; + + XSetSelectionOwner(display, XA_PRIMARY, window, CurrentTime); + XSetSelectionOwner(display, clipboard, window, CurrentTime); +} + +Point X11DisplayWindow::MapFromGlobal(const Point& pos) const +{ + double dpiscale = GetDpiScale(); + Window root = XRootWindow(display, screen); + Window child = {}; + int srcx = (int)std::round(pos.x * dpiscale); + int srcy = (int)std::round(pos.y * dpiscale); + int destx = 0; + int desty = 0; + Bool result = XTranslateCoordinates(display, root, window, srcx, srcy, &destx, &desty, &child); + return Point(destx / dpiscale, desty / dpiscale); +} + +Point X11DisplayWindow::MapToGlobal(const Point& pos) const +{ + double dpiscale = GetDpiScale(); + Window root = XRootWindow(display, screen); + Window child = {}; + int srcx = (int)std::round(pos.x * dpiscale); + int srcy = (int)std::round(pos.y * dpiscale); + int destx = 0; + int desty = 0; + Bool result = XTranslateCoordinates(display, window, root, srcx, srcy, &destx, &desty, &child); + return Point(destx / dpiscale, desty / dpiscale); +} + +void* X11DisplayWindow::GetNativeHandle() +{ + return reinterpret_cast(window); +} + +bool X11DisplayWindow::WaitForEvents(int timeout) +{ + Display* display = GetX11Connection()->display; + int fd = XConnectionNumber(display); + + struct timeval tv; + if (timeout > 0) + { + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) / 1000; + } + + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + int result = select(fd + 1, &rfds, nullptr, nullptr, timeout >= 0 ? &tv : nullptr); + return result > 0 && FD_ISSET(fd, &rfds); +} + +void X11DisplayWindow::CheckNeedsUpdate() +{ + for (auto& it : GetX11Connection()->windows) + { + if (it.second->needsUpdate) + { + it.second->needsUpdate = false; + it.second->windowHost->OnWindowPaint(); + } + } +} + +void X11DisplayWindow::ProcessEvents() +{ + CheckNeedsUpdate(); + Display* display = GetX11Connection()->display; + while (XPending(display) > 0) + { + XEvent event = {}; + XNextEvent(display, &event); + DispatchEvent(&event); + } +} + +void X11DisplayWindow::RunLoop() +{ + X11Connection* connection = GetX11Connection(); + connection->ExitRunLoop = false; + while (!connection->ExitRunLoop && !connection->windows.empty()) + { + CheckNeedsUpdate(); + XEvent event = {}; + XNextEvent(connection->display, &event); + DispatchEvent(&event); + } +} + +void X11DisplayWindow::ExitLoop() +{ + X11Connection* connection = GetX11Connection(); + connection->ExitRunLoop = true; +} + +void X11DisplayWindow::DispatchEvent(XEvent* event) +{ + X11Connection* connection = GetX11Connection(); + auto it = connection->windows.find(event->xany.window); + if (it != connection->windows.end()) + { + X11DisplayWindow* window = it->second; + window->OnEvent(event); + } +} + +void X11DisplayWindow::OnEvent(XEvent* event) +{ + if (event->type == ConfigureNotify) + OnConfigureNotify(event); + else if (event->type == ClientMessage) + OnClientMessage(event); + else if (event->type == Expose) + OnExpose(event); + else if (event->type == FocusIn) + OnFocusIn(event); + else if (event->type == FocusOut) + OnFocusOut(event); + else if (event->type == PropertyNotify) + OnPropertyNotify(event); + else if (event->type == KeyPress) + OnKeyPress(event); + else if (event->type == KeyRelease) + OnKeyRelease(event); + else if (event->type == ButtonPress) + OnButtonPress(event); + else if (event->type == ButtonRelease) + OnButtonRelease(event); + else if (event->type == MotionNotify) + OnMotionNotify(event); + else if (event->type == LeaveNotify) + OnLeaveNotify(event); + else if (event->type == SelectionClear) + OnSelectionClear(event); + else if (event->type == SelectionNotify) + OnSelectionNotify(event); + else if (event->type == SelectionRequest) + OnSelectionRequest(event); +} + +void X11DisplayWindow::OnConfigureNotify(XEvent* event) +{ + windowHost->OnWindowGeometryChanged(); +} + +void X11DisplayWindow::OnClientMessage(XEvent* event) +{ + Atom protocolsAtom = GetAtom("WM_PROTOCOLS"); + if (protocolsAtom != None && event->xclient.message_type == protocolsAtom) + { + Atom deleteAtom = GetAtom("WM_DELETE_WINDOW"); + Atom pingAtom = GetAtom("_NET_WM_PING"); + + Atom protocol = event->xclient.data.l[0]; + if (deleteAtom != None && protocol == deleteAtom) + { + windowHost->OnWindowClose(); + } + else if (pingAtom != None && protocol == pingAtom) + { + XSendEvent(display, RootWindow(display, screen), False, SubstructureNotifyMask | SubstructureRedirectMask, event); + } + } +} + +void X11DisplayWindow::OnExpose(XEvent* event) +{ + windowHost->OnWindowPaint(); +} + +void X11DisplayWindow::OnFocusIn(XEvent* event) +{ + if (xic) + XSetICFocus(xic); + + windowHost->OnWindowActivated(); +} + +void X11DisplayWindow::OnFocusOut(XEvent* event) +{ + windowHost->OnWindowDeactivated(); +} + +void X11DisplayWindow::OnPropertyNotify(XEvent* event) +{ + // Sent when window is minimized, maximized, etc. +} + +InputKey X11DisplayWindow::GetInputKey(XEvent* event) +{ + if (event->type == KeyPress || event->type == KeyRelease) + { + KeySym keysymbol = XkbKeycodeToKeysym(display, event->xkey.keycode, 0, 0); + switch (keysymbol) + { + case XK_BackSpace: return InputKey::Backspace; + case XK_Tab: return InputKey::Tab; + case XK_Return: return InputKey::Enter; + // To do: should we merge them or not? Windows merges them + case XK_Shift_L: return InputKey::Shift; // InputKey::LShift + case XK_Shift_R: return InputKey::Shift; // InputKey::RShift + case XK_Control_L: return InputKey::Ctrl; // InputKey::LControl + case XK_Control_R: return InputKey::Ctrl; // InputKey::RControl + case XK_Meta_L: return InputKey::Alt; + case XK_Meta_R: return InputKey::Alt; + case XK_Pause: return InputKey::Pause; + case XK_Caps_Lock: return InputKey::CapsLock; + case XK_Escape: return InputKey::Escape; + case XK_space: return InputKey::Space; + case XK_Page_Up: return InputKey::PageUp; + case XK_Page_Down: return InputKey::PageDown; + case XK_End: return InputKey::End; + case XK_Home: return InputKey::Home; + case XK_Left: return InputKey::Left; + case XK_Up: return InputKey::Up; + case XK_Right: return InputKey::Right; + case XK_Down: return InputKey::Select; + case XK_Print: return InputKey::Print; + case XK_Execute: return InputKey::Execute; + // case XK_Print_Screen: return InputKey::PrintScrn; + case XK_Insert: return InputKey::Insert; + case XK_Delete: return InputKey::Delete; + case XK_Help: return InputKey::Help; + case XK_0: return InputKey::_0; + case XK_1: return InputKey::_1; + case XK_2: return InputKey::_2; + case XK_3: return InputKey::_3; + case XK_4: return InputKey::_4; + case XK_5: return InputKey::_5; + case XK_6: return InputKey::_6; + case XK_7: return InputKey::_7; + case XK_8: return InputKey::_8; + case XK_9: return InputKey::_9; + case XK_A: return InputKey::A; + case XK_B: return InputKey::B; + case XK_C: return InputKey::C; + case XK_D: return InputKey::D; + case XK_E: return InputKey::E; + case XK_F: return InputKey::F; + case XK_G: return InputKey::G; + case XK_H: return InputKey::H; + case XK_I: return InputKey::I; + case XK_J: return InputKey::J; + case XK_K: return InputKey::K; + case XK_L: return InputKey::L; + case XK_M: return InputKey::M; + case XK_N: return InputKey::N; + case XK_O: return InputKey::O; + case XK_P: return InputKey::P; + case XK_Q: return InputKey::Q; + case XK_R: return InputKey::R; + case XK_S: return InputKey::S; + case XK_T: return InputKey::T; + case XK_U: return InputKey::U; + case XK_V: return InputKey::V; + case XK_W: return InputKey::W; + case XK_X: return InputKey::X; + case XK_Y: return InputKey::Y; + case XK_Z: return InputKey::Z; + case XK_KP_0: return InputKey::NumPad0; + case XK_KP_1: return InputKey::NumPad1; + case XK_KP_2: return InputKey::NumPad2; + case XK_KP_3: return InputKey::NumPad3; + case XK_KP_4: return InputKey::NumPad4; + case XK_KP_5: return InputKey::NumPad5; + case XK_KP_6: return InputKey::NumPad6; + case XK_KP_7: return InputKey::NumPad7; + case XK_KP_8: return InputKey::NumPad8; + case XK_KP_9: return InputKey::NumPad9; + case XK_KP_Multiply: return InputKey::GreyStar; + case XK_KP_Add: return InputKey::GreyPlus; + case XK_KP_Separator: return InputKey::Separator; + case XK_KP_Subtract: return InputKey::GreyMinus; + case XK_KP_Decimal: return InputKey::NumPadPeriod; + case XK_KP_Divide: return InputKey::GreySlash; + case XK_F1: return InputKey::F1; + case XK_F2: return InputKey::F2; + case XK_F3: return InputKey::F3; + case XK_F4: return InputKey::F4; + case XK_F5: return InputKey::F5; + case XK_F6: return InputKey::F6; + case XK_F7: return InputKey::F7; + case XK_F8: return InputKey::F8; + case XK_F9: return InputKey::F9; + case XK_F10: return InputKey::F10; + case XK_F11: return InputKey::F11; + case XK_F12: return InputKey::F12; + case XK_F13: return InputKey::F13; + case XK_F14: return InputKey::F14; + case XK_F15: return InputKey::F15; + case XK_F16: return InputKey::F16; + case XK_F17: return InputKey::F17; + case XK_F18: return InputKey::F18; + case XK_F19: return InputKey::F19; + case XK_F20: return InputKey::F20; + case XK_F21: return InputKey::F21; + case XK_F22: return InputKey::F22; + case XK_F23: return InputKey::F23; + case XK_F24: return InputKey::F24; + case XK_Num_Lock: return InputKey::NumLock; + case XK_Scroll_Lock: return InputKey::ScrollLock; + case XK_semicolon: return InputKey::Semicolon; + case XK_equal: return InputKey::Equals; + case XK_comma: return InputKey::Comma; + case XK_minus: return InputKey::Minus; + case XK_period: return InputKey::Period; + case XK_slash: return InputKey::Slash; + case XK_dead_tilde: return InputKey::Tilde; + case XK_bracketleft: return InputKey::LeftBracket; + case XK_backslash: return InputKey::Backslash; + case XK_bracketright: return InputKey::RightBracket; + case XK_apostrophe: return InputKey::SingleQuote; + default: return (InputKey)(((uint32_t)keysymbol) << 8); + } + } + else if (event->type == ButtonPress || event->type == ButtonRelease) + { + switch (event->xbutton.button) + { + case 1: return InputKey::LeftMouse; + case 2: return InputKey::MiddleMouse; + case 3: return InputKey::RightMouse; + case 4: return InputKey::MouseWheelUp; + case 5: return InputKey::MouseWheelDown; + // case 6: return InputKey::XButton1; + // case 7: return InputKey::XButton2; + default: break; + } + } + return {}; +} + +Point X11DisplayWindow::GetMousePos(XEvent* event) +{ + double dpiScale = GetDpiScale(); + int x = event->xbutton.x; + int y = event->xbutton.y; + return Point(x / dpiScale, y / dpiScale); +} + +void X11DisplayWindow::OnKeyPress(XEvent* event) +{ + // If we ever want to track keypress repeat: + // char keyboard_state[32]; + // XQueryKeymap(display, keyboard_state); + // unsigned int keycode = event->xkey.keycode; + // bool isrepeat = event->type == KeyPress && keyboard_state[keycode / 8] & (1 << keycode % 8); + + InputKey key = GetInputKey(event); + keyState[key] = true; + windowHost->OnWindowKeyDown(key); + + std::string text; + if (xic) // utf-8 text input + { + Status status = {}; + KeySym keysym = NoSymbol; + char buffer[32] = {}; + Xutf8LookupString(xic, &event->xkey, buffer, sizeof(buffer) - 1, &keysym, &status); + if (status == XLookupChars || status == XLookupBoth) + text = buffer; + } + else // latin-1 input fallback + { + const int buff_size = 16; + char buff[buff_size]; + int result = XLookupString(&event->xkey, buff, buff_size - 1, nullptr, nullptr); + if (result < 0) result = 0; + if (result > (buff_size - 1)) result = buff_size - 1; + buff[result] = 0; + text = std::string(buff, result); + + // Lazy way to convert to utf-8 + for (char& c : text) + { + if (c < 0) + c = '?'; + } + } + + if (!text.empty()) + windowHost->OnWindowKeyChar(std::move(text)); +} + +void X11DisplayWindow::OnKeyRelease(XEvent* event) +{ + InputKey key = GetInputKey(event); + keyState[key] = false; + windowHost->OnWindowKeyUp(key); +} + +void X11DisplayWindow::OnButtonPress(XEvent* event) +{ + InputKey key = GetInputKey(event); + keyState[key] = true; + windowHost->OnWindowMouseDown(GetMousePos(event), key); + // if (lastClickWithin400ms) + // windowHost->OnWindowMouseDoubleclick(GetMousePos(event), InputKey::LeftMouse); +} + +void X11DisplayWindow::OnButtonRelease(XEvent* event) +{ + InputKey key = GetInputKey(event); + keyState[key] = false; + windowHost->OnWindowMouseUp(GetMousePos(event), key); +} + +void X11DisplayWindow::OnMotionNotify(XEvent* event) +{ + double dpiScale = GetDpiScale(); + int x = event->xmotion.x; + int y = event->xmotion.y; + windowHost->OnWindowMouseMove(Point(x / dpiScale, y / dpiScale)); +} + +void X11DisplayWindow::OnLeaveNotify(XEvent* event) +{ + windowHost->OnWindowMouseLeave(); +} + +void X11DisplayWindow::OnSelectionClear(XEvent* event) +{ + clipboardText.clear(); +} + +void X11DisplayWindow::OnSelectionNotify(XEvent* event) +{ + // This is handled in GetClipboardText +} + +void X11DisplayWindow::OnSelectionRequest(XEvent* event) +{ + Atom requestor = event->xselectionrequest.requestor; + if (requestor == window) + return; + + Atom targetsAtom = GetAtom("TARGETS"); + Atom multipleAtom = GetAtom("MULTIPLE"); + + struct Request { Window target; Atom property; }; + std::vector requests; + + if (event->xselectionrequest.target == multipleAtom) + { + Atom actualType = None; + int actualFormat = 0; + unsigned long itemCount = 0; + std::vector data = GetWindowProperty(requestor, actualType, actualFormat, itemCount); + if (data.size() < itemCount * sizeof(Atom)) + return; + + Atom* atoms = (Atom*)data.data(); + for (unsigned long i = 0; i + 1 < itemCount; i += 2) + { + requests.push_back({ atoms[i], atoms[i + 1]}); + } + } + else + { + requests.push_back({ event->xselectionrequest.target, event->xselectionrequest.property }); + } + + for (const Request& request : requests) + { + Window xtarget = request.target; + Atom xproperty = request.property; + + XEvent response = {}; + response.xselection.type = SelectionNotify; + response.xselection.display = event->xselectionrequest.display; + response.xselection.requestor = event->xselectionrequest.requestor; + response.xselection.selection = event->xselectionrequest.selection; + response.xselection.target = event->xselectionrequest.target; + response.xselection.property = xproperty; + response.xselection.time = event->xselectionrequest.time; + + if (xtarget == targetsAtom) + { + Atom newTargets = XA_STRING; + XChangeProperty(display, requestor, xproperty, targetsAtom, 32, PropModeReplace, (unsigned char *)&newTargets, 1); + } + else if (xtarget == XA_STRING) + { + XChangeProperty(display, requestor, xproperty, xtarget, 8, PropModeReplace, (const unsigned char*)clipboardText.c_str(), clipboardText.size()); + } + else + { + response.xselection.property = None; // Is this correct? + } + + XSendEvent(display, requestor, False, 0, &response); + } +} + +Size X11DisplayWindow::GetScreenSize() +{ + X11Connection* connection = GetX11Connection(); + Display* display = connection->display; + int screen = XDefaultScreen(display); + + int disp_width_px = XDisplayWidth(display, screen); + int disp_height_px = XDisplayHeight(display, screen); + int disp_width_mm = XDisplayWidthMM(display, screen); + double ppi = (disp_width_mm < 24) ? 96.0 : (25.4 * static_cast(disp_width_px) / static_cast(disp_width_mm)); + double dpiScale = ppi / 96.0; + + return Size(disp_width_px / dpiScale, disp_height_px / dpiScale); +} + +void* X11DisplayWindow::StartTimer(int timeoutMilliseconds, std::function onTimer) +{ + return nullptr; +} + +void X11DisplayWindow::StopTimer(void* timerID) +{ +} + +// This is to avoid needing all the Vulkan headers and the volk binding library just for this: +#ifndef VK_VERSION_1_0 + +#define VKAPI_CALL +#define VKAPI_PTR VKAPI_CALL + +typedef uint32_t VkFlags; +typedef enum VkStructureType { VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR = 1000004000, VK_OBJECT_TYPE_MAX_ENUM = 0x7FFFFFFF } VkStructureType; +typedef enum VkResult { VK_SUCCESS = 0, VK_RESULT_MAX_ENUM = 0x7FFFFFFF } VkResult; +typedef struct VkAllocationCallbacks VkAllocationCallbacks; + +typedef void (VKAPI_PTR* PFN_vkVoidFunction)(void); +typedef PFN_vkVoidFunction(VKAPI_PTR* PFN_vkGetInstanceProcAddr)(VkInstance instance, const char* pName); + +#ifndef VK_KHR_xlib_surface + +typedef VkFlags VkXlibSurfaceCreateFlagsKHR; +typedef struct VkXlibSurfaceCreateInfoKHR +{ + VkStructureType sType; + const void* pNext; + VkXlibSurfaceCreateFlagsKHR flags; + Display* dpy; + Window window; +} VkXlibSurfaceCreateInfoKHR; + +typedef VkResult(VKAPI_PTR* PFN_vkCreateXlibSurfaceKHR)(VkInstance instance, const VkXlibSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface); + +#endif +#endif + +class ZWidgetX11VulkanLoader +{ +public: + ZWidgetX11VulkanLoader() + { +#if defined(__APPLE__) + module = dlopen("libvulkan.dylib", RTLD_NOW | RTLD_LOCAL); + if (!module) + module = dlopen("libvulkan.1.dylib", RTLD_NOW | RTLD_LOCAL); + if (!module) + module = dlopen("libMoltenVK.dylib", RTLD_NOW | RTLD_LOCAL); +#else + module = dlopen("libvulkan.so.1", RTLD_NOW | RTLD_LOCAL); + if (!module) + module = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL); +#endif + + if (!module) + throw std::runtime_error("Could not load vulkan"); + + vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym(module, "vkGetInstanceProcAddr"); + if (!vkGetInstanceProcAddr) + { + dlclose(module); + throw std::runtime_error("vkGetInstanceProcAddr not found"); + } + } + + ~ZWidgetX11VulkanLoader() + { + dlclose(module); + } + + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr; + void* module = nullptr; +}; + +VkSurfaceKHR X11DisplayWindow::CreateVulkanSurface(VkInstance instance) +{ + static ZWidgetX11VulkanLoader loader; + + auto vkCreateXlibSurfaceKHR = (PFN_vkCreateXlibSurfaceKHR)loader.vkGetInstanceProcAddr(instance, "vkCreateXlibSurfaceKHR"); + if (!vkCreateXlibSurfaceKHR) + throw std::runtime_error("Could not create vulkan surface"); + + VkXlibSurfaceCreateInfoKHR createInfo = { VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR }; + createInfo.dpy = display; + createInfo.window = window; + + VkSurfaceKHR surface = {}; + VkResult result = vkCreateXlibSurfaceKHR(instance, &createInfo, nullptr, &surface); + if (result != VK_SUCCESS) + throw std::runtime_error("Could not create vulkan surface"); + return surface; +} + +std::vector X11DisplayWindow::GetVulkanInstanceExtensions() +{ + return { "VK_KHR_surface", "VK_KHR_xlib_surface" }; +} diff --git a/libraries/ZWidget/src/window/x11/x11_display_window.h b/libraries/ZWidget/src/window/x11/x11_display_window.h new file mode 100644 index 000000000..bd65d8d1c --- /dev/null +++ b/libraries/ZWidget/src/window/x11/x11_display_window.h @@ -0,0 +1,137 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +class X11DisplayWindow : public DisplayWindow +{ +public: + X11DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, X11DisplayWindow* owner, RenderAPI renderAPI); + ~X11DisplayWindow(); + + void SetWindowTitle(const std::string& text) override; + void SetWindowFrame(const Rect& box) override; + void SetClientFrame(const Rect& box) override; + void Show() override; + void ShowFullscreen() override; + void ShowMaximized() override; + void ShowMinimized() override; + void ShowNormal() override; + bool IsWindowFullscreen() override; + void Hide() override; + void Activate() override; + void ShowCursor(bool enable) override; + void LockCursor() override; + void UnlockCursor() override; + void CaptureMouse() override; + void ReleaseMouseCapture() override; + void Update() override; + bool GetKeyState(InputKey key) override; + + void SetCursor(StandardCursor cursor) override; + + Rect GetWindowFrame() const override; + Size GetClientSize() const override; + int GetPixelWidth() const override; + int GetPixelHeight() const override; + double GetDpiScale() const override; + + void PresentBitmap(int width, int height, const uint32_t* pixels) override; + + void SetBorderColor(uint32_t bgra8) override; + void SetCaptionColor(uint32_t bgra8) override; + void SetCaptionTextColor(uint32_t bgra8) override; + + std::string GetClipboardText() override; + void SetClipboardText(const std::string& text) override; + + Point MapFromGlobal(const Point& pos) const override; + Point MapToGlobal(const Point& pos) const override; + + void* GetNativeHandle() override; + + std::vector GetVulkanInstanceExtensions() override; + VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override; + + static void ProcessEvents(); + static void RunLoop(); + static void ExitLoop(); + static Size GetScreenSize(); + static void* StartTimer(int timeoutMilliseconds, std::function onTimer); + static void StopTimer(void* timerID); + +private: + void UpdateCursor(); + + void OnEvent(XEvent* event); + void OnConfigureNotify(XEvent* event); + void OnClientMessage(XEvent* event); + void OnExpose(XEvent* event); + void OnFocusIn(XEvent* event); + void OnFocusOut(XEvent* event); + void OnPropertyNotify(XEvent* event); + void OnKeyPress(XEvent* event); + void OnKeyRelease(XEvent* event); + void OnButtonPress(XEvent* event); + void OnButtonRelease(XEvent* event); + void OnMotionNotify(XEvent* event); + void OnLeaveNotify(XEvent* event); + void OnSelectionClear(XEvent* event); + void OnSelectionNotify(XEvent* event); + void OnSelectionRequest(XEvent* event); + + void CreateBackbuffer(int width, int height); + void DestroyBackbuffer(); + + InputKey GetInputKey(XEvent* event); + Point GetMousePos(XEvent* event); + + static bool WaitForEvents(int timeout); + static void DispatchEvent(XEvent* event); + + static void CheckNeedsUpdate(); + + std::vector GetWindowProperty(Atom property, Atom &actual_type, int &actual_format, unsigned long &item_count); + + DisplayWindowHost* windowHost = nullptr; + X11DisplayWindow* owner = nullptr; + Display* display = nullptr; + Window window = {}; + int screen = 0; + int depth = 0; + Visual* visual = nullptr; + Colormap colormap = {}; + XIC xic = nullptr; + StandardCursor cursor = {}; + bool isCursorEnabled = true; + bool isMapped = false; + bool isMinimized = false; + bool isFullscreen = false; + double dpiScale = 1.0; + + Pixmap cursor_bitmap = None; + Cursor hidden_cursor = None; + + std::map keyState; + + std::string clipboardText; + + struct + { + Pixmap pixmap = None; + XImage* image = nullptr; + void* pixels = nullptr; + int width = 0; + int height = 0; + } backbuffer; + + bool needsUpdate = false; + + friend class X11DisplayBackend; +}; diff --git a/libraries/ZWidget/src/window/ztimer/ztimer.cpp b/libraries/ZWidget/src/window/ztimer/ztimer.cpp new file mode 100644 index 000000000..23e82d10a --- /dev/null +++ b/libraries/ZWidget/src/window/ztimer/ztimer.cpp @@ -0,0 +1,73 @@ +#include "ztimer.h" + +ZTimer::ZTimer() +{ +} + +ZTimer::ZTimer(Duration duration_ms) : m_timerDuration(duration_ms) +{ +} + +ZTimer::ZTimer(Duration duration_ms, CallbackFunc callback) + : m_timerDuration(duration_ms), m_callback(callback) +{ +} + +ZTimer::~ZTimer() +{ + Stop(); +} + +void ZTimer::Start() +{ + m_startTime = Clock::now(); + m_currentTime = m_startTime; + m_timerStarted = true; + m_timerFinished = false; +} + +void ZTimer::Stop() +{ + m_timerStarted = false; +} + +void ZTimer::SetDuration(Duration duration_ms) +{ + if (m_timerStarted) + return; + m_timerDuration = duration_ms; +} + +void ZTimer::SetCallback(std::function callback) +{ + if (m_timerStarted) + return; + m_callback = callback; +} + +void ZTimer::SetRepeating(bool value) +{ + if (m_timerStarted) + return; + m_repeatingTimer = value; +} + +void ZTimer::Update(Duration deltaTime) +{ + if (!m_timerStarted) + return; + + m_currentTime += deltaTime; + + if (m_currentTime >= m_startTime + m_timerDuration) + { + m_callback(); + if (!m_repeatingTimer) + { + Stop(); + m_timerFinished = true; + } + else + Start(); + } +} diff --git a/libraries/ZWidget/src/window/ztimer/ztimer.h b/libraries/ZWidget/src/window/ztimer/ztimer.h new file mode 100644 index 000000000..e72489884 --- /dev/null +++ b/libraries/ZWidget/src/window/ztimer/ztimer.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +// ZTimer: A small, independent timer +// Useful for implementing timed events on your backends +class ZTimer +{ +public: + using Duration = std::chrono::duration; + using Clock = std::chrono::system_clock; + using TimePoint = std::chrono::time_point; + using CallbackFunc = std::function; + + ZTimer(); + ZTimer(Duration duration_ms); + ZTimer(Duration duration_ms, CallbackFunc callback); + + ~ZTimer(); + + void Start(); + void Stop(); + void SetDuration(Duration duration_ms); + void SetCallback(CallbackFunc callback); + void SetRepeating(bool value); + void Update(Duration deltaTime); + + bool IsStarted() { return m_timerStarted; } + bool IsFinished() { return m_timerFinished; } + +private: + bool m_timerStarted = false; + bool m_repeatingTimer = false; + bool m_timerFinished = false; + + TimePoint m_startTime; + TimePoint m_currentTime; + Duration m_timerDuration; + CallbackFunc m_callback; +}; diff --git a/src/d_main.cpp b/src/d_main.cpp index 2b5e52edf..070620edf 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -3901,6 +3901,15 @@ int D_DoomMain_Game() int GameMain() { + // On Windows, prefer the native win32 backend. + // On other platforms, use SDL until the other backends are more mature. + auto zwidget = DisplayBackend::TryCreateWin32(); + if (!zwidget) + zwidget = DisplayBackend::TryCreateSDL2(); + if (!zwidget) + return -1; + DisplayBackend::Set(std::move(zwidget)); + int ret = 0; GameTicRate = TICRATE; I_InitTime();