Update to latest ZWidget version

This adds new features (such as themes) alongside fixing numerous bugs. This should be kept up-to-date with upstream more often and changes to it should be PR'd back to its main repo.
This commit is contained in:
Boondorl 2025-06-14 08:58:13 -04:00 committed by Ricardo Luís Vaz Silva
commit 885c1d2920
101 changed files with 10156 additions and 2204 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
@ -8,6 +24,7 @@ set(ZWIDGET_SOURCES
src/core/span_layout.cpp
src/core/timer.cpp
src/core/widget.cpp
src/core/theme.cpp
src/core/utf8reader.cpp
src/core/pathfill.cpp
src/core/truetypefont.cpp
@ -32,6 +49,17 @@ set(ZWIDGET_SOURCES
src/widgets/listview/listview.cpp
src/widgets/tabwidget/tabwidget.cpp
src/window/window.cpp
src/window/stub/stub_open_folder_dialog.cpp
src/window/stub/stub_open_folder_dialog.h
src/window/stub/stub_open_file_dialog.cpp
src/window/stub/stub_open_file_dialog.h
src/window/stub/stub_save_file_dialog.cpp
src/window/stub/stub_save_file_dialog.h
src/window/ztimer/ztimer.h
src/window/ztimer/ztimer.cpp
src/systemdialogs/open_folder_dialog.cpp
src/systemdialogs/open_file_dialog.cpp
src/systemdialogs/save_file_dialog.cpp
)
set(ZWIDGET_INCLUDES
@ -44,6 +72,7 @@ set(ZWIDGET_INCLUDES
include/zwidget/core/span_layout.h
include/zwidget/core/timer.h
include/zwidget/core/widget.h
include/zwidget/core/theme.h
include/zwidget/core/utf8reader.h
include/zwidget/core/resourcedata.h
include/zwidget/widgets/lineedit/lineedit.h
@ -61,19 +90,62 @@ set(ZWIDGET_INCLUDES
include/zwidget/widgets/listview/listview.h
include/zwidget/widgets/tabwidget/tabwidget.h
include/zwidget/window/window.h
include/zwidget/window/x11nativehandle.h
include/zwidget/window/waylandnativehandle.h
include/zwidget/window/win32nativehandle.h
include/zwidget/window/sdl2nativehandle.h
include/zwidget/systemdialogs/open_folder_dialog.h
include/zwidget/systemdialogs/open_file_dialog.h
include/zwidget/systemdialogs/save_file_dialog.h
)
set(ZWIDGET_WIN32_SOURCES
src/window/win32/win32window.cpp
src/window/win32/win32window.h
src/window/win32/win32_display_backend.cpp
src/window/win32/win32_display_backend.h
src/window/win32/win32_display_window.cpp
src/window/win32/win32_display_window.h
src/window/win32/win32_open_folder_dialog.cpp
src/window/win32/win32_open_folder_dialog.h
src/window/win32/win32_open_file_dialog.cpp
src/window/win32/win32_open_file_dialog.h
src/window/win32/win32_save_file_dialog.cpp
src/window/win32/win32_save_file_dialog.h
src/window/win32/win32_util.h
)
set(ZWIDGET_DBUS_SOURCES
src/window/dbus/dbus_open_folder_dialog.cpp
src/window/dbus/dbus_open_folder_dialog.h
src/window/dbus/dbus_open_file_dialog.cpp
src/window/dbus/dbus_open_file_dialog.h
src/window/dbus/dbus_save_file_dialog.cpp
src/window/dbus/dbus_save_file_dialog.h
)
set(ZWIDGET_COCOA_SOURCES
)
set(ZWIDGET_SDL2_SOURCES
src/window/sdl2/sdl2displaywindow.cpp
src/window/sdl2/sdl2displaywindow.h
src/window/sdl2/sdl2_display_backend.cpp
src/window/sdl2/sdl2_display_backend.h
src/window/sdl2/sdl2_display_window.cpp
src/window/sdl2/sdl2_display_window.h
)
set(ZWIDGET_X11_SOURCES
src/window/x11/x11_display_backend.cpp
src/window/x11/x11_display_backend.h
src/window/x11/x11_display_window.cpp
src/window/x11/x11_display_window.h
)
set(ZWIDGET_WAYLAND_SOURCES
src/window/wayland/wayland_display_backend.cpp
src/window/wayland/wayland_display_backend.h
src/window/wayland/wayland_display_window.cpp
src/window/wayland/wayland_display_window.h
src/window/wayland/wl_fractional_scaling_protocol.cpp
src/window/wayland/wl_fractional_scaling_protocol.hpp
)
source_group("src" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/.+")
@ -95,6 +167,13 @@ source_group("src\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_S
source_group("src\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/listview/.+")
source_group("src\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/tabwidget/.+")
source_group("src\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/.+")
source_group("src\\window\\stub" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/stub/.+")
source_group("src\\window\\win32" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/win32/.+")
source_group("src\\window\\sdl2" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/sdl2/.+")
source_group("src\\window\\x11" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/x11/.+")
source_group("src\\window\\wayland" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/wayland/.+")
source_group("src\\window\\dbus" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/dbus/.+")
source_group("src\\systemdialogs" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/systemdialogs/.+")
source_group("include" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/.+")
source_group("include\\core" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/core/.+")
source_group("include\\widgets" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/.+")
@ -112,42 +191,62 @@ source_group("include\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRE
source_group("include\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/listview/.+")
source_group("include\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/tabwidget/.+")
source_group("include\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/.+")
source_group("include\\window\\win32" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/win32/.+")
source_group("include\\window\\sdl2" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/sdl2/.+")
source_group("include\\systemdialogs" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/systemdialogs/.+")
include_directories(include include/zwidget src)
# Include directory to external projects using zwidget
include_directories(include)
# Internal include dirs for building zwidget
set(ZWIDGET_INCLUDE_DIRS include/zwidget src)
set(ZWIDGET_COMPILE_OPTIONS)
if(WIN32)
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_WIN32_SOURCES})
add_definitions(-DUNICODE -D_UNICODE)
if(MINGW)
add_definitions(-DMINGW)
set(ZWIDGET_DEFINES -DUNICODE -D_UNICODE)
set(ZWIDGET_LINK_OPTIONS)
if(MSVC)
# Use all cores for compilation
set(ZWIDGET_COMPILE_OPTIONS ${ZWIDGET_COMPILE_OPTIONS} /MP)
# Ignore specific warnings
#set(ZWIDGET_COMPILE_OPTIONS ${ZWIDGET_COMPILE_OPTIONS} /wd4244 /wd4267 /wd4005 /wd4018)
# Don't slow down std containers in debug builds
set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES})
# Ignore warning about legacy CRT functions
#set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES} -D_CRT_SECURE_NO_WARNINGS)
endif()
elseif(APPLE)
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_COCOA_SOURCES})
set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl)
add_definitions(-DUNIX -D_UNIX)
add_link_options(-pthread)
set(ZWIDGET_DEFINES -DUNIX -D_UNIX)
set(ZWIDGET_LINK_OPTIONS -pthread)
else()
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_SDL2_SOURCES})
if(NOT HAIKU)
set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl)
else()
set(ZWIDGET_LIBS ${CMAKE_DL_LIBS})
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_SDL2_SOURCES} ${ZWIDGET_X11_SOURCES})
set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl -lX11)
set(ZWIDGET_DEFINES -DUNIX -D_UNIX -DUSE_SDL2 -DUSE_X11)
set(ZWIDGET_LINK_OPTIONS -pthread)
if (DBUS_FOUND)
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_DBUS_SOURCES})
set(ZWIDGET_INCLUDE_DIRS ${ZWIDGET_INCLUDE_DIRS} ${DBUS_INCLUDE_DIRS})
set(ZWIDGET_LIBS ${ZWIDGET_LIBS} ${DBUS_LDFLAGS})
set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES} -DUSE_DBUS)
endif()
if (WAYLANDPP_FOUND)
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_WAYLAND_SOURCES})
set(ZWIDGET_INCLUDE_DIRS ${ZWIDGET_INCLUDE_DIRS} ${WAYLANDPP_INCLUDE_DIRS})
set(ZWIDGET_LIBS ${ZWIDGET_LIBS} ${WAYLANDPP_LDFLAGS})
set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES} -DUSE_WAYLAND)
endif()
add_definitions(-DUNIX -D_UNIX)
add_link_options(-pthread)
endif()
if(MSVC)
# Use all cores for compilation
set(CMAKE_CXX_FLAGS "/MP ${CMAKE_CXX_FLAGS}")
# Ignore warnings in third party code
#set_source_files_properties(${ZWIDGET_SOURCES} PROPERTIES COMPILE_FLAGS "/wd4244 /wd4267 /wd4005 /wd4018 -D_CRT_SECURE_NO_WARNINGS")
endif()
add_library(zwidget STATIC ${ZWIDGET_SOURCES} ${ZWIDGET_INCLUDES})
target_compile_options(zwidget PRIVATE ${ZWIDGET_COMPILE_OPTIONS})
target_compile_definitions(zwidget PRIVATE ${ZWIDGET_DEFINES})
target_include_directories(zwidget PRIVATE ${ZWIDGET_INCLUDE_DIRS})
target_link_options(zwidget PRIVATE ${ZWIDGET_LINK_OPTIONS})
target_link_libraries(zwidget ${ZWIDGET_LIBS})
set_target_properties(zwidget PROPERTIES CXX_STANDARD 17)

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

@ -0,0 +1,81 @@
#pragma once
#include <memory>
#include <string>
#include <variant>
#include <unordered_map>
#include "rect.h"
#include "colorf.h"
class Widget;
class Canvas;
class WidgetStyle
{
public:
WidgetStyle(WidgetStyle* parentStyle = nullptr) : ParentStyle(parentStyle) { }
virtual ~WidgetStyle() = default;
virtual void Paint(Widget* widget, Canvas* canvas, Size size) = 0;
void SetBool(const std::string& state, const std::string& propertyName, bool value);
void SetInt(const std::string& state, const std::string& propertyName, int value);
void SetDouble(const std::string& state, const std::string& propertyName, double value);
void SetString(const std::string& state, const std::string& propertyName, const std::string& value);
void SetColor(const std::string& state, const std::string& propertyName, const Colorf& value);
void SetBool(const std::string& propertyName, bool value) { SetBool(std::string(), propertyName, value); }
void SetInt(const std::string& propertyName, int value) { SetInt(std::string(), propertyName, value); }
void SetDouble(const std::string& propertyName, double value) { SetDouble(std::string(), propertyName, value); }
void SetString(const std::string& propertyName, const std::string& value) { SetString(std::string(), propertyName, value); }
void SetColor(const std::string& propertyName, const Colorf& value) { SetColor(std::string(), propertyName, value); }
private:
// Note: do not call these directly. Use widget->GetStyleXX instead since a widget may explicitly override a class style
bool GetBool(const std::string& state, const std::string& propertyName) const;
int GetInt(const std::string& state, const std::string& propertyName) const;
double GetDouble(const std::string& state, const std::string& propertyName) const;
std::string GetString(const std::string& state, const std::string& propertyName) const;
Colorf GetColor(const std::string& state, const std::string& propertyName) const;
WidgetStyle* ParentStyle = nullptr;
typedef std::variant<bool, int, double, std::string, Colorf> PropertyVariant;
std::unordered_map<std::string, std::unordered_map<std::string, PropertyVariant>> StyleProperties;
const PropertyVariant* FindProperty(const std::string& state, const std::string& propertyName) const;
friend class Widget;
};
class BasicWidgetStyle : public WidgetStyle
{
public:
using WidgetStyle::WidgetStyle;
void Paint(Widget* widget, Canvas* canvas, Size size) override;
};
class WidgetTheme
{
public:
virtual ~WidgetTheme() = default;
WidgetStyle* RegisterStyle(std::unique_ptr<WidgetStyle> widgetStyle, const std::string& widgetClass);
WidgetStyle* GetStyle(const std::string& widgetClass);
static void SetTheme(std::unique_ptr<WidgetTheme> theme);
static WidgetTheme* GetTheme();
private:
std::unordered_map<std::string, std::unique_ptr<WidgetStyle>> Styles;
};
class DarkWidgetTheme : public WidgetTheme
{
public:
DarkWidgetTheme();
};
class LightWidgetTheme : public WidgetTheme
{
public:
LightWidgetTheme();
};

View file

@ -2,6 +2,8 @@
#include <string>
#include <memory>
#include <variant>
#include <unordered_map>
#include "canvas.h"
#include "rect.h"
#include "colorf.h"
@ -20,7 +22,7 @@ enum class WidgetType
class Widget : DisplayWindowHost
{
public:
Widget(Widget* parent = nullptr, WidgetType type = WidgetType::Child);
Widget(Widget* parent = nullptr, WidgetType type = WidgetType::Child, RenderAPI api = RenderAPI::Unspecified);
virtual ~Widget();
void SetParent(Widget* parent);
@ -39,16 +41,45 @@ public:
// Widget noncontent area
void SetNoncontentSizes(double left, double top, double right, double bottom);
double GetNoncontentLeft() const { return Noncontent.Left; }
double GetNoncontentTop() const { return Noncontent.Top; }
double GetNoncontentRight() const { return Noncontent.Right; }
double GetNoncontentBottom() const { return Noncontent.Bottom; }
double GetNoncontentLeft() const { return GridFitSize(GetStyleDouble("noncontent-left")); }
double GetNoncontentTop() const { return GridFitSize(GetStyleDouble("noncontent-top")); }
double GetNoncontentRight() const { return GridFitSize(GetStyleDouble("noncontent-right")); }
double GetNoncontentBottom() const { return GridFitSize(GetStyleDouble("noncontent-bottom")); }
// Get the DPI scale factor for the window the widget is located on
double GetDpiScale() const;
// Align point to the nearest physical screen pixel
double GridFitPoint(double p) const;
Point GridFitPoint(double x, double y) const { return GridFitPoint(Point(x, y)); }
Point GridFitPoint(Point p) const { return Point(GridFitPoint(p.x), GridFitPoint(p.y)); }
// Convert size to exactly covering physical screen pixels
double GridFitSize(double s) const;
Size GridFitSize(double w, double h) const { return GridFitSize(Size(w, h)); }
Size GridFitSize(Size s) const { return Size(GridFitSize(s.width), GridFitSize(s.height)); }
// Widget frame box
Rect GetFrameGeometry() const;
void SetFrameGeometry(const Rect& geometry);
void SetFrameGeometry(double x, double y, double width, double height) { SetFrameGeometry(Rect::xywh(x, y, width, height)); }
// Style properties
void SetStyleClass(const std::string& styleClass);
const std::string& GetStyleClass() const { return StyleClass; }
void SetStyleState(const std::string& state);
const std::string& GetStyleState() const { return StyleState; }
void SetStyleBool(const std::string& propertyName, bool value);
void SetStyleInt(const std::string& propertyName, int value);
void SetStyleDouble(const std::string& propertyName, double value);
void SetStyleString(const std::string& propertyName, const std::string& value);
void SetStyleColor(const std::string& propertyName, const Colorf& value);
bool GetStyleBool(const std::string& propertyName) const;
int GetStyleInt(const std::string& propertyName) const;
double GetStyleDouble(const std::string& propertyName) const;
std::string GetStyleString(const std::string& propertyName) const;
Colorf GetStyleColor(const std::string& propertyName) const;
void SetWindowBackground(const Colorf& color);
void SetWindowBorderColor(const Colorf& color);
void SetWindowCaptionColor(const Colorf& color);
@ -73,6 +104,7 @@ public:
bool IsEnabled();
bool IsVisible();
bool IsHidden();
bool IsFullscreen();
void SetFocus();
void SetEnabled(bool value);
@ -82,19 +114,26 @@ public:
void LockCursor();
void UnlockCursor();
void SetCursor(StandardCursor cursor);
void CaptureMouse();
void ReleaseMouseCapture();
bool GetKeyState(EInputKey key);
void SetPointerCapture();
void ReleasePointerCapture();
void SetModalCapture();
void ReleaseModalCapture();
bool GetKeyState(InputKey key);
std::string GetClipboardText();
void SetClipboardText(const std::string& text);
Widget* Window();
Widget* Window() const;
Canvas* GetCanvas() const;
Widget* ChildAt(double x, double y) { return ChildAt(Point(x, y)); }
Widget* ChildAt(const Point& pos);
bool IsParent(const Widget* w) const;
bool IsChild(const Widget* w) const;
Widget* Parent() const { return ParentObj; }
Widget* PrevSibling() const { return PrevSiblingObj; }
Widget* NextSibling() const { return NextSiblingObj; }
@ -111,19 +150,28 @@ public:
static Size GetScreenSize();
void SetCanvas(std::unique_ptr<Canvas> 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) { }
virtual void OnPaintFrame(Canvas* canvas);
virtual void OnPaint(Canvas* canvas) { }
virtual bool OnMouseDown(const Point& pos, int key) { return false; }
virtual bool OnMouseDoubleclick(const Point& pos, int key) { return false; }
virtual bool OnMouseUp(const Point& pos, int key) { return false; }
virtual bool OnMouseWheel(const Point& pos, EInputKey key) { return false; }
virtual bool OnMouseDown(const Point& pos, InputKey key) { return false; }
virtual bool OnMouseDoubleclick(const Point& pos, InputKey key) { return false; }
virtual bool OnMouseUp(const Point& pos, InputKey key) { return false; }
virtual bool OnMouseWheel(const Point& pos, InputKey key) { return false; }
virtual void OnMouseMove(const Point& pos) { }
virtual void OnMouseLeave() { }
virtual void OnRawMouseMove(int dx, int dy) { }
virtual void OnKeyChar(std::string chars) { }
virtual void OnKeyDown(EInputKey key) { }
virtual void OnKeyUp(EInputKey key) { }
virtual void OnKeyDown(InputKey key) { }
virtual void OnKeyUp(InputKey key) { }
virtual void OnGeometryChanged() { }
virtual void OnClose() { delete this; }
virtual void OnSetFocus() { }
@ -138,14 +186,15 @@ private:
// DisplayWindowHost
void OnWindowPaint() override;
void OnWindowMouseMove(const Point& pos) override;
void OnWindowMouseDown(const Point& pos, EInputKey key) override;
void OnWindowMouseDoubleclick(const Point& pos, EInputKey key) override;
void OnWindowMouseUp(const Point& pos, EInputKey key) override;
void OnWindowMouseWheel(const Point& pos, EInputKey key) override;
void OnWindowMouseLeave() override;
void OnWindowMouseDown(const Point& pos, InputKey key) override;
void OnWindowMouseDoubleclick(const Point& pos, InputKey key) override;
void OnWindowMouseUp(const Point& pos, InputKey key) override;
void OnWindowMouseWheel(const Point& pos, InputKey key) override;
void OnWindowRawMouseMove(int dx, int dy) override;
void OnWindowKeyChar(std::string chars) override;
void OnWindowKeyDown(EInputKey key) override;
void OnWindowKeyUp(EInputKey key) override;
void OnWindowKeyDown(InputKey key) override;
void OnWindowKeyUp(InputKey key) override;
void OnWindowGeometryChanged() override;
void OnWindowClose() override;
void OnWindowActivated() override;
@ -167,14 +216,6 @@ private:
Colorf WindowBackground = Colorf::fromRgba8(240, 240, 240);
struct
{
double Left = 0.0;
double Top = 0.0;
double Right = 0.0;
double Bottom = 0.0;
} Noncontent;
std::string WindowTitle;
std::unique_ptr<DisplayWindow> DispWindow;
std::unique_ptr<Canvas> DispCanvas;
@ -185,8 +226,16 @@ private:
StandardCursor CurrentCursor = StandardCursor::arrow;
std::string StyleClass = "widget";
std::string StyleState;
typedef std::variant<bool, int, double, std::string, Colorf> PropertyVariant;
std::unordered_map<std::string, PropertyVariant> StyleProperties;
Widget(const Widget&) = delete;
Widget& operator=(const Widget&) = delete;
friend class Timer;
friend class OpenFileDialog;
friend class OpenFolderDialog;
friend class SaveFileDialog;
};

View file

@ -0,0 +1,55 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
class Widget;
/// \brief Displays the system open file dialog.
class OpenFileDialog
{
public:
/// \brief Constructs an open file dialog.
static std::unique_ptr<OpenFileDialog> Create(Widget* owner);
virtual ~OpenFileDialog() = default;
/// \brief Get the full path of the file selected.
///
/// If multiple files are selected, this returns the first file.
virtual std::string Filename() const = 0;
/// \brief Gets an array that contains one file name for each selected file.
virtual std::vector<std::string> Filenames() const = 0;
/// \brief Sets if multiple files can be selected or not.
/// \param multiselect = When true, multiple items can be selected.
virtual void SetMultiSelect(bool multiselect) = 0;
/// \brief Sets a string containing the full path of the file selected.
virtual void SetFilename(const std::string &filename) = 0;
/// \brief Sets the default extension to use.
virtual void SetDefaultExtension(const std::string& extension) = 0;
/// \brief Add a filter that determines what types of files are displayed.
virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0;
/// \brief Clears all filters.
virtual void ClearFilters() = 0;
/// \brief Sets a default filter, on a 0-based index.
virtual void SetFilterIndex(int filter_index) = 0;
/// \brief Sets the initial directory that is displayed.
virtual void SetInitialDirectory(const std::string &path) = 0;
/// \brief Sets the text that appears in the title bar.
virtual void SetTitle(const std::string &title) = 0;
/// \brief Shows the file dialog.
/// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise.
virtual bool Show() = 0;
};

View file

@ -0,0 +1,30 @@
#pragma once
#include <memory>
#include <string>
class Widget;
/// \brief Displays the system folder browsing dialog
class OpenFolderDialog
{
public:
/// \brief Constructs an browse folder dialog.
static std::unique_ptr<OpenFolderDialog> Create(Widget*owner);
virtual ~OpenFolderDialog() = default;
/// \brief Get the full path of the directory selected.
virtual std::string SelectedPath() const = 0;
/// \brief Sets the initial directory that is displayed.
virtual void SetInitialDirectory(const std::string& path) = 0;
/// \brief Sets the text that appears in the title bar.
virtual void SetTitle(const std::string& title) = 0;
/// \brief Shows the file dialog.
/// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise.
virtual bool Show() = 0;
};

View file

@ -0,0 +1,46 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
class Widget;
/// \brief Displays the system save file dialog.
class SaveFileDialog
{
public:
/// \brief Constructs a save file dialog.
static std::unique_ptr<SaveFileDialog> Create(Widget *owner);
virtual ~SaveFileDialog() = default;
/// \brief Get the full path of the file selected.
virtual std::string Filename() const = 0;
/// \brief Sets a string containing the full path of the file selected.
virtual void SetFilename(const std::string &filename) = 0;
/// \brief Sets the default extension to use.
virtual void SetDefaultExtension(const std::string& extension) = 0;
/// \brief Add a filter that determines what types of files are displayed.
virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0;
/// \brief Clears all filters.
virtual void ClearFilters() = 0;
/// \brief Sets a default filter, on a 0-based index.
virtual void SetFilterIndex(int filter_index) = 0;
/// \brief Sets the initial directory that is displayed.
virtual void SetInitialDirectory(const std::string &path) = 0;
/// \brief Sets the text that appears in the title bar.
virtual void SetTitle(const std::string &title) = 0;
/// \brief Shows the file dialog.
/// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise.
virtual bool Show() = 0;
};

View file

@ -21,15 +21,14 @@ public:
protected:
void OnPaint(Canvas* canvas) override;
bool OnMouseDown(const Point& pos, int key) override;
bool OnMouseUp(const Point& pos, int key) override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnMouseLeave() override;
void OnKeyUp(EInputKey key) override;
void OnKeyUp(InputKey key) override;
private:
std::string text;
bool checked = false;
bool radiostyle = false;
bool mouseDownActive = false;
};

View file

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

View file

@ -59,7 +59,7 @@ public:
void SetInputMask(const std::string& mask);
void SetDecimalCharacter(const std::string& decimal_char);
std::function<bool(int key)> FuncIgnoreKeyDown;
std::function<bool(InputKey key)> FuncIgnoreKeyDown;
std::function<std::string(std::string text)> FuncFilterKeyChar;
std::function<void()> FuncBeforeEditChanged;
std::function<void()> FuncAfterEditChanged;
@ -69,15 +69,14 @@ public:
std::function<void()> FuncEnterPressed;
protected:
void OnPaintFrame(Canvas* canvas) override;
void OnPaint(Canvas* canvas) override;
void OnMouseMove(const Point& pos) override;
bool OnMouseDown(const Point& pos, int key) override;
bool OnMouseDoubleclick(const Point& pos, int key) override;
bool OnMouseUp(const Point& pos, int key) override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseDoubleclick(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnKeyChar(std::string chars) override;
void OnKeyDown(EInputKey key) override;
void OnKeyUp(EInputKey key) override;
void OnKeyDown(InputKey key) override;
void OnKeyUp(InputKey key) override;
void OnGeometryChanged() override;
void OnEnableChanged() override;
void OnSetFocus() override;

View file

@ -29,11 +29,10 @@ public:
protected:
void OnPaint(Canvas* canvas) override;
void OnPaintFrame(Canvas* canvas) override;
bool OnMouseDown(const Point& pos, int key) override;
bool OnMouseDoubleclick(const Point& pos, int key) override;
bool OnMouseWheel(const Point& pos, EInputKey key) override;
void OnKeyDown(EInputKey key) override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseDoubleclick(const Point& pos, InputKey key) override;
bool OnMouseWheel(const Point& pos, InputKey key) override;
void OnKeyDown(InputKey key) override;
void OnGeometryChanged() override;
void OnScrollbarScroll();

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

@ -2,6 +2,14 @@
#pragma once
#include "../../core/widget.h"
#include "../textlabel/textlabel.h"
#include "../imagebox/imagebox.h"
#include <vector>
class Menu;
class MenubarItem;
class MenuItem;
class MenuItemSeparator;
class Menubar : public Widget
{
@ -9,6 +17,105 @@ public:
Menubar(Widget* parent);
~Menubar();
MenubarItem* AddItem(std::string text, std::function<void(Menu* menu)> onOpen, bool alignRight = false);
protected:
void OnGeometryChanged() override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnMouseMove(const Point& pos) override;
void OnKeyDown(InputKey key) override;
private:
void ShowMenu(MenubarItem* item);
void CloseMenu();
MenubarItem* GetMenubarItemAt(const Point& pos);
int GetItemIndex(MenubarItem* item);
std::vector<MenubarItem*> menuItems;
int currentMenubarItem = -1;
Menu* openMenu = nullptr;
bool modalMode = false;
friend class MenubarItem;
};
class MenubarItem : public Widget
{
public:
MenubarItem(Menubar* menubar, std::string text, bool alignRight);
void SetOpenCallback(std::function<void(Menu* menu)> callback) { onOpen = std::move(callback); }
const std::function<void(Menu* menu)>& GetOpenCallback() const { return onOpen; }
double GetPreferredWidth() const;
bool AlignRight = false;
protected:
void OnPaint(Canvas* canvas) override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnMouseMove(const Point& pos) override;
void OnMouseLeave() override;
private:
Menubar* menubar = nullptr;
std::function<void(Menu* menu)> onOpen;
std::string text;
};
class Menu : public Widget
{
public:
Menu(Widget* parent);
void SetLeftPosition(const Point& globalPos);
void SetRightPosition(const Point& globalPos);
MenuItem* AddItem(std::shared_ptr<Image> icon, std::string text, std::function<void()> onClick = {});
MenuItemSeparator* AddSeparator();
double GetPreferredWidth() const;
double GetPreferredHeight() const;
void SetSelected(MenuItem* item);
protected:
void OnGeometryChanged() override;
std::function<void()> onCloseMenu;
MenuItem* selectedItem = nullptr;
friend class Menubar;
};
class MenuItem : public Widget
{
public:
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

@ -19,17 +19,14 @@ public:
std::function<void()> OnClick;
protected:
void OnPaintFrame(Canvas* canvas) override;
void OnPaint(Canvas* canvas) override;
bool OnMouseDown(const Point& pos, int key) override;
bool OnMouseUp(const Point& pos, int key) override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnMouseMove(const Point& pos) override;
void OnMouseLeave() override;
void OnKeyDown(EInputKey key) override;
void OnKeyUp(EInputKey key) override;
void OnKeyDown(InputKey key) override;
void OnKeyUp(InputKey key) override;
private:
std::string text;
bool buttonDown = false;
bool hot = false;
};

View file

@ -47,8 +47,8 @@ public:
std::function<void()> FuncScrollEnd;
protected:
bool OnMouseDown(const Point& pos, int key) override;
bool OnMouseUp(const Point& pos, int key) override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnMouseMove(const Point& pos) override;
void OnMouseLeave() override;
void OnPaint(Canvas* canvas) override;

View file

@ -2,9 +2,9 @@
#pragma once
#include "../../core/widget.h"
#include <vector>
#include <functional>
#include <string>
#include <vector>
class TabBar;
class TabBarTab;
@ -37,7 +37,6 @@ public:
std::function<void()> OnCurrentChanged;
protected:
void OnPaintFrame(Canvas* canvas) override;
void OnGeometryChanged() override;
private:
@ -67,7 +66,6 @@ public:
std::function<void()> OnCurrentChanged;
protected:
void OnPaintFrame(Canvas* canvas) override;
void OnGeometryChanged() override;
private:
@ -92,10 +90,9 @@ public:
std::function<void()> OnClick;
protected:
void OnPaintFrame(Canvas* canvas) override;
void OnGeometryChanged() override;
bool OnMouseDown(const Point& pos, int key) override;
bool OnMouseUp(const Point& pos, int key) override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnMouseMove(const Point& pos) override;
void OnMouseLeave() override;
@ -116,7 +113,6 @@ public:
Widget* GetCurrentWidget() const { return CurrentWidget; }
protected:
void OnPaintFrame(Canvas* canvas) override;
void OnGeometryChanged() override;
private:

View file

@ -53,15 +53,14 @@ public:
std::function<void()> FuncEnterPressed;
protected:
void OnPaintFrame(Canvas* canvas) override;
void OnPaint(Canvas* canvas) override;
void OnMouseMove(const Point& pos) override;
bool OnMouseDown(const Point& pos, int key) override;
bool OnMouseDoubleclick(const Point& pos, int key) override;
bool OnMouseUp(const Point& pos, int key) override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseDoubleclick(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnKeyChar(std::string chars) override;
void OnKeyDown(EInputKey key) override;
void OnKeyUp(EInputKey key) override;
void OnKeyDown(InputKey key) override;
void OnKeyUp(InputKey key) override;
void OnGeometryChanged() override;
void OnEnableChanged() override;
void OnSetFocus() override;

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

@ -1,12 +1,31 @@
#pragma once
#include <cstdint>
#include <memory>
#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
{
@ -25,91 +44,83 @@ enum class StandardCursor
wait
};
enum EInputKey
enum class InputKey : uint32_t
{
IK_None, IK_LeftMouse, IK_RightMouse, IK_Cancel,
IK_MiddleMouse, IK_Unknown05, IK_Unknown06, IK_Unknown07,
IK_Backspace, IK_Tab, IK_Unknown0A, IK_Unknown0B,
IK_Unknown0C, IK_Enter, IK_Unknown0E, IK_Unknown0F,
IK_Shift, IK_Ctrl, IK_Alt, IK_Pause,
IK_CapsLock, IK_Unknown15, IK_Unknown16, IK_Unknown17,
IK_Unknown18, IK_Unknown19, IK_Unknown1A, IK_Escape,
IK_Unknown1C, IK_Unknown1D, IK_Unknown1E, IK_Unknown1F,
IK_Space, IK_PageUp, IK_PageDown, IK_End,
IK_Home, IK_Left, IK_Up, IK_Right,
IK_Down, IK_Select, IK_Print, IK_Execute,
IK_PrintScrn, IK_Insert, IK_Delete, IK_Help,
IK_0, IK_1, IK_2, IK_3,
IK_4, IK_5, IK_6, IK_7,
IK_8, IK_9, IK_Unknown3A, IK_Unknown3B,
IK_Unknown3C, IK_Unknown3D, IK_Unknown3E, IK_Unknown3F,
IK_Unknown40, IK_A, IK_B, IK_C,
IK_D, IK_E, IK_F, IK_G,
IK_H, IK_I, IK_J, IK_K,
IK_L, IK_M, IK_N, IK_O,
IK_P, IK_Q, IK_R, IK_S,
IK_T, IK_U, IK_V, IK_W,
IK_X, IK_Y, IK_Z, IK_Unknown5B,
IK_Unknown5C, IK_Unknown5D, IK_Unknown5E, IK_Unknown5F,
IK_NumPad0, IK_NumPad1, IK_NumPad2, IK_NumPad3,
IK_NumPad4, IK_NumPad5, IK_NumPad6, IK_NumPad7,
IK_NumPad8, IK_NumPad9, IK_GreyStar, IK_GreyPlus,
IK_Separator, IK_GreyMinus, IK_NumPadPeriod, IK_GreySlash,
IK_F1, IK_F2, IK_F3, IK_F4,
IK_F5, IK_F6, IK_F7, IK_F8,
IK_F9, IK_F10, IK_F11, IK_F12,
IK_F13, IK_F14, IK_F15, IK_F16,
IK_F17, IK_F18, IK_F19, IK_F20,
IK_F21, IK_F22, IK_F23, IK_F24,
IK_Unknown88, IK_Unknown89, IK_Unknown8A, IK_Unknown8B,
IK_Unknown8C, IK_Unknown8D, IK_Unknown8E, IK_Unknown8F,
IK_NumLock, IK_ScrollLock, IK_Unknown92, IK_Unknown93,
IK_Unknown94, IK_Unknown95, IK_Unknown96, IK_Unknown97,
IK_Unknown98, IK_Unknown99, IK_Unknown9A, IK_Unknown9B,
IK_Unknown9C, IK_Unknown9D, IK_Unknown9E, IK_Unknown9F,
IK_LShift, IK_RShift, IK_LControl, IK_RControl,
IK_UnknownA4, IK_UnknownA5, IK_UnknownA6, IK_UnknownA7,
IK_UnknownA8, IK_UnknownA9, IK_UnknownAA, IK_UnknownAB,
IK_UnknownAC, IK_UnknownAD, IK_UnknownAE, IK_UnknownAF,
IK_UnknownB0, IK_UnknownB1, IK_UnknownB2, IK_UnknownB3,
IK_UnknownB4, IK_UnknownB5, IK_UnknownB6, IK_UnknownB7,
IK_UnknownB8, IK_UnknownB9, IK_Semicolon, IK_Equals,
IK_Comma, IK_Minus, IK_Period, IK_Slash,
IK_Tilde, IK_UnknownC1, IK_UnknownC2, IK_UnknownC3,
IK_UnknownC4, IK_UnknownC5, IK_UnknownC6, IK_UnknownC7,
IK_Joy1, IK_Joy2, IK_Joy3, IK_Joy4,
IK_Joy5, IK_Joy6, IK_Joy7, IK_Joy8,
IK_Joy9, IK_Joy10, IK_Joy11, IK_Joy12,
IK_Joy13, IK_Joy14, IK_Joy15, IK_Joy16,
IK_UnknownD8, IK_UnknownD9, IK_UnknownDA, IK_LeftBracket,
IK_Backslash, IK_RightBracket, IK_SingleQuote, IK_UnknownDF,
IK_JoyX, IK_JoyY, IK_JoyZ, IK_JoyR,
IK_MouseX, IK_MouseY, IK_MouseZ, IK_MouseW,
IK_JoyU, IK_JoyV, IK_UnknownEA, IK_UnknownEB,
IK_MouseWheelUp, IK_MouseWheelDown, IK_Unknown10E, IK_Unknown10F,
IK_JoyPovUp, IK_JoyPovDown, IK_JoyPovLeft, IK_JoyPovRight,
IK_UnknownF4, IK_UnknownF5, IK_Attn, IK_CrSel,
IK_ExSel, IK_ErEof, IK_Play, IK_Zoom,
IK_NoName, IK_PA1, IK_OEMClear
None, LeftMouse, RightMouse, Cancel,
MiddleMouse, Unknown05, Unknown06, Unknown07,
Backspace, Tab, Unknown0A, Unknown0B,
Unknown0C, Enter, Unknown0E, Unknown0F,
Shift, Ctrl, Alt, Pause,
CapsLock, Unknown15, Unknown16, Unknown17,
Unknown18, Unknown19, Unknown1A, Escape,
Unknown1C, Unknown1D, Unknown1E, Unknown1F,
Space, PageUp, PageDown, End,
Home, Left, Up, Right,
Down, Select, Print, Execute,
PrintScrn, Insert, Delete, Help,
_0, _1, _2, _3,
_4, _5, _6, _7,
_8, _9, Unknown3A, Unknown3B,
Unknown3C, Unknown3D, Unknown3E, Unknown3F,
Unknown40, A, B, C,
D, E, F, G,
H, I, J, K,
L, M, N, O,
P, Q, R, S,
T, U, V, W,
X, Y, Z, Unknown5B,
Unknown5C, Unknown5D, Unknown5E, Unknown5F,
NumPad0, NumPad1, NumPad2, NumPad3,
NumPad4, NumPad5, NumPad6, NumPad7,
NumPad8, NumPad9, GreyStar, GreyPlus,
Separator, GreyMinus, NumPadPeriod, GreySlash,
F1, F2, F3, F4,
F5, F6, F7, F8,
F9, F10, F11, F12,
F13, F14, F15, F16,
F17, F18, F19, F20,
F21, F22, F23, F24,
Unknown88, Unknown89, Unknown8A, Unknown8B,
Unknown8C, Unknown8D, Unknown8E, Unknown8F,
NumLock, ScrollLock, Unknown92, Unknown93,
Unknown94, Unknown95, Unknown96, Unknown97,
Unknown98, Unknown99, Unknown9A, Unknown9B,
Unknown9C, Unknown9D, Unknown9E, Unknown9F,
LShift, RShift, LControl, RControl,
UnknownA4, UnknownA5, UnknownA6, UnknownA7,
UnknownA8, UnknownA9, UnknownAA, UnknownAB,
UnknownAC, UnknownAD, UnknownAE, UnknownAF,
UnknownB0, UnknownB1, UnknownB2, UnknownB3,
UnknownB4, UnknownB5, UnknownB6, UnknownB7,
UnknownB8, UnknownB9, Semicolon, Equals,
Comma, Minus, Period, Slash,
Tilde, UnknownC1, UnknownC2, UnknownC3,
UnknownC4, UnknownC5, UnknownC6, UnknownC7,
Joy1, Joy2, Joy3, Joy4,
Joy5, Joy6, Joy7, Joy8,
Joy9, Joy10, Joy11, Joy12,
Joy13, Joy14, Joy15, Joy16,
UnknownD8, UnknownD9, UnknownDA, LeftBracket,
Backslash, RightBracket, SingleQuote, UnknownDF,
JoyX, JoyY, JoyZ, JoyR,
MouseX, MouseY, MouseZ, MouseW,
JoyU, JoyV, UnknownEA, UnknownEB,
MouseWheelUp, MouseWheelDown, Unknown10E, Unknown10F,
JoyPovUp, JoyPovDown, JoyPovLeft, JoyPovRight,
UnknownF4, UnknownF5, Attn, CrSel,
ExSel, ErEof, Play, Zoom,
NoName, PA1, OEMClear
};
enum EInputType
enum class RenderAPI
{
IST_None,
IST_Press,
IST_Hold,
IST_Release,
IST_Axis
};
class KeyEvent
{
public:
EInputKey Key;
bool Ctrl = false;
bool Alt = false;
bool Shift = false;
Point MousePos = Point(0.0, 0.0);
Unspecified,
Bitmap,
Vulkan,
OpenGL,
D3D11,
D3D12,
Metal
};
class DisplayWindow;
@ -119,14 +130,15 @@ class DisplayWindowHost
public:
virtual void OnWindowPaint() = 0;
virtual void OnWindowMouseMove(const Point& pos) = 0;
virtual void OnWindowMouseDown(const Point& pos, EInputKey key) = 0;
virtual void OnWindowMouseDoubleclick(const Point& pos, EInputKey key) = 0;
virtual void OnWindowMouseUp(const Point& pos, EInputKey key) = 0;
virtual void OnWindowMouseWheel(const Point& pos, EInputKey key) = 0;
virtual void OnWindowMouseLeave() = 0;
virtual void OnWindowMouseDown(const Point& pos, InputKey key) = 0;
virtual void OnWindowMouseDoubleclick(const Point& pos, InputKey key) = 0;
virtual void OnWindowMouseUp(const Point& pos, InputKey key) = 0;
virtual void OnWindowMouseWheel(const Point& pos, InputKey key) = 0;
virtual void OnWindowRawMouseMove(int dx, int dy) = 0;
virtual void OnWindowKeyChar(std::string chars) = 0;
virtual void OnWindowKeyDown(EInputKey key) = 0;
virtual void OnWindowKeyUp(EInputKey key) = 0;
virtual void OnWindowKeyDown(InputKey key) = 0;
virtual void OnWindowKeyUp(InputKey key) = 0;
virtual void OnWindowGeometryChanged() = 0;
virtual void OnWindowClose() = 0;
virtual void OnWindowActivated() = 0;
@ -137,7 +149,7 @@ public:
class DisplayWindow
{
public:
static std::unique_ptr<DisplayWindow> Create(DisplayWindowHost* windowHost);
static std::unique_ptr<DisplayWindow> Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI);
static void ProcessEvents();
static void RunLoop();
@ -158,6 +170,7 @@ public:
virtual void ShowMaximized() = 0;
virtual void ShowMinimized() = 0;
virtual void ShowNormal() = 0;
virtual bool IsWindowFullscreen() = 0;
virtual void Hide() = 0;
virtual void Activate() = 0;
virtual void ShowCursor(bool enable) = 0;
@ -166,7 +179,7 @@ public:
virtual void CaptureMouse() = 0;
virtual void ReleaseMouseCapture() = 0;
virtual void Update() = 0;
virtual bool GetKeyState(EInputKey key) = 0;
virtual bool GetKeyState(InputKey key) = 0;
virtual void SetCursor(StandardCursor cursor) = 0;
@ -176,6 +189,9 @@ public:
virtual int GetPixelHeight() const = 0;
virtual double GetDpiScale() const = 0;
virtual Point MapFromGlobal(const Point& pos) const = 0;
virtual Point MapToGlobal(const Point& pos) const = 0;
virtual void SetBorderColor(uint32_t bgra8) = 0;
virtual void SetCaptionColor(uint32_t bgra8) = 0;
virtual void SetCaptionTextColor(uint32_t bgra8) = 0;
@ -184,4 +200,44 @@ public:
virtual std::string GetClipboardText() = 0;
virtual void SetClipboardText(const std::string& text) = 0;
virtual void* GetNativeHandle() = 0;
virtual std::vector<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;
};

File diff suppressed because it is too large Load diff

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

@ -0,0 +1,383 @@
#include "core/theme.h"
#include "core/widget.h"
#include "core/canvas.h"
void WidgetStyle::SetBool(const std::string& state, const std::string& propertyName, bool value)
{
StyleProperties[state][propertyName] = value;
}
void WidgetStyle::SetInt(const std::string& state, const std::string& propertyName, int value)
{
StyleProperties[state][propertyName] = value;
}
void WidgetStyle::SetDouble(const std::string& state, const std::string& propertyName, double value)
{
StyleProperties[state][propertyName] = value;
}
void WidgetStyle::SetString(const std::string& state, const std::string& propertyName, const std::string& value)
{
StyleProperties[state][propertyName] = value;
}
void WidgetStyle::SetColor(const std::string& state, const std::string& propertyName, const Colorf& value)
{
StyleProperties[state][propertyName] = value;
}
const WidgetStyle::PropertyVariant* WidgetStyle::FindProperty(const std::string& state, const std::string& propertyName) const
{
const WidgetStyle* style = this;
do
{
// Look for property in the specific state
auto stateIt = style->StyleProperties.find(state);
if (stateIt != style->StyleProperties.end())
{
auto it = stateIt->second.find(propertyName);
if (it != stateIt->second.end())
return &it->second;
}
// Fall back to the widget main style
if (state != std::string())
{
stateIt = style->StyleProperties.find(std::string());
if (stateIt != style->StyleProperties.end())
{
auto it = stateIt->second.find(propertyName);
if (it != stateIt->second.end())
return &it->second;
}
}
style = style->ParentStyle;
} while (style);
return nullptr;
}
bool WidgetStyle::GetBool(const std::string& state, const std::string& propertyName) const
{
const PropertyVariant* prop = FindProperty(state, propertyName);
return prop ? std::get<bool>(*prop) : false;
}
int WidgetStyle::GetInt(const std::string& state, const std::string& propertyName) const
{
const PropertyVariant* prop = FindProperty(state, propertyName);
return prop ? std::get<int>(*prop) : 0;
}
double WidgetStyle::GetDouble(const std::string& state, const std::string& propertyName) const
{
const PropertyVariant* prop = FindProperty(state, propertyName);
return prop ? std::get<double>(*prop) : 0.0;
}
std::string WidgetStyle::GetString(const std::string& state, const std::string& propertyName) const
{
const PropertyVariant* prop = FindProperty(state, propertyName);
return prop ? std::get<std::string>(*prop) : std::string();
}
Colorf WidgetStyle::GetColor(const std::string& state, const std::string& propertyName) const
{
const PropertyVariant* prop = FindProperty(state, propertyName);
return prop ? std::get<Colorf>(*prop) : Colorf::transparent();
}
/////////////////////////////////////////////////////////////////////////////
void BasicWidgetStyle::Paint(Widget* widget, Canvas* canvas, Size size)
{
Colorf bgcolor = widget->GetStyleColor("background-color");
if (bgcolor.a > 0.0f)
canvas->fillRect(Rect::xywh(0.0, 0.0, size.width, size.height), bgcolor);
Colorf borderleft = widget->GetStyleColor("border-left-color");
Colorf bordertop = widget->GetStyleColor("border-top-color");
Colorf borderright = widget->GetStyleColor("border-right-color");
Colorf borderbottom = widget->GetStyleColor("border-bottom-color");
double borderwidth = widget->GridFitSize(1.0);
if (bordertop.a > 0.0f)
canvas->fillRect(Rect::xywh(0.0, 0.0, size.width, borderwidth), bordertop);
if (borderbottom.a > 0.0f)
canvas->fillRect(Rect::xywh(0.0, size.height - borderwidth, size.width, borderwidth), borderbottom);
if (borderleft.a > 0.0f)
canvas->fillRect(Rect::xywh(0.0, 0.0, borderwidth, size.height), borderleft);
if (borderright.a > 0.0f)
canvas->fillRect(Rect::xywh(size.width - borderwidth, 0.0, borderwidth, size.height), borderright);
}
/////////////////////////////////////////////////////////////////////////////
static std::unique_ptr<WidgetTheme> CurrentTheme;
WidgetStyle* WidgetTheme::RegisterStyle(std::unique_ptr<WidgetStyle> widgetStyle, const std::string& widgetClass)
{
auto& style = Styles[widgetClass];
style = std::move(widgetStyle);
return style.get();
}
WidgetStyle* WidgetTheme::GetStyle(const std::string& widgetClass)
{
auto it = Styles.find(widgetClass);
return it != Styles.end() ? it->second.get() : nullptr;
}
void WidgetTheme::SetTheme(std::unique_ptr<WidgetTheme> theme)
{
CurrentTheme = std::move(theme);
}
WidgetTheme* WidgetTheme::GetTheme()
{
return CurrentTheme.get();
}
/////////////////////////////////////////////////////////////////////////////
DarkWidgetTheme::DarkWidgetTheme()
{
auto widget = RegisterStyle(std::make_unique<BasicWidgetStyle>(), "widget");
auto textlabel = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textlabel");
auto pushbutton = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "pushbutton");
auto lineedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "lineedit");
auto textedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textedit");
auto listview = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "listview");
auto scrollbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "scrollbar");
auto tabbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar");
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));
widget->SetColor("window-background", Colorf::fromRgba8(51, 51, 51));
widget->SetColor("window-border", Colorf::fromRgba8(51, 51, 51));
widget->SetColor("window-caption-color", Colorf::fromRgba8(33, 33, 33));
widget->SetColor("window-caption-text-color", Colorf::fromRgba8(226, 223, 219));
pushbutton->SetDouble("noncontent-left", 10.0);
pushbutton->SetDouble("noncontent-top", 5.0);
pushbutton->SetDouble("noncontent-right", 10.0);
pushbutton->SetDouble("noncontent-bottom", 5.0);
pushbutton->SetColor("background-color", Colorf::fromRgba8(68, 68, 68));
pushbutton->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100));
pushbutton->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100));
pushbutton->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100));
pushbutton->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
pushbutton->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78));
pushbutton->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88));
lineedit->SetDouble("noncontent-left", 5.0);
lineedit->SetDouble("noncontent-top", 3.0);
lineedit->SetDouble("noncontent-right", 5.0);
lineedit->SetDouble("noncontent-bottom", 3.0);
lineedit->SetColor("background-color", Colorf::fromRgba8(38, 38, 38));
lineedit->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100));
lineedit->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100));
lineedit->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100));
lineedit->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
lineedit->SetColor("selection-color", Colorf::fromRgba8(100, 100, 100));
lineedit->SetColor("no-focus-selection-color", Colorf::fromRgba8(68, 68, 68));
textedit->SetDouble("noncontent-left", 8.0);
textedit->SetDouble("noncontent-top", 8.0);
textedit->SetDouble("noncontent-right", 8.0);
textedit->SetDouble("noncontent-bottom", 8.0);
textedit->SetColor("background-color", Colorf::fromRgba8(38, 38, 38));
textedit->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100));
textedit->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100));
textedit->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100));
textedit->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
listview->SetDouble("noncontent-left", 10.0);
listview->SetDouble("noncontent-top", 10.0);
listview->SetDouble("noncontent-right", 3.0);
listview->SetDouble("noncontent-bottom", 10.0);
listview->SetColor("background-color", Colorf::fromRgba8(38, 38, 38));
listview->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100));
listview->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100));
listview->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100));
listview->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
listview->SetColor("selection-color", Colorf::fromRgba8(100, 100, 100));
scrollbar->SetColor("track-color", Colorf::fromRgba8(33, 33, 33));
scrollbar->SetColor("thumb-color", Colorf::fromRgba8(58, 58, 58));
tabbar->SetDouble("noncontent-left", 20.0);
tabbar->SetDouble("noncontent-right", 20.0);
tabbar->SetColor("background-color", Colorf::fromRgba8(38, 38, 38));
tabbar_tab->SetDouble("noncontent-left", 15.0);
tabbar_tab->SetDouble("noncontent-right", 15.0);
tabbar_tab->SetColor("hover", "background-color", Colorf::fromRgba8(45, 45, 45));
tabbar_tab->SetColor("active", "background-color", Colorf::fromRgba8(51, 51, 51));
tabwidget_stack->SetDouble("noncontent-left", 20.0);
tabwidget_stack->SetDouble("noncontent-top", 5.0);
tabwidget_stack->SetDouble("noncontent-right", 20.0);
tabwidget_stack->SetDouble("noncontent-bottom", 5.0);
checkbox_label->SetColor("checked-outer-border-color", Colorf::fromRgba8(100, 100, 100));
checkbox_label->SetColor("checked-inner-border-color", Colorf::fromRgba8(51, 51, 51));
checkbox_label->SetColor("checked-color", Colorf::fromRgba8(226, 223, 219));
checkbox_label->SetColor("unchecked-outer-border-color", Colorf::fromRgba8(99, 99, 99));
checkbox_label->SetColor("unchecked-inner-border-color", Colorf::fromRgba8(51, 51, 51));
menubar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33));
toolbar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33));
statusbar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33));
toolbarbutton->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78));
toolbarbutton->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88));
menubaritem->SetColor("color", Colorf::fromRgba8(226, 223, 219));
menubaritem->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78));
menubaritem->SetColor("hover", "color", Colorf::fromRgba8(0, 0, 0));
menubaritem->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88));
menubaritem->SetColor("down", "color", Colorf::fromRgba8(0, 0, 0));
menu->SetDouble("noncontent-left", 5.0);
menu->SetDouble("noncontent-top", 5.0);
menu->SetDouble("noncontent-right", 5.0);
menu->SetDouble("noncontent-bottom", 5.0);
menu->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100));
menu->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100));
menu->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100));
menu->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
menuitem->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78));
menuitem->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88));
}
/////////////////////////////////////////////////////////////////////////////
LightWidgetTheme::LightWidgetTheme()
{
auto widget = RegisterStyle(std::make_unique<BasicWidgetStyle>(), "widget");
auto textlabel = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textlabel");
auto pushbutton = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "pushbutton");
auto lineedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "lineedit");
auto textedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textedit");
auto listview = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "listview");
auto scrollbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "scrollbar");
auto tabbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar");
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");
widget->SetString("font-family", "NotoSans");
widget->SetColor("color", Colorf::fromRgba8(0, 0, 0));
widget->SetColor("window-background", Colorf::fromRgba8(240, 240, 240));
widget->SetColor("window-border", Colorf::fromRgba8(100, 100, 100));
widget->SetColor("window-caption-color", Colorf::fromRgba8(70, 70, 70));
widget->SetColor("window-caption-text-color", Colorf::fromRgba8(226, 223, 219));
pushbutton->SetDouble("noncontent-left", 10.0);
pushbutton->SetDouble("noncontent-top", 5.0);
pushbutton->SetDouble("noncontent-right", 10.0);
pushbutton->SetDouble("noncontent-bottom", 5.0);
pushbutton->SetColor("background-color", Colorf::fromRgba8(210, 210, 210));
pushbutton->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155));
pushbutton->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155));
pushbutton->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155));
pushbutton->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155));
pushbutton->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200));
pushbutton->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190));
lineedit->SetDouble("noncontent-left", 5.0);
lineedit->SetDouble("noncontent-top", 3.0);
lineedit->SetDouble("noncontent-right", 5.0);
lineedit->SetDouble("noncontent-bottom", 3.0);
lineedit->SetColor("background-color", Colorf::fromRgba8(255, 255, 255));
lineedit->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155));
lineedit->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155));
lineedit->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155));
lineedit->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155));
lineedit->SetColor("selection-color", Colorf::fromRgba8(210, 210, 255));
lineedit->SetColor("no-focus-selection-color", Colorf::fromRgba8(240, 240, 255));
textedit->SetDouble("noncontent-left", 8.0);
textedit->SetDouble("noncontent-top", 8.0);
textedit->SetDouble("noncontent-right", 8.0);
textedit->SetDouble("noncontent-bottom", 8.0);
textedit->SetColor("background-color", Colorf::fromRgba8(255, 255, 255));
textedit->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155));
textedit->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155));
textedit->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155));
textedit->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155));
listview->SetDouble("noncontent-left", 10.0);
listview->SetDouble("noncontent-top", 10.0);
listview->SetDouble("noncontent-right", 3.0);
listview->SetDouble("noncontent-bottom", 10.0);
listview->SetColor("background-color", Colorf::fromRgba8(230, 230, 230));
listview->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155));
listview->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155));
listview->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155));
listview->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155));
listview->SetColor("selection-color", Colorf::fromRgba8(200, 200, 200));
scrollbar->SetColor("track-color", Colorf::fromRgba8(210, 210, 220));
scrollbar->SetColor("thumb-color", Colorf::fromRgba8(180, 180, 180));
tabbar->SetDouble("noncontent-left", 20.0);
tabbar->SetDouble("noncontent-right", 20.0);
tabbar->SetColor("background-color", Colorf::fromRgba8(220, 220, 220));
tabbar_tab->SetDouble("noncontent-left", 15.0);
tabbar_tab->SetDouble("noncontent-right", 15.0);
tabbar_tab->SetColor("hover", "background-color", Colorf::fromRgba8(210, 210, 210));
tabbar_tab->SetColor("active", "background-color", Colorf::fromRgba8(240, 240, 240));
tabwidget_stack->SetDouble("noncontent-left", 20.0);
tabwidget_stack->SetDouble("noncontent-top", 5.0);
tabwidget_stack->SetDouble("noncontent-right", 20.0);
tabwidget_stack->SetDouble("noncontent-bottom", 5.0);
checkbox_label->SetColor("checked-outer-border-color", Colorf::fromRgba8(155, 155, 155));
checkbox_label->SetColor("checked-inner-border-color", Colorf::fromRgba8(200, 200, 200));
checkbox_label->SetColor("checked-color", Colorf::fromRgba8(50, 50, 50));
checkbox_label->SetColor("unchecked-outer-border-color", Colorf::fromRgba8(156, 156, 156));
checkbox_label->SetColor("unchecked-inner-border-color", Colorf::fromRgba8(200, 200, 200));
menubar->SetColor("background-color", Colorf::fromRgba8(70, 70, 70));
menubaritem->SetColor("color", Colorf::fromRgba8(226, 223, 219));
menubaritem->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200));
menubaritem->SetColor("hover", "color", Colorf::fromRgba8(0, 0, 0));
menubaritem->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190));
menubaritem->SetColor("down", "color", Colorf::fromRgba8(0, 0, 0));
menu->SetDouble("noncontent-left", 5.0);
menu->SetDouble("noncontent-top", 5.0);
menu->SetDouble("noncontent-right", 5.0);
menu->SetDouble("noncontent-bottom", 5.0);
menu->SetColor("background-color", Colorf::fromRgba8(255, 255, 255));
menu->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155));
menu->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155));
menu->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155));
menu->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155));
menuitem->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200));
menuitem->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190));
}

View file

@ -13,6 +13,8 @@ Timer::Timer(Widget* owner) : OwnerObj(owner)
Timer::~Timer()
{
Stop();
if (PrevTimerObj)
PrevTimerObj->NextTimerObj = NextTimerObj;
if (NextTimerObj)

View file

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

View file

@ -445,7 +445,7 @@ public:
class TrueTypeFontFileData
{
public:
TrueTypeFontFileData(std::vector<uint8_t>& data) : dataVector(std::move(data))
TrueTypeFontFileData(std::vector<uint8_t> data) : dataVector(std::move(data))
{
dataPtr = dataVector.data();
dataSize = dataVector.size();
@ -491,7 +491,7 @@ private:
class TrueTypeFont
{
public:
TrueTypeFont(std::shared_ptr<TrueTypeFontFileData>& data, int ttcFontIndex = 0);
TrueTypeFont(std::shared_ptr<TrueTypeFontFileData> data, int ttcFontIndex = 0);
static std::vector<TTCFontName> GetFontNames(const std::shared_ptr<TrueTypeFontFileData>& data);

View file

@ -2,14 +2,31 @@
#include "core/widget.h"
#include "core/timer.h"
#include "core/colorf.h"
#include "core/theme.h"
#include <stdexcept>
#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)
{
DispWindow = DisplayWindow::Create(this);
DispCanvas = Canvas::create(DispWindow.get());
Widget* owner = parent ? parent->Window() : nullptr;
DispWindow = DisplayWindow::Create(this, type == WidgetType::Popup, owner ? owner->DispWindow.get() : nullptr, renderAPI);
if (renderAPI == RenderAPI::Unspecified || renderAPI == RenderAPI::Bitmap)
{
DispCanvas = Canvas::create();
DispCanvas->attach(DispWindow.get());
}
SetStyleState("root");
SetWindowBackground(GetStyleColor("window-background"));
if (GetStyleColor("window-border").a > 0.0f)
SetWindowBorderColor(GetStyleColor("window-border"));
if (GetStyleColor("window-caption-color").a > 0.0f)
SetWindowCaptionColor(GetStyleColor("window-caption-color"));
if (GetStyleColor("window-caption-text-color").a > 0.0f)
SetWindowCaptionTextColor(GetStyleColor("window-caption-text-color"));
}
SetParent(parent);
@ -17,6 +34,9 @@ Widget::Widget(Widget* parent, WidgetType type) : Type(type)
Widget::~Widget()
{
if (DispCanvas)
DispCanvas->detach();
while (LastChildObj)
delete LastChildObj;
@ -26,6 +46,17 @@ Widget::~Widget()
DetachFromParent();
}
void Widget::SetCanvas(std::unique_ptr<Canvas> canvas)
{
if (DispWindow)
{
if (DispCanvas)
DispCanvas->detach();
DispCanvas = std::move(canvas);
DispCanvas->attach(DispWindow.get());
}
}
void Widget::SetParent(Widget* newParent)
{
if (ParentObj != newParent)
@ -138,10 +169,10 @@ Rect Widget::GetFrameGeometry() const
void Widget::SetNoncontentSizes(double left, double top, double right, double bottom)
{
Noncontent.Left = left;
Noncontent.Top = top;
Noncontent.Right = right;
Noncontent.Bottom = bottom;
SetStyleDouble("noncontent-left", left);
SetStyleDouble("noncontent-top", top);
SetStyleDouble("noncontent-right", right);
SetStyleDouble("noncontent-bottom", bottom);
}
void Widget::SetFrameGeometry(const Rect& geometry)
@ -149,14 +180,18 @@ void Widget::SetFrameGeometry(const Rect& geometry)
if (Type == WidgetType::Child)
{
FrameGeometry = geometry;
double left = FrameGeometry.left() + Noncontent.Left;
double top = FrameGeometry.top() + Noncontent.Top;
double right = FrameGeometry.right() - Noncontent.Right;
double bottom = FrameGeometry.bottom() - Noncontent.Bottom;
double left = FrameGeometry.left() + GetNoncontentLeft();
double top = FrameGeometry.top() + GetNoncontentTop();
double right = FrameGeometry.right() - GetNoncontentRight();
double bottom = FrameGeometry.bottom() - GetNoncontentBottom();
left = std::min(left, FrameGeometry.right());
top = std::min(top, FrameGeometry.bottom());
right = std::max(right, FrameGeometry.left());
bottom = std::max(bottom, FrameGeometry.top());
left = GridFitPoint(left);
top = GridFitPoint(top);
right = GridFitPoint(right);
bottom = GridFitPoint(bottom);
ContentGeometry = Rect::ltrb(left, top, right, bottom);
OnGeometryChanged();
}
@ -187,6 +222,15 @@ void Widget::ShowFullscreen()
}
}
bool Widget::IsFullscreen()
{
if (Type != WidgetType::Child)
{
return DispWindow->IsWindowFullscreen();
}
return false;
}
void Widget::ShowMaximized()
{
if (Type != WidgetType::Child)
@ -290,9 +334,12 @@ void Widget::Update()
void Widget::Repaint()
{
Widget* w = Window();
w->DispCanvas->begin(WindowBackground);
w->Paint(w->DispCanvas.get());
w->DispCanvas->end();
if (w->DispCanvas)
{
w->DispCanvas->begin(WindowBackground);
w->Paint(w->DispCanvas.get());
w->DispCanvas->end();
}
}
void Widget::Paint(Canvas* canvas)
@ -316,7 +363,16 @@ void Widget::Paint(Canvas* canvas)
canvas->popClip();
}
bool Widget::GetKeyState(EInputKey key)
void Widget::OnPaintFrame(Canvas* canvas)
{
WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass);
if (style)
{
style->Paint(this, canvas, GetFrameGeometry().size());
}
}
bool Widget::GetKeyState(InputKey key)
{
Widget* window = Window();
return window ? window->DispWindow->GetKeyState(key) : false;
@ -403,7 +459,7 @@ void Widget::SetCursor(StandardCursor cursor)
}
}
void Widget::CaptureMouse()
void Widget::SetPointerCapture()
{
Widget* w = Window();
if (w && w->CaptureWidget != this)
@ -413,7 +469,7 @@ void Widget::CaptureMouse()
}
}
void Widget::ReleaseMouseCapture()
void Widget::ReleasePointerCapture()
{
Widget* w = Window();
if (w && w->CaptureWidget != nullptr)
@ -423,6 +479,24 @@ void Widget::ReleaseMouseCapture()
}
}
void Widget::SetModalCapture()
{
Widget* w = Window();
if (w && w->CaptureWidget != this)
{
w->CaptureWidget = this;
}
}
void Widget::ReleaseModalCapture()
{
Widget* w = Window();
if (w && w->CaptureWidget != nullptr)
{
w->CaptureWidget = nullptr;
}
}
std::string Widget::GetClipboardText()
{
Widget* w = Window();
@ -439,12 +513,12 @@ void Widget::SetClipboardText(const std::string& text)
w->DispWindow->SetClipboardText(text);
}
Widget* Widget::Window()
Widget* Widget::Window() const
{
for (Widget* w = this; w != nullptr; w = w->Parent())
for (const Widget* w = this; w != nullptr; w = w->Parent())
{
if (w->DispWindow)
return w;
return const_cast<Widget*>(w);
}
return nullptr;
}
@ -459,11 +533,29 @@ Canvas* Widget::GetCanvas() const
return nullptr;
}
bool Widget::IsParent(const Widget* w) const
{
while (w)
{
w = w->Parent();
if (w == this)
return true;
}
return false;
}
bool Widget::IsChild(const Widget* w) const
{
if (!w)
return false;
return w->IsParent(this);
}
Widget* Widget::ChildAt(const Point& pos)
{
for (Widget* cur = LastChild(); cur != nullptr; cur = cur->PrevSibling())
{
if (!cur->HiddenFlag && cur->FrameGeometry.contains(pos))
if (cur->Type == WidgetType::Child && !cur->HiddenFlag && cur->FrameGeometry.contains(pos))
{
Widget* cur2 = cur->ChildAt(pos - cur->ContentGeometry.topLeft());
return cur2 ? cur2 : cur;
@ -491,7 +583,7 @@ Point Widget::MapFromGlobal(const Point& pos) const
{
if (cur->DispWindow)
{
return p - cur->GetFrameGeometry().topLeft();
return cur->DispWindow->MapFromGlobal(p);
}
p -= cur->ContentGeometry.topLeft();
}
@ -517,7 +609,7 @@ Point Widget::MapToGlobal(const Point& pos) const
{
if (cur->DispWindow)
{
return cur->GetFrameGeometry().topLeft() + p;
return cur->DispWindow->MapToGlobal(p);
}
p += cur->ContentGeometry.topLeft();
}
@ -570,7 +662,19 @@ void Widget::OnWindowMouseMove(const Point& pos)
}
}
void Widget::OnWindowMouseDown(const Point& pos, EInputKey key)
void Widget::OnWindowMouseLeave()
{
if (HoverWidget)
{
for (Widget* w = HoverWidget; w; w = w->Parent())
{
w->OnMouseLeave();
}
HoverWidget = nullptr;
}
}
void Widget::OnWindowMouseDown(const Point& pos, InputKey key)
{
if (CaptureWidget)
{
@ -591,7 +695,7 @@ void Widget::OnWindowMouseDown(const Point& pos, EInputKey key)
}
}
void Widget::OnWindowMouseDoubleclick(const Point& pos, EInputKey key)
void Widget::OnWindowMouseDoubleclick(const Point& pos, InputKey key)
{
if (CaptureWidget)
{
@ -612,7 +716,7 @@ void Widget::OnWindowMouseDoubleclick(const Point& pos, EInputKey key)
}
}
void Widget::OnWindowMouseUp(const Point& pos, EInputKey key)
void Widget::OnWindowMouseUp(const Point& pos, InputKey key)
{
if (CaptureWidget)
{
@ -633,7 +737,7 @@ void Widget::OnWindowMouseUp(const Point& pos, EInputKey key)
}
}
void Widget::OnWindowMouseWheel(const Point& pos, EInputKey key)
void Widget::OnWindowMouseWheel(const Point& pos, InputKey key)
{
if (CaptureWidget)
{
@ -672,13 +776,13 @@ void Widget::OnWindowKeyChar(std::string chars)
FocusWidget->OnKeyChar(chars);
}
void Widget::OnWindowKeyDown(EInputKey key)
void Widget::OnWindowKeyDown(InputKey key)
{
if (FocusWidget)
FocusWidget->OnKeyDown(key);
}
void Widget::OnWindowKeyUp(EInputKey key)
void Widget::OnWindowKeyUp(InputKey key)
{
if (FocusWidget)
FocusWidget->OnKeyUp(key);
@ -686,9 +790,21 @@ void Widget::OnWindowKeyUp(EInputKey key)
void Widget::OnWindowGeometryChanged()
{
if (!DispWindow)
return;
Size size = DispWindow->GetClientSize();
FrameGeometry = Rect::xywh(0.0, 0.0, size.width, size.height);
ContentGeometry = FrameGeometry;
double left = FrameGeometry.left() + GetNoncontentLeft();
double top = FrameGeometry.top() + GetNoncontentTop();
double right = FrameGeometry.right() - GetNoncontentRight();
double bottom = FrameGeometry.bottom() - GetNoncontentBottom();
left = std::min(left, FrameGeometry.right());
top = std::min(top, FrameGeometry.bottom());
right = std::max(right, FrameGeometry.left());
bottom = std::max(bottom, FrameGeometry.top());
ContentGeometry = Rect::ltrb(left, top, right, bottom);
OnGeometryChanged();
}
@ -709,7 +825,133 @@ void Widget::OnWindowDpiScaleChanged()
{
}
double Widget::GetDpiScale() const
{
Widget* w = Window();
return w ? w->DispWindow->GetDpiScale() : 1.0;
}
double Widget::GridFitPoint(double p) const
{
double dpiscale = GetDpiScale();
return std::round(p * dpiscale) / dpiscale;
}
double Widget::GridFitSize(double s) const
{
if (s <= 0.0)
return 0.0;
double dpiscale = GetDpiScale();
return std::max(std::floor(s * dpiscale + 0.25), 1.0) / dpiscale;
}
Size Widget::GetScreenSize()
{
return DisplayWindow::GetScreenSize();
}
void* Widget::GetNativeHandle()
{
Widget* w = Window();
return w ? w->DispWindow->GetNativeHandle() : nullptr;
}
int Widget::GetNativePixelWidth()
{
Widget* w = Window();
return w ? w->DispWindow->GetPixelWidth() : 0;
}
int Widget::GetNativePixelHeight()
{
Widget* w = Window();
return w ? w->DispWindow->GetPixelHeight() : 0;
}
void Widget::SetStyleClass(const std::string& themeClass)
{
if (StyleClass != themeClass)
{
StyleClass = themeClass;
Update();
}
}
void Widget::SetStyleState(const std::string& state)
{
if (StyleState != state)
{
StyleState = state;
Update();
}
}
void Widget::SetStyleBool(const std::string& propertyName, bool value)
{
StyleProperties[propertyName] = value;
}
void Widget::SetStyleInt(const std::string& propertyName, int value)
{
StyleProperties[propertyName] = value;
}
void Widget::SetStyleDouble(const std::string& propertyName, double value)
{
StyleProperties[propertyName] = value;
}
void Widget::SetStyleString(const std::string& propertyName, const std::string& value)
{
StyleProperties[propertyName] = value;
}
void Widget::SetStyleColor(const std::string& propertyName, const Colorf& value)
{
StyleProperties[propertyName] = value;
}
bool Widget::GetStyleBool(const std::string& propertyName) const
{
auto it = StyleProperties.find(propertyName);
if (it != StyleProperties.end())
return std::get<bool>(it->second);
WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass);
return style ? style->GetBool(StyleState, propertyName) : false;
}
int Widget::GetStyleInt(const std::string& propertyName) const
{
auto it = StyleProperties.find(propertyName);
if (it != StyleProperties.end())
return std::get<int>(it->second);
WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass);
return style ? style->GetInt(StyleState, propertyName) : 0;
}
double Widget::GetStyleDouble(const std::string& propertyName) const
{
auto it = StyleProperties.find(propertyName);
if (it != StyleProperties.end())
return std::get<double>(it->second);
WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass);
return style ? style->GetDouble(StyleState, propertyName) : 0.0;
}
std::string Widget::GetStyleString(const std::string& propertyName) const
{
auto it = StyleProperties.find(propertyName);
if (it != StyleProperties.end())
return std::get<std::string>(it->second);
WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass);
return style ? style->GetString(StyleState, propertyName) : std::string();
}
Colorf Widget::GetStyleColor(const std::string& propertyName) const
{
auto it = StyleProperties.find(propertyName);
if (it != StyleProperties.end())
return std::get<Colorf>(it->second);
WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass);
return style ? style->GetColor(StyleState, propertyName) : Colorf::transparent();
}

View file

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

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

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

View file

@ -3,6 +3,7 @@
CheckboxLabel::CheckboxLabel(Widget* parent) : Widget(parent)
{
SetStyleClass("checkbox-label");
}
void CheckboxLabel::SetText(const std::string& value)
@ -40,30 +41,37 @@ double CheckboxLabel::GetPreferredHeight() const
void CheckboxLabel::OnPaint(Canvas* canvas)
{
// To do: add and use GetStyleImage for the checkbox
double center = GridFitPoint(GetHeight() * 0.5);
double borderwidth = GridFitSize(1.0);
double outerboxsize = GridFitSize(10.0);
double innerboxsize = outerboxsize - 2.0 * borderwidth;
double checkedsize = innerboxsize - 2.0 * borderwidth;
if (checked)
{
canvas->fillRect(Rect::xywh(0.0, GetHeight() * 0.5 - 6.0, 10.0, 10.0), Colorf::fromRgba8(100, 100, 100));
canvas->fillRect(Rect::xywh(1.0, GetHeight() * 0.5 - 5.0, 8.0, 8.0), Colorf::fromRgba8(51, 51, 51));
canvas->fillRect(Rect::xywh(2.0, GetHeight() * 0.5 - 4.0, 6.0, 6.0), Colorf::fromRgba8(226, 223, 219));
canvas->fillRect(Rect::xywh(0.0, center - 6.0 * borderwidth, outerboxsize, outerboxsize), GetStyleColor("checked-outer-border-color"));
canvas->fillRect(Rect::xywh(1.0 * borderwidth, center - 5.0 * borderwidth, innerboxsize, innerboxsize), GetStyleColor("checked-inner-border-color"));
canvas->fillRect(Rect::xywh(2.0 * borderwidth, center - 4.0 * borderwidth, checkedsize, checkedsize), GetStyleColor("checked-color"));
}
else
{
canvas->fillRect(Rect::xywh(0.0, GetHeight() * 0.5 - 6.0, 10.0, 10.0), Colorf::fromRgba8(99, 99, 99));
canvas->fillRect(Rect::xywh(1.0, GetHeight() * 0.5 - 5.0, 8.0, 8.0), Colorf::fromRgba8(51, 51, 51));
canvas->fillRect(Rect::xywh(0.0, center - 6.0 * borderwidth, outerboxsize, outerboxsize), GetStyleColor("unchecked-outer-border-color"));
canvas->fillRect(Rect::xywh(1.0 * borderwidth, center - 5.0 * borderwidth, innerboxsize, innerboxsize), GetStyleColor("unchecked-inner-border-color"));
}
canvas->drawText(Point(14.0, GetHeight() - 5.0), Colorf::fromRgba8(255, 255, 255), text);
canvas->drawText(Point(14.0, GetHeight() - 5.0), GetStyleColor("color"), text);
}
bool CheckboxLabel::OnMouseDown(const Point& pos, int key)
bool CheckboxLabel::OnMouseDown(const Point& pos, InputKey key)
{
mouseDownActive = true;
SetFocus();
return true;
}
bool CheckboxLabel::OnMouseUp(const Point& pos, int key)
bool CheckboxLabel::OnMouseUp(const Point& pos, InputKey key)
{
if (mouseDownActive)
{
@ -78,9 +86,9 @@ void CheckboxLabel::OnMouseLeave()
mouseDownActive = false;
}
void CheckboxLabel::OnKeyUp(EInputKey key)
void CheckboxLabel::OnKeyUp(InputKey key)
{
if (key == IK_Space)
if (key == InputKey::Space)
Toggle();
}

View file

@ -5,6 +5,14 @@ ImageBox::ImageBox(Widget* parent) : Widget(parent)
{
}
double ImageBox::GetPreferredWidth() const
{
if (image)
return (double)image->GetWidth();
else
return 0.0;
}
double ImageBox::GetPreferredHeight() const
{
if (image)
@ -22,10 +30,44 @@ void ImageBox::SetImage(std::shared_ptr<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,11 +1,12 @@
#include <algorithm>
#include "widgets/lineedit/lineedit.h"
#include "core/utf8reader.h"
#include "core/colorf.h"
#include <algorithm>
LineEdit::LineEdit(Widget* parent) : Widget(parent)
{
SetNoncontentSizes(5.0, 3.0, 5.0, 3.0);
SetStyleClass("lineedit");
timer = new Timer(this);
timer->FuncExpired = [=]() { OnTimerExpired(); };
@ -18,8 +19,6 @@ LineEdit::LineEdit(Widget* parent) : Widget(parent)
LineEdit::~LineEdit()
{
delete timer;
delete scroll_timer;
}
bool LineEdit::IsReadOnly() const
@ -303,13 +302,13 @@ void LineEdit::OnMouseMove(const Point& pos)
}
}
bool LineEdit::OnMouseDown(const Point& pos, int key)
bool LineEdit::OnMouseDown(const Point& pos, InputKey key)
{
if (key == IK_LeftMouse)
if (key == InputKey::LeftMouse)
{
if (HasFocus())
{
CaptureMouse();
SetPointerCapture();
mouse_selecting = true;
cursor_pos = GetCharacterIndex(pos.x);
SetTextSelection(cursor_pos, 0);
@ -323,25 +322,25 @@ bool LineEdit::OnMouseDown(const Point& pos, int key)
return true;
}
bool LineEdit::OnMouseDoubleclick(const Point& pos, int key)
bool LineEdit::OnMouseDoubleclick(const Point& pos, InputKey key)
{
return true;
}
bool LineEdit::OnMouseUp(const Point& pos, int key)
bool LineEdit::OnMouseUp(const Point& pos, InputKey key)
{
if (mouse_selecting && key == IK_LeftMouse)
if (mouse_selecting && key == InputKey::LeftMouse)
{
if (ignore_mouse_events) // This prevents text selection from changing from what was set when focus was gained.
{
ReleaseMouseCapture();
ReleasePointerCapture();
ignore_mouse_events = false;
mouse_selecting = false;
}
else
{
scroll_timer->Stop();
ReleaseMouseCapture();
ReleasePointerCapture();
mouse_selecting = false;
int sel_end = GetCharacterIndex(pos.x);
SetSelectionLength(sel_end - selection_start);
@ -414,12 +413,12 @@ void LineEdit::OnKeyChar(std::string chars)
}
}
void LineEdit::OnKeyDown(EInputKey key)
void LineEdit::OnKeyDown(InputKey key)
{
if (FuncIgnoreKeyDown && FuncIgnoreKeyDown(key))
return;
if (key == IK_Enter)
if (key == InputKey::Enter)
{
if (FuncEnterPressed)
FuncEnterPressed();
@ -432,12 +431,12 @@ void LineEdit::OnKeyDown(EInputKey key)
timer->Start(500); // don't blink cursor when moving or typing.
}
if (key == IK_Enter || key == IK_Escape || key == IK_Tab)
if (key == InputKey::Enter || key == InputKey::Escape || key == InputKey::Tab)
{
// Do not consume these.
return;
}
else if (key == IK_A && GetKeyState(IK_Ctrl))
else if (key == InputKey::A && GetKeyState(InputKey::Ctrl))
{
// select all
SetTextSelection(0, (int)text.size());
@ -445,7 +444,7 @@ void LineEdit::OnKeyDown(EInputKey key)
UpdateTextClipping();
Update();
}
else if (key == IK_C && GetKeyState(IK_Ctrl))
else if (key == InputKey::C && GetKeyState(InputKey::Ctrl))
{
if (!password_mode) // Do not allow copying the password to clipboard
{
@ -458,54 +457,54 @@ void LineEdit::OnKeyDown(EInputKey key)
// Do not consume messages on read only component (only allow CTRL-A and CTRL-C)
return;
}
else if (key == IK_Left)
else if (key == InputKey::Left)
{
Move(-1, GetKeyState(IK_Ctrl), GetKeyState(IK_Shift));
Move(-1, GetKeyState(InputKey::Ctrl), GetKeyState(InputKey::Shift));
}
else if (key == IK_Right)
else if (key == InputKey::Right)
{
Move(1, GetKeyState(IK_Ctrl), GetKeyState(IK_Shift));
Move(1, GetKeyState(InputKey::Ctrl), GetKeyState(InputKey::Shift));
}
else if (key == IK_Backspace)
else if (key == InputKey::Backspace)
{
Backspace();
UpdateTextClipping();
}
else if (key == IK_Delete)
else if (key == InputKey::Delete)
{
Del();
UpdateTextClipping();
}
else if (key == IK_Home)
else if (key == InputKey::Home)
{
SetSelectionStart(cursor_pos);
cursor_pos = 0;
if (GetKeyState(IK_Shift))
if (GetKeyState(InputKey::Shift))
SetSelectionLength(-selection_start);
else
SetTextSelection(0, 0);
UpdateTextClipping();
Update();
}
else if (key == IK_End)
else if (key == InputKey::End)
{
SetSelectionStart(cursor_pos);
cursor_pos = (int)text.size();
if (GetKeyState(IK_Shift))
if (GetKeyState(InputKey::Shift))
SetSelectionLength((int)text.size() - selection_start);
else
SetTextSelection(0, 0);
UpdateTextClipping();
Update();
}
else if (key == IK_X && GetKeyState(IK_Ctrl))
else if (key == InputKey::X && GetKeyState(InputKey::Ctrl))
{
std::string str = GetSelection();
DeleteSelectedText();
SetClipboardText(str);
UpdateTextClipping();
}
else if (key == IK_V && GetKeyState(IK_Ctrl))
else if (key == InputKey::V && GetKeyState(InputKey::Ctrl))
{
std::string str = GetClipboardText();
std::string::const_iterator end_str = std::remove(str.begin(), str.end(), '\n');
@ -576,7 +575,7 @@ void LineEdit::OnKeyDown(EInputKey key)
UpdateTextClipping();
}
else if (GetKeyState(IK_Ctrl) && key == IK_Z)
else if (GetKeyState(InputKey::Ctrl) && key == InputKey::Z)
{
if (!readonly)
{
@ -585,7 +584,7 @@ void LineEdit::OnKeyDown(EInputKey key)
SetText(tmp);
}
}
else if (key == IK_Shift)
else if (key == InputKey::Shift)
{
if (selection_start == -1)
SetTextSelection(cursor_pos, 0);
@ -595,7 +594,7 @@ void LineEdit::OnKeyDown(EInputKey key)
FuncAfterEditChanged();
}
void LineEdit::OnKeyUp(EInputKey key)
void LineEdit::OnKeyUp(InputKey key)
{
}
@ -817,6 +816,8 @@ int LineEdit::GetCharacterIndex(double mouse_x)
void LineEdit::UpdateTextClipping()
{
Canvas* canvas = GetCanvas();
if (!canvas)
return;
Size text_size = GetVisualTextSize(canvas, clip_start_offset, (int)text.size() - clip_start_offset);
@ -1067,18 +1068,6 @@ std::string LineEdit::GetVisibleTextAfterSelection()
}
}
void LineEdit::OnPaintFrame(Canvas* canvas)
{
double w = GetFrameGeometry().width;
double h = GetFrameGeometry().height;
Colorf bordercolor = Colorf::fromRgba8(100, 100, 100);
canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(38, 38, 38));
canvas->fillRect(Rect::xywh(0.0, 0.0, w, 1.0), bordercolor);
canvas->fillRect(Rect::xywh(0.0, h - 1.0, w, 1.0), bordercolor);
canvas->fillRect(Rect::xywh(0.0, 0.0, 1.0, h - 0.0), bordercolor);
canvas->fillRect(Rect::xywh(w - 1.0, 0.0, 1.0, h - 0.0), bordercolor);
}
void LineEdit::OnPaint(Canvas* canvas)
{
std::string txt_before = GetVisibleTextBeforeSelection();
@ -1101,21 +1090,21 @@ void LineEdit::OnPaint(Canvas* canvas)
{
// Draw selection box.
Rect selection_rect = GetSelectionRect();
canvas->fillRect(selection_rect, HasFocus() ? Colorf::fromRgba8(100, 100, 100) : Colorf::fromRgba8(68, 68, 68));
canvas->fillRect(selection_rect, HasFocus() ? GetStyleColor("selection-color") : GetStyleColor("no-focus-selection-color"));
}
// Draw text before selection
if (!txt_before.empty())
{
canvas->drawText(Point(0.0, canvas->verticalTextAlign().baseline), Colorf::fromRgba8(255, 255, 255), txt_before);
canvas->drawText(Point(0.0, canvas->verticalTextAlign().baseline), GetStyleColor("color"), txt_before);
}
if (!txt_selected.empty())
{
canvas->drawText(Point(size_before.width, canvas->verticalTextAlign().baseline), Colorf::fromRgba8(255, 255, 255), txt_selected);
canvas->drawText(Point(size_before.width, canvas->verticalTextAlign().baseline), GetStyleColor("color"), txt_selected);
}
if (!txt_after.empty())
{
canvas->drawText(Point(size_before.width + size_selected.width, canvas->verticalTextAlign().baseline), Colorf::fromRgba8(255, 255, 255), txt_after);
canvas->drawText(Point(size_before.width + size_selected.width, canvas->verticalTextAlign().baseline), GetStyleColor("color"), txt_after);
}
// draw cursor
@ -1124,7 +1113,7 @@ void LineEdit::OnPaint(Canvas* canvas)
if (cursor_blink_visible)
{
Rect cursor_rect = GetCursorRect();
canvas->fillRect(cursor_rect, Colorf::fromRgba8(255, 255, 255));
canvas->fillRect(cursor_rect, GetStyleColor("color"));
}
}
}

View file

@ -4,7 +4,7 @@
ListView::ListView(Widget* parent) : Widget(parent)
{
SetNoncontentSizes(10.0, 10.0, 3.0, 10.0);
SetStyleClass("listview");
scrollbar = new Scrollbar(this);
scrollbar->FuncScroll = [=]() { OnScrollbarScroll(); };
@ -28,7 +28,7 @@ void ListView::SetColumnWidths(const std::vector<double>& widths)
while (column.size() > newWidth)
{
updated = true;
column.erase(column.end());
column.pop_back();
}
}
@ -146,6 +146,9 @@ void ListView::OnPaint(Canvas* canvas)
double w = GetWidth() - scrollbar->GetPreferredWidth() - 2.0;
double h = 20.0;
Colorf textColor = GetStyleColor("color");
Colorf selectionColor = GetStyleColor("selection-color");
int index = 0;
for (const std::vector<std::string>& item : items)
{
@ -154,12 +157,12 @@ void ListView::OnPaint(Canvas* canvas)
{
if (index == selectedItem)
{
canvas->fillRect(Rect::xywh(x - 2.0, itemY, w, h), Colorf::fromRgba8(100, 100, 100));
canvas->fillRect(Rect::xywh(x - 2.0, itemY, w, h), selectionColor);
}
double cx = x;
for (size_t entry = 0u; entry < item.size(); ++entry)
{
canvas->drawText(Point(cx, y + 15.0), Colorf::fromRgba8(255, 255, 255), item[entry]);
canvas->drawText(Point(cx, y + 15.0), textColor, item[entry]);
cx += columnwidths[entry];
}
}
@ -168,23 +171,11 @@ void ListView::OnPaint(Canvas* canvas)
}
}
void ListView::OnPaintFrame(Canvas* canvas)
{
double w = GetFrameGeometry().width;
double h = GetFrameGeometry().height;
Colorf bordercolor = Colorf::fromRgba8(100, 100, 100);
canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(38, 38, 38));
canvas->fillRect(Rect::xywh(0.0, 0.0, w, 1.0), bordercolor);
canvas->fillRect(Rect::xywh(0.0, h - 1.0, w, 1.0), bordercolor);
canvas->fillRect(Rect::xywh(0.0, 0.0, 1.0, h - 0.0), bordercolor);
canvas->fillRect(Rect::xywh(w - 1.0, 0.0, 1.0, h - 0.0), bordercolor);
}
bool ListView::OnMouseDown(const Point& pos, int key)
bool ListView::OnMouseDown(const Point& pos, InputKey key)
{
SetFocus();
if (key == IK_LeftMouse)
if (key == InputKey::LeftMouse)
{
int index = (int)((pos.y - 5.0 + scrollbar->GetPosition()) / 20.0);
if (index >= 0 && (size_t)index < items.size())
@ -196,31 +187,31 @@ bool ListView::OnMouseDown(const Point& pos, int key)
return true;
}
bool ListView::OnMouseDoubleclick(const Point& pos, int key)
bool ListView::OnMouseDoubleclick(const Point& pos, InputKey key)
{
if (key == IK_LeftMouse)
if (key == InputKey::LeftMouse)
{
Activate();
}
return true;
}
bool ListView::OnMouseWheel(const Point& pos, EInputKey key)
bool ListView::OnMouseWheel(const Point& pos, InputKey key)
{
if (key == IK_MouseWheelUp)
if (key == InputKey::MouseWheelUp)
{
scrollbar->SetPosition(std::max(scrollbar->GetPosition() - 20.0, 0.0));
}
else if (key == IK_MouseWheelDown)
else if (key == InputKey::MouseWheelDown)
{
scrollbar->SetPosition(std::min(scrollbar->GetPosition() + 20.0, scrollbar->GetMax()));
}
return true;
}
void ListView::OnKeyDown(EInputKey key)
void ListView::OnKeyDown(InputKey key)
{
if (key == IK_Down)
if (key == InputKey::Down)
{
if (selectedItem + 1 < (int)items.size())
{
@ -228,7 +219,7 @@ void ListView::OnKeyDown(EInputKey key)
}
ScrollToItem(selectedItem);
}
else if (key == IK_Up)
else if (key == InputKey::Up)
{
if (selectedItem > 0)
{
@ -236,7 +227,7 @@ void ListView::OnKeyDown(EInputKey key)
}
ScrollToItem(selectedItem);
}
else if (key == IK_Enter)
else if (key == InputKey::Enter)
{
Activate();
}

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

@ -4,13 +4,387 @@
Menubar::Menubar(Widget* parent) : Widget(parent)
{
SetStyleClass("menubar");
}
Menubar::~Menubar()
{
}
void Menubar::OnPaint(Canvas* canvas)
MenubarItem* Menubar::AddItem(std::string text, std::function<void(Menu* menu)> onOpen, bool alignRight)
{
canvas->drawText(Point(16.0, 21.0), Colorf::fromRgba8(0, 0, 0), "File Edit View Tools Window Help");
auto item = new MenubarItem(this, text, alignRight);
item->SetOpenCallback(std::move(onOpen));
menuItems.push_back(item);
OnGeometryChanged();
return item;
}
void Menubar::ShowMenu(MenubarItem* item)
{
int index = GetItemIndex(item);
if (index == currentMenubarItem)
return;
CloseMenu();
SetFocus();
SetModalCapture();
currentMenubarItem = index;
if (currentMenubarItem != -1)
menuItems[currentMenubarItem]->SetStyleState("hover");
modalMode = true;
if (item->GetOpenCallback())
{
openMenu = new Menu(this);
openMenu->onCloseMenu = [=]() { CloseMenu(); };
item->GetOpenCallback()(openMenu);
if (item->AlignRight)
openMenu->SetRightPosition(item->MapToGlobal(Point(item->GetWidth(), item->GetHeight())));
else
openMenu->SetLeftPosition(item->MapToGlobal(Point(0.0, item->GetHeight())));
openMenu->Show();
}
}
void Menubar::CloseMenu()
{
if (currentMenubarItem != -1)
menuItems[currentMenubarItem]->SetStyleState("");
currentMenubarItem = -1;
delete openMenu;
openMenu = nullptr;
ReleaseModalCapture();
modalMode = false;
}
void Menubar::OnGeometryChanged()
{
double w = GetWidth();
double h = GetHeight();
double left = 0.0;
double right = w;
for (MenubarItem* item : menuItems)
{
double itemwidth = item->GetPreferredWidth();
itemwidth += 16.0;
if (!item->AlignRight)
{
item->SetFrameGeometry(left, 0.0, itemwidth, h);
left += itemwidth;
}
else
{
right -= itemwidth;
item->SetFrameGeometry(right, 0.0, itemwidth, h);
}
}
}
MenubarItem* Menubar::GetMenubarItemAt(const Point& pos)
{
Widget* widget = ChildAt(pos);
for (MenubarItem* item : menuItems)
{
if (widget == item)
return item;
}
return nullptr;
}
bool Menubar::OnMouseDown(const Point& pos, InputKey key)
{
if (!modalMode)
return Widget::OnMouseDown(pos, key);
MenubarItem* item = GetMenubarItemAt(pos);
if (item)
ShowMenu(item);
else
CloseMenu();
return true;
}
bool Menubar::OnMouseUp(const Point& pos, InputKey key)
{
if (!modalMode)
return Widget::OnMouseUp(pos, key);
MenubarItem* item = GetMenubarItemAt(pos);
if (!item)
CloseMenu();
return true;
}
void Menubar::OnMouseMove(const Point& pos)
{
if (!modalMode)
return Widget::OnMouseMove(pos);
MenubarItem* item = GetMenubarItemAt(pos);
if (item)
ShowMenu(item);
}
int Menubar::GetItemIndex(MenubarItem* item)
{
int i = 0;
for (MenubarItem* cur : menuItems)
{
if (cur == item)
return i;
i++;
}
return -1;
}
void Menubar::OnKeyDown(InputKey key)
{
if (!modalMode)
return Widget::OnKeyDown(key);
if (key == InputKey::Left)
{
if (!menuItems.empty())
{
int index = currentMenubarItem - 1;
if (index < 0)
index = (int)menuItems.size() - 1;
ShowMenu(menuItems[index]);
}
}
else if (key == InputKey::Right)
{
if (!menuItems.empty())
{
int index = currentMenubarItem + 1;
if (index == (int)menuItems.size())
index = 0;
ShowMenu(menuItems[index]);
}
}
else if (key == InputKey::Up)
{
if (openMenu)
{
// Keep trying until we don't find a separator
for (int i = 0; i < 10; i++)
{
if (!openMenu->selectedItem || !openMenu->selectedItem->PrevSibling())
{
if (openMenu->LastChild())
openMenu->SetSelected(static_cast<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)
{
SetStyleClass("menubaritem");
}
bool MenubarItem::OnMouseDown(const Point& pos, InputKey key)
{
menubar->ShowMenu(this);
return true;
}
bool MenubarItem::OnMouseUp(const Point& pos, InputKey key)
{
return true;
}
void MenubarItem::OnMouseMove(const Point& pos)
{
if (GetStyleState().empty())
{
SetStyleState("hover");
}
}
void MenubarItem::OnMouseLeave()
{
SetStyleState("");
}
double MenubarItem::GetPreferredWidth() const
{
Canvas* canvas = GetCanvas();
return canvas->measureText(text).width;
}
void MenubarItem::OnPaint(Canvas* canvas)
{
double x = (GetWidth() - canvas->measureText(text).width) * 0.5;
canvas->drawText(Point(x, 21.0), GetStyleColor("color"), text);
}
/////////////////////////////////////////////////////////////////////////////
Menu::Menu(Widget* parent) : Widget(parent, WidgetType::Popup)
{
SetStyleClass("menu");
}
void Menu::SetLeftPosition(const Point& pos)
{
SetFrameGeometry(Rect::xywh(pos.x, pos.y, GetPreferredWidth() + GetNoncontentLeft() + GetNoncontentRight(), GetPreferredHeight() + GetNoncontentTop() + GetNoncontentBottom()));
}
void Menu::SetRightPosition(const Point& pos)
{
SetFrameGeometry(Rect::xywh(pos.x - GetWidth() - GetNoncontentLeft() - GetNoncontentRight(), pos.y, GetWidth() + GetNoncontentLeft() + GetNoncontentRight(), GetHeight() + GetNoncontentTop() + GetNoncontentBottom()));
}
MenuItem* Menu::AddItem(std::shared_ptr<Image> icon, std::string text, std::function<void()> onClick)
{
auto item = new MenuItem(this, [this, onClick]() { if (onCloseMenu) onCloseMenu(); if (onClick) onClick(); });
if (icon)
item->icon->SetImage(icon);
item->text->SetText(text);
return item;
}
MenuItemSeparator* Menu::AddSeparator()
{
auto sep = new MenuItemSeparator(this);
return sep;
}
double Menu::GetPreferredWidth() const
{
return GridFitSize(200.0);
}
double Menu::GetPreferredHeight() const
{
double h = 0.0;
for (Widget* item = FirstChild(); item != nullptr; item = item->NextSibling())
{
double itemheight = GridFitSize((item->GetStyleClass() == "menuitemseparator") ? 7.0 : 30.0);
h += itemheight;
}
return h;
}
void Menu::OnGeometryChanged()
{
double w = GetWidth();
double y = 0.0;
for (Widget* item = FirstChild(); item != nullptr; item = item->NextSibling())
{
double itemheight = GridFitSize((item->GetStyleClass() == "menuitemseparator") ? 7.0 : 30.0);
item->SetFrameGeometry(Rect::xywh(0.0, y, w, itemheight));
y += itemheight;
}
}
void Menu::SetSelected(MenuItem* item)
{
if (selectedItem)
{
selectedItem->SetStyleState("");
}
selectedItem = item;
if (selectedItem)
{
selectedItem->SetStyleState("hover");
}
}
/////////////////////////////////////////////////////////////////////////////
MenuItem::MenuItem(Menu* menu, std::function<void()> onClick) : Widget(menu), menu(menu), onClick(onClick)
{
SetStyleClass("menuitem");
icon = new ImageBox(this);
text = new TextLabel(this);
}
void MenuItem::OnMouseMove(const Point& pos)
{
menu->SetSelected(this);
}
bool MenuItem::OnMouseUp(const Point& pos, InputKey key)
{
Click();
return true;
}
void MenuItem::Click()
{
if (onClick)
{
// We have to make a copy of the handler as it may delete 'this'
auto handler = onClick;
handler();
}
}
void MenuItem::OnMouseLeave()
{
menu->SetSelected(nullptr);
}
void MenuItem::OnGeometryChanged()
{
double iconwidth = GridFitSize(icon->GetPreferredWidth());
double iconheight = GridFitSize(icon->GetPreferredHeight());
double w = GetWidth();
double h = GetHeight();
double textheight = 19.0;
double x0 = GridFitPoint(5.0);
double x1 = GridFitPoint(5.0 + iconwidth);
icon->SetFrameGeometry(Rect::xywh(x0, GridFitPoint((h - iconheight) * 0.5), iconwidth, iconheight));
text->SetFrameGeometry(Rect::xywh(x1, GridFitPoint((h - textheight) * 0.5), w - x1, textheight));
}
/////////////////////////////////////////////////////////////////////////////
MenuItemSeparator::MenuItemSeparator(Widget* parent) : Widget(parent)
{
SetStyleClass("menuitemseparator");
}
void MenuItemSeparator::OnPaint(Canvas* canvas)
{
canvas->fillRect(Rect::xywh(0.0, GridFitPoint(GetHeight() * 0.5), GetWidth(), GridFitSize(1.0)), Colorf::fromRgba8(75, 75, 75));
}

View file

@ -3,7 +3,7 @@
PushButton::PushButton(Widget* parent) : Widget(parent)
{
SetNoncontentSizes(10.0, 5.0, 10.0, 5.0);
SetStyleClass("pushbutton");
}
void PushButton::SetText(const std::string& value)
@ -25,52 +25,32 @@ double PushButton::GetPreferredHeight() const
return 30.0;
}
void PushButton::OnPaintFrame(Canvas* canvas)
{
double w = GetFrameGeometry().width;
double h = GetFrameGeometry().height;
Colorf bordercolor = Colorf::fromRgba8(100, 100, 100);
Colorf buttoncolor = Colorf::fromRgba8(68, 68, 68);
if (buttonDown)
buttoncolor = Colorf::fromRgba8(88, 88, 88);
else if (hot)
buttoncolor = Colorf::fromRgba8(78, 78, 78);
canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), buttoncolor);
canvas->fillRect(Rect::xywh(0.0, 0.0, w, 1.0), bordercolor);
canvas->fillRect(Rect::xywh(0.0, h - 1.0, w, 1.0), bordercolor);
canvas->fillRect(Rect::xywh(0.0, 0.0, 1.0, h - 0.0), bordercolor);
canvas->fillRect(Rect::xywh(w - 1.0, 0.0, 1.0, h - 0.0), bordercolor);
}
void PushButton::OnPaint(Canvas* canvas)
{
Rect box = canvas->measureText(text);
canvas->drawText(Point((GetWidth() - box.width) * 0.5, GetHeight() - 5.0), Colorf::fromRgba8(255, 255, 255), text);
canvas->drawText(Point((GetWidth() - box.width) * 0.5, GetHeight() - 5.0), GetStyleColor("color"), text);
}
void PushButton::OnMouseMove(const Point& pos)
{
if (!hot)
if (GetStyleState().empty())
{
hot = true;
Update();
SetStyleState("hover");
}
}
bool PushButton::OnMouseDown(const Point& pos, int key)
bool PushButton::OnMouseDown(const Point& pos, InputKey key)
{
SetFocus();
buttonDown = true;
Update();
SetStyleState("down");
return true;
}
bool PushButton::OnMouseUp(const Point& pos, int key)
bool PushButton::OnMouseUp(const Point& pos, InputKey key)
{
if (buttonDown)
if (GetStyleState() == "down")
{
buttonDown = false;
hot = false;
SetStyleState("");
Repaint();
Click();
}
@ -79,26 +59,23 @@ bool PushButton::OnMouseUp(const Point& pos, int key)
void PushButton::OnMouseLeave()
{
hot = false;
buttonDown = false;
Update();
SetStyleState("");
}
void PushButton::OnKeyDown(EInputKey key)
void PushButton::OnKeyDown(InputKey key)
{
if (key == IK_Space || key == IK_Enter)
if (key == InputKey::Space || key == InputKey::Enter)
{
buttonDown = true;
SetStyleState("down");
Update();
}
}
void PushButton::OnKeyUp(EInputKey key)
void PushButton::OnKeyUp(InputKey key)
{
if (key == IK_Space || key == IK_Enter)
if (key == InputKey::Space || key == InputKey::Enter)
{
buttonDown = false;
hot = false;
SetStyleState("");
Repaint();
Click();
}

View file

@ -5,6 +5,7 @@
Scrollbar::Scrollbar(Widget* parent) : Widget(parent)
{
SetStyleClass("scrollbar");
UpdatePartPositions();
mouse_down_timer = new Timer(this);
@ -169,7 +170,7 @@ void Scrollbar::OnMouseMove(const Point& pos)
Update();
}
bool Scrollbar::OnMouseDown(const Point& pos, int key)
bool Scrollbar::OnMouseDown(const Point& pos, InputKey key)
{
mouse_drag_start_pos = pos;
@ -253,11 +254,11 @@ bool Scrollbar::OnMouseDown(const Point& pos, int key)
UpdatePartPositions();
Update();
CaptureMouse();
SetPointerCapture();
return true;
}
bool Scrollbar::OnMouseUp(const Point& pos, int key)
bool Scrollbar::OnMouseUp(const Point& pos, InputKey key)
{
if (mouse_down_mode == mouse_down_thumb_drag)
{
@ -269,7 +270,7 @@ bool Scrollbar::OnMouseUp(const Point& pos, int key)
mouse_down_timer->Stop();
Update();
ReleaseMouseCapture();
ReleasePointerCapture();
return true;
}
@ -294,8 +295,8 @@ void Scrollbar::OnPaint(Canvas* canvas)
part_button_increment.render_box(canvas, rect_button_increment);
*/
canvas->fillRect(Rect::shrink(Rect::xywh(0.0, 0.0, GetWidth(), GetHeight()), 4.0, 0.0, 4.0, 0.0), Colorf::fromRgba8(33, 33, 33));
canvas->fillRect(Rect::shrink(rect_thumb, 4.0, 0.0, 4.0, 0.0), Colorf::fromRgba8(58, 58, 58));
canvas->fillRect(Rect::shrink(Rect::xywh(0.0, 0.0, GetWidth(), GetHeight()), 4.0, 0.0, 4.0, 0.0), GetStyleColor("track-color"));
canvas->fillRect(Rect::shrink(rect_thumb, 4.0, 0.0, 4.0, 0.0), GetStyleColor("thumb-color"));
}
// Calculates positions of all parts. Returns true if thumb position was changed compared to previously, false otherwise.

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

@ -85,7 +85,7 @@ int TabWidget::GetPageIndex(Widget* pageWidget) const
for (size_t i = 0; i < Pages.size(); i++)
{
if (Pages[i] == pageWidget)
return i;
return (int)i;
}
return -1;
}
@ -98,10 +98,6 @@ void TabWidget::OnBarCurrentChanged()
OnCurrentChanged();
}
void TabWidget::OnPaintFrame(Canvas* canvas)
{
}
void TabWidget::OnGeometryChanged()
{
double w = GetWidth();
@ -115,7 +111,7 @@ void TabWidget::OnGeometryChanged()
TabBar::TabBar(Widget* parent) : Widget(parent)
{
SetNoncontentSizes(20.0, 0.0, 20.0, 0.0);
SetStyleClass("tabbar");
}
int TabBar::AddTab(const std::string& label)
@ -129,7 +125,7 @@ int TabBar::AddTab(const std::shared_ptr<Image>& icon, const std::string& label)
tab->SetIcon(icon);
tab->SetText(label);
tab->OnClick = [=]() { OnTabClicked(tab); };
int pageIndex = Tabs.size();
int pageIndex = (int)Tabs.size();
Tabs.push_back(tab);
if (CurrentIndex == -1)
SetCurrentIndex(pageIndex);
@ -182,18 +178,11 @@ int TabBar::GetTabIndex(TabBarTab* tab)
for (size_t i = 0; i < Tabs.size(); i++)
{
if (Tabs[i] == tab)
return i;
return (int)i;
}
return -1;
}
void TabBar::OnPaintFrame(Canvas* canvas)
{
double w = GetFrameGeometry().width;
double h = GetFrameGeometry().height;
canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(38, 38, 38));
}
void TabBar::OnGeometryChanged()
{
double w = GetWidth();
@ -211,7 +200,7 @@ void TabBar::OnGeometryChanged()
TabBarTab::TabBarTab(Widget* parent) : Widget(parent)
{
SetNoncontentSizes(15.0, 0.0, 15.0, 0.0);
SetStyleClass("tabbar-tab");
}
void TabBarTab::SetText(const std::string& text)
@ -257,7 +246,7 @@ void TabBarTab::SetCurrent(bool value)
if (IsCurrent != value)
{
IsCurrent = value;
Update();
SetStyleState(value ? "active" : "");
}
}
@ -268,20 +257,6 @@ double TabBarTab::GetPreferredWidth() const
return x;
}
void TabBarTab::OnPaintFrame(Canvas* canvas)
{
double w = GetFrameGeometry().width;
double h = GetFrameGeometry().height;
if (IsCurrent)
{
canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(51, 51, 51));
}
else if (hot)
{
canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(45, 45, 45));
}
}
void TabBarTab::OnGeometryChanged()
{
double x = 0.0;
@ -300,28 +275,28 @@ void TabBarTab::OnGeometryChanged()
void TabBarTab::OnMouseMove(const Point& pos)
{
if (!hot)
if (GetStyleState().empty())
{
hot = true;
Update();
SetStyleState("hover");
}
}
bool TabBarTab::OnMouseDown(const Point& pos, int key)
bool TabBarTab::OnMouseDown(const Point& pos, InputKey key)
{
if (OnClick)
OnClick();
return true;
}
bool TabBarTab::OnMouseUp(const Point& pos, int key)
bool TabBarTab::OnMouseUp(const Point& pos, InputKey key)
{
return true;
}
void TabBarTab::OnMouseLeave()
{
hot = false;
if (GetStyleState() == "hover")
SetStyleState("");
Update();
}
@ -329,7 +304,7 @@ void TabBarTab::OnMouseLeave()
TabWidgetStack::TabWidgetStack(Widget* parent) : Widget(parent)
{
SetNoncontentSizes(20.0, 5.0, 20.0, 5.0);
SetStyleClass("tabwidget-stack");
}
void TabWidgetStack::SetCurrentWidget(Widget* widget)
@ -347,10 +322,6 @@ void TabWidgetStack::SetCurrentWidget(Widget* widget)
}
}
void TabWidgetStack::OnPaintFrame(Canvas* canvas)
{
}
void TabWidgetStack::OnGeometryChanged()
{
if (CurrentWidget)

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
@ -10,7 +11,7 @@
TextEdit::TextEdit(Widget* parent) : Widget(parent)
{
SetNoncontentSizes(8.0, 8.0, 8.0, 8.0);
SetStyleClass("textedit");
timer = new Timer(this);
timer->FuncExpired = [=]() { OnTimerExpired(); };
@ -286,11 +287,11 @@ void TextEdit::OnMouseMove(const Point& pos)
}
}
bool TextEdit::OnMouseDown(const Point& pos, int key)
bool TextEdit::OnMouseDown(const Point& pos, InputKey key)
{
if (key == IK_LeftMouse)
if (key == InputKey::LeftMouse)
{
CaptureMouse();
SetPointerCapture();
mouse_selecting = true;
cursor_pos = GetCharacterIndex(pos);
selection_start = cursor_pos;
@ -301,25 +302,25 @@ bool TextEdit::OnMouseDown(const Point& pos, int key)
return true;
}
bool TextEdit::OnMouseDoubleclick(const Point& pos, int key)
bool TextEdit::OnMouseDoubleclick(const Point& pos, InputKey key)
{
return true;
}
bool TextEdit::OnMouseUp(const Point& pos, int key)
bool TextEdit::OnMouseUp(const Point& pos, InputKey key)
{
if (mouse_selecting && key == IK_LeftMouse)
if (mouse_selecting && key == InputKey::LeftMouse)
{
if (ignore_mouse_events) // This prevents text selection from changing from what was set when focus was gained.
{
ReleaseMouseCapture();
ReleasePointerCapture();
ignore_mouse_events = false;
mouse_selecting = false;
}
else
{
scroll_timer->Stop();
ReleaseMouseCapture();
ReleasePointerCapture();
mouse_selecting = false;
ivec2 sel_end = GetCharacterIndex(pos);
selection_length = ToOffset(sel_end) - ToOffset(selection_start);
@ -360,9 +361,9 @@ void TextEdit::OnKeyChar(std::string chars)
}
}
void TextEdit::OnKeyDown(EInputKey key)
void TextEdit::OnKeyDown(InputKey key)
{
if (!readonly && key == IK_Enter)
if (!readonly && key == InputKey::Enter)
{
if (FuncEnterPressed)
{
@ -383,17 +384,17 @@ void TextEdit::OnKeyDown(EInputKey key)
timer->Start(500); // don't blink cursor when moving or typing.
}
if (key == IK_Enter || key == IK_Escape || key == IK_Tab)
if (key == InputKey::Enter || key == InputKey::Escape || key == InputKey::Tab)
{
// Do not consume these.
return;
}
else if (key == IK_A && GetKeyState(IK_Ctrl))
else if (key == InputKey::A && GetKeyState(InputKey::Ctrl))
{
// select all
SelectAll();
}
else if (key == IK_C && GetKeyState(IK_Ctrl))
else if (key == InputKey::C && GetKeyState(InputKey::Ctrl))
{
std::string str = GetSelection();
SetClipboardText(str);
@ -403,9 +404,9 @@ void TextEdit::OnKeyDown(EInputKey key)
// Do not consume messages on read only component (only allow CTRL-A and CTRL-C)
return;
}
else if (key == IK_Up)
else if (key == InputKey::Up)
{
if (GetKeyState(IK_Shift) && selection_length == 0)
if (GetKeyState(InputKey::Shift) && selection_length == 0)
selection_start = cursor_pos;
if (cursor_pos.y > 0)
@ -414,7 +415,7 @@ void TextEdit::OnKeyDown(EInputKey key)
cursor_pos.x = std::min(lines[cursor_pos.y].text.size(), (size_t)cursor_pos.x);
}
if (GetKeyState(IK_Shift))
if (GetKeyState(InputKey::Shift))
{
selection_length = ToOffset(cursor_pos) - ToOffset(selection_start);
}
@ -428,9 +429,9 @@ void TextEdit::OnKeyDown(EInputKey key)
Update();
undo_info.first_text_insert = true;
}
else if (key == IK_Down)
else if (key == InputKey::Down)
{
if (GetKeyState(IK_Shift) && selection_length == 0)
if (GetKeyState(InputKey::Shift) && selection_length == 0)
selection_start = cursor_pos;
if (cursor_pos.y < lines.size() - 1)
@ -439,7 +440,7 @@ void TextEdit::OnKeyDown(EInputKey key)
cursor_pos.x = std::min(lines[cursor_pos.y].text.size(), (size_t)cursor_pos.x);
}
if (GetKeyState(IK_Shift))
if (GetKeyState(InputKey::Shift))
{
selection_length = ToOffset(cursor_pos) - ToOffset(selection_start);
}
@ -454,55 +455,55 @@ void TextEdit::OnKeyDown(EInputKey key)
Update();
undo_info.first_text_insert = true;
}
else if (key == IK_Left)
else if (key == InputKey::Left)
{
Move(-1, GetKeyState(IK_Shift), GetKeyState(IK_Ctrl));
Move(-1, GetKeyState(InputKey::Shift), GetKeyState(InputKey::Ctrl));
}
else if (key == IK_Right)
else if (key == InputKey::Right)
{
Move(1, GetKeyState(IK_Shift), GetKeyState(IK_Ctrl));
Move(1, GetKeyState(InputKey::Shift), GetKeyState(InputKey::Ctrl));
}
else if (key == IK_Backspace)
else if (key == InputKey::Backspace)
{
Backspace();
}
else if (key == IK_Delete)
else if (key == InputKey::Delete)
{
Del();
}
else if (key == IK_Home)
else if (key == InputKey::Home)
{
if (GetKeyState(IK_Ctrl))
if (GetKeyState(InputKey::Ctrl))
cursor_pos = ivec2(0, 0);
else
cursor_pos.x = 0;
if (GetKeyState(IK_Shift))
if (GetKeyState(InputKey::Shift))
selection_length = ToOffset(cursor_pos) - ToOffset(selection_start);
else
ClearSelection();
Update();
MoveVerticalScroll();
}
else if (key == IK_End)
else if (key == InputKey::End)
{
if (GetKeyState(IK_Ctrl))
if (GetKeyState(InputKey::Ctrl))
cursor_pos = ivec2(lines.back().text.length(), lines.size() - 1);
else
cursor_pos.x = lines[cursor_pos.y].text.size();
if (GetKeyState(IK_Shift))
if (GetKeyState(InputKey::Shift))
selection_length = ToOffset(cursor_pos) - ToOffset(selection_start);
else
ClearSelection();
Update();
}
else if (key == IK_X && GetKeyState(IK_Ctrl))
else if (key == InputKey::X && GetKeyState(InputKey::Ctrl))
{
std::string str = GetSelection();
DeleteSelectedText();
SetClipboardText(str);
}
else if (key == IK_V && GetKeyState(IK_Ctrl))
else if (key == InputKey::V && GetKeyState(InputKey::Ctrl))
{
std::string str = GetClipboardText();
std::string::const_iterator end_str = std::remove(str.begin(), str.end(), '\r');
@ -524,7 +525,7 @@ void TextEdit::OnKeyDown(EInputKey key)
}
MoveVerticalScroll();
}
else if (GetKeyState(IK_Ctrl) && key == IK_Z)
else if (GetKeyState(InputKey::Ctrl) && key == InputKey::Z)
{
if (!readonly)
{
@ -533,7 +534,7 @@ void TextEdit::OnKeyDown(EInputKey key)
SetText(tmp);
}
}
else if (key == IK_Shift)
else if (key == InputKey::Shift)
{
if (selection_length == 0)
selection_start = cursor_pos;
@ -543,7 +544,7 @@ void TextEdit::OnKeyDown(EInputKey key)
FuncAfterEditChanged();
}
void TextEdit::OnKeyUp(EInputKey key)
void TextEdit::OnKeyUp(InputKey key)
{
}
@ -961,17 +962,18 @@ void TextEdit::LayoutLines(Canvas* canvas)
sel_end = selection_start;
}
Colorf textColor = GetStyleColor("color");
Point draw_pos;
for (size_t i = vert_scrollbar->GetPosition(); i < lines.size(); i++)
for (size_t i = (size_t)vert_scrollbar->GetPosition(); i < lines.size(); i++)
{
Line& line = lines[i];
if (line.invalidated)
{
line.layout.Clear();
if (!line.text.empty())
line.layout.AddText(line.text, font, Colorf::fromRgba8(255, 255, 255));
line.layout.AddText(line.text, font, textColor);
else
line.layout.AddText(" ", font, Colorf::fromRgba8(255, 255, 255)); // Draw one space character to get the correct height
line.layout.AddText(" ", font, textColor); // Draw one space character to get the correct height
line.layout.Layout(canvas, GetWidth());
line.box = Rect(draw_pos, line.layout.GetSize());
line.invalidated = false;
@ -992,7 +994,7 @@ void TextEdit::LayoutLines(Canvas* canvas)
if (cursor_blink_visible && cursor_pos.y == i)
{
line.layout.SetCursorPos(cursor_pos.x);
line.layout.SetCursorColor(Colorf::fromRgba8(255, 255, 255));
line.layout.SetCursorColor(textColor);
line.layout.ShowCursor();
}
}
@ -1006,22 +1008,10 @@ void TextEdit::LayoutLines(Canvas* canvas)
UpdateVerticalScroll();
}
void TextEdit::OnPaintFrame(Canvas* canvas)
{
double w = GetFrameGeometry().width;
double h = GetFrameGeometry().height;
Colorf bordercolor = Colorf::fromRgba8(100, 100, 100);
canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(38, 38, 38));
canvas->fillRect(Rect::xywh(0.0, 0.0, w, 1.0), bordercolor);
canvas->fillRect(Rect::xywh(0.0, h - 1.0, w, 1.0), bordercolor);
canvas->fillRect(Rect::xywh(0.0, 0.0, 1.0, h - 0.0), bordercolor);
canvas->fillRect(Rect::xywh(w - 1.0, 0.0, 1.0, h - 0.0), bordercolor);
}
void TextEdit::OnPaint(Canvas* canvas)
{
LayoutLines(canvas);
for (size_t i = vert_scrollbar->GetPosition(); i < lines.size(); i++)
for (size_t i = (size_t)vert_scrollbar->GetPosition(); i < lines.size(); i++)
lines[i].layout.DrawLayout(canvas);
}

View file

@ -56,5 +56,5 @@ void TextLabel::OnPaint(Canvas* canvas)
x = GetWidth() - canvas->measureText(text).width;
}
canvas->drawText(Point(x, GetHeight() - 5.0), Colorf::fromRgba8(255, 255, 255), text);
canvas->drawText(Point(x, GetHeight() - 5.0), GetStyleColor("color"), text);
}

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

@ -0,0 +1,772 @@
#include "sdl2_display_window.h"
#include <stdexcept>
#include <SDL2/SDL_vulkan.h>
Uint32 SDL2DisplayWindow::PaintEventNumber = 0xffffffff;
bool SDL2DisplayWindow::ExitRunLoop;
std::unordered_map<int, SDL2DisplayWindow*> SDL2DisplayWindow::WindowList;
class InitSDL
{
public:
InitSDL()
{
int result = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
if (result != 0)
throw std::runtime_error(std::string("Unable to initialize SDL:") + SDL_GetError());
SDL2DisplayWindow::PaintEventNumber = SDL_RegisterEvents(1);
}
};
static void CheckInitSDL()
{
static InitSDL initsdl;
}
SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, SDL2DisplayWindow* owner, RenderAPI renderAPI) : WindowHost(windowHost)
{
CheckInitSDL();
unsigned int flags = SDL_WINDOW_HIDDEN /*| SDL_WINDOW_ALLOW_HIGHDPI*/;
if (renderAPI == RenderAPI::Vulkan)
flags |= SDL_WINDOW_VULKAN;
else if (renderAPI == RenderAPI::OpenGL)
flags |= SDL_WINDOW_OPENGL;
#if defined(__APPLE__)
else if (renderAPI == RenderAPI::Metal)
flags |= SDL_WINDOW_METAL;
#endif
if (popupWindow)
flags |= SDL_WINDOW_BORDERLESS;
if (renderAPI == RenderAPI::Vulkan || renderAPI == RenderAPI::OpenGL || renderAPI == RenderAPI::Metal)
{
Handle.window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 320, 200, flags);
if (!Handle.window)
throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError());
}
else
{
int result = SDL_CreateWindowAndRenderer(320, 200, flags, &Handle.window, &RendererHandle);
if (result != 0)
throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError());
}
WindowList[SDL_GetWindowID(Handle.window)] = this;
}
SDL2DisplayWindow::~SDL2DisplayWindow()
{
UnlockCursor();
WindowList.erase(WindowList.find(SDL_GetWindowID(Handle.window)));
if (BackBufferTexture)
{
SDL_DestroyTexture(BackBufferTexture);
BackBufferTexture = nullptr;
}
if (RendererHandle)
SDL_DestroyRenderer(RendererHandle);
SDL_DestroyWindow(Handle.window);
RendererHandle = nullptr;
Handle.window = nullptr;
}
std::vector<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(Handle.window, text.c_str());
}
void SDL2DisplayWindow::SetWindowFrame(const Rect& box)
{
// SDL2 doesn't really seem to have an API for this.
// The docs aren't clear what you're setting when calling SDL_SetWindowSize.
SetClientFrame(box);
}
void SDL2DisplayWindow::SetClientFrame(const Rect& box)
{
// Is there a way to set both in one call?
double uiscale = GetDpiScale();
int x = (int)std::round(box.x * uiscale);
int y = (int)std::round(box.y * uiscale);
int w = (int)std::round(box.width * uiscale);
int h = (int)std::round(box.height * uiscale);
SDL_SetWindowPosition(Handle.window, x, y);
SDL_SetWindowSize(Handle.window, w, h);
}
void SDL2DisplayWindow::Show()
{
SDL_ShowWindow(Handle.window);
}
void SDL2DisplayWindow::ShowFullscreen()
{
SDL_ShowWindow(Handle.window);
SDL_SetWindowFullscreen(Handle.window, SDL_WINDOW_FULLSCREEN_DESKTOP);
isFullscreen = true;
}
void SDL2DisplayWindow::ShowMaximized()
{
SDL_ShowWindow(Handle.window);
SDL_MaximizeWindow(Handle.window);
}
void SDL2DisplayWindow::ShowMinimized()
{
SDL_ShowWindow(Handle.window);
SDL_MinimizeWindow(Handle.window);
}
void SDL2DisplayWindow::ShowNormal()
{
SDL_ShowWindow(Handle.window);
SDL_SetWindowFullscreen(Handle.window, 0);
isFullscreen = false;
}
bool SDL2DisplayWindow::IsWindowFullscreen()
{
return isFullscreen;
}
void SDL2DisplayWindow::Hide()
{
SDL_HideWindow(Handle.window);
}
void SDL2DisplayWindow::Activate()
{
SDL_RaiseWindow(Handle.window);
}
void SDL2DisplayWindow::ShowCursor(bool enable)
{
SDL_ShowCursor(enable);
}
void SDL2DisplayWindow::LockCursor()
{
if (!CursorLocked)
{
SDL_SetRelativeMouseMode(SDL_TRUE);
CursorLocked = true;
}
}
void SDL2DisplayWindow::UnlockCursor()
{
if (CursorLocked)
{
SDL_SetRelativeMouseMode(SDL_FALSE);
CursorLocked = false;
}
}
void SDL2DisplayWindow::CaptureMouse()
{
}
void SDL2DisplayWindow::ReleaseMouseCapture()
{
}
void SDL2DisplayWindow::SetCursor(StandardCursor cursor)
{
}
void SDL2DisplayWindow::Update()
{
SDL_Event event = {};
event.type = PaintEventNumber;
event.user.windowID = SDL_GetWindowID(Handle.window);
SDL_PushEvent(&event);
}
bool SDL2DisplayWindow::GetKeyState(InputKey key)
{
int numkeys = 0;
const Uint8* state = SDL_GetKeyboardState(&numkeys);
if (!state) return false;
SDL_Scancode index = InputKeyToScancode(key);
return (index < numkeys) ? state[index] != 0 : false;
}
Rect SDL2DisplayWindow::GetWindowFrame() const
{
int x = 0;
int y = 0;
int w = 0;
int h = 0;
double uiscale = GetDpiScale();
SDL_GetWindowPosition(Handle.window, &x, &y);
SDL_GetWindowSize(Handle.window, &w, &h);
return Rect::xywh(x / uiscale, y / uiscale, w / uiscale, h / uiscale);
}
Point SDL2DisplayWindow::MapFromGlobal(const Point& pos) const
{
int x = 0;
int y = 0;
double uiscale = GetDpiScale();
SDL_GetWindowPosition(Handle.window, &x, &y);
return Point(pos.x - x / uiscale, pos.y - y / uiscale);
}
Point SDL2DisplayWindow::MapToGlobal(const Point& pos) const
{
int x = 0;
int y = 0;
double uiscale = GetDpiScale();
SDL_GetWindowPosition(Handle.window, &x, &y);
return Point(pos.x + x / uiscale, pos.y + y / uiscale);
}
Size SDL2DisplayWindow::GetClientSize() const
{
int w = 0;
int h = 0;
double uiscale = GetDpiScale();
SDL_GetWindowSize(Handle.window, &w, &h);
return Size(w / uiscale, h / uiscale);
}
int SDL2DisplayWindow::GetPixelWidth() const
{
int w = 0;
int h = 0;
if (RendererHandle)
SDL_GetRendererOutputSize(RendererHandle, &w, &h);
else
SDL_GL_GetDrawableSize(Handle.window, &w, &h);
return w;
}
int SDL2DisplayWindow::GetPixelHeight() const
{
int w = 0;
int h = 0;
if (RendererHandle)
SDL_GetRendererOutputSize(RendererHandle, &w, &h);
else
SDL_GL_GetDrawableSize(Handle.window, &w, &h);
return h;
}
double SDL2DisplayWindow::GetDpiScale() const
{
// SDL2 doesn't really support this properly. SDL_GetDisplayDPI returns the wrong information according to the docs.
return 1.0;
}
void SDL2DisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels)
{
if (!RendererHandle)
return;
if (!BackBufferTexture || BackBufferWidth != width || BackBufferHeight != height)
{
if (BackBufferTexture)
{
SDL_DestroyTexture(BackBufferTexture);
BackBufferTexture = nullptr;
}
BackBufferTexture = SDL_CreateTexture(RendererHandle, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height);
if (!BackBufferTexture)
return;
BackBufferWidth = width;
BackBufferHeight = height;
}
int destpitch = 0;
void* dest = nullptr;
int result = SDL_LockTexture(BackBufferTexture, nullptr, &dest, &destpitch);
if (result != 0) return;
for (int y = 0; y < height; y++)
{
const void* sline = pixels + y * width;
void* dline = (uint8_t*)dest + y * destpitch;
memcpy(dline, sline, width << 2);
}
SDL_UnlockTexture(BackBufferTexture);
SDL_RenderCopy(RendererHandle, BackBufferTexture, nullptr, nullptr);
SDL_RenderPresent(RendererHandle);
}
void SDL2DisplayWindow::SetBorderColor(uint32_t bgra8)
{
// SDL doesn't have this
}
void SDL2DisplayWindow::SetCaptionColor(uint32_t bgra8)
{
// SDL doesn't have this
}
void SDL2DisplayWindow::SetCaptionTextColor(uint32_t bgra8)
{
// SDL doesn't have this
}
std::string SDL2DisplayWindow::GetClipboardText()
{
char* buffer = SDL_GetClipboardText();
if (!buffer)
return {};
std::string text = buffer;
SDL_free(buffer);
return text;
}
void SDL2DisplayWindow::SetClipboardText(const std::string& text)
{
SDL_SetClipboardText(text.c_str());
}
void SDL2DisplayWindow::ProcessEvents()
{
CheckInitSDL();
SDL_Event event;
while (SDL_PollEvent(&event) != 0)
{
DispatchEvent(event);
}
}
void SDL2DisplayWindow::RunLoop()
{
CheckInitSDL();
ExitRunLoop = false;
while (!ExitRunLoop)
{
SDL_Event event = {};
int result = SDL_WaitEvent(&event);
if (result == 1)
DispatchEvent(event); // Silently ignore if it fails and pray it doesn't busy loop, because SDL and Linux utterly sucks!
}
}
void SDL2DisplayWindow::ExitLoop()
{
CheckInitSDL();
ExitRunLoop = true;
}
Size SDL2DisplayWindow::GetScreenSize()
{
CheckInitSDL();
SDL_Rect rect = {};
int result = SDL_GetDisplayBounds(0, &rect);
if (result != 0)
throw std::runtime_error(std::string("Unable to get screen size:") + SDL_GetError());
double uiscale = 1.0; // SDL2 doesn't really support this properly. SDL_GetDisplayDPI returns the wrong information according to the docs.
return Size(rect.w / uiscale, rect.h / uiscale);
}
void* SDL2DisplayWindow::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)
{
CheckInitSDL();
// To do: implement timers
return nullptr;
}
void SDL2DisplayWindow::StopTimer(void* timerID)
{
CheckInitSDL();
// To do: implement timers
}
SDL2DisplayWindow* SDL2DisplayWindow::FindEventWindow(const SDL_Event& event)
{
int windowID;
switch (event.type)
{
case SDL_WINDOWEVENT: windowID = event.window.windowID; break;
case SDL_TEXTINPUT: windowID = event.text.windowID; break;
case SDL_KEYUP: windowID = event.key.windowID; break;
case SDL_KEYDOWN: windowID = event.key.windowID; break;
case SDL_MOUSEBUTTONUP: windowID = event.button.windowID; break;
case SDL_MOUSEBUTTONDOWN: windowID = event.button.windowID; break;
case SDL_MOUSEWHEEL: windowID = event.wheel.windowID; break;
case SDL_MOUSEMOTION: windowID = event.motion.windowID; break;
default:
if (event.type == PaintEventNumber) windowID = event.user.windowID;
else return nullptr;
}
auto it = WindowList.find(windowID);
return it != WindowList.end() ? it->second : nullptr;
}
void SDL2DisplayWindow::DispatchEvent(const SDL_Event& event)
{
SDL2DisplayWindow* window = FindEventWindow(event);
if (!window) return;
switch (event.type)
{
case SDL_WINDOWEVENT: window->OnWindowEvent(event.window); break;
case SDL_TEXTINPUT: window->OnTextInput(event.text); break;
case SDL_KEYUP: window->OnKeyUp(event.key); break;
case SDL_KEYDOWN: window->OnKeyDown(event.key); break;
case SDL_MOUSEBUTTONUP: window->OnMouseButtonUp(event.button); break;
case SDL_MOUSEBUTTONDOWN: window->OnMouseButtonDown(event.button); break;
case SDL_MOUSEWHEEL: window->OnMouseWheel(event.wheel); break;
case SDL_MOUSEMOTION: window->OnMouseMotion(event.motion); break;
default:
if (event.type == PaintEventNumber) window->OnPaintEvent();
}
}
void SDL2DisplayWindow::OnWindowEvent(const SDL_WindowEvent& event)
{
switch (event.event)
{
case SDL_WINDOWEVENT_CLOSE: WindowHost->OnWindowClose(); break;
case SDL_WINDOWEVENT_MOVED: WindowHost->OnWindowGeometryChanged(); break;
case SDL_WINDOWEVENT_RESIZED: WindowHost->OnWindowGeometryChanged(); break;
case SDL_WINDOWEVENT_SHOWN: WindowHost->OnWindowPaint(); break;
case SDL_WINDOWEVENT_EXPOSED: WindowHost->OnWindowPaint(); break;
case SDL_WINDOWEVENT_FOCUS_GAINED: WindowHost->OnWindowActivated(); break;
case SDL_WINDOWEVENT_FOCUS_LOST: WindowHost->OnWindowDeactivated(); break;
}
}
void SDL2DisplayWindow::OnTextInput(const SDL_TextInputEvent& event)
{
WindowHost->OnWindowKeyChar(event.text);
}
void SDL2DisplayWindow::OnKeyUp(const SDL_KeyboardEvent& event)
{
WindowHost->OnWindowKeyUp(ScancodeToInputKey(event.keysym.scancode));
}
void SDL2DisplayWindow::OnKeyDown(const SDL_KeyboardEvent& event)
{
WindowHost->OnWindowKeyDown(ScancodeToInputKey(event.keysym.scancode));
}
InputKey SDL2DisplayWindow::GetMouseButtonKey(const SDL_MouseButtonEvent& event)
{
switch (event.button)
{
case SDL_BUTTON_LEFT: return InputKey::LeftMouse;
case SDL_BUTTON_MIDDLE: return InputKey::MiddleMouse;
case SDL_BUTTON_RIGHT: return InputKey::RightMouse;
// case SDL_BUTTON_X1: return InputKey::XButton1;
// case SDL_BUTTON_X2: return InputKey::XButton2;
default: return InputKey::None;
}
}
void SDL2DisplayWindow::OnMouseButtonUp(const SDL_MouseButtonEvent& event)
{
InputKey key = GetMouseButtonKey(event);
if (key != InputKey::None)
{
WindowHost->OnWindowMouseUp(GetMousePos(event), key);
}
}
void SDL2DisplayWindow::OnMouseButtonDown(const SDL_MouseButtonEvent& event)
{
InputKey key = GetMouseButtonKey(event);
if (key != InputKey::None)
{
WindowHost->OnWindowMouseDown(GetMousePos(event), key);
}
}
void SDL2DisplayWindow::OnMouseWheel(const SDL_MouseWheelEvent& event)
{
InputKey key = (event.y > 0) ? InputKey::MouseWheelUp : (event.y < 0) ? InputKey::MouseWheelDown : InputKey::None;
if (key != InputKey::None)
{
WindowHost->OnWindowMouseWheel(GetMousePos(event), key);
}
}
void SDL2DisplayWindow::OnMouseMotion(const SDL_MouseMotionEvent& event)
{
if (CursorLocked)
{
WindowHost->OnWindowRawMouseMove(event.xrel, event.yrel);
}
else
{
WindowHost->OnWindowMouseMove(GetMousePos(event));
}
}
void SDL2DisplayWindow::OnPaintEvent()
{
WindowHost->OnWindowPaint();
}
InputKey SDL2DisplayWindow::ScancodeToInputKey(SDL_Scancode keycode)
{
switch (keycode)
{
case SDL_SCANCODE_BACKSPACE: return InputKey::Backspace;
case SDL_SCANCODE_TAB: return InputKey::Tab;
case SDL_SCANCODE_CLEAR: return InputKey::OEMClear;
case SDL_SCANCODE_RETURN: return InputKey::Enter;
case SDL_SCANCODE_MENU: return InputKey::Alt;
case SDL_SCANCODE_PAUSE: return InputKey::Pause;
case SDL_SCANCODE_ESCAPE: return InputKey::Escape;
case SDL_SCANCODE_SPACE: return InputKey::Space;
case SDL_SCANCODE_END: return InputKey::End;
case SDL_SCANCODE_HOME: return InputKey::Home;
case SDL_SCANCODE_LEFT: return InputKey::Left;
case SDL_SCANCODE_UP: return InputKey::Up;
case SDL_SCANCODE_RIGHT: return InputKey::Right;
case SDL_SCANCODE_DOWN: return InputKey::Down;
case SDL_SCANCODE_SELECT: return InputKey::Select;
case SDL_SCANCODE_PRINTSCREEN: return InputKey::Print;
case SDL_SCANCODE_EXECUTE: return InputKey::Execute;
case SDL_SCANCODE_INSERT: return InputKey::Insert;
case SDL_SCANCODE_DELETE: return InputKey::Delete;
case SDL_SCANCODE_HELP: return InputKey::Help;
case SDL_SCANCODE_0: return InputKey::_0;
case SDL_SCANCODE_1: return InputKey::_1;
case SDL_SCANCODE_2: return InputKey::_2;
case SDL_SCANCODE_3: return InputKey::_3;
case SDL_SCANCODE_4: return InputKey::_4;
case SDL_SCANCODE_5: return InputKey::_5;
case SDL_SCANCODE_6: return InputKey::_6;
case SDL_SCANCODE_7: return InputKey::_7;
case SDL_SCANCODE_8: return InputKey::_8;
case SDL_SCANCODE_9: return InputKey::_9;
case SDL_SCANCODE_A: return InputKey::A;
case SDL_SCANCODE_B: return InputKey::B;
case SDL_SCANCODE_C: return InputKey::C;
case SDL_SCANCODE_D: return InputKey::D;
case SDL_SCANCODE_E: return InputKey::E;
case SDL_SCANCODE_F: return InputKey::F;
case SDL_SCANCODE_G: return InputKey::G;
case SDL_SCANCODE_H: return InputKey::H;
case SDL_SCANCODE_I: return InputKey::I;
case SDL_SCANCODE_J: return InputKey::J;
case SDL_SCANCODE_K: return InputKey::K;
case SDL_SCANCODE_L: return InputKey::L;
case SDL_SCANCODE_M: return InputKey::M;
case SDL_SCANCODE_N: return InputKey::N;
case SDL_SCANCODE_O: return InputKey::O;
case SDL_SCANCODE_P: return InputKey::P;
case SDL_SCANCODE_Q: return InputKey::Q;
case SDL_SCANCODE_R: return InputKey::R;
case SDL_SCANCODE_S: return InputKey::S;
case SDL_SCANCODE_T: return InputKey::T;
case SDL_SCANCODE_U: return InputKey::U;
case SDL_SCANCODE_V: return InputKey::V;
case SDL_SCANCODE_W: return InputKey::W;
case SDL_SCANCODE_X: return InputKey::X;
case SDL_SCANCODE_Y: return InputKey::Y;
case SDL_SCANCODE_Z: return InputKey::Z;
case SDL_SCANCODE_KP_0: return InputKey::NumPad0;
case SDL_SCANCODE_KP_1: return InputKey::NumPad1;
case SDL_SCANCODE_KP_2: return InputKey::NumPad2;
case SDL_SCANCODE_KP_3: return InputKey::NumPad3;
case SDL_SCANCODE_KP_4: return InputKey::NumPad4;
case SDL_SCANCODE_KP_5: return InputKey::NumPad5;
case SDL_SCANCODE_KP_6: return InputKey::NumPad6;
case SDL_SCANCODE_KP_7: return InputKey::NumPad7;
case SDL_SCANCODE_KP_8: return InputKey::NumPad8;
case SDL_SCANCODE_KP_9: return InputKey::NumPad9;
// case SDL_SCANCODE_KP_ENTER: return InputKey::NumPadEnter;
// case SDL_SCANCODE_KP_MULTIPLY: return InputKey::Multiply;
// case SDL_SCANCODE_KP_PLUS: return InputKey::Add;
case SDL_SCANCODE_SEPARATOR: return InputKey::Separator;
// case SDL_SCANCODE_KP_MINUS: return InputKey::Subtract;
case SDL_SCANCODE_KP_PERIOD: return InputKey::NumPadPeriod;
// case SDL_SCANCODE_KP_DIVIDE: return InputKey::Divide;
case SDL_SCANCODE_F1: return InputKey::F1;
case SDL_SCANCODE_F2: return InputKey::F2;
case SDL_SCANCODE_F3: return InputKey::F3;
case SDL_SCANCODE_F4: return InputKey::F4;
case SDL_SCANCODE_F5: return InputKey::F5;
case SDL_SCANCODE_F6: return InputKey::F6;
case SDL_SCANCODE_F7: return InputKey::F7;
case SDL_SCANCODE_F8: return InputKey::F8;
case SDL_SCANCODE_F9: return InputKey::F9;
case SDL_SCANCODE_F10: return InputKey::F10;
case SDL_SCANCODE_F11: return InputKey::F11;
case SDL_SCANCODE_F12: return InputKey::F12;
case SDL_SCANCODE_F13: return InputKey::F13;
case SDL_SCANCODE_F14: return InputKey::F14;
case SDL_SCANCODE_F15: return InputKey::F15;
case SDL_SCANCODE_F16: return InputKey::F16;
case SDL_SCANCODE_F17: return InputKey::F17;
case SDL_SCANCODE_F18: return InputKey::F18;
case SDL_SCANCODE_F19: return InputKey::F19;
case SDL_SCANCODE_F20: return InputKey::F20;
case SDL_SCANCODE_F21: return InputKey::F21;
case SDL_SCANCODE_F22: return InputKey::F22;
case SDL_SCANCODE_F23: return InputKey::F23;
case SDL_SCANCODE_F24: return InputKey::F24;
case SDL_SCANCODE_NUMLOCKCLEAR: return InputKey::NumLock;
case SDL_SCANCODE_SCROLLLOCK: return InputKey::ScrollLock;
case SDL_SCANCODE_LSHIFT: return InputKey::LShift;
case SDL_SCANCODE_RSHIFT: return InputKey::RShift;
case SDL_SCANCODE_LCTRL: return InputKey::LControl;
case SDL_SCANCODE_RCTRL: return InputKey::RControl;
case SDL_SCANCODE_GRAVE: return InputKey::Tilde;
default: return InputKey::None;
}
}
SDL_Scancode SDL2DisplayWindow::InputKeyToScancode(InputKey inputkey)
{
switch (inputkey)
{
case InputKey::Backspace: return SDL_SCANCODE_BACKSPACE;
case InputKey::Tab: return SDL_SCANCODE_TAB;
case InputKey::OEMClear: return SDL_SCANCODE_CLEAR;
case InputKey::Enter: return SDL_SCANCODE_RETURN;
case InputKey::Alt: return SDL_SCANCODE_MENU;
case InputKey::Pause: return SDL_SCANCODE_PAUSE;
case InputKey::Escape: return SDL_SCANCODE_ESCAPE;
case InputKey::Space: return SDL_SCANCODE_SPACE;
case InputKey::End: return SDL_SCANCODE_END;
case InputKey::Home: return SDL_SCANCODE_HOME;
case InputKey::Left: return SDL_SCANCODE_LEFT;
case InputKey::Up: return SDL_SCANCODE_UP;
case InputKey::Right: return SDL_SCANCODE_RIGHT;
case InputKey::Down: return SDL_SCANCODE_DOWN;
case InputKey::Select: return SDL_SCANCODE_SELECT;
case InputKey::Print: return SDL_SCANCODE_PRINTSCREEN;
case InputKey::Execute: return SDL_SCANCODE_EXECUTE;
case InputKey::Insert: return SDL_SCANCODE_INSERT;
case InputKey::Delete: return SDL_SCANCODE_DELETE;
case InputKey::Help: return SDL_SCANCODE_HELP;
case InputKey::_0: return SDL_SCANCODE_0;
case InputKey::_1: return SDL_SCANCODE_1;
case InputKey::_2: return SDL_SCANCODE_2;
case InputKey::_3: return SDL_SCANCODE_3;
case InputKey::_4: return SDL_SCANCODE_4;
case InputKey::_5: return SDL_SCANCODE_5;
case InputKey::_6: return SDL_SCANCODE_6;
case InputKey::_7: return SDL_SCANCODE_7;
case InputKey::_8: return SDL_SCANCODE_8;
case InputKey::_9: return SDL_SCANCODE_9;
case InputKey::A: return SDL_SCANCODE_A;
case InputKey::B: return SDL_SCANCODE_B;
case InputKey::C: return SDL_SCANCODE_C;
case InputKey::D: return SDL_SCANCODE_D;
case InputKey::E: return SDL_SCANCODE_E;
case InputKey::F: return SDL_SCANCODE_F;
case InputKey::G: return SDL_SCANCODE_G;
case InputKey::H: return SDL_SCANCODE_H;
case InputKey::I: return SDL_SCANCODE_I;
case InputKey::J: return SDL_SCANCODE_J;
case InputKey::K: return SDL_SCANCODE_K;
case InputKey::L: return SDL_SCANCODE_L;
case InputKey::M: return SDL_SCANCODE_M;
case InputKey::N: return SDL_SCANCODE_N;
case InputKey::O: return SDL_SCANCODE_O;
case InputKey::P: return SDL_SCANCODE_P;
case InputKey::Q: return SDL_SCANCODE_Q;
case InputKey::R: return SDL_SCANCODE_R;
case InputKey::S: return SDL_SCANCODE_S;
case InputKey::T: return SDL_SCANCODE_T;
case InputKey::U: return SDL_SCANCODE_U;
case InputKey::V: return SDL_SCANCODE_V;
case InputKey::W: return SDL_SCANCODE_W;
case InputKey::X: return SDL_SCANCODE_X;
case InputKey::Y: return SDL_SCANCODE_Y;
case InputKey::Z: return SDL_SCANCODE_Z;
case InputKey::NumPad0: return SDL_SCANCODE_KP_0;
case InputKey::NumPad1: return SDL_SCANCODE_KP_1;
case InputKey::NumPad2: return SDL_SCANCODE_KP_2;
case InputKey::NumPad3: return SDL_SCANCODE_KP_3;
case InputKey::NumPad4: return SDL_SCANCODE_KP_4;
case InputKey::NumPad5: return SDL_SCANCODE_KP_5;
case InputKey::NumPad6: return SDL_SCANCODE_KP_6;
case InputKey::NumPad7: return SDL_SCANCODE_KP_7;
case InputKey::NumPad8: return SDL_SCANCODE_KP_8;
case InputKey::NumPad9: return SDL_SCANCODE_KP_9;
// case InputKey::NumPadEnter: return SDL_SCANCODE_KP_ENTER;
// case InputKey::Multiply return SDL_SCANCODE_KP_MULTIPLY:;
// case InputKey::Add: return SDL_SCANCODE_KP_PLUS;
case InputKey::Separator: return SDL_SCANCODE_SEPARATOR;
// case InputKey::Subtract: return SDL_SCANCODE_KP_MINUS;
case InputKey::NumPadPeriod: return SDL_SCANCODE_KP_PERIOD;
// case InputKey::Divide: return SDL_SCANCODE_KP_DIVIDE;
case InputKey::F1: return SDL_SCANCODE_F1;
case InputKey::F2: return SDL_SCANCODE_F2;
case InputKey::F3: return SDL_SCANCODE_F3;
case InputKey::F4: return SDL_SCANCODE_F4;
case InputKey::F5: return SDL_SCANCODE_F5;
case InputKey::F6: return SDL_SCANCODE_F6;
case InputKey::F7: return SDL_SCANCODE_F7;
case InputKey::F8: return SDL_SCANCODE_F8;
case InputKey::F9: return SDL_SCANCODE_F9;
case InputKey::F10: return SDL_SCANCODE_F10;
case InputKey::F11: return SDL_SCANCODE_F11;
case InputKey::F12: return SDL_SCANCODE_F12;
case InputKey::F13: return SDL_SCANCODE_F13;
case InputKey::F14: return SDL_SCANCODE_F14;
case InputKey::F15: return SDL_SCANCODE_F15;
case InputKey::F16: return SDL_SCANCODE_F16;
case InputKey::F17: return SDL_SCANCODE_F17;
case InputKey::F18: return SDL_SCANCODE_F18;
case InputKey::F19: return SDL_SCANCODE_F19;
case InputKey::F20: return SDL_SCANCODE_F20;
case InputKey::F21: return SDL_SCANCODE_F21;
case InputKey::F22: return SDL_SCANCODE_F22;
case InputKey::F23: return SDL_SCANCODE_F23;
case InputKey::F24: return SDL_SCANCODE_F24;
case InputKey::NumLock: return SDL_SCANCODE_NUMLOCKCLEAR;
case InputKey::ScrollLock: return SDL_SCANCODE_SCROLLLOCK;
case InputKey::LShift: return SDL_SCANCODE_LSHIFT;
case InputKey::RShift: return SDL_SCANCODE_RSHIFT;
case InputKey::LControl: return SDL_SCANCODE_LCTRL;
case InputKey::RControl: return SDL_SCANCODE_RCTRL;
case InputKey::Tilde: return SDL_SCANCODE_GRAVE;
default: return (SDL_Scancode)0;
}
}

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);
SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, SDL2DisplayWindow* owner, RenderAPI renderAPI);
~SDL2DisplayWindow();
void SetWindowTitle(const std::string& text) override;
@ -19,6 +20,7 @@ public:
void ShowMaximized() override;
void ShowMinimized() override;
void ShowNormal() override;
bool IsWindowFullscreen() override;
void Hide() override;
void Activate() override;
void ShowCursor(bool enable) override;
@ -27,7 +29,7 @@ public:
void CaptureMouse() override;
void ReleaseMouseCapture() override;
void Update() override;
bool GetKeyState(EInputKey key) override;
bool GetKeyState(InputKey key) override;
void SetCursor(StandardCursor cursor) override;
Rect GetWindowFrame() const override;
@ -45,6 +47,14 @@ public:
std::string GetClipboardText() override;
void SetClipboardText(const std::string& text) override;
Point MapFromGlobal(const Point& pos) const override;
Point MapToGlobal(const Point& pos) const override;
void* GetNativeHandle() override { return &Handle; }
std::vector<std::string> GetVulkanInstanceExtensions() override;
VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override;
static void DispatchEvent(const SDL_Event& event);
static SDL2DisplayWindow* FindEventWindow(const SDL_Event& event);
@ -58,10 +68,10 @@ public:
void OnMouseMotion(const SDL_MouseMotionEvent& event);
void OnPaintEvent();
EInputKey GetMouseButtonKey(const SDL_MouseButtonEvent& event);
InputKey GetMouseButtonKey(const SDL_MouseButtonEvent& event);
static EInputKey ScancodeToInputKey(SDL_Scancode keycode);
static SDL_Scancode InputKeyToScancode(EInputKey inputkey);
static InputKey ScancodeToInputKey(SDL_Scancode keycode);
static SDL_Scancode InputKeyToScancode(InputKey inputkey);
template<typename T>
Point GetMousePos(const T& event)
@ -79,12 +89,15 @@ public:
static void StopTimer(void* timerID);
DisplayWindowHost* WindowHost = nullptr;
SDL_Window* WindowHandle = nullptr;
SDL2NativeHandle Handle;
SDL_Renderer* RendererHandle = nullptr;
SDL_Texture* BackBufferTexture = nullptr;
int BackBufferWidth = 0;
int BackBufferHeight = 0;
bool CursorLocked = false;
bool isFullscreen = false;
static bool ExitRunLoop;
static Uint32 PaintEventNumber;
static std::unordered_map<int, SDL2DisplayWindow*> WindowList;

View file

@ -1,675 +0,0 @@
#include "sdl2displaywindow.h"
#include <stdexcept>
Uint32 SDL2DisplayWindow::PaintEventNumber = 0xffffffff;
bool SDL2DisplayWindow::ExitRunLoop;
std::unordered_map<int, SDL2DisplayWindow*> SDL2DisplayWindow::WindowList;
class InitSDL
{
public:
InitSDL()
{
int result = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
if (result != 0)
throw std::runtime_error(std::string("Unable to initialize SDL:") + SDL_GetError());
SDL2DisplayWindow::PaintEventNumber = SDL_RegisterEvents(1);
}
};
static void CheckInitSDL()
{
static InitSDL initsdl;
}
SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost) : WindowHost(windowHost)
{
CheckInitSDL();
int result = SDL_CreateWindowAndRenderer(320, 200, SDL_WINDOW_HIDDEN /*| SDL_WINDOW_ALLOW_HIGHDPI*/, &WindowHandle, &RendererHandle);
if (result != 0)
throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError());
WindowList[SDL_GetWindowID(WindowHandle)] = this;
}
SDL2DisplayWindow::~SDL2DisplayWindow()
{
WindowList.erase(WindowList.find(SDL_GetWindowID(WindowHandle)));
if (BackBufferTexture)
{
SDL_DestroyTexture(BackBufferTexture);
BackBufferTexture = nullptr;
}
SDL_DestroyRenderer(RendererHandle);
SDL_DestroyWindow(WindowHandle);
RendererHandle = nullptr;
WindowHandle = nullptr;
}
void SDL2DisplayWindow::SetWindowTitle(const std::string& text)
{
SDL_SetWindowTitle(WindowHandle, text.c_str());
}
void SDL2DisplayWindow::SetWindowFrame(const Rect& box)
{
// SDL2 doesn't really seem to have an API for this.
// The docs aren't clear what you're setting when calling SDL_SetWindowSize.
SetClientFrame(box);
}
void SDL2DisplayWindow::SetClientFrame(const Rect& box)
{
// Is there a way to set both in one call?
double uiscale = GetDpiScale();
int x = (int)std::round(box.x * uiscale);
int y = (int)std::round(box.y * uiscale);
int w = (int)std::round(box.width * uiscale);
int h = (int)std::round(box.height * uiscale);
SDL_SetWindowPosition(WindowHandle, x, y);
SDL_SetWindowSize(WindowHandle, w, h);
}
void SDL2DisplayWindow::Show()
{
SDL_ShowWindow(WindowHandle);
}
void SDL2DisplayWindow::ShowFullscreen()
{
SDL_SetWindowFullscreen(WindowHandle, SDL_WINDOW_FULLSCREEN_DESKTOP);
}
void SDL2DisplayWindow::ShowMaximized()
{
SDL_ShowWindow(WindowHandle);
SDL_MaximizeWindow(WindowHandle);
}
void SDL2DisplayWindow::ShowMinimized()
{
SDL_ShowWindow(WindowHandle);
SDL_MinimizeWindow(WindowHandle);
}
void SDL2DisplayWindow::ShowNormal()
{
SDL_ShowWindow(WindowHandle);
SDL_SetWindowFullscreen(WindowHandle, 0);
}
void SDL2DisplayWindow::Hide()
{
SDL_HideWindow(WindowHandle);
}
void SDL2DisplayWindow::Activate()
{
SDL_RaiseWindow(WindowHandle);
}
void SDL2DisplayWindow::ShowCursor(bool enable)
{
SDL_ShowCursor(enable);
}
void SDL2DisplayWindow::LockCursor()
{
SDL_SetWindowGrab(WindowHandle, SDL_TRUE);
SDL_ShowCursor(0);
}
void SDL2DisplayWindow::UnlockCursor()
{
SDL_SetWindowGrab(WindowHandle, SDL_FALSE);
SDL_ShowCursor(1);
}
void SDL2DisplayWindow::CaptureMouse()
{
}
void SDL2DisplayWindow::ReleaseMouseCapture()
{
}
void SDL2DisplayWindow::SetCursor(StandardCursor cursor)
{
}
void SDL2DisplayWindow::Update()
{
SDL_Event event = {};
event.type = PaintEventNumber;
event.user.windowID = SDL_GetWindowID(WindowHandle);
SDL_PushEvent(&event);
}
bool SDL2DisplayWindow::GetKeyState(EInputKey key)
{
int numkeys = 0;
const Uint8* state = SDL_GetKeyboardState(&numkeys);
if (!state) return false;
SDL_Scancode index = InputKeyToScancode(key);
return (index < numkeys) ? state[index] != 0 : false;
}
Rect SDL2DisplayWindow::GetWindowFrame() const
{
int x = 0;
int y = 0;
int w = 0;
int h = 0;
double uiscale = GetDpiScale();
SDL_GetWindowPosition(WindowHandle, &x, &y);
SDL_GetWindowSize(WindowHandle, &w, &h);
return Rect::xywh(x / uiscale, y / uiscale, w / uiscale, h / uiscale);
}
Size SDL2DisplayWindow::GetClientSize() const
{
int w = 0;
int h = 0;
double uiscale = GetDpiScale();
SDL_GetWindowSize(WindowHandle, &w, &h);
return Size(w / uiscale, h / uiscale);
}
int SDL2DisplayWindow::GetPixelWidth() const
{
int w = 0;
int h = 0;
int result = SDL_GetRendererOutputSize(RendererHandle, &w, &h);
return w;
}
int SDL2DisplayWindow::GetPixelHeight() const
{
int w = 0;
int h = 0;
int result = SDL_GetRendererOutputSize(RendererHandle, &w, &h);
return h;
}
double SDL2DisplayWindow::GetDpiScale() const
{
// SDL2 doesn't really support this properly. SDL_GetDisplayDPI returns the wrong information according to the docs.
return 1.0;
}
void SDL2DisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels)
{
if (!BackBufferTexture || BackBufferWidth != width || BackBufferHeight != height)
{
if (BackBufferTexture)
{
SDL_DestroyTexture(BackBufferTexture);
BackBufferTexture = nullptr;
}
BackBufferTexture = SDL_CreateTexture(RendererHandle, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height);
if (!BackBufferTexture)
return;
BackBufferWidth = width;
BackBufferHeight = height;
}
int destpitch = 0;
void* dest = nullptr;
int result = SDL_LockTexture(BackBufferTexture, nullptr, &dest, &destpitch);
if (result != 0) return;
for (int y = 0; y < height; y++)
{
const void* sline = pixels + y * width;
void* dline = (uint8_t*)dest + y * destpitch;
memcpy(dline, sline, width << 2);
}
SDL_UnlockTexture(BackBufferTexture);
SDL_RenderCopy(RendererHandle, BackBufferTexture, nullptr, nullptr);
SDL_RenderPresent(RendererHandle);
}
void SDL2DisplayWindow::SetBorderColor(uint32_t bgra8)
{
// SDL doesn't have this
}
void SDL2DisplayWindow::SetCaptionColor(uint32_t bgra8)
{
// SDL doesn't have this
}
void SDL2DisplayWindow::SetCaptionTextColor(uint32_t bgra8)
{
// SDL doesn't have this
}
std::string SDL2DisplayWindow::GetClipboardText()
{
char* buffer = SDL_GetClipboardText();
if (!buffer)
return {};
std::string text = buffer;
SDL_free(buffer);
return text;
}
void SDL2DisplayWindow::SetClipboardText(const std::string& text)
{
SDL_SetClipboardText(text.c_str());
}
void SDL2DisplayWindow::ProcessEvents()
{
CheckInitSDL();
SDL_Event event;
while (SDL_PollEvent(&event) != 0)
{
DispatchEvent(event);
}
}
void SDL2DisplayWindow::RunLoop()
{
CheckInitSDL();
while (!ExitRunLoop)
{
SDL_Event event;
int result = SDL_WaitEvent(&event);
if (result == 1)
DispatchEvent(event);
}
}
void SDL2DisplayWindow::ExitLoop()
{
CheckInitSDL();
ExitRunLoop = true;
}
Size SDL2DisplayWindow::GetScreenSize()
{
CheckInitSDL();
SDL_Rect rect = {};
int result = SDL_GetDisplayBounds(0, &rect);
if (result != 0)
throw std::runtime_error(std::string("Unable to get screen size:") + SDL_GetError());
double uiscale = 1.0; // SDL2 doesn't really support this properly. SDL_GetDisplayDPI returns the wrong information according to the docs.
return Size(rect.w / uiscale, rect.h / uiscale);
}
void* SDL2DisplayWindow::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)
{
CheckInitSDL();
// To do: implement timers
return nullptr;
}
void SDL2DisplayWindow::StopTimer(void* timerID)
{
CheckInitSDL();
// To do: implement timers
}
SDL2DisplayWindow* SDL2DisplayWindow::FindEventWindow(const SDL_Event& event)
{
int windowID;
switch (event.type)
{
case SDL_WINDOWEVENT: windowID = event.window.windowID; break;
case SDL_TEXTINPUT: windowID = event.text.windowID; break;
case SDL_KEYUP: windowID = event.key.windowID; break;
case SDL_KEYDOWN: windowID = event.key.windowID; break;
case SDL_MOUSEBUTTONUP: windowID = event.button.windowID; break;
case SDL_MOUSEBUTTONDOWN: windowID = event.button.windowID; break;
case SDL_MOUSEWHEEL: windowID = event.wheel.windowID; break;
case SDL_MOUSEMOTION: windowID = event.motion.windowID; break;
default:
if (event.type == PaintEventNumber) windowID = event.user.windowID;
else return nullptr;
}
auto it = WindowList.find(windowID);
return it != WindowList.end() ? it->second : nullptr;
}
void SDL2DisplayWindow::DispatchEvent(const SDL_Event& event)
{
SDL2DisplayWindow* window = FindEventWindow(event);
if (!window) return;
switch (event.type)
{
case SDL_WINDOWEVENT: window->OnWindowEvent(event.window); break;
case SDL_TEXTINPUT: window->OnTextInput(event.text); break;
case SDL_KEYUP: window->OnKeyUp(event.key); break;
case SDL_KEYDOWN: window->OnKeyDown(event.key); break;
case SDL_MOUSEBUTTONUP: window->OnMouseButtonUp(event.button); break;
case SDL_MOUSEBUTTONDOWN: window->OnMouseButtonDown(event.button); break;
case SDL_MOUSEWHEEL: window->OnMouseWheel(event.wheel); break;
case SDL_MOUSEMOTION: window->OnMouseMotion(event.motion); break;
default:
if (event.type == PaintEventNumber) window->OnPaintEvent();
}
}
void SDL2DisplayWindow::OnWindowEvent(const SDL_WindowEvent& event)
{
switch (event.event)
{
case SDL_WINDOWEVENT_CLOSE: WindowHost->OnWindowClose(); break;
case SDL_WINDOWEVENT_MOVED: WindowHost->OnWindowGeometryChanged(); break;
case SDL_WINDOWEVENT_RESIZED: WindowHost->OnWindowGeometryChanged(); break;
case SDL_WINDOWEVENT_SHOWN: WindowHost->OnWindowPaint(); break;
case SDL_WINDOWEVENT_EXPOSED: WindowHost->OnWindowPaint(); break;
case SDL_WINDOWEVENT_FOCUS_GAINED: WindowHost->OnWindowActivated(); break;
case SDL_WINDOWEVENT_FOCUS_LOST: WindowHost->OnWindowDeactivated(); break;
}
}
void SDL2DisplayWindow::OnTextInput(const SDL_TextInputEvent& event)
{
WindowHost->OnWindowKeyChar(event.text);
}
void SDL2DisplayWindow::OnKeyUp(const SDL_KeyboardEvent& event)
{
WindowHost->OnWindowKeyUp(ScancodeToInputKey(event.keysym.scancode));
}
void SDL2DisplayWindow::OnKeyDown(const SDL_KeyboardEvent& event)
{
WindowHost->OnWindowKeyDown(ScancodeToInputKey(event.keysym.scancode));
}
EInputKey SDL2DisplayWindow::GetMouseButtonKey(const SDL_MouseButtonEvent& event)
{
switch (event.button)
{
case SDL_BUTTON_LEFT: return IK_LeftMouse;
case SDL_BUTTON_MIDDLE: return IK_MiddleMouse;
case SDL_BUTTON_RIGHT: return IK_RightMouse;
// case SDL_BUTTON_X1: return IK_XButton1;
// case SDL_BUTTON_X2: return IK_XButton2;
default: return IK_None;
}
}
void SDL2DisplayWindow::OnMouseButtonUp(const SDL_MouseButtonEvent& event)
{
EInputKey key = GetMouseButtonKey(event);
if (key != IK_None)
{
WindowHost->OnWindowMouseUp(GetMousePos(event), key);
}
}
void SDL2DisplayWindow::OnMouseButtonDown(const SDL_MouseButtonEvent& event)
{
EInputKey key = GetMouseButtonKey(event);
if (key != IK_None)
{
WindowHost->OnWindowMouseDown(GetMousePos(event), key);
}
}
void SDL2DisplayWindow::OnMouseWheel(const SDL_MouseWheelEvent& event)
{
EInputKey key = (event.y > 0) ? IK_MouseWheelUp : (event.y < 0) ? IK_MouseWheelDown : IK_None;
if (key != IK_None)
{
WindowHost->OnWindowMouseWheel(GetMousePos(event), key);
}
}
void SDL2DisplayWindow::OnMouseMotion(const SDL_MouseMotionEvent& event)
{
WindowHost->OnWindowMouseMove(GetMousePos(event));
}
void SDL2DisplayWindow::OnPaintEvent()
{
WindowHost->OnWindowPaint();
}
EInputKey SDL2DisplayWindow::ScancodeToInputKey(SDL_Scancode keycode)
{
switch (keycode)
{
case SDL_SCANCODE_BACKSPACE: return IK_Backspace;
case SDL_SCANCODE_TAB: return IK_Tab;
case SDL_SCANCODE_CLEAR: return IK_OEMClear;
case SDL_SCANCODE_RETURN: return IK_Enter;
case SDL_SCANCODE_MENU: return IK_Alt;
case SDL_SCANCODE_PAUSE: return IK_Pause;
case SDL_SCANCODE_ESCAPE: return IK_Escape;
case SDL_SCANCODE_SPACE: return IK_Space;
case SDL_SCANCODE_END: return IK_End;
case SDL_SCANCODE_HOME: return IK_Home;
case SDL_SCANCODE_LEFT: return IK_Left;
case SDL_SCANCODE_UP: return IK_Up;
case SDL_SCANCODE_RIGHT: return IK_Right;
case SDL_SCANCODE_DOWN: return IK_Down;
case SDL_SCANCODE_SELECT: return IK_Select;
case SDL_SCANCODE_PRINTSCREEN: return IK_Print;
case SDL_SCANCODE_EXECUTE: return IK_Execute;
case SDL_SCANCODE_INSERT: return IK_Insert;
case SDL_SCANCODE_DELETE: return IK_Delete;
case SDL_SCANCODE_HELP: return IK_Help;
case SDL_SCANCODE_0: return IK_0;
case SDL_SCANCODE_1: return IK_1;
case SDL_SCANCODE_2: return IK_2;
case SDL_SCANCODE_3: return IK_3;
case SDL_SCANCODE_4: return IK_4;
case SDL_SCANCODE_5: return IK_5;
case SDL_SCANCODE_6: return IK_6;
case SDL_SCANCODE_7: return IK_7;
case SDL_SCANCODE_8: return IK_8;
case SDL_SCANCODE_9: return IK_9;
case SDL_SCANCODE_A: return IK_A;
case SDL_SCANCODE_B: return IK_B;
case SDL_SCANCODE_C: return IK_C;
case SDL_SCANCODE_D: return IK_D;
case SDL_SCANCODE_E: return IK_E;
case SDL_SCANCODE_F: return IK_F;
case SDL_SCANCODE_G: return IK_G;
case SDL_SCANCODE_H: return IK_H;
case SDL_SCANCODE_I: return IK_I;
case SDL_SCANCODE_J: return IK_J;
case SDL_SCANCODE_K: return IK_K;
case SDL_SCANCODE_L: return IK_L;
case SDL_SCANCODE_M: return IK_M;
case SDL_SCANCODE_N: return IK_N;
case SDL_SCANCODE_O: return IK_O;
case SDL_SCANCODE_P: return IK_P;
case SDL_SCANCODE_Q: return IK_Q;
case SDL_SCANCODE_R: return IK_R;
case SDL_SCANCODE_S: return IK_S;
case SDL_SCANCODE_T: return IK_T;
case SDL_SCANCODE_U: return IK_U;
case SDL_SCANCODE_V: return IK_V;
case SDL_SCANCODE_W: return IK_W;
case SDL_SCANCODE_X: return IK_X;
case SDL_SCANCODE_Y: return IK_Y;
case SDL_SCANCODE_Z: return IK_Z;
case SDL_SCANCODE_KP_0: return IK_NumPad0;
case SDL_SCANCODE_KP_1: return IK_NumPad1;
case SDL_SCANCODE_KP_2: return IK_NumPad2;
case SDL_SCANCODE_KP_3: return IK_NumPad3;
case SDL_SCANCODE_KP_4: return IK_NumPad4;
case SDL_SCANCODE_KP_5: return IK_NumPad5;
case SDL_SCANCODE_KP_6: return IK_NumPad6;
case SDL_SCANCODE_KP_7: return IK_NumPad7;
case SDL_SCANCODE_KP_8: return IK_NumPad8;
case SDL_SCANCODE_KP_9: return IK_NumPad9;
// case SDL_SCANCODE_KP_ENTER: return IK_NumPadEnter;
// case SDL_SCANCODE_KP_MULTIPLY: return IK_Multiply;
// case SDL_SCANCODE_KP_PLUS: return IK_Add;
case SDL_SCANCODE_SEPARATOR: return IK_Separator;
// case SDL_SCANCODE_KP_MINUS: return IK_Subtract;
case SDL_SCANCODE_KP_PERIOD: return IK_NumPadPeriod;
// case SDL_SCANCODE_KP_DIVIDE: return IK_Divide;
case SDL_SCANCODE_F1: return IK_F1;
case SDL_SCANCODE_F2: return IK_F2;
case SDL_SCANCODE_F3: return IK_F3;
case SDL_SCANCODE_F4: return IK_F4;
case SDL_SCANCODE_F5: return IK_F5;
case SDL_SCANCODE_F6: return IK_F6;
case SDL_SCANCODE_F7: return IK_F7;
case SDL_SCANCODE_F8: return IK_F8;
case SDL_SCANCODE_F9: return IK_F9;
case SDL_SCANCODE_F10: return IK_F10;
case SDL_SCANCODE_F11: return IK_F11;
case SDL_SCANCODE_F12: return IK_F12;
case SDL_SCANCODE_F13: return IK_F13;
case SDL_SCANCODE_F14: return IK_F14;
case SDL_SCANCODE_F15: return IK_F15;
case SDL_SCANCODE_F16: return IK_F16;
case SDL_SCANCODE_F17: return IK_F17;
case SDL_SCANCODE_F18: return IK_F18;
case SDL_SCANCODE_F19: return IK_F19;
case SDL_SCANCODE_F20: return IK_F20;
case SDL_SCANCODE_F21: return IK_F21;
case SDL_SCANCODE_F22: return IK_F22;
case SDL_SCANCODE_F23: return IK_F23;
case SDL_SCANCODE_F24: return IK_F24;
case SDL_SCANCODE_NUMLOCKCLEAR: return IK_NumLock;
case SDL_SCANCODE_SCROLLLOCK: return IK_ScrollLock;
case SDL_SCANCODE_LSHIFT: return IK_LShift;
case SDL_SCANCODE_RSHIFT: return IK_RShift;
case SDL_SCANCODE_LCTRL: return IK_LControl;
case SDL_SCANCODE_RCTRL: return IK_RControl;
case SDL_SCANCODE_GRAVE: return IK_Tilde;
default: return IK_None;
}
}
SDL_Scancode SDL2DisplayWindow::InputKeyToScancode(EInputKey inputkey)
{
switch (inputkey)
{
case IK_Backspace: return SDL_SCANCODE_BACKSPACE;
case IK_Tab: return SDL_SCANCODE_TAB;
case IK_OEMClear: return SDL_SCANCODE_CLEAR;
case IK_Enter: return SDL_SCANCODE_RETURN;
case IK_Alt: return SDL_SCANCODE_MENU;
case IK_Pause: return SDL_SCANCODE_PAUSE;
case IK_Escape: return SDL_SCANCODE_ESCAPE;
case IK_Space: return SDL_SCANCODE_SPACE;
case IK_End: return SDL_SCANCODE_END;
case IK_Home: return SDL_SCANCODE_HOME;
case IK_Left: return SDL_SCANCODE_LEFT;
case IK_Up: return SDL_SCANCODE_UP;
case IK_Right: return SDL_SCANCODE_RIGHT;
case IK_Down: return SDL_SCANCODE_DOWN;
case IK_Select: return SDL_SCANCODE_SELECT;
case IK_Print: return SDL_SCANCODE_PRINTSCREEN;
case IK_Execute: return SDL_SCANCODE_EXECUTE;
case IK_Insert: return SDL_SCANCODE_INSERT;
case IK_Delete: return SDL_SCANCODE_DELETE;
case IK_Help: return SDL_SCANCODE_HELP;
case IK_0: return SDL_SCANCODE_0;
case IK_1: return SDL_SCANCODE_1;
case IK_2: return SDL_SCANCODE_2;
case IK_3: return SDL_SCANCODE_3;
case IK_4: return SDL_SCANCODE_4;
case IK_5: return SDL_SCANCODE_5;
case IK_6: return SDL_SCANCODE_6;
case IK_7: return SDL_SCANCODE_7;
case IK_8: return SDL_SCANCODE_8;
case IK_9: return SDL_SCANCODE_9;
case IK_A: return SDL_SCANCODE_A;
case IK_B: return SDL_SCANCODE_B;
case IK_C: return SDL_SCANCODE_C;
case IK_D: return SDL_SCANCODE_D;
case IK_E: return SDL_SCANCODE_E;
case IK_F: return SDL_SCANCODE_F;
case IK_G: return SDL_SCANCODE_G;
case IK_H: return SDL_SCANCODE_H;
case IK_I: return SDL_SCANCODE_I;
case IK_J: return SDL_SCANCODE_J;
case IK_K: return SDL_SCANCODE_K;
case IK_L: return SDL_SCANCODE_L;
case IK_M: return SDL_SCANCODE_M;
case IK_N: return SDL_SCANCODE_N;
case IK_O: return SDL_SCANCODE_O;
case IK_P: return SDL_SCANCODE_P;
case IK_Q: return SDL_SCANCODE_Q;
case IK_R: return SDL_SCANCODE_R;
case IK_S: return SDL_SCANCODE_S;
case IK_T: return SDL_SCANCODE_T;
case IK_U: return SDL_SCANCODE_U;
case IK_V: return SDL_SCANCODE_V;
case IK_W: return SDL_SCANCODE_W;
case IK_X: return SDL_SCANCODE_X;
case IK_Y: return SDL_SCANCODE_Y;
case IK_Z: return SDL_SCANCODE_Z;
case IK_NumPad0: return SDL_SCANCODE_KP_0;
case IK_NumPad1: return SDL_SCANCODE_KP_1;
case IK_NumPad2: return SDL_SCANCODE_KP_2;
case IK_NumPad3: return SDL_SCANCODE_KP_3;
case IK_NumPad4: return SDL_SCANCODE_KP_4;
case IK_NumPad5: return SDL_SCANCODE_KP_5;
case IK_NumPad6: return SDL_SCANCODE_KP_6;
case IK_NumPad7: return SDL_SCANCODE_KP_7;
case IK_NumPad8: return SDL_SCANCODE_KP_8;
case IK_NumPad9: return SDL_SCANCODE_KP_9;
// case IK_NumPadEnter: return SDL_SCANCODE_KP_ENTER;
// case IK_Multiply return SDL_SCANCODE_KP_MULTIPLY:;
// case IK_Add: return SDL_SCANCODE_KP_PLUS;
case IK_Separator: return SDL_SCANCODE_SEPARATOR;
// case IK_Subtract: return SDL_SCANCODE_KP_MINUS;
case IK_NumPadPeriod: return SDL_SCANCODE_KP_PERIOD;
// case IK_Divide: return SDL_SCANCODE_KP_DIVIDE;
case IK_F1: return SDL_SCANCODE_F1;
case IK_F2: return SDL_SCANCODE_F2;
case IK_F3: return SDL_SCANCODE_F3;
case IK_F4: return SDL_SCANCODE_F4;
case IK_F5: return SDL_SCANCODE_F5;
case IK_F6: return SDL_SCANCODE_F6;
case IK_F7: return SDL_SCANCODE_F7;
case IK_F8: return SDL_SCANCODE_F8;
case IK_F9: return SDL_SCANCODE_F9;
case IK_F10: return SDL_SCANCODE_F10;
case IK_F11: return SDL_SCANCODE_F11;
case IK_F12: return SDL_SCANCODE_F12;
case IK_F13: return SDL_SCANCODE_F13;
case IK_F14: return SDL_SCANCODE_F14;
case IK_F15: return SDL_SCANCODE_F15;
case IK_F16: return SDL_SCANCODE_F16;
case IK_F17: return SDL_SCANCODE_F17;
case IK_F18: return SDL_SCANCODE_F18;
case IK_F19: return SDL_SCANCODE_F19;
case IK_F20: return SDL_SCANCODE_F20;
case IK_F21: return SDL_SCANCODE_F21;
case IK_F22: return SDL_SCANCODE_F22;
case IK_F23: return SDL_SCANCODE_F23;
case IK_F24: return SDL_SCANCODE_F24;
case IK_NumLock: return SDL_SCANCODE_NUMLOCKCLEAR;
case IK_ScrollLock: return SDL_SCANCODE_SCROLLLOCK;
case IK_LShift: return SDL_SCANCODE_LSHIFT;
case IK_RShift: return SDL_SCANCODE_RSHIFT;
case IK_LControl: return SDL_SCANCODE_LCTRL;
case IK_RControl: return SDL_SCANCODE_RCTRL;
case IK_Tilde: return SDL_SCANCODE_GRAVE;
default: return (SDL_Scancode)0;
}
}

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

@ -0,0 +1,788 @@
#include "win32_display_window.h"
#include <windowsx.h>
#include <stdexcept>
#include <cmath>
#include <vector>
#include <dwmapi.h>
#pragma comment(lib, "dwmapi.lib")
#ifndef HID_USAGE_PAGE_GENERIC
#define HID_USAGE_PAGE_GENERIC ((USHORT) 0x01)
#endif
#ifndef HID_USAGE_GENERIC_MOUSE
#define HID_USAGE_GENERIC_MOUSE ((USHORT) 0x02)
#endif
#ifndef HID_USAGE_GENERIC_JOYSTICK
#define HID_USAGE_GENERIC_JOYSTICK ((USHORT) 0x04)
#endif
#ifndef HID_USAGE_GENERIC_GAMEPAD
#define HID_USAGE_GENERIC_GAMEPAD ((USHORT) 0x05)
#endif
#ifndef RIDEV_INPUTSINK
#define RIDEV_INPUTSINK (0x100)
#endif
Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner, RenderAPI renderAPI) : WindowHost(windowHost), PopupWindow(popupWindow)
{
Windows.push_front(this);
WindowsIterator = Windows.begin();
WNDCLASSEX classdesc = {};
classdesc.cbSize = sizeof(WNDCLASSEX);
classdesc.hInstance = GetModuleHandle(0);
classdesc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
classdesc.lpszClassName = L"ZWidgetWindow";
classdesc.lpfnWndProc = &Win32DisplayWindow::WndProc;
RegisterClassEx(&classdesc);
// Microsoft logic at its finest:
// WS_EX_DLGMODALFRAME hides the sysmenu icon
// WS_CAPTION shows the caption (yay! actually a flag that does what it says it does!)
// WS_SYSMENU shows the min/max/close buttons
// WS_THICKFRAME makes the window resizable
DWORD style = 0, exstyle = 0;
if (popupWindow)
{
exstyle = WS_EX_NOACTIVATE;
style = WS_POPUP;
}
else
{
exstyle = WS_EX_APPWINDOW | WS_EX_DLGMODALFRAME;
style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
}
CreateWindowEx(exstyle, L"ZWidgetWindow", L"", style, 0, 0, 100, 100, owner ? owner->WindowHandle.hwnd : 0, 0, GetModuleHandle(0), this);
}
Win32DisplayWindow::~Win32DisplayWindow()
{
if (WindowHandle.hwnd)
{
DestroyWindow(WindowHandle.hwnd);
WindowHandle.hwnd = 0;
}
Windows.erase(WindowsIterator);
}
void Win32DisplayWindow::SetWindowTitle(const std::string& text)
{
SetWindowText(WindowHandle.hwnd, to_utf16(text).c_str());
}
void Win32DisplayWindow::SetBorderColor(uint32_t bgra8)
{
bgra8 = bgra8 & 0x00ffffff;
DwmSetWindowAttribute(WindowHandle.hwnd, 34/*DWMWA_BORDER_COLOR*/, &bgra8, sizeof(uint32_t));
}
void Win32DisplayWindow::SetCaptionColor(uint32_t bgra8)
{
bgra8 = bgra8 & 0x00ffffff;
DwmSetWindowAttribute(WindowHandle.hwnd, 35/*DWMWA_CAPTION_COLOR*/, &bgra8, sizeof(uint32_t));
}
void Win32DisplayWindow::SetCaptionTextColor(uint32_t bgra8)
{
bgra8 = bgra8 & 0x00ffffff;
DwmSetWindowAttribute(WindowHandle.hwnd, 36/*DWMWA_TEXT_COLOR*/, &bgra8, sizeof(uint32_t));
}
void Win32DisplayWindow::SetWindowFrame(const Rect& box)
{
double dpiscale = GetDpiScale();
SetWindowPos(WindowHandle.hwnd, nullptr, (int)std::round(box.x * dpiscale), (int)std::round(box.y * dpiscale), (int)std::round(box.width * dpiscale), (int)std::round(box.height * dpiscale), SWP_NOACTIVATE | SWP_NOZORDER);
}
void Win32DisplayWindow::SetClientFrame(const Rect& box)
{
double dpiscale = GetDpiScale();
RECT rect = {};
rect.left = (int)std::round(box.x * dpiscale);
rect.top = (int)std::round(box.y * dpiscale);
rect.right = rect.left + (int)std::round(box.width * dpiscale);
rect.bottom = rect.top + (int)std::round(box.height * dpiscale);
DWORD style = (DWORD)GetWindowLongPtr(WindowHandle.hwnd, GWL_STYLE);
DWORD exstyle = (DWORD)GetWindowLongPtr(WindowHandle.hwnd, GWL_EXSTYLE);
AdjustWindowRectExForDpi(&rect, style, FALSE, exstyle, GetDpiForWindow(WindowHandle.hwnd));
SetWindowPos(WindowHandle.hwnd, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER);
}
void Win32DisplayWindow::Show()
{
ShowWindow(WindowHandle.hwnd, PopupWindow ? SW_SHOWNA : SW_SHOW);
}
void Win32DisplayWindow::ShowFullscreen()
{
HDC screenDC = GetDC(0);
int width = GetDeviceCaps(screenDC, HORZRES);
int height = GetDeviceCaps(screenDC, VERTRES);
ReleaseDC(0, screenDC);
DWORD dwStyle = GetWindowLong(WindowHandle.hwnd, GWL_STYLE);
SetWindowLongPtr(WindowHandle.hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW);
SetWindowLongPtr(WindowHandle.hwnd, GWL_STYLE, dwStyle & ~WS_OVERLAPPEDWINDOW);
SetWindowPos(WindowHandle.hwnd, HWND_TOP, 0, 0, width, height, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
Fullscreen = true;
}
void Win32DisplayWindow::ShowMaximized()
{
ShowWindow(WindowHandle.hwnd, SW_SHOWMAXIMIZED);
}
void Win32DisplayWindow::ShowMinimized()
{
ShowWindow(WindowHandle.hwnd, SW_SHOWMINIMIZED);
}
void Win32DisplayWindow::ShowNormal()
{
if (Fullscreen)
{
SetWindowLongPtr(WindowHandle.hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
Fullscreen = false;
}
ShowWindow(WindowHandle.hwnd, SW_NORMAL);
}
bool Win32DisplayWindow::IsWindowFullscreen()
{
return Fullscreen;
}
void Win32DisplayWindow::Hide()
{
ShowWindow(WindowHandle.hwnd, SW_HIDE);
}
void Win32DisplayWindow::Activate()
{
if (!PopupWindow)
SetFocus(WindowHandle.hwnd);
}
void Win32DisplayWindow::ShowCursor(bool enable)
{
}
void Win32DisplayWindow::LockCursor()
{
if (!MouseLocked)
{
MouseLocked = true;
GetCursorPos(&MouseLockPos);
::ShowCursor(FALSE);
RAWINPUTDEVICE rid = {};
rid.usUsagePage = HID_USAGE_PAGE_GENERIC;
rid.usUsage = HID_USAGE_GENERIC_MOUSE;
rid.dwFlags = RIDEV_INPUTSINK;
rid.hwndTarget = WindowHandle.hwnd;
RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE));
}
}
void Win32DisplayWindow::UnlockCursor()
{
if (MouseLocked)
{
RAWINPUTDEVICE rid = {};
rid.usUsagePage = HID_USAGE_PAGE_GENERIC;
rid.usUsage = HID_USAGE_GENERIC_MOUSE;
rid.dwFlags = RIDEV_REMOVE;
rid.hwndTarget = 0;
RegisterRawInputDevices(&rid, 1, sizeof(rid));
MouseLocked = false;
SetCursorPos(MouseLockPos.x, MouseLockPos.y);
::ShowCursor(TRUE);
}
}
void Win32DisplayWindow::CaptureMouse()
{
SetCapture(WindowHandle.hwnd);
}
void Win32DisplayWindow::ReleaseMouseCapture()
{
ReleaseCapture();
}
void Win32DisplayWindow::Update()
{
InvalidateRect(WindowHandle.hwnd, nullptr, FALSE);
}
bool Win32DisplayWindow::GetKeyState(InputKey key)
{
return ::GetKeyState((int)key) & 0x8000; // High bit (0x8000) means key is down, Low bit (0x0001) means key is sticky on (like Caps Lock, Num Lock, etc.)
}
void Win32DisplayWindow::SetCursor(StandardCursor cursor)
{
if (cursor != CurrentCursor)
{
CurrentCursor = cursor;
UpdateCursor();
}
}
Rect Win32DisplayWindow::GetWindowFrame() const
{
RECT box = {};
GetWindowRect(WindowHandle.hwnd, &box);
double dpiscale = GetDpiScale();
return Rect(box.left / dpiscale, box.top / dpiscale, (box.right - box.left) / dpiscale, (box.bottom - box.top) / dpiscale);
}
Point Win32DisplayWindow::MapFromGlobal(const Point& pos) const
{
double dpiscale = GetDpiScale();
POINT point = {};
point.x = (LONG)std::round(pos.x / dpiscale);
point.y = (LONG)std::round(pos.y / dpiscale);
ScreenToClient(WindowHandle.hwnd, &point);
return Point(point.x * dpiscale, point.y * dpiscale);
}
Point Win32DisplayWindow::MapToGlobal(const Point& pos) const
{
double dpiscale = GetDpiScale();
POINT point = {};
point.x = (LONG)std::round(pos.x * dpiscale);
point.y = (LONG)std::round(pos.y * dpiscale);
ClientToScreen(WindowHandle.hwnd, &point);
return Point(point.x / dpiscale, point.y / dpiscale);
}
Size Win32DisplayWindow::GetClientSize() const
{
RECT box = {};
GetClientRect(WindowHandle.hwnd, &box);
double dpiscale = GetDpiScale();
return Size(box.right / dpiscale, box.bottom / dpiscale);
}
int Win32DisplayWindow::GetPixelWidth() const
{
RECT box = {};
GetClientRect(WindowHandle.hwnd, &box);
return box.right;
}
int Win32DisplayWindow::GetPixelHeight() const
{
RECT box = {};
GetClientRect(WindowHandle.hwnd, &box);
return box.bottom;
}
double Win32DisplayWindow::GetDpiScale() const
{
return GetDpiForWindow(WindowHandle.hwnd) / 96.0;
}
std::string Win32DisplayWindow::GetClipboardText()
{
BOOL result = OpenClipboard(WindowHandle.hwnd);
if (result == FALSE)
throw std::runtime_error("Unable to open clipboard");
HANDLE handle = GetClipboardData(CF_UNICODETEXT);
if (handle == 0)
{
CloseClipboard();
return std::string();
}
std::wstring::value_type* data = (std::wstring::value_type*)GlobalLock(handle);
if (data == 0)
{
CloseClipboard();
return std::string();
}
std::string str = from_utf16(data);
GlobalUnlock(handle);
CloseClipboard();
return str;
}
void Win32DisplayWindow::SetClipboardText(const std::string& text)
{
std::wstring text16 = to_utf16(text);
BOOL result = OpenClipboard(WindowHandle.hwnd);
if (result == FALSE)
throw std::runtime_error("Unable to open clipboard");
result = EmptyClipboard();
if (result == FALSE)
{
CloseClipboard();
throw std::runtime_error("Unable to empty clipboard");
}
unsigned int length = (unsigned int)((text16.length() + 1) * sizeof(std::wstring::value_type));
HANDLE handle = GlobalAlloc(GMEM_MOVEABLE, length);
if (handle == 0)
{
CloseClipboard();
throw std::runtime_error("Unable to allocate clipboard memory");
}
void* data = GlobalLock(handle);
if (data == 0)
{
GlobalFree(handle);
CloseClipboard();
throw std::runtime_error("Unable to lock clipboard memory");
}
memcpy(data, text16.c_str(), length);
GlobalUnlock(handle);
HANDLE data_result = SetClipboardData(CF_UNICODETEXT, handle);
if (data_result == 0)
{
GlobalFree(handle);
CloseClipboard();
throw std::runtime_error("Unable to set clipboard data");
}
CloseClipboard();
}
void Win32DisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels)
{
BITMAPV5HEADER header = {};
header.bV5Size = sizeof(BITMAPV5HEADER);
header.bV5Width = width;
header.bV5Height = -height;
header.bV5Planes = 1;
header.bV5BitCount = 32;
header.bV5Compression = BI_BITFIELDS;
header.bV5AlphaMask = 0xff000000;
header.bV5RedMask = 0x00ff0000;
header.bV5GreenMask = 0x0000ff00;
header.bV5BlueMask = 0x000000ff;
header.bV5SizeImage = width * height * sizeof(uint32_t);
header.bV5CSType = LCS_sRGB;
HDC dc = PaintDC;
if (dc != 0)
{
SetDIBitsToDevice(dc, 0, 0, width, height, 0, 0, 0, height, pixels, (const BITMAPINFO*)&header, BI_RGB);
}
}
LRESULT Win32DisplayWindow::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lparam)
{
LPARAM result = 0;
if (DwmDefWindowProc(WindowHandle.hwnd, msg, wparam, lparam, &result))
return result;
if (msg == WM_INPUT)
{
bool hasFocus = GetFocus() != 0;
HRAWINPUT handle = (HRAWINPUT)lparam;
UINT size = 0;
UINT result = GetRawInputData(handle, RID_INPUT, 0, &size, sizeof(RAWINPUTHEADER));
if (result == 0 && size > 0)
{
size *= 2;
std::vector<uint8_t*> buffer(size);
result = GetRawInputData(handle, RID_INPUT, buffer.data(), &size, sizeof(RAWINPUTHEADER));
if (result >= 0)
{
RAWINPUT* rawinput = (RAWINPUT*)buffer.data();
if (rawinput->header.dwType == RIM_TYPEMOUSE)
{
if (hasFocus)
WindowHost->OnWindowRawMouseMove(rawinput->data.mouse.lLastX, rawinput->data.mouse.lLastY);
}
}
}
return DefWindowProc(WindowHandle.hwnd, msg, wparam, lparam);
}
else if (msg == WM_PAINT)
{
PAINTSTRUCT paintStruct = {};
PaintDC = BeginPaint(WindowHandle.hwnd, &paintStruct);
if (PaintDC)
{
WindowHost->OnWindowPaint();
EndPaint(WindowHandle.hwnd, &paintStruct);
PaintDC = 0;
}
return 0;
}
else if (msg == WM_ACTIVATE)
{
WindowHost->OnWindowActivated();
}
else if (msg == WM_MOUSEACTIVATE)
{
// We don't want to activate the window on mouse clicks as that changes the focus from the popup owner to the popup itself
if (PopupWindow)
return MA_NOACTIVATE;
}
else if (msg == WM_MOUSEMOVE)
{
if (MouseLocked && GetFocus() != 0)
{
RECT box = {};
GetClientRect(WindowHandle.hwnd, &box);
POINT center = {};
center.x = box.right / 2;
center.y = box.bottom / 2;
ClientToScreen(WindowHandle.hwnd, &center);
SetCursorPos(center.x, center.y);
}
else
{
UpdateCursor();
}
if (!TrackMouseActive)
{
TRACKMOUSEEVENT eventTrack = {};
eventTrack.cbSize = sizeof(TRACKMOUSEEVENT);
eventTrack.hwndTrack = WindowHandle.hwnd;
eventTrack.dwFlags = TME_LEAVE;
if (TrackMouseEvent(&eventTrack))
TrackMouseActive = true;
}
WindowHost->OnWindowMouseMove(GetLParamPos(lparam));
}
else if (msg == WM_MOUSELEAVE)
{
TrackMouseActive = false;
WindowHost->OnWindowMouseLeave();
}
else if (msg == WM_LBUTTONDOWN)
{
WindowHost->OnWindowMouseDown(GetLParamPos(lparam), InputKey::LeftMouse);
}
else if (msg == WM_LBUTTONDBLCLK)
{
WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), InputKey::LeftMouse);
}
else if (msg == WM_LBUTTONUP)
{
WindowHost->OnWindowMouseUp(GetLParamPos(lparam), InputKey::LeftMouse);
}
else if (msg == WM_MBUTTONDOWN)
{
WindowHost->OnWindowMouseDown(GetLParamPos(lparam), InputKey::MiddleMouse);
}
else if (msg == WM_MBUTTONDBLCLK)
{
WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), InputKey::MiddleMouse);
}
else if (msg == WM_MBUTTONUP)
{
WindowHost->OnWindowMouseUp(GetLParamPos(lparam), InputKey::MiddleMouse);
}
else if (msg == WM_RBUTTONDOWN)
{
WindowHost->OnWindowMouseDown(GetLParamPos(lparam), InputKey::RightMouse);
}
else if (msg == WM_RBUTTONDBLCLK)
{
WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), InputKey::RightMouse);
}
else if (msg == WM_RBUTTONUP)
{
WindowHost->OnWindowMouseUp(GetLParamPos(lparam), InputKey::RightMouse);
}
else if (msg == WM_MOUSEWHEEL)
{
double delta = GET_WHEEL_DELTA_WPARAM(wparam) / (double)WHEEL_DELTA;
// Note: WM_MOUSEWHEEL uses screen coordinates. GetLParamPos assumes client coordinates.
double dpiscale = GetDpiScale();
POINT pos;
pos.x = GET_X_LPARAM(lparam);
pos.y = GET_Y_LPARAM(lparam);
ScreenToClient(WindowHandle.hwnd, &pos);
WindowHost->OnWindowMouseWheel(Point(pos.x / dpiscale, pos.y / dpiscale), delta < 0.0 ? InputKey::MouseWheelDown : InputKey::MouseWheelUp);
}
else if (msg == WM_CHAR)
{
wchar_t buf[2] = { (wchar_t)wparam, 0 };
WindowHost->OnWindowKeyChar(from_utf16(buf));
}
else if (msg == WM_KEYDOWN)
{
WindowHost->OnWindowKeyDown((InputKey)wparam);
}
else if (msg == WM_KEYUP)
{
WindowHost->OnWindowKeyUp((InputKey)wparam);
}
else if (msg == WM_SETFOCUS)
{
if (MouseLocked)
{
::ShowCursor(FALSE);
}
}
else if (msg == WM_KILLFOCUS)
{
if (MouseLocked)
{
::ShowCursor(TRUE);
}
}
else if (msg == WM_CLOSE)
{
WindowHost->OnWindowClose();
return 0;
}
else if (msg == WM_SIZE)
{
WindowHost->OnWindowGeometryChanged();
return 0;
}
/*else if (msg == WM_NCCALCSIZE && wparam == TRUE) // calculate client area for the window
{
NCCALCSIZE_PARAMS* calcsize = (NCCALCSIZE_PARAMS*)lparam;
return WVR_REDRAW;
}*/
return DefWindowProc(WindowHandle.hwnd, msg, wparam, lparam);
}
void Win32DisplayWindow::UpdateCursor()
{
LPCWSTR cursor = IDC_ARROW;
switch (CurrentCursor)
{
case StandardCursor::arrow: cursor = IDC_ARROW; break;
case StandardCursor::appstarting: cursor = IDC_APPSTARTING; break;
case StandardCursor::cross: cursor = IDC_CROSS; break;
case StandardCursor::hand: cursor = IDC_HAND; break;
case StandardCursor::ibeam: cursor = IDC_IBEAM; break;
case StandardCursor::no: cursor = IDC_NO; break;
case StandardCursor::size_all: cursor = IDC_SIZEALL; break;
case StandardCursor::size_nesw: cursor = IDC_SIZENESW; break;
case StandardCursor::size_ns: cursor = IDC_SIZENS; break;
case StandardCursor::size_nwse: cursor = IDC_SIZENWSE; break;
case StandardCursor::size_we: cursor = IDC_SIZEWE; break;
case StandardCursor::uparrow: cursor = IDC_UPARROW; break;
case StandardCursor::wait: cursor = IDC_WAIT; break;
default: break;
}
::SetCursor((HCURSOR)LoadImage(0, cursor, IMAGE_CURSOR, LR_DEFAULTSIZE, LR_DEFAULTSIZE, LR_SHARED));
}
Point Win32DisplayWindow::GetLParamPos(LPARAM lparam) const
{
double dpiscale = GetDpiScale();
return Point(GET_X_LPARAM(lparam) / dpiscale, GET_Y_LPARAM(lparam) / dpiscale);
}
LRESULT Win32DisplayWindow::WndProc(HWND windowhandle, UINT msg, WPARAM wparam, LPARAM lparam)
{
if (msg == WM_CREATE)
{
CREATESTRUCT* createstruct = (CREATESTRUCT*)lparam;
Win32DisplayWindow* viewport = (Win32DisplayWindow*)createstruct->lpCreateParams;
viewport->WindowHandle.hwnd = windowhandle;
SetWindowLongPtr(windowhandle, GWLP_USERDATA, (LONG_PTR)viewport);
return viewport->OnWindowMessage(msg, wparam, lparam);
}
else
{
Win32DisplayWindow* viewport = (Win32DisplayWindow*)GetWindowLongPtr(windowhandle, GWLP_USERDATA);
if (viewport)
{
LRESULT result = viewport->OnWindowMessage(msg, wparam, lparam);
if (msg == WM_DESTROY)
{
SetWindowLongPtr(windowhandle, GWLP_USERDATA, 0);
viewport->WindowHandle.hwnd = 0;
}
return result;
}
else
{
return DefWindowProc(windowhandle, msg, wparam, lparam);
}
}
}
void Win32DisplayWindow::ProcessEvents()
{
while (true)
{
MSG msg = {};
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE) <= 0)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
void Win32DisplayWindow::RunLoop()
{
while (!ExitRunLoop && !Windows.empty())
{
MSG msg = {};
if (GetMessage(&msg, 0, 0, 0) <= 0)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
ExitRunLoop = false;
}
void Win32DisplayWindow::ExitLoop()
{
ExitRunLoop = true;
}
Size Win32DisplayWindow::GetScreenSize()
{
HDC screenDC = GetDC(0);
int screenWidth = GetDeviceCaps(screenDC, HORZRES);
int screenHeight = GetDeviceCaps(screenDC, VERTRES);
double dpiScale = GetDeviceCaps(screenDC, LOGPIXELSX) / 96.0;
ReleaseDC(0, screenDC);
return Size(screenWidth / dpiScale, screenHeight / dpiScale);
}
// This is to avoid needing all the Vulkan headers and the volk binding library just for this:
#ifndef VK_VERSION_1_0
#define VKAPI_CALL __stdcall
#define VKAPI_PTR VKAPI_CALL
typedef uint32_t VkFlags;
typedef enum VkStructureType { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000, VK_OBJECT_TYPE_MAX_ENUM = 0x7FFFFFFF } VkStructureType;
typedef enum VkResult { VK_SUCCESS = 0, VK_RESULT_MAX_ENUM = 0x7FFFFFFF } VkResult;
typedef struct VkAllocationCallbacks VkAllocationCallbacks;
typedef void (VKAPI_PTR* PFN_vkVoidFunction)(void);
typedef PFN_vkVoidFunction(VKAPI_PTR* PFN_vkGetInstanceProcAddr)(VkInstance instance, const char* pName);
#ifndef VK_KHR_win32_surface
typedef VkFlags VkWin32SurfaceCreateFlagsKHR;
typedef struct VkWin32SurfaceCreateInfoKHR
{
VkStructureType sType;
const void* pNext;
VkWin32SurfaceCreateFlagsKHR flags;
HINSTANCE hinstance;
HWND hwnd;
} VkWin32SurfaceCreateInfoKHR;
typedef VkResult(VKAPI_PTR* PFN_vkCreateWin32SurfaceKHR)(VkInstance instance, const VkWin32SurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
#endif
#endif
class ZWidgetVulkanLoader
{
public:
ZWidgetVulkanLoader()
{
module = LoadLibraryA("vulkan-1.dll");
if (!module)
throw std::runtime_error("Could not load vulkan-1.dll");
vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)(void(*)(void))GetProcAddress(module, "vkGetInstanceProcAddr");
if (!vkGetInstanceProcAddr)
{
FreeLibrary(module);
throw std::runtime_error("vkGetInstanceProcAddr not found in vulkan-1.dll");
}
}
~ZWidgetVulkanLoader()
{
FreeLibrary(module);
}
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr;
HMODULE module = {};
};
VkSurfaceKHR Win32DisplayWindow::CreateVulkanSurface(VkInstance instance)
{
static ZWidgetVulkanLoader loader;
auto vkCreateWin32SurfaceKHR = (PFN_vkCreateWin32SurfaceKHR)loader.vkGetInstanceProcAddr(instance, "vkCreateWin32SurfaceKHR");
if (!vkCreateWin32SurfaceKHR)
throw std::runtime_error("Could not create vulkan surface");
VkWin32SurfaceCreateInfoKHR createInfo = { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR };
createInfo.hwnd = WindowHandle.hwnd;
createInfo.hinstance = GetModuleHandle(nullptr);
VkSurfaceKHR surface = {};
VkResult result = vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface);
if (result != VK_SUCCESS)
throw std::runtime_error("Could not create vulkan surface");
return surface;
}
std::vector<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);
if (it != Win32DisplayWindow::Timers.end())
{
auto callback = it->second;
callback();
}
}
void* Win32DisplayWindow::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)
{
UINT_PTR result = SetTimer(0, 0, timeoutMilliseconds, Win32TimerCallback);
if (result == 0)
throw std::runtime_error("Could not create timer");
Timers[result] = std::move(onTimer);
return (void*)result;
}
void Win32DisplayWindow::StopTimer(void* timerID)
{
auto it = Timers.find((UINT_PTR)timerID);
if (it != Timers.end())
{
Timers.erase(it);
KillTimer(0, (UINT_PTR)timerID);
}
}
std::list<Win32DisplayWindow*> Win32DisplayWindow::Windows;
bool Win32DisplayWindow::ExitRunLoop;
std::unordered_map<UINT_PTR, std::function<void()>> Win32DisplayWindow::Timers;

View file

@ -1,21 +1,17 @@
#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 Win32Window : public DisplayWindow
class Win32DisplayWindow : public DisplayWindow
{
public:
Win32Window(DisplayWindowHost* windowHost);
~Win32Window();
Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner, RenderAPI renderAPI);
~Win32DisplayWindow();
void SetWindowTitle(const std::string& text) override;
void SetWindowFrame(const Rect& box) override;
@ -25,6 +21,7 @@ public:
void ShowMaximized() override;
void ShowMinimized() override;
void ShowNormal() override;
bool IsWindowFullscreen() override;
void Hide() override;
void Activate() override;
void ShowCursor(bool enable) override;
@ -33,7 +30,7 @@ public:
void CaptureMouse() override;
void ReleaseMouseCapture() override;
void Update() override;
bool GetKeyState(EInputKey key) override;
bool GetKeyState(InputKey key) override;
void SetCursor(StandardCursor cursor) override;
void UpdateCursor();
@ -53,8 +50,16 @@ public:
std::string GetClipboardText() override;
void SetClipboardText(const std::string& text) override;
Point MapFromGlobal(const Point& pos) const override;
Point MapToGlobal(const Point& pos) const override;
Point GetLParamPos(LPARAM lparam) const;
void* GetNativeHandle() override { return &WindowHandle; }
std::vector<std::string> GetVulkanInstanceExtensions() override;
VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override;
static void ProcessEvents();
static void RunLoop();
static void ExitLoop();
@ -64,8 +69,8 @@ public:
static void StopTimer(void* timerID);
static bool ExitRunLoop;
static std::list<Win32Window*> Windows;
std::list<Win32Window*>::iterator WindowsIterator;
static std::list<Win32DisplayWindow*> Windows;
std::list<Win32DisplayWindow*>::iterator WindowsIterator;
static std::unordered_map<UINT_PTR, std::function<void()>> Timers;
@ -73,13 +78,16 @@ public:
static LRESULT CALLBACK WndProc(HWND windowhandle, UINT msg, WPARAM wparam, LPARAM lparam);
DisplayWindowHost* WindowHost = nullptr;
bool PopupWindow = false;
HWND WindowHandle = 0;
Win32NativeHandle WindowHandle;
bool Fullscreen = false;
bool MouseLocked = false;
POINT MouseLockPos = {};
bool TrackMouseActive = false;
HDC PaintDC = 0;
StandardCursor CurrentCursor = StandardCursor::arrow;

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,702 +0,0 @@
#include "win32window.h"
#include <windowsx.h>
#include <stdexcept>
#include <cmath>
#include <vector>
#include <dwmapi.h>
#pragma comment(lib, "dwmapi.lib")
#ifndef HID_USAGE_PAGE_GENERIC
#define HID_USAGE_PAGE_GENERIC ((USHORT) 0x01)
#endif
#ifndef HID_USAGE_GENERIC_MOUSE
#define HID_USAGE_GENERIC_MOUSE ((USHORT) 0x02)
#endif
#ifndef HID_USAGE_GENERIC_JOYSTICK
#define HID_USAGE_GENERIC_JOYSTICK ((USHORT) 0x04)
#endif
#ifndef HID_USAGE_GENERIC_GAMEPAD
#define HID_USAGE_GENERIC_GAMEPAD ((USHORT) 0x05)
#endif
#ifndef RIDEV_INPUTSINK
#define RIDEV_INPUTSINK (0x100)
#endif
#ifdef MINGW
// MinGW's library doesn't contain a thunk for DwmDefWindowProc, so we need to create our own
BOOL DwmDefWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, LRESULT *plResult )
{
typedef BOOL(* dwmdwp)(HWND, UINT, WPARAM, LPARAM, LRESULT* );
BOOL result(FALSE);
HMODULE module = LoadLibrary( _T( "dwmapi.dll" ) );
if( module ) {
dwmdwp proc = reinterpret_cast<dwmdwp>( GetProcAddress( module, "DwmDefWindowProc" ) );
if( proc ) {
result = proc( hWnd, msg, wParam, lParam, plResult );
}
FreeLibrary(module);
}
return result;
}
#endif
static std::string from_utf16(const std::wstring& str)
{
if (str.empty()) return {};
int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr);
if (needed == 0)
throw std::runtime_error("WideCharToMultiByte failed");
std::string result;
result.resize(needed);
needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr);
if (needed == 0)
throw std::runtime_error("WideCharToMultiByte failed");
return result;
}
static std::wstring to_utf16(const std::string& str)
{
if (str.empty()) return {};
int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);
if (needed == 0)
throw std::runtime_error("MultiByteToWideChar failed");
std::wstring result;
result.resize(needed);
needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size());
if (needed == 0)
throw std::runtime_error("MultiByteToWideChar failed");
return result;
}
Win32Window::Win32Window(DisplayWindowHost* windowHost) : WindowHost(windowHost)
{
Windows.push_front(this);
WindowsIterator = Windows.begin();
WNDCLASSEXW classdesc = {};
classdesc.cbSize = sizeof(WNDCLASSEX);
classdesc.hInstance = GetModuleHandle(0);
classdesc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
classdesc.lpszClassName = L"ZWidgetWindow";
classdesc.lpfnWndProc = &Win32Window::WndProc;
RegisterClassEx(&classdesc);
// Microsoft logic at its finest:
// WS_EX_DLGMODALFRAME hides the sysmenu icon
// WS_CAPTION shows the caption (yay! actually a flag that does what it says it does!)
// WS_SYSMENU shows the min/max/close buttons
// WS_THICKFRAME makes the window resizable
CreateWindowExW(WS_EX_APPWINDOW | WS_EX_DLGMODALFRAME, L"ZWidgetWindow", L"", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, 0, 0, 100, 100, 0, 0, GetModuleHandle(0), this);
/*
RAWINPUTDEVICE rid;
rid.usUsagePage = HID_USAGE_PAGE_GENERIC;
rid.usUsage = HID_USAGE_GENERIC_MOUSE;
rid.dwFlags = RIDEV_INPUTSINK;
rid.hwndTarget = WindowHandle;
BOOL result = RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE));
*/
}
Win32Window::~Win32Window()
{
if (WindowHandle)
{
DestroyWindow(WindowHandle);
WindowHandle = 0;
}
Windows.erase(WindowsIterator);
}
void Win32Window::SetWindowTitle(const std::string& text)
{
SetWindowText(WindowHandle, to_utf16(text).c_str());
}
void Win32Window::SetBorderColor(uint32_t bgra8)
{
bgra8 = bgra8 & 0x00ffffff;
DwmSetWindowAttribute(WindowHandle, 34/*DWMWA_BORDER_COLOR*/, &bgra8, sizeof(uint32_t));
}
void Win32Window::SetCaptionColor(uint32_t bgra8)
{
bgra8 = bgra8 & 0x00ffffff;
DwmSetWindowAttribute(WindowHandle, 35/*DWMWA_CAPTION_COLOR*/, &bgra8, sizeof(uint32_t));
}
void Win32Window::SetCaptionTextColor(uint32_t bgra8)
{
bgra8 = bgra8 & 0x00ffffff;
DwmSetWindowAttribute(WindowHandle, 36/*DWMWA_TEXT_COLOR*/, &bgra8, sizeof(uint32_t));
}
void Win32Window::SetWindowFrame(const Rect& box)
{
double dpiscale = GetDpiScale();
SetWindowPos(WindowHandle, nullptr, (int)std::round(box.x * dpiscale), (int)std::round(box.y * dpiscale), (int)std::round(box.width * dpiscale), (int)std::round(box.height * dpiscale), SWP_NOACTIVATE | SWP_NOZORDER);
}
void Win32Window::SetClientFrame(const Rect& box)
{
// This function is currently unused but needs to be disabled because it contains Windows API calls that were only added in Windows 10.
#if 0
double dpiscale = GetDpiScale();
RECT rect = {};
rect.left = (int)std::round(box.x * dpiscale);
rect.top = (int)std::round(box.y * dpiscale);
rect.right = rect.left + (int)std::round(box.width * dpiscale);
rect.bottom = rect.top + (int)std::round(box.height * dpiscale);
DWORD style = (DWORD)GetWindowLongPtr(WindowHandle, GWL_STYLE);
DWORD exstyle = (DWORD)GetWindowLongPtr(WindowHandle, GWL_EXSTYLE);
AdjustWindowRectExForDpi(&rect, style, FALSE, exstyle, GetDpiForWindow(WindowHandle));
SetWindowPos(WindowHandle, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER);
#endif
}
void Win32Window::Show()
{
ShowWindow(WindowHandle, SW_SHOW);
}
void Win32Window::ShowFullscreen()
{
HDC screenDC = GetDC(0);
int width = GetDeviceCaps(screenDC, HORZRES);
int height = GetDeviceCaps(screenDC, VERTRES);
ReleaseDC(0, screenDC);
SetWindowLongPtr(WindowHandle, GWL_EXSTYLE, WS_EX_APPWINDOW);
SetWindowLongPtr(WindowHandle, GWL_STYLE, WS_OVERLAPPED);
SetWindowPos(WindowHandle, HWND_TOP, 0, 0, width, height, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
Fullscreen = true;
}
void Win32Window::ShowMaximized()
{
ShowWindow(WindowHandle, SW_SHOWMAXIMIZED);
}
void Win32Window::ShowMinimized()
{
ShowWindow(WindowHandle, SW_SHOWMINIMIZED);
}
void Win32Window::ShowNormal()
{
ShowWindow(WindowHandle, SW_NORMAL);
}
void Win32Window::Hide()
{
ShowWindow(WindowHandle, SW_HIDE);
}
void Win32Window::Activate()
{
SetFocus(WindowHandle);
}
void Win32Window::ShowCursor(bool enable)
{
}
void Win32Window::LockCursor()
{
if (!MouseLocked)
{
MouseLocked = true;
GetCursorPos(&MouseLockPos);
::ShowCursor(FALSE);
}
}
void Win32Window::UnlockCursor()
{
if (MouseLocked)
{
MouseLocked = false;
SetCursorPos(MouseLockPos.x, MouseLockPos.y);
::ShowCursor(TRUE);
}
}
void Win32Window::CaptureMouse()
{
SetCapture(WindowHandle);
}
void Win32Window::ReleaseMouseCapture()
{
ReleaseCapture();
}
void Win32Window::Update()
{
InvalidateRect(WindowHandle, nullptr, FALSE);
}
bool Win32Window::GetKeyState(EInputKey key)
{
return ::GetKeyState((int)key) & 0x8000; // High bit (0x8000) means key is down, Low bit (0x0001) means key is sticky on (like Caps Lock, Num Lock, etc.)
}
void Win32Window::SetCursor(StandardCursor cursor)
{
if (cursor != CurrentCursor)
{
CurrentCursor = cursor;
UpdateCursor();
}
}
Rect Win32Window::GetWindowFrame() const
{
RECT box = {};
GetWindowRect(WindowHandle, &box);
double dpiscale = GetDpiScale();
return Rect(box.left / dpiscale, box.top / dpiscale, box.right / dpiscale, box.bottom / dpiscale);
}
Size Win32Window::GetClientSize() const
{
RECT box = {};
GetClientRect(WindowHandle, &box);
double dpiscale = GetDpiScale();
return Size(box.right / dpiscale, box.bottom / dpiscale);
}
int Win32Window::GetPixelWidth() const
{
RECT box = {};
GetClientRect(WindowHandle, &box);
return box.right;
}
int Win32Window::GetPixelHeight() const
{
RECT box = {};
GetClientRect(WindowHandle, &box);
return box.bottom;
}
typedef UINT(WINAPI* GetDpiForWindow_t)(HWND);
double Win32Window::GetDpiScale() const
{
static GetDpiForWindow_t pGetDpiForWindow = nullptr;
static bool done = false;
if (!done)
{
HMODULE hMod = GetModuleHandleA("User32.dll");
if (hMod != nullptr) pGetDpiForWindow = reinterpret_cast<GetDpiForWindow_t>(GetProcAddress(hMod, "GetDpiForWindow"));
done = true;
}
if (pGetDpiForWindow)
return pGetDpiForWindow(WindowHandle) / 96.0;
else
return 1.0;
}
std::string Win32Window::GetClipboardText()
{
BOOL result = OpenClipboard(WindowHandle);
if (result == FALSE)
throw std::runtime_error("Unable to open clipboard");
HANDLE handle = GetClipboardData(CF_UNICODETEXT);
if (handle == 0)
{
CloseClipboard();
return std::string();
}
std::wstring::value_type* data = (std::wstring::value_type*)GlobalLock(handle);
if (data == 0)
{
CloseClipboard();
return std::string();
}
std::string str = from_utf16(data);
GlobalUnlock(handle);
CloseClipboard();
return str;
}
void Win32Window::SetClipboardText(const std::string& text)
{
std::wstring text16 = to_utf16(text);
BOOL result = OpenClipboard(WindowHandle);
if (result == FALSE)
throw std::runtime_error("Unable to open clipboard");
result = EmptyClipboard();
if (result == FALSE)
{
CloseClipboard();
throw std::runtime_error("Unable to empty clipboard");
}
unsigned int length = (text16.length() + 1) * sizeof(std::wstring::value_type);
HANDLE handle = GlobalAlloc(GMEM_MOVEABLE, length);
if (handle == 0)
{
CloseClipboard();
throw std::runtime_error("Unable to allocate clipboard memory");
}
void* data = GlobalLock(handle);
if (data == 0)
{
GlobalFree(handle);
CloseClipboard();
throw std::runtime_error("Unable to lock clipboard memory");
}
memcpy(data, text16.c_str(), length);
GlobalUnlock(handle);
HANDLE data_result = SetClipboardData(CF_UNICODETEXT, handle);
if (data_result == 0)
{
GlobalFree(handle);
CloseClipboard();
throw std::runtime_error("Unable to set clipboard data");
}
CloseClipboard();
}
void Win32Window::PresentBitmap(int width, int height, const uint32_t* pixels)
{
BITMAPV5HEADER header = {};
header.bV5Size = sizeof(BITMAPV5HEADER);
header.bV5Width = width;
header.bV5Height = -height;
header.bV5Planes = 1;
header.bV5BitCount = 32;
header.bV5Compression = BI_BITFIELDS;
header.bV5AlphaMask = 0xff000000;
header.bV5RedMask = 0x00ff0000;
header.bV5GreenMask = 0x0000ff00;
header.bV5BlueMask = 0x000000ff;
header.bV5SizeImage = width * height * sizeof(uint32_t);
header.bV5CSType = LCS_sRGB;
HDC dc = PaintDC;
if (dc != 0)
{
int result = SetDIBitsToDevice(dc, 0, 0, width, height, 0, 0, 0, height, pixels, (const BITMAPINFO*)&header, BI_RGB);
ReleaseDC(WindowHandle, dc);
}
}
LRESULT Win32Window::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lparam)
{
LPARAM result = 0;
if (DwmDefWindowProc(WindowHandle, msg, wparam, lparam, &result))
return result;
if (msg == WM_INPUT)
{
bool hasFocus = GetFocus() != 0;
HRAWINPUT handle = (HRAWINPUT)lparam;
UINT size = 0;
UINT result = GetRawInputData(handle, RID_INPUT, 0, &size, sizeof(RAWINPUTHEADER));
if (result == 0 && size > 0)
{
size *= 2;
std::vector<uint8_t*> buffer(size);
result = GetRawInputData(handle, RID_INPUT, buffer.data(), &size, sizeof(RAWINPUTHEADER));
if (result >= 0)
{
RAWINPUT* rawinput = (RAWINPUT*)buffer.data();
if (rawinput->header.dwType == RIM_TYPEMOUSE)
{
if (hasFocus)
WindowHost->OnWindowRawMouseMove(rawinput->data.mouse.lLastX, rawinput->data.mouse.lLastY);
}
}
}
return DefWindowProc(WindowHandle, msg, wparam, lparam);
}
else if (msg == WM_PAINT)
{
PAINTSTRUCT paintStruct = {};
PaintDC = BeginPaint(WindowHandle, &paintStruct);
if (PaintDC)
{
WindowHost->OnWindowPaint();
EndPaint(WindowHandle, &paintStruct);
PaintDC = 0;
}
return 0;
}
else if (msg == WM_ACTIVATE)
{
WindowHost->OnWindowActivated();
}
else if (msg == WM_MOUSEMOVE)
{
if (MouseLocked && GetFocus() != 0)
{
RECT box = {};
GetClientRect(WindowHandle, &box);
POINT center = {};
center.x = box.right / 2;
center.y = box.bottom / 2;
ClientToScreen(WindowHandle, &center);
SetCursorPos(center.x, center.y);
}
else
{
UpdateCursor();
}
WindowHost->OnWindowMouseMove(GetLParamPos(lparam));
}
else if (msg == WM_LBUTTONDOWN)
{
WindowHost->OnWindowMouseDown(GetLParamPos(lparam), IK_LeftMouse);
}
else if (msg == WM_LBUTTONDBLCLK)
{
WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), IK_LeftMouse);
}
else if (msg == WM_LBUTTONUP)
{
WindowHost->OnWindowMouseUp(GetLParamPos(lparam), IK_LeftMouse);
}
else if (msg == WM_MBUTTONDOWN)
{
WindowHost->OnWindowMouseDown(GetLParamPos(lparam), IK_MiddleMouse);
}
else if (msg == WM_MBUTTONDBLCLK)
{
WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), IK_MiddleMouse);
}
else if (msg == WM_MBUTTONUP)
{
WindowHost->OnWindowMouseUp(GetLParamPos(lparam), IK_MiddleMouse);
}
else if (msg == WM_RBUTTONDOWN)
{
WindowHost->OnWindowMouseDown(GetLParamPos(lparam), IK_RightMouse);
}
else if (msg == WM_RBUTTONDBLCLK)
{
WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), IK_RightMouse);
}
else if (msg == WM_RBUTTONUP)
{
WindowHost->OnWindowMouseUp(GetLParamPos(lparam), IK_RightMouse);
}
else if (msg == WM_MOUSEWHEEL)
{
double delta = GET_WHEEL_DELTA_WPARAM(wparam) / (double)WHEEL_DELTA;
// Note: WM_MOUSEWHEEL uses screen coordinates. GetLParamPos assumes client coordinates.
double dpiscale = GetDpiScale();
POINT pos;
pos.x = GET_X_LPARAM(lparam);
pos.y = GET_Y_LPARAM(lparam);
ScreenToClient(WindowHandle, &pos);
WindowHost->OnWindowMouseWheel(Point(pos.x / dpiscale, pos.y / dpiscale), delta < 0.0 ? IK_MouseWheelDown : IK_MouseWheelUp);
}
else if (msg == WM_CHAR)
{
wchar_t buf[2] = { (wchar_t)wparam, 0 };
WindowHost->OnWindowKeyChar(from_utf16(buf));
}
else if (msg == WM_KEYDOWN)
{
WindowHost->OnWindowKeyDown((EInputKey)wparam);
}
else if (msg == WM_KEYUP)
{
WindowHost->OnWindowKeyUp((EInputKey)wparam);
}
else if (msg == WM_SETFOCUS)
{
if (MouseLocked)
{
::ShowCursor(FALSE);
}
}
else if (msg == WM_KILLFOCUS)
{
if (MouseLocked)
{
::ShowCursor(TRUE);
}
}
else if (msg == WM_CLOSE)
{
WindowHost->OnWindowClose();
return 0;
}
else if (msg == WM_SIZE)
{
WindowHost->OnWindowGeometryChanged();
return 0;
}
/*else if (msg == WM_NCCALCSIZE && wparam == TRUE) // calculate client area for the window
{
NCCALCSIZE_PARAMS* calcsize = (NCCALCSIZE_PARAMS*)lparam;
return WVR_REDRAW;
}*/
return DefWindowProc(WindowHandle, msg, wparam, lparam);
}
void Win32Window::UpdateCursor()
{
LPCWSTR cursor = IDC_ARROW;
switch (CurrentCursor)
{
case StandardCursor::arrow: cursor = IDC_ARROW; break;
case StandardCursor::appstarting: cursor = IDC_APPSTARTING; break;
case StandardCursor::cross: cursor = IDC_CROSS; break;
case StandardCursor::hand: cursor = IDC_HAND; break;
case StandardCursor::ibeam: cursor = IDC_IBEAM; break;
case StandardCursor::no: cursor = IDC_NO; break;
case StandardCursor::size_all: cursor = IDC_SIZEALL; break;
case StandardCursor::size_nesw: cursor = IDC_SIZENESW; break;
case StandardCursor::size_ns: cursor = IDC_SIZENS; break;
case StandardCursor::size_nwse: cursor = IDC_SIZENWSE; break;
case StandardCursor::size_we: cursor = IDC_SIZEWE; break;
case StandardCursor::uparrow: cursor = IDC_UPARROW; break;
case StandardCursor::wait: cursor = IDC_WAIT; break;
default: break;
}
::SetCursor((HCURSOR)LoadImage(0, cursor, IMAGE_CURSOR, LR_DEFAULTSIZE, LR_DEFAULTSIZE, LR_SHARED));
}
Point Win32Window::GetLParamPos(LPARAM lparam) const
{
double dpiscale = GetDpiScale();
return Point(GET_X_LPARAM(lparam) / dpiscale, GET_Y_LPARAM(lparam) / dpiscale);
}
LRESULT Win32Window::WndProc(HWND windowhandle, UINT msg, WPARAM wparam, LPARAM lparam)
{
if (msg == WM_CREATE)
{
CREATESTRUCT* createstruct = (CREATESTRUCT*)lparam;
Win32Window* viewport = (Win32Window*)createstruct->lpCreateParams;
viewport->WindowHandle = windowhandle;
SetWindowLongPtr(windowhandle, GWLP_USERDATA, (LONG_PTR)viewport);
return viewport->OnWindowMessage(msg, wparam, lparam);
}
else
{
Win32Window* viewport = (Win32Window*)GetWindowLongPtr(windowhandle, GWLP_USERDATA);
if (viewport)
{
LRESULT result = viewport->OnWindowMessage(msg, wparam, lparam);
if (msg == WM_DESTROY)
{
SetWindowLongPtr(windowhandle, GWLP_USERDATA, 0);
viewport->WindowHandle = 0;
}
return result;
}
else
{
return DefWindowProc(windowhandle, msg, wparam, lparam);
}
}
}
void Win32Window::ProcessEvents()
{
while (true)
{
MSG msg = {};
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE) <= 0)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
void Win32Window::RunLoop()
{
while (!ExitRunLoop && !Windows.empty())
{
MSG msg = {};
if (GetMessage(&msg, 0, 0, 0) <= 0)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
ExitRunLoop = false;
}
void Win32Window::ExitLoop()
{
ExitRunLoop = true;
}
Size Win32Window::GetScreenSize()
{
HDC screenDC = GetDC(0);
int screenWidth = GetDeviceCaps(screenDC, HORZRES);
int screenHeight = GetDeviceCaps(screenDC, VERTRES);
double dpiScale = GetDeviceCaps(screenDC, LOGPIXELSX) / 96.0;
ReleaseDC(0, screenDC);
return Size(screenWidth / dpiScale, screenHeight / dpiScale);
}
static void CALLBACK Win32TimerCallback(HWND handle, UINT message, UINT_PTR timerID, DWORD timestamp)
{
auto it = Win32Window::Timers.find(timerID);
if (it != Win32Window::Timers.end())
{
it->second();
}
}
void* Win32Window::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)
{
UINT_PTR result = SetTimer(0, 0, timeoutMilliseconds, Win32TimerCallback);
if (result == 0)
throw std::runtime_error("Could not create timer");
Timers[result] = std::move(onTimer);
return (void*)result;
}
void Win32Window::StopTimer(void* timerID)
{
auto it = Timers.find((UINT_PTR)timerID);
if (it != Timers.end())
{
Timers.erase(it);
KillTimer(0, (UINT_PTR)timerID);
}
}
std::list<Win32Window*> Win32Window::Windows;
bool Win32Window::ExitRunLoop;
std::unordered_map<UINT_PTR, std::function<void()>> Win32Window::Timers;

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/win32window.h"
std::unique_ptr<DisplayWindow> DisplayWindow::Create(DisplayWindowHost* windowHost)
std::unique_ptr<DisplayWindow> DisplayWindow::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI)
{
return std::make_unique<Win32Window>(windowHost);
return DisplayBackend::Get()->Create(windowHost, popupWindow, owner, renderAPI);
}
void DisplayWindow::ProcessEvents()
{
Win32Window::ProcessEvents();
DisplayBackend::Get()->ProcessEvents();
}
void DisplayWindow::RunLoop()
{
Win32Window::RunLoop();
DisplayBackend::Get()->RunLoop();
}
void DisplayWindow::ExitLoop()
{
Win32Window::ExitLoop();
}
Size DisplayWindow::GetScreenSize()
{
return Win32Window::GetScreenSize();
DisplayBackend::Get()->ExitLoop();
}
void* DisplayWindow::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)
{
return Win32Window::StartTimer(timeoutMilliseconds, std::move(onTimer));
return DisplayBackend::Get()->StartTimer(timeoutMilliseconds, onTimer);
}
void DisplayWindow::StopTimer(void* timerID)
{
Win32Window::StopTimer(timerID);
}
#elif defined(__APPLE__)
std::unique_ptr<DisplayWindow> DisplayWindow::Create(DisplayWindowHost* windowHost)
{
throw std::runtime_error("DisplayWindow::Create not implemented");
}
void DisplayWindow::ProcessEvents()
{
throw std::runtime_error("DisplayWindow::ProcessEvents not implemented");
}
void DisplayWindow::RunLoop()
{
throw std::runtime_error("DisplayWindow::RunLoop not implemented");
}
void DisplayWindow::ExitLoop()
{
throw std::runtime_error("DisplayWindow::ExitLoop not implemented");
DisplayBackend::Get()->StopTimer(timerID);
}
Size DisplayWindow::GetScreenSize()
{
throw std::runtime_error("DisplayWindow::GetScreenSize not implemented");
return DisplayBackend::Get()->GetScreenSize();
}
void* DisplayWindow::StartTimer(int timeoutMilliseconds, std::function<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)
std::unique_ptr<DisplayBackend> DisplayBackend::TryCreateWin32()
{
return std::make_unique<SDL2DisplayWindow>(windowHost);
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,142 @@
#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;
int ClientSizeX = 0;
int ClientSizeY = 0;
int MouseX = -1;
int MouseY = -1;
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

@ -6,14 +6,16 @@
#include <zwidget/core/image.h>
#include <zwidget/widgets/pushbutton/pushbutton.h>
#include <zwidget/widgets/scrollbar/scrollbar.h>
#include <zwidget/systemdialogs/save_file_dialog.h>
#include <miniz.h>
bool ErrorWindow::ExecModal(const std::string& text, const std::string& log)
bool ErrorWindow::ExecModal(const std::string& text, const std::string& log, std::vector<uint8_t> minidump)
{
Size screenSize = GetScreenSize();
double windowWidth = 1200.0;
double windowHeight = 700.0;
auto window = std::make_unique<ErrorWindow>();
auto window = std::make_unique<ErrorWindow>(std::move(minidump));
window->SetText(text, log);
window->SetFrameGeometry((screenSize.width - windowWidth) * 0.5, (screenSize.height - windowHeight) * 0.5, windowWidth, windowHeight);
window->Show();
@ -23,7 +25,7 @@ bool ErrorWindow::ExecModal(const std::string& text, const std::string& log)
return window->Restart;
}
ErrorWindow::ErrorWindow() : Widget(nullptr, WidgetType::Window)
ErrorWindow::ErrorWindow(std::vector<uint8_t> initminidump) : Widget(nullptr, WidgetType::Window), minidump(std::move(initminidump))
{
FStringf caption("Fatal Error - " GAMENAME " %s (%s)", GetVersionString(), GetGitTime());
SetWindowTitle(caption.GetChars());
@ -34,13 +36,21 @@ ErrorWindow::ErrorWindow() : Widget(nullptr, WidgetType::Window)
LogView = new LogViewer(this);
ClipboardButton = new PushButton(this);
RestartButton = new PushButton(this);
ClipboardButton->OnClick = [=]() { OnClipboardButtonClicked(); };
RestartButton->OnClick = [=]() { OnRestartButtonClicked(); };
ClipboardButton->SetText("Copy to clipboard");
RestartButton->SetText("Restart");
if (minidump.empty())
{
RestartButton = new PushButton(this);
RestartButton->OnClick = [=]() { OnRestartButtonClicked(); };
RestartButton->SetText("Restart");
}
else
{
SaveReportButton = new PushButton(this);
SaveReportButton->OnClick = [=]() { OnSaveReportButtonClicked(); };
SaveReportButton->SetText("Save Report");
}
LogView->SetFocus();
}
@ -83,6 +93,37 @@ void ErrorWindow::OnRestartButtonClicked()
DisplayWindow::ExitLoop();
}
void ErrorWindow::OnSaveReportButtonClicked()
{
auto dialog = SaveFileDialog::Create(this);
dialog->AddFilter("Crash Report Zip Files", "*.zip");
dialog->AddFilter("All Files", "*.*");
dialog->SetFilename("CrashReport.zip");
dialog->SetDefaultExtension("zip");
if (dialog->Show())
{
std::string filename = dialog->Filename();
mz_zip_archive zip = {};
if (mz_zip_writer_init_heap(&zip, 0, 16 * 1024 * 1024))
{
mz_zip_writer_add_mem(&zip, "minidump.dmp", minidump.data(), minidump.size(), MZ_DEFAULT_COMPRESSION);
mz_zip_writer_add_mem(&zip, "log.txt", clipboardtext.data(), clipboardtext.size(), MZ_DEFAULT_COMPRESSION);
}
void* buffer = nullptr;
size_t buffersize = 0;
mz_zip_writer_finalize_heap_archive(&zip, &buffer, &buffersize);
mz_zip_writer_end(&zip);
std::unique_ptr<FileWriter> f(FileWriter::Open(filename.c_str()));
if (f)
{
f->Write(buffer, buffersize);
f->Close();
}
}
}
void ErrorWindow::OnClose()
{
Restart = false;
@ -96,7 +137,10 @@ void ErrorWindow::OnGeometryChanged()
double y = GetHeight() - 15.0 - ClipboardButton->GetPreferredHeight();
ClipboardButton->SetFrameGeometry(20.0, y, 170.0, ClipboardButton->GetPreferredHeight());
RestartButton->SetFrameGeometry(GetWidth() - 20.0 - 100.0, y, 100.0, RestartButton->GetPreferredHeight());
if (RestartButton)
RestartButton->SetFrameGeometry(GetWidth() - 20.0 - 100.0, y, 100.0, RestartButton->GetPreferredHeight());
else if (SaveReportButton)
SaveReportButton->SetFrameGeometry(GetWidth() - 20.0 - 100.0, y, 100.0, SaveReportButton->GetPreferredHeight());
y -= 20.0;
LogView->SetFrameGeometry(Rect::xywh(0.0, 0.0, w, y));
@ -212,44 +256,44 @@ void LogViewer::OnPaint(Canvas* canvas)
}
}
bool LogViewer::OnMouseWheel(const Point& pos, EInputKey key)
bool LogViewer::OnMouseWheel(const Point& pos, InputKey key)
{
if (key == IK_MouseWheelUp)
if (key == InputKey::MouseWheelUp)
{
ScrollUp(4);
}
else if (key == IK_MouseWheelDown)
else if (key == InputKey::MouseWheelDown)
{
ScrollDown(4);
}
return true;
}
void LogViewer::OnKeyDown(EInputKey key)
void LogViewer::OnKeyDown(InputKey key)
{
if (key == IK_Home)
if (key == InputKey::Home)
{
scrollbar->SetPosition(0.0f);
Update();
}
if (key == IK_End)
if (key == InputKey::End)
{
scrollbar->SetPosition(scrollbar->GetMax());
Update();
}
else if (key == IK_PageUp)
else if (key == InputKey::PageUp)
{
ScrollUp(20);
}
else if (key == IK_PageDown)
else if (key == InputKey::PageDown)
{
ScrollDown(20);
}
else if (key == IK_Up)
else if (key == InputKey::Up)
{
ScrollUp(4);
}
else if (key == IK_Down)
else if (key == InputKey::Down)
{
ScrollDown(4);
}

View file

@ -10,9 +10,9 @@ class Scrollbar;
class ErrorWindow : public Widget
{
public:
static bool ExecModal(const std::string& text, const std::string& log);
static bool ExecModal(const std::string& text, const std::string& log, std::vector<uint8_t> minidump = {});
ErrorWindow();
ErrorWindow(std::vector<uint8_t> minidump);
bool Restart = false;
@ -25,11 +25,14 @@ private:
void OnClipboardButtonClicked();
void OnRestartButtonClicked();
void OnSaveReportButtonClicked();
LogViewer* LogView = nullptr;
PushButton* ClipboardButton = nullptr;
PushButton* RestartButton = nullptr;
PushButton* SaveReportButton = nullptr;
std::vector<uint8_t> minidump;
std::string clipboardtext;
};
@ -43,8 +46,8 @@ public:
protected:
void OnPaintFrame(Canvas* canvas) override;
void OnPaint(Canvas* canvas) override;
bool OnMouseWheel(const Point& pos, EInputKey key) override;
void OnKeyDown(EInputKey key) override;
bool OnMouseWheel(const Point& pos, InputKey key) override;
void OnKeyDown(InputKey key) override;
void OnGeometryChanged() override;
private:

View file

@ -1,5 +1,6 @@
#include <zwidget/core/resourcedata.h>
#include <zwidget/core/theme.h>
#include "filesystem.h"
#include "printf.h"
#include "zstring.h"
@ -11,21 +12,31 @@
FResourceFile* WidgetResources;
bool IsZWidgetAvailable()
{
return WidgetResources;
}
void InitWidgetResources(const char* filename)
{
WidgetResources = FResourceFile::OpenResourceFile(filename);
if (!WidgetResources)
I_FatalError("Unable to open %s", filename);
WidgetTheme::SetTheme(std::make_unique<DarkWidgetTheme>());
}
void CloseWidgetResources()
{
if (WidgetResources) delete WidgetResources;
delete WidgetResources;
WidgetResources = nullptr;
}
static std::vector<uint8_t> LoadFile(const char* name)
{
if (!WidgetResources)
I_FatalError("InitWidgetResources has not been called");
auto lump = WidgetResources->FindEntry(name);
if (lump == -1)
I_FatalError("Unable to find %s", name);

View file

@ -118,6 +118,7 @@
#include "screenjob.h"
#include "startscreen.h"
#include "shiftstate.h"
#include "common/widgets/errorwindow.h"
#ifdef __unix__
#include "i_system.h" // for SHARE_DIR
@ -3750,6 +3751,15 @@ static int D_DoomMain_Internal (void)
int GameMain()
{
// On Windows, prefer the native win32 backend.
// On other platforms, use SDL until the other backends are more mature.
auto zwidget = DisplayBackend::TryCreateWin32();
if (!zwidget)
zwidget = DisplayBackend::TryCreateSDL2();
if (!zwidget)
return -1;
DisplayBackend::Set(std::move(zwidget));
int ret = 0;
GameTicRate = TICRATE;
I_InitTime();

View file

@ -7,6 +7,7 @@
#include "version.h"
#include "i_interface.h"
#include "gstrings.h"
#include "c_cvars.h"
#include <zwidget/core/resourcedata.h>
#include <zwidget/window/window.h>
#include <zwidget/widgets/tabwidget/tabwidget.h>
@ -31,10 +32,6 @@ int LauncherWindow::ExecModal(WadStuff* wads, int numwads, int defaultiwad, int*
LauncherWindow::LauncherWindow(WadStuff* wads, int numwads, int defaultiwad, int* autoloadflags) : Widget(nullptr, WidgetType::Window)
{
SetWindowBackground(Colorf::fromRgba8(51, 51, 51));
SetWindowBorderColor(Colorf::fromRgba8(51, 51, 51));
SetWindowCaptionColor(Colorf::fromRgba8(33, 33, 33));
SetWindowCaptionTextColor(Colorf::fromRgba8(226, 223, 219));
SetWindowTitle(GAMENAME);
Banner = new LauncherBanner(this);

View file

@ -40,16 +40,16 @@ PlayGamePage::PlayGamePage(LauncherWindow* launcher, WadStuff* wads, int numwads
GamesList->OnActivated = [=]() { OnGamesListActivated(); };
}
std::string PlayGamePage::GetExtraArgs()
{
return ParametersEdit->GetText();
}
void PlayGamePage::SetExtraArgs(const std::string& args)
{
ParametersEdit->SetText(args);
}
std::string PlayGamePage::GetExtraArgs()
{
return ParametersEdit->GetText();
}
int PlayGamePage::GetSelectedGame()
{
return GamesList->GetSelectedItem();

View file

@ -16,6 +16,7 @@ public:
void SetExtraArgs(const std::string& args);
std::string GetExtraArgs();
int GetSelectedGame();
private:

Some files were not shown because too many files have changed in this diff Show more