diff --git a/libraries/ZWidget/CMakeLists.txt b/libraries/ZWidget/CMakeLists.txt index 329caf96e..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 @@ -8,6 +24,7 @@ set(ZWIDGET_SOURCES src/core/span_layout.cpp src/core/timer.cpp src/core/widget.cpp + src/core/theme.cpp src/core/utf8reader.cpp src/core/pathfill.cpp src/core/truetypefont.cpp @@ -32,6 +49,17 @@ set(ZWIDGET_SOURCES src/widgets/listview/listview.cpp src/widgets/tabwidget/tabwidget.cpp src/window/window.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 ) set(ZWIDGET_INCLUDES @@ -44,6 +72,7 @@ set(ZWIDGET_INCLUDES include/zwidget/core/span_layout.h include/zwidget/core/timer.h include/zwidget/core/widget.h + include/zwidget/core/theme.h include/zwidget/core/utf8reader.h include/zwidget/core/resourcedata.h include/zwidget/widgets/lineedit/lineedit.h @@ -61,19 +90,62 @@ set(ZWIDGET_INCLUDES include/zwidget/widgets/listview/listview.h include/zwidget/widgets/tabwidget/tabwidget.h include/zwidget/window/window.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/win32window.cpp - src/window/win32/win32window.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/.+") @@ -95,6 +167,13 @@ 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/.+") source_group("include\\widgets" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/.+") @@ -112,42 +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}) - if(NOT HAIKU) - set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl) - else() - set(ZWIDGET_LIBS ${CMAKE_DL_LIBS}) + 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() - 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") 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/theme.h b/libraries/ZWidget/include/zwidget/core/theme.h new file mode 100644 index 000000000..5a5271bc3 --- /dev/null +++ b/libraries/ZWidget/include/zwidget/core/theme.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include +#include "rect.h" +#include "colorf.h" + +class Widget; +class Canvas; + +class WidgetStyle +{ +public: + WidgetStyle(WidgetStyle* parentStyle = nullptr) : ParentStyle(parentStyle) { } + virtual ~WidgetStyle() = default; + virtual void Paint(Widget* widget, Canvas* canvas, Size size) = 0; + + void SetBool(const std::string& state, const std::string& propertyName, bool value); + void SetInt(const std::string& state, const std::string& propertyName, int value); + void SetDouble(const std::string& state, const std::string& propertyName, double value); + void SetString(const std::string& state, const std::string& propertyName, const std::string& value); + void SetColor(const std::string& state, const std::string& propertyName, const Colorf& value); + + void SetBool(const std::string& propertyName, bool value) { SetBool(std::string(), propertyName, value); } + void SetInt(const std::string& propertyName, int value) { SetInt(std::string(), propertyName, value); } + void SetDouble(const std::string& propertyName, double value) { SetDouble(std::string(), propertyName, value); } + void SetString(const std::string& propertyName, const std::string& value) { SetString(std::string(), propertyName, value); } + void SetColor(const std::string& propertyName, const Colorf& value) { SetColor(std::string(), propertyName, value); } + +private: + // Note: do not call these directly. Use widget->GetStyleXX instead since a widget may explicitly override a class style + bool GetBool(const std::string& state, const std::string& propertyName) const; + int GetInt(const std::string& state, const std::string& propertyName) const; + double GetDouble(const std::string& state, const std::string& propertyName) const; + std::string GetString(const std::string& state, const std::string& propertyName) const; + Colorf GetColor(const std::string& state, const std::string& propertyName) const; + + WidgetStyle* ParentStyle = nullptr; + typedef std::variant PropertyVariant; + std::unordered_map> StyleProperties; + + const PropertyVariant* FindProperty(const std::string& state, const std::string& propertyName) const; + + friend class Widget; +}; + +class BasicWidgetStyle : public WidgetStyle +{ +public: + using WidgetStyle::WidgetStyle; + void Paint(Widget* widget, Canvas* canvas, Size size) override; +}; + +class WidgetTheme +{ +public: + virtual ~WidgetTheme() = default; + + WidgetStyle* RegisterStyle(std::unique_ptr widgetStyle, const std::string& widgetClass); + WidgetStyle* GetStyle(const std::string& widgetClass); + + static void SetTheme(std::unique_ptr theme); + static WidgetTheme* GetTheme(); + +private: + std::unordered_map> Styles; +}; + +class DarkWidgetTheme : public WidgetTheme +{ +public: + DarkWidgetTheme(); +}; + +class LightWidgetTheme : public WidgetTheme +{ +public: + LightWidgetTheme(); +}; diff --git a/libraries/ZWidget/include/zwidget/core/widget.h b/libraries/ZWidget/include/zwidget/core/widget.h index 34faa14e9..6d87f72a0 100644 --- a/libraries/ZWidget/include/zwidget/core/widget.h +++ b/libraries/ZWidget/include/zwidget/core/widget.h @@ -2,6 +2,8 @@ #include #include +#include +#include #include "canvas.h" #include "rect.h" #include "colorf.h" @@ -20,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); @@ -39,16 +41,45 @@ public: // Widget noncontent area void SetNoncontentSizes(double left, double top, double right, double bottom); - double GetNoncontentLeft() const { return Noncontent.Left; } - double GetNoncontentTop() const { return Noncontent.Top; } - double GetNoncontentRight() const { return Noncontent.Right; } - double GetNoncontentBottom() const { return Noncontent.Bottom; } + double GetNoncontentLeft() const { return GridFitSize(GetStyleDouble("noncontent-left")); } + double GetNoncontentTop() const { return GridFitSize(GetStyleDouble("noncontent-top")); } + double GetNoncontentRight() const { return GridFitSize(GetStyleDouble("noncontent-right")); } + double GetNoncontentBottom() const { return GridFitSize(GetStyleDouble("noncontent-bottom")); } + + // Get the DPI scale factor for the window the widget is located on + double GetDpiScale() const; + + // Align point to the nearest physical screen pixel + double GridFitPoint(double p) const; + Point GridFitPoint(double x, double y) const { return GridFitPoint(Point(x, y)); } + Point GridFitPoint(Point p) const { return Point(GridFitPoint(p.x), GridFitPoint(p.y)); } + + // Convert size to exactly covering physical screen pixels + double GridFitSize(double s) const; + Size GridFitSize(double w, double h) const { return GridFitSize(Size(w, h)); } + Size GridFitSize(Size s) const { return Size(GridFitSize(s.width), GridFitSize(s.height)); } // Widget frame box Rect GetFrameGeometry() const; void SetFrameGeometry(const Rect& geometry); void SetFrameGeometry(double x, double y, double width, double height) { SetFrameGeometry(Rect::xywh(x, y, width, height)); } + // Style properties + void SetStyleClass(const std::string& styleClass); + const std::string& GetStyleClass() const { return StyleClass; } + void SetStyleState(const std::string& state); + const std::string& GetStyleState() const { return StyleState; } + void SetStyleBool(const std::string& propertyName, bool value); + void SetStyleInt(const std::string& propertyName, int value); + void SetStyleDouble(const std::string& propertyName, double value); + void SetStyleString(const std::string& propertyName, const std::string& value); + void SetStyleColor(const std::string& propertyName, const Colorf& value); + bool GetStyleBool(const std::string& propertyName) const; + int GetStyleInt(const std::string& propertyName) const; + double GetStyleDouble(const std::string& propertyName) const; + std::string GetStyleString(const std::string& propertyName) const; + Colorf GetStyleColor(const std::string& propertyName) const; + void SetWindowBackground(const Colorf& color); void SetWindowBorderColor(const Colorf& color); void SetWindowCaptionColor(const Colorf& color); @@ -73,6 +104,7 @@ public: bool IsEnabled(); bool IsVisible(); bool IsHidden(); + bool IsFullscreen(); void SetFocus(); void SetEnabled(bool value); @@ -82,19 +114,26 @@ public: void LockCursor(); void UnlockCursor(); void SetCursor(StandardCursor cursor); - void CaptureMouse(); - void ReleaseMouseCapture(); - bool GetKeyState(EInputKey key); + void SetPointerCapture(); + void ReleasePointerCapture(); + + void SetModalCapture(); + void ReleaseModalCapture(); + + bool GetKeyState(InputKey key); std::string GetClipboardText(); void SetClipboardText(const std::string& text); - Widget* Window(); + Widget* Window() const; Canvas* GetCanvas() const; 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; } @@ -111,19 +150,28 @@ 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) { } + virtual void OnPaintFrame(Canvas* canvas); virtual void OnPaint(Canvas* canvas) { } - virtual bool OnMouseDown(const Point& pos, int key) { return false; } - virtual bool OnMouseDoubleclick(const Point& pos, int key) { return false; } - virtual bool OnMouseUp(const Point& pos, int key) { return false; } - virtual bool OnMouseWheel(const Point& pos, EInputKey key) { return false; } + virtual bool OnMouseDown(const Point& pos, InputKey key) { return false; } + virtual bool OnMouseDoubleclick(const Point& pos, InputKey key) { return false; } + virtual bool OnMouseUp(const Point& pos, InputKey key) { return false; } + virtual bool OnMouseWheel(const Point& pos, InputKey key) { return false; } virtual void OnMouseMove(const Point& pos) { } virtual void OnMouseLeave() { } virtual void OnRawMouseMove(int dx, int dy) { } virtual void OnKeyChar(std::string chars) { } - virtual void OnKeyDown(EInputKey key) { } - virtual void OnKeyUp(EInputKey key) { } + virtual void OnKeyDown(InputKey key) { } + virtual void OnKeyUp(InputKey key) { } virtual void OnGeometryChanged() { } virtual void OnClose() { delete this; } virtual void OnSetFocus() { } @@ -138,14 +186,15 @@ private: // DisplayWindowHost void OnWindowPaint() override; void OnWindowMouseMove(const Point& pos) override; - void OnWindowMouseDown(const Point& pos, EInputKey key) override; - void OnWindowMouseDoubleclick(const Point& pos, EInputKey key) override; - void OnWindowMouseUp(const Point& pos, EInputKey key) override; - void OnWindowMouseWheel(const Point& pos, EInputKey key) override; + void OnWindowMouseLeave() override; + void OnWindowMouseDown(const Point& pos, InputKey key) override; + void OnWindowMouseDoubleclick(const Point& pos, InputKey key) override; + void OnWindowMouseUp(const Point& pos, InputKey key) override; + void OnWindowMouseWheel(const Point& pos, InputKey key) override; void OnWindowRawMouseMove(int dx, int dy) override; void OnWindowKeyChar(std::string chars) override; - void OnWindowKeyDown(EInputKey key) override; - void OnWindowKeyUp(EInputKey key) override; + void OnWindowKeyDown(InputKey key) override; + void OnWindowKeyUp(InputKey key) override; void OnWindowGeometryChanged() override; void OnWindowClose() override; void OnWindowActivated() override; @@ -167,14 +216,6 @@ private: Colorf WindowBackground = Colorf::fromRgba8(240, 240, 240); - struct - { - double Left = 0.0; - double Top = 0.0; - double Right = 0.0; - double Bottom = 0.0; - } Noncontent; - std::string WindowTitle; std::unique_ptr DispWindow; std::unique_ptr DispCanvas; @@ -185,8 +226,16 @@ private: StandardCursor CurrentCursor = StandardCursor::arrow; + std::string StyleClass = "widget"; + std::string StyleState; + typedef std::variant PropertyVariant; + std::unordered_map StyleProperties; + Widget(const Widget&) = delete; 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/open_file_dialog.h b/libraries/ZWidget/include/zwidget/systemdialogs/open_file_dialog.h new file mode 100644 index 000000000..29ede966e --- /dev/null +++ b/libraries/ZWidget/include/zwidget/systemdialogs/open_file_dialog.h @@ -0,0 +1,55 @@ + +#pragma once + +#include +#include +#include + +class Widget; + +/// \brief Displays the system open file dialog. +class OpenFileDialog +{ +public: + /// \brief Constructs an open file dialog. + static std::unique_ptr Create(Widget* owner); + + virtual ~OpenFileDialog() = default; + + /// \brief Get the full path of the file selected. + /// + /// If multiple files are selected, this returns the first file. + virtual std::string Filename() const = 0; + + /// \brief Gets an array that contains one file name for each selected file. + virtual std::vector Filenames() const = 0; + + /// \brief Sets if multiple files can be selected or not. + /// \param multiselect = When true, multiple items can be selected. + virtual void SetMultiSelect(bool multiselect) = 0; + + /// \brief Sets a string containing the full path of the file selected. + virtual void SetFilename(const std::string &filename) = 0; + + /// \brief Sets the default extension to use. + virtual void SetDefaultExtension(const std::string& extension) = 0; + + /// \brief Add a filter that determines what types of files are displayed. + virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0; + + /// \brief Clears all filters. + virtual void ClearFilters() = 0; + + /// \brief Sets a default filter, on a 0-based index. + virtual void SetFilterIndex(int filter_index) = 0; + + /// \brief Sets the initial directory that is displayed. + virtual void SetInitialDirectory(const std::string &path) = 0; + + /// \brief Sets the text that appears in the title bar. + virtual void SetTitle(const std::string &title) = 0; + + /// \brief Shows the file dialog. + /// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise. + virtual bool Show() = 0; +}; diff --git a/libraries/ZWidget/include/zwidget/systemdialogs/open_folder_dialog.h b/libraries/ZWidget/include/zwidget/systemdialogs/open_folder_dialog.h new file mode 100644 index 000000000..9727f353b --- /dev/null +++ b/libraries/ZWidget/include/zwidget/systemdialogs/open_folder_dialog.h @@ -0,0 +1,30 @@ + +#pragma once + +#include +#include + +class Widget; + +/// \brief Displays the system folder browsing dialog +class OpenFolderDialog +{ +public: + /// \brief Constructs an browse folder dialog. + static std::unique_ptr Create(Widget*owner); + + virtual ~OpenFolderDialog() = default; + + /// \brief Get the full path of the directory selected. + virtual std::string SelectedPath() const = 0; + + /// \brief Sets the initial directory that is displayed. + virtual void SetInitialDirectory(const std::string& path) = 0; + + /// \brief Sets the text that appears in the title bar. + virtual void SetTitle(const std::string& title) = 0; + + /// \brief Shows the file dialog. + /// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise. + virtual bool Show() = 0; +}; diff --git a/libraries/ZWidget/include/zwidget/systemdialogs/save_file_dialog.h b/libraries/ZWidget/include/zwidget/systemdialogs/save_file_dialog.h new file mode 100644 index 000000000..ce10a127f --- /dev/null +++ b/libraries/ZWidget/include/zwidget/systemdialogs/save_file_dialog.h @@ -0,0 +1,46 @@ + +#pragma once + +#include +#include +#include + +class Widget; + +/// \brief Displays the system save file dialog. +class SaveFileDialog +{ +public: + /// \brief Constructs a save file dialog. + static std::unique_ptr Create(Widget *owner); + + virtual ~SaveFileDialog() = default; + + /// \brief Get the full path of the file selected. + virtual std::string Filename() const = 0; + + /// \brief Sets a string containing the full path of the file selected. + virtual void SetFilename(const std::string &filename) = 0; + + /// \brief Sets the default extension to use. + virtual void SetDefaultExtension(const std::string& extension) = 0; + + /// \brief Add a filter that determines what types of files are displayed. + virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0; + + /// \brief Clears all filters. + virtual void ClearFilters() = 0; + + /// \brief Sets a default filter, on a 0-based index. + virtual void SetFilterIndex(int filter_index) = 0; + + /// \brief Sets the initial directory that is displayed. + virtual void SetInitialDirectory(const std::string &path) = 0; + + /// \brief Sets the text that appears in the title bar. + virtual void SetTitle(const std::string &title) = 0; + + /// \brief Shows the file dialog. + /// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise. + virtual bool Show() = 0; +}; diff --git a/libraries/ZWidget/include/zwidget/widgets/checkboxlabel/checkboxlabel.h b/libraries/ZWidget/include/zwidget/widgets/checkboxlabel/checkboxlabel.h index c3f3fb465..21ce0b697 100644 --- a/libraries/ZWidget/include/zwidget/widgets/checkboxlabel/checkboxlabel.h +++ b/libraries/ZWidget/include/zwidget/widgets/checkboxlabel/checkboxlabel.h @@ -21,15 +21,14 @@ public: protected: void OnPaint(Canvas* canvas) override; - bool OnMouseDown(const Point& pos, int key) override; - bool OnMouseUp(const Point& pos, int key) override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; void OnMouseLeave() override; - void OnKeyUp(EInputKey key) override; + void OnKeyUp(InputKey key) override; private: std::string text; bool checked = false; bool radiostyle = false; bool mouseDownActive = false; - }; diff --git a/libraries/ZWidget/include/zwidget/widgets/imagebox/imagebox.h b/libraries/ZWidget/include/zwidget/widgets/imagebox/imagebox.h index e3a724381..7cae08c24 100644 --- a/libraries/ZWidget/include/zwidget/widgets/imagebox/imagebox.h +++ b/libraries/ZWidget/include/zwidget/widgets/imagebox/imagebox.h @@ -4,13 +4,22 @@ #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; protected: @@ -18,4 +27,5 @@ protected: private: std::shared_ptr image; + ImageBoxMode mode = ImageBoxMode::Center; }; diff --git a/libraries/ZWidget/include/zwidget/widgets/lineedit/lineedit.h b/libraries/ZWidget/include/zwidget/widgets/lineedit/lineedit.h index 526d9eee9..8c5dcfa89 100644 --- a/libraries/ZWidget/include/zwidget/widgets/lineedit/lineedit.h +++ b/libraries/ZWidget/include/zwidget/widgets/lineedit/lineedit.h @@ -59,7 +59,7 @@ public: void SetInputMask(const std::string& mask); void SetDecimalCharacter(const std::string& decimal_char); - std::function FuncIgnoreKeyDown; + std::function FuncIgnoreKeyDown; std::function FuncFilterKeyChar; std::function FuncBeforeEditChanged; std::function FuncAfterEditChanged; @@ -69,15 +69,14 @@ public: std::function FuncEnterPressed; protected: - void OnPaintFrame(Canvas* canvas) override; void OnPaint(Canvas* canvas) override; void OnMouseMove(const Point& pos) override; - bool OnMouseDown(const Point& pos, int key) override; - bool OnMouseDoubleclick(const Point& pos, int key) override; - bool OnMouseUp(const Point& pos, int key) override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseDoubleclick(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; void OnKeyChar(std::string chars) override; - void OnKeyDown(EInputKey key) override; - void OnKeyUp(EInputKey key) override; + void OnKeyDown(InputKey key) override; + void OnKeyUp(InputKey key) override; void OnGeometryChanged() override; void OnEnableChanged() override; void OnSetFocus() override; diff --git a/libraries/ZWidget/include/zwidget/widgets/listview/listview.h b/libraries/ZWidget/include/zwidget/widgets/listview/listview.h index 550cc07f9..433119759 100644 --- a/libraries/ZWidget/include/zwidget/widgets/listview/listview.h +++ b/libraries/ZWidget/include/zwidget/widgets/listview/listview.h @@ -29,11 +29,10 @@ public: protected: void OnPaint(Canvas* canvas) override; - void OnPaintFrame(Canvas* canvas) override; - bool OnMouseDown(const Point& pos, int key) override; - bool OnMouseDoubleclick(const Point& pos, int key) override; - bool OnMouseWheel(const Point& pos, EInputKey key) override; - void OnKeyDown(EInputKey key) override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseDoubleclick(const Point& pos, InputKey key) override; + bool OnMouseWheel(const Point& pos, InputKey key) override; + void OnKeyDown(InputKey key) override; void OnGeometryChanged() override; void OnScrollbarScroll(); 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 9c13283cc..1881111f4 100644 --- a/libraries/ZWidget/include/zwidget/widgets/menubar/menubar.h +++ b/libraries/ZWidget/include/zwidget/widgets/menubar/menubar.h @@ -2,6 +2,14 @@ #pragma once #include "../../core/widget.h" +#include "../textlabel/textlabel.h" +#include "../imagebox/imagebox.h" +#include + +class Menu; +class MenubarItem; +class MenuItem; +class MenuItemSeparator; class Menubar : public Widget { @@ -9,6 +17,105 @@ public: Menubar(Widget* parent); ~Menubar(); + MenubarItem* AddItem(std::string text, std::function onOpen, bool alignRight = false); + +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; +}; + +class MenubarItem : public Widget +{ +public: + MenubarItem(Menubar* menubar, std::string text, bool alignRight); + + void SetOpenCallback(std::function callback) { onOpen = std::move(callback); } + const std::function& GetOpenCallback() const { return onOpen; } + + double GetPreferredWidth() const; + + bool AlignRight = false; + +protected: + void OnPaint(Canvas* canvas) override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; + void OnMouseMove(const Point& pos) override; + void OnMouseLeave() override; + +private: + Menubar* menubar = nullptr; + std::function onOpen; + std::string text; +}; + +class Menu : public Widget +{ +public: + Menu(Widget* parent); + + void SetLeftPosition(const Point& globalPos); + void SetRightPosition(const Point& globalPos); + MenuItem* AddItem(std::shared_ptr icon, std::string text, std::function onClick = {}); + MenuItemSeparator* AddSeparator(); + + double GetPreferredWidth() const; + double GetPreferredHeight() const; + + void SetSelected(MenuItem* item); + +protected: + void OnGeometryChanged() override; + + std::function onCloseMenu; + MenuItem* selectedItem = nullptr; + + friend class Menubar; +}; + +class MenuItem : public Widget +{ +public: + 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/pushbutton/pushbutton.h b/libraries/ZWidget/include/zwidget/widgets/pushbutton/pushbutton.h index b9b6c9654..d3fe0295b 100644 --- a/libraries/ZWidget/include/zwidget/widgets/pushbutton/pushbutton.h +++ b/libraries/ZWidget/include/zwidget/widgets/pushbutton/pushbutton.h @@ -19,17 +19,14 @@ public: std::function OnClick; protected: - void OnPaintFrame(Canvas* canvas) override; void OnPaint(Canvas* canvas) override; - bool OnMouseDown(const Point& pos, int key) override; - bool OnMouseUp(const Point& pos, int key) override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; void OnMouseMove(const Point& pos) override; void OnMouseLeave() override; - void OnKeyDown(EInputKey key) override; - void OnKeyUp(EInputKey key) override; + void OnKeyDown(InputKey key) override; + void OnKeyUp(InputKey key) override; private: std::string text; - bool buttonDown = false; - bool hot = false; }; diff --git a/libraries/ZWidget/include/zwidget/widgets/scrollbar/scrollbar.h b/libraries/ZWidget/include/zwidget/widgets/scrollbar/scrollbar.h index 5ec95c841..6e169b1fe 100644 --- a/libraries/ZWidget/include/zwidget/widgets/scrollbar/scrollbar.h +++ b/libraries/ZWidget/include/zwidget/widgets/scrollbar/scrollbar.h @@ -47,8 +47,8 @@ public: std::function FuncScrollEnd; protected: - bool OnMouseDown(const Point& pos, int key) override; - bool OnMouseUp(const Point& pos, int key) override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; void OnMouseMove(const Point& pos) override; void OnMouseLeave() override; void OnPaint(Canvas* canvas) override; diff --git a/libraries/ZWidget/include/zwidget/widgets/tabwidget/tabwidget.h b/libraries/ZWidget/include/zwidget/widgets/tabwidget/tabwidget.h index 242094024..00bb3d890 100644 --- a/libraries/ZWidget/include/zwidget/widgets/tabwidget/tabwidget.h +++ b/libraries/ZWidget/include/zwidget/widgets/tabwidget/tabwidget.h @@ -2,9 +2,9 @@ #pragma once #include "../../core/widget.h" -#include #include #include +#include class TabBar; class TabBarTab; @@ -37,7 +37,6 @@ public: std::function OnCurrentChanged; protected: - void OnPaintFrame(Canvas* canvas) override; void OnGeometryChanged() override; private: @@ -67,7 +66,6 @@ public: std::function OnCurrentChanged; protected: - void OnPaintFrame(Canvas* canvas) override; void OnGeometryChanged() override; private: @@ -92,10 +90,9 @@ public: std::function OnClick; protected: - void OnPaintFrame(Canvas* canvas) override; void OnGeometryChanged() override; - bool OnMouseDown(const Point& pos, int key) override; - bool OnMouseUp(const Point& pos, int key) override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; void OnMouseMove(const Point& pos) override; void OnMouseLeave() override; @@ -116,7 +113,6 @@ public: Widget* GetCurrentWidget() const { return CurrentWidget; } protected: - void OnPaintFrame(Canvas* canvas) override; void OnGeometryChanged() override; private: diff --git a/libraries/ZWidget/include/zwidget/widgets/textedit/textedit.h b/libraries/ZWidget/include/zwidget/widgets/textedit/textedit.h index ed80eec36..f392f85d6 100644 --- a/libraries/ZWidget/include/zwidget/widgets/textedit/textedit.h +++ b/libraries/ZWidget/include/zwidget/widgets/textedit/textedit.h @@ -53,15 +53,14 @@ public: std::function FuncEnterPressed; protected: - void OnPaintFrame(Canvas* canvas) override; void OnPaint(Canvas* canvas) override; void OnMouseMove(const Point& pos) override; - bool OnMouseDown(const Point& pos, int key) override; - bool OnMouseDoubleclick(const Point& pos, int key) override; - bool OnMouseUp(const Point& pos, int key) override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseDoubleclick(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; void OnKeyChar(std::string chars) override; - void OnKeyDown(EInputKey key) override; - void OnKeyUp(EInputKey key) override; + void OnKeyDown(InputKey key) override; + void OnKeyUp(InputKey key) override; void OnGeometryChanged() override; void OnEnableChanged() override; void OnSetFocus() 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 4cdb748d8..ad59f1f02 100644 --- a/libraries/ZWidget/include/zwidget/window/window.h +++ b/libraries/ZWidget/include/zwidget/window/window.h @@ -1,12 +1,31 @@ #pragma once -#include #include #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 { @@ -25,91 +44,83 @@ enum class StandardCursor wait }; -enum EInputKey +enum class InputKey : uint32_t { - IK_None, IK_LeftMouse, IK_RightMouse, IK_Cancel, - IK_MiddleMouse, IK_Unknown05, IK_Unknown06, IK_Unknown07, - IK_Backspace, IK_Tab, IK_Unknown0A, IK_Unknown0B, - IK_Unknown0C, IK_Enter, IK_Unknown0E, IK_Unknown0F, - IK_Shift, IK_Ctrl, IK_Alt, IK_Pause, - IK_CapsLock, IK_Unknown15, IK_Unknown16, IK_Unknown17, - IK_Unknown18, IK_Unknown19, IK_Unknown1A, IK_Escape, - IK_Unknown1C, IK_Unknown1D, IK_Unknown1E, IK_Unknown1F, - IK_Space, IK_PageUp, IK_PageDown, IK_End, - IK_Home, IK_Left, IK_Up, IK_Right, - IK_Down, IK_Select, IK_Print, IK_Execute, - IK_PrintScrn, IK_Insert, IK_Delete, IK_Help, - IK_0, IK_1, IK_2, IK_3, - IK_4, IK_5, IK_6, IK_7, - IK_8, IK_9, IK_Unknown3A, IK_Unknown3B, - IK_Unknown3C, IK_Unknown3D, IK_Unknown3E, IK_Unknown3F, - IK_Unknown40, IK_A, IK_B, IK_C, - IK_D, IK_E, IK_F, IK_G, - IK_H, IK_I, IK_J, IK_K, - IK_L, IK_M, IK_N, IK_O, - IK_P, IK_Q, IK_R, IK_S, - IK_T, IK_U, IK_V, IK_W, - IK_X, IK_Y, IK_Z, IK_Unknown5B, - IK_Unknown5C, IK_Unknown5D, IK_Unknown5E, IK_Unknown5F, - IK_NumPad0, IK_NumPad1, IK_NumPad2, IK_NumPad3, - IK_NumPad4, IK_NumPad5, IK_NumPad6, IK_NumPad7, - IK_NumPad8, IK_NumPad9, IK_GreyStar, IK_GreyPlus, - IK_Separator, IK_GreyMinus, IK_NumPadPeriod, IK_GreySlash, - IK_F1, IK_F2, IK_F3, IK_F4, - IK_F5, IK_F6, IK_F7, IK_F8, - IK_F9, IK_F10, IK_F11, IK_F12, - IK_F13, IK_F14, IK_F15, IK_F16, - IK_F17, IK_F18, IK_F19, IK_F20, - IK_F21, IK_F22, IK_F23, IK_F24, - IK_Unknown88, IK_Unknown89, IK_Unknown8A, IK_Unknown8B, - IK_Unknown8C, IK_Unknown8D, IK_Unknown8E, IK_Unknown8F, - IK_NumLock, IK_ScrollLock, IK_Unknown92, IK_Unknown93, - IK_Unknown94, IK_Unknown95, IK_Unknown96, IK_Unknown97, - IK_Unknown98, IK_Unknown99, IK_Unknown9A, IK_Unknown9B, - IK_Unknown9C, IK_Unknown9D, IK_Unknown9E, IK_Unknown9F, - IK_LShift, IK_RShift, IK_LControl, IK_RControl, - IK_UnknownA4, IK_UnknownA5, IK_UnknownA6, IK_UnknownA7, - IK_UnknownA8, IK_UnknownA9, IK_UnknownAA, IK_UnknownAB, - IK_UnknownAC, IK_UnknownAD, IK_UnknownAE, IK_UnknownAF, - IK_UnknownB0, IK_UnknownB1, IK_UnknownB2, IK_UnknownB3, - IK_UnknownB4, IK_UnknownB5, IK_UnknownB6, IK_UnknownB7, - IK_UnknownB8, IK_UnknownB9, IK_Semicolon, IK_Equals, - IK_Comma, IK_Minus, IK_Period, IK_Slash, - IK_Tilde, IK_UnknownC1, IK_UnknownC2, IK_UnknownC3, - IK_UnknownC4, IK_UnknownC5, IK_UnknownC6, IK_UnknownC7, - IK_Joy1, IK_Joy2, IK_Joy3, IK_Joy4, - IK_Joy5, IK_Joy6, IK_Joy7, IK_Joy8, - IK_Joy9, IK_Joy10, IK_Joy11, IK_Joy12, - IK_Joy13, IK_Joy14, IK_Joy15, IK_Joy16, - IK_UnknownD8, IK_UnknownD9, IK_UnknownDA, IK_LeftBracket, - IK_Backslash, IK_RightBracket, IK_SingleQuote, IK_UnknownDF, - IK_JoyX, IK_JoyY, IK_JoyZ, IK_JoyR, - IK_MouseX, IK_MouseY, IK_MouseZ, IK_MouseW, - IK_JoyU, IK_JoyV, IK_UnknownEA, IK_UnknownEB, - IK_MouseWheelUp, IK_MouseWheelDown, IK_Unknown10E, IK_Unknown10F, - IK_JoyPovUp, IK_JoyPovDown, IK_JoyPovLeft, IK_JoyPovRight, - IK_UnknownF4, IK_UnknownF5, IK_Attn, IK_CrSel, - IK_ExSel, IK_ErEof, IK_Play, IK_Zoom, - IK_NoName, IK_PA1, IK_OEMClear + None, LeftMouse, RightMouse, Cancel, + MiddleMouse, Unknown05, Unknown06, Unknown07, + Backspace, Tab, Unknown0A, Unknown0B, + Unknown0C, Enter, Unknown0E, Unknown0F, + Shift, Ctrl, Alt, Pause, + CapsLock, Unknown15, Unknown16, Unknown17, + Unknown18, Unknown19, Unknown1A, Escape, + Unknown1C, Unknown1D, Unknown1E, Unknown1F, + Space, PageUp, PageDown, End, + Home, Left, Up, Right, + Down, Select, Print, Execute, + PrintScrn, Insert, Delete, Help, + _0, _1, _2, _3, + _4, _5, _6, _7, + _8, _9, Unknown3A, Unknown3B, + Unknown3C, Unknown3D, Unknown3E, Unknown3F, + Unknown40, A, B, C, + D, E, F, G, + H, I, J, K, + L, M, N, O, + P, Q, R, S, + T, U, V, W, + X, Y, Z, Unknown5B, + Unknown5C, Unknown5D, Unknown5E, Unknown5F, + NumPad0, NumPad1, NumPad2, NumPad3, + NumPad4, NumPad5, NumPad6, NumPad7, + NumPad8, NumPad9, GreyStar, GreyPlus, + Separator, GreyMinus, NumPadPeriod, GreySlash, + F1, F2, F3, F4, + F5, F6, F7, F8, + F9, F10, F11, F12, + F13, F14, F15, F16, + F17, F18, F19, F20, + F21, F22, F23, F24, + Unknown88, Unknown89, Unknown8A, Unknown8B, + Unknown8C, Unknown8D, Unknown8E, Unknown8F, + NumLock, ScrollLock, Unknown92, Unknown93, + Unknown94, Unknown95, Unknown96, Unknown97, + Unknown98, Unknown99, Unknown9A, Unknown9B, + Unknown9C, Unknown9D, Unknown9E, Unknown9F, + LShift, RShift, LControl, RControl, + UnknownA4, UnknownA5, UnknownA6, UnknownA7, + UnknownA8, UnknownA9, UnknownAA, UnknownAB, + UnknownAC, UnknownAD, UnknownAE, UnknownAF, + UnknownB0, UnknownB1, UnknownB2, UnknownB3, + UnknownB4, UnknownB5, UnknownB6, UnknownB7, + UnknownB8, UnknownB9, Semicolon, Equals, + Comma, Minus, Period, Slash, + Tilde, UnknownC1, UnknownC2, UnknownC3, + UnknownC4, UnknownC5, UnknownC6, UnknownC7, + Joy1, Joy2, Joy3, Joy4, + Joy5, Joy6, Joy7, Joy8, + Joy9, Joy10, Joy11, Joy12, + Joy13, Joy14, Joy15, Joy16, + UnknownD8, UnknownD9, UnknownDA, LeftBracket, + Backslash, RightBracket, SingleQuote, UnknownDF, + JoyX, JoyY, JoyZ, JoyR, + MouseX, MouseY, MouseZ, MouseW, + JoyU, JoyV, UnknownEA, UnknownEB, + MouseWheelUp, MouseWheelDown, Unknown10E, Unknown10F, + JoyPovUp, JoyPovDown, JoyPovLeft, JoyPovRight, + UnknownF4, UnknownF5, Attn, CrSel, + ExSel, ErEof, Play, Zoom, + NoName, PA1, OEMClear }; -enum EInputType +enum class RenderAPI { - IST_None, - IST_Press, - IST_Hold, - IST_Release, - IST_Axis -}; - -class KeyEvent -{ -public: - EInputKey Key; - bool Ctrl = false; - bool Alt = false; - bool Shift = false; - Point MousePos = Point(0.0, 0.0); + Unspecified, + Bitmap, + Vulkan, + OpenGL, + D3D11, + D3D12, + Metal }; class DisplayWindow; @@ -119,14 +130,15 @@ class DisplayWindowHost public: virtual void OnWindowPaint() = 0; virtual void OnWindowMouseMove(const Point& pos) = 0; - virtual void OnWindowMouseDown(const Point& pos, EInputKey key) = 0; - virtual void OnWindowMouseDoubleclick(const Point& pos, EInputKey key) = 0; - virtual void OnWindowMouseUp(const Point& pos, EInputKey key) = 0; - virtual void OnWindowMouseWheel(const Point& pos, EInputKey key) = 0; + virtual void OnWindowMouseLeave() = 0; + virtual void OnWindowMouseDown(const Point& pos, InputKey key) = 0; + virtual void OnWindowMouseDoubleclick(const Point& pos, InputKey key) = 0; + virtual void OnWindowMouseUp(const Point& pos, InputKey key) = 0; + virtual void OnWindowMouseWheel(const Point& pos, InputKey key) = 0; virtual void OnWindowRawMouseMove(int dx, int dy) = 0; virtual void OnWindowKeyChar(std::string chars) = 0; - virtual void OnWindowKeyDown(EInputKey key) = 0; - virtual void OnWindowKeyUp(EInputKey key) = 0; + virtual void OnWindowKeyDown(InputKey key) = 0; + virtual void OnWindowKeyUp(InputKey key) = 0; virtual void OnWindowGeometryChanged() = 0; virtual void OnWindowClose() = 0; virtual void OnWindowActivated() = 0; @@ -137,7 +149,7 @@ public: class DisplayWindow { public: - static std::unique_ptr Create(DisplayWindowHost* windowHost); + static std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI); static void ProcessEvents(); static void RunLoop(); @@ -158,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; @@ -166,7 +179,7 @@ public: virtual void CaptureMouse() = 0; virtual void ReleaseMouseCapture() = 0; virtual void Update() = 0; - virtual bool GetKeyState(EInputKey key) = 0; + virtual bool GetKeyState(InputKey key) = 0; virtual void SetCursor(StandardCursor cursor) = 0; @@ -176,6 +189,9 @@ public: virtual int GetPixelHeight() const = 0; virtual double GetDpiScale() const = 0; + virtual Point MapFromGlobal(const Point& pos) const = 0; + virtual Point MapToGlobal(const Point& pos) const = 0; + virtual void SetBorderColor(uint32_t bgra8) = 0; virtual void SetCaptionColor(uint32_t bgra8) = 0; virtual void SetCaptionTextColor(uint32_t bgra8) = 0; @@ -184,4 +200,44 @@ public: virtual std::string GetClipboardText() = 0; 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 76c7a16e7..bc72b8f19 100644 --- a/libraries/ZWidget/src/core/canvas.cpp +++ b/libraries/ZWidget/src/core/canvas.cpp @@ -13,13 +13,12 @@ #include #include -class CanvasTexture -{ -public: - int Width = 0; - int Height = 0; - std::vector Data; -}; +#if defined(__SSE2__) || defined(_M_X64) +#include +#define USE_SSE2 +#endif + +//////////////////////////////////////////////////////////////////////////// class CanvasGlyph { @@ -41,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(_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; @@ -120,6 +53,8 @@ public: TrueTypeTextMetrics textmetrics; std::unordered_map> glyphs; + + friend class CanvasFontGroup; }; class CanvasFontGroup @@ -130,143 +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 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()) { @@ -293,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) { - drawTile(whiteTexture.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, 1.0, 1.0, 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) @@ -314,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; @@ -401,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); @@ -409,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) @@ -427,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; @@ -435,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); @@ -448,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; @@ -458,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); @@ -485,47 +544,232 @@ 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) { - drawTile(whiteTexture.get(), (float)((p0.x - 0.5) * uiscale), (float)(p0.y * uiscale), (float)((p1.x + 0.5) * uiscale), (float)(p1.y * uiscale), 0.0f, 0.0f, 1.0f, 1.0f, color); + std::swap(x0, y0); + std::swap(x1, y1); } - else if (p0.y == p1.y) + + if (x0 > x1) { - drawTile(whiteTexture.get(), (float)(p0.x * uiscale), (float)((p0.y - 0.5) * uiscale), (float)(p1.x * uiscale), (float)((p1.y + 0.5) * uiscale), 0.0f, 0.0f, 1.0f, 1.0f, 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 + + // 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); + } + + // 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; + } } } -int BitmapCanvas::getClipMinX() const +void BitmapCanvas::fillTile(float left, float top, float width, float height, Colorf color) { - return clipStack.empty() ? 0 : (int)std::max(clipStack.back().x * uiscale, 0.0); -} - -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); -} - -void BitmapCanvas::drawTile(CanvasTexture* texture, 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) + if (width <= 0.0f || height <= 0.0f || color.a <= 0.0f) return; + int dwidth = this->width; + int dheight = this->height; + uint32_t* dest = this->pixels.data(); + + int x0 = (int)left; + int x1 = (int)(left + width); + int y0 = (int)top; + int y1 = (int)(top + height); + + x0 = std::max(x0, getClipMinX()); + y0 = std::max(y0, getClipMinY()); + x1 = std::min(x1, getClipMaxX()); + y1 = std::min(y1, getClipMaxY()); + if (x1 <= x0 || y1 <= y0) + return; + + uint32_t cred = (int32_t)clamp(color.r * 255.0f, 0.0f, 255.0f); + uint32_t cgreen = (int32_t)clamp(color.g * 255.0f, 0.0f, 255.0f); + uint32_t cblue = (int32_t)clamp(color.b * 255.0f, 0.0f, 255.0f); + uint32_t calpha = (int32_t)clamp(color.a * 255.0f, 0.0f, 255.0f); + uint32_t invalpha = 256 - (calpha + (calpha >> 7)); + + if (invalpha == 0) // Solid fill + { + uint32_t c = (calpha << 24) | (cred << 16) | (cgreen << 8) | cblue; +#ifdef USE_SSE2 + __m128i cargb = _mm_set1_epi32(c); +#endif + + for (int y = y0; y < y1; y++) + { + uint32_t* dline = dest + y * dwidth; + + int x = x0; +#ifdef USE_SSE2 + int ssex1 = x0 + (((x1 - x0) >> 2) << 2); + while (x < ssex1) + { + _mm_storeu_si128((__m128i*)(dline + x), cargb); + x += 4; + } +#endif + + while (x < x1) + { + dline[x] = c; + x++; + } + } + } + else // Alpha blended fill + { + cred <<= 8; + cgreen <<= 8; + cblue <<= 8; + calpha <<= 8; +#ifdef USE_SSE2 + __m128i cargb = _mm_set_epi16(calpha, cred, cgreen, cblue, calpha, cred, cgreen, cblue); + __m128i cinvalpha = _mm_set1_epi16(invalpha); +#endif + + for (int y = y0; y < y1; y++) + { + uint32_t* dline = dest + y * dwidth; + + int x = x0; +#ifdef USE_SSE2 + int ssex1 = x0 + (((x1 - x0) >> 1) << 1); + while (x < ssex1) + { + __m128i dpixel = _mm_loadl_epi64((const __m128i*)(dline + x)); + dpixel = _mm_unpacklo_epi8(dpixel, _mm_setzero_si128()); + + // dest.rgba = color.rgba + dest.rgba * (1-color.a) + __m128i result = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(cargb, _mm_mullo_epi16(dpixel, cinvalpha)), _mm_set1_epi16(127)), 8); + _mm_storel_epi64((__m128i*)(dline + x), _mm_packus_epi16(result, _mm_setzero_si128())); + x += 2; + } +#endif + + while (x < x1) + { + uint32_t dpixel = dline[x]; + 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 + dalpha * invalpha + 127) >> 8; + uint32_t r = (cred + dred * invalpha + 127) >> 8; + uint32_t g = (cgreen + dgreen * invalpha + 127) >> 8; + uint32_t b = (cblue + dblue * invalpha + 127) >> 8; + dline[x] = (a << 24) | (r << 16) | (g << 8) | b; + x++; + } + } + } +} + +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(); @@ -550,6 +794,9 @@ void BitmapCanvas::drawTile(CanvasTexture* texture, float left, float top, float 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 * 256.0f, 0.0f, 256.0f); +#ifdef USE_SSE2 + __m128i cargb = _mm_set_epi16(calpha, cred, cgreen, cblue, calpha, cred, cgreen, cblue); +#endif float uscale = uvwidth / width; float vscale = uvheight / height; @@ -558,9 +805,39 @@ void BitmapCanvas::drawTile(CanvasTexture* texture, float left, float top, float { float vpix = v + vscale * (y + 0.5f - top); const uint32_t* sline = src + ((int)vpix) * swidth; - uint32_t* dline = dest + y * dwidth; - for (int x = x0; x < x1; x++) + + int x = x0; +#ifdef USE_SSE2 + int ssex1 = x0 + (((x1 - x0) >> 1) << 1); + while (x < ssex1) + { + float upix0 = u + uscale * (x + 0.5f - left); + float upix1 = u + uscale * (x + 1 + 0.5f - left); + uint32_t spixel0 = sline[(int)upix0]; + uint32_t spixel1 = sline[(int)upix1]; + __m128i spixel = _mm_set_epi32(0, 0, spixel1, spixel0); + spixel = _mm_unpacklo_epi8(spixel, _mm_setzero_si128()); + + __m128i dpixel = _mm_loadl_epi64((const __m128i*)(dline + x)); + dpixel = _mm_unpacklo_epi8(dpixel, _mm_setzero_si128()); + + // Pixel shade + spixel = _mm_srli_epi16(_mm_add_epi16(_mm_mullo_epi16(spixel, cargb), _mm_set1_epi16(127)), 8); + + // Rescale from [0,255] to [0,256] + __m128i sa = _mm_shufflehi_epi16(_mm_shufflelo_epi16(spixel, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)); + sa = _mm_add_epi16(sa, _mm_srli_epi16(sa, 7)); + __m128i sinva = _mm_sub_epi16(_mm_set1_epi16(256), sa); + + // dest.rgba = color.rgba * src.rgba * src.a + dest.rgba * (1-src.a) + __m128i result = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(spixel, sa), _mm_mullo_epi16(dpixel, sinva)), _mm_set1_epi16(127)), 8); + _mm_storel_epi64((__m128i*)(dline + x), _mm_packus_epi16(result, _mm_setzero_si128())); + x += 2; + } +#endif + + while (x < x1) { float upix = u + uscale * (x + 0.5f - left); uint32_t spixel = sline[(int)upix]; @@ -591,15 +868,17 @@ void BitmapCanvas::drawTile(CanvasTexture* texture, float left, float top, float uint32_t g = (sgreen * sa + dgreen * sinva + 127) >> 8; uint32_t b = (sblue * sa + dblue * sinva + 127) >> 8; dline[x] = (a << 24) | (r << 16) | (g << 8) | b; + x++; } } } -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(); @@ -620,9 +899,15 @@ void BitmapCanvas::drawGlyph(CanvasTexture* texture, float left, float top, floa if (x1 <= x0 || y1 <= y0) return; - uint32_t cred = (int32_t)clamp(color.r * 255.0f, 0.0f, 255.0f); - uint32_t cgreen = (int32_t)clamp(color.g * 255.0f, 0.0f, 255.0f); - uint32_t cblue = (int32_t)clamp(color.b * 255.0f, 0.0f, 255.0f); +#if 1 // Use gamma correction + + // To linear + float cred = color.r * color.r; // std::pow(color.r, 2.2f); + float cgreen = color.g * color.g; // std::pow(color.g, 2.2f); + float cblue = color.b * color.b; // std::pow(color.b, 2.2f); +#ifdef USE_SSE2 + __m128 crgba = _mm_set_ps(0.0f, cred, cgreen, cblue); +#endif float uscale = uvwidth / width; float vscale = uvheight / height; @@ -631,9 +916,120 @@ void BitmapCanvas::drawGlyph(CanvasTexture* texture, float left, float top, floa { float vpix = v + vscale * (y + 0.5f - top); const uint32_t* sline = src + ((int)vpix) * swidth; - uint32_t* dline = dest + y * dwidth; - for (int x = x0; x < x1; x++) + + int x = x0; +#ifdef USE_SSE2 + while (x < x1) + { + float upix = u + uscale * (x + 0.5f - left); + __m128i spixel = _mm_cvtsi32_si128(sline[(int)upix]); + spixel = _mm_unpacklo_epi8(spixel, _mm_setzero_si128()); + spixel = _mm_unpacklo_epi16(spixel, _mm_setzero_si128()); + __m128 srgba = _mm_mul_ps(_mm_cvtepi32_ps(spixel), _mm_set_ps1(1.0f / 255.0f)); + + __m128i dpixel = _mm_cvtsi32_si128(dline[x]); + dpixel = _mm_unpacklo_epi8(dpixel, _mm_setzero_si128()); + dpixel = _mm_unpacklo_epi16(dpixel, _mm_setzero_si128()); + __m128 drgba = _mm_mul_ps(_mm_cvtepi32_ps(dpixel), _mm_set_ps1(1.0f / 255.0f)); + + // To linear + drgba = _mm_mul_ps(drgba, drgba); + + // dest.rgb = color.rgb * src.rgb + dest.rgb * (1-src.rgb) + __m128 frgba = _mm_add_ps(_mm_mul_ps(crgba, srgba), _mm_mul_ps(drgba, _mm_sub_ps(_mm_set_ps1(1.0f), srgba))); + + // To srgb + frgba = _mm_sqrt_ps(frgba); + + __m128i rgba = _mm_cvtps_epi32(_mm_add_ps(_mm_mul_ps(frgba, _mm_set_ps1(255.0f)), _mm_set_ps1(0.5f))); + rgba = _mm_packs_epi32(rgba, _mm_setzero_si128()); + rgba = _mm_packus_epi16(rgba, _mm_setzero_si128()); + dline[x] = ((uint32_t)_mm_cvtsi128_si32(rgba)) | 0xff000000; + x++; + } +#else + while (x < x1) + { + float upix = u + uscale * (x + 0.5f - left); + uint32_t spixel = sline[(int)upix]; + float sred = ((spixel >> 16) & 0xff) * (1.0f / 255.0f); + float sgreen = ((spixel >> 8) & 0xff) * (1.0f / 255.0f); + float sblue = (spixel & 0xff) * (1.0f / 255.0f); + + uint32_t dpixel = dline[x]; + float dred = ((dpixel >> 16) & 0xff) * (1.0f / 255.0f); + float dgreen = ((dpixel >> 8) & 0xff) * (1.0f / 255.0f); + float dblue = (dpixel & 0xff) * (1.0f / 255.0f); + + // To linear + dred = dred * dred; // std::pow(dred, 2.2f); + dgreen = dgreen * dgreen; // std::pow(dgreen, 2.2f); + dblue = dblue * dblue; // std::pow(dblue, 2.2f); + + // dest.rgb = color.rgb * src.rgb + dest.rgb * (1-src.rgb) + double fr = cred * sred + dred * (1.0f - sred); + double fg = cgreen * sgreen + dgreen * (1.0f - sgreen); + double fb = cblue * sblue + dblue * (1.0f - sblue); + + // To srgb + fr = std::sqrt(fr); // std::pow(fr, 1.0f / 2.2f); + fg = std::sqrt(fg); // std::pow(fg, 1.0f / 2.2f); + fb = std::sqrt(fb); // std::pow(fb, 1.0f / 2.2f); + + uint32_t r = (int)(fr * 255.0f + 0.5f); + uint32_t g = (int)(fg * 255.0f + 0.5f); + uint32_t b = (int)(fb * 255.0f + 0.5f); + dline[x] = 0xff000000 | (r << 16) | (g << 8) | b; + x++; + } +#endif + } + +#else + + uint32_t cred = (int32_t)clamp(color.r * 255.0f, 0.0f, 255.0f); + uint32_t cgreen = (int32_t)clamp(color.g * 255.0f, 0.0f, 255.0f); + uint32_t cblue = (int32_t)clamp(color.b * 255.0f, 0.0f, 255.0f); +#ifdef USE_SSE2 + __m128i crgba = _mm_set_epi16(0, cred, cgreen, cblue, 0, cred, cgreen, cblue); +#endif + + float uscale = uvwidth / width; + float vscale = uvheight / height; + + for (int y = y0; y < y1; y++) + { + float vpix = v + vscale * (y + 0.5f - top); + const uint32_t* sline = src + ((int)vpix) * swidth; + uint32_t* dline = dest + y * dwidth; + + int x = x0; +#ifdef USE_SSE2 + int ssex1 = x0 + (((x1 - x0) >> 1) << 1); + while (x < ssex1) + { + float upix0 = u + uscale * (x + 0.5f - left); + float upix1 = u + uscale * (x + 1 + 0.5f - left); + uint32_t spixel0 = sline[(int)upix0]; + uint32_t spixel1 = sline[(int)upix1]; + __m128i spixel = _mm_set_epi32(0, 0, spixel1, spixel0); + spixel = _mm_unpacklo_epi8(spixel, _mm_setzero_si128()); + + __m128i dpixel = _mm_loadl_epi64((const __m128i*)(dline + x)); + dpixel = _mm_unpacklo_epi8(dpixel, _mm_setzero_si128()); + + // Rescale from [0,255] to [0,256] + spixel = _mm_add_epi16(spixel, _mm_srli_epi16(spixel, 7)); + + // dest.rgb = color.rgb * src.rgb + dest.rgb * (1-src.rgb) + __m128i result = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(crgba, spixel), _mm_mullo_epi16(dpixel, _mm_sub_epi16(_mm_set1_epi16(256), spixel))), _mm_set1_epi16(127)), 8); + _mm_storel_epi64((__m128i*)(dline + x), _mm_or_si128(_mm_packus_epi16(result, _mm_setzero_si128()), _mm_set1_epi32(0xff000000))); + x += 2; + } +#endif + + while (x < x1) { float upix = u + uscale * (x + 0.5f - left); uint32_t spixel = sline[(int)upix]; @@ -656,21 +1052,21 @@ void BitmapCanvas::drawGlyph(CanvasTexture* texture, float left, float top, floa uint32_t g = (cgreen * sgreen + dgreen * (256 - sgreen) + 127) >> 8; uint32_t b = (cblue * sblue + dblue * (256 - sblue) + 127) >> 8; dline[x] = 0xff000000 | (r << 16) | (g << 8) | b; + x++; } } +#endif } 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); } @@ -680,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 new file mode 100644 index 000000000..415224429 --- /dev/null +++ b/libraries/ZWidget/src/core/theme.cpp @@ -0,0 +1,383 @@ + +#include "core/theme.h" +#include "core/widget.h" +#include "core/canvas.h" + +void WidgetStyle::SetBool(const std::string& state, const std::string& propertyName, bool value) +{ + StyleProperties[state][propertyName] = value; +} + +void WidgetStyle::SetInt(const std::string& state, const std::string& propertyName, int value) +{ + StyleProperties[state][propertyName] = value; +} + +void WidgetStyle::SetDouble(const std::string& state, const std::string& propertyName, double value) +{ + StyleProperties[state][propertyName] = value; +} + +void WidgetStyle::SetString(const std::string& state, const std::string& propertyName, const std::string& value) +{ + StyleProperties[state][propertyName] = value; +} + +void WidgetStyle::SetColor(const std::string& state, const std::string& propertyName, const Colorf& value) +{ + StyleProperties[state][propertyName] = value; +} + +const WidgetStyle::PropertyVariant* WidgetStyle::FindProperty(const std::string& state, const std::string& propertyName) const +{ + const WidgetStyle* style = this; + do + { + // Look for property in the specific state + auto stateIt = style->StyleProperties.find(state); + if (stateIt != style->StyleProperties.end()) + { + auto it = stateIt->second.find(propertyName); + if (it != stateIt->second.end()) + return &it->second; + } + + // Fall back to the widget main style + if (state != std::string()) + { + stateIt = style->StyleProperties.find(std::string()); + if (stateIt != style->StyleProperties.end()) + { + auto it = stateIt->second.find(propertyName); + if (it != stateIt->second.end()) + return &it->second; + } + } + + style = style->ParentStyle; + } while (style); + return nullptr; +} + +bool WidgetStyle::GetBool(const std::string& state, const std::string& propertyName) const +{ + const PropertyVariant* prop = FindProperty(state, propertyName); + return prop ? std::get(*prop) : false; +} + +int WidgetStyle::GetInt(const std::string& state, const std::string& propertyName) const +{ + const PropertyVariant* prop = FindProperty(state, propertyName); + return prop ? std::get(*prop) : 0; +} + +double WidgetStyle::GetDouble(const std::string& state, const std::string& propertyName) const +{ + const PropertyVariant* prop = FindProperty(state, propertyName); + return prop ? std::get(*prop) : 0.0; +} + +std::string WidgetStyle::GetString(const std::string& state, const std::string& propertyName) const +{ + const PropertyVariant* prop = FindProperty(state, propertyName); + return prop ? std::get(*prop) : std::string(); +} + +Colorf WidgetStyle::GetColor(const std::string& state, const std::string& propertyName) const +{ + const PropertyVariant* prop = FindProperty(state, propertyName); + return prop ? std::get(*prop) : Colorf::transparent(); +} + +///////////////////////////////////////////////////////////////////////////// + +void BasicWidgetStyle::Paint(Widget* widget, Canvas* canvas, Size size) +{ + Colorf bgcolor = widget->GetStyleColor("background-color"); + if (bgcolor.a > 0.0f) + canvas->fillRect(Rect::xywh(0.0, 0.0, size.width, size.height), bgcolor); + + Colorf borderleft = widget->GetStyleColor("border-left-color"); + Colorf bordertop = widget->GetStyleColor("border-top-color"); + Colorf borderright = widget->GetStyleColor("border-right-color"); + Colorf borderbottom = widget->GetStyleColor("border-bottom-color"); + + double borderwidth = widget->GridFitSize(1.0); + + if (bordertop.a > 0.0f) + canvas->fillRect(Rect::xywh(0.0, 0.0, size.width, borderwidth), bordertop); + if (borderbottom.a > 0.0f) + canvas->fillRect(Rect::xywh(0.0, size.height - borderwidth, size.width, borderwidth), borderbottom); + if (borderleft.a > 0.0f) + canvas->fillRect(Rect::xywh(0.0, 0.0, borderwidth, size.height), borderleft); + if (borderright.a > 0.0f) + canvas->fillRect(Rect::xywh(size.width - borderwidth, 0.0, borderwidth, size.height), borderright); +} + +///////////////////////////////////////////////////////////////////////////// + +static std::unique_ptr CurrentTheme; + +WidgetStyle* WidgetTheme::RegisterStyle(std::unique_ptr widgetStyle, const std::string& widgetClass) +{ + auto& style = Styles[widgetClass]; + style = std::move(widgetStyle); + return style.get(); +} + +WidgetStyle* WidgetTheme::GetStyle(const std::string& widgetClass) +{ + auto it = Styles.find(widgetClass); + return it != Styles.end() ? it->second.get() : nullptr; +} + +void WidgetTheme::SetTheme(std::unique_ptr theme) +{ + CurrentTheme = std::move(theme); +} + +WidgetTheme* WidgetTheme::GetTheme() +{ + return CurrentTheme.get(); +} + +///////////////////////////////////////////////////////////////////////////// + +DarkWidgetTheme::DarkWidgetTheme() +{ + auto widget = RegisterStyle(std::make_unique(), "widget"); + auto textlabel = RegisterStyle(std::make_unique(widget), "textlabel"); + auto pushbutton = RegisterStyle(std::make_unique(widget), "pushbutton"); + auto lineedit = RegisterStyle(std::make_unique(widget), "lineedit"); + auto textedit = RegisterStyle(std::make_unique(widget), "textedit"); + auto listview = RegisterStyle(std::make_unique(widget), "listview"); + auto scrollbar = RegisterStyle(std::make_unique(widget), "scrollbar"); + auto tabbar = RegisterStyle(std::make_unique(widget), "tabbar"); + 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)); + widget->SetColor("window-background", Colorf::fromRgba8(51, 51, 51)); + widget->SetColor("window-border", Colorf::fromRgba8(51, 51, 51)); + widget->SetColor("window-caption-color", Colorf::fromRgba8(33, 33, 33)); + widget->SetColor("window-caption-text-color", Colorf::fromRgba8(226, 223, 219)); + + pushbutton->SetDouble("noncontent-left", 10.0); + pushbutton->SetDouble("noncontent-top", 5.0); + pushbutton->SetDouble("noncontent-right", 10.0); + pushbutton->SetDouble("noncontent-bottom", 5.0); + pushbutton->SetColor("background-color", Colorf::fromRgba8(68, 68, 68)); + pushbutton->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); + pushbutton->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); + pushbutton->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); + pushbutton->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); + pushbutton->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78)); + pushbutton->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88)); + + lineedit->SetDouble("noncontent-left", 5.0); + lineedit->SetDouble("noncontent-top", 3.0); + lineedit->SetDouble("noncontent-right", 5.0); + lineedit->SetDouble("noncontent-bottom", 3.0); + lineedit->SetColor("background-color", Colorf::fromRgba8(38, 38, 38)); + lineedit->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); + lineedit->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); + lineedit->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); + lineedit->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); + lineedit->SetColor("selection-color", Colorf::fromRgba8(100, 100, 100)); + lineedit->SetColor("no-focus-selection-color", Colorf::fromRgba8(68, 68, 68)); + + textedit->SetDouble("noncontent-left", 8.0); + textedit->SetDouble("noncontent-top", 8.0); + textedit->SetDouble("noncontent-right", 8.0); + textedit->SetDouble("noncontent-bottom", 8.0); + textedit->SetColor("background-color", Colorf::fromRgba8(38, 38, 38)); + textedit->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); + textedit->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); + textedit->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); + textedit->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); + + listview->SetDouble("noncontent-left", 10.0); + listview->SetDouble("noncontent-top", 10.0); + listview->SetDouble("noncontent-right", 3.0); + listview->SetDouble("noncontent-bottom", 10.0); + listview->SetColor("background-color", Colorf::fromRgba8(38, 38, 38)); + listview->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); + listview->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); + listview->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); + listview->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); + listview->SetColor("selection-color", Colorf::fromRgba8(100, 100, 100)); + + scrollbar->SetColor("track-color", Colorf::fromRgba8(33, 33, 33)); + scrollbar->SetColor("thumb-color", Colorf::fromRgba8(58, 58, 58)); + + tabbar->SetDouble("noncontent-left", 20.0); + tabbar->SetDouble("noncontent-right", 20.0); + tabbar->SetColor("background-color", Colorf::fromRgba8(38, 38, 38)); + + tabbar_tab->SetDouble("noncontent-left", 15.0); + tabbar_tab->SetDouble("noncontent-right", 15.0); + tabbar_tab->SetColor("hover", "background-color", Colorf::fromRgba8(45, 45, 45)); + tabbar_tab->SetColor("active", "background-color", Colorf::fromRgba8(51, 51, 51)); + + tabwidget_stack->SetDouble("noncontent-left", 20.0); + tabwidget_stack->SetDouble("noncontent-top", 5.0); + tabwidget_stack->SetDouble("noncontent-right", 20.0); + tabwidget_stack->SetDouble("noncontent-bottom", 5.0); + + checkbox_label->SetColor("checked-outer-border-color", Colorf::fromRgba8(100, 100, 100)); + checkbox_label->SetColor("checked-inner-border-color", Colorf::fromRgba8(51, 51, 51)); + checkbox_label->SetColor("checked-color", Colorf::fromRgba8(226, 223, 219)); + 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); + menu->SetDouble("noncontent-right", 5.0); + menu->SetDouble("noncontent-bottom", 5.0); + menu->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); + menu->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); + menu->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); + menu->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); + + menuitem->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78)); + menuitem->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88)); +} + +///////////////////////////////////////////////////////////////////////////// + +LightWidgetTheme::LightWidgetTheme() +{ + auto widget = RegisterStyle(std::make_unique(), "widget"); + auto textlabel = RegisterStyle(std::make_unique(widget), "textlabel"); + auto pushbutton = RegisterStyle(std::make_unique(widget), "pushbutton"); + auto lineedit = RegisterStyle(std::make_unique(widget), "lineedit"); + auto textedit = RegisterStyle(std::make_unique(widget), "textedit"); + auto listview = RegisterStyle(std::make_unique(widget), "listview"); + auto scrollbar = RegisterStyle(std::make_unique(widget), "scrollbar"); + auto tabbar = RegisterStyle(std::make_unique(widget), "tabbar"); + 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"); + + widget->SetString("font-family", "NotoSans"); + widget->SetColor("color", Colorf::fromRgba8(0, 0, 0)); + widget->SetColor("window-background", Colorf::fromRgba8(240, 240, 240)); + widget->SetColor("window-border", Colorf::fromRgba8(100, 100, 100)); + widget->SetColor("window-caption-color", Colorf::fromRgba8(70, 70, 70)); + widget->SetColor("window-caption-text-color", Colorf::fromRgba8(226, 223, 219)); + + pushbutton->SetDouble("noncontent-left", 10.0); + pushbutton->SetDouble("noncontent-top", 5.0); + pushbutton->SetDouble("noncontent-right", 10.0); + pushbutton->SetDouble("noncontent-bottom", 5.0); + pushbutton->SetColor("background-color", Colorf::fromRgba8(210, 210, 210)); + pushbutton->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); + pushbutton->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); + pushbutton->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); + pushbutton->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); + pushbutton->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200)); + pushbutton->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190)); + + lineedit->SetDouble("noncontent-left", 5.0); + lineedit->SetDouble("noncontent-top", 3.0); + lineedit->SetDouble("noncontent-right", 5.0); + lineedit->SetDouble("noncontent-bottom", 3.0); + lineedit->SetColor("background-color", Colorf::fromRgba8(255, 255, 255)); + lineedit->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); + lineedit->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); + lineedit->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); + lineedit->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); + lineedit->SetColor("selection-color", Colorf::fromRgba8(210, 210, 255)); + lineedit->SetColor("no-focus-selection-color", Colorf::fromRgba8(240, 240, 255)); + + textedit->SetDouble("noncontent-left", 8.0); + textedit->SetDouble("noncontent-top", 8.0); + textedit->SetDouble("noncontent-right", 8.0); + textedit->SetDouble("noncontent-bottom", 8.0); + textedit->SetColor("background-color", Colorf::fromRgba8(255, 255, 255)); + textedit->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); + textedit->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); + textedit->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); + textedit->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); + + listview->SetDouble("noncontent-left", 10.0); + listview->SetDouble("noncontent-top", 10.0); + listview->SetDouble("noncontent-right", 3.0); + listview->SetDouble("noncontent-bottom", 10.0); + listview->SetColor("background-color", Colorf::fromRgba8(230, 230, 230)); + listview->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); + listview->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); + listview->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); + listview->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); + listview->SetColor("selection-color", Colorf::fromRgba8(200, 200, 200)); + + scrollbar->SetColor("track-color", Colorf::fromRgba8(210, 210, 220)); + scrollbar->SetColor("thumb-color", Colorf::fromRgba8(180, 180, 180)); + + tabbar->SetDouble("noncontent-left", 20.0); + tabbar->SetDouble("noncontent-right", 20.0); + tabbar->SetColor("background-color", Colorf::fromRgba8(220, 220, 220)); + + tabbar_tab->SetDouble("noncontent-left", 15.0); + tabbar_tab->SetDouble("noncontent-right", 15.0); + tabbar_tab->SetColor("hover", "background-color", Colorf::fromRgba8(210, 210, 210)); + tabbar_tab->SetColor("active", "background-color", Colorf::fromRgba8(240, 240, 240)); + + tabwidget_stack->SetDouble("noncontent-left", 20.0); + tabwidget_stack->SetDouble("noncontent-top", 5.0); + tabwidget_stack->SetDouble("noncontent-right", 20.0); + tabwidget_stack->SetDouble("noncontent-bottom", 5.0); + + checkbox_label->SetColor("checked-outer-border-color", Colorf::fromRgba8(155, 155, 155)); + checkbox_label->SetColor("checked-inner-border-color", Colorf::fromRgba8(200, 200, 200)); + checkbox_label->SetColor("checked-color", Colorf::fromRgba8(50, 50, 50)); + 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)); + menu->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); + + menuitem->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200)); + menuitem->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190)); +} diff --git a/libraries/ZWidget/src/core/timer.cpp b/libraries/ZWidget/src/core/timer.cpp index 4c748561b..5ebe2612f 100644 --- a/libraries/ZWidget/src/core/timer.cpp +++ b/libraries/ZWidget/src/core/timer.cpp @@ -13,6 +13,8 @@ Timer::Timer(Widget* owner) : OwnerObj(owner) Timer::~Timer() { + Stop(); + if (PrevTimerObj) PrevTimerObj->NextTimerObj = NextTimerObj; if (NextTimerObj) diff --git a/libraries/ZWidget/src/core/truetypefont.cpp b/libraries/ZWidget/src/core/truetypefont.cpp index 3f71c058d..757929e3e 100644 --- a/libraries/ZWidget/src/core/truetypefont.cpp +++ b/libraries/ZWidget/src/core/truetypefont.cpp @@ -11,7 +11,7 @@ #include #endif -TrueTypeFont::TrueTypeFont(std::shared_ptr& initdata, int ttcFontIndex) : data(initdata) +TrueTypeFont::TrueTypeFont(std::shared_ptr initdata, int ttcFontIndex) : data(std::move(initdata)) { if (data->size() > 0x7fffffff) throw std::runtime_error("TTF file is larger than 2 gigabytes!"); @@ -23,7 +23,7 @@ TrueTypeFont::TrueTypeFont(std::shared_ptr& initdata, int 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/truetypefont.h b/libraries/ZWidget/src/core/truetypefont.h index 5fd4c5099..53d8837fc 100644 --- a/libraries/ZWidget/src/core/truetypefont.h +++ b/libraries/ZWidget/src/core/truetypefont.h @@ -445,7 +445,7 @@ public: class TrueTypeFontFileData { public: - TrueTypeFontFileData(std::vector& data) : dataVector(std::move(data)) + TrueTypeFontFileData(std::vector data) : dataVector(std::move(data)) { dataPtr = dataVector.data(); dataSize = dataVector.size(); @@ -491,7 +491,7 @@ private: class TrueTypeFont { public: - TrueTypeFont(std::shared_ptr& data, int ttcFontIndex = 0); + TrueTypeFont(std::shared_ptr data, int ttcFontIndex = 0); static std::vector GetFontNames(const std::shared_ptr& data); diff --git a/libraries/ZWidget/src/core/widget.cpp b/libraries/ZWidget/src/core/widget.cpp index 2bbbf21f2..e0c637f08 100644 --- a/libraries/ZWidget/src/core/widget.cpp +++ b/libraries/ZWidget/src/core/widget.cpp @@ -2,14 +2,31 @@ #include "core/widget.h" #include "core/timer.h" #include "core/colorf.h" +#include "core/theme.h" #include +#include +#include -Widget::Widget(Widget* parent, WidgetType type) : Type(type) +Widget::Widget(Widget* parent, WidgetType type, RenderAPI renderAPI) : Type(type) { if (type != WidgetType::Child) { - DispWindow = DisplayWindow::Create(this); - DispCanvas = Canvas::create(DispWindow.get()); + Widget* owner = parent ? parent->Window() : nullptr; + 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")); + if (GetStyleColor("window-border").a > 0.0f) + SetWindowBorderColor(GetStyleColor("window-border")); + if (GetStyleColor("window-caption-color").a > 0.0f) + SetWindowCaptionColor(GetStyleColor("window-caption-color")); + if (GetStyleColor("window-caption-text-color").a > 0.0f) + SetWindowCaptionTextColor(GetStyleColor("window-caption-text-color")); } SetParent(parent); @@ -17,6 +34,9 @@ Widget::Widget(Widget* parent, WidgetType type) : Type(type) Widget::~Widget() { + if (DispCanvas) + DispCanvas->detach(); + while (LastChildObj) delete LastChildObj; @@ -26,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) @@ -138,10 +169,10 @@ Rect Widget::GetFrameGeometry() const void Widget::SetNoncontentSizes(double left, double top, double right, double bottom) { - Noncontent.Left = left; - Noncontent.Top = top; - Noncontent.Right = right; - Noncontent.Bottom = bottom; + SetStyleDouble("noncontent-left", left); + SetStyleDouble("noncontent-top", top); + SetStyleDouble("noncontent-right", right); + SetStyleDouble("noncontent-bottom", bottom); } void Widget::SetFrameGeometry(const Rect& geometry) @@ -149,14 +180,18 @@ void Widget::SetFrameGeometry(const Rect& geometry) if (Type == WidgetType::Child) { FrameGeometry = geometry; - double left = FrameGeometry.left() + Noncontent.Left; - double top = FrameGeometry.top() + Noncontent.Top; - double right = FrameGeometry.right() - Noncontent.Right; - double bottom = FrameGeometry.bottom() - Noncontent.Bottom; + double left = FrameGeometry.left() + GetNoncontentLeft(); + double top = FrameGeometry.top() + GetNoncontentTop(); + double right = FrameGeometry.right() - GetNoncontentRight(); + double bottom = FrameGeometry.bottom() - GetNoncontentBottom(); left = std::min(left, FrameGeometry.right()); top = std::min(top, FrameGeometry.bottom()); right = std::max(right, FrameGeometry.left()); bottom = std::max(bottom, FrameGeometry.top()); + left = GridFitPoint(left); + top = GridFitPoint(top); + right = GridFitPoint(right); + bottom = GridFitPoint(bottom); ContentGeometry = Rect::ltrb(left, top, right, bottom); OnGeometryChanged(); } @@ -187,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) @@ -290,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) @@ -316,7 +363,16 @@ void Widget::Paint(Canvas* canvas) canvas->popClip(); } -bool Widget::GetKeyState(EInputKey key) +void Widget::OnPaintFrame(Canvas* canvas) +{ + WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass); + if (style) + { + style->Paint(this, canvas, GetFrameGeometry().size()); + } +} + +bool Widget::GetKeyState(InputKey key) { Widget* window = Window(); return window ? window->DispWindow->GetKeyState(key) : false; @@ -403,7 +459,7 @@ void Widget::SetCursor(StandardCursor cursor) } } -void Widget::CaptureMouse() +void Widget::SetPointerCapture() { Widget* w = Window(); if (w && w->CaptureWidget != this) @@ -413,7 +469,7 @@ void Widget::CaptureMouse() } } -void Widget::ReleaseMouseCapture() +void Widget::ReleasePointerCapture() { Widget* w = Window(); if (w && w->CaptureWidget != nullptr) @@ -423,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(); @@ -439,12 +513,12 @@ void Widget::SetClipboardText(const std::string& text) w->DispWindow->SetClipboardText(text); } -Widget* Widget::Window() +Widget* Widget::Window() const { - for (Widget* w = this; w != nullptr; w = w->Parent()) + for (const Widget* w = this; w != nullptr; w = w->Parent()) { if (w->DispWindow) - return w; + return const_cast(w); } return nullptr; } @@ -459,11 +533,29 @@ 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()) { - if (!cur->HiddenFlag && cur->FrameGeometry.contains(pos)) + if (cur->Type == WidgetType::Child && !cur->HiddenFlag && cur->FrameGeometry.contains(pos)) { Widget* cur2 = cur->ChildAt(pos - cur->ContentGeometry.topLeft()); return cur2 ? cur2 : cur; @@ -491,7 +583,7 @@ Point Widget::MapFromGlobal(const Point& pos) const { if (cur->DispWindow) { - return p - cur->GetFrameGeometry().topLeft(); + return cur->DispWindow->MapFromGlobal(p); } p -= cur->ContentGeometry.topLeft(); } @@ -517,7 +609,7 @@ Point Widget::MapToGlobal(const Point& pos) const { if (cur->DispWindow) { - return cur->GetFrameGeometry().topLeft() + p; + return cur->DispWindow->MapToGlobal(p); } p += cur->ContentGeometry.topLeft(); } @@ -570,7 +662,19 @@ void Widget::OnWindowMouseMove(const Point& pos) } } -void Widget::OnWindowMouseDown(const Point& pos, EInputKey key) +void Widget::OnWindowMouseLeave() +{ + if (HoverWidget) + { + for (Widget* w = HoverWidget; w; w = w->Parent()) + { + w->OnMouseLeave(); + } + HoverWidget = nullptr; + } +} + +void Widget::OnWindowMouseDown(const Point& pos, InputKey key) { if (CaptureWidget) { @@ -591,7 +695,7 @@ void Widget::OnWindowMouseDown(const Point& pos, EInputKey key) } } -void Widget::OnWindowMouseDoubleclick(const Point& pos, EInputKey key) +void Widget::OnWindowMouseDoubleclick(const Point& pos, InputKey key) { if (CaptureWidget) { @@ -612,7 +716,7 @@ void Widget::OnWindowMouseDoubleclick(const Point& pos, EInputKey key) } } -void Widget::OnWindowMouseUp(const Point& pos, EInputKey key) +void Widget::OnWindowMouseUp(const Point& pos, InputKey key) { if (CaptureWidget) { @@ -633,7 +737,7 @@ void Widget::OnWindowMouseUp(const Point& pos, EInputKey key) } } -void Widget::OnWindowMouseWheel(const Point& pos, EInputKey key) +void Widget::OnWindowMouseWheel(const Point& pos, InputKey key) { if (CaptureWidget) { @@ -672,13 +776,13 @@ void Widget::OnWindowKeyChar(std::string chars) FocusWidget->OnKeyChar(chars); } -void Widget::OnWindowKeyDown(EInputKey key) +void Widget::OnWindowKeyDown(InputKey key) { if (FocusWidget) FocusWidget->OnKeyDown(key); } -void Widget::OnWindowKeyUp(EInputKey key) +void Widget::OnWindowKeyUp(InputKey key) { if (FocusWidget) FocusWidget->OnKeyUp(key); @@ -686,9 +790,21 @@ void Widget::OnWindowKeyUp(EInputKey key) void Widget::OnWindowGeometryChanged() { + if (!DispWindow) + return; Size size = DispWindow->GetClientSize(); FrameGeometry = Rect::xywh(0.0, 0.0, size.width, size.height); - ContentGeometry = FrameGeometry; + + double left = FrameGeometry.left() + GetNoncontentLeft(); + double top = FrameGeometry.top() + GetNoncontentTop(); + double right = FrameGeometry.right() - GetNoncontentRight(); + double bottom = FrameGeometry.bottom() - GetNoncontentBottom(); + left = std::min(left, FrameGeometry.right()); + top = std::min(top, FrameGeometry.bottom()); + right = std::max(right, FrameGeometry.left()); + bottom = std::max(bottom, FrameGeometry.top()); + ContentGeometry = Rect::ltrb(left, top, right, bottom); + OnGeometryChanged(); } @@ -709,7 +825,133 @@ void Widget::OnWindowDpiScaleChanged() { } +double Widget::GetDpiScale() const +{ + Widget* w = Window(); + return w ? w->DispWindow->GetDpiScale() : 1.0; +} + +double Widget::GridFitPoint(double p) const +{ + double dpiscale = GetDpiScale(); + return std::round(p * dpiscale) / dpiscale; +} + +double Widget::GridFitSize(double s) const +{ + if (s <= 0.0) + return 0.0; + double dpiscale = GetDpiScale(); + return std::max(std::floor(s * dpiscale + 0.25), 1.0) / dpiscale; +} + Size Widget::GetScreenSize() { return DisplayWindow::GetScreenSize(); } + +void* Widget::GetNativeHandle() +{ + Widget* w = Window(); + 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) + { + StyleClass = themeClass; + Update(); + } +} + +void Widget::SetStyleState(const std::string& state) +{ + if (StyleState != state) + { + StyleState = state; + Update(); + } +} + +void Widget::SetStyleBool(const std::string& propertyName, bool value) +{ + StyleProperties[propertyName] = value; +} + +void Widget::SetStyleInt(const std::string& propertyName, int value) +{ + StyleProperties[propertyName] = value; +} + +void Widget::SetStyleDouble(const std::string& propertyName, double value) +{ + StyleProperties[propertyName] = value; +} + +void Widget::SetStyleString(const std::string& propertyName, const std::string& value) +{ + StyleProperties[propertyName] = value; +} + +void Widget::SetStyleColor(const std::string& propertyName, const Colorf& value) +{ + StyleProperties[propertyName] = value; +} + +bool Widget::GetStyleBool(const std::string& propertyName) const +{ + auto it = StyleProperties.find(propertyName); + if (it != StyleProperties.end()) + return std::get(it->second); + WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass); + return style ? style->GetBool(StyleState, propertyName) : false; +} + +int Widget::GetStyleInt(const std::string& propertyName) const +{ + auto it = StyleProperties.find(propertyName); + if (it != StyleProperties.end()) + return std::get(it->second); + WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass); + return style ? style->GetInt(StyleState, propertyName) : 0; +} + +double Widget::GetStyleDouble(const std::string& propertyName) const +{ + auto it = StyleProperties.find(propertyName); + if (it != StyleProperties.end()) + return std::get(it->second); + WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass); + return style ? style->GetDouble(StyleState, propertyName) : 0.0; +} + +std::string Widget::GetStyleString(const std::string& propertyName) const +{ + auto it = StyleProperties.find(propertyName); + if (it != StyleProperties.end()) + return std::get(it->second); + WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass); + return style ? style->GetString(StyleState, propertyName) : std::string(); +} + +Colorf Widget::GetStyleColor(const std::string& propertyName) const +{ + auto it = StyleProperties.find(propertyName); + if (it != StyleProperties.end()) + return std::get(it->second); + WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass); + return style ? style->GetColor(StyleState, propertyName) : Colorf::transparent(); +} diff --git a/libraries/ZWidget/src/systemdialogs/open_file_dialog.cpp b/libraries/ZWidget/src/systemdialogs/open_file_dialog.cpp new file mode 100644 index 000000000..6d0ce3931 --- /dev/null +++ b/libraries/ZWidget/src/systemdialogs/open_file_dialog.cpp @@ -0,0 +1,12 @@ + +#include "systemdialogs/open_file_dialog.h" +#include "window/window.h" +#include "core/widget.h" + +std::unique_ptr OpenFileDialog::Create(Widget* owner) +{ + DisplayWindow* windowOwner = nullptr; + if (owner && owner->Window()) + windowOwner = owner->Window()->DispWindow.get(); + return DisplayBackend::Get()->CreateOpenFileDialog(windowOwner); +} 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 new file mode 100644 index 000000000..0163fe7cb --- /dev/null +++ b/libraries/ZWidget/src/systemdialogs/save_file_dialog.cpp @@ -0,0 +1,12 @@ + +#include "systemdialogs/save_file_dialog.h" +#include "window/window.h" +#include "core/widget.h" + +std::unique_ptr SaveFileDialog::Create(Widget* owner) +{ + DisplayWindow* windowOwner = nullptr; + if (owner && owner->Window()) + windowOwner = owner->Window()->DispWindow.get(); + return DisplayBackend::Get()->CreateSaveFileDialog(windowOwner); +} diff --git a/libraries/ZWidget/src/widgets/checkboxlabel/checkboxlabel.cpp b/libraries/ZWidget/src/widgets/checkboxlabel/checkboxlabel.cpp index 6cf90badf..77c1bd645 100644 --- a/libraries/ZWidget/src/widgets/checkboxlabel/checkboxlabel.cpp +++ b/libraries/ZWidget/src/widgets/checkboxlabel/checkboxlabel.cpp @@ -3,6 +3,7 @@ CheckboxLabel::CheckboxLabel(Widget* parent) : Widget(parent) { + SetStyleClass("checkbox-label"); } void CheckboxLabel::SetText(const std::string& value) @@ -40,30 +41,37 @@ double CheckboxLabel::GetPreferredHeight() const void CheckboxLabel::OnPaint(Canvas* canvas) { + // To do: add and use GetStyleImage for the checkbox + + double center = GridFitPoint(GetHeight() * 0.5); + double borderwidth = GridFitSize(1.0); + double outerboxsize = GridFitSize(10.0); + double innerboxsize = outerboxsize - 2.0 * borderwidth; + double checkedsize = innerboxsize - 2.0 * borderwidth; if (checked) { - canvas->fillRect(Rect::xywh(0.0, GetHeight() * 0.5 - 6.0, 10.0, 10.0), Colorf::fromRgba8(100, 100, 100)); - canvas->fillRect(Rect::xywh(1.0, GetHeight() * 0.5 - 5.0, 8.0, 8.0), Colorf::fromRgba8(51, 51, 51)); - canvas->fillRect(Rect::xywh(2.0, GetHeight() * 0.5 - 4.0, 6.0, 6.0), Colorf::fromRgba8(226, 223, 219)); + canvas->fillRect(Rect::xywh(0.0, center - 6.0 * borderwidth, outerboxsize, outerboxsize), GetStyleColor("checked-outer-border-color")); + canvas->fillRect(Rect::xywh(1.0 * borderwidth, center - 5.0 * borderwidth, innerboxsize, innerboxsize), GetStyleColor("checked-inner-border-color")); + canvas->fillRect(Rect::xywh(2.0 * borderwidth, center - 4.0 * borderwidth, checkedsize, checkedsize), GetStyleColor("checked-color")); } else { - canvas->fillRect(Rect::xywh(0.0, GetHeight() * 0.5 - 6.0, 10.0, 10.0), Colorf::fromRgba8(99, 99, 99)); - canvas->fillRect(Rect::xywh(1.0, GetHeight() * 0.5 - 5.0, 8.0, 8.0), Colorf::fromRgba8(51, 51, 51)); + canvas->fillRect(Rect::xywh(0.0, center - 6.0 * borderwidth, outerboxsize, outerboxsize), GetStyleColor("unchecked-outer-border-color")); + canvas->fillRect(Rect::xywh(1.0 * borderwidth, center - 5.0 * borderwidth, innerboxsize, innerboxsize), GetStyleColor("unchecked-inner-border-color")); } - canvas->drawText(Point(14.0, GetHeight() - 5.0), Colorf::fromRgba8(255, 255, 255), text); + canvas->drawText(Point(14.0, GetHeight() - 5.0), GetStyleColor("color"), text); } -bool CheckboxLabel::OnMouseDown(const Point& pos, int key) +bool CheckboxLabel::OnMouseDown(const Point& pos, InputKey key) { mouseDownActive = true; SetFocus(); return true; } -bool CheckboxLabel::OnMouseUp(const Point& pos, int key) +bool CheckboxLabel::OnMouseUp(const Point& pos, InputKey key) { if (mouseDownActive) { @@ -78,9 +86,9 @@ void CheckboxLabel::OnMouseLeave() mouseDownActive = false; } -void CheckboxLabel::OnKeyUp(EInputKey key) +void CheckboxLabel::OnKeyUp(InputKey key) { - if (key == IK_Space) + if (key == InputKey::Space) Toggle(); } diff --git a/libraries/ZWidget/src/widgets/imagebox/imagebox.cpp b/libraries/ZWidget/src/widgets/imagebox/imagebox.cpp index 45d35a160..70323cc89 100644 --- a/libraries/ZWidget/src/widgets/imagebox/imagebox.cpp +++ b/libraries/ZWidget/src/widgets/imagebox/imagebox.cpp @@ -5,6 +5,14 @@ ImageBox::ImageBox(Widget* parent) : Widget(parent) { } +double ImageBox::GetPreferredWidth() const +{ + if (image) + return (double)image->GetWidth(); + else + return 0.0; +} + double ImageBox::GetPreferredHeight() const { if (image) @@ -22,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 9065ccde4..b34a18c5e 100644 --- a/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp +++ b/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp @@ -1,11 +1,12 @@ -#include + #include "widgets/lineedit/lineedit.h" #include "core/utf8reader.h" #include "core/colorf.h" +#include LineEdit::LineEdit(Widget* parent) : Widget(parent) { - SetNoncontentSizes(5.0, 3.0, 5.0, 3.0); + SetStyleClass("lineedit"); timer = new Timer(this); timer->FuncExpired = [=]() { OnTimerExpired(); }; @@ -18,8 +19,6 @@ LineEdit::LineEdit(Widget* parent) : Widget(parent) LineEdit::~LineEdit() { - delete timer; - delete scroll_timer; } bool LineEdit::IsReadOnly() const @@ -303,13 +302,13 @@ void LineEdit::OnMouseMove(const Point& pos) } } -bool LineEdit::OnMouseDown(const Point& pos, int key) +bool LineEdit::OnMouseDown(const Point& pos, InputKey key) { - if (key == IK_LeftMouse) + if (key == InputKey::LeftMouse) { if (HasFocus()) { - CaptureMouse(); + SetPointerCapture(); mouse_selecting = true; cursor_pos = GetCharacterIndex(pos.x); SetTextSelection(cursor_pos, 0); @@ -323,25 +322,25 @@ bool LineEdit::OnMouseDown(const Point& pos, int key) return true; } -bool LineEdit::OnMouseDoubleclick(const Point& pos, int key) +bool LineEdit::OnMouseDoubleclick(const Point& pos, InputKey key) { return true; } -bool LineEdit::OnMouseUp(const Point& pos, int key) +bool LineEdit::OnMouseUp(const Point& pos, InputKey key) { - if (mouse_selecting && key == IK_LeftMouse) + if (mouse_selecting && key == InputKey::LeftMouse) { 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); @@ -414,12 +413,12 @@ void LineEdit::OnKeyChar(std::string chars) } } -void LineEdit::OnKeyDown(EInputKey key) +void LineEdit::OnKeyDown(InputKey key) { if (FuncIgnoreKeyDown && FuncIgnoreKeyDown(key)) return; - if (key == IK_Enter) + if (key == InputKey::Enter) { if (FuncEnterPressed) FuncEnterPressed(); @@ -432,12 +431,12 @@ void LineEdit::OnKeyDown(EInputKey key) timer->Start(500); // don't blink cursor when moving or typing. } - if (key == IK_Enter || key == IK_Escape || key == IK_Tab) + if (key == InputKey::Enter || key == InputKey::Escape || key == InputKey::Tab) { // Do not consume these. return; } - else if (key == IK_A && GetKeyState(IK_Ctrl)) + else if (key == InputKey::A && GetKeyState(InputKey::Ctrl)) { // select all SetTextSelection(0, (int)text.size()); @@ -445,7 +444,7 @@ void LineEdit::OnKeyDown(EInputKey key) UpdateTextClipping(); Update(); } - else if (key == IK_C && GetKeyState(IK_Ctrl)) + else if (key == InputKey::C && GetKeyState(InputKey::Ctrl)) { if (!password_mode) // Do not allow copying the password to clipboard { @@ -458,54 +457,54 @@ void LineEdit::OnKeyDown(EInputKey key) // Do not consume messages on read only component (only allow CTRL-A and CTRL-C) return; } - else if (key == IK_Left) + else if (key == InputKey::Left) { - Move(-1, GetKeyState(IK_Ctrl), GetKeyState(IK_Shift)); + Move(-1, GetKeyState(InputKey::Ctrl), GetKeyState(InputKey::Shift)); } - else if (key == IK_Right) + else if (key == InputKey::Right) { - Move(1, GetKeyState(IK_Ctrl), GetKeyState(IK_Shift)); + Move(1, GetKeyState(InputKey::Ctrl), GetKeyState(InputKey::Shift)); } - else if (key == IK_Backspace) + else if (key == InputKey::Backspace) { Backspace(); UpdateTextClipping(); } - else if (key == IK_Delete) + else if (key == InputKey::Delete) { Del(); UpdateTextClipping(); } - else if (key == IK_Home) + else if (key == InputKey::Home) { SetSelectionStart(cursor_pos); cursor_pos = 0; - if (GetKeyState(IK_Shift)) + if (GetKeyState(InputKey::Shift)) SetSelectionLength(-selection_start); else SetTextSelection(0, 0); UpdateTextClipping(); Update(); } - else if (key == IK_End) + else if (key == InputKey::End) { SetSelectionStart(cursor_pos); cursor_pos = (int)text.size(); - if (GetKeyState(IK_Shift)) + if (GetKeyState(InputKey::Shift)) SetSelectionLength((int)text.size() - selection_start); else SetTextSelection(0, 0); UpdateTextClipping(); Update(); } - else if (key == IK_X && GetKeyState(IK_Ctrl)) + else if (key == InputKey::X && GetKeyState(InputKey::Ctrl)) { std::string str = GetSelection(); DeleteSelectedText(); SetClipboardText(str); UpdateTextClipping(); } - else if (key == IK_V && GetKeyState(IK_Ctrl)) + else if (key == InputKey::V && GetKeyState(InputKey::Ctrl)) { std::string str = GetClipboardText(); std::string::const_iterator end_str = std::remove(str.begin(), str.end(), '\n'); @@ -576,7 +575,7 @@ void LineEdit::OnKeyDown(EInputKey key) UpdateTextClipping(); } - else if (GetKeyState(IK_Ctrl) && key == IK_Z) + else if (GetKeyState(InputKey::Ctrl) && key == InputKey::Z) { if (!readonly) { @@ -585,7 +584,7 @@ void LineEdit::OnKeyDown(EInputKey key) SetText(tmp); } } - else if (key == IK_Shift) + else if (key == InputKey::Shift) { if (selection_start == -1) SetTextSelection(cursor_pos, 0); @@ -595,7 +594,7 @@ void LineEdit::OnKeyDown(EInputKey key) FuncAfterEditChanged(); } -void LineEdit::OnKeyUp(EInputKey key) +void LineEdit::OnKeyUp(InputKey key) { } @@ -817,6 +816,8 @@ int LineEdit::GetCharacterIndex(double mouse_x) void LineEdit::UpdateTextClipping() { Canvas* canvas = GetCanvas(); + if (!canvas) + return; Size text_size = GetVisualTextSize(canvas, clip_start_offset, (int)text.size() - clip_start_offset); @@ -1067,18 +1068,6 @@ std::string LineEdit::GetVisibleTextAfterSelection() } } -void LineEdit::OnPaintFrame(Canvas* canvas) -{ - double w = GetFrameGeometry().width; - double h = GetFrameGeometry().height; - Colorf bordercolor = Colorf::fromRgba8(100, 100, 100); - canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(38, 38, 38)); - canvas->fillRect(Rect::xywh(0.0, 0.0, w, 1.0), bordercolor); - canvas->fillRect(Rect::xywh(0.0, h - 1.0, w, 1.0), bordercolor); - canvas->fillRect(Rect::xywh(0.0, 0.0, 1.0, h - 0.0), bordercolor); - canvas->fillRect(Rect::xywh(w - 1.0, 0.0, 1.0, h - 0.0), bordercolor); -} - void LineEdit::OnPaint(Canvas* canvas) { std::string txt_before = GetVisibleTextBeforeSelection(); @@ -1101,21 +1090,21 @@ void LineEdit::OnPaint(Canvas* canvas) { // Draw selection box. Rect selection_rect = GetSelectionRect(); - canvas->fillRect(selection_rect, HasFocus() ? Colorf::fromRgba8(100, 100, 100) : Colorf::fromRgba8(68, 68, 68)); + canvas->fillRect(selection_rect, HasFocus() ? GetStyleColor("selection-color") : GetStyleColor("no-focus-selection-color")); } // Draw text before selection if (!txt_before.empty()) { - canvas->drawText(Point(0.0, canvas->verticalTextAlign().baseline), Colorf::fromRgba8(255, 255, 255), txt_before); + canvas->drawText(Point(0.0, canvas->verticalTextAlign().baseline), GetStyleColor("color"), txt_before); } if (!txt_selected.empty()) { - canvas->drawText(Point(size_before.width, canvas->verticalTextAlign().baseline), Colorf::fromRgba8(255, 255, 255), txt_selected); + canvas->drawText(Point(size_before.width, canvas->verticalTextAlign().baseline), GetStyleColor("color"), txt_selected); } if (!txt_after.empty()) { - canvas->drawText(Point(size_before.width + size_selected.width, canvas->verticalTextAlign().baseline), Colorf::fromRgba8(255, 255, 255), txt_after); + canvas->drawText(Point(size_before.width + size_selected.width, canvas->verticalTextAlign().baseline), GetStyleColor("color"), txt_after); } // draw cursor @@ -1124,7 +1113,7 @@ void LineEdit::OnPaint(Canvas* canvas) if (cursor_blink_visible) { Rect cursor_rect = GetCursorRect(); - canvas->fillRect(cursor_rect, Colorf::fromRgba8(255, 255, 255)); + canvas->fillRect(cursor_rect, GetStyleColor("color")); } } } diff --git a/libraries/ZWidget/src/widgets/listview/listview.cpp b/libraries/ZWidget/src/widgets/listview/listview.cpp index a8dd066d0..146d9bc23 100644 --- a/libraries/ZWidget/src/widgets/listview/listview.cpp +++ b/libraries/ZWidget/src/widgets/listview/listview.cpp @@ -4,7 +4,7 @@ ListView::ListView(Widget* parent) : Widget(parent) { - SetNoncontentSizes(10.0, 10.0, 3.0, 10.0); + SetStyleClass("listview"); scrollbar = new Scrollbar(this); scrollbar->FuncScroll = [=]() { OnScrollbarScroll(); }; @@ -28,7 +28,7 @@ void ListView::SetColumnWidths(const std::vector& widths) while (column.size() > newWidth) { updated = true; - column.erase(column.end()); + column.pop_back(); } } @@ -146,6 +146,9 @@ void ListView::OnPaint(Canvas* canvas) double w = GetWidth() - scrollbar->GetPreferredWidth() - 2.0; double h = 20.0; + Colorf textColor = GetStyleColor("color"); + Colorf selectionColor = GetStyleColor("selection-color"); + int index = 0; for (const std::vector& item : items) { @@ -154,12 +157,12 @@ void ListView::OnPaint(Canvas* canvas) { if (index == selectedItem) { - canvas->fillRect(Rect::xywh(x - 2.0, itemY, w, h), Colorf::fromRgba8(100, 100, 100)); + canvas->fillRect(Rect::xywh(x - 2.0, itemY, w, h), selectionColor); } double cx = x; for (size_t entry = 0u; entry < item.size(); ++entry) { - canvas->drawText(Point(cx, y + 15.0), Colorf::fromRgba8(255, 255, 255), item[entry]); + canvas->drawText(Point(cx, y + 15.0), textColor, item[entry]); cx += columnwidths[entry]; } } @@ -168,23 +171,11 @@ void ListView::OnPaint(Canvas* canvas) } } -void ListView::OnPaintFrame(Canvas* canvas) -{ - double w = GetFrameGeometry().width; - double h = GetFrameGeometry().height; - Colorf bordercolor = Colorf::fromRgba8(100, 100, 100); - canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(38, 38, 38)); - canvas->fillRect(Rect::xywh(0.0, 0.0, w, 1.0), bordercolor); - canvas->fillRect(Rect::xywh(0.0, h - 1.0, w, 1.0), bordercolor); - canvas->fillRect(Rect::xywh(0.0, 0.0, 1.0, h - 0.0), bordercolor); - canvas->fillRect(Rect::xywh(w - 1.0, 0.0, 1.0, h - 0.0), bordercolor); -} - -bool ListView::OnMouseDown(const Point& pos, int key) +bool ListView::OnMouseDown(const Point& pos, InputKey key) { SetFocus(); - if (key == IK_LeftMouse) + if (key == InputKey::LeftMouse) { int index = (int)((pos.y - 5.0 + scrollbar->GetPosition()) / 20.0); if (index >= 0 && (size_t)index < items.size()) @@ -196,31 +187,31 @@ bool ListView::OnMouseDown(const Point& pos, int key) return true; } -bool ListView::OnMouseDoubleclick(const Point& pos, int key) +bool ListView::OnMouseDoubleclick(const Point& pos, InputKey key) { - if (key == IK_LeftMouse) + if (key == InputKey::LeftMouse) { Activate(); } return true; } -bool ListView::OnMouseWheel(const Point& pos, EInputKey key) +bool ListView::OnMouseWheel(const Point& pos, InputKey key) { - if (key == IK_MouseWheelUp) + if (key == InputKey::MouseWheelUp) { scrollbar->SetPosition(std::max(scrollbar->GetPosition() - 20.0, 0.0)); } - else if (key == IK_MouseWheelDown) + else if (key == InputKey::MouseWheelDown) { scrollbar->SetPosition(std::min(scrollbar->GetPosition() + 20.0, scrollbar->GetMax())); } return true; } -void ListView::OnKeyDown(EInputKey key) +void ListView::OnKeyDown(InputKey key) { - if (key == IK_Down) + if (key == InputKey::Down) { if (selectedItem + 1 < (int)items.size()) { @@ -228,7 +219,7 @@ void ListView::OnKeyDown(EInputKey key) } ScrollToItem(selectedItem); } - else if (key == IK_Up) + else if (key == InputKey::Up) { if (selectedItem > 0) { @@ -236,7 +227,7 @@ void ListView::OnKeyDown(EInputKey key) } ScrollToItem(selectedItem); } - else if (key == IK_Enter) + else if (key == InputKey::Enter) { Activate(); } 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 8a66ce8f3..37353756f 100644 --- a/libraries/ZWidget/src/widgets/menubar/menubar.cpp +++ b/libraries/ZWidget/src/widgets/menubar/menubar.cpp @@ -4,13 +4,387 @@ Menubar::Menubar(Widget* parent) : Widget(parent) { + SetStyleClass("menubar"); } Menubar::~Menubar() { } -void Menubar::OnPaint(Canvas* canvas) +MenubarItem* Menubar::AddItem(std::string text, std::function onOpen, bool alignRight) { - canvas->drawText(Point(16.0, 21.0), Colorf::fromRgba8(0, 0, 0), "File Edit View Tools Window Help"); + auto item = new MenubarItem(this, text, alignRight); + item->SetOpenCallback(std::move(onOpen)); + menuItems.push_back(item); + OnGeometryChanged(); + return item; +} + +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); + openMenu->onCloseMenu = [=]() { CloseMenu(); }; + item->GetOpenCallback()(openMenu); + if (item->AlignRight) + openMenu->SetRightPosition(item->MapToGlobal(Point(item->GetWidth(), item->GetHeight()))); + else + openMenu->SetLeftPosition(item->MapToGlobal(Point(0.0, item->GetHeight()))); + openMenu->Show(); + } +} + +void Menubar::CloseMenu() +{ + if (currentMenubarItem != -1) + menuItems[currentMenubarItem]->SetStyleState(""); + currentMenubarItem = -1; + delete openMenu; + openMenu = nullptr; + ReleaseModalCapture(); + modalMode = false; +} + +void Menubar::OnGeometryChanged() +{ + double w = GetWidth(); + double h = GetHeight(); + double left = 0.0; + double right = w; + for (MenubarItem* item : menuItems) + { + double itemwidth = item->GetPreferredWidth(); + itemwidth += 16.0; + if (!item->AlignRight) + { + item->SetFrameGeometry(left, 0.0, itemwidth, h); + left += itemwidth; + } + else + { + right -= itemwidth; + item->SetFrameGeometry(right, 0.0, itemwidth, h); + } + } +} + +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) +{ + SetStyleClass("menubaritem"); +} + +bool MenubarItem::OnMouseDown(const Point& pos, InputKey key) +{ + menubar->ShowMenu(this); + return true; +} + +bool MenubarItem::OnMouseUp(const Point& pos, InputKey key) +{ + return true; +} + +void MenubarItem::OnMouseMove(const Point& pos) +{ + if (GetStyleState().empty()) + { + SetStyleState("hover"); + } +} + +void MenubarItem::OnMouseLeave() +{ + SetStyleState(""); +} + +double MenubarItem::GetPreferredWidth() const +{ + Canvas* canvas = GetCanvas(); + return canvas->measureText(text).width; +} + +void MenubarItem::OnPaint(Canvas* canvas) +{ + double x = (GetWidth() - canvas->measureText(text).width) * 0.5; + canvas->drawText(Point(x, 21.0), GetStyleColor("color"), text); +} + +///////////////////////////////////////////////////////////////////////////// + +Menu::Menu(Widget* parent) : Widget(parent, WidgetType::Popup) +{ + SetStyleClass("menu"); +} + +void Menu::SetLeftPosition(const Point& pos) +{ + SetFrameGeometry(Rect::xywh(pos.x, pos.y, GetPreferredWidth() + GetNoncontentLeft() + GetNoncontentRight(), GetPreferredHeight() + GetNoncontentTop() + GetNoncontentBottom())); +} + +void Menu::SetRightPosition(const Point& pos) +{ + SetFrameGeometry(Rect::xywh(pos.x - GetWidth() - GetNoncontentLeft() - GetNoncontentRight(), pos.y, GetWidth() + GetNoncontentLeft() + GetNoncontentRight(), GetHeight() + GetNoncontentTop() + GetNoncontentBottom())); +} + +MenuItem* Menu::AddItem(std::shared_ptr icon, std::string text, std::function onClick) +{ + auto item = new MenuItem(this, [this, onClick]() { if (onCloseMenu) onCloseMenu(); if (onClick) onClick(); }); + if (icon) + item->icon->SetImage(icon); + item->text->SetText(text); + return item; +} + +MenuItemSeparator* Menu::AddSeparator() +{ + auto sep = new MenuItemSeparator(this); + return sep; +} + +double Menu::GetPreferredWidth() const +{ + return GridFitSize(200.0); +} + +double Menu::GetPreferredHeight() const +{ + double h = 0.0; + for (Widget* item = FirstChild(); item != nullptr; item = item->NextSibling()) + { + double itemheight = GridFitSize((item->GetStyleClass() == "menuitemseparator") ? 7.0 : 30.0); + h += itemheight; + } + return h; +} + +void Menu::OnGeometryChanged() +{ + double w = GetWidth(); + double y = 0.0; + for (Widget* item = FirstChild(); item != nullptr; item = item->NextSibling()) + { + 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(Menu* menu, std::function onClick) : Widget(menu), menu(menu), onClick(onClick) +{ + SetStyleClass("menuitem"); + icon = new ImageBox(this); + text = new TextLabel(this); +} + +void MenuItem::OnMouseMove(const Point& pos) +{ + menu->SetSelected(this); +} + +bool MenuItem::OnMouseUp(const Point& pos, InputKey key) +{ + Click(); + return true; +} + +void MenuItem::Click() +{ + if (onClick) + { + // We have to make a copy of the handler as it may delete 'this' + auto handler = onClick; + handler(); + } +} + +void MenuItem::OnMouseLeave() +{ + menu->SetSelected(nullptr); +} + +void MenuItem::OnGeometryChanged() +{ + double iconwidth = GridFitSize(icon->GetPreferredWidth()); + double iconheight = GridFitSize(icon->GetPreferredHeight()); + double w = GetWidth(); + double h = GetHeight(); + 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)); +} + +///////////////////////////////////////////////////////////////////////////// + +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/pushbutton/pushbutton.cpp b/libraries/ZWidget/src/widgets/pushbutton/pushbutton.cpp index d838fe424..e85b2c557 100644 --- a/libraries/ZWidget/src/widgets/pushbutton/pushbutton.cpp +++ b/libraries/ZWidget/src/widgets/pushbutton/pushbutton.cpp @@ -3,7 +3,7 @@ PushButton::PushButton(Widget* parent) : Widget(parent) { - SetNoncontentSizes(10.0, 5.0, 10.0, 5.0); + SetStyleClass("pushbutton"); } void PushButton::SetText(const std::string& value) @@ -25,52 +25,32 @@ double PushButton::GetPreferredHeight() const return 30.0; } -void PushButton::OnPaintFrame(Canvas* canvas) -{ - double w = GetFrameGeometry().width; - double h = GetFrameGeometry().height; - Colorf bordercolor = Colorf::fromRgba8(100, 100, 100); - Colorf buttoncolor = Colorf::fromRgba8(68, 68, 68); - if (buttonDown) - buttoncolor = Colorf::fromRgba8(88, 88, 88); - else if (hot) - buttoncolor = Colorf::fromRgba8(78, 78, 78); - canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), buttoncolor); - canvas->fillRect(Rect::xywh(0.0, 0.0, w, 1.0), bordercolor); - canvas->fillRect(Rect::xywh(0.0, h - 1.0, w, 1.0), bordercolor); - canvas->fillRect(Rect::xywh(0.0, 0.0, 1.0, h - 0.0), bordercolor); - canvas->fillRect(Rect::xywh(w - 1.0, 0.0, 1.0, h - 0.0), bordercolor); -} - void PushButton::OnPaint(Canvas* canvas) { Rect box = canvas->measureText(text); - canvas->drawText(Point((GetWidth() - box.width) * 0.5, GetHeight() - 5.0), Colorf::fromRgba8(255, 255, 255), text); + canvas->drawText(Point((GetWidth() - box.width) * 0.5, GetHeight() - 5.0), GetStyleColor("color"), text); } void PushButton::OnMouseMove(const Point& pos) { - if (!hot) + if (GetStyleState().empty()) { - hot = true; - Update(); + SetStyleState("hover"); } } -bool PushButton::OnMouseDown(const Point& pos, int key) +bool PushButton::OnMouseDown(const Point& pos, InputKey key) { SetFocus(); - buttonDown = true; - Update(); + SetStyleState("down"); return true; } -bool PushButton::OnMouseUp(const Point& pos, int key) +bool PushButton::OnMouseUp(const Point& pos, InputKey key) { - if (buttonDown) + if (GetStyleState() == "down") { - buttonDown = false; - hot = false; + SetStyleState(""); Repaint(); Click(); } @@ -79,26 +59,23 @@ bool PushButton::OnMouseUp(const Point& pos, int key) void PushButton::OnMouseLeave() { - hot = false; - buttonDown = false; - Update(); + SetStyleState(""); } -void PushButton::OnKeyDown(EInputKey key) +void PushButton::OnKeyDown(InputKey key) { - if (key == IK_Space || key == IK_Enter) + if (key == InputKey::Space || key == InputKey::Enter) { - buttonDown = true; + SetStyleState("down"); Update(); } } -void PushButton::OnKeyUp(EInputKey key) +void PushButton::OnKeyUp(InputKey key) { - if (key == IK_Space || key == IK_Enter) + if (key == InputKey::Space || key == InputKey::Enter) { - buttonDown = false; - hot = false; + SetStyleState(""); Repaint(); Click(); } diff --git a/libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp b/libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp index 489a66343..c4e29329a 100644 --- a/libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp +++ b/libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp @@ -5,6 +5,7 @@ Scrollbar::Scrollbar(Widget* parent) : Widget(parent) { + SetStyleClass("scrollbar"); UpdatePartPositions(); mouse_down_timer = new Timer(this); @@ -169,7 +170,7 @@ void Scrollbar::OnMouseMove(const Point& pos) Update(); } -bool Scrollbar::OnMouseDown(const Point& pos, int key) +bool Scrollbar::OnMouseDown(const Point& pos, InputKey key) { mouse_drag_start_pos = pos; @@ -253,11 +254,11 @@ bool Scrollbar::OnMouseDown(const Point& pos, int key) UpdatePartPositions(); Update(); - CaptureMouse(); + SetPointerCapture(); return true; } -bool Scrollbar::OnMouseUp(const Point& pos, int key) +bool Scrollbar::OnMouseUp(const Point& pos, InputKey key) { if (mouse_down_mode == mouse_down_thumb_drag) { @@ -269,7 +270,7 @@ bool Scrollbar::OnMouseUp(const Point& pos, int key) mouse_down_timer->Stop(); Update(); - ReleaseMouseCapture(); + ReleasePointerCapture(); return true; } @@ -294,8 +295,8 @@ void Scrollbar::OnPaint(Canvas* canvas) part_button_increment.render_box(canvas, rect_button_increment); */ - canvas->fillRect(Rect::shrink(Rect::xywh(0.0, 0.0, GetWidth(), GetHeight()), 4.0, 0.0, 4.0, 0.0), Colorf::fromRgba8(33, 33, 33)); - canvas->fillRect(Rect::shrink(rect_thumb, 4.0, 0.0, 4.0, 0.0), Colorf::fromRgba8(58, 58, 58)); + canvas->fillRect(Rect::shrink(Rect::xywh(0.0, 0.0, GetWidth(), GetHeight()), 4.0, 0.0, 4.0, 0.0), GetStyleColor("track-color")); + canvas->fillRect(Rect::shrink(rect_thumb, 4.0, 0.0, 4.0, 0.0), GetStyleColor("thumb-color")); } // Calculates positions of all parts. Returns true if thumb position was changed compared to previously, false otherwise. 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/tabwidget/tabwidget.cpp b/libraries/ZWidget/src/widgets/tabwidget/tabwidget.cpp index cbac877aa..4bd7551c7 100644 --- a/libraries/ZWidget/src/widgets/tabwidget/tabwidget.cpp +++ b/libraries/ZWidget/src/widgets/tabwidget/tabwidget.cpp @@ -85,7 +85,7 @@ int TabWidget::GetPageIndex(Widget* pageWidget) const for (size_t i = 0; i < Pages.size(); i++) { if (Pages[i] == pageWidget) - return i; + return (int)i; } return -1; } @@ -98,10 +98,6 @@ void TabWidget::OnBarCurrentChanged() OnCurrentChanged(); } -void TabWidget::OnPaintFrame(Canvas* canvas) -{ -} - void TabWidget::OnGeometryChanged() { double w = GetWidth(); @@ -115,7 +111,7 @@ void TabWidget::OnGeometryChanged() TabBar::TabBar(Widget* parent) : Widget(parent) { - SetNoncontentSizes(20.0, 0.0, 20.0, 0.0); + SetStyleClass("tabbar"); } int TabBar::AddTab(const std::string& label) @@ -129,7 +125,7 @@ int TabBar::AddTab(const std::shared_ptr& icon, const std::string& label) tab->SetIcon(icon); tab->SetText(label); tab->OnClick = [=]() { OnTabClicked(tab); }; - int pageIndex = Tabs.size(); + int pageIndex = (int)Tabs.size(); Tabs.push_back(tab); if (CurrentIndex == -1) SetCurrentIndex(pageIndex); @@ -182,18 +178,11 @@ int TabBar::GetTabIndex(TabBarTab* tab) for (size_t i = 0; i < Tabs.size(); i++) { if (Tabs[i] == tab) - return i; + return (int)i; } return -1; } -void TabBar::OnPaintFrame(Canvas* canvas) -{ - double w = GetFrameGeometry().width; - double h = GetFrameGeometry().height; - canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(38, 38, 38)); -} - void TabBar::OnGeometryChanged() { double w = GetWidth(); @@ -211,7 +200,7 @@ void TabBar::OnGeometryChanged() TabBarTab::TabBarTab(Widget* parent) : Widget(parent) { - SetNoncontentSizes(15.0, 0.0, 15.0, 0.0); + SetStyleClass("tabbar-tab"); } void TabBarTab::SetText(const std::string& text) @@ -257,7 +246,7 @@ void TabBarTab::SetCurrent(bool value) if (IsCurrent != value) { IsCurrent = value; - Update(); + SetStyleState(value ? "active" : ""); } } @@ -268,20 +257,6 @@ double TabBarTab::GetPreferredWidth() const return x; } -void TabBarTab::OnPaintFrame(Canvas* canvas) -{ - double w = GetFrameGeometry().width; - double h = GetFrameGeometry().height; - if (IsCurrent) - { - canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(51, 51, 51)); - } - else if (hot) - { - canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(45, 45, 45)); - } -} - void TabBarTab::OnGeometryChanged() { double x = 0.0; @@ -300,28 +275,28 @@ void TabBarTab::OnGeometryChanged() void TabBarTab::OnMouseMove(const Point& pos) { - if (!hot) + if (GetStyleState().empty()) { - hot = true; - Update(); + SetStyleState("hover"); } } -bool TabBarTab::OnMouseDown(const Point& pos, int key) +bool TabBarTab::OnMouseDown(const Point& pos, InputKey key) { if (OnClick) OnClick(); return true; } -bool TabBarTab::OnMouseUp(const Point& pos, int key) +bool TabBarTab::OnMouseUp(const Point& pos, InputKey key) { return true; } void TabBarTab::OnMouseLeave() { - hot = false; + if (GetStyleState() == "hover") + SetStyleState(""); Update(); } @@ -329,7 +304,7 @@ void TabBarTab::OnMouseLeave() TabWidgetStack::TabWidgetStack(Widget* parent) : Widget(parent) { - SetNoncontentSizes(20.0, 5.0, 20.0, 5.0); + SetStyleClass("tabwidget-stack"); } void TabWidgetStack::SetCurrentWidget(Widget* widget) @@ -347,10 +322,6 @@ void TabWidgetStack::SetCurrentWidget(Widget* widget) } } -void TabWidgetStack::OnPaintFrame(Canvas* canvas) -{ -} - void TabWidgetStack::OnGeometryChanged() { if (CurrentWidget) diff --git a/libraries/ZWidget/src/widgets/textedit/textedit.cpp b/libraries/ZWidget/src/widgets/textedit/textedit.cpp index dca840a1b..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 @@ -10,7 +11,7 @@ TextEdit::TextEdit(Widget* parent) : Widget(parent) { - SetNoncontentSizes(8.0, 8.0, 8.0, 8.0); + SetStyleClass("textedit"); timer = new Timer(this); timer->FuncExpired = [=]() { OnTimerExpired(); }; @@ -286,11 +287,11 @@ void TextEdit::OnMouseMove(const Point& pos) } } -bool TextEdit::OnMouseDown(const Point& pos, int key) +bool TextEdit::OnMouseDown(const Point& pos, InputKey key) { - if (key == IK_LeftMouse) + if (key == InputKey::LeftMouse) { - CaptureMouse(); + SetPointerCapture(); mouse_selecting = true; cursor_pos = GetCharacterIndex(pos); selection_start = cursor_pos; @@ -301,25 +302,25 @@ bool TextEdit::OnMouseDown(const Point& pos, int key) return true; } -bool TextEdit::OnMouseDoubleclick(const Point& pos, int key) +bool TextEdit::OnMouseDoubleclick(const Point& pos, InputKey key) { return true; } -bool TextEdit::OnMouseUp(const Point& pos, int key) +bool TextEdit::OnMouseUp(const Point& pos, InputKey key) { - if (mouse_selecting && key == IK_LeftMouse) + if (mouse_selecting && key == InputKey::LeftMouse) { 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); @@ -360,9 +361,9 @@ void TextEdit::OnKeyChar(std::string chars) } } -void TextEdit::OnKeyDown(EInputKey key) +void TextEdit::OnKeyDown(InputKey key) { - if (!readonly && key == IK_Enter) + if (!readonly && key == InputKey::Enter) { if (FuncEnterPressed) { @@ -383,17 +384,17 @@ void TextEdit::OnKeyDown(EInputKey key) timer->Start(500); // don't blink cursor when moving or typing. } - if (key == IK_Enter || key == IK_Escape || key == IK_Tab) + if (key == InputKey::Enter || key == InputKey::Escape || key == InputKey::Tab) { // Do not consume these. return; } - else if (key == IK_A && GetKeyState(IK_Ctrl)) + else if (key == InputKey::A && GetKeyState(InputKey::Ctrl)) { // select all SelectAll(); } - else if (key == IK_C && GetKeyState(IK_Ctrl)) + else if (key == InputKey::C && GetKeyState(InputKey::Ctrl)) { std::string str = GetSelection(); SetClipboardText(str); @@ -403,9 +404,9 @@ void TextEdit::OnKeyDown(EInputKey key) // Do not consume messages on read only component (only allow CTRL-A and CTRL-C) return; } - else if (key == IK_Up) + else if (key == InputKey::Up) { - if (GetKeyState(IK_Shift) && selection_length == 0) + if (GetKeyState(InputKey::Shift) && selection_length == 0) selection_start = cursor_pos; if (cursor_pos.y > 0) @@ -414,7 +415,7 @@ void TextEdit::OnKeyDown(EInputKey key) cursor_pos.x = std::min(lines[cursor_pos.y].text.size(), (size_t)cursor_pos.x); } - if (GetKeyState(IK_Shift)) + if (GetKeyState(InputKey::Shift)) { selection_length = ToOffset(cursor_pos) - ToOffset(selection_start); } @@ -428,9 +429,9 @@ void TextEdit::OnKeyDown(EInputKey key) Update(); undo_info.first_text_insert = true; } - else if (key == IK_Down) + else if (key == InputKey::Down) { - if (GetKeyState(IK_Shift) && selection_length == 0) + if (GetKeyState(InputKey::Shift) && selection_length == 0) selection_start = cursor_pos; if (cursor_pos.y < lines.size() - 1) @@ -439,7 +440,7 @@ void TextEdit::OnKeyDown(EInputKey key) cursor_pos.x = std::min(lines[cursor_pos.y].text.size(), (size_t)cursor_pos.x); } - if (GetKeyState(IK_Shift)) + if (GetKeyState(InputKey::Shift)) { selection_length = ToOffset(cursor_pos) - ToOffset(selection_start); } @@ -454,55 +455,55 @@ void TextEdit::OnKeyDown(EInputKey key) Update(); undo_info.first_text_insert = true; } - else if (key == IK_Left) + else if (key == InputKey::Left) { - Move(-1, GetKeyState(IK_Shift), GetKeyState(IK_Ctrl)); + Move(-1, GetKeyState(InputKey::Shift), GetKeyState(InputKey::Ctrl)); } - else if (key == IK_Right) + else if (key == InputKey::Right) { - Move(1, GetKeyState(IK_Shift), GetKeyState(IK_Ctrl)); + Move(1, GetKeyState(InputKey::Shift), GetKeyState(InputKey::Ctrl)); } - else if (key == IK_Backspace) + else if (key == InputKey::Backspace) { Backspace(); } - else if (key == IK_Delete) + else if (key == InputKey::Delete) { Del(); } - else if (key == IK_Home) + else if (key == InputKey::Home) { - if (GetKeyState(IK_Ctrl)) + if (GetKeyState(InputKey::Ctrl)) cursor_pos = ivec2(0, 0); else cursor_pos.x = 0; - if (GetKeyState(IK_Shift)) + if (GetKeyState(InputKey::Shift)) selection_length = ToOffset(cursor_pos) - ToOffset(selection_start); else ClearSelection(); Update(); MoveVerticalScroll(); } - else if (key == IK_End) + else if (key == InputKey::End) { - if (GetKeyState(IK_Ctrl)) + if (GetKeyState(InputKey::Ctrl)) cursor_pos = ivec2(lines.back().text.length(), lines.size() - 1); else cursor_pos.x = lines[cursor_pos.y].text.size(); - if (GetKeyState(IK_Shift)) + if (GetKeyState(InputKey::Shift)) selection_length = ToOffset(cursor_pos) - ToOffset(selection_start); else ClearSelection(); Update(); } - else if (key == IK_X && GetKeyState(IK_Ctrl)) + else if (key == InputKey::X && GetKeyState(InputKey::Ctrl)) { std::string str = GetSelection(); DeleteSelectedText(); SetClipboardText(str); } - else if (key == IK_V && GetKeyState(IK_Ctrl)) + else if (key == InputKey::V && GetKeyState(InputKey::Ctrl)) { std::string str = GetClipboardText(); std::string::const_iterator end_str = std::remove(str.begin(), str.end(), '\r'); @@ -524,7 +525,7 @@ void TextEdit::OnKeyDown(EInputKey key) } MoveVerticalScroll(); } - else if (GetKeyState(IK_Ctrl) && key == IK_Z) + else if (GetKeyState(InputKey::Ctrl) && key == InputKey::Z) { if (!readonly) { @@ -533,7 +534,7 @@ void TextEdit::OnKeyDown(EInputKey key) SetText(tmp); } } - else if (key == IK_Shift) + else if (key == InputKey::Shift) { if (selection_length == 0) selection_start = cursor_pos; @@ -543,7 +544,7 @@ void TextEdit::OnKeyDown(EInputKey key) FuncAfterEditChanged(); } -void TextEdit::OnKeyUp(EInputKey key) +void TextEdit::OnKeyUp(InputKey key) { } @@ -961,17 +962,18 @@ void TextEdit::LayoutLines(Canvas* canvas) sel_end = selection_start; } + Colorf textColor = GetStyleColor("color"); Point draw_pos; - for (size_t i = vert_scrollbar->GetPosition(); i < lines.size(); i++) + for (size_t i = (size_t)vert_scrollbar->GetPosition(); i < lines.size(); i++) { Line& line = lines[i]; if (line.invalidated) { line.layout.Clear(); if (!line.text.empty()) - line.layout.AddText(line.text, font, Colorf::fromRgba8(255, 255, 255)); + line.layout.AddText(line.text, font, textColor); else - line.layout.AddText(" ", font, Colorf::fromRgba8(255, 255, 255)); // Draw one space character to get the correct height + line.layout.AddText(" ", font, textColor); // Draw one space character to get the correct height line.layout.Layout(canvas, GetWidth()); line.box = Rect(draw_pos, line.layout.GetSize()); line.invalidated = false; @@ -992,7 +994,7 @@ void TextEdit::LayoutLines(Canvas* canvas) if (cursor_blink_visible && cursor_pos.y == i) { line.layout.SetCursorPos(cursor_pos.x); - line.layout.SetCursorColor(Colorf::fromRgba8(255, 255, 255)); + line.layout.SetCursorColor(textColor); line.layout.ShowCursor(); } } @@ -1006,22 +1008,10 @@ void TextEdit::LayoutLines(Canvas* canvas) UpdateVerticalScroll(); } -void TextEdit::OnPaintFrame(Canvas* canvas) -{ - double w = GetFrameGeometry().width; - double h = GetFrameGeometry().height; - Colorf bordercolor = Colorf::fromRgba8(100, 100, 100); - canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(38, 38, 38)); - canvas->fillRect(Rect::xywh(0.0, 0.0, w, 1.0), bordercolor); - canvas->fillRect(Rect::xywh(0.0, h - 1.0, w, 1.0), bordercolor); - canvas->fillRect(Rect::xywh(0.0, 0.0, 1.0, h - 0.0), bordercolor); - canvas->fillRect(Rect::xywh(w - 1.0, 0.0, 1.0, h - 0.0), bordercolor); -} - void TextEdit::OnPaint(Canvas* canvas) { LayoutLines(canvas); - for (size_t i = vert_scrollbar->GetPosition(); i < lines.size(); i++) + for (size_t i = (size_t)vert_scrollbar->GetPosition(); i < lines.size(); i++) lines[i].layout.DrawLayout(canvas); } diff --git a/libraries/ZWidget/src/widgets/textlabel/textlabel.cpp b/libraries/ZWidget/src/widgets/textlabel/textlabel.cpp index 55518f0a4..aa6e7e739 100644 --- a/libraries/ZWidget/src/widgets/textlabel/textlabel.cpp +++ b/libraries/ZWidget/src/widgets/textlabel/textlabel.cpp @@ -56,5 +56,5 @@ void TextLabel::OnPaint(Canvas* canvas) x = GetWidth() - canvas->measureText(text).width; } - canvas->drawText(Point(x, GetHeight() - 5.0), Colorf::fromRgba8(255, 255, 255), text); + canvas->drawText(Point(x, GetHeight() - 5.0), GetStyleColor("color"), text); } 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/sdl2_display_window.cpp b/libraries/ZWidget/src/window/sdl2/sdl2_display_window.cpp new file mode 100644 index 000000000..b5d2ffad5 --- /dev/null +++ b/libraries/ZWidget/src/window/sdl2/sdl2_display_window.cpp @@ -0,0 +1,772 @@ + +#include "sdl2_display_window.h" +#include +#include + +Uint32 SDL2DisplayWindow::PaintEventNumber = 0xffffffff; +bool SDL2DisplayWindow::ExitRunLoop; +std::unordered_map SDL2DisplayWindow::WindowList; + +class InitSDL +{ +public: + InitSDL() + { + int result = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); + if (result != 0) + throw std::runtime_error(std::string("Unable to initialize SDL:") + SDL_GetError()); + + SDL2DisplayWindow::PaintEventNumber = SDL_RegisterEvents(1); + } +}; + +static void CheckInitSDL() +{ + static InitSDL initsdl; +} + +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; + + 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(Handle.window)] = this; +} + +SDL2DisplayWindow::~SDL2DisplayWindow() +{ + UnlockCursor(); + + WindowList.erase(WindowList.find(SDL_GetWindowID(Handle.window))); + + if (BackBufferTexture) + { + SDL_DestroyTexture(BackBufferTexture); + BackBufferTexture = nullptr; + } + + if (RendererHandle) + SDL_DestroyRenderer(RendererHandle); + SDL_DestroyWindow(Handle.window); + RendererHandle = 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(Handle.window, text.c_str()); +} + +void SDL2DisplayWindow::SetWindowFrame(const Rect& box) +{ + // SDL2 doesn't really seem to have an API for this. + // The docs aren't clear what you're setting when calling SDL_SetWindowSize. + SetClientFrame(box); +} + +void SDL2DisplayWindow::SetClientFrame(const Rect& box) +{ + // Is there a way to set both in one call? + + double uiscale = GetDpiScale(); + int x = (int)std::round(box.x * uiscale); + int y = (int)std::round(box.y * uiscale); + int w = (int)std::round(box.width * uiscale); + int h = (int)std::round(box.height * uiscale); + + SDL_SetWindowPosition(Handle.window, x, y); + SDL_SetWindowSize(Handle.window, w, h); +} + +void SDL2DisplayWindow::Show() +{ + SDL_ShowWindow(Handle.window); +} + +void SDL2DisplayWindow::ShowFullscreen() +{ + SDL_ShowWindow(Handle.window); + SDL_SetWindowFullscreen(Handle.window, SDL_WINDOW_FULLSCREEN_DESKTOP); + isFullscreen = true; +} + +void SDL2DisplayWindow::ShowMaximized() +{ + SDL_ShowWindow(Handle.window); + SDL_MaximizeWindow(Handle.window); +} + +void SDL2DisplayWindow::ShowMinimized() +{ + SDL_ShowWindow(Handle.window); + SDL_MinimizeWindow(Handle.window); +} + +void SDL2DisplayWindow::ShowNormal() +{ + SDL_ShowWindow(Handle.window); + SDL_SetWindowFullscreen(Handle.window, 0); + isFullscreen = false; +} + +bool SDL2DisplayWindow::IsWindowFullscreen() +{ + return isFullscreen; +} + +void SDL2DisplayWindow::Hide() +{ + SDL_HideWindow(Handle.window); +} + +void SDL2DisplayWindow::Activate() +{ + SDL_RaiseWindow(Handle.window); +} + +void SDL2DisplayWindow::ShowCursor(bool enable) +{ + SDL_ShowCursor(enable); +} + +void SDL2DisplayWindow::LockCursor() +{ + if (!CursorLocked) + { + SDL_SetRelativeMouseMode(SDL_TRUE); + CursorLocked = true; + } +} + +void SDL2DisplayWindow::UnlockCursor() +{ + if (CursorLocked) + { + SDL_SetRelativeMouseMode(SDL_FALSE); + CursorLocked = false; + } +} + +void SDL2DisplayWindow::CaptureMouse() +{ +} + +void SDL2DisplayWindow::ReleaseMouseCapture() +{ +} + +void SDL2DisplayWindow::SetCursor(StandardCursor cursor) +{ +} + +void SDL2DisplayWindow::Update() +{ + SDL_Event event = {}; + event.type = PaintEventNumber; + event.user.windowID = SDL_GetWindowID(Handle.window); + SDL_PushEvent(&event); +} + +bool SDL2DisplayWindow::GetKeyState(InputKey key) +{ + int numkeys = 0; + const Uint8* state = SDL_GetKeyboardState(&numkeys); + if (!state) return false; + + SDL_Scancode index = InputKeyToScancode(key); + return (index < numkeys) ? state[index] != 0 : false; +} + +Rect SDL2DisplayWindow::GetWindowFrame() const +{ + int x = 0; + int y = 0; + int w = 0; + int h = 0; + double uiscale = GetDpiScale(); + SDL_GetWindowPosition(Handle.window, &x, &y); + SDL_GetWindowSize(Handle.window, &w, &h); + return Rect::xywh(x / uiscale, y / uiscale, w / uiscale, h / uiscale); +} + +Point SDL2DisplayWindow::MapFromGlobal(const Point& pos) const +{ + int x = 0; + int y = 0; + double uiscale = GetDpiScale(); + SDL_GetWindowPosition(Handle.window, &x, &y); + return Point(pos.x - x / uiscale, pos.y - y / uiscale); +} + +Point SDL2DisplayWindow::MapToGlobal(const Point& pos) const +{ + int x = 0; + int y = 0; + double uiscale = GetDpiScale(); + SDL_GetWindowPosition(Handle.window, &x, &y); + return Point(pos.x + x / uiscale, pos.y + y / uiscale); +} + +Size SDL2DisplayWindow::GetClientSize() const +{ + int w = 0; + int h = 0; + double uiscale = GetDpiScale(); + SDL_GetWindowSize(Handle.window, &w, &h); + return Size(w / uiscale, h / uiscale); +} + +int SDL2DisplayWindow::GetPixelWidth() const +{ + int w = 0; + int h = 0; + if (RendererHandle) + SDL_GetRendererOutputSize(RendererHandle, &w, &h); + else + SDL_GL_GetDrawableSize(Handle.window, &w, &h); + return w; +} + +int SDL2DisplayWindow::GetPixelHeight() const +{ + int w = 0; + int h = 0; + if (RendererHandle) + SDL_GetRendererOutputSize(RendererHandle, &w, &h); + else + SDL_GL_GetDrawableSize(Handle.window, &w, &h); + return h; +} + +double SDL2DisplayWindow::GetDpiScale() const +{ + // SDL2 doesn't really support this properly. SDL_GetDisplayDPI returns the wrong information according to the docs. + return 1.0; +} + +void SDL2DisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels) +{ + if (!RendererHandle) + return; + + if (!BackBufferTexture || BackBufferWidth != width || BackBufferHeight != height) + { + if (BackBufferTexture) + { + SDL_DestroyTexture(BackBufferTexture); + BackBufferTexture = nullptr; + } + + BackBufferTexture = SDL_CreateTexture(RendererHandle, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height); + if (!BackBufferTexture) + return; + + BackBufferWidth = width; + BackBufferHeight = height; + } + + int destpitch = 0; + void* dest = nullptr; + int result = SDL_LockTexture(BackBufferTexture, nullptr, &dest, &destpitch); + if (result != 0) return; + for (int y = 0; y < height; y++) + { + const void* sline = pixels + y * width; + void* dline = (uint8_t*)dest + y * destpitch; + memcpy(dline, sline, width << 2); + } + SDL_UnlockTexture(BackBufferTexture); + + SDL_RenderCopy(RendererHandle, BackBufferTexture, nullptr, nullptr); + SDL_RenderPresent(RendererHandle); +} + +void SDL2DisplayWindow::SetBorderColor(uint32_t bgra8) +{ + // SDL doesn't have this +} + +void SDL2DisplayWindow::SetCaptionColor(uint32_t bgra8) +{ + // SDL doesn't have this +} + +void SDL2DisplayWindow::SetCaptionTextColor(uint32_t bgra8) +{ + // SDL doesn't have this +} + +std::string SDL2DisplayWindow::GetClipboardText() +{ + char* buffer = SDL_GetClipboardText(); + if (!buffer) + return {}; + std::string text = buffer; + SDL_free(buffer); + return text; +} + +void SDL2DisplayWindow::SetClipboardText(const std::string& text) +{ + SDL_SetClipboardText(text.c_str()); +} + +void SDL2DisplayWindow::ProcessEvents() +{ + CheckInitSDL(); + + SDL_Event event; + while (SDL_PollEvent(&event) != 0) + { + DispatchEvent(event); + } +} + +void SDL2DisplayWindow::RunLoop() +{ + CheckInitSDL(); + + ExitRunLoop = false; + while (!ExitRunLoop) + { + SDL_Event event = {}; + int result = SDL_WaitEvent(&event); + if (result == 1) + DispatchEvent(event); // Silently ignore if it fails and pray it doesn't busy loop, because SDL and Linux utterly sucks! + } +} + +void SDL2DisplayWindow::ExitLoop() +{ + CheckInitSDL(); + + ExitRunLoop = true; +} + +Size SDL2DisplayWindow::GetScreenSize() +{ + CheckInitSDL(); + + SDL_Rect rect = {}; + int result = SDL_GetDisplayBounds(0, &rect); + if (result != 0) + throw std::runtime_error(std::string("Unable to get screen size:") + SDL_GetError()); + + double uiscale = 1.0; // SDL2 doesn't really support this properly. SDL_GetDisplayDPI returns the wrong information according to the docs. + return Size(rect.w / uiscale, rect.h / uiscale); +} + +void* SDL2DisplayWindow::StartTimer(int timeoutMilliseconds, std::function onTimer) +{ + CheckInitSDL(); + + // To do: implement timers + + return nullptr; +} + +void SDL2DisplayWindow::StopTimer(void* timerID) +{ + CheckInitSDL(); + + // To do: implement timers +} + +SDL2DisplayWindow* SDL2DisplayWindow::FindEventWindow(const SDL_Event& event) +{ + int windowID; + switch (event.type) + { + case SDL_WINDOWEVENT: windowID = event.window.windowID; break; + case SDL_TEXTINPUT: windowID = event.text.windowID; break; + case SDL_KEYUP: windowID = event.key.windowID; break; + case SDL_KEYDOWN: windowID = event.key.windowID; break; + case SDL_MOUSEBUTTONUP: windowID = event.button.windowID; break; + case SDL_MOUSEBUTTONDOWN: windowID = event.button.windowID; break; + case SDL_MOUSEWHEEL: windowID = event.wheel.windowID; break; + case SDL_MOUSEMOTION: windowID = event.motion.windowID; break; + default: + if (event.type == PaintEventNumber) windowID = event.user.windowID; + else return nullptr; + } + + auto it = WindowList.find(windowID); + return it != WindowList.end() ? it->second : nullptr; +} + +void SDL2DisplayWindow::DispatchEvent(const SDL_Event& event) +{ + SDL2DisplayWindow* window = FindEventWindow(event); + if (!window) return; + + switch (event.type) + { + case SDL_WINDOWEVENT: window->OnWindowEvent(event.window); break; + case SDL_TEXTINPUT: window->OnTextInput(event.text); break; + case SDL_KEYUP: window->OnKeyUp(event.key); break; + case SDL_KEYDOWN: window->OnKeyDown(event.key); break; + case SDL_MOUSEBUTTONUP: window->OnMouseButtonUp(event.button); break; + case SDL_MOUSEBUTTONDOWN: window->OnMouseButtonDown(event.button); break; + case SDL_MOUSEWHEEL: window->OnMouseWheel(event.wheel); break; + case SDL_MOUSEMOTION: window->OnMouseMotion(event.motion); break; + default: + if (event.type == PaintEventNumber) window->OnPaintEvent(); + } +} + +void SDL2DisplayWindow::OnWindowEvent(const SDL_WindowEvent& event) +{ + switch (event.event) + { + case SDL_WINDOWEVENT_CLOSE: WindowHost->OnWindowClose(); break; + case SDL_WINDOWEVENT_MOVED: WindowHost->OnWindowGeometryChanged(); break; + case SDL_WINDOWEVENT_RESIZED: WindowHost->OnWindowGeometryChanged(); break; + case SDL_WINDOWEVENT_SHOWN: WindowHost->OnWindowPaint(); break; + case SDL_WINDOWEVENT_EXPOSED: WindowHost->OnWindowPaint(); break; + case SDL_WINDOWEVENT_FOCUS_GAINED: WindowHost->OnWindowActivated(); break; + case SDL_WINDOWEVENT_FOCUS_LOST: WindowHost->OnWindowDeactivated(); break; + } +} + +void SDL2DisplayWindow::OnTextInput(const SDL_TextInputEvent& event) +{ + WindowHost->OnWindowKeyChar(event.text); +} + +void SDL2DisplayWindow::OnKeyUp(const SDL_KeyboardEvent& event) +{ + WindowHost->OnWindowKeyUp(ScancodeToInputKey(event.keysym.scancode)); +} + +void SDL2DisplayWindow::OnKeyDown(const SDL_KeyboardEvent& event) +{ + WindowHost->OnWindowKeyDown(ScancodeToInputKey(event.keysym.scancode)); +} + +InputKey SDL2DisplayWindow::GetMouseButtonKey(const SDL_MouseButtonEvent& event) +{ + switch (event.button) + { + case SDL_BUTTON_LEFT: return InputKey::LeftMouse; + case SDL_BUTTON_MIDDLE: return InputKey::MiddleMouse; + case SDL_BUTTON_RIGHT: return InputKey::RightMouse; + // case SDL_BUTTON_X1: return InputKey::XButton1; + // case SDL_BUTTON_X2: return InputKey::XButton2; + default: return InputKey::None; + } +} + +void SDL2DisplayWindow::OnMouseButtonUp(const SDL_MouseButtonEvent& event) +{ + InputKey key = GetMouseButtonKey(event); + if (key != InputKey::None) + { + WindowHost->OnWindowMouseUp(GetMousePos(event), key); + } +} + +void SDL2DisplayWindow::OnMouseButtonDown(const SDL_MouseButtonEvent& event) +{ + InputKey key = GetMouseButtonKey(event); + if (key != InputKey::None) + { + WindowHost->OnWindowMouseDown(GetMousePos(event), key); + } +} + +void SDL2DisplayWindow::OnMouseWheel(const SDL_MouseWheelEvent& event) +{ + InputKey key = (event.y > 0) ? InputKey::MouseWheelUp : (event.y < 0) ? InputKey::MouseWheelDown : InputKey::None; + if (key != InputKey::None) + { + WindowHost->OnWindowMouseWheel(GetMousePos(event), key); + } +} + +void SDL2DisplayWindow::OnMouseMotion(const SDL_MouseMotionEvent& event) +{ + if (CursorLocked) + { + WindowHost->OnWindowRawMouseMove(event.xrel, event.yrel); + } + else + { + WindowHost->OnWindowMouseMove(GetMousePos(event)); + } +} + +void SDL2DisplayWindow::OnPaintEvent() +{ + WindowHost->OnWindowPaint(); +} + +InputKey SDL2DisplayWindow::ScancodeToInputKey(SDL_Scancode keycode) +{ + switch (keycode) + { + case SDL_SCANCODE_BACKSPACE: return InputKey::Backspace; + case SDL_SCANCODE_TAB: return InputKey::Tab; + case SDL_SCANCODE_CLEAR: return InputKey::OEMClear; + case SDL_SCANCODE_RETURN: return InputKey::Enter; + case SDL_SCANCODE_MENU: return InputKey::Alt; + case SDL_SCANCODE_PAUSE: return InputKey::Pause; + case SDL_SCANCODE_ESCAPE: return InputKey::Escape; + case SDL_SCANCODE_SPACE: return InputKey::Space; + case SDL_SCANCODE_END: return InputKey::End; + case SDL_SCANCODE_HOME: return InputKey::Home; + case SDL_SCANCODE_LEFT: return InputKey::Left; + case SDL_SCANCODE_UP: return InputKey::Up; + case SDL_SCANCODE_RIGHT: return InputKey::Right; + case SDL_SCANCODE_DOWN: return InputKey::Down; + case SDL_SCANCODE_SELECT: return InputKey::Select; + case SDL_SCANCODE_PRINTSCREEN: return InputKey::Print; + case SDL_SCANCODE_EXECUTE: return InputKey::Execute; + case SDL_SCANCODE_INSERT: return InputKey::Insert; + case SDL_SCANCODE_DELETE: return InputKey::Delete; + case SDL_SCANCODE_HELP: return InputKey::Help; + case SDL_SCANCODE_0: return InputKey::_0; + case SDL_SCANCODE_1: return InputKey::_1; + case SDL_SCANCODE_2: return InputKey::_2; + case SDL_SCANCODE_3: return InputKey::_3; + case SDL_SCANCODE_4: return InputKey::_4; + case SDL_SCANCODE_5: return InputKey::_5; + case SDL_SCANCODE_6: return InputKey::_6; + case SDL_SCANCODE_7: return InputKey::_7; + case SDL_SCANCODE_8: return InputKey::_8; + case SDL_SCANCODE_9: return InputKey::_9; + case SDL_SCANCODE_A: return InputKey::A; + case SDL_SCANCODE_B: return InputKey::B; + case SDL_SCANCODE_C: return InputKey::C; + case SDL_SCANCODE_D: return InputKey::D; + case SDL_SCANCODE_E: return InputKey::E; + case SDL_SCANCODE_F: return InputKey::F; + case SDL_SCANCODE_G: return InputKey::G; + case SDL_SCANCODE_H: return InputKey::H; + case SDL_SCANCODE_I: return InputKey::I; + case SDL_SCANCODE_J: return InputKey::J; + case SDL_SCANCODE_K: return InputKey::K; + case SDL_SCANCODE_L: return InputKey::L; + case SDL_SCANCODE_M: return InputKey::M; + case SDL_SCANCODE_N: return InputKey::N; + case SDL_SCANCODE_O: return InputKey::O; + case SDL_SCANCODE_P: return InputKey::P; + case SDL_SCANCODE_Q: return InputKey::Q; + case SDL_SCANCODE_R: return InputKey::R; + case SDL_SCANCODE_S: return InputKey::S; + case SDL_SCANCODE_T: return InputKey::T; + case SDL_SCANCODE_U: return InputKey::U; + case SDL_SCANCODE_V: return InputKey::V; + case SDL_SCANCODE_W: return InputKey::W; + case SDL_SCANCODE_X: return InputKey::X; + case SDL_SCANCODE_Y: return InputKey::Y; + case SDL_SCANCODE_Z: return InputKey::Z; + case SDL_SCANCODE_KP_0: return InputKey::NumPad0; + case SDL_SCANCODE_KP_1: return InputKey::NumPad1; + case SDL_SCANCODE_KP_2: return InputKey::NumPad2; + case SDL_SCANCODE_KP_3: return InputKey::NumPad3; + case SDL_SCANCODE_KP_4: return InputKey::NumPad4; + case SDL_SCANCODE_KP_5: return InputKey::NumPad5; + case SDL_SCANCODE_KP_6: return InputKey::NumPad6; + case SDL_SCANCODE_KP_7: return InputKey::NumPad7; + case SDL_SCANCODE_KP_8: return InputKey::NumPad8; + case SDL_SCANCODE_KP_9: return InputKey::NumPad9; + // case SDL_SCANCODE_KP_ENTER: return InputKey::NumPadEnter; + // case SDL_SCANCODE_KP_MULTIPLY: return InputKey::Multiply; + // case SDL_SCANCODE_KP_PLUS: return InputKey::Add; + case SDL_SCANCODE_SEPARATOR: return InputKey::Separator; + // case SDL_SCANCODE_KP_MINUS: return InputKey::Subtract; + case SDL_SCANCODE_KP_PERIOD: return InputKey::NumPadPeriod; + // case SDL_SCANCODE_KP_DIVIDE: return InputKey::Divide; + case SDL_SCANCODE_F1: return InputKey::F1; + case SDL_SCANCODE_F2: return InputKey::F2; + case SDL_SCANCODE_F3: return InputKey::F3; + case SDL_SCANCODE_F4: return InputKey::F4; + case SDL_SCANCODE_F5: return InputKey::F5; + case SDL_SCANCODE_F6: return InputKey::F6; + case SDL_SCANCODE_F7: return InputKey::F7; + case SDL_SCANCODE_F8: return InputKey::F8; + case SDL_SCANCODE_F9: return InputKey::F9; + case SDL_SCANCODE_F10: return InputKey::F10; + case SDL_SCANCODE_F11: return InputKey::F11; + case SDL_SCANCODE_F12: return InputKey::F12; + case SDL_SCANCODE_F13: return InputKey::F13; + case SDL_SCANCODE_F14: return InputKey::F14; + case SDL_SCANCODE_F15: return InputKey::F15; + case SDL_SCANCODE_F16: return InputKey::F16; + case SDL_SCANCODE_F17: return InputKey::F17; + case SDL_SCANCODE_F18: return InputKey::F18; + case SDL_SCANCODE_F19: return InputKey::F19; + case SDL_SCANCODE_F20: return InputKey::F20; + case SDL_SCANCODE_F21: return InputKey::F21; + case SDL_SCANCODE_F22: return InputKey::F22; + case SDL_SCANCODE_F23: return InputKey::F23; + case SDL_SCANCODE_F24: return InputKey::F24; + case SDL_SCANCODE_NUMLOCKCLEAR: return InputKey::NumLock; + case SDL_SCANCODE_SCROLLLOCK: return InputKey::ScrollLock; + case SDL_SCANCODE_LSHIFT: return InputKey::LShift; + case SDL_SCANCODE_RSHIFT: return InputKey::RShift; + case SDL_SCANCODE_LCTRL: return InputKey::LControl; + case SDL_SCANCODE_RCTRL: return InputKey::RControl; + case SDL_SCANCODE_GRAVE: return InputKey::Tilde; + default: return InputKey::None; + } +} + +SDL_Scancode SDL2DisplayWindow::InputKeyToScancode(InputKey inputkey) +{ + switch (inputkey) + { + case InputKey::Backspace: return SDL_SCANCODE_BACKSPACE; + case InputKey::Tab: return SDL_SCANCODE_TAB; + case InputKey::OEMClear: return SDL_SCANCODE_CLEAR; + case InputKey::Enter: return SDL_SCANCODE_RETURN; + case InputKey::Alt: return SDL_SCANCODE_MENU; + case InputKey::Pause: return SDL_SCANCODE_PAUSE; + case InputKey::Escape: return SDL_SCANCODE_ESCAPE; + case InputKey::Space: return SDL_SCANCODE_SPACE; + case InputKey::End: return SDL_SCANCODE_END; + case InputKey::Home: return SDL_SCANCODE_HOME; + case InputKey::Left: return SDL_SCANCODE_LEFT; + case InputKey::Up: return SDL_SCANCODE_UP; + case InputKey::Right: return SDL_SCANCODE_RIGHT; + case InputKey::Down: return SDL_SCANCODE_DOWN; + case InputKey::Select: return SDL_SCANCODE_SELECT; + case InputKey::Print: return SDL_SCANCODE_PRINTSCREEN; + case InputKey::Execute: return SDL_SCANCODE_EXECUTE; + case InputKey::Insert: return SDL_SCANCODE_INSERT; + case InputKey::Delete: return SDL_SCANCODE_DELETE; + case InputKey::Help: return SDL_SCANCODE_HELP; + case InputKey::_0: return SDL_SCANCODE_0; + case InputKey::_1: return SDL_SCANCODE_1; + case InputKey::_2: return SDL_SCANCODE_2; + case InputKey::_3: return SDL_SCANCODE_3; + case InputKey::_4: return SDL_SCANCODE_4; + case InputKey::_5: return SDL_SCANCODE_5; + case InputKey::_6: return SDL_SCANCODE_6; + case InputKey::_7: return SDL_SCANCODE_7; + case InputKey::_8: return SDL_SCANCODE_8; + case InputKey::_9: return SDL_SCANCODE_9; + case InputKey::A: return SDL_SCANCODE_A; + case InputKey::B: return SDL_SCANCODE_B; + case InputKey::C: return SDL_SCANCODE_C; + case InputKey::D: return SDL_SCANCODE_D; + case InputKey::E: return SDL_SCANCODE_E; + case InputKey::F: return SDL_SCANCODE_F; + case InputKey::G: return SDL_SCANCODE_G; + case InputKey::H: return SDL_SCANCODE_H; + case InputKey::I: return SDL_SCANCODE_I; + case InputKey::J: return SDL_SCANCODE_J; + case InputKey::K: return SDL_SCANCODE_K; + case InputKey::L: return SDL_SCANCODE_L; + case InputKey::M: return SDL_SCANCODE_M; + case InputKey::N: return SDL_SCANCODE_N; + case InputKey::O: return SDL_SCANCODE_O; + case InputKey::P: return SDL_SCANCODE_P; + case InputKey::Q: return SDL_SCANCODE_Q; + case InputKey::R: return SDL_SCANCODE_R; + case InputKey::S: return SDL_SCANCODE_S; + case InputKey::T: return SDL_SCANCODE_T; + case InputKey::U: return SDL_SCANCODE_U; + case InputKey::V: return SDL_SCANCODE_V; + case InputKey::W: return SDL_SCANCODE_W; + case InputKey::X: return SDL_SCANCODE_X; + case InputKey::Y: return SDL_SCANCODE_Y; + case InputKey::Z: return SDL_SCANCODE_Z; + case InputKey::NumPad0: return SDL_SCANCODE_KP_0; + case InputKey::NumPad1: return SDL_SCANCODE_KP_1; + case InputKey::NumPad2: return SDL_SCANCODE_KP_2; + case InputKey::NumPad3: return SDL_SCANCODE_KP_3; + case InputKey::NumPad4: return SDL_SCANCODE_KP_4; + case InputKey::NumPad5: return SDL_SCANCODE_KP_5; + case InputKey::NumPad6: return SDL_SCANCODE_KP_6; + case InputKey::NumPad7: return SDL_SCANCODE_KP_7; + case InputKey::NumPad8: return SDL_SCANCODE_KP_8; + case InputKey::NumPad9: return SDL_SCANCODE_KP_9; + // case InputKey::NumPadEnter: return SDL_SCANCODE_KP_ENTER; + // case InputKey::Multiply return SDL_SCANCODE_KP_MULTIPLY:; + // case InputKey::Add: return SDL_SCANCODE_KP_PLUS; + case InputKey::Separator: return SDL_SCANCODE_SEPARATOR; + // case InputKey::Subtract: return SDL_SCANCODE_KP_MINUS; + case InputKey::NumPadPeriod: return SDL_SCANCODE_KP_PERIOD; + // case InputKey::Divide: return SDL_SCANCODE_KP_DIVIDE; + case InputKey::F1: return SDL_SCANCODE_F1; + case InputKey::F2: return SDL_SCANCODE_F2; + case InputKey::F3: return SDL_SCANCODE_F3; + case InputKey::F4: return SDL_SCANCODE_F4; + case InputKey::F5: return SDL_SCANCODE_F5; + case InputKey::F6: return SDL_SCANCODE_F6; + case InputKey::F7: return SDL_SCANCODE_F7; + case InputKey::F8: return SDL_SCANCODE_F8; + case InputKey::F9: return SDL_SCANCODE_F9; + case InputKey::F10: return SDL_SCANCODE_F10; + case InputKey::F11: return SDL_SCANCODE_F11; + case InputKey::F12: return SDL_SCANCODE_F12; + case InputKey::F13: return SDL_SCANCODE_F13; + case InputKey::F14: return SDL_SCANCODE_F14; + case InputKey::F15: return SDL_SCANCODE_F15; + case InputKey::F16: return SDL_SCANCODE_F16; + case InputKey::F17: return SDL_SCANCODE_F17; + case InputKey::F18: return SDL_SCANCODE_F18; + case InputKey::F19: return SDL_SCANCODE_F19; + case InputKey::F20: return SDL_SCANCODE_F20; + case InputKey::F21: return SDL_SCANCODE_F21; + case InputKey::F22: return SDL_SCANCODE_F22; + case InputKey::F23: return SDL_SCANCODE_F23; + case InputKey::F24: return SDL_SCANCODE_F24; + case InputKey::NumLock: return SDL_SCANCODE_NUMLOCKCLEAR; + case InputKey::ScrollLock: return SDL_SCANCODE_SCROLLLOCK; + case InputKey::LShift: return SDL_SCANCODE_LSHIFT; + case InputKey::RShift: return SDL_SCANCODE_RSHIFT; + case InputKey::LControl: return SDL_SCANCODE_LCTRL; + case InputKey::RControl: return SDL_SCANCODE_RCTRL; + case InputKey::Tilde: return SDL_SCANCODE_GRAVE; + default: return (SDL_Scancode)0; + } +} diff --git a/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.h b/libraries/ZWidget/src/window/sdl2/sdl2_display_window.h similarity index 76% rename from libraries/ZWidget/src/window/sdl2/sdl2displaywindow.h rename to libraries/ZWidget/src/window/sdl2/sdl2_display_window.h index fa0e8253c..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); + 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; @@ -27,7 +29,7 @@ public: void CaptureMouse() override; void ReleaseMouseCapture() override; void Update() override; - bool GetKeyState(EInputKey key) override; + bool GetKeyState(InputKey key) override; void SetCursor(StandardCursor cursor) override; Rect GetWindowFrame() const override; @@ -45,6 +47,14 @@ public: 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 &Handle; } + + std::vector GetVulkanInstanceExtensions() override; + VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override; + static void DispatchEvent(const SDL_Event& event); static SDL2DisplayWindow* FindEventWindow(const SDL_Event& event); @@ -58,10 +68,10 @@ public: void OnMouseMotion(const SDL_MouseMotionEvent& event); void OnPaintEvent(); - EInputKey GetMouseButtonKey(const SDL_MouseButtonEvent& event); + InputKey GetMouseButtonKey(const SDL_MouseButtonEvent& event); - static EInputKey ScancodeToInputKey(SDL_Scancode keycode); - static SDL_Scancode InputKeyToScancode(EInputKey inputkey); + static InputKey ScancodeToInputKey(SDL_Scancode keycode); + static SDL_Scancode InputKeyToScancode(InputKey inputkey); template Point GetMousePos(const T& event) @@ -79,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/sdl2/sdl2displaywindow.cpp b/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.cpp deleted file mode 100644 index 17db8a863..000000000 --- a/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.cpp +++ /dev/null @@ -1,675 +0,0 @@ - -#include "sdl2displaywindow.h" -#include - -Uint32 SDL2DisplayWindow::PaintEventNumber = 0xffffffff; -bool SDL2DisplayWindow::ExitRunLoop; -std::unordered_map SDL2DisplayWindow::WindowList; - -class InitSDL -{ -public: - InitSDL() - { - int result = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); - if (result != 0) - throw std::runtime_error(std::string("Unable to initialize SDL:") + SDL_GetError()); - - SDL2DisplayWindow::PaintEventNumber = SDL_RegisterEvents(1); - } -}; - -static void CheckInitSDL() -{ - static InitSDL initsdl; -} - -SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost) : WindowHost(windowHost) -{ - CheckInitSDL(); - - int result = SDL_CreateWindowAndRenderer(320, 200, SDL_WINDOW_HIDDEN /*| SDL_WINDOW_ALLOW_HIGHDPI*/, &WindowHandle, &RendererHandle); - if (result != 0) - throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError()); - - WindowList[SDL_GetWindowID(WindowHandle)] = this; -} - -SDL2DisplayWindow::~SDL2DisplayWindow() -{ - WindowList.erase(WindowList.find(SDL_GetWindowID(WindowHandle))); - - if (BackBufferTexture) - { - SDL_DestroyTexture(BackBufferTexture); - BackBufferTexture = nullptr; - } - - SDL_DestroyRenderer(RendererHandle); - SDL_DestroyWindow(WindowHandle); - RendererHandle = nullptr; - WindowHandle = nullptr; -} - -void SDL2DisplayWindow::SetWindowTitle(const std::string& text) -{ - SDL_SetWindowTitle(WindowHandle, text.c_str()); -} - -void SDL2DisplayWindow::SetWindowFrame(const Rect& box) -{ - // SDL2 doesn't really seem to have an API for this. - // The docs aren't clear what you're setting when calling SDL_SetWindowSize. - SetClientFrame(box); -} - -void SDL2DisplayWindow::SetClientFrame(const Rect& box) -{ - // Is there a way to set both in one call? - - double uiscale = GetDpiScale(); - int x = (int)std::round(box.x * uiscale); - int y = (int)std::round(box.y * uiscale); - 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); -} - -void SDL2DisplayWindow::Show() -{ - SDL_ShowWindow(WindowHandle); -} - -void SDL2DisplayWindow::ShowFullscreen() -{ - SDL_SetWindowFullscreen(WindowHandle, SDL_WINDOW_FULLSCREEN_DESKTOP); -} - -void SDL2DisplayWindow::ShowMaximized() -{ - SDL_ShowWindow(WindowHandle); - SDL_MaximizeWindow(WindowHandle); -} - -void SDL2DisplayWindow::ShowMinimized() -{ - SDL_ShowWindow(WindowHandle); - SDL_MinimizeWindow(WindowHandle); -} - -void SDL2DisplayWindow::ShowNormal() -{ - SDL_ShowWindow(WindowHandle); - SDL_SetWindowFullscreen(WindowHandle, 0); -} - -void SDL2DisplayWindow::Hide() -{ - SDL_HideWindow(WindowHandle); -} - -void SDL2DisplayWindow::Activate() -{ - SDL_RaiseWindow(WindowHandle); -} - -void SDL2DisplayWindow::ShowCursor(bool enable) -{ - SDL_ShowCursor(enable); -} - -void SDL2DisplayWindow::LockCursor() -{ - SDL_SetWindowGrab(WindowHandle, SDL_TRUE); - SDL_ShowCursor(0); -} - -void SDL2DisplayWindow::UnlockCursor() -{ - SDL_SetWindowGrab(WindowHandle, SDL_FALSE); - SDL_ShowCursor(1); -} - -void SDL2DisplayWindow::CaptureMouse() -{ -} - -void SDL2DisplayWindow::ReleaseMouseCapture() -{ -} - -void SDL2DisplayWindow::SetCursor(StandardCursor cursor) -{ -} - -void SDL2DisplayWindow::Update() -{ - SDL_Event event = {}; - event.type = PaintEventNumber; - event.user.windowID = SDL_GetWindowID(WindowHandle); - SDL_PushEvent(&event); -} - -bool SDL2DisplayWindow::GetKeyState(EInputKey key) -{ - int numkeys = 0; - const Uint8* state = SDL_GetKeyboardState(&numkeys); - if (!state) return false; - - SDL_Scancode index = InputKeyToScancode(key); - return (index < numkeys) ? state[index] != 0 : false; -} - -Rect SDL2DisplayWindow::GetWindowFrame() const -{ - int x = 0; - int y = 0; - int w = 0; - int h = 0; - double uiscale = GetDpiScale(); - SDL_GetWindowPosition(WindowHandle, &x, &y); - SDL_GetWindowSize(WindowHandle, &w, &h); - return Rect::xywh(x / uiscale, y / uiscale, w / uiscale, h / uiscale); -} - -Size SDL2DisplayWindow::GetClientSize() const -{ - int w = 0; - int h = 0; - double uiscale = GetDpiScale(); - SDL_GetWindowSize(WindowHandle, &w, &h); - return Size(w / uiscale, h / uiscale); -} - -int SDL2DisplayWindow::GetPixelWidth() const -{ - int w = 0; - int h = 0; - int result = SDL_GetRendererOutputSize(RendererHandle, &w, &h); - return w; -} - -int SDL2DisplayWindow::GetPixelHeight() const -{ - int w = 0; - int h = 0; - int result = SDL_GetRendererOutputSize(RendererHandle, &w, &h); - return h; -} - -double SDL2DisplayWindow::GetDpiScale() const -{ - // SDL2 doesn't really support this properly. SDL_GetDisplayDPI returns the wrong information according to the docs. - return 1.0; -} - -void SDL2DisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels) -{ - if (!BackBufferTexture || BackBufferWidth != width || BackBufferHeight != height) - { - if (BackBufferTexture) - { - SDL_DestroyTexture(BackBufferTexture); - BackBufferTexture = nullptr; - } - - BackBufferTexture = SDL_CreateTexture(RendererHandle, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height); - if (!BackBufferTexture) - return; - - BackBufferWidth = width; - BackBufferHeight = height; - } - - int destpitch = 0; - void* dest = nullptr; - int result = SDL_LockTexture(BackBufferTexture, nullptr, &dest, &destpitch); - if (result != 0) return; - for (int y = 0; y < height; y++) - { - const void* sline = pixels + y * width; - void* dline = (uint8_t*)dest + y * destpitch; - memcpy(dline, sline, width << 2); - } - SDL_UnlockTexture(BackBufferTexture); - - SDL_RenderCopy(RendererHandle, BackBufferTexture, nullptr, nullptr); - SDL_RenderPresent(RendererHandle); -} - -void SDL2DisplayWindow::SetBorderColor(uint32_t bgra8) -{ - // SDL doesn't have this -} - -void SDL2DisplayWindow::SetCaptionColor(uint32_t bgra8) -{ - // SDL doesn't have this -} - -void SDL2DisplayWindow::SetCaptionTextColor(uint32_t bgra8) -{ - // SDL doesn't have this -} - -std::string SDL2DisplayWindow::GetClipboardText() -{ - char* buffer = SDL_GetClipboardText(); - if (!buffer) - return {}; - std::string text = buffer; - SDL_free(buffer); - return text; -} - -void SDL2DisplayWindow::SetClipboardText(const std::string& text) -{ - SDL_SetClipboardText(text.c_str()); -} - -void SDL2DisplayWindow::ProcessEvents() -{ - CheckInitSDL(); - - SDL_Event event; - while (SDL_PollEvent(&event) != 0) - { - DispatchEvent(event); - } -} - -void SDL2DisplayWindow::RunLoop() -{ - CheckInitSDL(); - - while (!ExitRunLoop) - { - SDL_Event event; - int result = SDL_WaitEvent(&event); - if (result == 1) - DispatchEvent(event); - } -} - -void SDL2DisplayWindow::ExitLoop() -{ - CheckInitSDL(); - - ExitRunLoop = true; -} - -Size SDL2DisplayWindow::GetScreenSize() -{ - CheckInitSDL(); - - SDL_Rect rect = {}; - int result = SDL_GetDisplayBounds(0, &rect); - if (result != 0) - throw std::runtime_error(std::string("Unable to get screen size:") + SDL_GetError()); - - double uiscale = 1.0; // SDL2 doesn't really support this properly. SDL_GetDisplayDPI returns the wrong information according to the docs. - return Size(rect.w / uiscale, rect.h / uiscale); -} - -void* SDL2DisplayWindow::StartTimer(int timeoutMilliseconds, std::function onTimer) -{ - CheckInitSDL(); - - // To do: implement timers - - return nullptr; -} - -void SDL2DisplayWindow::StopTimer(void* timerID) -{ - CheckInitSDL(); - - // To do: implement timers -} - -SDL2DisplayWindow* SDL2DisplayWindow::FindEventWindow(const SDL_Event& event) -{ - int windowID; - switch (event.type) - { - case SDL_WINDOWEVENT: windowID = event.window.windowID; break; - case SDL_TEXTINPUT: windowID = event.text.windowID; break; - case SDL_KEYUP: windowID = event.key.windowID; break; - case SDL_KEYDOWN: windowID = event.key.windowID; break; - case SDL_MOUSEBUTTONUP: windowID = event.button.windowID; break; - case SDL_MOUSEBUTTONDOWN: windowID = event.button.windowID; break; - case SDL_MOUSEWHEEL: windowID = event.wheel.windowID; break; - case SDL_MOUSEMOTION: windowID = event.motion.windowID; break; - default: - if (event.type == PaintEventNumber) windowID = event.user.windowID; - else return nullptr; - } - - auto it = WindowList.find(windowID); - return it != WindowList.end() ? it->second : nullptr; -} - -void SDL2DisplayWindow::DispatchEvent(const SDL_Event& event) -{ - SDL2DisplayWindow* window = FindEventWindow(event); - if (!window) return; - - switch (event.type) - { - case SDL_WINDOWEVENT: window->OnWindowEvent(event.window); break; - case SDL_TEXTINPUT: window->OnTextInput(event.text); break; - case SDL_KEYUP: window->OnKeyUp(event.key); break; - case SDL_KEYDOWN: window->OnKeyDown(event.key); break; - case SDL_MOUSEBUTTONUP: window->OnMouseButtonUp(event.button); break; - case SDL_MOUSEBUTTONDOWN: window->OnMouseButtonDown(event.button); break; - case SDL_MOUSEWHEEL: window->OnMouseWheel(event.wheel); break; - case SDL_MOUSEMOTION: window->OnMouseMotion(event.motion); break; - default: - if (event.type == PaintEventNumber) window->OnPaintEvent(); - } -} - -void SDL2DisplayWindow::OnWindowEvent(const SDL_WindowEvent& event) -{ - switch (event.event) - { - case SDL_WINDOWEVENT_CLOSE: WindowHost->OnWindowClose(); break; - case SDL_WINDOWEVENT_MOVED: WindowHost->OnWindowGeometryChanged(); break; - case SDL_WINDOWEVENT_RESIZED: WindowHost->OnWindowGeometryChanged(); break; - case SDL_WINDOWEVENT_SHOWN: WindowHost->OnWindowPaint(); break; - case SDL_WINDOWEVENT_EXPOSED: WindowHost->OnWindowPaint(); break; - case SDL_WINDOWEVENT_FOCUS_GAINED: WindowHost->OnWindowActivated(); break; - case SDL_WINDOWEVENT_FOCUS_LOST: WindowHost->OnWindowDeactivated(); break; - } -} - -void SDL2DisplayWindow::OnTextInput(const SDL_TextInputEvent& event) -{ - WindowHost->OnWindowKeyChar(event.text); -} - -void SDL2DisplayWindow::OnKeyUp(const SDL_KeyboardEvent& event) -{ - WindowHost->OnWindowKeyUp(ScancodeToInputKey(event.keysym.scancode)); -} - -void SDL2DisplayWindow::OnKeyDown(const SDL_KeyboardEvent& event) -{ - WindowHost->OnWindowKeyDown(ScancodeToInputKey(event.keysym.scancode)); -} - -EInputKey SDL2DisplayWindow::GetMouseButtonKey(const SDL_MouseButtonEvent& event) -{ - switch (event.button) - { - case SDL_BUTTON_LEFT: return IK_LeftMouse; - case SDL_BUTTON_MIDDLE: return IK_MiddleMouse; - case SDL_BUTTON_RIGHT: return IK_RightMouse; - // case SDL_BUTTON_X1: return IK_XButton1; - // case SDL_BUTTON_X2: return IK_XButton2; - default: return IK_None; - } -} - -void SDL2DisplayWindow::OnMouseButtonUp(const SDL_MouseButtonEvent& event) -{ - EInputKey key = GetMouseButtonKey(event); - if (key != IK_None) - { - WindowHost->OnWindowMouseUp(GetMousePos(event), key); - } -} - -void SDL2DisplayWindow::OnMouseButtonDown(const SDL_MouseButtonEvent& event) -{ - EInputKey key = GetMouseButtonKey(event); - if (key != IK_None) - { - WindowHost->OnWindowMouseDown(GetMousePos(event), key); - } -} - -void SDL2DisplayWindow::OnMouseWheel(const SDL_MouseWheelEvent& event) -{ - EInputKey key = (event.y > 0) ? IK_MouseWheelUp : (event.y < 0) ? IK_MouseWheelDown : IK_None; - if (key != IK_None) - { - WindowHost->OnWindowMouseWheel(GetMousePos(event), key); - } -} - -void SDL2DisplayWindow::OnMouseMotion(const SDL_MouseMotionEvent& event) -{ - WindowHost->OnWindowMouseMove(GetMousePos(event)); -} - -void SDL2DisplayWindow::OnPaintEvent() -{ - WindowHost->OnWindowPaint(); -} - -EInputKey SDL2DisplayWindow::ScancodeToInputKey(SDL_Scancode keycode) -{ - switch (keycode) - { - case SDL_SCANCODE_BACKSPACE: return IK_Backspace; - case SDL_SCANCODE_TAB: return IK_Tab; - case SDL_SCANCODE_CLEAR: return IK_OEMClear; - case SDL_SCANCODE_RETURN: return IK_Enter; - case SDL_SCANCODE_MENU: return IK_Alt; - case SDL_SCANCODE_PAUSE: return IK_Pause; - case SDL_SCANCODE_ESCAPE: return IK_Escape; - case SDL_SCANCODE_SPACE: return IK_Space; - case SDL_SCANCODE_END: return IK_End; - case SDL_SCANCODE_HOME: return IK_Home; - case SDL_SCANCODE_LEFT: return IK_Left; - case SDL_SCANCODE_UP: return IK_Up; - case SDL_SCANCODE_RIGHT: return IK_Right; - case SDL_SCANCODE_DOWN: return IK_Down; - case SDL_SCANCODE_SELECT: return IK_Select; - case SDL_SCANCODE_PRINTSCREEN: return IK_Print; - case SDL_SCANCODE_EXECUTE: return IK_Execute; - case SDL_SCANCODE_INSERT: return IK_Insert; - case SDL_SCANCODE_DELETE: return IK_Delete; - case SDL_SCANCODE_HELP: return IK_Help; - case SDL_SCANCODE_0: return IK_0; - case SDL_SCANCODE_1: return IK_1; - case SDL_SCANCODE_2: return IK_2; - case SDL_SCANCODE_3: return IK_3; - case SDL_SCANCODE_4: return IK_4; - case SDL_SCANCODE_5: return IK_5; - case SDL_SCANCODE_6: return IK_6; - case SDL_SCANCODE_7: return IK_7; - case SDL_SCANCODE_8: return IK_8; - case SDL_SCANCODE_9: return IK_9; - case SDL_SCANCODE_A: return IK_A; - case SDL_SCANCODE_B: return IK_B; - case SDL_SCANCODE_C: return IK_C; - case SDL_SCANCODE_D: return IK_D; - case SDL_SCANCODE_E: return IK_E; - case SDL_SCANCODE_F: return IK_F; - case SDL_SCANCODE_G: return IK_G; - case SDL_SCANCODE_H: return IK_H; - case SDL_SCANCODE_I: return IK_I; - case SDL_SCANCODE_J: return IK_J; - case SDL_SCANCODE_K: return IK_K; - case SDL_SCANCODE_L: return IK_L; - case SDL_SCANCODE_M: return IK_M; - case SDL_SCANCODE_N: return IK_N; - case SDL_SCANCODE_O: return IK_O; - case SDL_SCANCODE_P: return IK_P; - case SDL_SCANCODE_Q: return IK_Q; - case SDL_SCANCODE_R: return IK_R; - case SDL_SCANCODE_S: return IK_S; - case SDL_SCANCODE_T: return IK_T; - case SDL_SCANCODE_U: return IK_U; - case SDL_SCANCODE_V: return IK_V; - case SDL_SCANCODE_W: return IK_W; - case SDL_SCANCODE_X: return IK_X; - case SDL_SCANCODE_Y: return IK_Y; - case SDL_SCANCODE_Z: return IK_Z; - case SDL_SCANCODE_KP_0: return IK_NumPad0; - case SDL_SCANCODE_KP_1: return IK_NumPad1; - case SDL_SCANCODE_KP_2: return IK_NumPad2; - case SDL_SCANCODE_KP_3: return IK_NumPad3; - case SDL_SCANCODE_KP_4: return IK_NumPad4; - case SDL_SCANCODE_KP_5: return IK_NumPad5; - case SDL_SCANCODE_KP_6: return IK_NumPad6; - case SDL_SCANCODE_KP_7: return IK_NumPad7; - case SDL_SCANCODE_KP_8: return IK_NumPad8; - case SDL_SCANCODE_KP_9: return IK_NumPad9; - // case SDL_SCANCODE_KP_ENTER: return IK_NumPadEnter; - // case SDL_SCANCODE_KP_MULTIPLY: return IK_Multiply; - // case SDL_SCANCODE_KP_PLUS: return IK_Add; - case SDL_SCANCODE_SEPARATOR: return IK_Separator; - // case SDL_SCANCODE_KP_MINUS: return IK_Subtract; - case SDL_SCANCODE_KP_PERIOD: return IK_NumPadPeriod; - // case SDL_SCANCODE_KP_DIVIDE: return IK_Divide; - case SDL_SCANCODE_F1: return IK_F1; - case SDL_SCANCODE_F2: return IK_F2; - case SDL_SCANCODE_F3: return IK_F3; - case SDL_SCANCODE_F4: return IK_F4; - case SDL_SCANCODE_F5: return IK_F5; - case SDL_SCANCODE_F6: return IK_F6; - case SDL_SCANCODE_F7: return IK_F7; - case SDL_SCANCODE_F8: return IK_F8; - case SDL_SCANCODE_F9: return IK_F9; - case SDL_SCANCODE_F10: return IK_F10; - case SDL_SCANCODE_F11: return IK_F11; - case SDL_SCANCODE_F12: return IK_F12; - case SDL_SCANCODE_F13: return IK_F13; - case SDL_SCANCODE_F14: return IK_F14; - case SDL_SCANCODE_F15: return IK_F15; - case SDL_SCANCODE_F16: return IK_F16; - case SDL_SCANCODE_F17: return IK_F17; - case SDL_SCANCODE_F18: return IK_F18; - case SDL_SCANCODE_F19: return IK_F19; - case SDL_SCANCODE_F20: return IK_F20; - case SDL_SCANCODE_F21: return IK_F21; - case SDL_SCANCODE_F22: return IK_F22; - case SDL_SCANCODE_F23: return IK_F23; - case SDL_SCANCODE_F24: return IK_F24; - case SDL_SCANCODE_NUMLOCKCLEAR: return IK_NumLock; - case SDL_SCANCODE_SCROLLLOCK: return IK_ScrollLock; - case SDL_SCANCODE_LSHIFT: return IK_LShift; - case SDL_SCANCODE_RSHIFT: return IK_RShift; - case SDL_SCANCODE_LCTRL: return IK_LControl; - case SDL_SCANCODE_RCTRL: return IK_RControl; - case SDL_SCANCODE_GRAVE: return IK_Tilde; - default: return IK_None; - } -} - -SDL_Scancode SDL2DisplayWindow::InputKeyToScancode(EInputKey inputkey) -{ - switch (inputkey) - { - case IK_Backspace: return SDL_SCANCODE_BACKSPACE; - case IK_Tab: return SDL_SCANCODE_TAB; - case IK_OEMClear: return SDL_SCANCODE_CLEAR; - case IK_Enter: return SDL_SCANCODE_RETURN; - case IK_Alt: return SDL_SCANCODE_MENU; - case IK_Pause: return SDL_SCANCODE_PAUSE; - case IK_Escape: return SDL_SCANCODE_ESCAPE; - case IK_Space: return SDL_SCANCODE_SPACE; - case IK_End: return SDL_SCANCODE_END; - case IK_Home: return SDL_SCANCODE_HOME; - case IK_Left: return SDL_SCANCODE_LEFT; - case IK_Up: return SDL_SCANCODE_UP; - case IK_Right: return SDL_SCANCODE_RIGHT; - case IK_Down: return SDL_SCANCODE_DOWN; - case IK_Select: return SDL_SCANCODE_SELECT; - case IK_Print: return SDL_SCANCODE_PRINTSCREEN; - case IK_Execute: return SDL_SCANCODE_EXECUTE; - case IK_Insert: return SDL_SCANCODE_INSERT; - case IK_Delete: return SDL_SCANCODE_DELETE; - case IK_Help: return SDL_SCANCODE_HELP; - case IK_0: return SDL_SCANCODE_0; - case IK_1: return SDL_SCANCODE_1; - case IK_2: return SDL_SCANCODE_2; - case IK_3: return SDL_SCANCODE_3; - case IK_4: return SDL_SCANCODE_4; - case IK_5: return SDL_SCANCODE_5; - case IK_6: return SDL_SCANCODE_6; - case IK_7: return SDL_SCANCODE_7; - case IK_8: return SDL_SCANCODE_8; - case IK_9: return SDL_SCANCODE_9; - case IK_A: return SDL_SCANCODE_A; - case IK_B: return SDL_SCANCODE_B; - case IK_C: return SDL_SCANCODE_C; - case IK_D: return SDL_SCANCODE_D; - case IK_E: return SDL_SCANCODE_E; - case IK_F: return SDL_SCANCODE_F; - case IK_G: return SDL_SCANCODE_G; - case IK_H: return SDL_SCANCODE_H; - case IK_I: return SDL_SCANCODE_I; - case IK_J: return SDL_SCANCODE_J; - case IK_K: return SDL_SCANCODE_K; - case IK_L: return SDL_SCANCODE_L; - case IK_M: return SDL_SCANCODE_M; - case IK_N: return SDL_SCANCODE_N; - case IK_O: return SDL_SCANCODE_O; - case IK_P: return SDL_SCANCODE_P; - case IK_Q: return SDL_SCANCODE_Q; - case IK_R: return SDL_SCANCODE_R; - case IK_S: return SDL_SCANCODE_S; - case IK_T: return SDL_SCANCODE_T; - case IK_U: return SDL_SCANCODE_U; - case IK_V: return SDL_SCANCODE_V; - case IK_W: return SDL_SCANCODE_W; - case IK_X: return SDL_SCANCODE_X; - case IK_Y: return SDL_SCANCODE_Y; - case IK_Z: return SDL_SCANCODE_Z; - case IK_NumPad0: return SDL_SCANCODE_KP_0; - case IK_NumPad1: return SDL_SCANCODE_KP_1; - case IK_NumPad2: return SDL_SCANCODE_KP_2; - case IK_NumPad3: return SDL_SCANCODE_KP_3; - case IK_NumPad4: return SDL_SCANCODE_KP_4; - case IK_NumPad5: return SDL_SCANCODE_KP_5; - case IK_NumPad6: return SDL_SCANCODE_KP_6; - case IK_NumPad7: return SDL_SCANCODE_KP_7; - case IK_NumPad8: return SDL_SCANCODE_KP_8; - case IK_NumPad9: return SDL_SCANCODE_KP_9; - // case IK_NumPadEnter: return SDL_SCANCODE_KP_ENTER; - // case IK_Multiply return SDL_SCANCODE_KP_MULTIPLY:; - // case IK_Add: return SDL_SCANCODE_KP_PLUS; - case IK_Separator: return SDL_SCANCODE_SEPARATOR; - // case IK_Subtract: return SDL_SCANCODE_KP_MINUS; - case IK_NumPadPeriod: return SDL_SCANCODE_KP_PERIOD; - // case IK_Divide: return SDL_SCANCODE_KP_DIVIDE; - case IK_F1: return SDL_SCANCODE_F1; - case IK_F2: return SDL_SCANCODE_F2; - case IK_F3: return SDL_SCANCODE_F3; - case IK_F4: return SDL_SCANCODE_F4; - case IK_F5: return SDL_SCANCODE_F5; - case IK_F6: return SDL_SCANCODE_F6; - case IK_F7: return SDL_SCANCODE_F7; - case IK_F8: return SDL_SCANCODE_F8; - case IK_F9: return SDL_SCANCODE_F9; - case IK_F10: return SDL_SCANCODE_F10; - case IK_F11: return SDL_SCANCODE_F11; - case IK_F12: return SDL_SCANCODE_F12; - case IK_F13: return SDL_SCANCODE_F13; - case IK_F14: return SDL_SCANCODE_F14; - case IK_F15: return SDL_SCANCODE_F15; - case IK_F16: return SDL_SCANCODE_F16; - case IK_F17: return SDL_SCANCODE_F17; - case IK_F18: return SDL_SCANCODE_F18; - case IK_F19: return SDL_SCANCODE_F19; - case IK_F20: return SDL_SCANCODE_F20; - case IK_F21: return SDL_SCANCODE_F21; - case IK_F22: return SDL_SCANCODE_F22; - case IK_F23: return SDL_SCANCODE_F23; - case IK_F24: return SDL_SCANCODE_F24; - case IK_NumLock: return SDL_SCANCODE_NUMLOCKCLEAR; - case IK_ScrollLock: return SDL_SCANCODE_SCROLLLOCK; - case IK_LShift: return SDL_SCANCODE_LSHIFT; - case IK_RShift: return SDL_SCANCODE_RSHIFT; - case IK_LControl: return SDL_SCANCODE_LCTRL; - case IK_RControl: return SDL_SCANCODE_RCTRL; - case IK_Tilde: return SDL_SCANCODE_GRAVE; - default: return (SDL_Scancode)0; - } -} 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/win32_display_window.cpp b/libraries/ZWidget/src/window/win32/win32_display_window.cpp new file mode 100644 index 000000000..f45da1a14 --- /dev/null +++ b/libraries/ZWidget/src/window/win32/win32_display_window.cpp @@ -0,0 +1,788 @@ + +#include "win32_display_window.h" +#include +#include +#include +#include +#include + +#pragma comment(lib, "dwmapi.lib") + +#ifndef HID_USAGE_PAGE_GENERIC +#define HID_USAGE_PAGE_GENERIC ((USHORT) 0x01) +#endif + +#ifndef HID_USAGE_GENERIC_MOUSE +#define HID_USAGE_GENERIC_MOUSE ((USHORT) 0x02) +#endif + +#ifndef HID_USAGE_GENERIC_JOYSTICK +#define HID_USAGE_GENERIC_JOYSTICK ((USHORT) 0x04) +#endif + +#ifndef HID_USAGE_GENERIC_GAMEPAD +#define HID_USAGE_GENERIC_GAMEPAD ((USHORT) 0x05) +#endif + +#ifndef RIDEV_INPUTSINK +#define RIDEV_INPUTSINK (0x100) +#endif + +Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner, RenderAPI renderAPI) : WindowHost(windowHost), PopupWindow(popupWindow) +{ + Windows.push_front(this); + WindowsIterator = Windows.begin(); + + WNDCLASSEX classdesc = {}; + classdesc.cbSize = sizeof(WNDCLASSEX); + classdesc.hInstance = GetModuleHandle(0); + classdesc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS; + classdesc.lpszClassName = L"ZWidgetWindow"; + classdesc.lpfnWndProc = &Win32DisplayWindow::WndProc; + RegisterClassEx(&classdesc); + + // Microsoft logic at its finest: + // WS_EX_DLGMODALFRAME hides the sysmenu icon + // WS_CAPTION shows the caption (yay! actually a flag that does what it says it does!) + // WS_SYSMENU shows the min/max/close buttons + // WS_THICKFRAME makes the window resizable + + DWORD style = 0, exstyle = 0; + if (popupWindow) + { + exstyle = WS_EX_NOACTIVATE; + style = WS_POPUP; + } + else + { + 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.hwnd : 0, 0, GetModuleHandle(0), this); +} + +Win32DisplayWindow::~Win32DisplayWindow() +{ + if (WindowHandle.hwnd) + { + DestroyWindow(WindowHandle.hwnd); + WindowHandle.hwnd = 0; + } + + Windows.erase(WindowsIterator); +} + +void Win32DisplayWindow::SetWindowTitle(const std::string& text) +{ + SetWindowText(WindowHandle.hwnd, to_utf16(text).c_str()); +} + +void Win32DisplayWindow::SetBorderColor(uint32_t bgra8) +{ + bgra8 = bgra8 & 0x00ffffff; + DwmSetWindowAttribute(WindowHandle.hwnd, 34/*DWMWA_BORDER_COLOR*/, &bgra8, sizeof(uint32_t)); +} + +void Win32DisplayWindow::SetCaptionColor(uint32_t bgra8) +{ + bgra8 = bgra8 & 0x00ffffff; + DwmSetWindowAttribute(WindowHandle.hwnd, 35/*DWMWA_CAPTION_COLOR*/, &bgra8, sizeof(uint32_t)); +} + +void Win32DisplayWindow::SetCaptionTextColor(uint32_t bgra8) +{ + bgra8 = bgra8 & 0x00ffffff; + DwmSetWindowAttribute(WindowHandle.hwnd, 36/*DWMWA_TEXT_COLOR*/, &bgra8, sizeof(uint32_t)); +} + +void Win32DisplayWindow::SetWindowFrame(const Rect& box) +{ + double dpiscale = GetDpiScale(); + 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) +{ + double dpiscale = GetDpiScale(); + + RECT rect = {}; + rect.left = (int)std::round(box.x * dpiscale); + rect.top = (int)std::round(box.y * dpiscale); + 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.hwnd, GWL_STYLE); + DWORD exstyle = (DWORD)GetWindowLongPtr(WindowHandle.hwnd, GWL_EXSTYLE); + AdjustWindowRectExForDpi(&rect, style, FALSE, exstyle, GetDpiForWindow(WindowHandle.hwnd)); + + 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.hwnd, PopupWindow ? SW_SHOWNA : SW_SHOW); +} + +void Win32DisplayWindow::ShowFullscreen() +{ + HDC screenDC = GetDC(0); + int width = GetDeviceCaps(screenDC, HORZRES); + int height = GetDeviceCaps(screenDC, VERTRES); + ReleaseDC(0, screenDC); + 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.hwnd, SW_SHOWMAXIMIZED); +} + +void Win32DisplayWindow::ShowMinimized() +{ + ShowWindow(WindowHandle.hwnd, SW_SHOWMINIMIZED); +} + +void Win32DisplayWindow::ShowNormal() +{ + 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.hwnd, SW_HIDE); +} + +void Win32DisplayWindow::Activate() +{ + if (!PopupWindow) + SetFocus(WindowHandle.hwnd); +} + +void Win32DisplayWindow::ShowCursor(bool enable) +{ +} + +void Win32DisplayWindow::LockCursor() +{ + if (!MouseLocked) + { + 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)); + } +} + +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); + } +} + +void Win32DisplayWindow::CaptureMouse() +{ + SetCapture(WindowHandle.hwnd); +} + +void Win32DisplayWindow::ReleaseMouseCapture() +{ + ReleaseCapture(); +} + +void Win32DisplayWindow::Update() +{ + InvalidateRect(WindowHandle.hwnd, nullptr, FALSE); +} + +bool Win32DisplayWindow::GetKeyState(InputKey key) +{ + return ::GetKeyState((int)key) & 0x8000; // High bit (0x8000) means key is down, Low bit (0x0001) means key is sticky on (like Caps Lock, Num Lock, etc.) +} + +void Win32DisplayWindow::SetCursor(StandardCursor cursor) +{ + if (cursor != CurrentCursor) + { + CurrentCursor = cursor; + UpdateCursor(); + } +} + +Rect Win32DisplayWindow::GetWindowFrame() const +{ + RECT box = {}; + GetWindowRect(WindowHandle.hwnd, &box); + double dpiscale = GetDpiScale(); + 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 +{ + double dpiscale = GetDpiScale(); + POINT point = {}; + point.x = (LONG)std::round(pos.x / dpiscale); + point.y = (LONG)std::round(pos.y / dpiscale); + ScreenToClient(WindowHandle.hwnd, &point); + return Point(point.x * dpiscale, point.y * dpiscale); +} + +Point Win32DisplayWindow::MapToGlobal(const Point& pos) const +{ + double dpiscale = GetDpiScale(); + POINT point = {}; + point.x = (LONG)std::round(pos.x * dpiscale); + point.y = (LONG)std::round(pos.y * dpiscale); + ClientToScreen(WindowHandle.hwnd, &point); + return Point(point.x / dpiscale, point.y / dpiscale); +} + +Size Win32DisplayWindow::GetClientSize() const +{ + RECT box = {}; + GetClientRect(WindowHandle.hwnd, &box); + double dpiscale = GetDpiScale(); + return Size(box.right / dpiscale, box.bottom / dpiscale); +} + +int Win32DisplayWindow::GetPixelWidth() const +{ + RECT box = {}; + GetClientRect(WindowHandle.hwnd, &box); + return box.right; +} + +int Win32DisplayWindow::GetPixelHeight() const +{ + RECT box = {}; + GetClientRect(WindowHandle.hwnd, &box); + return box.bottom; +} + +double Win32DisplayWindow::GetDpiScale() const +{ + return GetDpiForWindow(WindowHandle.hwnd) / 96.0; +} + +std::string Win32DisplayWindow::GetClipboardText() +{ + BOOL result = OpenClipboard(WindowHandle.hwnd); + if (result == FALSE) + throw std::runtime_error("Unable to open clipboard"); + + HANDLE handle = GetClipboardData(CF_UNICODETEXT); + if (handle == 0) + { + CloseClipboard(); + return std::string(); + } + + std::wstring::value_type* data = (std::wstring::value_type*)GlobalLock(handle); + if (data == 0) + { + CloseClipboard(); + return std::string(); + } + std::string str = from_utf16(data); + GlobalUnlock(handle); + + CloseClipboard(); + return str; +} + +void Win32DisplayWindow::SetClipboardText(const std::string& text) +{ + std::wstring text16 = to_utf16(text); + + BOOL result = OpenClipboard(WindowHandle.hwnd); + if (result == FALSE) + throw std::runtime_error("Unable to open clipboard"); + + result = EmptyClipboard(); + if (result == FALSE) + { + CloseClipboard(); + throw std::runtime_error("Unable to empty clipboard"); + } + + unsigned int length = (unsigned int)((text16.length() + 1) * sizeof(std::wstring::value_type)); + HANDLE handle = GlobalAlloc(GMEM_MOVEABLE, length); + if (handle == 0) + { + CloseClipboard(); + throw std::runtime_error("Unable to allocate clipboard memory"); + } + + void* data = GlobalLock(handle); + if (data == 0) + { + GlobalFree(handle); + CloseClipboard(); + throw std::runtime_error("Unable to lock clipboard memory"); + } + memcpy(data, text16.c_str(), length); + GlobalUnlock(handle); + + HANDLE data_result = SetClipboardData(CF_UNICODETEXT, handle); + + if (data_result == 0) + { + GlobalFree(handle); + CloseClipboard(); + throw std::runtime_error("Unable to set clipboard data"); + } + + CloseClipboard(); +} + +void Win32DisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels) +{ + BITMAPV5HEADER header = {}; + header.bV5Size = sizeof(BITMAPV5HEADER); + header.bV5Width = width; + header.bV5Height = -height; + header.bV5Planes = 1; + header.bV5BitCount = 32; + header.bV5Compression = BI_BITFIELDS; + header.bV5AlphaMask = 0xff000000; + header.bV5RedMask = 0x00ff0000; + header.bV5GreenMask = 0x0000ff00; + header.bV5BlueMask = 0x000000ff; + header.bV5SizeImage = width * height * sizeof(uint32_t); + header.bV5CSType = LCS_sRGB; + + HDC dc = PaintDC; + if (dc != 0) + { + SetDIBitsToDevice(dc, 0, 0, width, height, 0, 0, 0, height, pixels, (const BITMAPINFO*)&header, BI_RGB); + } +} + +LRESULT Win32DisplayWindow::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lparam) +{ + LPARAM result = 0; + if (DwmDefWindowProc(WindowHandle.hwnd, msg, wparam, lparam, &result)) + return result; + + if (msg == WM_INPUT) + { + bool hasFocus = GetFocus() != 0; + + HRAWINPUT handle = (HRAWINPUT)lparam; + UINT size = 0; + UINT result = GetRawInputData(handle, RID_INPUT, 0, &size, sizeof(RAWINPUTHEADER)); + if (result == 0 && size > 0) + { + size *= 2; + std::vector buffer(size); + result = GetRawInputData(handle, RID_INPUT, buffer.data(), &size, sizeof(RAWINPUTHEADER)); + if (result >= 0) + { + RAWINPUT* rawinput = (RAWINPUT*)buffer.data(); + if (rawinput->header.dwType == RIM_TYPEMOUSE) + { + if (hasFocus) + WindowHost->OnWindowRawMouseMove(rawinput->data.mouse.lLastX, rawinput->data.mouse.lLastY); + } + } + } + return DefWindowProc(WindowHandle.hwnd, msg, wparam, lparam); + } + else if (msg == WM_PAINT) + { + PAINTSTRUCT paintStruct = {}; + PaintDC = BeginPaint(WindowHandle.hwnd, &paintStruct); + if (PaintDC) + { + WindowHost->OnWindowPaint(); + EndPaint(WindowHandle.hwnd, &paintStruct); + PaintDC = 0; + } + return 0; + } + else if (msg == WM_ACTIVATE) + { + 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.hwnd, &box); + + POINT center = {}; + center.x = box.right / 2; + center.y = box.bottom / 2; + ClientToScreen(WindowHandle.hwnd, ¢er); + + SetCursorPos(center.x, center.y); + } + else + { + UpdateCursor(); + } + + if (!TrackMouseActive) + { + TRACKMOUSEEVENT eventTrack = {}; + eventTrack.cbSize = sizeof(TRACKMOUSEEVENT); + eventTrack.hwndTrack = WindowHandle.hwnd; + eventTrack.dwFlags = TME_LEAVE; + if (TrackMouseEvent(&eventTrack)) + TrackMouseActive = true; + } + + WindowHost->OnWindowMouseMove(GetLParamPos(lparam)); + } + else if (msg == WM_MOUSELEAVE) + { + TrackMouseActive = false; + WindowHost->OnWindowMouseLeave(); + } + else if (msg == WM_LBUTTONDOWN) + { + WindowHost->OnWindowMouseDown(GetLParamPos(lparam), InputKey::LeftMouse); + } + else if (msg == WM_LBUTTONDBLCLK) + { + WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), InputKey::LeftMouse); + } + else if (msg == WM_LBUTTONUP) + { + WindowHost->OnWindowMouseUp(GetLParamPos(lparam), InputKey::LeftMouse); + } + else if (msg == WM_MBUTTONDOWN) + { + WindowHost->OnWindowMouseDown(GetLParamPos(lparam), InputKey::MiddleMouse); + } + else if (msg == WM_MBUTTONDBLCLK) + { + WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), InputKey::MiddleMouse); + } + else if (msg == WM_MBUTTONUP) + { + WindowHost->OnWindowMouseUp(GetLParamPos(lparam), InputKey::MiddleMouse); + } + else if (msg == WM_RBUTTONDOWN) + { + WindowHost->OnWindowMouseDown(GetLParamPos(lparam), InputKey::RightMouse); + } + else if (msg == WM_RBUTTONDBLCLK) + { + WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), InputKey::RightMouse); + } + else if (msg == WM_RBUTTONUP) + { + WindowHost->OnWindowMouseUp(GetLParamPos(lparam), InputKey::RightMouse); + } + else if (msg == WM_MOUSEWHEEL) + { + double delta = GET_WHEEL_DELTA_WPARAM(wparam) / (double)WHEEL_DELTA; + + // Note: WM_MOUSEWHEEL uses screen coordinates. GetLParamPos assumes client coordinates. + double dpiscale = GetDpiScale(); + POINT pos; + pos.x = GET_X_LPARAM(lparam); + pos.y = GET_Y_LPARAM(lparam); + ScreenToClient(WindowHandle.hwnd, &pos); + + WindowHost->OnWindowMouseWheel(Point(pos.x / dpiscale, pos.y / dpiscale), delta < 0.0 ? InputKey::MouseWheelDown : InputKey::MouseWheelUp); + } + else if (msg == WM_CHAR) + { + wchar_t buf[2] = { (wchar_t)wparam, 0 }; + WindowHost->OnWindowKeyChar(from_utf16(buf)); + } + else if (msg == WM_KEYDOWN) + { + WindowHost->OnWindowKeyDown((InputKey)wparam); + } + else if (msg == WM_KEYUP) + { + WindowHost->OnWindowKeyUp((InputKey)wparam); + } + else if (msg == WM_SETFOCUS) + { + if (MouseLocked) + { + ::ShowCursor(FALSE); + } + } + else if (msg == WM_KILLFOCUS) + { + if (MouseLocked) + { + ::ShowCursor(TRUE); + } + } + else if (msg == WM_CLOSE) + { + WindowHost->OnWindowClose(); + return 0; + } + else if (msg == WM_SIZE) + { + WindowHost->OnWindowGeometryChanged(); + return 0; + } + /*else if (msg == WM_NCCALCSIZE && wparam == TRUE) // calculate client area for the window + { + NCCALCSIZE_PARAMS* calcsize = (NCCALCSIZE_PARAMS*)lparam; + return WVR_REDRAW; + }*/ + + return DefWindowProc(WindowHandle.hwnd, msg, wparam, lparam); +} + +void Win32DisplayWindow::UpdateCursor() +{ + LPCWSTR cursor = IDC_ARROW; + switch (CurrentCursor) + { + case StandardCursor::arrow: cursor = IDC_ARROW; break; + case StandardCursor::appstarting: cursor = IDC_APPSTARTING; break; + case StandardCursor::cross: cursor = IDC_CROSS; break; + case StandardCursor::hand: cursor = IDC_HAND; break; + case StandardCursor::ibeam: cursor = IDC_IBEAM; break; + case StandardCursor::no: cursor = IDC_NO; break; + case StandardCursor::size_all: cursor = IDC_SIZEALL; break; + case StandardCursor::size_nesw: cursor = IDC_SIZENESW; break; + case StandardCursor::size_ns: cursor = IDC_SIZENS; break; + case StandardCursor::size_nwse: cursor = IDC_SIZENWSE; break; + case StandardCursor::size_we: cursor = IDC_SIZEWE; break; + case StandardCursor::uparrow: cursor = IDC_UPARROW; break; + case StandardCursor::wait: cursor = IDC_WAIT; break; + default: break; + } + + ::SetCursor((HCURSOR)LoadImage(0, cursor, IMAGE_CURSOR, LR_DEFAULTSIZE, LR_DEFAULTSIZE, LR_SHARED)); +} + +Point Win32DisplayWindow::GetLParamPos(LPARAM lparam) const +{ + double dpiscale = GetDpiScale(); + return Point(GET_X_LPARAM(lparam) / dpiscale, GET_Y_LPARAM(lparam) / dpiscale); +} + +LRESULT Win32DisplayWindow::WndProc(HWND windowhandle, UINT msg, WPARAM wparam, LPARAM lparam) +{ + if (msg == WM_CREATE) + { + CREATESTRUCT* createstruct = (CREATESTRUCT*)lparam; + Win32DisplayWindow* viewport = (Win32DisplayWindow*)createstruct->lpCreateParams; + viewport->WindowHandle.hwnd = windowhandle; + SetWindowLongPtr(windowhandle, GWLP_USERDATA, (LONG_PTR)viewport); + return viewport->OnWindowMessage(msg, wparam, lparam); + } + else + { + Win32DisplayWindow* viewport = (Win32DisplayWindow*)GetWindowLongPtr(windowhandle, GWLP_USERDATA); + if (viewport) + { + LRESULT result = viewport->OnWindowMessage(msg, wparam, lparam); + if (msg == WM_DESTROY) + { + SetWindowLongPtr(windowhandle, GWLP_USERDATA, 0); + viewport->WindowHandle.hwnd = 0; + } + return result; + } + else + { + return DefWindowProc(windowhandle, msg, wparam, lparam); + } + } +} + +void Win32DisplayWindow::ProcessEvents() +{ + while (true) + { + MSG msg = {}; + if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE) <= 0) + break; + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + +void Win32DisplayWindow::RunLoop() +{ + while (!ExitRunLoop && !Windows.empty()) + { + MSG msg = {}; + if (GetMessage(&msg, 0, 0, 0) <= 0) + break; + TranslateMessage(&msg); + DispatchMessage(&msg); + } + ExitRunLoop = false; +} + +void Win32DisplayWindow::ExitLoop() +{ + ExitRunLoop = true; +} + +Size Win32DisplayWindow::GetScreenSize() +{ + HDC screenDC = GetDC(0); + int screenWidth = GetDeviceCaps(screenDC, HORZRES); + int screenHeight = GetDeviceCaps(screenDC, VERTRES); + double dpiScale = GetDeviceCaps(screenDC, LOGPIXELSX) / 96.0; + ReleaseDC(0, screenDC); + + 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); + if (it != Win32DisplayWindow::Timers.end()) + { + auto callback = it->second; + callback(); + } +} + +void* Win32DisplayWindow::StartTimer(int timeoutMilliseconds, std::function onTimer) +{ + UINT_PTR result = SetTimer(0, 0, timeoutMilliseconds, Win32TimerCallback); + if (result == 0) + throw std::runtime_error("Could not create timer"); + Timers[result] = std::move(onTimer); + return (void*)result; +} + +void Win32DisplayWindow::StopTimer(void* timerID) +{ + auto it = Timers.find((UINT_PTR)timerID); + if (it != Timers.end()) + { + Timers.erase(it); + KillTimer(0, (UINT_PTR)timerID); + } +} + +std::list Win32DisplayWindow::Windows; +bool Win32DisplayWindow::ExitRunLoop; + +std::unordered_map> Win32DisplayWindow::Timers; diff --git a/libraries/ZWidget/src/window/win32/win32window.h b/libraries/ZWidget/src/window/win32/win32_display_window.h similarity index 70% rename from libraries/ZWidget/src/window/win32/win32window.h rename to libraries/ZWidget/src/window/win32/win32_display_window.h index a0f493c8b..7666274bd 100644 --- a/libraries/ZWidget/src/window/win32/win32window.h +++ b/libraries/ZWidget/src/window/win32/win32_display_window.h @@ -1,21 +1,17 @@ #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 Win32Window : public DisplayWindow +class Win32DisplayWindow : public DisplayWindow { public: - Win32Window(DisplayWindowHost* windowHost); - ~Win32Window(); + Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner, RenderAPI renderAPI); + ~Win32DisplayWindow(); void SetWindowTitle(const std::string& text) override; void SetWindowFrame(const Rect& box) 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; @@ -33,7 +30,7 @@ public: void CaptureMouse() override; void ReleaseMouseCapture() override; void Update() override; - bool GetKeyState(EInputKey key) override; + bool GetKeyState(InputKey key) override; void SetCursor(StandardCursor cursor) override; void UpdateCursor(); @@ -53,8 +50,16 @@ public: 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; + Point GetLParamPos(LPARAM lparam) const; + void* GetNativeHandle() override { return &WindowHandle; } + + std::vector GetVulkanInstanceExtensions() override; + VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override; + static void ProcessEvents(); static void RunLoop(); static void ExitLoop(); @@ -64,8 +69,8 @@ public: static void StopTimer(void* timerID); static bool ExitRunLoop; - static std::list Windows; - std::list::iterator WindowsIterator; + static std::list Windows; + std::list::iterator WindowsIterator; static std::unordered_map> Timers; @@ -73,13 +78,16 @@ 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; POINT MouseLockPos = {}; + bool TrackMouseActive = false; + HDC PaintDC = 0; StandardCursor CurrentCursor = StandardCursor::arrow; 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/win32/win32window.cpp b/libraries/ZWidget/src/window/win32/win32window.cpp deleted file mode 100644 index 6ad09ef60..000000000 --- a/libraries/ZWidget/src/window/win32/win32window.cpp +++ /dev/null @@ -1,702 +0,0 @@ - -#include "win32window.h" -#include -#include -#include -#include -#include - -#pragma comment(lib, "dwmapi.lib") - -#ifndef HID_USAGE_PAGE_GENERIC -#define HID_USAGE_PAGE_GENERIC ((USHORT) 0x01) -#endif - -#ifndef HID_USAGE_GENERIC_MOUSE -#define HID_USAGE_GENERIC_MOUSE ((USHORT) 0x02) -#endif - -#ifndef HID_USAGE_GENERIC_JOYSTICK -#define HID_USAGE_GENERIC_JOYSTICK ((USHORT) 0x04) -#endif - -#ifndef HID_USAGE_GENERIC_GAMEPAD -#define HID_USAGE_GENERIC_GAMEPAD ((USHORT) 0x05) -#endif - -#ifndef RIDEV_INPUTSINK -#define RIDEV_INPUTSINK (0x100) -#endif - -#ifdef MINGW -// MinGW's library doesn't contain a thunk for DwmDefWindowProc, so we need to create our own - -BOOL DwmDefWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, LRESULT *plResult ) -{ - typedef BOOL(* dwmdwp)(HWND, UINT, WPARAM, LPARAM, LRESULT* ); - BOOL result(FALSE); - HMODULE module = LoadLibrary( _T( "dwmapi.dll" ) ); - if( module ) { - dwmdwp proc = reinterpret_cast( GetProcAddress( module, "DwmDefWindowProc" ) ); - if( proc ) { - result = proc( hWnd, msg, wParam, lParam, plResult ); - } - FreeLibrary(module); - } - return result; -} - -#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; -} - -Win32Window::Win32Window(DisplayWindowHost* windowHost) : WindowHost(windowHost) -{ - Windows.push_front(this); - WindowsIterator = Windows.begin(); - - WNDCLASSEXW classdesc = {}; - classdesc.cbSize = sizeof(WNDCLASSEX); - classdesc.hInstance = GetModuleHandle(0); - classdesc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS; - classdesc.lpszClassName = L"ZWidgetWindow"; - classdesc.lpfnWndProc = &Win32Window::WndProc; - RegisterClassEx(&classdesc); - - // Microsoft logic at its finest: - // WS_EX_DLGMODALFRAME hides the sysmenu icon - // WS_CAPTION shows the caption (yay! actually a flag that does what it says it does!) - // WS_SYSMENU shows the min/max/close buttons - // WS_THICKFRAME makes the window resizable - CreateWindowExW(WS_EX_APPWINDOW | WS_EX_DLGMODALFRAME, L"ZWidgetWindow", L"", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, 0, 0, 100, 100, 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)); - */ -} - -Win32Window::~Win32Window() -{ - if (WindowHandle) - { - DestroyWindow(WindowHandle); - WindowHandle = 0; - } - - Windows.erase(WindowsIterator); -} - -void Win32Window::SetWindowTitle(const std::string& text) -{ - SetWindowText(WindowHandle, to_utf16(text).c_str()); -} - -void Win32Window::SetBorderColor(uint32_t bgra8) -{ - bgra8 = bgra8 & 0x00ffffff; - DwmSetWindowAttribute(WindowHandle, 34/*DWMWA_BORDER_COLOR*/, &bgra8, sizeof(uint32_t)); -} - -void Win32Window::SetCaptionColor(uint32_t bgra8) -{ - bgra8 = bgra8 & 0x00ffffff; - DwmSetWindowAttribute(WindowHandle, 35/*DWMWA_CAPTION_COLOR*/, &bgra8, sizeof(uint32_t)); -} - -void Win32Window::SetCaptionTextColor(uint32_t bgra8) -{ - bgra8 = bgra8 & 0x00ffffff; - DwmSetWindowAttribute(WindowHandle, 36/*DWMWA_TEXT_COLOR*/, &bgra8, sizeof(uint32_t)); -} - -void Win32Window::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); -} - -void Win32Window::SetClientFrame(const Rect& box) -{ - // This function is currently unused but needs to be disabled because it contains Windows API calls that were only added in Windows 10. -#if 0 - double dpiscale = GetDpiScale(); - - RECT rect = {}; - rect.left = (int)std::round(box.x * dpiscale); - rect.top = (int)std::round(box.y * dpiscale); - 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)); - - SetWindowPos(WindowHandle, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER); -#endif -} - -void Win32Window::Show() -{ - ShowWindow(WindowHandle, SW_SHOW); -} - -void Win32Window::ShowFullscreen() -{ - HDC screenDC = GetDC(0); - 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); - Fullscreen = true; -} - -void Win32Window::ShowMaximized() -{ - ShowWindow(WindowHandle, SW_SHOWMAXIMIZED); -} - -void Win32Window::ShowMinimized() -{ - ShowWindow(WindowHandle, SW_SHOWMINIMIZED); -} - -void Win32Window::ShowNormal() -{ - ShowWindow(WindowHandle, SW_NORMAL); -} - -void Win32Window::Hide() -{ - ShowWindow(WindowHandle, SW_HIDE); -} - -void Win32Window::Activate() -{ - SetFocus(WindowHandle); -} - -void Win32Window::ShowCursor(bool enable) -{ -} - -void Win32Window::LockCursor() -{ - if (!MouseLocked) - { - MouseLocked = true; - GetCursorPos(&MouseLockPos); - ::ShowCursor(FALSE); - } -} - -void Win32Window::UnlockCursor() -{ - if (MouseLocked) - { - MouseLocked = false; - SetCursorPos(MouseLockPos.x, MouseLockPos.y); - ::ShowCursor(TRUE); - } -} - -void Win32Window::CaptureMouse() -{ - SetCapture(WindowHandle); -} - -void Win32Window::ReleaseMouseCapture() -{ - ReleaseCapture(); -} - -void Win32Window::Update() -{ - InvalidateRect(WindowHandle, nullptr, FALSE); -} - -bool Win32Window::GetKeyState(EInputKey key) -{ - return ::GetKeyState((int)key) & 0x8000; // High bit (0x8000) means key is down, Low bit (0x0001) means key is sticky on (like Caps Lock, Num Lock, etc.) -} - -void Win32Window::SetCursor(StandardCursor cursor) -{ - if (cursor != CurrentCursor) - { - CurrentCursor = cursor; - UpdateCursor(); - } -} - -Rect Win32Window::GetWindowFrame() const -{ - RECT box = {}; - GetWindowRect(WindowHandle, &box); - double dpiscale = GetDpiScale(); - return Rect(box.left / dpiscale, box.top / dpiscale, box.right / dpiscale, box.bottom / dpiscale); -} - -Size Win32Window::GetClientSize() const -{ - RECT box = {}; - GetClientRect(WindowHandle, &box); - double dpiscale = GetDpiScale(); - return Size(box.right / dpiscale, box.bottom / dpiscale); -} - -int Win32Window::GetPixelWidth() const -{ - RECT box = {}; - GetClientRect(WindowHandle, &box); - return box.right; -} - -int Win32Window::GetPixelHeight() const -{ - RECT box = {}; - GetClientRect(WindowHandle, &box); - return box.bottom; -} - -typedef UINT(WINAPI* GetDpiForWindow_t)(HWND); -double Win32Window::GetDpiScale() const -{ - static GetDpiForWindow_t pGetDpiForWindow = nullptr; - static bool done = false; - if (!done) - { - HMODULE hMod = GetModuleHandleA("User32.dll"); - if (hMod != nullptr) pGetDpiForWindow = reinterpret_cast(GetProcAddress(hMod, "GetDpiForWindow")); - done = true; - } - - if (pGetDpiForWindow) - return pGetDpiForWindow(WindowHandle) / 96.0; - else - return 1.0; -} - -std::string Win32Window::GetClipboardText() -{ - BOOL result = OpenClipboard(WindowHandle); - if (result == FALSE) - throw std::runtime_error("Unable to open clipboard"); - - HANDLE handle = GetClipboardData(CF_UNICODETEXT); - if (handle == 0) - { - CloseClipboard(); - return std::string(); - } - - std::wstring::value_type* data = (std::wstring::value_type*)GlobalLock(handle); - if (data == 0) - { - CloseClipboard(); - return std::string(); - } - std::string str = from_utf16(data); - GlobalUnlock(handle); - - CloseClipboard(); - return str; -} - -void Win32Window::SetClipboardText(const std::string& text) -{ - std::wstring text16 = to_utf16(text); - - BOOL result = OpenClipboard(WindowHandle); - if (result == FALSE) - throw std::runtime_error("Unable to open clipboard"); - - result = EmptyClipboard(); - if (result == FALSE) - { - CloseClipboard(); - throw std::runtime_error("Unable to empty clipboard"); - } - - unsigned int length = (text16.length() + 1) * sizeof(std::wstring::value_type); - HANDLE handle = GlobalAlloc(GMEM_MOVEABLE, length); - if (handle == 0) - { - CloseClipboard(); - throw std::runtime_error("Unable to allocate clipboard memory"); - } - - void* data = GlobalLock(handle); - if (data == 0) - { - GlobalFree(handle); - CloseClipboard(); - throw std::runtime_error("Unable to lock clipboard memory"); - } - memcpy(data, text16.c_str(), length); - GlobalUnlock(handle); - - HANDLE data_result = SetClipboardData(CF_UNICODETEXT, handle); - - if (data_result == 0) - { - GlobalFree(handle); - CloseClipboard(); - throw std::runtime_error("Unable to set clipboard data"); - } - - CloseClipboard(); -} - -void Win32Window::PresentBitmap(int width, int height, const uint32_t* pixels) -{ - BITMAPV5HEADER header = {}; - header.bV5Size = sizeof(BITMAPV5HEADER); - header.bV5Width = width; - header.bV5Height = -height; - header.bV5Planes = 1; - header.bV5BitCount = 32; - header.bV5Compression = BI_BITFIELDS; - header.bV5AlphaMask = 0xff000000; - header.bV5RedMask = 0x00ff0000; - header.bV5GreenMask = 0x0000ff00; - header.bV5BlueMask = 0x000000ff; - header.bV5SizeImage = width * height * sizeof(uint32_t); - header.bV5CSType = LCS_sRGB; - - HDC dc = PaintDC; - if (dc != 0) - { - int result = SetDIBitsToDevice(dc, 0, 0, width, height, 0, 0, 0, height, pixels, (const BITMAPINFO*)&header, BI_RGB); - ReleaseDC(WindowHandle, dc); - } -} - -LRESULT Win32Window::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lparam) -{ - LPARAM result = 0; - - if (DwmDefWindowProc(WindowHandle, msg, wparam, lparam, &result)) - return result; - - if (msg == WM_INPUT) - { - bool hasFocus = GetFocus() != 0; - - HRAWINPUT handle = (HRAWINPUT)lparam; - UINT size = 0; - UINT result = GetRawInputData(handle, RID_INPUT, 0, &size, sizeof(RAWINPUTHEADER)); - if (result == 0 && size > 0) - { - size *= 2; - std::vector buffer(size); - result = GetRawInputData(handle, RID_INPUT, buffer.data(), &size, sizeof(RAWINPUTHEADER)); - if (result >= 0) - { - RAWINPUT* rawinput = (RAWINPUT*)buffer.data(); - if (rawinput->header.dwType == RIM_TYPEMOUSE) - { - if (hasFocus) - WindowHost->OnWindowRawMouseMove(rawinput->data.mouse.lLastX, rawinput->data.mouse.lLastY); - } - } - } - return DefWindowProc(WindowHandle, msg, wparam, lparam); - } - else if (msg == WM_PAINT) - { - PAINTSTRUCT paintStruct = {}; - PaintDC = BeginPaint(WindowHandle, &paintStruct); - if (PaintDC) - { - WindowHost->OnWindowPaint(); - EndPaint(WindowHandle, &paintStruct); - PaintDC = 0; - } - return 0; - } - else if (msg == WM_ACTIVATE) - { - WindowHost->OnWindowActivated(); - } - else if (msg == WM_MOUSEMOVE) - { - if (MouseLocked && GetFocus() != 0) - { - RECT box = {}; - GetClientRect(WindowHandle, &box); - - POINT center = {}; - center.x = box.right / 2; - center.y = box.bottom / 2; - ClientToScreen(WindowHandle, ¢er); - - SetCursorPos(center.x, center.y); - } - else - { - UpdateCursor(); - } - - WindowHost->OnWindowMouseMove(GetLParamPos(lparam)); - } - else if (msg == WM_LBUTTONDOWN) - { - WindowHost->OnWindowMouseDown(GetLParamPos(lparam), IK_LeftMouse); - } - else if (msg == WM_LBUTTONDBLCLK) - { - WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), IK_LeftMouse); - } - else if (msg == WM_LBUTTONUP) - { - WindowHost->OnWindowMouseUp(GetLParamPos(lparam), IK_LeftMouse); - } - else if (msg == WM_MBUTTONDOWN) - { - WindowHost->OnWindowMouseDown(GetLParamPos(lparam), IK_MiddleMouse); - } - else if (msg == WM_MBUTTONDBLCLK) - { - WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), IK_MiddleMouse); - } - else if (msg == WM_MBUTTONUP) - { - WindowHost->OnWindowMouseUp(GetLParamPos(lparam), IK_MiddleMouse); - } - else if (msg == WM_RBUTTONDOWN) - { - WindowHost->OnWindowMouseDown(GetLParamPos(lparam), IK_RightMouse); - } - else if (msg == WM_RBUTTONDBLCLK) - { - WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), IK_RightMouse); - } - else if (msg == WM_RBUTTONUP) - { - WindowHost->OnWindowMouseUp(GetLParamPos(lparam), IK_RightMouse); - } - else if (msg == WM_MOUSEWHEEL) - { - double delta = GET_WHEEL_DELTA_WPARAM(wparam) / (double)WHEEL_DELTA; - - // Note: WM_MOUSEWHEEL uses screen coordinates. GetLParamPos assumes client coordinates. - double dpiscale = GetDpiScale(); - POINT pos; - pos.x = GET_X_LPARAM(lparam); - pos.y = GET_Y_LPARAM(lparam); - ScreenToClient(WindowHandle, &pos); - - WindowHost->OnWindowMouseWheel(Point(pos.x / dpiscale, pos.y / dpiscale), delta < 0.0 ? IK_MouseWheelDown : IK_MouseWheelUp); - } - else if (msg == WM_CHAR) - { - wchar_t buf[2] = { (wchar_t)wparam, 0 }; - WindowHost->OnWindowKeyChar(from_utf16(buf)); - } - else if (msg == WM_KEYDOWN) - { - WindowHost->OnWindowKeyDown((EInputKey)wparam); - } - else if (msg == WM_KEYUP) - { - WindowHost->OnWindowKeyUp((EInputKey)wparam); - } - else if (msg == WM_SETFOCUS) - { - if (MouseLocked) - { - ::ShowCursor(FALSE); - } - } - else if (msg == WM_KILLFOCUS) - { - if (MouseLocked) - { - ::ShowCursor(TRUE); - } - } - else if (msg == WM_CLOSE) - { - WindowHost->OnWindowClose(); - return 0; - } - else if (msg == WM_SIZE) - { - WindowHost->OnWindowGeometryChanged(); - return 0; - } - /*else if (msg == WM_NCCALCSIZE && wparam == TRUE) // calculate client area for the window - { - NCCALCSIZE_PARAMS* calcsize = (NCCALCSIZE_PARAMS*)lparam; - return WVR_REDRAW; - }*/ - - return DefWindowProc(WindowHandle, msg, wparam, lparam); -} - -void Win32Window::UpdateCursor() -{ - LPCWSTR cursor = IDC_ARROW; - switch (CurrentCursor) - { - case StandardCursor::arrow: cursor = IDC_ARROW; break; - case StandardCursor::appstarting: cursor = IDC_APPSTARTING; break; - case StandardCursor::cross: cursor = IDC_CROSS; break; - case StandardCursor::hand: cursor = IDC_HAND; break; - case StandardCursor::ibeam: cursor = IDC_IBEAM; break; - case StandardCursor::no: cursor = IDC_NO; break; - case StandardCursor::size_all: cursor = IDC_SIZEALL; break; - case StandardCursor::size_nesw: cursor = IDC_SIZENESW; break; - case StandardCursor::size_ns: cursor = IDC_SIZENS; break; - case StandardCursor::size_nwse: cursor = IDC_SIZENWSE; break; - case StandardCursor::size_we: cursor = IDC_SIZEWE; break; - case StandardCursor::uparrow: cursor = IDC_UPARROW; break; - case StandardCursor::wait: cursor = IDC_WAIT; break; - default: break; - } - - ::SetCursor((HCURSOR)LoadImage(0, cursor, IMAGE_CURSOR, LR_DEFAULTSIZE, LR_DEFAULTSIZE, LR_SHARED)); -} - -Point Win32Window::GetLParamPos(LPARAM lparam) const -{ - double dpiscale = GetDpiScale(); - return Point(GET_X_LPARAM(lparam) / dpiscale, GET_Y_LPARAM(lparam) / dpiscale); -} - -LRESULT Win32Window::WndProc(HWND windowhandle, UINT msg, WPARAM wparam, LPARAM lparam) -{ - if (msg == WM_CREATE) - { - CREATESTRUCT* createstruct = (CREATESTRUCT*)lparam; - Win32Window* viewport = (Win32Window*)createstruct->lpCreateParams; - viewport->WindowHandle = windowhandle; - SetWindowLongPtr(windowhandle, GWLP_USERDATA, (LONG_PTR)viewport); - return viewport->OnWindowMessage(msg, wparam, lparam); - } - else - { - Win32Window* viewport = (Win32Window*)GetWindowLongPtr(windowhandle, GWLP_USERDATA); - if (viewport) - { - LRESULT result = viewport->OnWindowMessage(msg, wparam, lparam); - if (msg == WM_DESTROY) - { - SetWindowLongPtr(windowhandle, GWLP_USERDATA, 0); - viewport->WindowHandle = 0; - } - return result; - } - else - { - return DefWindowProc(windowhandle, msg, wparam, lparam); - } - } -} - -void Win32Window::ProcessEvents() -{ - while (true) - { - MSG msg = {}; - if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE) <= 0) - break; - TranslateMessage(&msg); - DispatchMessage(&msg); - } -} - -void Win32Window::RunLoop() -{ - while (!ExitRunLoop && !Windows.empty()) - { - MSG msg = {}; - if (GetMessage(&msg, 0, 0, 0) <= 0) - break; - TranslateMessage(&msg); - DispatchMessage(&msg); - } - ExitRunLoop = false; -} - -void Win32Window::ExitLoop() -{ - ExitRunLoop = true; -} - -Size Win32Window::GetScreenSize() -{ - HDC screenDC = GetDC(0); - int screenWidth = GetDeviceCaps(screenDC, HORZRES); - int screenHeight = GetDeviceCaps(screenDC, VERTRES); - double dpiScale = GetDeviceCaps(screenDC, LOGPIXELSX) / 96.0; - ReleaseDC(0, screenDC); - - return Size(screenWidth / dpiScale, screenHeight / dpiScale); -} - -static void CALLBACK Win32TimerCallback(HWND handle, UINT message, UINT_PTR timerID, DWORD timestamp) -{ - auto it = Win32Window::Timers.find(timerID); - if (it != Win32Window::Timers.end()) - { - it->second(); - } -} - -void* Win32Window::StartTimer(int timeoutMilliseconds, std::function onTimer) -{ - UINT_PTR result = SetTimer(0, 0, timeoutMilliseconds, Win32TimerCallback); - if (result == 0) - throw std::runtime_error("Could not create timer"); - Timers[result] = std::move(onTimer); - return (void*)result; -} - -void Win32Window::StopTimer(void* timerID) -{ - auto it = Timers.find((UINT_PTR)timerID); - if (it != Timers.end()) - { - Timers.erase(it); - KillTimer(0, (UINT_PTR)timerID); - } -} - -std::list Win32Window::Windows; -bool Win32Window::ExitRunLoop; - -std::unordered_map> Win32Window::Timers; diff --git a/libraries/ZWidget/src/window/window.cpp b/libraries/ZWidget/src/window/window.cpp index b67b1a296..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/win32window.h" - -std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost) +std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) { - return std::make_unique(windowHost); + return DisplayBackend::Get()->Create(windowHost, popupWindow, owner, renderAPI); } void DisplayWindow::ProcessEvents() { - Win32Window::ProcessEvents(); + DisplayBackend::Get()->ProcessEvents(); } void DisplayWindow::RunLoop() { - Win32Window::RunLoop(); + DisplayBackend::Get()->RunLoop(); } void DisplayWindow::ExitLoop() { - Win32Window::ExitLoop(); -} - -Size DisplayWindow::GetScreenSize() -{ - return Win32Window::GetScreenSize(); + DisplayBackend::Get()->ExitLoop(); } void* DisplayWindow::StartTimer(int timeoutMilliseconds, std::function onTimer) { - return Win32Window::StartTimer(timeoutMilliseconds, std::move(onTimer)); + return DisplayBackend::Get()->StartTimer(timeoutMilliseconds, onTimer); } void DisplayWindow::StopTimer(void* timerID) { - Win32Window::StopTimer(timerID); -} - -#elif defined(__APPLE__) - -std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost) -{ - 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) +std::unique_ptr DisplayBackend::TryCreateWin32() { - return std::make_unique(windowHost); + 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..5797b0b4f --- /dev/null +++ b/libraries/ZWidget/src/window/x11/x11_display_window.cpp @@ -0,0 +1,1184 @@ + +#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() +{ + ShowCursor(false); +} + +void X11DisplayWindow::UnlockCursor() +{ + ShowCursor(true); +} + +void X11DisplayWindow::CaptureMouse() +{ + ShowCursor(false); +} + +void X11DisplayWindow::ReleaseMouseCapture() +{ + ShowCursor(true); +} + +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) +{ + ClientSizeX = event->xconfigure.width; + ClientSizeY = event->xconfigure.height; + 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::Down; + 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: case XK_a: return InputKey::A; + case XK_B: case XK_b: return InputKey::B; + case XK_C: case XK_c: return InputKey::C; + case XK_D: case XK_d: return InputKey::D; + case XK_E: case XK_e: return InputKey::E; + case XK_F: case XK_f: return InputKey::F; + case XK_G: case XK_g: return InputKey::G; + case XK_H: case XK_h: return InputKey::H; + case XK_I: case XK_i: return InputKey::I; + case XK_J: case XK_j: return InputKey::J; + case XK_K: case XK_k: return InputKey::K; + case XK_L: case XK_l: return InputKey::L; + case XK_M: case XK_m: return InputKey::M; + case XK_N: case XK_n: return InputKey::N; + case XK_O: case XK_o: return InputKey::O; + case XK_P: case XK_p: return InputKey::P; + case XK_Q: case XK_q: return InputKey::Q; + case XK_R: case XK_r: return InputKey::R; + case XK_S: case XK_s: return InputKey::S; + case XK_T: case XK_t: return InputKey::T; + case XK_U: case XK_u: return InputKey::U; + case XK_V: case XK_v: return InputKey::V; + case XK_W: case XK_w: return InputKey::W; + case XK_X: case XK_x: return InputKey::X; + case XK_Y: case XK_y: return InputKey::Y; + case XK_Z: 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; + if (isCursorEnabled) + { + windowHost->OnWindowMouseMove(Point(x / dpiScale, y / dpiScale)); + } + else + { + MouseX = ClientSizeX / 2; + MouseY = ClientSizeY / 2; + + if (MouseX != -1 && MouseY != -1) + { + windowHost->OnWindowRawMouseMove(x - MouseX, y - MouseY); + } + + // Warp pointer to the center of the window + XWarpPointer(display, window, window, 0, 0, ClientSizeX, ClientSizeY, ClientSizeX / 2, ClientSizeY / 2); + } + +} + +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..62a63f151 --- /dev/null +++ b/libraries/ZWidget/src/window/x11/x11_display_window.h @@ -0,0 +1,142 @@ +#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; + + int ClientSizeX = 0; + int ClientSizeY = 0; + int MouseX = -1; + int MouseY = -1; + + 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/common/widgets/errorwindow.cpp b/src/common/widgets/errorwindow.cpp index e0af0730b..d74864bb6 100644 --- a/src/common/widgets/errorwindow.cpp +++ b/src/common/widgets/errorwindow.cpp @@ -6,14 +6,16 @@ #include #include #include +#include +#include -bool ErrorWindow::ExecModal(const std::string& text, const std::string& log) +bool ErrorWindow::ExecModal(const std::string& text, const std::string& log, std::vector minidump) { Size screenSize = GetScreenSize(); double windowWidth = 1200.0; double windowHeight = 700.0; - auto window = std::make_unique(); + auto window = std::make_unique(std::move(minidump)); window->SetText(text, log); window->SetFrameGeometry((screenSize.width - windowWidth) * 0.5, (screenSize.height - windowHeight) * 0.5, windowWidth, windowHeight); window->Show(); @@ -23,7 +25,7 @@ bool ErrorWindow::ExecModal(const std::string& text, const std::string& log) return window->Restart; } -ErrorWindow::ErrorWindow() : Widget(nullptr, WidgetType::Window) +ErrorWindow::ErrorWindow(std::vector initminidump) : Widget(nullptr, WidgetType::Window), minidump(std::move(initminidump)) { FStringf caption("Fatal Error - " GAMENAME " %s (%s)", GetVersionString(), GetGitTime()); SetWindowTitle(caption.GetChars()); @@ -34,13 +36,21 @@ ErrorWindow::ErrorWindow() : Widget(nullptr, WidgetType::Window) LogView = new LogViewer(this); ClipboardButton = new PushButton(this); - RestartButton = new PushButton(this); - ClipboardButton->OnClick = [=]() { OnClipboardButtonClicked(); }; - RestartButton->OnClick = [=]() { OnRestartButtonClicked(); }; - ClipboardButton->SetText("Copy to clipboard"); - RestartButton->SetText("Restart"); + + if (minidump.empty()) + { + RestartButton = new PushButton(this); + RestartButton->OnClick = [=]() { OnRestartButtonClicked(); }; + RestartButton->SetText("Restart"); + } + else + { + SaveReportButton = new PushButton(this); + SaveReportButton->OnClick = [=]() { OnSaveReportButtonClicked(); }; + SaveReportButton->SetText("Save Report"); + } LogView->SetFocus(); } @@ -83,6 +93,37 @@ void ErrorWindow::OnRestartButtonClicked() DisplayWindow::ExitLoop(); } +void ErrorWindow::OnSaveReportButtonClicked() +{ + auto dialog = SaveFileDialog::Create(this); + dialog->AddFilter("Crash Report Zip Files", "*.zip"); + dialog->AddFilter("All Files", "*.*"); + dialog->SetFilename("CrashReport.zip"); + dialog->SetDefaultExtension("zip"); + if (dialog->Show()) + { + std::string filename = dialog->Filename(); + + mz_zip_archive zip = {}; + if (mz_zip_writer_init_heap(&zip, 0, 16 * 1024 * 1024)) + { + mz_zip_writer_add_mem(&zip, "minidump.dmp", minidump.data(), minidump.size(), MZ_DEFAULT_COMPRESSION); + mz_zip_writer_add_mem(&zip, "log.txt", clipboardtext.data(), clipboardtext.size(), MZ_DEFAULT_COMPRESSION); + } + void* buffer = nullptr; + size_t buffersize = 0; + mz_zip_writer_finalize_heap_archive(&zip, &buffer, &buffersize); + mz_zip_writer_end(&zip); + + std::unique_ptr f(FileWriter::Open(filename.c_str())); + if (f) + { + f->Write(buffer, buffersize); + f->Close(); + } + } +} + void ErrorWindow::OnClose() { Restart = false; @@ -96,7 +137,10 @@ void ErrorWindow::OnGeometryChanged() double y = GetHeight() - 15.0 - ClipboardButton->GetPreferredHeight(); ClipboardButton->SetFrameGeometry(20.0, y, 170.0, ClipboardButton->GetPreferredHeight()); - RestartButton->SetFrameGeometry(GetWidth() - 20.0 - 100.0, y, 100.0, RestartButton->GetPreferredHeight()); + if (RestartButton) + RestartButton->SetFrameGeometry(GetWidth() - 20.0 - 100.0, y, 100.0, RestartButton->GetPreferredHeight()); + else if (SaveReportButton) + SaveReportButton->SetFrameGeometry(GetWidth() - 20.0 - 100.0, y, 100.0, SaveReportButton->GetPreferredHeight()); y -= 20.0; LogView->SetFrameGeometry(Rect::xywh(0.0, 0.0, w, y)); @@ -212,44 +256,44 @@ void LogViewer::OnPaint(Canvas* canvas) } } -bool LogViewer::OnMouseWheel(const Point& pos, EInputKey key) +bool LogViewer::OnMouseWheel(const Point& pos, InputKey key) { - if (key == IK_MouseWheelUp) + if (key == InputKey::MouseWheelUp) { ScrollUp(4); } - else if (key == IK_MouseWheelDown) + else if (key == InputKey::MouseWheelDown) { ScrollDown(4); } return true; } -void LogViewer::OnKeyDown(EInputKey key) +void LogViewer::OnKeyDown(InputKey key) { - if (key == IK_Home) + if (key == InputKey::Home) { scrollbar->SetPosition(0.0f); Update(); } - if (key == IK_End) + if (key == InputKey::End) { scrollbar->SetPosition(scrollbar->GetMax()); Update(); } - else if (key == IK_PageUp) + else if (key == InputKey::PageUp) { ScrollUp(20); } - else if (key == IK_PageDown) + else if (key == InputKey::PageDown) { ScrollDown(20); } - else if (key == IK_Up) + else if (key == InputKey::Up) { ScrollUp(4); } - else if (key == IK_Down) + else if (key == InputKey::Down) { ScrollDown(4); } diff --git a/src/common/widgets/errorwindow.h b/src/common/widgets/errorwindow.h index f38eb676c..1cb9b236d 100644 --- a/src/common/widgets/errorwindow.h +++ b/src/common/widgets/errorwindow.h @@ -10,9 +10,9 @@ class Scrollbar; class ErrorWindow : public Widget { public: - static bool ExecModal(const std::string& text, const std::string& log); + static bool ExecModal(const std::string& text, const std::string& log, std::vector minidump = {}); - ErrorWindow(); + ErrorWindow(std::vector minidump); bool Restart = false; @@ -25,11 +25,14 @@ private: void OnClipboardButtonClicked(); void OnRestartButtonClicked(); + void OnSaveReportButtonClicked(); LogViewer* LogView = nullptr; PushButton* ClipboardButton = nullptr; PushButton* RestartButton = nullptr; + PushButton* SaveReportButton = nullptr; + std::vector minidump; std::string clipboardtext; }; @@ -43,8 +46,8 @@ public: protected: void OnPaintFrame(Canvas* canvas) override; void OnPaint(Canvas* canvas) override; - bool OnMouseWheel(const Point& pos, EInputKey key) override; - void OnKeyDown(EInputKey key) override; + bool OnMouseWheel(const Point& pos, InputKey key) override; + void OnKeyDown(InputKey key) override; void OnGeometryChanged() override; private: diff --git a/src/common/widgets/widgetresourcedata.cpp b/src/common/widgets/widgetresourcedata.cpp index 13f151932..849b6d553 100644 --- a/src/common/widgets/widgetresourcedata.cpp +++ b/src/common/widgets/widgetresourcedata.cpp @@ -1,5 +1,6 @@ #include +#include #include "filesystem.h" #include "printf.h" #include "zstring.h" @@ -11,21 +12,31 @@ FResourceFile* WidgetResources; +bool IsZWidgetAvailable() +{ + return WidgetResources; +} + void InitWidgetResources(const char* filename) { WidgetResources = FResourceFile::OpenResourceFile(filename); if (!WidgetResources) I_FatalError("Unable to open %s", filename); + + WidgetTheme::SetTheme(std::make_unique()); } void CloseWidgetResources() { - if (WidgetResources) delete WidgetResources; + delete WidgetResources; WidgetResources = nullptr; } static std::vector LoadFile(const char* name) { + if (!WidgetResources) + I_FatalError("InitWidgetResources has not been called"); + auto lump = WidgetResources->FindEntry(name); if (lump == -1) I_FatalError("Unable to find %s", name); diff --git a/src/d_main.cpp b/src/d_main.cpp index 28e96e5a6..7ebd1c40c 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -118,6 +118,7 @@ #include "screenjob.h" #include "startscreen.h" #include "shiftstate.h" +#include "common/widgets/errorwindow.h" #ifdef __unix__ #include "i_system.h" // for SHARE_DIR @@ -3750,6 +3751,15 @@ static int D_DoomMain_Internal (void) 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(); diff --git a/src/launcher/launcherwindow.cpp b/src/launcher/launcherwindow.cpp index 11f1079d4..c4b84df82 100644 --- a/src/launcher/launcherwindow.cpp +++ b/src/launcher/launcherwindow.cpp @@ -7,6 +7,7 @@ #include "version.h" #include "i_interface.h" #include "gstrings.h" +#include "c_cvars.h" #include #include #include @@ -31,10 +32,6 @@ int LauncherWindow::ExecModal(WadStuff* wads, int numwads, int defaultiwad, int* LauncherWindow::LauncherWindow(WadStuff* wads, int numwads, int defaultiwad, int* autoloadflags) : Widget(nullptr, WidgetType::Window) { - SetWindowBackground(Colorf::fromRgba8(51, 51, 51)); - SetWindowBorderColor(Colorf::fromRgba8(51, 51, 51)); - SetWindowCaptionColor(Colorf::fromRgba8(33, 33, 33)); - SetWindowCaptionTextColor(Colorf::fromRgba8(226, 223, 219)); SetWindowTitle(GAMENAME); Banner = new LauncherBanner(this); diff --git a/src/launcher/playgamepage.cpp b/src/launcher/playgamepage.cpp index d561a672b..84ac64ad1 100644 --- a/src/launcher/playgamepage.cpp +++ b/src/launcher/playgamepage.cpp @@ -40,16 +40,16 @@ PlayGamePage::PlayGamePage(LauncherWindow* launcher, WadStuff* wads, int numwads GamesList->OnActivated = [=]() { OnGamesListActivated(); }; } -std::string PlayGamePage::GetExtraArgs() -{ - return ParametersEdit->GetText(); -} - void PlayGamePage::SetExtraArgs(const std::string& args) { ParametersEdit->SetText(args); } +std::string PlayGamePage::GetExtraArgs() +{ + return ParametersEdit->GetText(); +} + int PlayGamePage::GetSelectedGame() { return GamesList->GetSelectedItem(); diff --git a/src/launcher/playgamepage.h b/src/launcher/playgamepage.h index d54ba2ca1..79e92e67b 100644 --- a/src/launcher/playgamepage.h +++ b/src/launcher/playgamepage.h @@ -16,6 +16,7 @@ public: void SetExtraArgs(const std::string& args); std::string GetExtraArgs(); + int GetSelectedGame(); private: diff --git a/src/launcher/settingspage.h b/src/launcher/settingspage.h index a65f4f164..77a6ff048 100644 --- a/src/launcher/settingspage.h +++ b/src/launcher/settingspage.h @@ -3,7 +3,7 @@ #include #include "gstrings.h" -#define RENDER_BACKENDS +// #define RENDER_BACKENDS class LauncherWindow; class TextLabel;