Update to latest ZWidget

This commit is contained in:
dpjudas 2025-02-27 02:54:04 +01:00
commit 92b08a5c18
78 changed files with 7123 additions and 1261 deletions

View file

@ -1,6 +1,22 @@
cmake_minimum_required(VERSION 3.11)
project(zwidget)
if (UNIX AND NOT APPLE)
include(FindPkgConfig)
pkg_check_modules(DBUS dbus-1)
if (!DBUS_FOUND)
pkg_check_modules(DBUS REQUIRED dbus) # Fedora Linux looks for dbus instead
endif()
pkg_check_modules(WAYLANDPP
wayland-client
wayland-client++
wayland-client-extra++
wayland-client-unstable++
wayland-cursor++
xkbcommon>=0.5.0
)
endif()
set(ZWIDGET_SOURCES
src/core/canvas.cpp
src/core/font.cpp
@ -33,7 +49,15 @@ set(ZWIDGET_SOURCES
src/widgets/listview/listview.cpp
src/widgets/tabwidget/tabwidget.cpp
src/window/window.cpp
src/systemdialogs/folder_browse_dialog.cpp
src/window/stub/stub_open_folder_dialog.cpp
src/window/stub/stub_open_folder_dialog.h
src/window/stub/stub_open_file_dialog.cpp
src/window/stub/stub_open_file_dialog.h
src/window/stub/stub_save_file_dialog.cpp
src/window/stub/stub_save_file_dialog.h
src/window/ztimer/ztimer.h
src/window/ztimer/ztimer.cpp
src/systemdialogs/open_folder_dialog.cpp
src/systemdialogs/open_file_dialog.cpp
src/systemdialogs/save_file_dialog.cpp
)
@ -66,22 +90,62 @@ set(ZWIDGET_INCLUDES
include/zwidget/widgets/listview/listview.h
include/zwidget/widgets/tabwidget/tabwidget.h
include/zwidget/window/window.h
include/zwidget/systemdialogs/folder_browse_dialog.h
include/zwidget/window/x11nativehandle.h
include/zwidget/window/waylandnativehandle.h
include/zwidget/window/win32nativehandle.h
include/zwidget/window/sdl2nativehandle.h
include/zwidget/systemdialogs/open_folder_dialog.h
include/zwidget/systemdialogs/open_file_dialog.h
include/zwidget/systemdialogs/save_file_dialog.h
)
set(ZWIDGET_WIN32_SOURCES
src/window/win32/win32displaywindow.cpp
src/window/win32/win32displaywindow.h
src/window/win32/win32_display_backend.cpp
src/window/win32/win32_display_backend.h
src/window/win32/win32_display_window.cpp
src/window/win32/win32_display_window.h
src/window/win32/win32_open_folder_dialog.cpp
src/window/win32/win32_open_folder_dialog.h
src/window/win32/win32_open_file_dialog.cpp
src/window/win32/win32_open_file_dialog.h
src/window/win32/win32_save_file_dialog.cpp
src/window/win32/win32_save_file_dialog.h
src/window/win32/win32_util.h
)
set(ZWIDGET_DBUS_SOURCES
src/window/dbus/dbus_open_folder_dialog.cpp
src/window/dbus/dbus_open_folder_dialog.h
src/window/dbus/dbus_open_file_dialog.cpp
src/window/dbus/dbus_open_file_dialog.h
src/window/dbus/dbus_save_file_dialog.cpp
src/window/dbus/dbus_save_file_dialog.h
)
set(ZWIDGET_COCOA_SOURCES
)
set(ZWIDGET_SDL2_SOURCES
src/window/sdl2/sdl2displaywindow.cpp
src/window/sdl2/sdl2displaywindow.h
src/window/sdl2/sdl2_display_backend.cpp
src/window/sdl2/sdl2_display_backend.h
src/window/sdl2/sdl2_display_window.cpp
src/window/sdl2/sdl2_display_window.h
)
set(ZWIDGET_X11_SOURCES
src/window/x11/x11_display_backend.cpp
src/window/x11/x11_display_backend.h
src/window/x11/x11_display_window.cpp
src/window/x11/x11_display_window.h
)
set(ZWIDGET_WAYLAND_SOURCES
src/window/wayland/wayland_display_backend.cpp
src/window/wayland/wayland_display_backend.h
src/window/wayland/wayland_display_window.cpp
src/window/wayland/wayland_display_window.h
src/window/wayland/wl_fractional_scaling_protocol.cpp
src/window/wayland/wl_fractional_scaling_protocol.hpp
)
source_group("src" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/.+")
@ -103,6 +167,12 @@ source_group("src\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_S
source_group("src\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/listview/.+")
source_group("src\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/tabwidget/.+")
source_group("src\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/.+")
source_group("src\\window\\stub" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/stub/.+")
source_group("src\\window\\win32" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/win32/.+")
source_group("src\\window\\sdl2" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/sdl2/.+")
source_group("src\\window\\x11" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/x11/.+")
source_group("src\\window\\wayland" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/wayland/.+")
source_group("src\\window\\dbus" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/dbus/.+")
source_group("src\\systemdialogs" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/systemdialogs/.+")
source_group("include" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/.+")
source_group("include\\core" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/core/.+")
@ -121,39 +191,62 @@ source_group("include\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRE
source_group("include\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/listview/.+")
source_group("include\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/tabwidget/.+")
source_group("include\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/.+")
source_group("include\\window\\win32" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/win32/.+")
source_group("include\\window\\sdl2" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/sdl2/.+")
source_group("include\\systemdialogs" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/systemdialogs/.+")
include_directories(include include/zwidget src)
# Include directory to external projects using zwidget
include_directories(include)
# Internal include dirs for building zwidget
set(ZWIDGET_INCLUDE_DIRS include/zwidget src)
set(ZWIDGET_COMPILE_OPTIONS)
if(WIN32)
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_WIN32_SOURCES})
add_definitions(-DUNICODE -D_UNICODE)
if(MINGW)
add_definitions(-DMINGW)
set(ZWIDGET_DEFINES -DUNICODE -D_UNICODE)
set(ZWIDGET_LINK_OPTIONS)
if(MSVC)
# Use all cores for compilation
set(ZWIDGET_COMPILE_OPTIONS ${ZWIDGET_COMPILE_OPTIONS} /MP)
# Ignore specific warnings
#set(ZWIDGET_COMPILE_OPTIONS ${ZWIDGET_COMPILE_OPTIONS} /wd4244 /wd4267 /wd4005 /wd4018)
# Don't slow down std containers in debug builds
set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES})
# Ignore warning about legacy CRT functions
#set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES} -D_CRT_SECURE_NO_WARNINGS)
endif()
elseif(APPLE)
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_COCOA_SOURCES})
set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl)
add_definitions(-DUNIX -D_UNIX)
add_link_options(-pthread)
set(ZWIDGET_DEFINES -DUNIX -D_UNIX)
set(ZWIDGET_LINK_OPTIONS -pthread)
else()
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_SDL2_SOURCES})
set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl)
add_definitions(-DUNIX -D_UNIX)
add_link_options(-pthread)
endif()
if(MSVC)
# Use all cores for compilation
set(CMAKE_CXX_FLAGS "/MP ${CMAKE_CXX_FLAGS}")
# Ignore warnings in third party code
#set_source_files_properties(${ZWIDGET_SOURCES} PROPERTIES COMPILE_FLAGS "/wd4244 /wd4267 /wd4005 /wd4018 -D_CRT_SECURE_NO_WARNINGS")
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_SDL2_SOURCES} ${ZWIDGET_X11_SOURCES})
set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl -lX11)
set(ZWIDGET_DEFINES -DUNIX -D_UNIX -DUSE_SDL2 -DUSE_X11)
set(ZWIDGET_LINK_OPTIONS -pthread)
if (DBUS_FOUND)
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_DBUS_SOURCES})
set(ZWIDGET_INCLUDE_DIRS ${ZWIDGET_INCLUDE_DIRS} ${DBUS_INCLUDE_DIRS})
set(ZWIDGET_LIBS ${ZWIDGET_LIBS} ${DBUS_LDFLAGS})
set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES} -DUSE_DBUS)
endif()
if (WAYLANDPP_FOUND)
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_WAYLAND_SOURCES})
set(ZWIDGET_INCLUDE_DIRS ${ZWIDGET_INCLUDE_DIRS} ${WAYLANDPP_INCLUDE_DIRS})
set(ZWIDGET_LIBS ${ZWIDGET_LIBS} ${WAYLANDPP_LDFLAGS})
set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES} -DUSE_WAYLAND)
endif()
endif()
add_library(zwidget STATIC ${ZWIDGET_SOURCES} ${ZWIDGET_INCLUDES})
target_compile_options(zwidget PRIVATE ${ZWIDGET_COMPILE_OPTIONS})
target_compile_definitions(zwidget PRIVATE ${ZWIDGET_DEFINES})
target_include_directories(zwidget PRIVATE ${ZWIDGET_INCLUDE_DIRS})
target_link_options(zwidget PRIVATE ${ZWIDGET_LINK_OPTIONS})
target_link_libraries(zwidget ${ZWIDGET_LIBS})
set_target_properties(zwidget PROPERTIES CXX_STANDARD 17)

View file

@ -2,14 +2,26 @@
#include <memory>
#include <string>
#include <unordered_map>
#include "image.h"
#include "rect.h"
#include <vector>
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<Canvas> 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>& font, const Point& pos, const std::string& text, const Colorf& color) = 0;
virtual void drawTextEllipsis(const std::shared_ptr<Font>& font, const Point& pos, const Rect& clipBox, const std::string& text, const Colorf& color) = 0;
virtual Rect measureText(const std::shared_ptr<Font>& font, const std::string& text) = 0;
virtual FontMetrics getFontMetrics(const std::shared_ptr<Font>& font) = 0;
virtual int getCharacterIndex(const std::shared_ptr<Font>& font, const std::string& text, const Point& hitPoint) = 0;
virtual void drawImage(const std::shared_ptr<Image>& 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<Canvas> 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>& font, const Point& pos, const std::string& text, const Colorf& color);
void drawTextEllipsis(const std::shared_ptr<Font>& font, const Point& pos, const Rect& clipBox, const std::string& text, const Colorf& color);
Rect measureText(const std::shared_ptr<Font>& font, const std::string& text);
FontMetrics getFontMetrics(const std::shared_ptr<Font>& font);
int getCharacterIndex(const std::shared_ptr<Font>& font, const std::string& text, const Point& hitPoint);
void drawImage(const std::shared_ptr<Image>& image, const Point& pos);
void drawImage(const std::shared_ptr<Image>& image, const Rect& box);
protected:
virtual std::unique_ptr<CanvasTexture> 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<typename T>
static T clamp(T val, T minval, T maxval) { return std::max<T>(std::min<T>(val, maxval), minval); }
DisplayWindow* window = nullptr;
int width = 0;
int height = 0;
double uiscale = 1.0f;
std::unique_ptr<CanvasTexture> whiteTexture;
private:
void setLanguage(const char* lang) { language = lang; }
void drawLineUnclipped(const Point& p0, const Point& p1, const Colorf& color);
std::unique_ptr<CanvasFontGroup> font;
Point origin;
std::vector<Rect> clipStack;
std::unordered_map<std::shared_ptr<Image>, std::unique_ptr<CanvasTexture>> imageTextures;
std::string language;
friend class CanvasFont;
};

View file

@ -1,5 +1,7 @@
#pragma once
#include <algorithm>
class Point
{
public:

View file

@ -22,7 +22,7 @@ enum class WidgetType
class Widget : DisplayWindowHost
{
public:
Widget(Widget* parent = nullptr, WidgetType type = WidgetType::Child);
Widget(Widget* parent = nullptr, WidgetType type = WidgetType::Child, RenderAPI api = RenderAPI::Unspecified);
virtual ~Widget();
void SetParent(Widget* parent);
@ -104,6 +104,7 @@ public:
bool IsEnabled();
bool IsVisible();
bool IsHidden();
bool IsFullscreen();
void SetFocus();
void SetEnabled(bool value);
@ -113,8 +114,12 @@ public:
void LockCursor();
void UnlockCursor();
void SetCursor(StandardCursor cursor);
void CaptureMouse();
void ReleaseMouseCapture();
void SetPointerCapture();
void ReleasePointerCapture();
void SetModalCapture();
void ReleaseModalCapture();
bool GetKeyState(InputKey key);
@ -126,6 +131,9 @@ public:
Widget* ChildAt(double x, double y) { return ChildAt(Point(x, y)); }
Widget* ChildAt(const Point& pos);
bool IsParent(const Widget* w) const;
bool IsChild(const Widget* w) const;
Widget* Parent() const { return ParentObj; }
Widget* PrevSibling() const { return PrevSiblingObj; }
Widget* NextSibling() const { return NextSiblingObj; }
@ -142,7 +150,14 @@ public:
static Size GetScreenSize();
void SetCanvas(std::unique_ptr<Canvas> canvas);
void* GetNativeHandle();
int GetNativePixelWidth();
int GetNativePixelHeight();
// Vulkan support:
std::vector<std::string> GetVulkanInstanceExtensions() { return Window()->DispWindow->GetVulkanInstanceExtensions(); }
VkSurfaceKHR CreateVulkanSurface(VkInstance instance) { return Window()->DispWindow->CreateVulkanSurface(instance); }
protected:
virtual void OnPaintFrame(Canvas* canvas);
@ -220,4 +235,7 @@ private:
Widget& operator=(const Widget&) = delete;
friend class Timer;
friend class OpenFileDialog;
friend class OpenFolderDialog;
friend class SaveFileDialog;
};

View file

@ -7,13 +7,13 @@
class Widget;
/// \brief Displays the system folder browsing dialog
class BrowseFolderDialog
class OpenFolderDialog
{
public:
/// \brief Constructs an browse folder dialog.
static std::unique_ptr<BrowseFolderDialog> Create(Widget*owner);
static std::unique_ptr<OpenFolderDialog> Create(Widget*owner);
virtual ~BrowseFolderDialog() = default;
virtual ~OpenFolderDialog() = default;
/// \brief Get the full path of the directory selected.
virtual std::string SelectedPath() const = 0;

View file

@ -4,12 +4,20 @@
#include "../../core/widget.h"
#include "../../core/image.h"
enum ImageBoxMode
{
Center,
Contain,
Cover
};
class ImageBox : public Widget
{
public:
ImageBox(Widget* parent);
void SetImage(std::shared_ptr<Image> newImage);
void SetImageMode(ImageBoxMode mode);
double GetPreferredWidth() const;
double GetPreferredHeight() const;
@ -19,4 +27,5 @@ protected:
private:
std::shared_ptr<Image> image;
ImageBoxMode mode = ImageBoxMode::Center;
};

View file

@ -13,6 +13,7 @@ public:
ListView(Widget* parent = nullptr);
void AddItem(const std::string& text);
void RemoveItem(int index);
int GetSelectedItem() const { return selectedItem; }
void SetSelectedItem(int index);
void ScrollToItem(int index);

View file

@ -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;
};

View file

@ -21,13 +21,22 @@ public:
protected:
void OnGeometryChanged() override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnMouseMove(const Point& pos) override;
void OnKeyDown(InputKey key) override;
private:
void ShowMenu(MenubarItem* item);
void CloseMenu();
MenubarItem* GetMenubarItemAt(const Point& pos);
int GetItemIndex(MenubarItem* item);
std::vector<MenubarItem*> menuItems;
int currentMenubarItem = -1;
Menu* openMenu = nullptr;
bool modalMode = false;
friend class MenubarItem;
};
@ -70,10 +79,13 @@ public:
double GetPreferredWidth() const;
double GetPreferredHeight() const;
void SetSelected(MenuItem* item);
protected:
void OnGeometryChanged() override;
std::function<void()> onCloseMenu;
MenuItem* selectedItem = nullptr;
friend class Menubar;
};
@ -81,19 +93,29 @@ protected:
class MenuItem : public Widget
{
public:
MenuItem(Widget* parent);
MenuItem(Menu* menu, std::function<void()> 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<void()> onClick;
};
class MenuItemSeparator : public Widget
{
public:
MenuItemSeparator(Widget* parent);
protected:
void OnPaint(Canvas* canvas) override;
};

View file

@ -3,7 +3,7 @@
#include "../../core/widget.h"
enum TextLabelAlignment
enum class TextLabelAlignment
{
Left,
Center,

View file

@ -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<void()> onClicked = {});
protected:
void OnGeometryChanged() override;
private:
ToolbarDirection direction = ToolbarDirection::Horizontal;
std::vector<ToolbarButton*> buttons;
};

View file

@ -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<void()> 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;
};

View file

@ -0,0 +1,12 @@
#pragma once
#include <string>
#include <vector>
struct SDL_Window;
class SDL2NativeHandle
{
public:
SDL_Window* window = nullptr;
};

View file

@ -0,0 +1,10 @@
#pragma once
#include <wayland-client-protocol.h>
class WaylandNativeHandle
{
public:
wl_display* display = nullptr;
wl_surface* surface = nullptr;
};

View file

@ -0,0 +1,9 @@
#pragma once
typedef struct HWND__* HWND;
class Win32NativeHandle
{
public:
HWND hwnd = {};
};

View file

@ -4,9 +4,28 @@
#include <string>
#include <functional>
#include <cstdint>
#include <cstdlib>
#include "../core/rect.h"
class Engine;
#ifndef VULKAN_H_
#define VK_DEFINE_HANDLE(object) typedef struct object##_T* object;
#if defined(__LP64__) || defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)
#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object;
#else
#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;
#endif
VK_DEFINE_HANDLE(VkInstance)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSurfaceKHR)
#endif
class Widget;
class OpenFileDialog;
class SaveFileDialog;
class OpenFolderDialog;
enum class StandardCursor
{
@ -93,6 +112,17 @@ enum class InputKey : uint32_t
NoName, PA1, OEMClear
};
enum class RenderAPI
{
Unspecified,
Bitmap,
Vulkan,
OpenGL,
D3D11,
D3D12,
Metal
};
class DisplayWindow;
class DisplayWindowHost
@ -119,7 +149,7 @@ public:
class DisplayWindow
{
public:
static std::unique_ptr<DisplayWindow> Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner);
static std::unique_ptr<DisplayWindow> Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI);
static void ProcessEvents();
static void RunLoop();
@ -140,6 +170,7 @@ public:
virtual void ShowMaximized() = 0;
virtual void ShowMinimized() = 0;
virtual void ShowNormal() = 0;
virtual bool IsWindowFullscreen() = 0;
virtual void Hide() = 0;
virtual void Activate() = 0;
virtual void ShowCursor(bool enable) = 0;
@ -171,4 +202,42 @@ public:
virtual void SetClipboardText(const std::string& text) = 0;
virtual void* GetNativeHandle() = 0;
virtual std::vector<std::string> GetVulkanInstanceExtensions() = 0;
virtual VkSurfaceKHR CreateVulkanSurface(VkInstance instance) = 0;
};
class DisplayBackend
{
public:
static DisplayBackend* Get();
static void Set(std::unique_ptr<DisplayBackend> instance);
static std::unique_ptr<DisplayBackend> TryCreateWin32();
static std::unique_ptr<DisplayBackend> TryCreateSDL2();
static std::unique_ptr<DisplayBackend> TryCreateX11();
static std::unique_ptr<DisplayBackend> TryCreateWayland();
static std::unique_ptr<DisplayBackend> 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<DisplayWindow> 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<void()> onTimer) = 0;
virtual void StopTimer(void* timerID) = 0;
virtual Size GetScreenSize() = 0;
virtual std::unique_ptr<OpenFileDialog> CreateOpenFileDialog(DisplayWindow* owner);
virtual std::unique_ptr<SaveFileDialog> CreateSaveFileDialog(DisplayWindow* owner);
virtual std::unique_ptr<OpenFolderDialog> CreateOpenFolderDialog(DisplayWindow* owner);
};

View file

@ -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;
};

View file

@ -18,13 +18,7 @@
#define USE_SSE2
#endif
class CanvasTexture
{
public:
int Width = 0;
int Height = 0;
std::vector<uint32_t> Data;
};
////////////////////////////////////////////////////////////////////////////
class CanvasGlyph
{
@ -46,78 +40,12 @@ public:
class CanvasFont
{
public:
CanvasFont(const std::string& fontname, double height, std::vector<uint8_t> data) : fontname(fontname), height(height)
{
auto tdata = std::make_shared<TrueTypeFontFileData>(std::move(data));
ttf = std::make_unique<TrueTypeFont>(tdata);
textmetrics = ttf->GetTextMetrics(height);
}
CanvasFont(const std::string& fontname, double height, std::vector<uint8_t> 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<CanvasGlyph>();
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<CanvasTexture>();
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<TrueTypeFont> ttf;
std::string fontname;
@ -125,6 +53,8 @@ public:
TrueTypeTextMetrics textmetrics;
std::unordered_map<uint32_t, std::unique_ptr<CanvasGlyph>> glyphs;
friend class CanvasFontGroup;
};
class CanvasFontGroup
@ -135,144 +65,164 @@ public:
std::unique_ptr<CanvasFont> 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<CanvasFont>(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<SingleFont> fonts;
};
class BitmapCanvas : public Canvas
////////////////////////////////////////////////////////////////////////////
CanvasFont::CanvasFont(const std::string& fontname, double height, std::vector<uint8_t> data) : fontname(fontname), height(height)
{
public:
BitmapCanvas(DisplayWindow* window);
~BitmapCanvas();
auto tdata = std::make_shared<TrueTypeFontFileData>(std::move(data));
ttf = std::make_unique<TrueTypeFont>(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<CanvasGlyph>();
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<CanvasTexture>();
std::vector<uint32_t> data(destwidth * h);
void drawText(const std::shared_ptr<Font>& font, const Point& pos, const std::string& text, const Colorf& color) override { drawText(pos, color, text); }
void drawTextEllipsis(const std::shared_ptr<Font>& 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>& font, const std::string& text) override { return measureText(text); }
FontMetrics getFontMetrics(const std::shared_ptr<Font>& 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>& font, const std::string& text, const Point& hitPoint) override { return 0; }
void drawImage(const std::shared_ptr<Image>& image, const Point& pos) override;
glyph->u = 0.0;
glyph->v = 0.0;
glyph->uvwidth = destwidth;
glyph->uvheight = h;
glyph->texture = canvas->createTexture(destwidth, h, data.data(), ImageFormat::B8G8R8A8);
void drawLineUnclipped(const Point& p0, const Point& p1, const Colorf& color);
glyph->metrics.advanceWidth = (ttfglyph.advanceWidth + 2) / 3;
glyph->metrics.leftSideBearing = (ttfglyph.leftSideBearing + 2) / 3;
glyph->metrics.yOffset = ttfglyph.yOffset;
void fillTile(float x, float y, float width, float height, Colorf color);
void drawTile(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color);
void drawGlyph(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color);
return glyph.get();
}
int getClipMinX() const;
int getClipMinY() const;
int getClipMaxX() const;
int getClipMaxY() const;
////////////////////////////////////////////////////////////////////////////
void setLanguage(const char* lang) { language = lang; }
std::unique_ptr<CanvasTexture> createTexture(int width, int height, const void* pixels, ImageFormat format = ImageFormat::B8G8R8A8);
template<typename T>
static T clamp(T val, T minval, T maxval) { return std::max<T>(std::min<T>(val, maxval), minval); }
DisplayWindow* window = nullptr;
std::unique_ptr<CanvasFontGroup> font;
std::unique_ptr<CanvasTexture> whiteTexture;
Point origin;
double uiscale = 1.0f;
std::vector<Rect> clipStack;
int width = 0;
int height = 0;
std::vector<uint32_t> pixels;
std::unordered_map<std::shared_ptr<Image>, std::unique_ptr<CanvasTexture>> 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<CanvasFont>(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<CanvasFontGroup>("NotoSans", 13.0 * uiscale);
}
BitmapCanvas::~BitmapCanvas()
void Canvas::detach()
{
}
Point BitmapCanvas::getOrigin()
void Canvas::begin(const Colorf& color)
{
uiscale = window->GetDpiScale();
width = window->GetPixelWidth();
height = window->GetPixelHeight();
}
Point Canvas::getOrigin()
{
return origin;
}
void BitmapCanvas::setOrigin(const Point& newOrigin)
void Canvas::setOrigin(const Point& newOrigin)
{
origin = newOrigin;
}
void BitmapCanvas::pushClip(const Rect& box)
void Canvas::pushClip(const Rect& box)
{
if (!clipStack.empty())
{
@ -299,17 +249,17 @@ void BitmapCanvas::pushClip(const Rect& box)
}
}
void BitmapCanvas::popClip()
void Canvas::popClip()
{
clipStack.pop_back();
}
void BitmapCanvas::fillRect(const Rect& box, const Colorf& color)
void Canvas::fillRect(const Rect& box, const Colorf& color)
{
fillTile((float)((origin.x + box.x) * uiscale), (float)((origin.y + box.y) * uiscale), (float)(box.width * uiscale), (float)(box.height * uiscale), color);
}
void BitmapCanvas::drawImage(const std::shared_ptr<Image>& image, const Point& pos)
void Canvas::drawImage(const std::shared_ptr<Image>& image, const Point& pos)
{
auto& texture = imageTextures[image];
if (!texture)
@ -320,7 +270,18 @@ void BitmapCanvas::drawImage(const std::shared_ptr<Image>& 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>& image, const Rect& box)
{
auto& texture = imageTextures[image];
if (!texture)
{
texture = createTexture(image->GetWidth(), image->GetHeight(), image->GetData(), image->GetFormat());
}
Colorf color(1.0f, 1.0f, 1.0f);
drawTile(texture.get(), (float)((origin.x + box.x) * uiscale), (float)((origin.y + box.y) * uiscale), (float)(box.width * uiscale), (float)(box.height * uiscale), 0.0, 0.0, (float)texture->Width, (float)texture->Height, color);
}
void Canvas::line(const Point& p0, const Point& p1, const Colorf& color)
{
double x0 = origin.x + p0.x;
double y0 = origin.y + p0.y;
@ -407,7 +368,7 @@ void BitmapCanvas::line(const Point& p0, const Point& p1, const Colorf& color)
}
}
void BitmapCanvas::drawText(const Point& pos, const Colorf& color, const std::string& text)
void Canvas::drawText(const Point& pos, const Colorf& color, const std::string& text)
{
double x = std::round((origin.x + pos.x) * uiscale);
double y = std::round((origin.y + pos.y) * uiscale);
@ -415,10 +376,10 @@ void BitmapCanvas::drawText(const Point& pos, const Colorf& color, const std::st
UTF8Reader reader(text.data(), text.size());
while (!reader.is_end())
{
CanvasGlyph* glyph = font->getGlyph(reader.character(), language.c_str());
CanvasGlyph* glyph = font->getGlyph(this, reader.character(), language.c_str());
if (!glyph || !glyph->texture)
{
glyph = font->getGlyph(32);
glyph = font->getGlyph(this, 32);
}
if (glyph->texture)
@ -433,7 +394,22 @@ void BitmapCanvas::drawText(const Point& pos, const Colorf& color, const std::st
}
}
Rect BitmapCanvas::measureText(const std::string& text)
void Canvas::drawText(const std::shared_ptr<Font>& font, const Point& pos, const std::string& text, const Colorf& color)
{
drawText(pos, color, text);
}
void Canvas::drawTextEllipsis(const std::shared_ptr<Font>& 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>& font, const std::string& text)
{
return measureText(text);
}
Rect Canvas::measureText(const std::string& text)
{
double x = 0.0;
double y = font->GetTextMetrics().ascender - font->GetTextMetrics().descender;
@ -441,10 +417,10 @@ Rect BitmapCanvas::measureText(const std::string& text)
UTF8Reader reader(text.data(), text.size());
while (!reader.is_end())
{
CanvasGlyph* glyph = font->getGlyph(reader.character(), language.c_str());
CanvasGlyph* glyph = font->getGlyph(this, reader.character(), language.c_str());
if (!glyph || !glyph->texture)
{
glyph = font->getGlyph(32);
glyph = font->getGlyph(this, 32);
}
x += std::round(glyph->metrics.advanceWidth);
@ -454,7 +430,23 @@ Rect BitmapCanvas::measureText(const std::string& text)
return Rect::xywh(0.0, 0.0, x / uiscale, y / uiscale);
}
VerticalTextPosition BitmapCanvas::verticalTextAlign()
FontMetrics Canvas::getFontMetrics(const std::shared_ptr<Font>& 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>& font, const std::string& text, const Point& hitPoint)
{
return 0;
}
VerticalTextPosition Canvas::verticalTextAlign()
{
VerticalTextPosition align;
align.top = 0.0f;
@ -464,9 +456,70 @@ VerticalTextPosition BitmapCanvas::verticalTextAlign()
return align;
}
void Canvas::drawLineUnclipped(const Point& p0, const Point& p1, const Colorf& color)
{
if (p0.x == p1.x)
{
fillTile((float)((p0.x - 0.5) * uiscale), (float)(p0.y * uiscale), (float)uiscale, (float)((p1.y - p0.y) * uiscale), color);
}
else if (p0.y == p1.y)
{
fillTile((float)(p0.x * uiscale), (float)((p0.y - 0.5) * uiscale), (float)((p1.x - p0.x) * uiscale), (float)uiscale, color);
}
else
{
drawLineAntialiased((float)(p0.x * uiscale), (float)(p0.y * uiscale), (float)(p1.x * uiscale), (float)(p1.y * uiscale), color);
}
}
int Canvas::getClipMinX() const
{
return clipStack.empty() ? 0 : (int)std::round(std::max(clipStack.back().x * uiscale, 0.0));
}
int Canvas::getClipMinY() const
{
return clipStack.empty() ? 0 : (int)std::round(std::max(clipStack.back().y * uiscale, 0.0));
}
int Canvas::getClipMaxX() const
{
return clipStack.empty() ? width : (int)std::round(std::min((clipStack.back().x + clipStack.back().width) * uiscale, (double)width));
}
int Canvas::getClipMaxY() const
{
return clipStack.empty() ? height : (int)std::round(std::min((clipStack.back().y + clipStack.back().height) * uiscale, (double)height));
}
/////////////////////////////////////////////////////////////////////////////
class BitmapTexture : public CanvasTexture
{
public:
std::vector<uint32_t> 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<CanvasTexture> createTexture(int width, int height, const void* pixels, ImageFormat format = ImageFormat::B8G8R8A8) override;
std::vector<uint32_t> pixels;
};
std::unique_ptr<CanvasTexture> BitmapCanvas::createTexture(int width, int height, const void* pixels, ImageFormat format)
{
auto texture = std::make_unique<CanvasTexture>();
auto texture = std::make_unique<BitmapTexture>();
texture->Width = width;
texture->Height = height;
texture->Data.resize(width * height);
@ -491,40 +544,119 @@ std::unique_ptr<CanvasTexture> BitmapCanvas::createTexture(int width, int height
return texture;
}
void BitmapCanvas::drawLineUnclipped(const Point& p0, const Point& p1, const Colorf& color)
void BitmapCanvas::plot(float x, float y, float alpha, const Colorf& color)
{
if (p0.x == p1.x)
int xx = (int)x;
int yy = (int)y;
int dwidth = width;
int dheight = height;
uint32_t* dest = pixels.data() + xx + yy * dwidth;
uint32_t cred = (int32_t)clamp(color.r * 256.0f, 0.0f, 256.0f);
uint32_t cgreen = (int32_t)clamp(color.g * 256.0f, 0.0f, 256.0f);
uint32_t cblue = (int32_t)clamp(color.b * 256.0f, 0.0f, 256.0f);
uint32_t calpha = (int32_t)clamp(color.a * alpha * 256.0f, 0.0f, 256.0f);
uint32_t invalpha = 256 - calpha;
uint32_t dpixel = *dest;
uint32_t dalpha = dpixel >> 24;
uint32_t dred = (dpixel >> 16) & 0xff;
uint32_t dgreen = (dpixel >> 8) & 0xff;
uint32_t dblue = dpixel & 0xff;
// dest.rgba = color.rgba + dest.rgba * (1-color.a)
uint32_t a = (calpha * calpha + dalpha * invalpha + 127) >> 8;
uint32_t r = (cred * calpha + dred * invalpha + 127) >> 8;
uint32_t g = (cgreen * calpha + dgreen * invalpha + 127) >> 8;
uint32_t b = (cblue * calpha + dblue * invalpha + 127) >> 8;
*dest = (a << 24) | (r << 16) | (g << 8) | b;
}
static float fpart(float x)
{
return x - std::floor(x);
}
static float rfpart(float x)
{
return 1 - fpart(x);
}
void BitmapCanvas::drawLineAntialiased(float x0, float y0, float x1, float y1, Colorf color)
{
bool steep = std::abs(y1 - y0) > std::abs(x1 - x0);
if (steep)
{
fillTile((float)((p0.x - 0.5) * uiscale), (float)(p0.y * uiscale), (float)uiscale, (float)((p1.y - p0.y) * uiscale), color);
std::swap(x0, y0);
std::swap(x1, y1);
}
else if (p0.y == p1.y)
if (x0 > x1)
{
fillTile((float)(p0.x * uiscale), (float)((p0.y - 0.5) * uiscale), (float)((p1.x - p0.x) * uiscale), (float)uiscale, color);
std::swap(x0, x1);
std::swap(y0, y1);
}
float dx = x1 - x0;
float dy = y1 - y0;
float gradient = (dx == 0.0f) ? 1.0f : dy / dx;
// handle first endpoint
float xend = std::round(x0);
float yend = y0 + gradient * (xend - x0);
float xgap = rfpart(x0 + 0.5f);
float xpxl1 = xend; // this will be used in the main loop
float ypxl1 = std::floor(yend);
if (steep)
{
plot(ypxl1, xpxl1, rfpart(yend) * xgap, color);
plot(ypxl1 + 1, xpxl1, fpart(yend) * xgap, color);
}
else
{
// To do: draw line using bresenham
plot(xpxl1, ypxl1, rfpart(yend) * xgap, color);
plot(xpxl1, ypxl1 + 1, fpart(yend) * xgap, color);
}
}
float intery = yend + gradient; // first y-intersection for the main loop
int BitmapCanvas::getClipMinX() const
{
return clipStack.empty() ? 0 : (int)std::max(clipStack.back().x * uiscale, 0.0);
}
// handle second endpoint
xend = std::floor(x1 + 0.5f);
yend = y1 + gradient * (xend - x1);
xgap = fpart(x1 + 0.5f);
float xpxl2 = xend; // this will be used in the main loop
float ypxl2 = std::floor(yend);
if (steep)
{
plot(ypxl2, xpxl2, rfpart(yend) * xgap, color);
plot(ypxl2 + 1.0f, xpxl2, fpart(yend) * xgap, color);
}
else
{
plot(xpxl2, ypxl2, rfpart(yend) * xgap, color);
plot(xpxl2, ypxl2 + 1.0f, fpart(yend) * xgap, color);
}
int BitmapCanvas::getClipMinY() const
{
return clipStack.empty() ? 0 : (int)std::max(clipStack.back().y * uiscale, 0.0);
}
int BitmapCanvas::getClipMaxX() const
{
return clipStack.empty() ? width : (int)std::min((clipStack.back().x + clipStack.back().width) * uiscale, (double)width);
}
int BitmapCanvas::getClipMaxY() const
{
return clipStack.empty() ? height : (int)std::min((clipStack.back().y + clipStack.back().height) * uiscale, (double)height);
// main loop
if (steep)
{
for (float x = xpxl1 + 1.0f; x <= xpxl2 - 1.0f; x++)
{
plot(std::floor(intery), x, rfpart(intery), color);
plot(std::floor(intery) + 1.0f, x, fpart(intery), color);
intery = intery + gradient;
}
}
else
{
for (float x = xpxl1 + 1.0f; x <= xpxl2 - 1.0f; x++)
{
plot(x, std::floor(intery), rfpart(intery), color);
plot(x, std::floor(intery) + 1, fpart(intery), color);
intery = intery + gradient;
}
}
}
void BitmapCanvas::fillTile(float left, float top, float width, float height, Colorf color)
@ -632,11 +764,12 @@ void BitmapCanvas::fillTile(float left, float top, float width, float height, Co
}
}
void BitmapCanvas::drawTile(CanvasTexture* texture, float left, float top, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color)
void BitmapCanvas::drawTile(CanvasTexture* tex, float left, float top, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color)
{
if (width <= 0.0f || height <= 0.0f || color.a <= 0.0f)
return;
auto texture = static_cast<BitmapTexture*>(tex);
int swidth = texture->Width;
int sheight = texture->Height;
const uint32_t* src = texture->Data.data();
@ -740,11 +873,12 @@ void BitmapCanvas::drawTile(CanvasTexture* texture, float left, float top, float
}
}
void BitmapCanvas::drawGlyph(CanvasTexture* texture, float left, float top, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color)
void BitmapCanvas::drawGlyph(CanvasTexture* tex, float left, float top, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color)
{
if (width <= 0.0f || height <= 0.0f)
return;
auto texture = static_cast<BitmapTexture*>(tex);
int swidth = texture->Width;
int sheight = texture->Height;
const uint32_t* src = texture->Data.data();
@ -926,15 +1060,13 @@ void BitmapCanvas::drawGlyph(CanvasTexture* texture, float left, float top, floa
void BitmapCanvas::begin(const Colorf& color)
{
uiscale = window->GetDpiScale();
Canvas::begin(color);
uint32_t r = (int32_t)clamp(color.r * 255.0f, 0.0f, 255.0f);
uint32_t g = (int32_t)clamp(color.g * 255.0f, 0.0f, 255.0f);
uint32_t b = (int32_t)clamp(color.b * 255.0f, 0.0f, 255.0f);
uint32_t a = (int32_t)clamp(color.a * 255.0f, 0.0f, 255.0f);
uint32_t bgcolor = (a << 24) | (r << 16) | (g << 8) | b;
width = window->GetPixelWidth();
height = window->GetPixelHeight();
pixels.clear();
pixels.resize(width * height, bgcolor);
}
@ -944,17 +1076,9 @@ void BitmapCanvas::end()
window->PresentBitmap(width, height, pixels.data());
}
void BitmapCanvas::begin3d()
{
}
void BitmapCanvas::end3d()
{
}
/////////////////////////////////////////////////////////////////////////////
std::unique_ptr<Canvas> Canvas::create(DisplayWindow* window)
std::unique_ptr<Canvas> Canvas::create()
{
return std::make_unique<BitmapCanvas>(window);
return std::make_unique<BitmapCanvas>();
}

View file

@ -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,

View file

@ -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;
}

View file

@ -156,9 +156,13 @@ DarkWidgetTheme::DarkWidgetTheme()
auto tabbar_tab = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar-tab");
auto tabwidget_stack = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabwidget-stack");
auto checkbox_label = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "checkbox-label");
auto menubar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menubar");
auto menubaritem = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menubaritem");
auto menu = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menu");
auto menuitem = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menuitem");
auto toolbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "toolbar");
auto toolbarbutton = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "toolbarbutton");
auto statusbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "statusbar");
widget->SetString("font-family", "NotoSans");
widget->SetColor("color", Colorf::fromRgba8(226, 223, 219));
@ -235,8 +239,18 @@ DarkWidgetTheme::DarkWidgetTheme()
checkbox_label->SetColor("unchecked-outer-border-color", Colorf::fromRgba8(99, 99, 99));
checkbox_label->SetColor("unchecked-inner-border-color", Colorf::fromRgba8(51, 51, 51));
menubar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33));
toolbar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33));
statusbar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33));
toolbarbutton->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78));
toolbarbutton->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88));
menubaritem->SetColor("color", Colorf::fromRgba8(226, 223, 219));
menubaritem->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78));
menubaritem->SetColor("hover", "color", Colorf::fromRgba8(0, 0, 0));
menubaritem->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88));
menubaritem->SetColor("down", "color", Colorf::fromRgba8(0, 0, 0));
menu->SetDouble("noncontent-left", 5.0);
menu->SetDouble("noncontent-top", 5.0);
@ -266,6 +280,7 @@ LightWidgetTheme::LightWidgetTheme()
auto tabbar_tab = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar-tab");
auto tabwidget_stack = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabwidget-stack");
auto checkbox_label = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "checkbox-label");
auto menubar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menubar");
auto menubaritem = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menubaritem");
auto menu = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menu");
auto menuitem = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menuitem");
@ -345,13 +360,19 @@ LightWidgetTheme::LightWidgetTheme()
checkbox_label->SetColor("unchecked-outer-border-color", Colorf::fromRgba8(156, 156, 156));
checkbox_label->SetColor("unchecked-inner-border-color", Colorf::fromRgba8(200, 200, 200));
menubar->SetColor("background-color", Colorf::fromRgba8(70, 70, 70));
menubaritem->SetColor("color", Colorf::fromRgba8(226, 223, 219));
menubaritem->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200));
menubaritem->SetColor("hover", "color", Colorf::fromRgba8(0, 0, 0));
menubaritem->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190));
menubaritem->SetColor("down", "color", Colorf::fromRgba8(0, 0, 0));
menu->SetDouble("noncontent-left", 5.0);
menu->SetDouble("noncontent-top", 5.0);
menu->SetDouble("noncontent-right", 5.0);
menu->SetDouble("noncontent-bottom", 5.0);
menu->SetColor("background-color", Colorf::fromRgba8(255, 255, 255));
menu->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155));
menu->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155));
menu->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155));

View file

@ -23,7 +23,7 @@ TrueTypeFont::TrueTypeFont(std::shared_ptr<TrueTypeFontFileData> initdata, int t
if (memcmp(versionTag.data(), "ttcf", 4) == 0) // TTC header
{
ttcHeader.Load(reader);
if (ttcFontIndex >= ttcHeader.numFonts)
if (ttcFontIndex >= (int)ttcHeader.numFonts)
throw std::runtime_error("TTC font index out of bounds");
reader.Seek(ttcHeader.tableDirectoryOffsets[ttcFontIndex]);
}
@ -122,7 +122,7 @@ TrueTypeGlyph TrueTypeFont::LoadGlyph(uint32_t glyphIndex, double height) const
path.fill_mode = PathFillMode::winding;
int startPoint = 0;
int numberOfContours = g.endPtsOfContours.size();
int numberOfContours = (int)g.endPtsOfContours.size();
for (int i = 0; i < numberOfContours; i++)
{
int endPoint = g.endPtsOfContours[i];
@ -310,7 +310,7 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos
if (numberOfContours > 0) // Simple glyph
{
int pointsOffset = g.points.size();
int pointsOffset = (int)g.points.size();
for (ttf_uint16 i = 0; i < numberOfContours; i++)
g.endPtsOfContours.push_back(pointsOffset + reader.ReadUInt16());
@ -339,15 +339,15 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos
if (g.flags[i] & TTF_X_SHORT_VECTOR)
{
ttf_int16 x = reader.ReadUInt8();
g.points[i].x = (g.flags[i] & TTF_X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) ? x : -x;
g.points[i].x = (float)((g.flags[i] & TTF_X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) ? x : -x);
}
else if (g.flags[i] & TTF_X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR)
{
g.points[i].x = 0;
g.points[i].x = 0.0f;
}
else
{
g.points[i].x = reader.ReadInt16();
g.points[i].x = (float)reader.ReadInt16();
}
}
@ -356,15 +356,15 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos
if (g.flags[i] & TTF_Y_SHORT_VECTOR)
{
ttf_int16 y = reader.ReadUInt8();
g.points[i].y = (g.flags[i] & TTF_Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) ? y : -y;
g.points[i].y = (float)((g.flags[i] & TTF_Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) ? y : -y);
}
else if (g.flags[i] & TTF_Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR)
{
g.points[i].y = 0;
g.points[i].y = 0.0f;
}
else
{
g.points[i].y = reader.ReadInt16();
g.points[i].y = (float)reader.ReadInt16();
}
}
@ -380,7 +380,7 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos
if (compositeDepth == 8)
throw std::runtime_error("Composite glyph recursion exceeded");
int parentPointsOffset = g.points.size();
int parentPointsOffset = (int)g.points.size();
bool weHaveInstructions = false;
while (true)
@ -420,7 +420,7 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos
bool transform = true;
if (flags & TTF_WE_HAVE_A_SCALE)
{
ttf_F2DOT14 scale = F2DOT14_ToFloat(reader.ReadF2DOT14());
float scale = F2DOT14_ToFloat(reader.ReadF2DOT14());
mat2x2[0] = scale;
mat2x2[1] = 0;
mat2x2[2] = 0;
@ -445,7 +445,7 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos
transform = false;
}
int childPointsOffset = g.points.size();
int childPointsOffset = (int)g.points.size();
LoadGlyph(g, childGlyphIndex, compositeDepth + 1);
if (transform)
@ -463,8 +463,8 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos
if (flags & TTF_ARGS_ARE_XY_VALUES)
{
dx = argument1;
dy = argument2;
dx = (float)argument1;
dy = (float)argument2;
// Spec states we must fall back to TTF_UNSCALED_COMPONENT_OFFSET if both flags are set
if ((flags & (TTF_SCALED_COMPONENT_OFFSET | TTF_UNSCALED_COMPONENT_OFFSET)) == TTF_SCALED_COMPONENT_OFFSET)
@ -976,8 +976,8 @@ void TTF_NamingTable::Load(TrueTypeFileReader& reader)
for (ttf_uint16 i = 0; i < langTagCount; i++)
{
LangTagRecord record;
ttf_uint16 length;
ttf_Offset16 langTagOffset;
record.length = reader.ReadUInt16();
record.langTagOffset = reader.ReadOffset16();
langTagRecord.push_back(record);
}
}

View file

@ -7,13 +7,17 @@
#include <cmath>
#include <algorithm>
Widget::Widget(Widget* parent, WidgetType type) : Type(type)
Widget::Widget(Widget* parent, WidgetType type, RenderAPI renderAPI) : Type(type)
{
if (type != WidgetType::Child)
{
Widget* owner = parent ? parent->Window() : nullptr;
DispWindow = DisplayWindow::Create(this, type == WidgetType::Popup, owner ? owner->DispWindow.get() : nullptr);
DispCanvas = Canvas::create(DispWindow.get());
DispWindow = DisplayWindow::Create(this, type == WidgetType::Popup, owner ? owner->DispWindow.get() : nullptr, renderAPI);
if (renderAPI == RenderAPI::Unspecified || renderAPI == RenderAPI::Bitmap)
{
DispCanvas = Canvas::create();
DispCanvas->attach(DispWindow.get());
}
SetStyleState("root");
SetWindowBackground(GetStyleColor("window-background"));
@ -30,6 +34,9 @@ Widget::Widget(Widget* parent, WidgetType type) : Type(type)
Widget::~Widget()
{
if (DispCanvas)
DispCanvas->detach();
while (LastChildObj)
delete LastChildObj;
@ -39,6 +46,17 @@ Widget::~Widget()
DetachFromParent();
}
void Widget::SetCanvas(std::unique_ptr<Canvas> canvas)
{
if (DispWindow)
{
if (DispCanvas)
DispCanvas->detach();
DispCanvas = std::move(canvas);
DispCanvas->attach(DispWindow.get());
}
}
void Widget::SetParent(Widget* newParent)
{
if (ParentObj != newParent)
@ -204,6 +222,15 @@ void Widget::ShowFullscreen()
}
}
bool Widget::IsFullscreen()
{
if (Type != WidgetType::Child)
{
return DispWindow->IsWindowFullscreen();
}
return false;
}
void Widget::ShowMaximized()
{
if (Type != WidgetType::Child)
@ -307,9 +334,12 @@ void Widget::Update()
void Widget::Repaint()
{
Widget* w = Window();
w->DispCanvas->begin(WindowBackground);
w->Paint(w->DispCanvas.get());
w->DispCanvas->end();
if (w->DispCanvas)
{
w->DispCanvas->begin(WindowBackground);
w->Paint(w->DispCanvas.get());
w->DispCanvas->end();
}
}
void Widget::Paint(Canvas* canvas)
@ -429,7 +459,7 @@ void Widget::SetCursor(StandardCursor cursor)
}
}
void Widget::CaptureMouse()
void Widget::SetPointerCapture()
{
Widget* w = Window();
if (w && w->CaptureWidget != this)
@ -439,7 +469,7 @@ void Widget::CaptureMouse()
}
}
void Widget::ReleaseMouseCapture()
void Widget::ReleasePointerCapture()
{
Widget* w = Window();
if (w && w->CaptureWidget != nullptr)
@ -449,6 +479,24 @@ void Widget::ReleaseMouseCapture()
}
}
void Widget::SetModalCapture()
{
Widget* w = Window();
if (w && w->CaptureWidget != this)
{
w->CaptureWidget = this;
}
}
void Widget::ReleaseModalCapture()
{
Widget* w = Window();
if (w && w->CaptureWidget != nullptr)
{
w->CaptureWidget = nullptr;
}
}
std::string Widget::GetClipboardText()
{
Widget* w = Window();
@ -485,6 +533,24 @@ Canvas* Widget::GetCanvas() const
return nullptr;
}
bool Widget::IsParent(const Widget* w) const
{
while (w)
{
w = w->Parent();
if (w == this)
return true;
}
return false;
}
bool Widget::IsChild(const Widget* w) const
{
if (!w)
return false;
return w->IsParent(this);
}
Widget* Widget::ChildAt(const Point& pos)
{
for (Widget* cur = LastChild(); cur != nullptr; cur = cur->PrevSibling())
@ -790,6 +856,18 @@ void* Widget::GetNativeHandle()
return w ? w->DispWindow->GetNativeHandle() : nullptr;
}
int Widget::GetNativePixelWidth()
{
Widget* w = Window();
return w ? w->DispWindow->GetPixelWidth() : 0;
}
int Widget::GetNativePixelHeight()
{
Widget* w = Window();
return w ? w->DispWindow->GetPixelHeight() : 0;
}
void Widget::SetStyleClass(const std::string& themeClass)
{
if (StyleClass != themeClass)

View file

@ -1,188 +0,0 @@
#include "systemdialogs/folder_browse_dialog.h"
#include "core/widget.h"
#include "window/window.h"
#include <stdexcept>
#if defined(WIN32)
#include <Shlobj.h>
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<typename T>
class ComPtr
{
public:
ComPtr() { Ptr = nullptr; }
ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); }
ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; }
~ComPtr() { reset(); }
ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; }
void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; }
T* get() { return Ptr; }
static IID GetIID() { return __uuidof(T); }
void** InitPtr() { return (void**)TypedInitPtr(); }
T** TypedInitPtr() { reset(); return &Ptr; }
operator T* () const { return Ptr; }
T* operator ->() const { return Ptr; }
T* Ptr;
};
}
class BrowseFolderDialogImpl : public BrowseFolderDialog
{
public:
BrowseFolderDialogImpl(Widget *owner) : owner(owner)
{
}
Widget *owner = nullptr;
std::string selected_path;
std::string initial_directory;
std::string title;
bool Show() override
{
std::wstring title16 = to_utf16(title);
std::wstring initial_directory16 = to_utf16(initial_directory);
HRESULT result;
ComPtr<IFileOpenDialog> 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<IShellItem> 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<IShellItem> folder_item;
result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr());
throw_if_failed(result, "SHCreateItemFromParsingName failed");
*/
if (folder_item)
{
result = open_dialog->SetFolder(folder_item);
throw_if_failed(result, "IFileOpenDialog.SetFolder failed");
}
}
if (owner && owner->Window())
result = open_dialog->Show((HWND)owner->Window()->GetNativeHandle());
else
result = open_dialog->Show(0);
if (SUCCEEDED(result))
{
ComPtr<IShellItem> chosen_folder;
result = open_dialog->GetResult(chosen_folder.TypedInitPtr());
throw_if_failed(result, "IFileOpenDialog.GetResult failed");
WCHAR *buffer = nullptr;
result = chosen_folder->GetDisplayName(SIGDN_FILESYSPATH, &buffer);
throw_if_failed(result, "IShellItem.GetDisplayName failed");
std::wstring output_directory16;
if (buffer)
{
try
{
output_directory16 = buffer;
}
catch (...)
{
CoTaskMemFree(buffer);
throw;
}
}
CoTaskMemFree(buffer);
selected_path = from_utf16(output_directory16);
return true;
}
else
{
return false;
}
}
std::string BrowseFolderDialog::SelectedPath() const override
{
return selected_path;
}
void BrowseFolderDialog::SetInitialDirectory(const std::string& path) override
{
initial_directory = path;
}
void BrowseFolderDialog::SetTitle(const std::string& newtitle) override
{
title = newtitle;
}
void throw_if_failed(HRESULT result, const std::string &error)
{
if (FAILED(result))
throw std::runtime_error(error);
}
};
std::unique_ptr<BrowseFolderDialog> BrowseFolderDialog::Create(Widget* owner)
{
return std::make_unique<BrowseFolderDialogImpl>(owner);
}
#else
std::unique_ptr<BrowseFolderDialog> BrowseFolderDialog::Create(Widget* owner)
{
return {};
}
#endif

View file

@ -1,286 +1,12 @@
#include "systemdialogs/open_file_dialog.h"
#include "core/widget.h"
#include "window/window.h"
#include <stdexcept>
#if defined(WIN32)
#include <Shlobj.h>
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<typename T>
class ComPtr
{
public:
ComPtr() { Ptr = nullptr; }
ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); }
ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; }
~ComPtr() { reset(); }
ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; }
void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; }
T* get() { return Ptr; }
static IID GetIID() { return __uuidof(T); }
void** InitPtr() { return (void**)TypedInitPtr(); }
T** TypedInitPtr() { reset(); return &Ptr; }
operator T* () const { return Ptr; }
T* operator ->() const { return Ptr; }
T* Ptr;
};
}
class OpenFileDialogImpl : public OpenFileDialog
{
public:
OpenFileDialogImpl(Widget* owner) : owner(owner)
{
}
Widget* owner = nullptr;
std::string initial_directory;
std::string initial_filename;
std::string title;
std::vector<std::string> filenames;
bool multi_select = false;
struct Filter
{
std::string description;
std::string extension;
};
std::vector<Filter> filters;
int filterindex = 0;
std::string defaultext;
bool Show() override
{
std::wstring title16 = to_utf16(title);
std::wstring initial_directory16 = to_utf16(initial_directory);
HRESULT result;
ComPtr<IFileOpenDialog> 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<COMDLG_FILTERSPEC> filterspecs(filters.size());
std::vector<std::wstring> descriptions(filters.size());
std::vector<std::wstring> 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<IShellItem> 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<IShellItem> folder_item;
result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr());
throw_if_failed(result, "SHCreateItemFromParsingName failed");
*/
if (folder_item)
{
result = open_dialog->SetFolder(folder_item);
throw_if_failed(result, "IFileOpenDialog.SetFolder failed");
}
}
if (owner && owner->Window())
result = open_dialog->Show((HWND)owner->Window()->GetNativeHandle());
else
result = open_dialog->Show(0);
if (SUCCEEDED(result))
{
ComPtr<IShellItemArray> items;
result = open_dialog->GetSelectedItems(items.TypedInitPtr());
throw_if_failed(result, "IFileOpenDialog.GetSelectedItems failed");
DWORD num_items = 0;
result = items->GetCount(&num_items);
throw_if_failed(result, "IShellItemArray.GetCount failed");
for (DWORD i = 0; i < num_items; i++)
{
ComPtr<IShellItem> item;
result = items->GetItemAt(i, item.TypedInitPtr());
throw_if_failed(result, "IShellItemArray.GetItemAt failed");
WCHAR* buffer = nullptr;
result = item->GetDisplayName(SIGDN_FILESYSPATH, &buffer);
throw_if_failed(result, "IShellItem.GetDisplayName failed");
std::wstring output16;
if (buffer)
{
try
{
output16 = buffer;
}
catch (...)
{
CoTaskMemFree(buffer);
throw;
}
}
CoTaskMemFree(buffer);
filenames.push_back(from_utf16(output16));
}
return true;
}
else
{
return false;
}
}
std::string Filename() const override
{
return !filenames.empty() ? filenames.front() : std::string();
}
std::vector<std::string> Filenames() const override
{
return filenames;
}
void SetMultiSelect(bool new_multi_select) override
{
multi_select = new_multi_select;
}
void SetFilename(const std::string& filename) override
{
initial_filename = filename;
}
void AddFilter(const std::string& filter_description, const std::string& filter_extension) override
{
Filter f;
f.description = filter_description;
f.extension = filter_extension;
filters.push_back(std::move(f));
}
void ClearFilters() override
{
filters.clear();
}
void SetFilterIndex(int filter_index) override
{
filterindex = filter_index;
}
void SetInitialDirectory(const std::string& path) override
{
initial_directory = path;
}
void SetTitle(const std::string& newtitle) override
{
title = newtitle;
}
void SetDefaultExtension(const std::string& extension) override
{
defaultext = extension;
}
void throw_if_failed(HRESULT result, const std::string& error)
{
if (FAILED(result))
throw std::runtime_error(error);
}
};
#include "core/widget.h"
std::unique_ptr<OpenFileDialog> OpenFileDialog::Create(Widget* owner)
{
return std::make_unique<OpenFileDialogImpl>(owner);
DisplayWindow* windowOwner = nullptr;
if (owner && owner->Window())
windowOwner = owner->Window()->DispWindow.get();
return DisplayBackend::Get()->CreateOpenFileDialog(windowOwner);
}
#else
std::unique_ptr<OpenFileDialog> OpenFileDialog::Create(Widget* owner)
{
return {};
}
#endif

View file

@ -0,0 +1,12 @@
#include "systemdialogs/open_folder_dialog.h"
#include "window/window.h"
#include "core/widget.h"
std::unique_ptr<OpenFolderDialog> OpenFolderDialog::Create(Widget* owner)
{
DisplayWindow* windowOwner = nullptr;
if (owner && owner->Window())
windowOwner = owner->Window()->DispWindow.get();
return DisplayBackend::Get()->CreateOpenFolderDialog(windowOwner);
}

View file

@ -1,263 +1,12 @@
#include "systemdialogs/save_file_dialog.h"
#include "core/widget.h"
#include "window/window.h"
#include <stdexcept>
#if defined(WIN32)
#include <Shlobj.h>
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<typename T>
class ComPtr
{
public:
ComPtr() { Ptr = nullptr; }
ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); }
ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; }
~ComPtr() { reset(); }
ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; }
void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; }
T* get() { return Ptr; }
static IID GetIID() { return __uuidof(T); }
void** InitPtr() { return (void**)TypedInitPtr(); }
T** TypedInitPtr() { reset(); return &Ptr; }
operator T* () const { return Ptr; }
T* operator ->() const { return Ptr; }
T* Ptr;
};
}
class SaveFileDialogImpl : public SaveFileDialog
{
public:
SaveFileDialogImpl(Widget* owner) : owner(owner)
{
}
Widget* owner = nullptr;
std::string filename;
std::string initial_directory;
std::string initial_filename;
std::string title;
std::vector<std::string> filenames;
struct Filter
{
std::string description;
std::string extension;
};
std::vector<Filter> filters;
int filterindex = 0;
std::string defaultext;
bool Show() override
{
std::wstring title16 = to_utf16(title);
std::wstring initial_directory16 = to_utf16(initial_directory);
HRESULT result;
ComPtr<IFileSaveDialog> 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<COMDLG_FILTERSPEC> filterspecs(filters.size());
std::vector<std::wstring> descriptions(filters.size());
std::vector<std::wstring> 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<IShellItem> 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<IShellItem> folder_item;
result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr());
throw_if_failed(result, "SHCreateItemFromParsingName failed");
*/
if (folder_item)
{
result = save_dialog->SetFolder(folder_item);
throw_if_failed(result, "IFileSaveDialog.SetFolder failed");
}
}
if (owner && owner->Window())
result = save_dialog->Show((HWND)owner->Window()->GetNativeHandle());
else
result = save_dialog->Show(0);
if (SUCCEEDED(result))
{
ComPtr<IShellItem> chosen_folder;
result = save_dialog->GetResult(chosen_folder.TypedInitPtr());
throw_if_failed(result, "IFileSaveDialog.GetResult failed");
WCHAR* buffer = nullptr;
result = chosen_folder->GetDisplayName(SIGDN_FILESYSPATH, &buffer);
throw_if_failed(result, "IShellItem.GetDisplayName failed");
std::wstring output16;
if (buffer)
{
try
{
output16 = buffer;
}
catch (...)
{
CoTaskMemFree(buffer);
throw;
}
}
CoTaskMemFree(buffer);
filename = from_utf16(output16);
return true;
}
else
{
return false;
}
}
std::string Filename() const override
{
return filename;
}
void SetFilename(const std::string& filename) override
{
initial_filename = filename;
}
void AddFilter(const std::string& filter_description, const std::string& filter_extension) override
{
Filter f;
f.description = filter_description;
f.extension = filter_extension;
filters.push_back(std::move(f));
}
void ClearFilters() override
{
filters.clear();
}
void SetFilterIndex(int filter_index) override
{
filterindex = filter_index;
}
void SetInitialDirectory(const std::string& path) override
{
initial_directory = path;
}
void SetTitle(const std::string& newtitle) override
{
title = newtitle;
}
void SetDefaultExtension(const std::string& extension) override
{
defaultext = extension;
}
void throw_if_failed(HRESULT result, const std::string& error)
{
if (FAILED(result))
throw std::runtime_error(error);
}
};
#include "core/widget.h"
std::unique_ptr<SaveFileDialog> SaveFileDialog::Create(Widget* owner)
{
return std::make_unique<SaveFileDialogImpl>(owner);
DisplayWindow* windowOwner = nullptr;
if (owner && owner->Window())
windowOwner = owner->Window()->DispWindow.get();
return DisplayBackend::Get()->CreateSaveFileDialog(windowOwner);
}
#else
std::unique_ptr<SaveFileDialog> SaveFileDialog::Create(Widget* owner)
{
return {};
}
#endif

View file

@ -30,10 +30,44 @@ void ImageBox::SetImage(std::shared_ptr<Image> 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));
}
}
}

View file

@ -1,4 +1,4 @@
#include <algorithm>
#include "widgets/lineedit/lineedit.h"
#include "core/utf8reader.h"
#include "core/colorf.h"
@ -19,8 +19,6 @@ LineEdit::LineEdit(Widget* parent) : Widget(parent)
LineEdit::~LineEdit()
{
delete timer;
delete scroll_timer;
}
bool LineEdit::IsReadOnly() const
@ -310,7 +308,7 @@ bool LineEdit::OnMouseDown(const Point& pos, InputKey key)
{
if (HasFocus())
{
CaptureMouse();
SetPointerCapture();
mouse_selecting = true;
cursor_pos = GetCharacterIndex(pos.x);
SetTextSelection(cursor_pos, 0);
@ -335,14 +333,14 @@ bool LineEdit::OnMouseUp(const Point& pos, InputKey key)
{
if (ignore_mouse_events) // This prevents text selection from changing from what was set when focus was gained.
{
ReleaseMouseCapture();
ReleasePointerCapture();
ignore_mouse_events = false;
mouse_selecting = false;
}
else
{
scroll_timer->Stop();
ReleaseMouseCapture();
ReleasePointerCapture();
mouse_selecting = false;
int sel_end = GetCharacterIndex(pos.x);
SetSelectionLength(sel_end - selection_start);

View file

@ -16,6 +16,19 @@ void ListView::AddItem(const std::string& text)
Update();
}
void ListView::RemoveItem(int index)
{
if (index >= 0 && index < items.size())
{
items.erase(items.begin() + index);
}
if (selectedItem == items.size())
{
selectedItem = !items.empty() ? (int)items.size() - 1 : 0;
}
}
void ListView::Activate()
{
if (OnActivated)

View file

@ -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);
}

View file

@ -22,7 +22,17 @@ MenubarItem* Menubar::AddItem(std::string text, std::function<void(Menu* menu)>
void Menubar::ShowMenu(MenubarItem* item)
{
int index = GetItemIndex(item);
if (index == currentMenubarItem)
return;
CloseMenu();
SetFocus();
SetModalCapture();
currentMenubarItem = index;
if (currentMenubarItem != -1)
menuItems[currentMenubarItem]->SetStyleState("hover");
modalMode = true;
if (item->GetOpenCallback())
{
openMenu = new Menu(this);
@ -38,8 +48,13 @@ void Menubar::ShowMenu(MenubarItem* item)
void Menubar::CloseMenu()
{
//delete openMenu;
//openMenu = nullptr;
if (currentMenubarItem != -1)
menuItems[currentMenubarItem]->SetStyleState("");
currentMenubarItem = -1;
delete openMenu;
openMenu = nullptr;
ReleaseModalCapture();
modalMode = false;
}
void Menubar::OnGeometryChanged()
@ -65,6 +80,139 @@ void Menubar::OnGeometryChanged()
}
}
MenubarItem* Menubar::GetMenubarItemAt(const Point& pos)
{
Widget* widget = ChildAt(pos);
for (MenubarItem* item : menuItems)
{
if (widget == item)
return item;
}
return nullptr;
}
bool Menubar::OnMouseDown(const Point& pos, InputKey key)
{
if (!modalMode)
return Widget::OnMouseDown(pos, key);
MenubarItem* item = GetMenubarItemAt(pos);
if (item)
ShowMenu(item);
else
CloseMenu();
return true;
}
bool Menubar::OnMouseUp(const Point& pos, InputKey key)
{
if (!modalMode)
return Widget::OnMouseUp(pos, key);
MenubarItem* item = GetMenubarItemAt(pos);
if (!item)
CloseMenu();
return true;
}
void Menubar::OnMouseMove(const Point& pos)
{
if (!modalMode)
return Widget::OnMouseMove(pos);
MenubarItem* item = GetMenubarItemAt(pos);
if (item)
ShowMenu(item);
}
int Menubar::GetItemIndex(MenubarItem* item)
{
int i = 0;
for (MenubarItem* cur : menuItems)
{
if (cur == item)
return i;
i++;
}
return -1;
}
void Menubar::OnKeyDown(InputKey key)
{
if (!modalMode)
return Widget::OnKeyDown(key);
if (key == InputKey::Left)
{
if (!menuItems.empty())
{
int index = currentMenubarItem - 1;
if (index < 0)
index = (int)menuItems.size() - 1;
ShowMenu(menuItems[index]);
}
}
else if (key == InputKey::Right)
{
if (!menuItems.empty())
{
int index = currentMenubarItem + 1;
if (index == (int)menuItems.size())
index = 0;
ShowMenu(menuItems[index]);
}
}
else if (key == InputKey::Up)
{
if (openMenu)
{
// Keep trying until we don't find a separator
for (int i = 0; i < 10; i++)
{
if (!openMenu->selectedItem || !openMenu->selectedItem->PrevSibling())
{
if (openMenu->LastChild())
openMenu->SetSelected(static_cast<MenuItem*>(openMenu->LastChild()));
}
else
{
openMenu->SetSelected(static_cast<MenuItem*>(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<MenuItem*>(openMenu->FirstChild()));
}
else
{
openMenu->SetSelected(static_cast<MenuItem*>(openMenu->selectedItem->NextSibling()));
}
if (openMenu->selectedItem && openMenu->selectedItem->GetStyleClass() != "menuitemseparator")
break;
}
}
}
else if (key == InputKey::Enter)
{
if (openMenu && openMenu->selectedItem)
openMenu->selectedItem->Click();
}
}
/////////////////////////////////////////////////////////////////////////////
MenubarItem::MenubarItem(Menubar* menubar, std::string text, bool alignRight) : Widget(menubar), menubar(menubar), text(text), AlignRight(alignRight)
@ -127,20 +275,10 @@ void Menu::SetRightPosition(const Point& pos)
MenuItem* Menu::AddItem(std::shared_ptr<Image> icon, std::string text, std::function<void()> onClick)
{
auto item = new MenuItem(this);
auto item = new MenuItem(this, [this, onClick]() { if (onCloseMenu) onCloseMenu(); if (onClick) onClick(); });
if (icon)
item->icon->SetImage(icon);
item->text->SetText(text);
/*
item->element->addEventListener("click", [=](Event* event)
{
event->stopPropagation();
if (onCloseMenu)
onCloseMenu();
if (onClick)
onClick();
});
*/
return item;
}
@ -152,7 +290,7 @@ MenuItemSeparator* Menu::AddSeparator()
double Menu::GetPreferredWidth() const
{
return 200.0;
return GridFitSize(200.0);
}
double Menu::GetPreferredHeight() const
@ -160,7 +298,8 @@ double Menu::GetPreferredHeight() const
double h = 0.0;
for (Widget* item = FirstChild(); item != nullptr; item = item->NextSibling())
{
h += 20.0;
double itemheight = GridFitSize((item->GetStyleClass() == "menuitemseparator") ? 7.0 : 30.0);
h += itemheight;
}
return h;
}
@ -168,18 +307,31 @@ double Menu::GetPreferredHeight() const
void Menu::OnGeometryChanged()
{
double w = GetWidth();
double h = GetHeight();
double y = 0.0;
for (Widget* item = FirstChild(); item != nullptr; item = item->NextSibling())
{
item->SetFrameGeometry(Rect::xywh(0.0, y, w, 20.0));
y += 20.0;
double itemheight = GridFitSize((item->GetStyleClass() == "menuitemseparator") ? 7.0 : 30.0);
item->SetFrameGeometry(Rect::xywh(0.0, y, w, itemheight));
y += itemheight;
}
}
void Menu::SetSelected(MenuItem* item)
{
if (selectedItem)
{
selectedItem->SetStyleState("");
}
selectedItem = item;
if (selectedItem)
{
selectedItem->SetStyleState("hover");
}
}
/////////////////////////////////////////////////////////////////////////////
MenuItem::MenuItem(Widget* parent) : Widget(parent)
MenuItem::MenuItem(Menu* menu, std::function<void()> onClick) : Widget(menu), menu(menu), onClick(onClick)
{
SetStyleClass("menuitem");
icon = new ImageBox(this);
@ -188,25 +340,41 @@ MenuItem::MenuItem(Widget* parent) : Widget(parent)
void MenuItem::OnMouseMove(const Point& pos)
{
if (GetStyleState().empty())
menu->SetSelected(this);
}
bool MenuItem::OnMouseUp(const Point& pos, InputKey key)
{
Click();
return true;
}
void MenuItem::Click()
{
if (onClick)
{
SetStyleState("hover");
// We have to make a copy of the handler as it may delete 'this'
auto handler = onClick;
handler();
}
}
void MenuItem::OnMouseLeave()
{
SetStyleState("");
menu->SetSelected(nullptr);
}
void MenuItem::OnGeometryChanged()
{
double iconwidth = icon->GetPreferredWidth();
double iconheight = icon->GetPreferredHeight();
double iconwidth = GridFitSize(icon->GetPreferredWidth());
double iconheight = GridFitSize(icon->GetPreferredHeight());
double w = GetWidth();
double h = GetHeight();
icon->SetFrameGeometry(Rect::xywh(0.0, (h - iconheight) * 0.5, iconwidth, iconheight));
text->SetFrameGeometry(Rect::xywh(iconwidth, 0.0, w - iconwidth, h));
double textheight = 19.0;
double x0 = GridFitPoint(5.0);
double x1 = GridFitPoint(5.0 + iconwidth);
icon->SetFrameGeometry(Rect::xywh(x0, GridFitPoint((h - iconheight) * 0.5), iconwidth, iconheight));
text->SetFrameGeometry(Rect::xywh(x1, GridFitPoint((h - textheight) * 0.5), w - x1, textheight));
}
/////////////////////////////////////////////////////////////////////////////
@ -215,3 +383,8 @@ MenuItemSeparator::MenuItemSeparator(Widget* parent) : Widget(parent)
{
SetStyleClass("menuitemseparator");
}
void MenuItemSeparator::OnPaint(Canvas* canvas)
{
canvas->fillRect(Rect::xywh(0.0, GridFitPoint(GetHeight() * 0.5), GetWidth(), GridFitSize(1.0)), Colorf::fromRgba8(75, 75, 75));
}

View file

@ -254,7 +254,7 @@ bool Scrollbar::OnMouseDown(const Point& pos, InputKey key)
UpdatePartPositions();
Update();
CaptureMouse();
SetPointerCapture();
return true;
}
@ -270,7 +270,7 @@ bool Scrollbar::OnMouseUp(const Point& pos, InputKey key)
mouse_down_timer->Stop();
Update();
ReleaseMouseCapture();
ReleasePointerCapture();
return true;
}

View file

@ -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:");
}

View file

@ -3,6 +3,7 @@
#include "widgets/scrollbar/scrollbar.h"
#include "core/utf8reader.h"
#include "core/colorf.h"
#include <algorithm>
#ifdef _MSC_VER
#pragma warning(disable: 4267) // warning C4267: 'initializing': conversion from 'size_t' to 'int', possible loss of data
@ -290,7 +291,7 @@ bool TextEdit::OnMouseDown(const Point& pos, InputKey key)
{
if (key == InputKey::LeftMouse)
{
CaptureMouse();
SetPointerCapture();
mouse_selecting = true;
cursor_pos = GetCharacterIndex(pos);
selection_start = cursor_pos;
@ -312,14 +313,14 @@ bool TextEdit::OnMouseUp(const Point& pos, InputKey key)
{
if (ignore_mouse_events) // This prevents text selection from changing from what was set when focus was gained.
{
ReleaseMouseCapture();
ReleasePointerCapture();
ignore_mouse_events = false;
mouse_selecting = false;
}
else
{
scroll_timer->Stop();
ReleaseMouseCapture();
ReleasePointerCapture();
mouse_selecting = false;
ivec2 sel_end = GetCharacterIndex(pos);
selection_length = ToOffset(sel_end) - ToOffset(selection_start);

View file

@ -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<void()> 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;
}
}
}

View file

@ -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));
}
}

View file

@ -0,0 +1,288 @@
#include "dbus_open_file_dialog.h"
#include <dbus/dbus.h>
DBusOpenFileDialog::DBusOpenFileDialog(std::string ownerHandle) : ownerHandle(ownerHandle)
{
}
std::string DBusOpenFileDialog::Filename() const
{
return outputFilenames.empty() ? std::string() : outputFilenames.front();
}
std::vector<std::string> 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<std::string> 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();
}

View file

@ -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<std::string> 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<std::string> outputFilenames;
bool multiSelect = false;
struct Filter
{
std::string description;
std::string extension;
};
std::vector<Filter> filters;
int filter_index = 0;
};

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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;
}

View file

@ -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<std::string> filenames;
struct Filter
{
std::string description;
std::string extension;
};
std::vector<Filter> filters;
int filterindex = 0;
std::string defaultext;
};

View file

@ -0,0 +1,38 @@
#include "sdl2_display_backend.h"
#include "sdl2_display_window.h"
std::unique_ptr<DisplayWindow> SDL2DisplayBackend::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI)
{
return std::make_unique<SDL2DisplayWindow>(windowHost, popupWindow, static_cast<SDL2DisplayWindow*>(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<void()> onTimer)
{
return SDL2DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer));
}
void SDL2DisplayBackend::StopTimer(void* timerID)
{
SDL2DisplayWindow::StopTimer(timerID);
}

View file

@ -0,0 +1,19 @@
#pragma once
#include "window/window.h"
class SDL2DisplayBackend : public DisplayBackend
{
public:
std::unique_ptr<DisplayWindow> 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<void()> onTimer) override;
void StopTimer(void* timerID) override;
Size GetScreenSize() override;
bool IsSDL2() override { return true; }
};

View file

@ -1,6 +1,7 @@
#include "sdl2displaywindow.h"
#include "sdl2_display_window.h"
#include <stdexcept>
#include <SDL2/SDL_vulkan.h>
Uint32 SDL2DisplayWindow::PaintEventNumber = 0xffffffff;
bool SDL2DisplayWindow::ExitRunLoop;
@ -24,23 +25,43 @@ static void CheckInitSDL()
static InitSDL initsdl;
}
SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, SDL2DisplayWindow* owner) : WindowHost(windowHost)
SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, SDL2DisplayWindow* owner, RenderAPI renderAPI) : WindowHost(windowHost)
{
CheckInitSDL();
unsigned int flags = SDL_WINDOW_HIDDEN /*| SDL_WINDOW_ALLOW_HIGHDPI*/;
if (renderAPI == RenderAPI::Vulkan)
flags |= SDL_WINDOW_VULKAN;
else if (renderAPI == RenderAPI::OpenGL)
flags |= SDL_WINDOW_OPENGL;
#if defined(__APPLE__)
else if (renderAPI == RenderAPI::Metal)
flags |= SDL_WINDOW_METAL;
#endif
if (popupWindow)
flags |= SDL_WINDOW_BORDERLESS;
int result = SDL_CreateWindowAndRenderer(320, 200, flags, &WindowHandle, &RendererHandle);
if (result != 0)
throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError());
if (renderAPI == RenderAPI::Vulkan || renderAPI == RenderAPI::OpenGL || renderAPI == RenderAPI::Metal)
{
Handle.window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 320, 200, flags);
if (!Handle.window)
throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError());
}
else
{
int result = SDL_CreateWindowAndRenderer(320, 200, flags, &Handle.window, &RendererHandle);
if (result != 0)
throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError());
}
WindowList[SDL_GetWindowID(WindowHandle)] = this;
WindowList[SDL_GetWindowID(Handle.window)] = this;
}
SDL2DisplayWindow::~SDL2DisplayWindow()
{
WindowList.erase(WindowList.find(SDL_GetWindowID(WindowHandle)));
UnlockCursor();
WindowList.erase(WindowList.find(SDL_GetWindowID(Handle.window)));
if (BackBufferTexture)
{
@ -48,15 +69,39 @@ SDL2DisplayWindow::~SDL2DisplayWindow()
BackBufferTexture = nullptr;
}
SDL_DestroyRenderer(RendererHandle);
SDL_DestroyWindow(WindowHandle);
if (RendererHandle)
SDL_DestroyRenderer(RendererHandle);
SDL_DestroyWindow(Handle.window);
RendererHandle = nullptr;
WindowHandle = nullptr;
Handle.window = nullptr;
}
std::vector<std::string> SDL2DisplayWindow::GetVulkanInstanceExtensions()
{
unsigned int extCount = 0;
SDL_Vulkan_GetInstanceExtensions(Handle.window, &extCount, nullptr);
std::vector<const char*> extNames(extCount);
SDL_Vulkan_GetInstanceExtensions(Handle.window, &extCount, extNames.data());
std::vector<std::string> result;
result.reserve(extNames.size());
for (const char* ext : extNames)
result.emplace_back(ext);
return result;
}
VkSurfaceKHR SDL2DisplayWindow::CreateVulkanSurface(VkInstance instance)
{
VkSurfaceKHR surfaceHandle = {};
SDL_Vulkan_CreateSurface(Handle.window, instance, &surfaceHandle);
if (surfaceHandle)
throw std::runtime_error("Could not create vulkan surface");
return surfaceHandle;
}
void SDL2DisplayWindow::SetWindowTitle(const std::string& text)
{
SDL_SetWindowTitle(WindowHandle, text.c_str());
SDL_SetWindowTitle(Handle.window, text.c_str());
}
void SDL2DisplayWindow::SetWindowFrame(const Rect& box)
@ -76,46 +121,54 @@ void SDL2DisplayWindow::SetClientFrame(const Rect& box)
int w = (int)std::round(box.width * uiscale);
int h = (int)std::round(box.height * uiscale);
SDL_SetWindowPosition(WindowHandle, x, y);
SDL_SetWindowSize(WindowHandle, w, h);
SDL_SetWindowPosition(Handle.window, x, y);
SDL_SetWindowSize(Handle.window, w, h);
}
void SDL2DisplayWindow::Show()
{
SDL_ShowWindow(WindowHandle);
SDL_ShowWindow(Handle.window);
}
void SDL2DisplayWindow::ShowFullscreen()
{
SDL_SetWindowFullscreen(WindowHandle, SDL_WINDOW_FULLSCREEN_DESKTOP);
SDL_ShowWindow(Handle.window);
SDL_SetWindowFullscreen(Handle.window, SDL_WINDOW_FULLSCREEN_DESKTOP);
isFullscreen = true;
}
void SDL2DisplayWindow::ShowMaximized()
{
SDL_ShowWindow(WindowHandle);
SDL_MaximizeWindow(WindowHandle);
SDL_ShowWindow(Handle.window);
SDL_MaximizeWindow(Handle.window);
}
void SDL2DisplayWindow::ShowMinimized()
{
SDL_ShowWindow(WindowHandle);
SDL_MinimizeWindow(WindowHandle);
SDL_ShowWindow(Handle.window);
SDL_MinimizeWindow(Handle.window);
}
void SDL2DisplayWindow::ShowNormal()
{
SDL_ShowWindow(WindowHandle);
SDL_SetWindowFullscreen(WindowHandle, 0);
SDL_ShowWindow(Handle.window);
SDL_SetWindowFullscreen(Handle.window, 0);
isFullscreen = false;
}
bool SDL2DisplayWindow::IsWindowFullscreen()
{
return isFullscreen;
}
void SDL2DisplayWindow::Hide()
{
SDL_HideWindow(WindowHandle);
SDL_HideWindow(Handle.window);
}
void SDL2DisplayWindow::Activate()
{
SDL_RaiseWindow(WindowHandle);
SDL_RaiseWindow(Handle.window);
}
void SDL2DisplayWindow::ShowCursor(bool enable)
@ -125,14 +178,20 @@ void SDL2DisplayWindow::ShowCursor(bool enable)
void SDL2DisplayWindow::LockCursor()
{
SDL_SetWindowGrab(WindowHandle, SDL_TRUE);
SDL_ShowCursor(0);
if (!CursorLocked)
{
SDL_SetRelativeMouseMode(SDL_TRUE);
CursorLocked = true;
}
}
void SDL2DisplayWindow::UnlockCursor()
{
SDL_SetWindowGrab(WindowHandle, SDL_FALSE);
SDL_ShowCursor(1);
if (CursorLocked)
{
SDL_SetRelativeMouseMode(SDL_FALSE);
CursorLocked = false;
}
}
void SDL2DisplayWindow::CaptureMouse()
@ -151,7 +210,7 @@ void SDL2DisplayWindow::Update()
{
SDL_Event event = {};
event.type = PaintEventNumber;
event.user.windowID = SDL_GetWindowID(WindowHandle);
event.user.windowID = SDL_GetWindowID(Handle.window);
SDL_PushEvent(&event);
}
@ -172,8 +231,8 @@ Rect SDL2DisplayWindow::GetWindowFrame() const
int w = 0;
int h = 0;
double uiscale = GetDpiScale();
SDL_GetWindowPosition(WindowHandle, &x, &y);
SDL_GetWindowSize(WindowHandle, &w, &h);
SDL_GetWindowPosition(Handle.window, &x, &y);
SDL_GetWindowSize(Handle.window, &w, &h);
return Rect::xywh(x / uiscale, y / uiscale, w / uiscale, h / uiscale);
}
@ -182,7 +241,7 @@ Point SDL2DisplayWindow::MapFromGlobal(const Point& pos) const
int x = 0;
int y = 0;
double uiscale = GetDpiScale();
SDL_GetWindowPosition(WindowHandle, &x, &y);
SDL_GetWindowPosition(Handle.window, &x, &y);
return Point(pos.x - x / uiscale, pos.y - y / uiscale);
}
@ -191,7 +250,7 @@ Point SDL2DisplayWindow::MapToGlobal(const Point& pos) const
int x = 0;
int y = 0;
double uiscale = GetDpiScale();
SDL_GetWindowPosition(WindowHandle, &x, &y);
SDL_GetWindowPosition(Handle.window, &x, &y);
return Point(pos.x + x / uiscale, pos.y + y / uiscale);
}
@ -200,7 +259,7 @@ Size SDL2DisplayWindow::GetClientSize() const
int w = 0;
int h = 0;
double uiscale = GetDpiScale();
SDL_GetWindowSize(WindowHandle, &w, &h);
SDL_GetWindowSize(Handle.window, &w, &h);
return Size(w / uiscale, h / uiscale);
}
@ -208,7 +267,10 @@ int SDL2DisplayWindow::GetPixelWidth() const
{
int w = 0;
int h = 0;
int result = SDL_GetRendererOutputSize(RendererHandle, &w, &h);
if (RendererHandle)
SDL_GetRendererOutputSize(RendererHandle, &w, &h);
else
SDL_GL_GetDrawableSize(Handle.window, &w, &h);
return w;
}
@ -216,7 +278,10 @@ int SDL2DisplayWindow::GetPixelHeight() const
{
int w = 0;
int h = 0;
int result = SDL_GetRendererOutputSize(RendererHandle, &w, &h);
if (RendererHandle)
SDL_GetRendererOutputSize(RendererHandle, &w, &h);
else
SDL_GL_GetDrawableSize(Handle.window, &w, &h);
return h;
}
@ -228,6 +293,9 @@ double SDL2DisplayWindow::GetDpiScale() const
void SDL2DisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels)
{
if (!RendererHandle)
return;
if (!BackBufferTexture || BackBufferWidth != width || BackBufferHeight != height)
{
if (BackBufferTexture)
@ -305,6 +373,7 @@ void SDL2DisplayWindow::RunLoop()
{
CheckInitSDL();
ExitRunLoop = false;
while (!ExitRunLoop)
{
SDL_Event event = {};
@ -463,7 +532,14 @@ void SDL2DisplayWindow::OnMouseWheel(const SDL_MouseWheelEvent& event)
void SDL2DisplayWindow::OnMouseMotion(const SDL_MouseMotionEvent& event)
{
WindowHost->OnWindowMouseMove(GetMousePos(event));
if (CursorLocked)
{
WindowHost->OnWindowRawMouseMove(event.xrel, event.yrel);
}
else
{
WindowHost->OnWindowMouseMove(GetMousePos(event));
}
}
void SDL2DisplayWindow::OnPaintEvent()

View file

@ -3,12 +3,13 @@
#include <list>
#include <unordered_map>
#include <zwidget/window/window.h>
#include <zwidget/window/sdl2nativehandle.h>
#include <SDL2/SDL.h>
class SDL2DisplayWindow : public DisplayWindow
{
public:
SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, SDL2DisplayWindow* owner);
SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, SDL2DisplayWindow* owner, RenderAPI renderAPI);
~SDL2DisplayWindow();
void SetWindowTitle(const std::string& text) override;
@ -19,6 +20,7 @@ public:
void ShowMaximized() override;
void ShowMinimized() override;
void ShowNormal() override;
bool IsWindowFullscreen() override;
void Hide() override;
void Activate() override;
void ShowCursor(bool enable) override;
@ -48,7 +50,10 @@ public:
Point MapFromGlobal(const Point& pos) const override;
Point MapToGlobal(const Point& pos) const override;
void* GetNativeHandle() override { return WindowHandle; }
void* GetNativeHandle() override { return &Handle; }
std::vector<std::string> GetVulkanInstanceExtensions() override;
VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override;
static void DispatchEvent(const SDL_Event& event);
static SDL2DisplayWindow* FindEventWindow(const SDL_Event& event);
@ -84,12 +89,15 @@ public:
static void StopTimer(void* timerID);
DisplayWindowHost* WindowHost = nullptr;
SDL_Window* WindowHandle = nullptr;
SDL2NativeHandle Handle;
SDL_Renderer* RendererHandle = nullptr;
SDL_Texture* BackBufferTexture = nullptr;
int BackBufferWidth = 0;
int BackBufferHeight = 0;
bool CursorLocked = false;
bool isFullscreen = false;
static bool ExitRunLoop;
static Uint32 PaintEventNumber;
static std::unordered_map<int, SDL2DisplayWindow*> WindowList;

View file

@ -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<std::string> 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;
}

View file

@ -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<std::string> 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<std::string> filenames;
bool multi_select = false;
struct Filter
{
std::string description;
std::string extension;
};
std::vector<Filter> filters;
int filterindex = 0;
std::string defaultext;
};

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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;
}

View file

@ -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<std::string> filenames;
struct Filter
{
std::string description;
std::string extension;
};
std::vector<Filter> filters;
int filterindex = 0;
std::string defaultext;
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,168 @@
#pragma once
#include "window/window.h"
#include "window/ztimer/ztimer.h"
#include <wayland-client.h>
#include <wayland-client.hpp>
#include <wayland-client-protocol-extra.hpp>
#include <wayland-client-protocol-unstable.hpp>
#include <wayland-cursor.hpp>
#include "wl_fractional_scaling_protocol.hpp"
#include <linux/input.h>
#include <poll.h>
#include <map>
#include <xkbcommon/xkbcommon.h>
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<DisplayWindow> 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<void()> 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<OpenFileDialog> CreateOpenFileDialog(DisplayWindow* owner) override;
std::unique_ptr<SaveFileDialog> CreateSaveFileDialog(DisplayWindow* owner) override;
std::unique_ptr<OpenFolderDialog> 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<WaylandDisplayWindow*> 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<InputKey, bool> 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};
};

View file

@ -0,0 +1,472 @@
#include "wayland_display_window.h"
#include <cstring>
#include <dlfcn.h>
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<SharedMemHelper>(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<std::string> WaylandDisplayWindow::GetVulkanInstanceExtensions()
{
return { "VK_KHR_surface", "VK_KHR_wayland_surface" };
}

View file

@ -0,0 +1,198 @@
#pragma once
#include "wayland_display_backend.h"
#include "window/ztimer/ztimer.h"
#include <stdexcept>
#include <array>
#include <memory>
#include <sstream>
#include <algorithm>
#include <random>
#include <map>
#include <vector>
#include "zwidget/window/window.h"
#include "zwidget/window/waylandnativehandle.h"
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
template <typename R, typename T, typename... Args>
std::function<R(Args...)> 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<unsigned int> distribution(0, std::numeric_limits<unsigned int>::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<std::string> 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<SharedMemHelper> shared_mem;
bool isFullscreen = false;
// Helper functions
void CreateBuffers(int32_t width, int32_t height);
std::string GetWaylandWindowID();
friend WaylandDisplayBackend;
};

View file

@ -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<detail::events_base_t>(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<wl_proxy*> (p), t){
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
{
set_events(std::shared_ptr<detail::events_base_t>(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<wp_fractional_scale_manager_v1*> (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<wl_object*>(surface.c_ptr()) : nullptr);
return fractional_scale_v1_t(p);
}
int fractional_scale_manager_v1_t::dispatcher(uint32_t opcode, const std::vector<any>& args, const std::shared_ptr<detail::events_base_t>& 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<detail::events_base_t>(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<wl_proxy*> (p), t){
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
{
set_events(std::shared_ptr<detail::events_base_t>(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<wp_fractional_scale_v1*> (c_ptr());
}
std::function<void(uint32_t)> &fractional_scale_v1_t::on_preferred_scale()
{
return std::static_pointer_cast<events_t>(get_events())->preferred_scale;
}
int fractional_scale_v1_t::dispatcher(uint32_t opcode, const std::vector<any>& args, const std::shared_ptr<detail::events_base_t>& e)
{
std::shared_ptr<events_t> events = std::static_pointer_cast<events_t>(e);
switch(opcode)
{
case 0:
if(events->preferred_scale) events->preferred_scale(args[0].get<uint32_t>());
break;
}
return 0;
}

View file

@ -0,0 +1,126 @@
#pragma once
#include <array>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include <wayland-client.hpp>
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<detail::any>& args, const std::shared_ptr<detail::events_base_t>& 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<void(uint32_t)> preferred_scale;
};
static int dispatcher(uint32_t opcode, const std::vector<detail::any>& args, const std::shared_ptr<detail::events_base_t>& 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<void(uint32_t)> &on_preferred_scale();
};
}

View file

@ -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<DisplayWindow> Win32DisplayBackend::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI)
{
return std::make_unique<Win32DisplayWindow>(windowHost, popupWindow, static_cast<Win32DisplayWindow*>(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<void()> onTimer)
{
return Win32DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer));
}
void Win32DisplayBackend::StopTimer(void* timerID)
{
Win32DisplayWindow::StopTimer(timerID);
}
std::unique_ptr<OpenFileDialog> Win32DisplayBackend::CreateOpenFileDialog(DisplayWindow* owner)
{
return std::make_unique<Win32OpenFileDialog>(static_cast<Win32DisplayWindow*>(owner));
}
std::unique_ptr<SaveFileDialog> Win32DisplayBackend::CreateSaveFileDialog(DisplayWindow* owner)
{
return std::make_unique<Win32SaveFileDialog>(static_cast<Win32DisplayWindow*>(owner));
}
std::unique_ptr<OpenFolderDialog> Win32DisplayBackend::CreateOpenFolderDialog(DisplayWindow* owner)
{
return std::make_unique<Win32OpenFolderDialog>(static_cast<Win32DisplayWindow*>(owner));
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "window/window.h"
class Win32DisplayBackend : public DisplayBackend
{
public:
std::unique_ptr<DisplayWindow> 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<void()> onTimer) override;
void StopTimer(void* timerID) override;
Size GetScreenSize() override;
std::unique_ptr<OpenFileDialog> CreateOpenFileDialog(DisplayWindow* owner) override;
std::unique_ptr<SaveFileDialog> CreateSaveFileDialog(DisplayWindow* owner) override;
std::unique_ptr<OpenFolderDialog> CreateOpenFolderDialog(DisplayWindow* owner) override;
bool IsWin32() override { return true; }
};

View file

@ -1,5 +1,5 @@
#include "win32displaywindow.h"
#include "win32_display_window.h"
#include <windowsx.h>
#include <stdexcept>
#include <cmath>
@ -28,40 +28,12 @@
#define RIDEV_INPUTSINK (0x100)
#endif
static std::string from_utf16(const std::wstring& str)
{
if (str.empty()) return {};
int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr);
if (needed == 0)
throw std::runtime_error("WideCharToMultiByte failed");
std::string result;
result.resize(needed);
needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr);
if (needed == 0)
throw std::runtime_error("WideCharToMultiByte failed");
return result;
}
static std::wstring to_utf16(const std::string& str)
{
if (str.empty()) return {};
int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);
if (needed == 0)
throw std::runtime_error("MultiByteToWideChar failed");
std::wstring result;
result.resize(needed);
needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size());
if (needed == 0)
throw std::runtime_error("MultiByteToWideChar failed");
return result;
}
Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner) : WindowHost(windowHost)
Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner, RenderAPI renderAPI) : WindowHost(windowHost), PopupWindow(popupWindow)
{
Windows.push_front(this);
WindowsIterator = Windows.begin();
WNDCLASSEXW classdesc = {};
WNDCLASSEX classdesc = {};
classdesc.cbSize = sizeof(WNDCLASSEX);
classdesc.hInstance = GetModuleHandle(0);
classdesc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
@ -78,6 +50,7 @@ Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popup
DWORD style = 0, exstyle = 0;
if (popupWindow)
{
exstyle = WS_EX_NOACTIVATE;
style = WS_POPUP;
}
else
@ -85,24 +58,15 @@ Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popup
exstyle = WS_EX_APPWINDOW | WS_EX_DLGMODALFRAME;
style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
}
CreateWindowEx(exstyle, L"ZWidgetWindow", L"", style, 0, 0, 100, 100, owner ? owner->WindowHandle : 0, 0, GetModuleHandle(0), this);
/*
RAWINPUTDEVICE rid;
rid.usUsagePage = HID_USAGE_PAGE_GENERIC;
rid.usUsage = HID_USAGE_GENERIC_MOUSE;
rid.dwFlags = RIDEV_INPUTSINK;
rid.hwndTarget = WindowHandle;
BOOL result = RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE));
*/
CreateWindowEx(exstyle, L"ZWidgetWindow", L"", style, 0, 0, 100, 100, owner ? owner->WindowHandle.hwnd : 0, 0, GetModuleHandle(0), this);
}
Win32DisplayWindow::~Win32DisplayWindow()
{
if (WindowHandle)
if (WindowHandle.hwnd)
{
DestroyWindow(WindowHandle);
WindowHandle = 0;
DestroyWindow(WindowHandle.hwnd);
WindowHandle.hwnd = 0;
}
Windows.erase(WindowsIterator);
@ -110,31 +74,31 @@ Win32DisplayWindow::~Win32DisplayWindow()
void Win32DisplayWindow::SetWindowTitle(const std::string& text)
{
SetWindowText(WindowHandle, to_utf16(text).c_str());
SetWindowText(WindowHandle.hwnd, to_utf16(text).c_str());
}
void Win32DisplayWindow::SetBorderColor(uint32_t bgra8)
{
bgra8 = bgra8 & 0x00ffffff;
DwmSetWindowAttribute(WindowHandle, 34/*DWMWA_BORDER_COLOR*/, &bgra8, sizeof(uint32_t));
DwmSetWindowAttribute(WindowHandle.hwnd, 34/*DWMWA_BORDER_COLOR*/, &bgra8, sizeof(uint32_t));
}
void Win32DisplayWindow::SetCaptionColor(uint32_t bgra8)
{
bgra8 = bgra8 & 0x00ffffff;
DwmSetWindowAttribute(WindowHandle, 35/*DWMWA_CAPTION_COLOR*/, &bgra8, sizeof(uint32_t));
DwmSetWindowAttribute(WindowHandle.hwnd, 35/*DWMWA_CAPTION_COLOR*/, &bgra8, sizeof(uint32_t));
}
void Win32DisplayWindow::SetCaptionTextColor(uint32_t bgra8)
{
bgra8 = bgra8 & 0x00ffffff;
DwmSetWindowAttribute(WindowHandle, 36/*DWMWA_TEXT_COLOR*/, &bgra8, sizeof(uint32_t));
DwmSetWindowAttribute(WindowHandle.hwnd, 36/*DWMWA_TEXT_COLOR*/, &bgra8, sizeof(uint32_t));
}
void Win32DisplayWindow::SetWindowFrame(const Rect& box)
{
double dpiscale = GetDpiScale();
SetWindowPos(WindowHandle, nullptr, (int)std::round(box.x * dpiscale), (int)std::round(box.y * dpiscale), (int)std::round(box.width * dpiscale), (int)std::round(box.height * dpiscale), SWP_NOACTIVATE | SWP_NOZORDER);
SetWindowPos(WindowHandle.hwnd, nullptr, (int)std::round(box.x * dpiscale), (int)std::round(box.y * dpiscale), (int)std::round(box.width * dpiscale), (int)std::round(box.height * dpiscale), SWP_NOACTIVATE | SWP_NOZORDER);
}
void Win32DisplayWindow::SetClientFrame(const Rect& box)
@ -147,16 +111,16 @@ void Win32DisplayWindow::SetClientFrame(const Rect& box)
rect.right = rect.left + (int)std::round(box.width * dpiscale);
rect.bottom = rect.top + (int)std::round(box.height * dpiscale);
DWORD style = (DWORD)GetWindowLongPtr(WindowHandle, GWL_STYLE);
DWORD exstyle = (DWORD)GetWindowLongPtr(WindowHandle, GWL_EXSTYLE);
AdjustWindowRectExForDpi(&rect, style, FALSE, exstyle, GetDpiForWindow(WindowHandle));
DWORD style = (DWORD)GetWindowLongPtr(WindowHandle.hwnd, GWL_STYLE);
DWORD exstyle = (DWORD)GetWindowLongPtr(WindowHandle.hwnd, GWL_EXSTYLE);
AdjustWindowRectExForDpi(&rect, style, FALSE, exstyle, GetDpiForWindow(WindowHandle.hwnd));
SetWindowPos(WindowHandle, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER);
SetWindowPos(WindowHandle.hwnd, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER);
}
void Win32DisplayWindow::Show()
{
ShowWindow(WindowHandle, SW_SHOW);
ShowWindow(WindowHandle.hwnd, PopupWindow ? SW_SHOWNA : SW_SHOW);
}
void Win32DisplayWindow::ShowFullscreen()
@ -165,35 +129,47 @@ void Win32DisplayWindow::ShowFullscreen()
int width = GetDeviceCaps(screenDC, HORZRES);
int height = GetDeviceCaps(screenDC, VERTRES);
ReleaseDC(0, screenDC);
SetWindowLongPtr(WindowHandle, GWL_EXSTYLE, WS_EX_APPWINDOW);
SetWindowLongPtr(WindowHandle, GWL_STYLE, WS_OVERLAPPED);
SetWindowPos(WindowHandle, HWND_TOP, 0, 0, width, height, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
DWORD dwStyle = GetWindowLong(WindowHandle.hwnd, GWL_STYLE);
SetWindowLongPtr(WindowHandle.hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW);
SetWindowLongPtr(WindowHandle.hwnd, GWL_STYLE, dwStyle & ~WS_OVERLAPPEDWINDOW);
SetWindowPos(WindowHandle.hwnd, HWND_TOP, 0, 0, width, height, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
Fullscreen = true;
}
void Win32DisplayWindow::ShowMaximized()
{
ShowWindow(WindowHandle, SW_SHOWMAXIMIZED);
ShowWindow(WindowHandle.hwnd, SW_SHOWMAXIMIZED);
}
void Win32DisplayWindow::ShowMinimized()
{
ShowWindow(WindowHandle, SW_SHOWMINIMIZED);
ShowWindow(WindowHandle.hwnd, SW_SHOWMINIMIZED);
}
void Win32DisplayWindow::ShowNormal()
{
ShowWindow(WindowHandle, SW_NORMAL);
if (Fullscreen)
{
SetWindowLongPtr(WindowHandle.hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
Fullscreen = false;
}
ShowWindow(WindowHandle.hwnd, SW_NORMAL);
}
bool Win32DisplayWindow::IsWindowFullscreen()
{
return Fullscreen;
}
void Win32DisplayWindow::Hide()
{
ShowWindow(WindowHandle, SW_HIDE);
ShowWindow(WindowHandle.hwnd, SW_HIDE);
}
void Win32DisplayWindow::Activate()
{
SetFocus(WindowHandle);
if (!PopupWindow)
SetFocus(WindowHandle.hwnd);
}
void Win32DisplayWindow::ShowCursor(bool enable)
@ -207,6 +183,13 @@ void Win32DisplayWindow::LockCursor()
MouseLocked = true;
GetCursorPos(&MouseLockPos);
::ShowCursor(FALSE);
RAWINPUTDEVICE rid = {};
rid.usUsagePage = HID_USAGE_PAGE_GENERIC;
rid.usUsage = HID_USAGE_GENERIC_MOUSE;
rid.dwFlags = RIDEV_INPUTSINK;
rid.hwndTarget = WindowHandle.hwnd;
RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE));
}
}
@ -214,6 +197,13 @@ void Win32DisplayWindow::UnlockCursor()
{
if (MouseLocked)
{
RAWINPUTDEVICE rid = {};
rid.usUsagePage = HID_USAGE_PAGE_GENERIC;
rid.usUsage = HID_USAGE_GENERIC_MOUSE;
rid.dwFlags = RIDEV_REMOVE;
rid.hwndTarget = 0;
RegisterRawInputDevices(&rid, 1, sizeof(rid));
MouseLocked = false;
SetCursorPos(MouseLockPos.x, MouseLockPos.y);
::ShowCursor(TRUE);
@ -222,7 +212,7 @@ void Win32DisplayWindow::UnlockCursor()
void Win32DisplayWindow::CaptureMouse()
{
SetCapture(WindowHandle);
SetCapture(WindowHandle.hwnd);
}
void Win32DisplayWindow::ReleaseMouseCapture()
@ -232,7 +222,7 @@ void Win32DisplayWindow::ReleaseMouseCapture()
void Win32DisplayWindow::Update()
{
InvalidateRect(WindowHandle, nullptr, FALSE);
InvalidateRect(WindowHandle.hwnd, nullptr, FALSE);
}
bool Win32DisplayWindow::GetKeyState(InputKey key)
@ -252,9 +242,9 @@ void Win32DisplayWindow::SetCursor(StandardCursor cursor)
Rect Win32DisplayWindow::GetWindowFrame() const
{
RECT box = {};
GetWindowRect(WindowHandle, &box);
GetWindowRect(WindowHandle.hwnd, &box);
double dpiscale = GetDpiScale();
return Rect(box.left / dpiscale, box.top / dpiscale, box.right / dpiscale, box.bottom / dpiscale);
return Rect(box.left / dpiscale, box.top / dpiscale, (box.right - box.left) / dpiscale, (box.bottom - box.top) / dpiscale);
}
Point Win32DisplayWindow::MapFromGlobal(const Point& pos) const
@ -263,7 +253,7 @@ Point Win32DisplayWindow::MapFromGlobal(const Point& pos) const
POINT point = {};
point.x = (LONG)std::round(pos.x / dpiscale);
point.y = (LONG)std::round(pos.y / dpiscale);
ScreenToClient(WindowHandle, &point);
ScreenToClient(WindowHandle.hwnd, &point);
return Point(point.x * dpiscale, point.y * dpiscale);
}
@ -273,14 +263,14 @@ Point Win32DisplayWindow::MapToGlobal(const Point& pos) const
POINT point = {};
point.x = (LONG)std::round(pos.x * dpiscale);
point.y = (LONG)std::round(pos.y * dpiscale);
ClientToScreen(WindowHandle, &point);
ClientToScreen(WindowHandle.hwnd, &point);
return Point(point.x / dpiscale, point.y / dpiscale);
}
Size Win32DisplayWindow::GetClientSize() const
{
RECT box = {};
GetClientRect(WindowHandle, &box);
GetClientRect(WindowHandle.hwnd, &box);
double dpiscale = GetDpiScale();
return Size(box.right / dpiscale, box.bottom / dpiscale);
}
@ -288,25 +278,25 @@ Size Win32DisplayWindow::GetClientSize() const
int Win32DisplayWindow::GetPixelWidth() const
{
RECT box = {};
GetClientRect(WindowHandle, &box);
GetClientRect(WindowHandle.hwnd, &box);
return box.right;
}
int Win32DisplayWindow::GetPixelHeight() const
{
RECT box = {};
GetClientRect(WindowHandle, &box);
GetClientRect(WindowHandle.hwnd, &box);
return box.bottom;
}
double Win32DisplayWindow::GetDpiScale() const
{
return GetDpiForWindow(WindowHandle) / 96.0;
return GetDpiForWindow(WindowHandle.hwnd) / 96.0;
}
std::string Win32DisplayWindow::GetClipboardText()
{
BOOL result = OpenClipboard(WindowHandle);
BOOL result = OpenClipboard(WindowHandle.hwnd);
if (result == FALSE)
throw std::runtime_error("Unable to open clipboard");
@ -334,7 +324,7 @@ void Win32DisplayWindow::SetClipboardText(const std::string& text)
{
std::wstring text16 = to_utf16(text);
BOOL result = OpenClipboard(WindowHandle);
BOOL result = OpenClipboard(WindowHandle.hwnd);
if (result == FALSE)
throw std::runtime_error("Unable to open clipboard");
@ -395,14 +385,14 @@ void Win32DisplayWindow::PresentBitmap(int width, int height, const uint32_t* pi
if (dc != 0)
{
int result = SetDIBitsToDevice(dc, 0, 0, width, height, 0, 0, 0, height, pixels, (const BITMAPINFO*)&header, BI_RGB);
ReleaseDC(WindowHandle, dc);
ReleaseDC(WindowHandle.hwnd, dc);
}
}
LRESULT Win32DisplayWindow::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lparam)
{
LPARAM result = 0;
if (DwmDefWindowProc(WindowHandle, msg, wparam, lparam, &result))
if (DwmDefWindowProc(WindowHandle.hwnd, msg, wparam, lparam, &result))
return result;
if (msg == WM_INPUT)
@ -427,16 +417,16 @@ LRESULT Win32DisplayWindow::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lpar
}
}
}
return DefWindowProc(WindowHandle, msg, wparam, lparam);
return DefWindowProc(WindowHandle.hwnd, msg, wparam, lparam);
}
else if (msg == WM_PAINT)
{
PAINTSTRUCT paintStruct = {};
PaintDC = BeginPaint(WindowHandle, &paintStruct);
PaintDC = BeginPaint(WindowHandle.hwnd, &paintStruct);
if (PaintDC)
{
WindowHost->OnWindowPaint();
EndPaint(WindowHandle, &paintStruct);
EndPaint(WindowHandle.hwnd, &paintStruct);
PaintDC = 0;
}
return 0;
@ -445,17 +435,23 @@ LRESULT Win32DisplayWindow::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lpar
{
WindowHost->OnWindowActivated();
}
else if (msg == WM_MOUSEACTIVATE)
{
// We don't want to activate the window on mouse clicks as that changes the focus from the popup owner to the popup itself
if (PopupWindow)
return MA_NOACTIVATE;
}
else if (msg == WM_MOUSEMOVE)
{
if (MouseLocked && GetFocus() != 0)
{
RECT box = {};
GetClientRect(WindowHandle, &box);
GetClientRect(WindowHandle.hwnd, &box);
POINT center = {};
center.x = box.right / 2;
center.y = box.bottom / 2;
ClientToScreen(WindowHandle, &center);
ClientToScreen(WindowHandle.hwnd, &center);
SetCursorPos(center.x, center.y);
}
@ -468,7 +464,7 @@ LRESULT Win32DisplayWindow::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lpar
{
TRACKMOUSEEVENT eventTrack = {};
eventTrack.cbSize = sizeof(TRACKMOUSEEVENT);
eventTrack.hwndTrack = WindowHandle;
eventTrack.hwndTrack = WindowHandle.hwnd;
eventTrack.dwFlags = TME_LEAVE;
if (TrackMouseEvent(&eventTrack))
TrackMouseActive = true;
@ -526,7 +522,7 @@ LRESULT Win32DisplayWindow::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lpar
POINT pos;
pos.x = GET_X_LPARAM(lparam);
pos.y = GET_Y_LPARAM(lparam);
ScreenToClient(WindowHandle, &pos);
ScreenToClient(WindowHandle.hwnd, &pos);
WindowHost->OnWindowMouseWheel(Point(pos.x / dpiscale, pos.y / dpiscale), delta < 0.0 ? InputKey::MouseWheelDown : InputKey::MouseWheelUp);
}
@ -573,7 +569,7 @@ LRESULT Win32DisplayWindow::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lpar
return WVR_REDRAW;
}*/
return DefWindowProc(WindowHandle, msg, wparam, lparam);
return DefWindowProc(WindowHandle.hwnd, msg, wparam, lparam);
}
void Win32DisplayWindow::UpdateCursor()
@ -612,7 +608,7 @@ LRESULT Win32DisplayWindow::WndProc(HWND windowhandle, UINT msg, WPARAM wparam,
{
CREATESTRUCT* createstruct = (CREATESTRUCT*)lparam;
Win32DisplayWindow* viewport = (Win32DisplayWindow*)createstruct->lpCreateParams;
viewport->WindowHandle = windowhandle;
viewport->WindowHandle.hwnd = windowhandle;
SetWindowLongPtr(windowhandle, GWLP_USERDATA, (LONG_PTR)viewport);
return viewport->OnWindowMessage(msg, wparam, lparam);
}
@ -625,7 +621,7 @@ LRESULT Win32DisplayWindow::WndProc(HWND windowhandle, UINT msg, WPARAM wparam,
if (msg == WM_DESTROY)
{
SetWindowLongPtr(windowhandle, GWLP_USERDATA, 0);
viewport->WindowHandle = 0;
viewport->WindowHandle.hwnd = 0;
}
return result;
}
@ -677,6 +673,87 @@ Size Win32DisplayWindow::GetScreenSize()
return Size(screenWidth / dpiScale, screenHeight / dpiScale);
}
// This is to avoid needing all the Vulkan headers and the volk binding library just for this:
#ifndef VK_VERSION_1_0
#define VKAPI_CALL __stdcall
#define VKAPI_PTR VKAPI_CALL
typedef uint32_t VkFlags;
typedef enum VkStructureType { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000, VK_OBJECT_TYPE_MAX_ENUM = 0x7FFFFFFF } VkStructureType;
typedef enum VkResult { VK_SUCCESS = 0, VK_RESULT_MAX_ENUM = 0x7FFFFFFF } VkResult;
typedef struct VkAllocationCallbacks VkAllocationCallbacks;
typedef void (VKAPI_PTR* PFN_vkVoidFunction)(void);
typedef PFN_vkVoidFunction(VKAPI_PTR* PFN_vkGetInstanceProcAddr)(VkInstance instance, const char* pName);
#ifndef VK_KHR_win32_surface
typedef VkFlags VkWin32SurfaceCreateFlagsKHR;
typedef struct VkWin32SurfaceCreateInfoKHR
{
VkStructureType sType;
const void* pNext;
VkWin32SurfaceCreateFlagsKHR flags;
HINSTANCE hinstance;
HWND hwnd;
} VkWin32SurfaceCreateInfoKHR;
typedef VkResult(VKAPI_PTR* PFN_vkCreateWin32SurfaceKHR)(VkInstance instance, const VkWin32SurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
#endif
#endif
class ZWidgetVulkanLoader
{
public:
ZWidgetVulkanLoader()
{
module = LoadLibraryA("vulkan-1.dll");
if (!module)
throw std::runtime_error("Could not load vulkan-1.dll");
vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)(void(*)(void))GetProcAddress(module, "vkGetInstanceProcAddr");
if (!vkGetInstanceProcAddr)
{
FreeLibrary(module);
throw std::runtime_error("vkGetInstanceProcAddr not found in vulkan-1.dll");
}
}
~ZWidgetVulkanLoader()
{
FreeLibrary(module);
}
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr;
HMODULE module = {};
};
VkSurfaceKHR Win32DisplayWindow::CreateVulkanSurface(VkInstance instance)
{
static ZWidgetVulkanLoader loader;
auto vkCreateWin32SurfaceKHR = (PFN_vkCreateWin32SurfaceKHR)loader.vkGetInstanceProcAddr(instance, "vkCreateWin32SurfaceKHR");
if (!vkCreateWin32SurfaceKHR)
throw std::runtime_error("Could not create vulkan surface");
VkWin32SurfaceCreateInfoKHR createInfo = { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR };
createInfo.hwnd = WindowHandle.hwnd;
createInfo.hinstance = GetModuleHandle(nullptr);
VkSurfaceKHR surface = {};
VkResult result = vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface);
if (result != VK_SUCCESS)
throw std::runtime_error("Could not create vulkan surface");
return surface;
}
std::vector<std::string> 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);

View file

@ -1,20 +1,16 @@
#pragma once
#define NOMINMAX
#define WIN32_MEAN_AND_LEAN
#ifndef WINVER
#define WINVER 0x0605
#endif
#include <Windows.h>
#include "win32_util.h"
#include <list>
#include <unordered_map>
#include <zwidget/window/window.h>
#include <zwidget/window/win32nativehandle.h>
class Win32DisplayWindow : public DisplayWindow
{
public:
Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner);
Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner, RenderAPI renderAPI);
~Win32DisplayWindow();
void SetWindowTitle(const std::string& text) override;
@ -25,6 +21,7 @@ public:
void ShowMaximized() override;
void ShowMinimized() override;
void ShowNormal() override;
bool IsWindowFullscreen() override;
void Hide() override;
void Activate() override;
void ShowCursor(bool enable) override;
@ -58,7 +55,10 @@ public:
Point GetLParamPos(LPARAM lparam) const;
void* GetNativeHandle() override { return reinterpret_cast<void*>(WindowHandle); }
void* GetNativeHandle() override { return &WindowHandle; }
std::vector<std::string> GetVulkanInstanceExtensions() override;
VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override;
static void ProcessEvents();
static void RunLoop();
@ -78,8 +78,9 @@ public:
static LRESULT CALLBACK WndProc(HWND windowhandle, UINT msg, WPARAM wparam, LPARAM lparam);
DisplayWindowHost* WindowHost = nullptr;
bool PopupWindow = false;
HWND WindowHandle = 0;
Win32NativeHandle WindowHandle;
bool Fullscreen = false;
bool MouseLocked = false;

View file

@ -0,0 +1,227 @@
#include "win32_open_file_dialog.h"
#include "win32_display_window.h"
#include "core/widget.h"
#include <stdexcept>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
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<IFileOpenDialog> 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<COMDLG_FILTERSPEC> filterspecs(filters.size());
std::vector<std::wstring> descriptions(filters.size());
std::vector<std::wstring> 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<IShellItem> 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<IShellItem> 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<IShellItemArray> 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<IShellItem> 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<std::string> 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);
}

View file

@ -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<std::string> 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<std::string> filenames;
bool multi_select = false;
struct Filter
{
std::string description;
std::string extension;
};
std::vector<Filter> filters;
int filterindex = 0;
std::string defaultext;
};

View file

@ -0,0 +1,111 @@
#include "win32_open_folder_dialog.h"
#include "win32_display_window.h"
#include "core/widget.h"
#include <stdexcept>
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<IFileOpenDialog> 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<IShellItem> 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<IShellItem> 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<IShellItem> 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);
}

View file

@ -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;
};

View file

@ -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<IFileSaveDialog> 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<COMDLG_FILTERSPEC> filterspecs(filters.size());
std::vector<std::wstring> descriptions(filters.size());
std::vector<std::wstring> 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<IShellItem> 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<IShellItem> 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<IShellItem> 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);
}

View file

@ -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<std::string> filenames;
struct Filter
{
std::string description;
std::string extension;
};
std::vector<Filter> filters;
int filterindex = 0;
std::string defaultext;
};

View file

@ -0,0 +1,60 @@
#pragma once
#define NOMINMAX
#define WIN32_MEAN_AND_LEAN
#ifndef WINVER
#define WINVER 0x0605
#endif
#include <Windows.h>
#include <Shlobj.h>
#include <stdexcept>
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<typename T>
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;
};
}

View file

@ -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 <stdexcept>
#ifdef _WIN32
#include "win32/win32displaywindow.h"
std::unique_ptr<DisplayWindow> DisplayWindow::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner)
std::unique_ptr<DisplayWindow> DisplayWindow::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI)
{
return std::make_unique<Win32DisplayWindow>(windowHost, popupWindow, static_cast<Win32DisplayWindow*>(owner));
return DisplayBackend::Get()->Create(windowHost, popupWindow, owner, renderAPI);
}
void DisplayWindow::ProcessEvents()
{
Win32DisplayWindow::ProcessEvents();
DisplayBackend::Get()->ProcessEvents();
}
void DisplayWindow::RunLoop()
{
Win32DisplayWindow::RunLoop();
DisplayBackend::Get()->RunLoop();
}
void DisplayWindow::ExitLoop()
{
Win32DisplayWindow::ExitLoop();
}
Size DisplayWindow::GetScreenSize()
{
return Win32DisplayWindow::GetScreenSize();
DisplayBackend::Get()->ExitLoop();
}
void* DisplayWindow::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)
{
return Win32DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer));
return DisplayBackend::Get()->StartTimer(timeoutMilliseconds, onTimer);
}
void DisplayWindow::StopTimer(void* timerID)
{
Win32DisplayWindow::StopTimer(timerID);
}
#elif defined(__APPLE__)
std::unique_ptr<DisplayWindow> DisplayWindow::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner)
{
throw std::runtime_error("DisplayWindow::Create not implemented");
}
void DisplayWindow::ProcessEvents()
{
throw std::runtime_error("DisplayWindow::ProcessEvents not implemented");
}
void DisplayWindow::RunLoop()
{
throw std::runtime_error("DisplayWindow::RunLoop not implemented");
}
void DisplayWindow::ExitLoop()
{
throw std::runtime_error("DisplayWindow::ExitLoop not implemented");
DisplayBackend::Get()->StopTimer(timerID);
}
Size DisplayWindow::GetScreenSize()
{
throw std::runtime_error("DisplayWindow::GetScreenSize not implemented");
return DisplayBackend::Get()->GetScreenSize();
}
void* DisplayWindow::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)
/////////////////////////////////////////////////////////////////////////////
static std::unique_ptr<DisplayBackend>& 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<DisplayBackend> 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<DisplayBackend> instance)
{
GetBackendVar() = std::move(instance);
}
std::unique_ptr<OpenFileDialog> DisplayBackend::CreateOpenFileDialog(DisplayWindow* owner)
{
return std::make_unique<StubOpenFileDialog>(owner);
}
std::unique_ptr<SaveFileDialog> DisplayBackend::CreateSaveFileDialog(DisplayWindow* owner)
{
return std::make_unique<StubSaveFileDialog>(owner);
}
std::unique_ptr<OpenFolderDialog> DisplayBackend::CreateOpenFolderDialog(DisplayWindow* owner)
{
return std::make_unique<StubOpenFolderDialog>(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> DisplayBackend::TryCreateBackend()
{
std::unique_ptr<DisplayBackend> 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> DisplayBackend::TryCreateWin32()
{
return std::make_unique<Win32DisplayBackend>();
}
#else
#include "sdl2/sdl2displaywindow.h"
std::unique_ptr<DisplayWindow> DisplayWindow::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner)
std::unique_ptr<DisplayBackend> DisplayBackend::TryCreateWin32()
{
return std::make_unique<SDL2DisplayWindow>(windowHost, popupWindow, static_cast<SDL2DisplayWindow*>(owner));
return nullptr;
}
void DisplayWindow::ProcessEvents()
#endif
#ifdef USE_SDL2
#include "sdl2/sdl2_display_backend.h"
std::unique_ptr<DisplayBackend> DisplayBackend::TryCreateSDL2()
{
SDL2DisplayWindow::ProcessEvents();
return std::make_unique<SDL2DisplayBackend>();
}
void DisplayWindow::RunLoop()
#else
std::unique_ptr<DisplayBackend> DisplayBackend::TryCreateSDL2()
{
SDL2DisplayWindow::RunLoop();
return nullptr;
}
#endif
#ifdef USE_X11
void DisplayWindow::ExitLoop()
#include "x11/x11_display_backend.h"
std::unique_ptr<DisplayBackend> DisplayBackend::TryCreateX11()
{
SDL2DisplayWindow::ExitLoop();
try
{
return std::make_unique<X11DisplayBackend>();
}
catch (...)
{
return nullptr;
}
}
#else
Size DisplayWindow::GetScreenSize()
std::unique_ptr<DisplayBackend> DisplayBackend::TryCreateX11()
{
return SDL2DisplayWindow::GetScreenSize();
return nullptr;
}
#endif
void* DisplayWindow::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)
#ifdef USE_WAYLAND
#include "wayland/wayland_display_backend.h"
std::unique_ptr<DisplayBackend> DisplayBackend::TryCreateWayland()
{
return SDL2DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer));
try
{
return std::make_unique<WaylandDisplayBackend>();
}
catch (...)
{
return nullptr;
}
}
#else
void DisplayWindow::StopTimer(void* timerID)
std::unique_ptr<DisplayBackend> DisplayBackend::TryCreateWayland()
{
SDL2DisplayWindow::StopTimer(timerID);
return nullptr;
}
#endif

View file

@ -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<DisplayWindow> X11DisplayBackend::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI)
{
return std::make_unique<X11DisplayWindow>(windowHost, popupWindow, static_cast<X11DisplayWindow*>(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<void()> onTimer)
{
return X11DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer));
}
void X11DisplayBackend::StopTimer(void* timerID)
{
X11DisplayWindow::StopTimer(timerID);
}
#ifdef USE_DBUS
std::unique_ptr<OpenFileDialog> X11DisplayBackend::CreateOpenFileDialog(DisplayWindow* owner)
{
std::string ownerHandle;
if (owner)
ownerHandle = "x11:" + std::to_string(static_cast<X11DisplayWindow*>(owner)->window);
return std::make_unique<DBusOpenFileDialog>(ownerHandle);
}
std::unique_ptr<SaveFileDialog> X11DisplayBackend::CreateSaveFileDialog(DisplayWindow* owner)
{
std::string ownerHandle;
if (owner)
ownerHandle = "x11:" + std::to_string(static_cast<X11DisplayWindow*>(owner)->window);
return std::make_unique<DBusSaveFileDialog>(ownerHandle);
}
std::unique_ptr<OpenFolderDialog> X11DisplayBackend::CreateOpenFolderDialog(DisplayWindow* owner)
{
std::string ownerHandle;
if (owner)
ownerHandle = "x11:" + std::to_string(static_cast<X11DisplayWindow*>(owner)->window);
return std::make_unique<DBusOpenFolderDialog>(ownerHandle);
}
#endif

View file

@ -0,0 +1,25 @@
#pragma once
#include "window/window.h"
class X11DisplayBackend : public DisplayBackend
{
public:
std::unique_ptr<DisplayWindow> 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<void()> onTimer) override;
void StopTimer(void* timerID) override;
Size GetScreenSize() override;
bool IsX11() override { return true; }
#ifdef USE_DBUS
std::unique_ptr<OpenFileDialog> CreateOpenFileDialog(DisplayWindow* owner) override;
std::unique_ptr<SaveFileDialog> CreateSaveFileDialog(DisplayWindow* owner) override;
std::unique_ptr<OpenFolderDialog> CreateOpenFolderDialog(DisplayWindow* owner) override;
#endif
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,137 @@
#pragma once
#include <zwidget/window/window.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/cursorfont.h>
#include <X11/keysymdef.h>
#include <X11/XKBlib.h>
#include <map>
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<std::string> 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<void()> 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<uint8_t> GetWindowProperty(Atom property, Atom &actual_type, int &actual_format, unsigned long &item_count);
DisplayWindowHost* windowHost = nullptr;
X11DisplayWindow* owner = nullptr;
Display* display = nullptr;
Window window = {};
int screen = 0;
int depth = 0;
Visual* visual = nullptr;
Colormap colormap = {};
XIC xic = nullptr;
StandardCursor cursor = {};
bool isCursorEnabled = true;
bool isMapped = false;
bool isMinimized = false;
bool isFullscreen = false;
double dpiScale = 1.0;
Pixmap cursor_bitmap = None;
Cursor hidden_cursor = None;
std::map<InputKey, bool> 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;
};

View file

@ -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<void()> 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();
}
}

View file

@ -0,0 +1,41 @@
#pragma once
#include <chrono>
#include <functional>
// ZTimer: A small, independent timer
// Useful for implementing timed events on your backends
class ZTimer
{
public:
using Duration = std::chrono::duration<double, std::milli>;
using Clock = std::chrono::system_clock;
using TimePoint = std::chrono::time_point<Clock, Duration>;
using CallbackFunc = std::function<void()>;
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;
};

View file

@ -3901,6 +3901,15 @@ int D_DoomMain_Game()
int GameMain()
{
// On Windows, prefer the native win32 backend.
// On other platforms, use SDL until the other backends are more mature.
auto zwidget = DisplayBackend::TryCreateWin32();
if (!zwidget)
zwidget = DisplayBackend::TryCreateSDL2();
if (!zwidget)
return -1;
DisplayBackend::Set(std::move(zwidget));
int ret = 0;
GameTicRate = TICRATE;
I_InitTime();