Merge remote-tracking branch 'gzdoom/master' into gzdoom_merge

This commit is contained in:
Mari the Deer 2025-09-24 19:39:17 +02:00
commit b2a9dbf809
203 changed files with 11531 additions and 4298 deletions

View file

@ -1,4 +1,5 @@
cmake_minimum_required(VERSION 3.15)
cmake_minimum_required(VERSION 3.24)
project(zvulkan)
option( VULKAN_USE_XLIB "Use Vulkan xlib (X11) WSI integration" ON )
@ -198,15 +199,19 @@ source_group("include\\volk" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/inc
include_directories(include include/zvulkan src)
if(WIN32)
set(ZVULKAN_SOURCES ${ZVULKAN_SOURCES} ${ZVULKAN_WIN32_SOURCES})
list(APPEND ZVULKAN_SOURCES ${ZVULKAN_WIN32_SOURCES})
add_definitions(-DUNICODE -D_UNICODE)
else()
set(ZVULKAN_SOURCES ${ZVULKAN_SOURCES} ${ZVULKAN_UNIX_SOURCES})
if(NOT HAIKU)
set(ZVULKAN_LIBS ${CMAKE_DL_LIBS} -ldl)
if(APPLE)
find_package(Vulkan REQUIRED COMPONENTS MoltenVK)
list(APPEND ZVULKAN_LIBS Vulkan::Vulkan)
elseif(NOT HAIKU AND NOT CMAKE_SYSTEM_NAME MATCHES "FreeBSD|NetBSD|OpenBSD|DragonFly")
list(APPEND ZVULKAN_LIBS ${CMAKE_DL_LIBS} -ldl)
else()
set(ZVULKAN_LIBS ${CMAKE_DL_LIBS})
list(APPEND ZVULKAN_LIBS ${CMAKE_DL_LIBS})
endif()
list(APPEND ZVULKAN_SOURCES ${ZVULKAN_UNIX_SOURCES})
add_definitions(-DUNIX -D_UNIX)
add_link_options(-pthread)
endif()
@ -220,7 +225,7 @@ if(MSVC)
endif()
add_library(zvulkan STATIC ${ZVULKAN_SOURCES} ${ZVULKAN_INCLUDES} ${VULKAN_INCLUDES})
target_link_libraries(zvulkan ${ZVULKAN_LIBS})
target_link_libraries(zvulkan PUBLIC ${ZVULKAN_LIBS})
set_target_properties(zvulkan PROPERTIES CXX_STANDARD 17)
if(MSVC)

View file

@ -569,7 +569,7 @@ inline RenderPassBegin& RenderPassBegin::Framebuffer(VulkanFramebuffer* framebuf
inline RenderPassBegin& RenderPassBegin::AddClearColor(float r, float g, float b, float a)
{
VkClearValue clearValue = { };
clearValue.color = { r, g, b, a };
clearValue.color = {{ r, g, b, a }};
clearValues.push_back(clearValue);
renderPassInfo.clearValueCount = (uint32_t)clearValues.size();

View file

@ -64,30 +64,30 @@ jobs:
shell: bash
run: cmake --build . --config $BUILD_TYPE
# macos-build:
# runs-on: macos-10.15
# name: 🍎 macOS 10.15
# steps:
macos-build:
runs-on: macos-latest
name: 🍎 macOS arm64
steps:
# - name: 🧰 Checkout
# uses: actions/checkout@v2
# with:
# fetch-depth: 0
# submodules: true
- name: 🧰 Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: true
# - name: ⬇️ Install dependencies
# run: brew install sdl2 sdl2_mixer
- name: ⬇️ Install dependencies
run: brew install sdl2 sdl2_mixer
# - name: Create Build Environment
# run: cmake -E make_directory ${{runner.workspace}}/build
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
# - name: Configure CMake
# shell: bash
# working-directory: ${{runner.workspace}}/build
# run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE
- name: Configure CMake
shell: bash
working-directory: ${{runner.workspace}}/build
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE
# - name: Build
# working-directory: ${{runner.workspace}}/build
# shell: bash
# run: |
# cmake --build . --config $BUILD_TYPE
- name: Build
working-directory: ${{runner.workspace}}/build
shell: bash
run: |
cmake --build . --config $BUILD_TYPE

View file

@ -17,6 +17,14 @@ if (UNIX AND NOT APPLE)
)
endif()
# SDL2 finding stuff
# Optional on all platforms
find_package(SDL2 QUIET)
if(NOT ${SDL2_FOUND})
include(FindPkgConfig)
pkg_search_module(SDL2 sdl2)
endif()
set(ZWIDGET_SOURCES
src/core/canvas.cpp
src/core/font.cpp
@ -138,6 +146,8 @@ set(ZWIDGET_X11_SOURCES
src/window/x11/x11_display_backend.h
src/window/x11/x11_display_window.cpp
src/window/x11/x11_display_window.h
src/window/x11/x11_connection.cpp
src/window/x11/x11_connection.h
)
set(ZWIDGET_WAYLAND_SOURCES
@ -147,6 +157,10 @@ set(ZWIDGET_WAYLAND_SOURCES
src/window/wayland/wayland_display_window.h
src/window/wayland/wl_fractional_scaling_protocol.cpp
src/window/wayland/wl_fractional_scaling_protocol.hpp
src/window/wayland/wl_cursor_shape.cpp
src/window/wayland/wl_cursor_shape.hpp
src/window/wayland/wl_xdg_toplevel_icon.cpp
src/window/wayland/wl_xdg_toplevel_icon.hpp
)
source_group("src" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/.+")
@ -226,11 +240,12 @@ elseif(APPLE)
set(ZWIDGET_DEFINES -DUNIX -D_UNIX)
set(ZWIDGET_LINK_OPTIONS -pthread)
else()
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_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_X11_SOURCES})
set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -lX11 -lXi)
set(ZWIDGET_DEFINES -DUNIX -D_UNIX -DUSE_X11)
set(ZWIDGET_LINK_OPTIONS -pthread)
if (DBUS_FOUND)
include_directories("/usr/lib64/dbus-1.0/include") # Bazzite (and probably Fedora 42) keeps the platform-specific dbus headers in this folder
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})
@ -244,14 +259,77 @@ else()
endif()
endif()
if(SDL2_FOUND AND NOT WIN32)
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_SDL2_SOURCES})
set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES} -DUSE_SDL2)
endif()
if(MSVC)
set(CXX_WARNING_FLAGS /W3)
else()
set(CXX_WARNING_FLAGS -Wall -Wpedantic)
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)
target_link_libraries(zwidget PRIVATE ${ZWIDGET_LIBS})
if(SDL2_FOUND)
if(TARGET SDL2::SDL2)
target_link_libraries(zwidget PRIVATE SDL2::SDL2)
else() # needed for gzdoom compat for now
target_include_directories(zwidget PRIVATE ${SDL2_INCLUDE_DIR} ${SDL2_INCLUDE_DIRS})
target_link_libraries(zwidget PRIVATE ${SDL2_LIBRARY})
endif()
endif()
set_target_properties(zwidget PROPERTIES CXX_STANDARD 20)
target_compile_options(zwidget PRIVATE ${CXX_WARNING_FLAGS})
if(MSVC)
set_property(TARGET zwidget PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
option(ZWIDGET_BUILD_EXAMPLE "Build the zwidget example application" ON)
if(ZWIDGET_BUILD_EXAMPLE)
add_executable(zwidget_example WIN32
example/example.cpp
example/picopng.cpp
example/picopng.h
)
target_compile_options(zwidget_example PRIVATE ${CXX_WARNING_FLAGS})
add_custom_command(
TARGET zwidget_example POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${CMAKE_CURRENT_SOURCE_DIR}/example/banner.png"
"${CMAKE_CURRENT_BINARY_DIR}/banner.png"
COMMAND ${CMAKE_COMMAND} -E copy
"${CMAKE_CURRENT_SOURCE_DIR}/example/OpenSans.ttf"
"${CMAKE_CURRENT_BINARY_DIR}/OpenSans.ttf"
)
target_include_directories(zwidget_example PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/example)
target_link_libraries(zwidget_example PRIVATE zwidget)
if(WIN32)
target_compile_definitions(zwidget_example PRIVATE UNICODE _UNICODE)
target_link_libraries(zwidget_example PRIVATE gdi32 user32 shell32 comdlg32)
elseif(APPLE)
target_link_libraries(zwidget_example PRIVATE "-framework Cocoa -framework OpenGL")
else()
target_link_libraries(zwidget_example PRIVATE ${ZWIDGET_LIBS})
endif()
if(SDL2_FOUND)
target_link_libraries(zwidget_example PRIVATE SDL2::SDL2)
endif()
set_target_properties(zwidget_example PROPERTIES CXX_STANDARD 20)
if(MSVC)
set_property(TARGET zwidget_example PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
endif()

View file

@ -13,136 +13,29 @@
#include <zwidget/widgets/pushbutton/pushbutton.h>
#include <zwidget/widgets/checkboxlabel/checkboxlabel.h>
#include "picopng.h"
#include <zwidget/widgets/tabwidget/tabwidget.h>
// ************************************************************
// Prototypes
// ************************************************************
static std::vector<uint8_t> ReadAllBytes(const std::string& filename);
class LauncherWindow : public Widget
class LauncherWindowTab1 : public Widget
{
public:
LauncherWindow() : Widget(nullptr, WidgetType::Window)
{
Logo = new ImageBox(this);
WelcomeLabel = new TextLabel(this);
VersionLabel = new TextLabel(this);
SelectLabel = new TextLabel(this);
GeneralLabel = new TextLabel(this);
ExtrasLabel = new TextLabel(this);
FullscreenCheckbox = new CheckboxLabel(this);
DisableAutoloadCheckbox = new CheckboxLabel(this);
DontAskAgainCheckbox = new CheckboxLabel(this);
LightsCheckbox = new CheckboxLabel(this);
BrightmapsCheckbox = new CheckboxLabel(this);
WidescreenCheckbox = new CheckboxLabel(this);
PlayButton = new PushButton(this);
ExitButton = new PushButton(this);
GamesList = new ListView(this);
Choices = new Dropdown(this);
LauncherWindowTab1(Widget parent);
void OnGeometryChanged() override;
private:
TextEdit* Text = nullptr;
};
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("VKDoom Launcher");
WelcomeLabel->SetText("Welcome to VKDoom");
VersionLabel->SetText("Version 0xdeadbabe.");
SelectLabel->SetText("Select which game file (IWAD) to run.");
PlayButton->SetText("Play Game");
ExitButton->SetText("Exit");
ExitButton->OnClick = []{
DisplayWindow::ExitLoop();
};
GeneralLabel->SetText("General");
ExtrasLabel->SetText("Extra Graphics");
FullscreenCheckbox->SetText("Fullscreen");
DisableAutoloadCheckbox->SetText("Disable autoload");
DontAskAgainCheckbox->SetText("Don't ask me again");
LightsCheckbox->SetText("Lights");
BrightmapsCheckbox->SetText("Brightmaps");
WidescreenCheckbox->SetText("Widescreen");
Choices->SetMaxDisplayItems(2);
Choices->AddItem("First");
Choices->AddItem("Second");
Choices->AddItem("Third");
Choices->AddItem("Fourth");
Choices->AddItem("Fifth");
Choices->AddItem("Sixth");
Choices->OnChanged = [this](int index) {
std::cout << "Selected " << index << ":" << Choices->GetItem(index) << std::endl;
};
try
{
auto filedata = ReadAllBytes("banner.png");
std::vector<unsigned char> pixels;
unsigned long width = 0, height = 0;
int result = decodePNG(pixels, width, height, (const unsigned char*)filedata.data(), filedata.size(), true);
if (result == 0)
{
Logo->SetImage(Image::Create(width, height, ImageFormat::R8G8B8A8, pixels.data()));
}
}
catch (...)
{
}
}
void OnGeometryChanged() override
{
double y = 0.0;
Logo->SetFrameGeometry(0.0, y, GetWidth(), Logo->GetPreferredHeight());
y += Logo->GetPreferredHeight();
y += 10.0;
WelcomeLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0, WelcomeLabel->GetPreferredHeight());
y += WelcomeLabel->GetPreferredHeight();
VersionLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0, VersionLabel->GetPreferredHeight());
y += VersionLabel->GetPreferredHeight();
y += 10.0;
SelectLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0 - Choices->GetPreferredWidth(), SelectLabel->GetPreferredHeight());
y += SelectLabel->GetPreferredHeight();
Choices->SetFrameGeometry(GetWidth() - 20.0 - Choices->GetPreferredWidth(), y-Choices->GetPreferredHeight(), Choices->GetPreferredWidth(), Choices->GetPreferredHeight());
double listViewTop = y + 10.0;
y = GetHeight() - 15.0 - PlayButton->GetPreferredHeight();
PlayButton->SetFrameGeometry(20.0, y, 120.0, PlayButton->GetPreferredHeight());
ExitButton->SetFrameGeometry(GetWidth() - 20.0 - 120.0, y, 120.0, PlayButton->GetPreferredHeight());
y -= 20.0;
double panelWidth = 150.0;
y -= DontAskAgainCheckbox->GetPreferredHeight();
DontAskAgainCheckbox->SetFrameGeometry(20.0, y, 190.0, DontAskAgainCheckbox->GetPreferredHeight());
WidescreenCheckbox->SetFrameGeometry(GetWidth() - 20.0 - panelWidth, y, panelWidth, WidescreenCheckbox->GetPreferredHeight());
y -= DisableAutoloadCheckbox->GetPreferredHeight();
DisableAutoloadCheckbox->SetFrameGeometry(20.0, y, 190.0, DisableAutoloadCheckbox->GetPreferredHeight());
BrightmapsCheckbox->SetFrameGeometry(GetWidth() - 20.0 - panelWidth, y, panelWidth, BrightmapsCheckbox->GetPreferredHeight());
y -= FullscreenCheckbox->GetPreferredHeight();
FullscreenCheckbox->SetFrameGeometry(20.0, y, 190.0, FullscreenCheckbox->GetPreferredHeight());
LightsCheckbox->SetFrameGeometry(GetWidth() - 20.0 - panelWidth, y, panelWidth, LightsCheckbox->GetPreferredHeight());
y -= GeneralLabel->GetPreferredHeight();
GeneralLabel->SetFrameGeometry(20.0, y, 190.0, GeneralLabel->GetPreferredHeight());
ExtrasLabel->SetFrameGeometry(GetWidth() - 20.0 - panelWidth, y, panelWidth, ExtrasLabel->GetPreferredHeight());
double listViewBottom = y - 10.0;
GamesList->SetFrameGeometry(20.0, listViewTop, GetWidth() - 40.0, std::max(listViewBottom - listViewTop, 0.0));
}
ImageBox* Logo = nullptr;
class LauncherWindowTab2 : public Widget
{
public:
LauncherWindowTab2(Widget parent);
void OnGeometryChanged() override;
private:
TextLabel* WelcomeLabel = nullptr;
TextLabel* VersionLabel = nullptr;
TextLabel* SelectLabel = nullptr;
@ -154,13 +47,261 @@ public:
CheckboxLabel* LightsCheckbox = nullptr;
CheckboxLabel* BrightmapsCheckbox = nullptr;
CheckboxLabel* WidescreenCheckbox = nullptr;
PushButton* PlayButton = nullptr;
PushButton* ExitButton = nullptr;
ListView* GamesList = nullptr;
Dropdown* Choices = nullptr;
};
static std::vector<uint8_t> ReadAllBytes(const std::string& filename);
class LauncherWindowTab3 : public Widget
{
public:
LauncherWindowTab3(Widget parent);
void OnGeometryChanged() override;
private:
TextLabel* Label = nullptr;
Dropdown* Choices = nullptr;
PushButton* Popup = nullptr;
};
class LauncherWindow : public Widget
{
public:
LauncherWindow();
private:
void OnClose() override;
void OnGeometryChanged() override;
ImageBox* Logo = nullptr;
TabWidget* Pages = nullptr;
PushButton* ExitButton = nullptr;
LauncherWindowTab1* Tab1 = nullptr;
LauncherWindowTab2* Tab2 = nullptr;
LauncherWindowTab3* Tab3 = nullptr;
};
// ************************************************************
// UI implementation
// ************************************************************
LauncherWindow::LauncherWindow(): Widget(nullptr, WidgetType::Window)
{
SetWindowTitle("ZWidget Demo");
try
{
SetWindowIcon({
Image::LoadResource("surreal-engine-icon-16.png"),
Image::LoadResource("surreal-engine-icon-24.png"),
Image::LoadResource("surreal-engine-icon-32.png"),
Image::LoadResource("surreal-engine-icon-48.png"),
Image::LoadResource("surreal-engine-icon-64.png"),
Image::LoadResource("surreal-engine-icon-128.png"),
Image::LoadResource("surreal-engine-icon-256.png")
});
}
catch (...)
{
}
Logo = new ImageBox(this);
ExitButton = new PushButton(this);
Pages = new TabWidget(this);
Tab1 = new LauncherWindowTab1(this);
Tab2 = new LauncherWindowTab2(this);
Tab3 = new LauncherWindowTab3(this);
Pages->AddTab(Tab1, "Welcome");
Pages->AddTab(Tab2, "VKDoom");
Pages->AddTab(Tab3, "ZWidgets");
ExitButton->SetText("Exit");
ExitButton->OnClick = []{
DisplayWindow::ExitLoop();
};
try
{
auto filedata = ReadAllBytes("banner.png");
std::vector<unsigned char> pixels;
unsigned long width = 0, height = 0;
int result = decodePNG(pixels, width, height, (const unsigned char*)filedata.data(), filedata.size(), true);
if (result == 0)
{
Logo->SetImage(Image::Create(width, height, ImageFormat::R8G8B8A8, pixels.data()));
}
}
catch (...)
{
}
}
void LauncherWindow::OnGeometryChanged()
{
double y = 0, h;
h = Logo->GetPreferredHeight();
Logo->SetFrameGeometry(0, y, GetWidth(), h);
y += h;
h = GetHeight() - y - ExitButton->GetPreferredHeight() - 40;
Pages->SetFrameGeometry(0, y, GetWidth(), h);
y += h + 20;
ExitButton->SetFrameGeometry(GetWidth() - 20 - 120, y, 120, ExitButton->GetPreferredHeight());
}
void LauncherWindow::OnClose()
{
DisplayWindow::ExitLoop();
}
LauncherWindowTab1::LauncherWindowTab1(Widget parent): Widget(nullptr)
{
Text = new TextEdit(this);
Text->SetText(
"Welcome to VKDoom\n\n"
"Click the tabs to look at other widgets\n\n"
"Also, this text is editable\n"
);
}
void LauncherWindowTab1::OnGeometryChanged()
{
Text->SetFrameGeometry(0, 10, GetWidth(), GetHeight());
}
LauncherWindowTab2::LauncherWindowTab2(Widget parent): Widget(nullptr)
{
WelcomeLabel = new TextLabel(this);
VersionLabel = new TextLabel(this);
SelectLabel = new TextLabel(this);
GeneralLabel = new TextLabel(this);
ExtrasLabel = new TextLabel(this);
FullscreenCheckbox = new CheckboxLabel(this);
DisableAutoloadCheckbox = new CheckboxLabel(this);
DontAskAgainCheckbox = new CheckboxLabel(this);
LightsCheckbox = new CheckboxLabel(this);
BrightmapsCheckbox = new CheckboxLabel(this);
WidescreenCheckbox = new CheckboxLabel(this);
GamesList = new ListView(this);
WelcomeLabel->SetText("Welcome to VKDoom");
VersionLabel->SetText("Version 0xdeadbabe.");
SelectLabel->SetText("Select which game file (IWAD) to run.");
GamesList->AddItem("Doom");
GamesList->AddItem("Doom 2: Electric Boogaloo");
GamesList->AddItem("Doom 3D");
GamesList->AddItem("Doom 4: The Quest for Peace");
GamesList->AddItem("Doom on Ice");
GamesList->AddItem("The Doom");
GamesList->AddItem("Doom 2");
GeneralLabel->SetText("General");
ExtrasLabel->SetText("Extra Graphics");
FullscreenCheckbox->SetText("Fullscreen");
DisableAutoloadCheckbox->SetText("Disable autoload");
DontAskAgainCheckbox->SetText("Don't ask me again");
LightsCheckbox->SetText("Lights");
BrightmapsCheckbox->SetText("Brightmaps");
WidescreenCheckbox->SetText("Widescreen");
}
void LauncherWindowTab2::OnGeometryChanged()
{
double y = 0, h;
h = WelcomeLabel->GetPreferredHeight();
WelcomeLabel->SetFrameGeometry(20, y, GetWidth() - 40, h);
y += h;
h = VersionLabel->GetPreferredHeight();
VersionLabel->SetFrameGeometry(20, y, GetWidth() - 40, h);
y += h + 10;
h = SelectLabel->GetPreferredHeight();
SelectLabel->SetFrameGeometry(20, y, GetWidth() - 40, h);
y += h;
double listViewTop = y + 10, listViewBottom;
y = GetHeight();
h = DontAskAgainCheckbox->GetPreferredHeight();
y -= h;
DontAskAgainCheckbox->SetFrameGeometry(20, y, 190, h);
WidescreenCheckbox->SetFrameGeometry(GetWidth() - 170, y, 150, WidescreenCheckbox->GetPreferredHeight());
h = DisableAutoloadCheckbox->GetPreferredHeight();
y -= h;
DisableAutoloadCheckbox->SetFrameGeometry(20, y, 190, h);
BrightmapsCheckbox->SetFrameGeometry(GetWidth() - 170, y, 150, BrightmapsCheckbox->GetPreferredHeight());
h = FullscreenCheckbox->GetPreferredHeight();
y -= h;
FullscreenCheckbox->SetFrameGeometry(20, y, 190, h);
LightsCheckbox->SetFrameGeometry(GetWidth() - 170, y, 150, LightsCheckbox->GetPreferredHeight());
h = GeneralLabel->GetPreferredHeight();
y -= h;
GeneralLabel->SetFrameGeometry(20, y, 190, GeneralLabel->GetPreferredHeight());
ExtrasLabel->SetFrameGeometry(GetWidth() - 170, y, 150, ExtrasLabel->GetPreferredHeight());
listViewBottom = y - 10;
GamesList->SetFrameGeometry(20, listViewTop, GetWidth() - 40, std::max<double>(listViewBottom - listViewTop, 0));
}
LauncherWindowTab3::LauncherWindowTab3(Widget parent): Widget(nullptr)
{
Label = new TextLabel(this);
Choices = new Dropdown(this);
Popup = new PushButton(this);
Label->SetText("Oh my, even more widgets");
Popup->SetText("Click me.");
Choices->SetMaxDisplayItems(2);
Choices->AddItem("First");
Choices->AddItem("Second");
Choices->AddItem("Third");
Choices->AddItem("Fourth");
Choices->AddItem("Fifth");
Choices->AddItem("Sixth");
Choices->OnChanged = [this](int index) {
std::cout << "Selected " << index << ":" << Choices->GetItem(index) << std::endl;
};
Popup->OnClick = []{
std::cout << "TODO: open popup" << std::endl;
};
}
void LauncherWindowTab3::OnGeometryChanged()
{
double y = 0, h;
y += 10;
h = Label->GetPreferredHeight();
Label->SetFrameGeometry(20, y, GetWidth() - 40, h);
y += h + 10;
h = Choices->GetPreferredHeight();
Choices->SetFrameGeometry(20, y, Choices->GetPreferredWidth(), h);
y += h + 10;
h = Popup->GetPreferredHeight();
Popup->SetFrameGeometry(20, y, 120, h);
y += h;
}
// ************************************************************
// Shared code
// ************************************************************
std::vector<SingleFontData> LoadWidgetFontData(const std::string& name)
{
@ -174,13 +315,24 @@ std::vector<uint8_t> LoadWidgetData(const std::string& name)
return ReadAllBytes(name);
}
enum class Backend {
{
Default, Win32, SDL2, X11, Wayland
};
int example(Backend backend = Backend::Default)
enum class Theme
{
WidgetTheme::SetTheme(std::make_unique<DarkWidgetTheme>());
Default, Light, Dark
};
int example(Backend backend = Backend::Default, Theme theme = Theme::Default)
{
// just for testing themes
switch (theme)
{
case Theme::Default: WidgetTheme::SetTheme(std::make_unique<DarkWidgetTheme>()); break;
case Theme::Dark: WidgetTheme::SetTheme(std::make_unique<DarkWidgetTheme>()); break;
case Theme::Light: WidgetTheme::SetTheme(std::make_unique<LightWidgetTheme>()); break;
}
// just for testing backends
switch (backend)
@ -192,40 +344,23 @@ int example(Backend backend = Backend::Default)
case Backend::Wayland: DisplayBackend::Set(DisplayBackend::TryCreateWayland()); break;
}
#if 1
auto launcher = new LauncherWindow();
launcher->SetFrameGeometry(100.0, 100.0, 615.0, 668.0);
launcher->Show();
#else
auto mainwindow = new MainWindow();
auto textedit = new TextEdit(mainwindow);
textedit->SetText(R"(
#version 460
in vec4 AttrPos;
in vec4 AttrColor;
out vec4 Color;
void main()
{
gl_Position = AttrPos;
Color = AttrColor;
}
)");
mainwindow->SetWindowTitle("ZWidget Example");
mainwindow->SetFrameGeometry(100.0, 100.0, 1700.0, 900.0);
mainwindow->SetCentralWidget(textedit);
textedit->SetFocus();
mainwindow->Show();
#endif
DisplayWindow::RunLoop();
return 0;
}
// ************************************************************
// Platform-specific code
// ************************************************************
#ifdef WIN32
#define WIN32_MEAN_AND_LEAN
#define NOMINMAX
#include <Windows.h>
#include <stdexcept>
@ -277,6 +412,7 @@ static std::vector<uint8_t> ReadAllBytes(const std::string& filename)
int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, PSTR cmdline, int cmdshow)
{
SetProcessDPIAware();
example();
}
@ -305,18 +441,25 @@ static std::vector<uint8_t> ReadAllBytes(const std::string& filename)
int main(int argc, const char** argv)
{
std::string backendStr = argc > 1? argv[1]: "";
std::transform(backendStr.begin(), backendStr.end(), backendStr.begin(),
[](unsigned char c){ return std::tolower(c); });
Backend backend = Backend::Default;
Theme theme = Theme::Default;
if (backendStr == "sdl2") backend = Backend::SDL2;
if (backendStr == "x11") backend = Backend::X11;
if (backendStr == "wayland") backend = Backend::Wayland;
if (backendStr == "win32") backend = Backend::Win32; // lol
for (auto i = 1; i < argc; i++)
{
std::string s = argv[i];
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c){ return std::tolower(c); });
example(backend);
if (s == "light") { theme = Theme::Light; continue; }
if (s == "dark") { theme = Theme::Dark; continue; }
if (s == "sdl2") { backend = Backend::SDL2; continue; }
if (s == "x11") { backend = Backend::X11; continue; }
if (s == "wayland") { backend = Backend::Wayland; continue; }
if (s == "win32") { backend = Backend::Win32; continue; } // lol
}
example(backend, theme);
}
#endif

View file

@ -260,7 +260,7 @@ int decodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width,
readPngHeader(&in[0], size); if(error) return;
size_t pos = 33; //first byte of the first chunk after the header
std::vector<unsigned char> idat; //the data from idat chunks
bool IEND = false, known_type = true;
bool IEND = false/*, known_type = true*/;
info.key_defined = false;
while(!IEND) //loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. IDAT data is put at the start of the in buffer
{
@ -312,7 +312,7 @@ int decodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width,
{
if(!(in[pos + 0] & 32)) { error = 69; return; } //error: unknown critical chunk (5th bit of first byte of chunk type is 0)
pos += (chunkLength + 4); //skip 4 letters and uninterpreted data of unimplemented chunk
known_type = false;
//known_type = false;
}
pos += 4; //step over CRC (which is ignored)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View file

@ -17,6 +17,21 @@ public:
return { r * s, g * s, b * s, a * s };
}
static Colorf fromRgba(uint32_t rgba)
{
return fromRgba8(
0xff & rgba>>24,
0xff & rgba>>16,
0xff & rgba>>8,
0xff & rgba
);
}
static Colorf fromRgb(uint32_t rgb)
{
return fromRgba(rgb<<8 | 0xff);
}
uint32_t toBgra8() const
{
uint32_t cr = (int)(std::max(std::min(r * 255.0f, 255.0f), 0.0f));

View file

@ -212,7 +212,7 @@ private:
Colorf cursor_color;
std::string::size_type sel_start = 0, sel_end = 0;
Colorf sel_foreground, sel_background = Colorf::fromRgba8(153, 201, 239);
Colorf sel_foreground, sel_background;
std::string text;
std::vector<SpanObject> objects;

View file

@ -55,7 +55,23 @@ public:
class WidgetTheme
{
struct SimpleTheme {
const Colorf bgMain; // background
const Colorf fgMain; //
const Colorf bgLight; // headers / inputs
const Colorf fgLight; //
const Colorf bgAction; // interactive elements
const Colorf fgAction; //
const Colorf bgHover; // hover / highlight
const Colorf fgHover; //
const Colorf bgActive; // click
const Colorf fgActive; //
const Colorf border; // around elements
const Colorf divider; // between elements
};
public:
WidgetTheme() {}
WidgetTheme(const struct SimpleTheme &theme);
virtual ~WidgetTheme() = default;
WidgetStyle* RegisterStyle(std::unique_ptr<WidgetStyle> widgetStyle, const std::string& widgetClass);

View file

@ -40,8 +40,8 @@ public:
std::string GetWindowTitle() const;
void SetWindowTitle(const std::string& text);
// Icon GetWindowIcon() const;
// void SetWindowIcon(const Icon& icon);
std::vector<std::shared_ptr<Image>> GetWindowIcon() const;
void SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images);
// Widget content box
Size GetSize() const;
@ -233,6 +233,7 @@ private:
Colorf WindowBackground = Colorf::fromRgba8(240, 240, 240);
std::string WindowTitle;
std::vector<std::shared_ptr<Image>> WindowIcon;
std::unique_ptr<DisplayWindow> DispWindow;
std::unique_ptr<Canvas> DispCanvas;
Widget* FocusWidget = nullptr;

View file

@ -83,6 +83,11 @@ private:
SpanLayout layout;
Rect box;
bool invalidated = true;
Line(const TextEdit *self)
{
layout.SetSelectionColors(self->selectionFG, self->selectionBG);
}
};
struct ivec2
@ -96,9 +101,10 @@ private:
bool operator!=(const ivec2& b) const { return x != b.x || y != b.y; }
};
Colorf selectionBG, selectionFG;
Scrollbar* vert_scrollbar;
Timer* timer = nullptr;
std::vector<Line> lines = { Line() };
std::vector<Line> lines = { Line{this} };
ivec2 cursor_pos = { 0, 0 };
int max_length = -1;
bool mouse_selecting = false;

View file

@ -26,6 +26,7 @@ class Widget;
class OpenFileDialog;
class SaveFileDialog;
class OpenFolderDialog;
class Image;
enum class StandardCursor
{
@ -163,6 +164,7 @@ public:
virtual ~DisplayWindow() = default;
virtual void SetWindowTitle(const std::string& text) = 0;
virtual void SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images) = 0;
virtual void SetWindowFrame(const Rect& box) = 0;
virtual void SetClientFrame(const Rect& box) = 0;
virtual void Show() = 0;

View file

@ -549,9 +549,7 @@ void BitmapCanvas::plot(float x, float y, float alpha, const Colorf& color)
int xx = (int)x;
int yy = (int)y;
int dwidth = width;
int dheight = height;
uint32_t* dest = pixels.data() + xx + yy * dwidth;
uint32_t* dest = pixels.data() + xx + yy * width;
uint32_t cred = (int32_t)clamp(color.r * 256.0f, 0.0f, 256.0f);
uint32_t cgreen = (int32_t)clamp(color.g * 256.0f, 0.0f, 256.0f);
@ -665,7 +663,6 @@ void BitmapCanvas::fillTile(float left, float top, float width, float height, Co
return;
int dwidth = this->width;
int dheight = this->height;
uint32_t* dest = this->pixels.data();
int x0 = (int)left;
@ -771,11 +768,9 @@ void BitmapCanvas::drawTile(CanvasTexture* tex, float left, float top, float wid
auto texture = static_cast<BitmapTexture*>(tex);
int swidth = texture->Width;
int sheight = texture->Height;
const uint32_t* src = texture->Data.data();
int dwidth = this->width;
int dheight = this->height;
uint32_t* dest = this->pixels.data();
int x0 = (int)left;
@ -880,11 +875,9 @@ void BitmapCanvas::drawGlyph(CanvasTexture* tex, float left, float top, float wi
auto texture = static_cast<BitmapTexture*>(tex);
int swidth = texture->Width;
int sheight = texture->Height;
const uint32_t* src = texture->Data.data();
int dwidth = this->width;
int dheight = this->height;
uint32_t* dest = this->pixels.data();
int x0 = (int)left;

View file

@ -255,7 +255,7 @@ PathFillRasterizer::Extent PathFillRasterizer::FindExtent(const PathScanline* sc
void PathFillRasterizer::Clear()
{
for (size_t y = first_scanline; y < last_scanline; y++)
for (int y = first_scanline; y < last_scanline; y++)
{
auto& scanline = scanlines[y];
if (!scanline.edges.empty())

View file

@ -262,7 +262,7 @@ int decodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width,
readPngHeader(&in[0], size); if(error) return;
size_t pos = 33; //first byte of the first chunk after the header
std::vector<unsigned char> idat; //the data from idat chunks
bool IEND = false, known_type = true;
bool IEND = false/*, known_type = true*/;
info.key_defined = false;
while(!IEND) //loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. IDAT data is put at the start of the in buffer
{
@ -314,7 +314,7 @@ int decodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width,
{
if(!(in[pos + 0] & 32)) { error = 69; return; } //error: unknown critical chunk (5th bit of first byte of chunk type is 0)
pos += (chunkLength + 4); //skip 4 letters and uninterpreted data of unimplemented chunk
known_type = false;
/*known_type = false;*/
}
pos += 4; //step over CRC (which is ignored)
}

View file

@ -72,16 +72,12 @@ void SpanLayout::DrawLayout(Canvas* canvas)
LineSegment& segment = line.segments.back();
if (cursor_visible && segment.end <= cursor_pos)
{
switch (segment.type)
{
case object_text:
if (segment.type == object_text)
{
double cursor_x = x + segment.x_position + canvas->measureText(segment.font, text.substr(segment.start, segment.end - segment.start)).width;
double cursor_width = 1;
canvas->fillRect(Rect::ltrb(cursor_x, y + line.ascender - segment.ascender, cursor_width, y + line.ascender + segment.descender), cursor_color);
}
break;
}
}
}

View file

@ -141,12 +141,26 @@ WidgetTheme* WidgetTheme::GetTheme()
return CurrentTheme.get();
}
/////////////////////////////////////////////////////////////////////////////
DarkWidgetTheme::DarkWidgetTheme()
WidgetTheme::WidgetTheme(const struct SimpleTheme &theme)
{
auto bgMain = theme.bgMain; // background
auto fgMain = theme.fgMain; //
auto bgLight = theme.bgLight; // headers / inputs
auto fgLight = theme.fgLight; //
auto bgAction = theme.bgAction; // interactive elements
auto fgAction = theme.fgAction; //
auto bgHover = theme.bgHover; // hover / highlight
auto fgHover = theme.fgHover; //
auto bgActive = theme.bgActive; // click
auto fgActive = theme.fgActive; //
auto border = theme.border; // around elements
auto divider = theme.divider; // between elements
auto none = Colorf::transparent();
auto widget = RegisterStyle(std::make_unique<BasicWidgetStyle>(), "widget");
auto textlabel = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textlabel");
/*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");
@ -167,272 +181,177 @@ DarkWidgetTheme::DarkWidgetTheme()
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));
widget->SetColor("color", fgMain);
widget->SetColor("window-background", bgMain);
widget->SetColor("window-border", bgMain);
widget->SetColor("window-caption-color", bgLight);
widget->SetColor("window-caption-text-color", fgLight);
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));
pushbutton->SetColor("color", fgAction);
pushbutton->SetColor("background-color", bgAction);
pushbutton->SetColor("border-left-color", border);
pushbutton->SetColor("border-top-color", border);
pushbutton->SetColor("border-right-color", border);
pushbutton->SetColor("border-bottom-color", border);
pushbutton->SetColor("hover", "color", fgHover);
pushbutton->SetColor("hover", "background-color", bgHover);
pushbutton->SetColor("down", "color", fgActive);
pushbutton->SetColor("down", "background-color", bgActive);
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));
lineedit->SetColor("color", fgLight);
lineedit->SetColor("background-color", bgLight);
lineedit->SetColor("border-left-color", border);
lineedit->SetColor("border-top-color", border);
lineedit->SetColor("border-right-color", border);
lineedit->SetColor("border-bottom-color", border);
lineedit->SetColor("selection-color", bgHover);
lineedit->SetColor("no-focus-selection-color", bgHover);
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));
textedit->SetColor("color", fgLight);
textedit->SetColor("background-color", bgLight);
textedit->SetColor("border-left-color", border);
textedit->SetColor("border-top-color", border);
textedit->SetColor("border-right-color", border);
textedit->SetColor("border-bottom-color", border);
textedit->SetColor("selection-color", bgHover);
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));
listview->SetColor("color", fgLight);
listview->SetColor("background-color", bgLight);
listview->SetColor("border-left-color", border);
listview->SetColor("border-top-color", border);
listview->SetColor("border-right-color", border);
listview->SetColor("border-bottom-color", border);
listview->SetColor("selection-color", bgHover);
dropdown->SetDouble("noncontent-left", 5.0);
dropdown->SetDouble("noncontent-top", 5.0);
dropdown->SetDouble("noncontent-right", 5.0);
dropdown->SetDouble("noncontent-bottom", 5.0);
dropdown->SetColor("background-color", Colorf::fromRgba8(38, 38, 38));
dropdown->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100));
dropdown->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100));
dropdown->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100));
dropdown->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
dropdown->SetColor("selection-color", Colorf::fromRgba8(100, 100, 100));
dropdown->SetColor("arrow-color", Colorf::fromRgba8(100, 100, 100));
dropdown->SetColor("color", fgLight);
dropdown->SetColor("background-color", bgLight);
dropdown->SetColor("border-left-color", border);
dropdown->SetColor("border-top-color", border);
dropdown->SetColor("border-right-color", border);
dropdown->SetColor("border-bottom-color", border);
dropdown->SetColor("arrow-color", border);
scrollbar->SetColor("track-color", Colorf::fromRgba8(33, 33, 33));
scrollbar->SetColor("thumb-color", Colorf::fromRgba8(58, 58, 58));
scrollbar->SetColor("track-color", divider);
scrollbar->SetColor("thumb-color", border);
tabbar->SetDouble("spacer-left", 20.0);
tabbar->SetDouble("spacer-right", 20.0);
tabbar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33));
tabbar->SetColor("background-color", bgLight);
tabbar_tab->SetDouble("noncontent-left", 15.0);
tabbar_tab->SetDouble("noncontent-right", 15.0);
tabbar_tab->SetDouble("noncontent-top", 1.0);
tabbar_tab->SetDouble("noncontent-bottom", 1.0);
tabbar_tab->SetColor("background-color", Colorf::fromRgba8(38, 38, 38));
tabbar_tab->SetColor("border-left-color", Colorf::fromRgba8(68, 68, 68));
tabbar_tab->SetColor("border-top-color", Colorf::fromRgba8(68, 68, 68));
tabbar_tab->SetColor("border-right-color", Colorf::fromRgba8(68, 68, 68));
tabbar_tab->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
tabbar_tab->SetColor("hover", "background-color", Colorf::fromRgba8(45, 45, 45));
tabbar_tab->SetColor("active", "background-color", Colorf::fromRgba8(51, 51, 51));
tabbar_tab->SetColor("active", "border-left-color", Colorf::fromRgba8(100, 100, 100));
tabbar_tab->SetColor("active", "border-top-color", Colorf::fromRgba8(100, 100, 100));
tabbar_tab->SetColor("active", "border-right-color", Colorf::fromRgba8(100, 100, 100));
tabbar_tab->SetColor("active", "border-bottom-color", Colorf::transparent());
tabbar_tab->SetColor("color", fgMain);
tabbar_tab->SetColor("background-color", bgMain);
tabbar_tab->SetColor("border-left-color", divider);
tabbar_tab->SetColor("border-top-color", divider);
tabbar_tab->SetColor("border-right-color", divider);
tabbar_tab->SetColor("border-bottom-color", border);
tabbar_tab->SetColor("hover", "color", fgAction);
tabbar_tab->SetColor("hover", "background-color", bgAction);
tabbar_tab->SetColor("active", "background-color", bgMain);
tabbar_tab->SetColor("active", "border-left-color", border);
tabbar_tab->SetColor("active", "border-top-color", border);
tabbar_tab->SetColor("active", "border-right-color", border);
tabbar_tab->SetColor("active", "border-bottom-color", none);
tabbar_spacer->SetDouble("noncontent-bottom", 1.0);
tabbar_spacer->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
tabbar_spacer->SetColor("border-bottom-color", border);
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));
checkbox_label->SetColor("checked-outer-border-color", border);
checkbox_label->SetColor("checked-inner-border-color", bgMain);
checkbox_label->SetColor("checked-color", fgMain);
checkbox_label->SetColor("unchecked-outer-border-color", border);
checkbox_label->SetColor("unchecked-inner-border-color", bgMain);
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));
menubar->SetColor("background-color", bgLight);
toolbar->SetColor("background-color", bgLight);
statusbar->SetColor("background-color", bgLight);
toolbarbutton->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78));
toolbarbutton->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88));
toolbarbutton->SetColor("hover", "color", fgHover);
toolbarbutton->SetColor("hover", "background-color", bgHover);
toolbarbutton->SetColor("down", "color", fgActive);
toolbarbutton->SetColor("down", "background-color", bgActive);
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));
menubaritem->SetColor("color", fgMain);
menubaritem->SetColor("hover", "color", fgHover);
menubaritem->SetColor("hover", "background-color", bgHover);
menubaritem->SetColor("down", "color", fgActive);
menubaritem->SetColor("down", "background-color", bgActive);
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));
menu->SetColor("color", fgMain);
menu->SetColor("background-color", bgMain);
menu->SetColor("border-left-color", border);
menu->SetColor("border-top-color", border);
menu->SetColor("border-right-color", border);
menu->SetColor("border-bottom-color", border);
menuitem->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78));
menuitem->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88));
menuitem->SetColor("hover", "color", fgHover);
menuitem->SetColor("hover", "background-color", bgHover);
menuitem->SetColor("down", "color", fgActive);
menuitem->SetColor("down", "background-color", bgActive);
}
/////////////////////////////////////////////////////////////////////////////
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 dropdown = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "dropdown");
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 tabbar_spacer = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar-spacer");
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");
DarkWidgetTheme::DarkWidgetTheme(): WidgetTheme({
Colorf::fromRgb(0x2A2A2A), // background
Colorf::fromRgb(0xE2DFDB), //
Colorf::fromRgb(0x212121), // headers / inputs
Colorf::fromRgb(0xE2DFDB), //
Colorf::fromRgb(0x444444), // interactive elements
Colorf::fromRgb(0xFFFFFF), //
Colorf::fromRgb(0xC83C00), // hover / highlight
Colorf::fromRgb(0xFFFFFF), //
Colorf::fromRgb(0xBBBBBB), // click
Colorf::fromRgb(0x000000), //
Colorf::fromRgb(0x646464), // around elements
Colorf::fromRgb(0x555555) // between elements
}) {};
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));
dropdown->SetDouble("noncontent-left", 5.0);
dropdown->SetDouble("noncontent-top", 5.0);
dropdown->SetDouble("noncontent-right", 5.0);
dropdown->SetDouble("noncontent-bottom", 5.0);
dropdown->SetColor("background-color", Colorf::fromRgba8(230, 230, 230));
dropdown->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155));
dropdown->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155));
dropdown->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155));
dropdown->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155));
dropdown->SetColor("selection-color", Colorf::fromRgba8(200, 200, 200));
dropdown->SetColor("arrow-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("spacer-left", 20.0);
tabbar->SetDouble("spacer-right", 20.0);
tabbar->SetColor("background-color", Colorf::fromRgba8(210, 210, 210));
tabbar_tab->SetDouble("noncontent-left", 15.0);
tabbar_tab->SetDouble("noncontent-right", 15.0);
tabbar_tab->SetDouble("noncontent-top", 1.0);
tabbar_tab->SetDouble("noncontent-bottom", 1.0);
tabbar_tab->SetColor("background-color", Colorf::fromRgba8(220, 220, 220));
tabbar_tab->SetColor("border-left-color", Colorf::fromRgba8(200, 200, 200));
tabbar_tab->SetColor("border-top-color", Colorf::fromRgba8(200, 200, 200));
tabbar_tab->SetColor("border-right-color", Colorf::fromRgba8(200, 200, 200));
tabbar_tab->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155));
tabbar_tab->SetColor("hover", "background-color", Colorf::fromRgba8(210, 210, 210));
tabbar_tab->SetColor("active", "background-color", Colorf::fromRgba8(240, 240, 240));
tabbar_tab->SetColor("active", "border-left-color", Colorf::fromRgba8(155, 155, 155));
tabbar_tab->SetColor("active", "border-top-color", Colorf::fromRgba8(155, 155, 155));
tabbar_tab->SetColor("active", "border-right-color", Colorf::fromRgba8(155, 155, 155));
tabbar_tab->SetColor("active", "border-bottom-color", Colorf::transparent());
tabbar_spacer->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155));
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));
}
LightWidgetTheme::LightWidgetTheme(): WidgetTheme({
Colorf::fromRgb(0xF0F0F0), // background
Colorf::fromRgb(0x191919), //
Colorf::fromRgb(0xFAFAFA), // headers / inputs
Colorf::fromRgb(0x191919), //
Colorf::fromRgb(0xC8C8C8), // interactive elements
Colorf::fromRgb(0x000000), //
Colorf::fromRgb(0xD2D2FF), // hover / highlight
Colorf::fromRgb(0x000000), //
Colorf::fromRgb(0xC7B4FF), // click
Colorf::fromRgb(0x000000), //
Colorf::fromRgb(0xA0A0A0), // around elements
Colorf::fromRgb(0xB9B9B9) // between elements
}) {};

View file

@ -5,9 +5,9 @@
Timer::Timer(Widget* owner) : OwnerObj(owner)
{
PrevTimerObj = owner->FirstTimerObj;
if (PrevTimerObj)
PrevTimerObj->PrevTimerObj = this;
NextTimerObj = owner->FirstTimerObj;
if (NextTimerObj)
NextTimerObj->PrevTimerObj = this;
owner->FirstTimerObj = this;
}
@ -27,7 +27,7 @@ void Timer::Start(int timeoutMilliseconds, bool repeat)
{
Stop();
TimerId = DisplayWindow::StartTimer(timeoutMilliseconds, [=]() {
TimerId = DisplayWindow::StartTimer(timeoutMilliseconds, [this,repeat]() {
if (!repeat)
Stop();
if (FuncExpired)

View file

@ -303,10 +303,10 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos
reader.Seek(loca.offsets[glyphIndex]);
ttf_int16 numberOfContours = reader.ReadInt16();
ttf_int16 xMin = reader.ReadInt16();
ttf_int16 yMin = reader.ReadInt16();
ttf_int16 xMax = reader.ReadInt16();
ttf_int16 yMax = reader.ReadInt16();
/*ttf_int16 xMin =*/ reader.ReadInt16();
/*ttf_int16 yMin =*/ reader.ReadInt16();
/*ttf_int16 xMax =*/ reader.ReadInt16();
/*ttf_int16 yMax =*/ reader.ReadInt16();
if (numberOfContours > 0) // Simple glyph
{
@ -450,7 +450,7 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos
if (transform)
{
for (int i = childPointsOffset; i < g.points.size(); i++)
for (int i = childPointsOffset; i < (int)g.points.size(); i++)
{
float x = g.points[i].x * mat2x2[0] + g.points[i].y * mat2x2[1];
float y = g.points[i].x * mat2x2[2] + g.points[i].y * mat2x2[3];
@ -492,7 +492,7 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos
dy = g.points[parentPointIndex].y - g.points[childPointIndex].y;
}
for (int i = childPointsOffset; i < g.points.size(); i++)
for (int i = childPointsOffset; i < (int)g.points.size(); i++)
{
g.points[i].x += dx;
g.points[i].y += dy;
@ -589,9 +589,9 @@ void TrueTypeFont::LoadCharacterMapEncoding(TrueTypeFileReader& reader)
for (ttf_uint16 c = startCode; c <= endCode; c++)
{
int offset = idRangeOffset / 2 + (c - startCode) - ((int)subformat.segCount - i);
if (offset >= 0 && offset < subformat.glyphIdArray.size())
if (offset >= 0 && offset < (int)subformat.glyphIdArray.size())
{
int glyphId = subformat.glyphIdArray[offset];
ttf_uint32 glyphId = subformat.glyphIdArray[offset];
if (firstGlyph)
{
range.startGlyphID = glyphId;
@ -689,9 +689,9 @@ void TTF_CMapSubtable4::Load(TrueTypeFileReader& reader)
language = reader.ReadUInt16();
segCount = reader.ReadUInt16() / 2;
ttf_uint16 searchRange = reader.ReadUInt16();
ttf_uint16 entrySelector = reader.ReadUInt16();
ttf_uint16 rangeShift = reader.ReadUInt16();
/*ttf_uint16 searchRange =*/ reader.ReadUInt16();
/*ttf_uint16 entrySelector =*/ reader.ReadUInt16();
/*ttf_uint16 rangeShift =*/ reader.ReadUInt16();
endCode.reserve(segCount);
startCode.reserve(segCount);
@ -746,9 +746,9 @@ void TTF_TableDirectory::Load(TrueTypeFileReader& reader)
numTables = reader.ReadUInt16();
// opentype spec says we can't use these for security reasons, so we pretend they never was part of the header
ttf_uint16 searchRange = reader.ReadUInt16();
ttf_uint16 entrySelector = reader.ReadUInt16();
ttf_uint16 rangeShift = reader.ReadUInt16();
/*ttf_uint16 searchRange =*/ reader.ReadUInt16();
/*ttf_uint16 entrySelector =*/ reader.ReadUInt16();
/*ttf_uint16 rangeShift =*/ reader.ReadUInt16();
for (ttf_uint16 i = 0; i < numTables; i++)
{

View file

@ -71,7 +71,7 @@ unsigned int UTF8Reader::character()
else
{
unsigned int ucs4 = (data[current_position] & bitmask_leadbyte_for_utf8[trailing_bytes]);
for (std::string::size_type i = 0; i < trailing_bytes; i++)
for (std::string::size_type i = 0; i < (size_t)trailing_bytes; i++)
{
if ((data[current_position + 1 + i] & 0xC0) == 0x80)
ucs4 = (ucs4 << 6) + (data[current_position + 1 + i] & 0x3f);
@ -94,7 +94,7 @@ std::string::size_type UTF8Reader::char_length()
if (current_position + 1 + trailing_bytes > length)
return 1;
for (std::string::size_type i = 0; i < trailing_bytes; i++)
for (std::string::size_type i = 0; i < (size_t)trailing_bytes; i++)
{
if ((data[current_position + 1 + i] & 0xC0) != 0x80)
return 1;
@ -136,7 +136,7 @@ void UTF8Reader::move_to_leadbyte()
lead_position--;
int trailing_bytes = trailing_bytes_for_utf8[data[lead_position]];
if (lead_position + trailing_bytes >= current_position)
if (lead_position + trailing_bytes >= (int)current_position)
current_position = lead_position;
}

View file

@ -173,6 +173,18 @@ void Widget::SetWindowTitle(const std::string& text)
}
}
std::vector<std::shared_ptr<Image>> Widget::GetWindowIcon() const
{
return WindowIcon;
}
void Widget::SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images)
{
WindowIcon = images;
if (DispWindow)
DispWindow->SetWindowIcon(WindowIcon);
}
Size Widget::GetSize() const
{
return ContentGeometry.size();
@ -365,7 +377,7 @@ void Widget::Repaint()
Widget* w = Window();
if (w->DispCanvas)
{
w->DispCanvas->begin(WindowBackground);
w->DispCanvas->begin(w->WindowBackground);
w->Paint(w->DispCanvas.get());
w->DispCanvas->end();
}

View file

@ -124,7 +124,7 @@ void Dropdown::ClearItems()
void Dropdown::SetSelectedItem(int index)
{
if (index < 0) index = 0;
if (index >= (int)items.size()) index = items.size() - 1;
if (index >= (int)items.size()) index = (int)items.size() - 1;
if (selectedItem == index) return;
@ -270,7 +270,7 @@ void Dropdown::OnGeometryChanged()
Point pos = MapTo(Window(), Point(0,0));
double width = GetWidth() + GetNoncontentLeft() + GetNoncontentRight();
double innerH = GetDisplayItems() * 25.0 + 10.0;
double innerH = GetDisplayItems() * 20.0 + 20;
double outerH = GetHeight();
pos.x -= GetNoncontentLeft();
@ -311,14 +311,14 @@ bool Dropdown::OpenDropdown()
listView->SetSelectedItem(selectedItem);
listView->OnActivated = [=]() { OnDropdownActivated(); };
listView->OnChanged = [=](int index) { OnDropdownActivated(); };
listView->OnActivated = [this]() { OnDropdownActivated(); };
listView->OnChanged = [this](int index) { OnDropdownActivated(); };
listView->SetFrameGeometry(
0,
0,
GetWidth() + GetNoncontentLeft() + GetNoncontentRight(),
GetDisplayItems() * 25.0 + 10.0
GetDisplayItems() * 20.0 + 20
);
OnGeometryChanged();

View file

@ -9,10 +9,10 @@ LineEdit::LineEdit(Widget* parent) : Widget(parent)
SetStyleClass("lineedit");
timer = new Timer(this);
timer->FuncExpired = [=]() { OnTimerExpired(); };
timer->FuncExpired = [this]() { OnTimerExpired(); };
scroll_timer = new Timer(this);
scroll_timer->FuncExpired = [=]() { OnScrollTimerExpired(); };
scroll_timer->FuncExpired = [this]() { OnScrollTimerExpired(); };
SetCursor(StandardCursor::ibeam);
}
@ -90,12 +90,16 @@ int LineEdit::GetCursorPos() const
Size LineEdit::GetTextSize()
{
Canvas* canvas = GetCanvas();
if (!canvas)
return Size(0.0, 0.0);
return GetVisualTextSize(canvas);
}
Size LineEdit::GetTextSize(const std::string& str)
{
Canvas* canvas = GetCanvas();
if (!canvas)
return Size(0.0, 0.0);
return canvas->measureText(str).size();
}
@ -674,7 +678,7 @@ bool LineEdit::InsertText(int pos, const std::string& str)
}
// checking if insert exceeds max length
if (UTF8Reader::utf8_length(text) + UTF8Reader::utf8_length(str) > max_length)
if (UTF8Reader::utf8_length(text) + UTF8Reader::utf8_length(str) > (size_t)max_length)
{
return false;
}
@ -757,6 +761,9 @@ int LineEdit::GetCharacterIndex(double mouse_x)
}
Canvas* canvas = GetCanvas();
if (!canvas)
return 0;
UTF8Reader utf8_reader(text.data(), text.length());
int seek_start = clip_start_offset;
@ -808,7 +815,7 @@ void LineEdit::UpdateTextClipping()
if (!canvas)
return;
Size text_size = GetVisualTextSize(canvas, clip_start_offset, (int)text.size() - clip_start_offset);
// Size text_size = GetVisualTextSize(canvas, clip_start_offset, (int)text.size() - clip_start_offset);
if (cursor_pos < clip_start_offset)
clip_start_offset = cursor_pos;
@ -822,7 +829,7 @@ void LineEdit::UpdateTextClipping()
utf8_reader.set_position(clip_start_offset);
utf8_reader.next();
clip_start_offset = (int)utf8_reader.position();
if (clip_start_offset == text.size())
if (clip_start_offset == (int)text.size())
break;
cursor_rect = GetCursorRect();
}
@ -837,7 +844,7 @@ void LineEdit::UpdateTextClipping()
utf8_reader.set_position(midpoint);
utf8_reader.move_to_leadbyte();
if (midpoint != utf8_reader.position())
if (midpoint != (int)utf8_reader.position())
utf8_reader.next();
midpoint = (int)utf8_reader.position();
@ -862,6 +869,8 @@ void LineEdit::UpdateTextClipping()
Rect LineEdit::GetCursorRect()
{
Canvas* canvas = GetCanvas();
if (!canvas)
return Rect::xywh(0.0, 0.0, 0.0, 0.0);
int substr_end = cursor_pos - clip_start_offset;
if (substr_end < 0)
@ -890,6 +899,8 @@ Rect LineEdit::GetCursorRect()
Rect LineEdit::GetSelectionRect()
{
Canvas* canvas = GetCanvas();
if (!canvas)
return Rect::xywh(0.0, 0.0, 0.0, 0.0);
// text before selection:
@ -949,6 +960,8 @@ void LineEdit::OnTimerExpired()
void LineEdit::OnGeometryChanged()
{
Canvas* canvas = GetCanvas();
if (!canvas)
return;
vertical_text_align = canvas->verticalTextAlign();

View file

@ -6,7 +6,7 @@ ListView::ListView(Widget* parent) : Widget(parent)
SetStyleClass("listview");
scrollbar = new Scrollbar(this);
scrollbar->FuncScroll = [=]() { OnScrollbarScroll(); };
scrollbar->FuncScroll = [this]() { OnScrollbarScroll(); };
SetColumnWidths({ 0.0 });
}
@ -37,7 +37,7 @@ void ListView::SetColumnWidths(const std::vector<double>& widths)
void ListView::AddItem(const std::string& text, int index, int column)
{
if (column < 0 || column >= columnwidths.size())
if (column < 0 || column >= (int)columnwidths.size())
return;
std::vector<std::string> newEntry;
@ -47,10 +47,10 @@ void ListView::AddItem(const std::string& text, int index, int column)
newEntry[column] = text;
if (index >= 0)
{
if (index >= items.size())
if (index >= (int)items.size())
{
newEntry[column] = "";
while (items.size() < index)
while ((int)items.size() < index)
items.push_back(newEntry);
newEntry[column] = text;
@ -71,7 +71,7 @@ void ListView::AddItem(const std::string& text, int index, int column)
void ListView::UpdateItem(const std::string& text, int index, int column)
{
if (index < 0 || index >= items.size() || column < 0 || column >= columnwidths.size())
if (index < 0 || index >= (int)items.size() || column < 0 || column >= (int)columnwidths.size())
return;
items[index][column] = text;
@ -80,7 +80,7 @@ void ListView::UpdateItem(const std::string& text, int index, int column)
void ListView::RemoveItem(int index)
{
if (!items.size() || index >= items.size())
if (!items.size() || index >= (int)items.size())
return;
if (index < 0)
@ -102,7 +102,7 @@ void ListView::Activate()
void ListView::SetSelectedItem(int index)
{
if (selectedItem != index && index >= 0 && index < items.size())
if (selectedItem != index && index >= 0 && index < (int)items.size())
{
selectedItem = index;
if (OnChanged) OnChanged(selectedItem);

View file

@ -36,7 +36,7 @@ void Menubar::ShowMenu(MenubarItem* item)
if (item->GetOpenCallback())
{
openMenu = new Menu(this);
openMenu->onCloseMenu = [=]() { CloseMenu(); };
openMenu->onCloseMenu = [this]() { CloseMenu(); };
item->GetOpenCallback()(openMenu);
if (item->AlignRight)
openMenu->SetRightPosition(item->MapToGlobal(Point(item->GetWidth(), item->GetHeight())));
@ -215,7 +215,7 @@ void Menubar::OnKeyDown(InputKey key)
/////////////////////////////////////////////////////////////////////////////
MenubarItem::MenubarItem(Menubar* menubar, std::string text, bool alignRight) : Widget(menubar), menubar(menubar), text(text), AlignRight(alignRight)
MenubarItem::MenubarItem(Menubar* menubar, std::string text, bool alignRight) : Widget(menubar), AlignRight(alignRight), menubar(menubar), text(text)
{
SetStyleClass("menubaritem");
}

View file

@ -3,13 +3,16 @@
#include "core/colorf.h"
#include <stdexcept>
#define HIDE_SMALL_BAR false
#define HIDE_SMALL_THUMB true
Scrollbar::Scrollbar(Widget* parent) : Widget(parent)
{
SetStyleClass("scrollbar");
UpdatePartPositions();
mouse_down_timer = new Timer(this);
mouse_down_timer->FuncExpired = [=]() { OnTimerExpired(); };
mouse_down_timer->FuncExpired = [this]() { OnTimerExpired(); };
}
Scrollbar::~Scrollbar()
@ -295,7 +298,15 @@ 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), GetStyleColor("track-color"));
auto height = GetHeight();
auto paint = rect_thumb.height < height;
if (HIDE_SMALL_BAR && !paint) return;
canvas->fillRect(Rect::shrink(Rect::xywh(0.0, 0.0, GetWidth(), height), 4.0, 0.0, 4.0, 0.0), GetStyleColor("track-color"));
if (HIDE_SMALL_THUMB && !paint) return;
canvas->fillRect(Rect::shrink(rect_thumb, 4.0, 0.0, 4.0, 0.0), GetStyleColor("thumb-color"));
}

View file

@ -9,7 +9,7 @@ TabWidget::TabWidget(Widget* parent) : Widget(parent)
Bar = new TabBar(this);
PageStack = new TabWidgetStack(this);
Bar->OnCurrentChanged = [=]() { OnBarCurrentChanged(); };
Bar->OnCurrentChanged = [this]() { OnBarCurrentChanged(); };
}
int TabWidget::AddTab(Widget* page, const std::string& label)
@ -127,7 +127,7 @@ int TabBar::AddTab(const std::shared_ptr<Image>& icon, const std::string& label)
TabBarTab* tab = new TabBarTab(this);
tab->SetIcon(icon);
tab->SetText(label);
tab->OnClick = [=]() { OnTabClicked(tab); };
tab->OnClick = [this,tab]() { OnTabClicked(tab); };
int pageIndex = (int)Tabs.size();
Tabs.push_back(tab);
if (CurrentIndex == -1)

View file

@ -13,11 +13,14 @@ TextEdit::TextEdit(Widget* parent) : Widget(parent)
{
SetStyleClass("textedit");
selectionBG = GetStyleColor("selection-color");
selectionFG = GetStyleColor("color");
timer = new Timer(this);
timer->FuncExpired = [=]() { OnTimerExpired(); };
timer->FuncExpired = [this]() { OnTimerExpired(); };
scroll_timer = new Timer(this);
scroll_timer->FuncExpired = [=]() { OnScrollTimerExpired(); };
scroll_timer->FuncExpired = [this]() { OnScrollTimerExpired(); };
SetCursor(StandardCursor::ibeam);
@ -175,13 +178,13 @@ void TextEdit::SetText(const std::string& text)
std::string::size_type end = text.find('\n');
while (end != std::string::npos)
{
TextEdit::Line line;
TextEdit::Line line{this};
line.text = text.substr(start, end - start);
lines.push_back(line);
start = end + 1;
end = text.find('\n', start);
}
TextEdit::Line line;
TextEdit::Line line{this};
line.text = text.substr(start);
lines.push_back(line);
@ -197,13 +200,13 @@ void TextEdit::AddText(const std::string& text)
std::string::size_type end = text.find('\n');
while (end != std::string::npos)
{
TextEdit::Line line;
TextEdit::Line line{this};
line.text = text.substr(start, end - start);
lines.push_back(line);
start = end + 1;
end = text.find('\n', start);
}
TextEdit::Line line;
TextEdit::Line line{this};
line.text = text.substr(start);
lines.push_back(line);
@ -434,7 +437,7 @@ void TextEdit::OnKeyDown(InputKey key)
if (GetKeyState(InputKey::Shift) && selection_length == 0)
selection_start = cursor_pos;
if (cursor_pos.y < lines.size() - 1)
if (cursor_pos.y < (int)lines.size() - 1)
{
cursor_pos.y++;
cursor_pos.x = std::min(lines[cursor_pos.y].text.size(), (size_t)cursor_pos.x);
@ -578,7 +581,7 @@ void TextEdit::OnLostFocus()
void TextEdit::CreateComponents()
{
vert_scrollbar = new Scrollbar(this);
vert_scrollbar->FuncScroll = [=]() { OnVerticalScroll(); };
vert_scrollbar->FuncScroll = [this]() { OnVerticalScroll(); };
vert_scrollbar->SetVisible(false);
vert_scrollbar->SetVertical();
}
@ -714,10 +717,10 @@ TextEdit::ivec2 TextEdit::FindNextBreakCharacter(ivec2 search_start)
if (search_start.x >= int(lines[search_start.y].text.size()) - 1)
return ivec2(lines[search_start.y].text.size(), search_start.y);
int pos = lines[search_start.y].text.find_first_of(break_characters, search_start.x);
size_t pos = lines[search_start.y].text.find_first_of(break_characters, search_start.x);
if (pos == std::string::npos)
return ivec2(lines[search_start.y].text.size(), search_start.y);
return ivec2(pos, search_start.y);
return ivec2((int)pos, search_start.y);
}
TextEdit::ivec2 TextEdit::FindPreviousBreakCharacter(ivec2 search_start)
@ -725,10 +728,10 @@ TextEdit::ivec2 TextEdit::FindPreviousBreakCharacter(ivec2 search_start)
search_start.x--;
if (search_start.x <= 0)
return ivec2(0, search_start.y);
int pos = lines[search_start.y].text.find_last_of(break_characters, search_start.x);
size_t pos = lines[search_start.y].text.find_last_of(break_characters, search_start.x);
if (pos == std::string::npos)
return ivec2(0, search_start.y);
return ivec2(pos, search_start.y);
return ivec2((int)pos, search_start.y);
}
void TextEdit::InsertText(ivec2 pos, const std::string& str)
@ -741,7 +744,7 @@ void TextEdit::InsertText(ivec2 pos, const std::string& str)
}
// checking if insert exceeds max length
if (ToOffset(ivec2(lines[lines.size() - 1].text.size(), lines.size() - 1)) + str.length() > max_length)
if (ToOffset(ivec2((int)lines[lines.size() - 1].text.size(), (int)lines.size() - 1)) + str.length() > (size_t)max_length)
{
return;
}
@ -759,7 +762,7 @@ void TextEdit::InsertText(ivec2 pos, const std::string& str)
pos.x += next_newline - start;
Line line;
Line line{this};
line.text = lines[pos.y].text.substr(pos.x);
lines.insert(lines.begin() + pos.y + 1, line);
lines[pos.y].text = lines[pos.y].text.substr(0, pos.x);
@ -836,9 +839,9 @@ void TextEdit::Del()
lines[cursor_pos.y].invalidated = true;
Update();
}
else if (cursor_pos.y + 1 < lines.size())
else if (cursor_pos.y + 1 < (int)lines.size())
{
selection_start = ivec2(lines[cursor_pos.y].text.length(), cursor_pos.y);
selection_start = ivec2((int)lines[cursor_pos.y].text.length(), cursor_pos.y);
selection_length = 1;
DeleteSelectedText();
}
@ -904,7 +907,7 @@ bool TextEdit::InputMaskAcceptsInput(ivec2 cursor_pos, const std::string& str)
std::string::size_type TextEdit::ToOffset(ivec2 pos) const
{
if (pos.y < lines.size())
if (pos.y < (int)lines.size())
{
std::string::size_type offset = 0;
for (int line = 0; line < pos.y; line++)
@ -927,7 +930,7 @@ std::string::size_type TextEdit::ToOffset(ivec2 pos) const
TextEdit::ivec2 TextEdit::FromOffset(std::string::size_type offset) const
{
int line_offset = 0;
for (int line = 0; line < lines.size(); line++)
for (int line = 0; line < (int)lines.size(); line++)
{
if (offset <= line_offset + lines[line].text.size())
{
@ -984,9 +987,9 @@ void TextEdit::LayoutLines(Canvas* canvas)
line.invalidated = false;
}
if (sel_start != sel_end && sel_start.y <= i && sel_end.y >= i)
if (sel_start != sel_end && sel_start.y <= (int)i && sel_end.y >= (int)i)
{
line.layout.SetSelectionRange(sel_start.y < i ? 0 : sel_start.x, sel_end.y > i ? line.text.size() : sel_end.x);
line.layout.SetSelectionRange(sel_start.y < (int)i ? 0 : sel_start.x, sel_end.y > (int)i ? line.text.size() : sel_end.x);
}
else
{
@ -996,7 +999,7 @@ void TextEdit::LayoutLines(Canvas* canvas)
line.layout.HideCursor();
if (HasFocus())
{
if (cursor_blink_visible && cursor_pos.y == i)
if (cursor_blink_visible && cursor_pos.y == (int)i)
{
line.layout.SetCursorPos(cursor_pos.x);
line.layout.SetCursorColor(textColor);
@ -1035,6 +1038,7 @@ TextEdit::ivec2 TextEdit::GetCharacterIndex(Point mouse_wincoords)
return ivec2(clamp(result.offset, (size_t)0, line.text.size()), i);
case SpanLayout::HitTestResult::outside_left:
return ivec2(0, i);
default:
case SpanLayout::HitTestResult::outside_right:
return ivec2(line.text.size(), i);
}

View file

@ -281,8 +281,30 @@ bool DBusOpenFileDialog::Show()
for (const std::string& uri : uris)
{
if (uri.size() > 7 && uri.substr(0, 7) == "file://")
outputFilenames.push_back(uri.substr(7));
outputFilenames.push_back(unescapeUri(uri.substr(7)));
}
return !uris.empty();
}
std::string DBusOpenFileDialog::unescapeUri(const std::string& s)
{
// This needs to be much more advanced to support any kind of uri. Why did they have to pass it as an uri!? idiots.
std::string result;
result.reserve(s.size());
size_t i = 0;
while (i < s.length())
{
if (s[i] == '%' && i + 2 < s.length() && s[i + 1] == '2' && s[i + 2] == '0')
{
result += ' ';
i += 3;
}
else
{
result += s[i];
i++;
}
}
return result;
}

View file

@ -20,6 +20,8 @@ public:
bool Show() override;
private:
static std::string unescapeUri(const std::string& s);
std::string ownerHandle;
std::string title;
std::string initialDirectory;

View file

@ -1,5 +1,8 @@
#include "dbus_open_folder_dialog.h"
#include <dbus/dbus.h>
#include <vector>
#include <algorithm>
DBusOpenFolderDialog::DBusOpenFolderDialog(std::string ownerHandle) : ownerHandle(ownerHandle)
{
@ -7,20 +10,271 @@ DBusOpenFolderDialog::DBusOpenFolderDialog(std::string ownerHandle) : ownerHandl
bool DBusOpenFolderDialog::Show()
{
return false;
// 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);
}
// directory - b
{
const char* key = "directory";
dbus_bool_t value = true;
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://")
{
selectedPath = unescapeUri(uri.substr(7));
break;
}
}
return !selectedPath.empty();
}
std::string DBusOpenFolderDialog::SelectedPath() const
{
return selected_path;
return selectedPath;
}
void DBusOpenFolderDialog::SetInitialDirectory(const std::string& path)
{
initial_directory = path;
initialDirectory = path;
}
void DBusOpenFolderDialog::SetTitle(const std::string& newtitle)
{
title = newtitle;
}
std::string DBusOpenFolderDialog::unescapeUri(const std::string& s)
{
// This needs to be much more advanced to support any kind of uri. Why did they have to pass it as an uri!? idiots.
std::string result;
result.reserve(s.size());
size_t i = 0;
while (i < s.length())
{
if (s[i] == '%' && i + 2 < s.length() && s[i + 1] == '2' && s[i + 2] == '0')
{
result += ' ';
i += 3;
}
else
{
result += s[i];
i++;
}
}
return result;
}

View file

@ -13,9 +13,10 @@ public:
void SetTitle(const std::string& newtitle) override;
private:
std::string ownerHandle;
static std::string unescapeUri(const std::string& s);
std::string selected_path;
std::string initial_directory;
std::string ownerHandle;
std::string title;
std::string initialDirectory;
std::string selectedPath;
};

View file

@ -1,4 +1,3 @@
#include "sdl2_display_window.h"
#include <stdexcept>
#include <SDL2/SDL_vulkan.h>
@ -33,7 +32,7 @@ SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWi
if (result != 0)
throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError());
}
WindowList[SDL_GetWindowID(Handle.window)] = this;
}
@ -73,8 +72,7 @@ std::vector<std::string> SDL2DisplayWindow::GetVulkanInstanceExtensions()
VkSurfaceKHR SDL2DisplayWindow::CreateVulkanSurface(VkInstance instance)
{
VkSurfaceKHR surfaceHandle = {};
SDL_Vulkan_CreateSurface(Handle.window, instance, &surfaceHandle);
if (surfaceHandle)
if (SDL_Vulkan_CreateSurface(Handle.window, instance, &surfaceHandle) != SDL_TRUE)
throw std::runtime_error("Could not create vulkan surface");
return surfaceHandle;
}
@ -84,6 +82,12 @@ void SDL2DisplayWindow::SetWindowTitle(const std::string& text)
SDL_SetWindowTitle(Handle.window, text.c_str());
}
void SDL2DisplayWindow::SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images)
{
// To do: call SDL_SetWindowIcon for the highest resolution image (or maybe the first 32x32 or higher? not sure what is best here)
// To do: when this is upgraded to SDL3, call SDL_AddSurfaceAlternateImage for all the images
}
void SDL2DisplayWindow::SetWindowFrame(const Rect& box)
{
// SDL2 doesn't really seem to have an API for this.
@ -423,14 +427,14 @@ 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_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;
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;
@ -451,14 +455,14 @@ void SDL2DisplayWindow::DispatchEvent(const SDL_Event& event)
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_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;
case SDL_MOUSEWHEEL: window->OnMouseWheel (event.wheel); break;
case SDL_MOUSEMOTION: window->OnMouseMotion (event.motion); break;
default:
if (event.type == PaintEventNumber) window->OnPaintEvent();
}
@ -468,13 +472,39 @@ 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;
case SDL_WINDOWEVENT_CLOSE:
WindowHost->OnWindowClose();
break;
case SDL_WINDOWEVENT_MOVED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
WindowHost->OnWindowGeometryChanged();
break;
case SDL_WINDOWEVENT_SHOWN:
case SDL_WINDOWEVENT_EXPOSED:
WindowHost->OnWindowPaint();
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
case SDL_WINDOWEVENT_FOCUS_LOST:
WindowHost->OnWindowDeactivated();
break;
case SDL_WINDOWEVENT_NONE:
case SDL_WINDOWEVENT_HIDDEN:
case SDL_WINDOWEVENT_MINIMIZED:
case SDL_WINDOWEVENT_MAXIMIZED:
case SDL_WINDOWEVENT_RESTORED:
case SDL_WINDOWEVENT_ENTER:
case SDL_WINDOWEVENT_LEAVE:
case SDL_WINDOWEVENT_TAKE_FOCUS:
case SDL_WINDOWEVENT_HIT_TEST:
case SDL_WINDOWEVENT_ICCPROF_CHANGED:
case SDL_WINDOWEVENT_DISPLAY_CHANGED:
// nope
break;
}
}
@ -788,5 +818,5 @@ SDL_Scancode SDL2DisplayWindow::InputKeyToScancode(InputKey inputkey)
case InputKey::RControl: return SDL_SCANCODE_RCTRL;
case InputKey::Tilde: return SDL_SCANCODE_GRAVE;
default: return (SDL_Scancode)0;
}
}
}

View file

@ -13,6 +13,7 @@ public:
~SDL2DisplayWindow();
void SetWindowTitle(const std::string& text) override;
void SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images) override;
void SetWindowFrame(const Rect& box) override;
void SetClientFrame(const Rect& box) override;
void Show() override;

View file

@ -9,49 +9,69 @@
#include <wayland-client-protocol-unstable.hpp>
#include <wayland-cursor.hpp>
#include "wl_fractional_scaling_protocol.hpp"
#include "wl_xdg_toplevel_icon.hpp"
#include <linux/input.h>
#include <poll.h>
#include <map>
#include <set>
#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");
}
#include "wl_cursor_shape.hpp"
return pfd.revents;
inline auto hash_proxy = [](wayland::proxy_t const& proxy) { return reinterpret_cast<size_t>(proxy.c_ptr()); };
using hash_proxy_t = decltype(hash_proxy);
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,
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;
uint32_t event_mask;
double surfaceX, surfaceY;
double dx, dy, dx_unaccel, dy_unaccel;
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 WaylandTimer
{
public:
WaylandTimer(int timeoutMilliseconds, std::function<void()> onTimer, int64_t nextTime) : timeoutMilliseconds(timeoutMilliseconds), onTimer(onTimer), nextTime(nextTime) {}
int timeoutMilliseconds = 0;
std::function<void()> onTimer;
int64_t nextTime = 0;
};
class WaylandDisplayWindow;
@ -60,7 +80,7 @@ class WaylandDisplayBackend : public DisplayBackend
{
public:
WaylandDisplayBackend();
~WaylandDisplayBackend();
~WaylandDisplayBackend();
std::unique_ptr<DisplayWindow> Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) override;
void ProcessEvents() override;
@ -78,9 +98,13 @@ public:
void OnWindowDestroyed(WaylandDisplayWindow* window);
void SetCursor(StandardCursor cursor);
void ShowCursor(bool enable);
void ShowCursor(bool enable);
bool GetKeyState(InputKey key);
std::string GetClipboardText() { return m_ClipboardContents; }
wayland::data_device_t& GetDataDevice() { return m_DataDevice; }
uint32_t GetKeyboardSerial() const { return m_KeyboardSerial; }
#ifdef USE_DBUS
std::unique_ptr<OpenFileDialog> CreateOpenFileDialog(DisplayWindow* owner) override;
std::unique_ptr<SaveFileDialog> CreateSaveFileDialog(DisplayWindow* owner) override;
@ -93,76 +117,92 @@ public:
wayland::registry_t s_waylandRegistry;
std::vector<WaylandDisplayWindow*> s_Windows;
WaylandDisplayWindow* m_FocusWindow = nullptr;
WaylandDisplayWindow* m_MouseFocusWindow = nullptr; // Mouse focus should be tracked separately.
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::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::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::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;
wayland::xdg_toplevel_icon_manager_v1_t m_XDGToplevelIconManager;
std::map<InputKey, bool> inputKeyStates; // True when the key is pressed, false when isn't
wayland::cursor_shape_manager_v1_t m_CursorShapeManager;
wayland::cursor_shape_device_v1_t m_CursorShapeDevice;
bool IsMouseLocked() { return hasMouseLock; }
void SetMouseLocked(bool val) { hasMouseLock = val; }
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 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);
void WaitForEvents(int timeout);
int GetTimerTimeout();
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);
InputKey XKBKeySymToInputKey(xkb_keysym_t keySym);
InputKey LinuxInputEventCodeToInputKey(uint32_t inputCode);
std::string GetWaylandCursorName(StandardCursor cursor);
wayland::cursor_shape_device_v1_shape GetWaylandCursorShape(StandardCursor cursor);
std::string GetWaylandCursorName(StandardCursor cursor);
bool hasKeyboard = false;
bool hasPointer = false;
bool hasMouseLock = false;
bool hasKeyboard = false;
bool hasPointer = false;
bool hasMouseLock = false;
ZTimer::TimePoint m_previousTime;
ZTimer::TimePoint m_currentTime;
ZTimer::TimePoint m_previousTime;
ZTimer::TimePoint m_currentTime;
ZTimer m_keyboardDelayTimer;
ZTimer m_keyboardRepeatTimer;
ZTimer m_keyboardDelayTimer;
ZTimer m_keyboardRepeatTimer;
InputKey previousKey = {};
std::string previousChars;
InputKey previousKey = {};
std::string previousChars;
uint32_t m_KeyboardSerial = 0;
uint32_t m_KeyboardSerial = 0;
uint32_t m_MouseSerial = 0;
xkb_context* m_KeymapContext = nullptr;
xkb_keymap* m_Keymap = nullptr;
xkb_state* m_KeyboardState = nullptr;
xkb_context* m_KeymapContext = nullptr;
xkb_keymap* m_Keymap = nullptr;
xkb_state* m_KeyboardState = nullptr;
WaylandPointerEvent currentPointerEvent = {0};
WaylandPointerEvent currentPointerEvent = {0};
wayland::data_device_t m_DataDevice;
std::unordered_map<wayland::data_offer_t, std::set<std::string>, hash_proxy_t> m_DataOfferMimeTypes;
std::string m_ClipboardContents;
std::vector<std::shared_ptr<WaylandTimer>> m_timers;
};

View file

@ -3,288 +3,313 @@
#include <cstring>
#include <dlfcn.h>
#include "core/image.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)
: backend(backend), m_owner(owner), windowHost(windowHost), m_PopupWindow(popupWindow), m_renderAPI(renderAPI)
{
m_AppSurface = backend->m_waylandCompositor.create_surface();
m_AppSurface = backend->m_waylandCompositor.create_surface();
m_NativeHandle.display = backend->s_waylandDisplay;
m_NativeHandle.surface = m_AppSurface;
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);
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_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_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);
};
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();
}
if (popupWindow)
{
InitializePopup();
}
else
{
InitializeToplevel();
}
backend->OnWindowCreated(this);
backend->OnWindowCreated(this);
this->DrawSurface();
this->DrawSurface();
}
WaylandDisplayWindow::~WaylandDisplayWindow()
{
backend->OnWindowDestroyed(this);
backend->OnWindowDestroyed(this);
}
void WaylandDisplayWindow::InitializeToplevel()
{
m_XDGToplevel = m_XDGSurface.get_toplevel();
m_XDGToplevel.set_title("ZWidget Window");
m_XDGToplevel = m_XDGSurface.get_toplevel();
m_XDGToplevel.set_title("ZWidget Window");
if (m_owner)
m_XDGToplevel.set_parent(m_owner->m_XDGToplevel);
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);
}
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();
m_AppSurface.commit();
backend->s_waylandDisplay.roundtrip();
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);
};
// 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_close() = [&] () {
OnExitEvent();
};
m_XDGToplevel.on_configure_bounds() = [this] (int32_t width, int32_t height)
{
m_XDGToplevel.on_configure_bounds() = [this] (int32_t width, int32_t height)
{
};
};
m_XDGExported = backend->m_XDGExporter.export_toplevel(m_AppSurface);
m_XDGExported = backend->m_XDGExporter.export_toplevel(m_AppSurface);
m_XDGExported.on_handle() = [&] (std::string handleStr) {
OnExportHandleEvent(handleStr);
};
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!");
if (!m_owner)
throw std::runtime_error("Popup window must have an owner!");
wayland::xdg_positioner_t popupPositioner = backend->m_XDGWMBase.create_positioner();
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);
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 = 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_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_repositioned()
m_XDGPopup.on_popup_done() = [&] () {
OnExitEvent();
};
m_XDGPopup.on_popup_done() = [&] () {
OnExitEvent();
};
m_AppSurface.commit();
m_AppSurface.commit();
backend->s_waylandDisplay.roundtrip();
backend->s_waylandDisplay.roundtrip();
}
void WaylandDisplayWindow::SetWindowTitle(const std::string& text)
{
if (m_XDGToplevel)
m_XDGToplevel.set_title(text);
if (m_XDGToplevel)
m_XDGToplevel.set_title(text);
}
void WaylandDisplayWindow::SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images)
{
if (backend->m_XDGToplevelIconManager)
{
if (images.empty())
{
backend->m_XDGToplevelIconManager.set_icon(m_XDGToplevel, nullptr);
return;
}
m_XDGToplevelIcon = backend->m_XDGToplevelIconManager.create_icon();
CreateAppIconBuffers(images);
for (auto& icon_buffer : appIconBuffers)
{
m_XDGToplevelIcon.add_buffer(icon_buffer, 1);
}
backend->m_XDGToplevelIconManager.set_icon(m_XDGToplevel, m_XDGToplevelIcon);
}
}
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();
// 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);
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();
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;
}
if (m_XDGToplevel)
{
m_XDGToplevel.set_fullscreen(backend->m_waylandOutput);
isFullscreen = true;
}
}
void WaylandDisplayWindow::ShowMaximized()
{
if (m_XDGToplevel)
m_XDGToplevel.set_maximized();
if (m_XDGToplevel)
m_XDGToplevel.set_maximized();
}
void WaylandDisplayWindow::ShowMinimized()
{
if (m_XDGToplevel)
m_XDGToplevel.set_minimized();
if (m_XDGToplevel)
m_XDGToplevel.set_minimized();
}
void WaylandDisplayWindow::ShowNormal()
{
if (m_XDGToplevel)
m_XDGToplevel.unset_fullscreen();
if (m_XDGToplevel)
m_XDGToplevel.unset_fullscreen();
}
bool WaylandDisplayWindow::IsWindowFullscreen()
{
return isFullscreen;
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();
// 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();
// 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;
std::string tokenString;
xdgActivationToken.on_done() = [&tokenString] (std::string obtainedString) {
tokenString = obtainedString;
};
xdgActivationToken.on_done() = [&tokenString] (std::string obtainedString) {
tokenString = obtainedString;
};
xdgActivationToken.set_surface(m_AppSurface);
xdgActivationToken.commit(); // This will set our token string
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();
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);
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);
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);
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);
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);
if (m_ConfinedPointer)
m_ConfinedPointer.proxy_release();
ShowCursor(true);
}
void WaylandDisplayWindow::Update()
{
m_NeedsUpdate = true;
m_NeedsUpdate = true;
}
bool WaylandDisplayWindow::GetKeyState(InputKey key)
{
return backend->GetKeyState(key);
return backend->GetKeyState(key);
}
void WaylandDisplayWindow::SetCursor(StandardCursor cursor)
{
backend->SetCursor(cursor);
backend->SetCursor(cursor);
}
Rect WaylandDisplayWindow::GetWindowFrame() const
{
return Rect(m_WindowGlobalPos.x, m_WindowGlobalPos.y, m_WindowSize.width, m_WindowSize.height);
return Rect(m_WindowGlobalPos.x, m_WindowGlobalPos.y, m_WindowSize.width, m_WindowSize.height);
}
Size WaylandDisplayWindow::GetClientSize() const
{
return m_WindowSize / m_ScaleFactor;
return m_WindowSize / m_ScaleFactor;
}
int WaylandDisplayWindow::GetPixelWidth() const
{
return m_WindowSize.width;
return m_WindowSize.width;
}
int WaylandDisplayWindow::GetPixelHeight() const
{
return m_WindowSize.height;
return m_WindowSize.height;
}
double WaylandDisplayWindow::GetDpiScale() const
{
return m_ScaleFactor;
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);
// 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);
std::memcpy(shared_mem->get_mem(), (void*)pixels, width * height * 4);
}
void WaylandDisplayWindow::SetBorderColor(uint32_t bgra8)
@ -304,79 +329,115 @@ void WaylandDisplayWindow::SetCaptionTextColor(uint32_t bgra8)
std::string WaylandDisplayWindow::GetClipboardText()
{
return m_ClipboardContents;
return backend->GetClipboardText();
}
void WaylandDisplayWindow::SetClipboardText(const std::string& text)
{
m_ClipboardContents = text;
m_DataSource = backend->m_DataDeviceManager.create_data_source();
m_DataSource.on_send() = [text] (const std::string& mime_type, const int fd)
{
write(fd, text.c_str(), text.size());
close(fd);
};
m_DataSource.offer("text/plain");
backend->GetDataDevice().set_selection(m_DataSource, backend->GetKeyboardSerial());
}
Point WaylandDisplayWindow::MapFromGlobal(const Point& pos) const
{
return (pos - m_WindowGlobalPos) / m_ScaleFactor;
return (pos - m_WindowGlobalPos) / m_ScaleFactor;
}
Point WaylandDisplayWindow::MapToGlobal(const Point& pos) const
{
return (m_WindowGlobalPos + pos) / m_ScaleFactor;
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();
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;
m_windowID = exportedHandle;
}
void WaylandDisplayWindow::OnExitEvent()
{
windowHost->OnWindowClose();
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);
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();
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();
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 (width == 0 || height == 0)
return;
if (shared_mem)
shared_mem.reset();
if (shared_mem)
shared_mem.reset();
int scaled_width = width * m_ScaleFactor;
int scaled_height = height * m_ScaleFactor;
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);
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_AppSurfaceBuffer = pool.create_buffer(0, scaled_width, scaled_height, scaled_width * 4, wayland::shm_format::xrgb8888);
m_WindowSize = Size(scaled_width, scaled_height);
m_WindowSize = Size(scaled_width, scaled_height);
}
void WaylandDisplayWindow::CreateAppIconBuffers(const std::vector<std::shared_ptr<Image>>& images)
{
if (images.empty())
return;
appIconBuffers.clear();
appIconSharedMems.clear();
for (auto& image: images)
{
const int image_size = image->GetWidth() * image->GetHeight() * 4;
const int stride = image->GetWidth() * 4;
auto new_shared_mem = std::make_shared<SharedMemHelper>(image_size);
auto pool = backend->m_waylandSHM.create_pool(new_shared_mem->get_fd(), image_size);
auto new_buffer = pool.create_buffer(0, image->GetWidth(), image->GetHeight(), stride, wayland::shm_format::argb8888);
// Fill the buffer with the icon data
std::memcpy(new_shared_mem->get_mem(), image->GetData(), image_size);
appIconSharedMems.push_back(new_shared_mem);
appIconBuffers.push_back(new_buffer);
}
}
std::string WaylandDisplayWindow::GetWaylandWindowID()
{
return m_windowID;
return m_windowID;
}
// This is to avoid needing all the Vulkan headers and the volk binding library just for this:
@ -398,11 +459,11 @@ typedef PFN_vkVoidFunction(VKAPI_PTR* PFN_vkGetInstanceProcAddr)(VkInstance inst
typedef VkFlags VkWaylandSurfaceCreateFlagsKHR;
typedef struct VkWaylandSurfaceCreateInfoKHR
{
VkStructureType sType;
const void* pNext;
VkWaylandSurfaceCreateFlagsKHR flags;
struct wl_display* display;
struct wl_surface* surface;
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);

View file

@ -25,70 +25,71 @@ 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...);
};
{
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();
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.");
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.");
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) + ".");
}
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());
}
}
~SharedMemHelper() noexcept
{
if(fd)
{
munmap(mem, len);
close(fd);
shm_unlink(name.c_str());
}
}
int get_fd() const
{
return fd;
}
int get_fd() const
{
return fd;
}
void *get_mem()
{
return mem;
}
void *get_mem()
{
return mem;
}
private:
std::string name;
int fd = 0;
size_t len = 0;
void *mem = nullptr;
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();
WaylandDisplayWindow(WaylandDisplayBackend* backend, DisplayWindowHost* windowHost, bool popupWindow, WaylandDisplayWindow* owner, RenderAPI renderAPI);
~WaylandDisplayWindow();
void SetWindowTitle(const std::string& text) override;
void SetWindowTitle(const std::string& text) override;
void SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images) override;
void SetWindowFrame(const Rect& box) override;
void SetClientFrame(const Rect& box) override;
void Show() override;
@ -135,64 +136,69 @@ public:
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();
// 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 DrawSurface(uint32_t serial = 0);
void InitializeToplevel();
void InitializePopup();
void InitializeToplevel();
void InitializePopup();
WaylandDisplayBackend* backend = nullptr;
WaylandDisplayWindow* m_owner = nullptr;
DisplayWindowHost* windowHost = nullptr;
bool m_PopupWindow = false;
WaylandDisplayBackend* backend = nullptr;
WaylandDisplayWindow* m_owner = nullptr;
DisplayWindowHost* windowHost = nullptr;
bool m_PopupWindow = false;
bool m_NeedsUpdate = true;
bool m_NeedsUpdate = true;
Point m_WindowGlobalPos = Point(0, 0);
Size m_WindowSize = Size(0, 0);
double m_ScaleFactor = 1.0;
Point m_WindowGlobalPos = Point(0, 0);
Size m_WindowSize = Size(0, 0);
double m_ScaleFactor = 1.0;
Point m_SurfaceMousePos = Point(0, 0);
Point m_SurfaceMousePos = Point(0, 0);
WaylandNativeHandle m_NativeHandle;
RenderAPI m_renderAPI;
WaylandNativeHandle m_NativeHandle;
RenderAPI m_renderAPI;
wayland::data_device_t m_DataDevice;
wayland::data_source_t m_DataSource;
wayland::data_source_t m_DataSource;
wayland::zxdg_toplevel_decoration_v1_t m_XDGToplevelDecoration;
wayland::zxdg_toplevel_decoration_v1_t m_XDGToplevelDecoration;
wayland::fractional_scale_v1_t m_FractionalScale;
wayland::fractional_scale_v1_t m_FractionalScale;
wayland::surface_t m_AppSurface;
wayland::buffer_t m_AppSurfaceBuffer;
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::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::zxdg_exported_v2_t m_XDGExported;
wayland::zwp_locked_pointer_v1_t m_LockedPointer;
wayland::zwp_confined_pointer_v1_t m_ConfinedPointer;
wayland::zwp_locked_pointer_v1_t m_LockedPointer;
wayland::zwp_confined_pointer_v1_t m_ConfinedPointer;
wayland::callback_t m_FrameCallback;
wayland::xdg_toplevel_icon_v1_t m_XDGToplevelIcon;
std::string m_windowID;
std::string m_ClipboardContents;
wayland::callback_t m_FrameCallback;
std::shared_ptr<SharedMemHelper> shared_mem;
std::string m_windowID;
bool isFullscreen = false;
std::shared_ptr<SharedMemHelper> shared_mem;
// Helper functions
void CreateBuffers(int32_t width, int32_t height);
std::string GetWaylandWindowID();
std::vector<std::shared_ptr<SharedMemHelper>> appIconSharedMems;
std::vector<wayland::buffer_t> appIconBuffers;
friend WaylandDisplayBackend;
bool isFullscreen = false;
// Helper functions
void CreateBuffers(int32_t width, int32_t height);
void CreateAppIconBuffers(const std::vector<std::shared_ptr<Image>>& images);
std::string GetWaylandWindowID();
friend WaylandDisplayBackend;
};

View file

@ -0,0 +1,218 @@
#include "wl_cursor_shape.hpp"
using namespace wayland;
using namespace wayland::detail;
const wl_interface* cursor_shape_manager_v1_interface_destroy_request[0] = {
};
const wl_interface* cursor_shape_manager_v1_interface_get_pointer_request[2] = {
&cursor_shape_device_v1_interface,
&pointer_interface,
};
const wl_interface* cursor_shape_manager_v1_interface_get_tablet_tool_v2_request[2] = {
&cursor_shape_device_v1_interface,
&zwp_tablet_tool_v2_interface,
};
const wl_message cursor_shape_manager_v1_interface_requests[3] = {
{
"destroy",
"",
cursor_shape_manager_v1_interface_destroy_request,
},
{
"get_pointer",
"no",
cursor_shape_manager_v1_interface_get_pointer_request,
},
{
"get_tablet_tool_v2",
"no",
cursor_shape_manager_v1_interface_get_tablet_tool_v2_request,
},
};
const wl_message cursor_shape_manager_v1_interface_events[0] = {
};
const wl_interface wayland::detail::cursor_shape_manager_v1_interface =
{
"wp_cursor_shape_manager_v1",
2,
3,
cursor_shape_manager_v1_interface_requests,
0,
cursor_shape_manager_v1_interface_events,
};
const wl_interface* cursor_shape_device_v1_interface_destroy_request[0] = {
};
const wl_interface* cursor_shape_device_v1_interface_set_shape_request[2] = {
nullptr,
nullptr,
};
const wl_message cursor_shape_device_v1_interface_requests[2] = {
{
"destroy",
"",
cursor_shape_device_v1_interface_destroy_request,
},
{
"set_shape",
"uu",
cursor_shape_device_v1_interface_set_shape_request,
},
};
const wl_message cursor_shape_device_v1_interface_events[0] = {
};
const wl_interface wayland::detail::cursor_shape_device_v1_interface =
{
"wp_cursor_shape_device_v1",
2,
2,
cursor_shape_device_v1_interface_requests,
0,
cursor_shape_device_v1_interface_events,
};
cursor_shape_manager_v1_t::cursor_shape_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(&cursor_shape_manager_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return cursor_shape_manager_v1_t(p); });
}
cursor_shape_manager_v1_t::cursor_shape_manager_v1_t()
{
set_interface(&cursor_shape_manager_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return cursor_shape_manager_v1_t(p); });
}
cursor_shape_manager_v1_t::cursor_shape_manager_v1_t(wp_cursor_shape_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(&cursor_shape_manager_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return cursor_shape_manager_v1_t(p); });
}
cursor_shape_manager_v1_t::cursor_shape_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/)
: proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){
set_interface(&cursor_shape_manager_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return cursor_shape_manager_v1_t(p); });
}
cursor_shape_manager_v1_t cursor_shape_manager_v1_t::proxy_create_wrapper()
{
return {*this, construct_proxy_wrapper_tag()};
}
const std::string cursor_shape_manager_v1_t::interface_name = "wp_cursor_shape_manager_v1";
cursor_shape_manager_v1_t::operator wp_cursor_shape_manager_v1*() const
{
return reinterpret_cast<wp_cursor_shape_manager_v1*> (c_ptr());
}
cursor_shape_device_v1_t cursor_shape_manager_v1_t::get_pointer(pointer_t const& pointer)
{
proxy_t p = marshal_constructor(1U, &cursor_shape_device_v1_interface, nullptr, pointer.proxy_has_object() ? reinterpret_cast<wl_object*>(pointer.c_ptr()) : nullptr);
return cursor_shape_device_v1_t(p);
}
cursor_shape_device_v1_t cursor_shape_manager_v1_t::get_tablet_tool_v2(zwp_tablet_tool_v2_t const& tablet_tool)
{
proxy_t p = marshal_constructor(2U, &cursor_shape_device_v1_interface, nullptr, tablet_tool.proxy_has_object() ? reinterpret_cast<wl_object*>(tablet_tool.c_ptr()) : nullptr);
return cursor_shape_device_v1_t(p);
}
int cursor_shape_manager_v1_t::dispatcher(uint32_t opcode, const std::vector<any>& args, const std::shared_ptr<detail::events_base_t>& e)
{
return 0;
}
cursor_shape_device_v1_t::cursor_shape_device_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(&cursor_shape_device_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return cursor_shape_device_v1_t(p); });
}
cursor_shape_device_v1_t::cursor_shape_device_v1_t()
{
set_interface(&cursor_shape_device_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return cursor_shape_device_v1_t(p); });
}
cursor_shape_device_v1_t::cursor_shape_device_v1_t(wp_cursor_shape_device_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(&cursor_shape_device_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return cursor_shape_device_v1_t(p); });
}
cursor_shape_device_v1_t::cursor_shape_device_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/)
: proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){
set_interface(&cursor_shape_device_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return cursor_shape_device_v1_t(p); });
}
cursor_shape_device_v1_t cursor_shape_device_v1_t::proxy_create_wrapper()
{
return {*this, construct_proxy_wrapper_tag()};
}
const std::string cursor_shape_device_v1_t::interface_name = "wp_cursor_shape_device_v1";
cursor_shape_device_v1_t::operator wp_cursor_shape_device_v1*() const
{
return reinterpret_cast<wp_cursor_shape_device_v1*> (c_ptr());
}
void cursor_shape_device_v1_t::set_shape(uint32_t serial, cursor_shape_device_v1_shape const& shape)
{
marshal(1U, serial, static_cast<uint32_t>(shape));
}
int cursor_shape_device_v1_t::dispatcher(uint32_t opcode, const std::vector<any>& args, const std::shared_ptr<detail::events_base_t>& e)
{
return 0;
}

View file

@ -0,0 +1,258 @@
#pragma once
#include <array>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include <wayland-client.hpp>
#include <wayland-client-protocol-unstable.hpp>
struct wp_cursor_shape_manager_v1;
struct wp_cursor_shape_device_v1;
namespace wayland
{
class cursor_shape_manager_v1_t;
class cursor_shape_device_v1_t;
enum class cursor_shape_device_v1_shape : uint32_t;
enum class cursor_shape_device_v1_error : uint32_t;
namespace detail
{
extern const wl_interface cursor_shape_manager_v1_interface;
extern const wl_interface cursor_shape_device_v1_interface;
}
/** \brief cursor shape manager
This global offers an alternative, optional way to set cursor images. This
new way uses enumerated cursors instead of a wl_surface like
wl_pointer.set_cursor does.
Warning! The protocol described in this file is currently in the testing
phase. Backward compatible changes may be added together with the
corresponding interface version bump. Backward incompatible changes can
only be done by creating a new major version of the extension.
*/
class cursor_shape_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);
cursor_shape_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/);
public:
cursor_shape_manager_v1_t();
explicit cursor_shape_manager_v1_t(const proxy_t &proxy);
cursor_shape_manager_v1_t(wp_cursor_shape_manager_v1 *p, wrapper_type t = wrapper_type::standard);
cursor_shape_manager_v1_t proxy_create_wrapper();
static const std::string interface_name;
operator wp_cursor_shape_manager_v1*() const;
/** \brief manage the cursor shape of a pointer device
\param pointer
Obtain a wp_cursor_shape_device_v1 for a wl_pointer object.
When the pointer capability is removed from the wl_seat, the
wp_cursor_shape_device_v1 object becomes inert.
*/
cursor_shape_device_v1_t get_pointer(pointer_t const& pointer);
/** \brief Minimum protocol version required for the \ref get_pointer function
*/
static constexpr std::uint32_t get_pointer_since_version = 1;
/** \brief manage the cursor shape of a tablet tool device
\param tablet_tool
Obtain a wp_cursor_shape_device_v1 for a zwp_tablet_tool_v2 object.
When the zwp_tablet_tool_v2 is removed, the wp_cursor_shape_device_v1
object becomes inert.
*/
cursor_shape_device_v1_t get_tablet_tool_v2(zwp_tablet_tool_v2_t const& tablet_tool);
/** \brief Minimum protocol version required for the \ref get_tablet_tool_v2 function
*/
static constexpr std::uint32_t get_tablet_tool_v2_since_version = 1;
};
/** \brief cursor shape for a device
This interface allows clients to set the cursor shape.
*/
class cursor_shape_device_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);
cursor_shape_device_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/);
public:
cursor_shape_device_v1_t();
explicit cursor_shape_device_v1_t(const proxy_t &proxy);
cursor_shape_device_v1_t(wp_cursor_shape_device_v1 *p, wrapper_type t = wrapper_type::standard);
cursor_shape_device_v1_t proxy_create_wrapper();
static const std::string interface_name;
operator wp_cursor_shape_device_v1*() const;
/** \brief set device cursor to the shape
\param serial serial number of the enter event
\param shape
Sets the device cursor to the specified shape. The compositor will
change the cursor image based on the specified shape.
The cursor actually changes only if the input device focus is one of
the requesting client's surfaces. If any, the previous cursor image
(surface or shape) is replaced.
The "shape" argument must be a valid enum entry, otherwise the
invalid_shape protocol error is raised.
This is similar to the wl_pointer.set_cursor and
zwp_tablet_tool_v2.set_cursor requests, but this request accepts a
shape instead of contents in the form of a surface. Clients can mix
set_cursor and set_shape requests.
The serial parameter must match the latest wl_pointer.enter or
zwp_tablet_tool_v2.proximity_in serial number sent to the client.
Otherwise the request will be ignored.
*/
void set_shape(uint32_t serial, cursor_shape_device_v1_shape const& shape);
/** \brief Minimum protocol version required for the \ref set_shape function
*/
static constexpr std::uint32_t set_shape_since_version = 1;
};
/** \brief cursor shapes
This enum describes cursor shapes.
The names are taken from the CSS W3C specification:
https://w3c.github.io/csswg-drafts/css-ui/#cursor
with a few additions.
Note that there are some groups of cursor shapes that are related:
The first group is drag-and-drop cursors which are used to indicate
the selected action during dnd operations. The second group is resize
cursors which are used to indicate resizing and moving possibilities
on window borders. It is recommended that the shapes in these groups
should use visually compatible images and metaphors.
*/
enum class cursor_shape_device_v1_shape : uint32_t
{
/** \brief default cursor */
_default = 1,
/** \brief a context menu is available for the object under the cursor */
context_menu = 2,
/** \brief help is available for the object under the cursor */
help = 3,
/** \brief pointer that indicates a link or another interactive element */
pointer = 4,
/** \brief progress indicator */
progress = 5,
/** \brief program is busy, user should wait */
wait = 6,
/** \brief a cell or set of cells may be selected */
cell = 7,
/** \brief simple crosshair */
crosshair = 8,
/** \brief text may be selected */
text = 9,
/** \brief vertical text may be selected */
vertical_text = 10,
/** \brief drag-and-drop: alias of/shortcut to something is to be created */
alias = 11,
/** \brief drag-and-drop: something is to be copied */
copy = 12,
/** \brief drag-and-drop: something is to be moved */
move = 13,
/** \brief drag-and-drop: the dragged item cannot be dropped at the current cursor location */
no_drop = 14,
/** \brief drag-and-drop: the requested action will not be carried out */
not_allowed = 15,
/** \brief drag-and-drop: something can be grabbed */
grab = 16,
/** \brief drag-and-drop: something is being grabbed */
grabbing = 17,
/** \brief resizing: the east border is to be moved */
e_resize = 18,
/** \brief resizing: the north border is to be moved */
n_resize = 19,
/** \brief resizing: the north-east corner is to be moved */
ne_resize = 20,
/** \brief resizing: the north-west corner is to be moved */
nw_resize = 21,
/** \brief resizing: the south border is to be moved */
s_resize = 22,
/** \brief resizing: the south-east corner is to be moved */
se_resize = 23,
/** \brief resizing: the south-west corner is to be moved */
sw_resize = 24,
/** \brief resizing: the west border is to be moved */
w_resize = 25,
/** \brief resizing: the east and west borders are to be moved */
ew_resize = 26,
/** \brief resizing: the north and south borders are to be moved */
ns_resize = 27,
/** \brief resizing: the north-east and south-west corners are to be moved */
nesw_resize = 28,
/** \brief resizing: the north-west and south-east corners are to be moved */
nwse_resize = 29,
/** \brief resizing: that the item/column can be resized horizontally */
col_resize = 30,
/** \brief resizing: that the item/row can be resized vertically */
row_resize = 31,
/** \brief something can be scrolled in any direction */
all_scroll = 32,
/** \brief something can be zoomed in */
zoom_in = 33,
/** \brief something can be zoomed out */
zoom_out = 34,
/** \brief drag-and-drop: the user will select which action will be carried out (non-css value) */
dnd_ask = 35,
/** \brief resizing: something can be moved or resized in any direction (non-css value) */
all_resize = 36
};
/** \brief
*/
enum class cursor_shape_device_v1_error : uint32_t
{
/** \brief the specified shape value is invalid */
invalid_shape = 1
};
}

View file

@ -0,0 +1,267 @@
#include "wl_xdg_toplevel_icon.hpp"
using namespace wayland;
using namespace wayland::detail;
const wl_interface* xdg_toplevel_icon_manager_v1_interface_destroy_request[0] = {
};
const wl_interface* xdg_toplevel_icon_manager_v1_interface_create_icon_request[1] = {
&xdg_toplevel_icon_v1_interface,
};
const wl_interface* xdg_toplevel_icon_manager_v1_interface_set_icon_request[2] = {
&xdg_toplevel_interface,
&xdg_toplevel_icon_v1_interface,
};
const wl_interface* xdg_toplevel_icon_manager_v1_interface_icon_size_event[1] = {
nullptr,
};
const wl_interface* xdg_toplevel_icon_manager_v1_interface_done_event[0] = {
};
const wl_message xdg_toplevel_icon_manager_v1_interface_requests[3] = {
{
"destroy",
"",
xdg_toplevel_icon_manager_v1_interface_destroy_request,
},
{
"create_icon",
"n",
xdg_toplevel_icon_manager_v1_interface_create_icon_request,
},
{
"set_icon",
"o?o",
xdg_toplevel_icon_manager_v1_interface_set_icon_request,
},
};
const wl_message xdg_toplevel_icon_manager_v1_interface_events[2] = {
{
"icon_size",
"i",
xdg_toplevel_icon_manager_v1_interface_icon_size_event,
},
{
"done",
"",
xdg_toplevel_icon_manager_v1_interface_done_event,
},
};
const wl_interface wayland::detail::xdg_toplevel_icon_manager_v1_interface =
{
"xdg_toplevel_icon_manager_v1",
1,
3,
xdg_toplevel_icon_manager_v1_interface_requests,
2,
xdg_toplevel_icon_manager_v1_interface_events,
};
const wl_interface* xdg_toplevel_icon_v1_interface_destroy_request[0] = {
};
const wl_interface* xdg_toplevel_icon_v1_interface_set_name_request[1] = {
nullptr,
};
const wl_interface* xdg_toplevel_icon_v1_interface_add_buffer_request[2] = {
&buffer_interface,
nullptr,
};
const wl_message xdg_toplevel_icon_v1_interface_requests[3] = {
{
"destroy",
"",
xdg_toplevel_icon_v1_interface_destroy_request,
},
{
"set_name",
"s",
xdg_toplevel_icon_v1_interface_set_name_request,
},
{
"add_buffer",
"oi",
xdg_toplevel_icon_v1_interface_add_buffer_request,
},
};
const wl_message xdg_toplevel_icon_v1_interface_events[0] = {
};
const wl_interface wayland::detail::xdg_toplevel_icon_v1_interface =
{
"xdg_toplevel_icon_v1",
1,
3,
xdg_toplevel_icon_v1_interface_requests,
0,
xdg_toplevel_icon_v1_interface_events,
};
xdg_toplevel_icon_manager_v1_t::xdg_toplevel_icon_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(&xdg_toplevel_icon_manager_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return xdg_toplevel_icon_manager_v1_t(p); });
}
xdg_toplevel_icon_manager_v1_t::xdg_toplevel_icon_manager_v1_t()
{
set_interface(&xdg_toplevel_icon_manager_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return xdg_toplevel_icon_manager_v1_t(p); });
}
xdg_toplevel_icon_manager_v1_t::xdg_toplevel_icon_manager_v1_t(xdg_toplevel_icon_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(&xdg_toplevel_icon_manager_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return xdg_toplevel_icon_manager_v1_t(p); });
}
xdg_toplevel_icon_manager_v1_t::xdg_toplevel_icon_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/)
: proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){
set_interface(&xdg_toplevel_icon_manager_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return xdg_toplevel_icon_manager_v1_t(p); });
}
xdg_toplevel_icon_manager_v1_t xdg_toplevel_icon_manager_v1_t::proxy_create_wrapper()
{
return {*this, construct_proxy_wrapper_tag()};
}
const std::string xdg_toplevel_icon_manager_v1_t::interface_name = "xdg_toplevel_icon_manager_v1";
xdg_toplevel_icon_manager_v1_t::operator xdg_toplevel_icon_manager_v1*() const
{
return reinterpret_cast<xdg_toplevel_icon_manager_v1*> (c_ptr());
}
xdg_toplevel_icon_v1_t xdg_toplevel_icon_manager_v1_t::create_icon()
{
proxy_t p = marshal_constructor(1U, &xdg_toplevel_icon_v1_interface, nullptr);
return xdg_toplevel_icon_v1_t(p);
}
void xdg_toplevel_icon_manager_v1_t::set_icon(xdg_toplevel_t const& toplevel, xdg_toplevel_icon_v1_t const& icon)
{
marshal(2U, toplevel.proxy_has_object() ? reinterpret_cast<wl_object*>(toplevel.c_ptr()) : nullptr, icon.proxy_has_object() ? reinterpret_cast<wl_object*>(icon.c_ptr()) : nullptr);
}
std::function<void(int32_t)> &xdg_toplevel_icon_manager_v1_t::on_icon_size()
{
return std::static_pointer_cast<events_t>(get_events())->icon_size;
}
std::function<void()> &xdg_toplevel_icon_manager_v1_t::on_done()
{
return std::static_pointer_cast<events_t>(get_events())->done;
}
int xdg_toplevel_icon_manager_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->icon_size) events->icon_size(args[0].get<int32_t>());
break;
case 1:
if(events->done) events->done();
break;
}
return 0;
}
xdg_toplevel_icon_v1_t::xdg_toplevel_icon_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(&xdg_toplevel_icon_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return xdg_toplevel_icon_v1_t(p); });
}
xdg_toplevel_icon_v1_t::xdg_toplevel_icon_v1_t()
{
set_interface(&xdg_toplevel_icon_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return xdg_toplevel_icon_v1_t(p); });
}
xdg_toplevel_icon_v1_t::xdg_toplevel_icon_v1_t(xdg_toplevel_icon_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(&xdg_toplevel_icon_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return xdg_toplevel_icon_v1_t(p); });
}
xdg_toplevel_icon_v1_t::xdg_toplevel_icon_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/)
: proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){
set_interface(&xdg_toplevel_icon_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return xdg_toplevel_icon_v1_t(p); });
}
xdg_toplevel_icon_v1_t xdg_toplevel_icon_v1_t::proxy_create_wrapper()
{
return {*this, construct_proxy_wrapper_tag()};
}
const std::string xdg_toplevel_icon_v1_t::interface_name = "xdg_toplevel_icon_v1";
xdg_toplevel_icon_v1_t::operator xdg_toplevel_icon_v1*() const
{
return reinterpret_cast<xdg_toplevel_icon_v1*> (c_ptr());
}
void xdg_toplevel_icon_v1_t::set_name(std::string const& icon_name)
{
marshal(1U, icon_name);
}
void xdg_toplevel_icon_v1_t::add_buffer(buffer_t const& buffer, int32_t scale)
{
marshal(2U, buffer.proxy_has_object() ? reinterpret_cast<wl_object*>(buffer.c_ptr()) : nullptr, scale);
}
int xdg_toplevel_icon_v1_t::dispatcher(uint32_t opcode, const std::vector<any>& args, const std::shared_ptr<detail::events_base_t>& e)
{
return 0;
}

View file

@ -0,0 +1,244 @@
#pragma once
#include <array>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include <wayland-client.hpp>
#include <wayland-client-protocol-extra.hpp>
struct xdg_toplevel_icon_manager_v1;
struct xdg_toplevel_icon_v1;
namespace wayland
{
class xdg_toplevel_icon_manager_v1_t;
class xdg_toplevel_icon_v1_t;
enum class xdg_toplevel_icon_v1_error : uint32_t;
namespace detail
{
extern const wl_interface xdg_toplevel_icon_manager_v1_interface;
extern const wl_interface xdg_toplevel_icon_v1_interface;
}
/** \brief interface to manage toplevel icons
This interface allows clients to create toplevel window icons and set
them on toplevel windows to be displayed to the user.
*/
class xdg_toplevel_icon_manager_v1_t : public proxy_t
{
private:
struct events_t : public detail::events_base_t
{
std::function<void(int32_t)> icon_size;
std::function<void()> done;
};
static int dispatcher(uint32_t opcode, const std::vector<detail::any>& args, const std::shared_ptr<detail::events_base_t>& e);
xdg_toplevel_icon_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/);
public:
xdg_toplevel_icon_manager_v1_t();
explicit xdg_toplevel_icon_manager_v1_t(const proxy_t &proxy);
xdg_toplevel_icon_manager_v1_t(xdg_toplevel_icon_manager_v1 *p, wrapper_type t = wrapper_type::standard);
xdg_toplevel_icon_manager_v1_t proxy_create_wrapper();
static const std::string interface_name;
operator xdg_toplevel_icon_manager_v1*() const;
/** \brief create a new icon instance
Creates a new icon object. This icon can then be attached to a
xdg_toplevel via the 'set_icon' request.
*/
xdg_toplevel_icon_v1_t create_icon();
/** \brief Minimum protocol version required for the \ref create_icon function
*/
static constexpr std::uint32_t create_icon_since_version = 1;
/** \brief set an icon on a toplevel window
\param toplevel the toplevel to act on
\param icon
This request assigns the icon 'icon' to 'toplevel', or clears the
toplevel icon if 'icon' was null.
This state is double-buffered and is applied on the next
wl_surface.commit of the toplevel.
After making this call, the xdg_toplevel_icon_v1 provided as 'icon'
can be destroyed by the client without 'toplevel' losing its icon.
The xdg_toplevel_icon_v1 is immutable from this point, and any
future attempts to change it must raise the
'xdg_toplevel_icon_v1.immutable' protocol error.
The compositor must set the toplevel icon from either the pixel data
the icon provides, or by loading a stock icon using the icon name.
See the description of 'xdg_toplevel_icon_v1' for details.
If 'icon' is set to null, the icon of the respective toplevel is reset
to its default icon (usually the icon of the application, derived from
its desktop-entry file, or a placeholder icon).
If this request is passed an icon with no pixel buffers or icon name
assigned, the icon must be reset just like if 'icon' was null.
*/
void set_icon(xdg_toplevel_t const& toplevel, xdg_toplevel_icon_v1_t const& icon);
/** \brief Minimum protocol version required for the \ref set_icon function
*/
static constexpr std::uint32_t set_icon_since_version = 1;
/** \brief describes a supported & preferred icon size
\param size the edge size of the square icon in surface-local coordinates, e.g. 64
This event indicates an icon size the compositor prefers to be
available if the client has scalable icons and can render to any size.
When the 'xdg_toplevel_icon_manager_v1' object is created, the
compositor may send one or more 'icon_size' events to describe the list
of preferred icon sizes. If the compositor has no size preference, it
may not send any 'icon_size' event, and it is up to the client to
decide a suitable icon size.
A sequence of 'icon_size' events must be finished with a 'done' event.
If the compositor has no size preferences, it must still send the
'done' event, without any preceding 'icon_size' events.
*/
std::function<void(int32_t)> &on_icon_size();
/** \brief all information has been sent
This event is sent after all 'icon_size' events have been sent.
*/
std::function<void()> &on_done();
};
/** \brief a toplevel window icon
This interface defines a toplevel icon.
An icon can have a name, and multiple buffers.
In order to be applied, the icon must have either a name, or at least
one buffer assigned. Applying an empty icon (with no buffer or name) to
a toplevel should reset its icon to the default icon.
It is up to compositor policy whether to prefer using a buffer or loading
an icon via its name. See 'set_name' and 'add_buffer' for details.
*/
class xdg_toplevel_icon_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);
xdg_toplevel_icon_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/);
public:
xdg_toplevel_icon_v1_t();
explicit xdg_toplevel_icon_v1_t(const proxy_t &proxy);
xdg_toplevel_icon_v1_t(xdg_toplevel_icon_v1 *p, wrapper_type t = wrapper_type::standard);
xdg_toplevel_icon_v1_t proxy_create_wrapper();
static const std::string interface_name;
operator xdg_toplevel_icon_v1*() const;
/** \brief set an icon name
\param icon_name
This request assigns an icon name to this icon.
Any previously set name is overridden.
The compositor must resolve 'icon_name' according to the lookup rules
described in the XDG icon theme specification[1] using the
environment's current icon theme.
If the compositor does not support icon names or cannot resolve
'icon_name' according to the XDG icon theme specification it must
fall back to using pixel buffer data instead.
If this request is made after the icon has been assigned to a toplevel
via 'set_icon', a 'immutable' error must be raised.
[1]: https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
*/
void set_name(std::string const& icon_name);
/** \brief Minimum protocol version required for the \ref set_name function
*/
static constexpr std::uint32_t set_name_since_version = 1;
/** \brief add icon data from a pixel buffer
\param buffer
\param scale the scaling factor of the icon, e.g. 1
This request adds pixel data supplied as wl_buffer to the icon.
The client should add pixel data for all icon sizes and scales that
it can provide, or which are explicitly requested by the compositor
via 'icon_size' events on xdg_toplevel_icon_manager_v1.
The wl_buffer supplying pixel data as 'buffer' must be backed by wl_shm
and must be a square (width and height being equal).
If any of these buffer requirements are not fulfilled, a 'invalid_buffer'
error must be raised.
If this icon instance already has a buffer of the same size and scale
from a previous 'add_buffer' request, data from the last request
overrides the preexisting pixel data.
The wl_buffer must be kept alive for as long as the xdg_toplevel_icon
it is associated with is not destroyed, otherwise a 'no_buffer' error
is raised. The buffer contents must not be modified after it was
assigned to the icon. As a result, the region of the wl_shm_pool's
backing storage used for the wl_buffer must not be modified after this
request is sent. The wl_buffer.release event is unused.
If this request is made after the icon has been assigned to a toplevel
via 'set_icon', a 'immutable' error must be raised.
*/
void add_buffer(buffer_t const& buffer, int32_t scale);
/** \brief Minimum protocol version required for the \ref add_buffer function
*/
static constexpr std::uint32_t add_buffer_since_version = 1;
};
/** \brief
*/
enum class xdg_toplevel_icon_v1_error : uint32_t
{
/** \brief the provided buffer does not satisfy requirements */
invalid_buffer = 1,
/** \brief the icon has already been assigned to a toplevel and must not be changed */
immutable = 2,
/** \brief the provided buffer has been destroyed before the toplevel icon */
no_buffer = 3
};
}

View file

@ -1,5 +1,6 @@
#include "win32_display_window.h"
#include <zwidget/core/image.h>
#include <windowsx.h>
#include <stdexcept>
#include <cmath>
@ -30,11 +31,11 @@
// Code for delay loading DPI related functions, needed for continued Windows 7 compatibility.
typedef BOOL (WINAPI *PFN_AdjustWindowRectExForDpi)(
LPRECT lpRect,
DWORD dwStyle,
BOOL bMenu,
DWORD dwExStyle,
UINT dpi
LPRECT lpRect,
DWORD dwStyle,
BOOL bMenu,
DWORD dwExStyle,
UINT dpi
);
typedef UINT (WINAPI *PFN_GetDpiForWindow)(HWND hwnd);
@ -44,39 +45,36 @@ static PFN_GetDpiForWindow pGetDpiForWindow = nullptr;
static void DPIDelayLoad()
{
HMODULE hUser32 = GetModuleHandleW(L"user32.dll");
if (hUser32) {
pAdjustWindowRectExForDpi = (PFN_AdjustWindowRectExForDpi)
GetProcAddress(hUser32, "AdjustWindowRectExForDpi");
pGetDpiForWindow = (PFN_GetDpiForWindow)
GetProcAddress(hUser32, "GetDpiForWindow");
}
HMODULE hUser32 = GetModuleHandleW(L"user32.dll");
if (hUser32)
{
pAdjustWindowRectExForDpi = (PFN_AdjustWindowRectExForDpi)GetProcAddress(hUser32, "AdjustWindowRectExForDpi");
pGetDpiForWindow = (PFN_GetDpiForWindow)GetProcAddress(hUser32, "GetDpiForWindow");
}
}
static BOOL DelayLoadAdjustWindowRectExForDpi(
LPRECT lpRect,
DWORD dwStyle,
BOOL bMenu,
DWORD dwExStyle,
HWND hwnd
) {
static BOOL DelayLoadAdjustWindowRectExForDpi(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, HWND hwnd)
{
DPIDelayLoad();
if (pAdjustWindowRectExForDpi) {
return pAdjustWindowRectExForDpi(lpRect, dwStyle, bMenu, dwExStyle,
pGetDpiForWindow(hwnd));
} else {
return AdjustWindowRectEx(lpRect, dwStyle, bMenu, dwExStyle);
}
if (pAdjustWindowRectExForDpi)
{
return pAdjustWindowRectExForDpi(lpRect, dwStyle, bMenu, dwExStyle,
pGetDpiForWindow(hwnd));
}
else
{
return AdjustWindowRectEx(lpRect, dwStyle, bMenu, dwExStyle);
}
}
static double DelayLoadGetDpiScale(HWND hwnd)
{
DPIDelayLoad();
if (pGetDpiForWindow) {
return pGetDpiForWindow(hwnd) / 96.0;
} else {
return 1.0;
}
if (pGetDpiForWindow) {
return pGetDpiForWindow(hwnd) / 96.0;
} else {
return 1.0;
}
}
Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner, RenderAPI renderAPI) : WindowHost(windowHost), PopupWindow(popupWindow)
@ -120,6 +118,11 @@ Win32DisplayWindow::~Win32DisplayWindow()
WindowHandle.hwnd = 0;
}
if (SmallIcon)
DestroyCursor(SmallIcon);
if (LargeIcon)
DestroyCursor(LargeIcon);
Windows.erase(WindowsIterator);
}
@ -128,6 +131,95 @@ void Win32DisplayWindow::SetWindowTitle(const std::string& text)
SetWindowText(WindowHandle.hwnd, to_utf16(text).c_str());
}
static HICON CreateIconFromImageList(HDC hdc, const std::vector<std::shared_ptr<Image>>& images, int desiredSize)
{
if (images.empty())
return 0;
Image* image = images.front().get();
for (const auto& i : images)
{
int curdist = std::abs(image->GetWidth() - desiredSize);
int dist = std::abs(i->GetWidth() - desiredSize);
if (dist < curdist)
image = i.get();
}
int width = image->GetWidth();
int height = image->GetHeight();
std::vector<uint32_t> pixels(width * height * 4);
if (image->GetFormat() == ImageFormat::R8G8B8A8)
{
int count = width * height;
const uint32_t* src = (const uint32_t*)image->GetData();
uint32_t* dest = pixels.data();
for (int i = 0; i < count; i++)
{
uint32_t r = src[i] & 0xff;
uint32_t g = (src[i] >> 8) & 0xff;
uint32_t b = (src[i] >> 16) & 0xff;
uint32_t a = (src[i] >> 24) & 0xff;
dest[i] = (a << 24) | (r << 16) | (g << 8) | b;
}
}
else if (image->GetFormat() == ImageFormat::B8G8R8A8)
{
memcpy(pixels.data(), image->GetData(), width * height * 4);
}
else
{
return 0;
}
BITMAPV5HEADER bmp_header = {};
bmp_header.bV5Size = sizeof(BITMAPV5HEADER);
bmp_header.bV5Width = width;
bmp_header.bV5Height = height;
bmp_header.bV5Planes = 1;
bmp_header.bV5BitCount = 32;
bmp_header.bV5Compression = BI_RGB;
HBITMAP bitmap = CreateDIBitmap(hdc, (BITMAPINFOHEADER*)&bmp_header, CBM_INIT, pixels.data(), (BITMAPINFO*)&bmp_header, DIB_RGB_COLORS);
if (!bitmap)
return 0;
ICONINFO iconinfo = {};
iconinfo.fIcon = TRUE;
iconinfo.hbmColor = bitmap;
iconinfo.hbmMask = bitmap;
HICON icon = CreateIconIndirect(&iconinfo);
DeleteObject(bitmap);
return icon;
}
void Win32DisplayWindow::SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images)
{
double dpiScale = GetDpiScale();
if (SmallIcon)
{
DestroyCursor(SmallIcon);
SmallIcon = {};
}
if (LargeIcon)
{
DestroyCursor(LargeIcon);
LargeIcon = {};
}
HDC hdc = GetDC(WindowHandle.hwnd);
if (hdc)
{
SmallIcon = CreateIconFromImageList(hdc, images, (int)std::round(16 * dpiScale));
LargeIcon = CreateIconFromImageList(hdc, images, (int)std::round(32 * dpiScale));
ReleaseDC(WindowHandle.hwnd, hdc);
}
SendMessage(WindowHandle.hwnd, WM_SETICON, ICON_SMALL, (LPARAM)SmallIcon);
SendMessage(WindowHandle.hwnd, WM_SETICON, ICON_BIG, (LPARAM)LargeIcon);
}
void Win32DisplayWindow::SetBorderColor(uint32_t bgra8)
{
bgra8 = bgra8 & 0x00ffffff;

View file

@ -14,6 +14,7 @@ public:
~Win32DisplayWindow();
void SetWindowTitle(const std::string& text) override;
void SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images) override;
void SetWindowFrame(const Rect& box) override;
void SetClientFrame(const Rect& box) override;
void Show() override;
@ -90,5 +91,8 @@ public:
HDC PaintDC = 0;
HICON SmallIcon = {};
HICON LargeIcon = {};
StandardCursor CurrentCursor = StandardCursor::arrow;
};

View file

@ -3,6 +3,7 @@
#include "win32_display_window.h"
#include "core/widget.h"
#include <stdexcept>
#include <exception>
#include <thread>
#include <mutex>
#include <condition_variable>
@ -14,6 +15,65 @@ Win32OpenFileDialog::Win32OpenFileDialog(Win32DisplayWindow* owner) : owner(owne
bool Win32OpenFileDialog::Show()
{
// 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;
bool showResult = false;
std::exception_ptr exception;
std::thread thread([&]() {
try
{
showResult = ShowWorkerThread();
}
catch (...)
{
exception = std::current_exception();
}
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 (exception)
std::rethrow_exception(exception);
return showResult;
}
bool Win32OpenFileDialog::ShowWorkerThread()
{
class InitCOM
{
public:
InitCOM()
{
HRESULT result = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
if (FAILED(result))
throw std::runtime_error("CoInitializeEx(COINIT_APARTMENTTHREADED) failed");
}
~InitCOM()
{
CoUninitialize();
}
};
InitCOM initCOM;
std::wstring title16 = to_utf16(title);
std::wstring initial_directory16 = to_utf16(initial_directory);
@ -92,35 +152,10 @@ bool Win32OpenFileDialog::Show()
}
}
// 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 (owner)
result = open_dialog->Show(owner->WindowHandle.hwnd);
else
result = open_dialog->Show(0);
if (SUCCEEDED(result))
{

View file

@ -23,6 +23,7 @@ public:
void SetDefaultExtension(const std::string& extension) override;
private:
bool ShowWorkerThread();
void throw_if_failed(HRESULT result, const std::string& error);
Win32DisplayWindow* owner = nullptr;

View file

@ -0,0 +1,261 @@
#include "x11_connection.h"
#include "x11_display_window.h"
#include <stdexcept>
#include <chrono>
X11Connection* GetX11Connection()
{
static X11Connection connection;
return &connection;
}
X11Connection::X11Connection()
{
// This is required by vulkan
XInitThreads();
display = XOpenDisplay(nullptr);
if (!display)
throw std::runtime_error("Could not open X11 display");
// Make auto-repeat keys detectable
Bool supports_detectable_autorepeat = {};
XkbSetDetectableAutoRepeat(display, True, &supports_detectable_autorepeat);
// Loads the XMODIFIERS environment variable to see what IME to use
XSetLocaleModifiers("");
xim = XOpenIM(display, 0, 0, 0);
if (!xim)
{
// fallback to internal input method
XSetLocaleModifiers("@im=none");
xim = XOpenIM(display, 0, 0, 0);
}
// Look for XInput support
int event = 0, error = 0;
if (XQueryExtension(display, "XInputExtension", &XInputOpcode, &event, &error))
{
// We need XInput 2.0 support
int major = 2, minor = 0;
if (XIQueryVersion(display, &major, &minor) != BadRequest)
{
// And we need a master pointer ID
int ndevices = 0;
XIDeviceInfo *devices = XIQueryDevice(display, XIAllDevices, &ndevices);
if (devices)
{
for (int i = 0; i < ndevices; i++)
{
if (devices[i].use == XIMasterPointer)
{
MasterPointerID = devices[i].deviceid;
XInput2Supported = true;
break;
}
}
XIFreeDeviceInfo(devices);
}
}
}
// This XInput API is the gift that just keeps on giving. Instead of routing events to the
// focus window it sends it to the root window. Thanks!
if (XInput2Supported)
{
unsigned char mask[3] = { 0 };
XISetMask(mask, XI_RawMotion);
XIEventMask eventmask;
eventmask.deviceid = MasterPointerID;
eventmask.mask_len = sizeof(mask);
eventmask.mask = mask;
Window root = XRootWindow(display, XDefaultScreen(display));
XISelectEvents(display, root, &eventmask, 1);
}
}
X11Connection::~X11Connection()
{
for (auto& it : standardCursors)
XFreeCursor(display, it.second);
if (xim)
XCloseIM(xim);
XCloseDisplay(display);
}
Atom X11Connection::GetAtom(const std::string& name)
{
auto it = atoms.find(name);
if (it != atoms.end())
return it->second;
Atom atom = XInternAtom(display, name.c_str(), True);
atoms[name] = atom;
return atom;
}
void X11Connection::ProcessEvents()
{
CheckNeedsUpdate();
while (XPending(display) > 0)
{
XEvent event = {};
XNextEvent(display, &event);
DispatchEvent(&event);
}
CheckTimers();
}
void X11Connection::RunLoop()
{
X11Connection* connection = GetX11Connection();
connection->ExitRunLoop = false;
while (!connection->ExitRunLoop && !connection->windows.empty())
{
ProcessEvents();
WaitForEvents(GetTimerTimeout());
}
}
void X11Connection::ExitLoop()
{
X11Connection* connection = GetX11Connection();
connection->ExitRunLoop = true;
}
bool X11Connection::WaitForEvents(int timeout)
{
int fd = XConnectionNumber(display);
struct timeval tv;
if (timeout > 0)
{
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) / 1000;
}
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
int result = select(fd + 1, &rfds, nullptr, nullptr, timeout >= 0 ? &tv : nullptr);
return result > 0 && FD_ISSET(fd, &rfds);
}
void X11Connection::CheckNeedsUpdate()
{
for (auto& it : windows)
{
if (it.second->needsUpdate)
{
it.second->needsUpdate = false;
it.second->windowHost->OnWindowPaint();
}
}
}
void X11Connection::DispatchEvent(XEvent* event)
{
if (XInput2Supported)
{
// XInput sends all raw input to the root window. We want it routed to the focused window
if (event->xcookie.type == GenericEvent &&
event->xcookie.extension == XInputOpcode &&
XGetEventData(display, &event->xcookie))
{
for (auto& it : windows)
{
X11DisplayWindow* window = it.second;
if (window->OnXInputEvent(event))
break;
}
XFreeEventData(display, &event->xcookie);
}
}
auto it = windows.find(event->xany.window);
if (it != windows.end())
{
X11DisplayWindow* window = it->second;
window->OnEvent(event);
}
}
Size X11Connection::GetScreenSize()
{
int screen = XDefaultScreen(display);
int disp_width_px = XDisplayWidth(display, screen);
int disp_height_px = XDisplayHeight(display, screen);
int disp_width_mm = XDisplayWidthMM(display, screen);
double ppi = (disp_width_mm < 24) ? 96.0 : (25.4 * static_cast<double>(disp_width_px) / static_cast<double>(disp_width_mm));
double dpiScale = ppi / 96.0;
return Size(disp_width_px / dpiScale, disp_height_px / dpiScale);
}
static int64_t GetTimePoint()
{
using namespace std::chrono;
return (int64_t)(duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count());
}
void* X11Connection::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)
{
timeoutMilliseconds = std::max(timeoutMilliseconds, 1);
timers.push_back(std::make_shared<X11Timer>(timeoutMilliseconds, onTimer, GetTimePoint() + timeoutMilliseconds));
return timers.back().get();
}
void X11Connection::StopTimer(void* timerID)
{
for (auto it = timers.begin(); it != timers.end(); ++it)
{
if (it->get() == timerID)
{
timers.erase(it);
return;
}
}
}
void X11Connection::CheckTimers()
{
int64_t now = GetTimePoint();
// The callback may stop timers. Iterators might invalidate.
while (true)
{
std::shared_ptr<X11Timer> foundTimer;
for (auto& timer : timers)
{
if (timer->nextTime < now)
{
foundTimer = timer;
break;
}
}
if (!foundTimer)
break;
// Not very precise, but these aren't high precision timers
foundTimer->nextTime = now + foundTimer->timeoutMilliseconds;
foundTimer->onTimer();
}
}
int X11Connection::GetTimerTimeout()
{
if (timers.empty())
return 0;
int64_t nextTime = timers.front()->nextTime;
for (auto& timer : timers)
{
nextTime = std::min(nextTime, timer->nextTime);
}
int64_t now = GetTimePoint();
return (int)std::max(nextTime - now, (int64_t)1);
}

View file

@ -0,0 +1,65 @@
#pragma once
#include "window/window.h"
#include <string>
#include <map>
#include <memory>
#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 <X11/extensions/XInput2.h>
class X11DisplayWindow;
class X11Timer
{
public:
X11Timer(int timeoutMilliseconds, std::function<void()> onTimer, int64_t nextTime) : timeoutMilliseconds(timeoutMilliseconds), onTimer(onTimer), nextTime(nextTime) {}
int timeoutMilliseconds = 0;
std::function<void()> onTimer;
int64_t nextTime = 0;
};
class X11Connection
{
public:
X11Connection();
~X11Connection();
void ProcessEvents();
void RunLoop();
void ExitLoop();
bool WaitForEvents(int timeout);
void* StartTimer(int timeoutMilliseconds, std::function<void()> onTimer);
void StopTimer(void* timerID);
Size GetScreenSize();
Atom GetAtom(const std::string& name);
Display* display = nullptr;
std::map<std::string, Atom> atoms;
std::map<Window, X11DisplayWindow*> windows;
std::map<StandardCursor, Cursor> standardCursors;
bool ExitRunLoop = false;
XIM xim = nullptr;
bool XInput2Supported = false;
int XInputOpcode = 0;
int MasterPointerID = 0;
private:
void DispatchEvent(XEvent* event);
void CheckNeedsUpdate();
void CheckTimers();
int GetTimerTimeout();
std::vector<std::shared_ptr<X11Timer>> timers;
};
X11Connection* GetX11Connection();

View file

@ -1,6 +1,8 @@
#include "x11_display_backend.h"
#include "x11_display_window.h"
#include "x11_connection.h"
#include <stdexcept>
#ifdef USE_DBUS
#include "window/dbus/dbus_open_file_dialog.h"
@ -15,32 +17,32 @@ std::unique_ptr<DisplayWindow> X11DisplayBackend::Create(DisplayWindowHost* wind
void X11DisplayBackend::ProcessEvents()
{
X11DisplayWindow::ProcessEvents();
GetX11Connection()->ProcessEvents();
}
void X11DisplayBackend::RunLoop()
{
X11DisplayWindow::RunLoop();
GetX11Connection()->RunLoop();
}
void X11DisplayBackend::ExitLoop()
{
X11DisplayWindow::ExitLoop();
GetX11Connection()->ExitLoop();
}
Size X11DisplayBackend::GetScreenSize()
{
return X11DisplayWindow::GetScreenSize();
return GetX11Connection()->GetScreenSize();
}
void* X11DisplayBackend::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)
{
return X11DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer));
return GetX11Connection()->StartTimer(timeoutMilliseconds, onTimer);
}
void X11DisplayBackend::StopTimer(void* timerID)
{
X11DisplayWindow::StopTimer(timerID);
GetX11Connection()->StopTimer(timerID);
}
#ifdef USE_DBUS

View file

@ -1,5 +1,7 @@
#include "x11_display_window.h"
#include "x11_connection.h"
#include <zwidget/core/image.h>
#include <stdexcept>
#include <vector>
#include <cmath>
@ -10,83 +12,24 @@
#include <unistd.h>
#include <iostream>
class X11Connection
{
public:
X11Connection()
{
// If we ever want to support windows on multiple threads:
// XInitThreads();
display = XOpenDisplay(nullptr);
if (!display)
throw std::runtime_error("Could not open X11 display");
// Make auto-repeat keys detectable
Bool supports_detectable_autorepeat = {};
XkbSetDetectableAutoRepeat(display, True, &supports_detectable_autorepeat);
// Loads the XMODIFIERS environment variable to see what IME to use
XSetLocaleModifiers("");
xim = XOpenIM(display, 0, 0, 0);
if (!xim)
{
// fallback to internal input method
XSetLocaleModifiers("@im=none");
xim = XOpenIM(display, 0, 0, 0);
}
}
~X11Connection()
{
for (auto& it : standardCursors)
XFreeCursor(display, it.second);
if (xim)
XCloseIM(xim);
XCloseDisplay(display);
}
Display* display = nullptr;
std::map<std::string, Atom> atoms;
std::map<Window, X11DisplayWindow*> windows;
std::map<StandardCursor, Cursor> standardCursors;
bool ExitRunLoop = false;
XIM xim = nullptr;
};
static X11Connection* GetX11Connection()
{
static X11Connection connection;
return &connection;
}
static Atom GetAtom(const std::string& name)
{
auto connection = GetX11Connection();
auto it = connection->atoms.find(name);
if (it != connection->atoms.end())
return it->second;
Atom atom = XInternAtom(connection->display, name.c_str(), True);
connection->atoms[name] = atom;
return atom;
}
X11DisplayWindow::X11DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, X11DisplayWindow* owner, RenderAPI renderAPI) : windowHost(windowHost), owner(owner)
{
display = GetX11Connection()->display;
auto connection = GetX11Connection();
display = connection->display;
screen = XDefaultScreen(display);
depth = XDefaultDepth(display, screen);
visual = XDefaultVisual(display, screen);
colormap = XDefaultColormap(display, screen);
int disp_width_px = XDisplayWidth(display, screen);
int disp_height_px = XDisplayHeight(display, screen);
int disp_width_mm = XDisplayWidthMM(display, screen);
double ppi = (disp_width_mm < 24) ? 96.0 : (25.4 * static_cast<double>(disp_width_px) / static_cast<double>(disp_width_mm));
dpiScale = std::round(ppi / 96.0 * 4.0) / 4.0; // 100%, 125%, 150%, 175%, 200%, etc.
if (char* value = XGetDefault(display, "Xft", "dpi"))
{
int dpi = std::atoi(value);
if (dpi != 0)
{
dpiScale = dpi / 96.0;
}
}
XSetWindowAttributes attributes = {};
attributes.backing_store = Always;
@ -94,14 +37,32 @@ X11DisplayWindow::X11DisplayWindow(DisplayWindowHost* windowHost, bool popupWind
attributes.save_under = popupWindow ? True : False;
attributes.colormap = colormap;
attributes.event_mask =
KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
EnterWindowMask | LeaveWindowMask | PointerMotionMask | KeymapStateMask |
KeyPressMask | KeyReleaseMask |
EnterWindowMask | LeaveWindowMask | KeymapStateMask |
ExposureMask | StructureNotifyMask | FocusChangeMask | PropertyChangeMask;
if (!connection->XInput2Supported)
{
attributes.event_mask |= ButtonPressMask | ButtonReleaseMask | PointerMotionMask;
}
unsigned long mask = CWBackingStore | CWSaveUnder | CWEventMask | CWOverrideRedirect;
window = XCreateWindow(display, XRootWindow(display, screen), 0, 0, 100, 100, 0, depth, InputOutput, visual, mask, &attributes);
GetX11Connection()->windows[window] = this;
connection->windows[window] = this;
if (connection->XInput2Supported)
{
unsigned char mask[1] = { 0 };
XISetMask(mask, XI_ButtonPress);
XISetMask(mask, XI_ButtonRelease);
XISetMask(mask, XI_Motion);
XIEventMask eventmask;
eventmask.deviceid = connection->MasterPointerID;
eventmask.mask_len = sizeof(mask);
eventmask.mask = mask;
XISelectEvents(display, window, &eventmask, 1);
}
if (owner)
{
@ -109,59 +70,59 @@ X11DisplayWindow::X11DisplayWindow(DisplayWindowHost* windowHost, bool popupWind
}
// Tell window manager which process this window came from
if (GetAtom("_NET_WM_PID") != None)
if (connection->GetAtom("_NET_WM_PID") != None)
{
int32_t pid = getpid();
if (pid != 0)
{
XChangeProperty(display, window, GetAtom("_NET_WM_PID"), XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);
XChangeProperty(display, window, connection->GetAtom("_NET_WM_PID"), XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);
}
}
// Tell window manager which machine this window came from
if (GetAtom("WM_CLIENT_MACHINE") != None)
if (connection->GetAtom("WM_CLIENT_MACHINE") != None)
{
std::vector<char> hostname(256);
if (gethostname(hostname.data(), hostname.size()) >= 0)
{
hostname.push_back(0);
XChangeProperty(display, window, GetAtom("WM_CLIENT_MACHINE"), XA_STRING, 8, PropModeReplace, (unsigned char *)hostname.data(), strlen(hostname.data()));
XChangeProperty(display, window, connection->GetAtom("WM_CLIENT_MACHINE"), XA_STRING, 8, PropModeReplace, (unsigned char *)hostname.data(), strlen(hostname.data()));
}
}
// Tell window manager we want to listen to close events
if (GetAtom("WM_DELETE_WINDOW") != None)
if (connection->GetAtom("WM_DELETE_WINDOW") != None)
{
Atom protocol = GetAtom("WM_DELETE_WINDOW");
Atom protocol = connection->GetAtom("WM_DELETE_WINDOW");
XSetWMProtocols(display, window, &protocol, 1);
}
// Tell window manager what type of window we are
if (GetAtom("_NET_WM_WINDOW_TYPE") != None)
if (connection->GetAtom("_NET_WM_WINDOW_TYPE") != None)
{
Atom type = None;
if (popupWindow)
{
type = GetAtom("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU");
type = connection->GetAtom("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU");
if (type == None)
type = GetAtom("_NET_WM_WINDOW_TYPE_POPUP_MENU");
type = connection->GetAtom("_NET_WM_WINDOW_TYPE_POPUP_MENU");
if (type == None)
type = GetAtom("_NET_WM_WINDOW_TYPE_COMBO");
type = connection->GetAtom("_NET_WM_WINDOW_TYPE_COMBO");
}
if (type == None)
type = GetAtom("_NET_WM_WINDOW_TYPE_NORMAL");
type = connection->GetAtom("_NET_WM_WINDOW_TYPE_NORMAL");
if (type != None)
{
XChangeProperty(display, window, GetAtom("_NET_WM_WINDOW_TYPE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&type, 1);
XChangeProperty(display, window, connection->GetAtom("_NET_WM_WINDOW_TYPE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&type, 1);
}
}
// Create input context
if (GetX11Connection()->xim)
if (connection->xim)
{
xic = XCreateIC(
GetX11Connection()->xim,
connection->xim,
XNInputStyle,
XIMPreeditNothing | XIMStatusNothing,
XNClientWindow, window,
@ -188,6 +149,87 @@ void X11DisplayWindow::SetWindowTitle(const std::string& text)
XSetStandardProperties(display, window, text.c_str(), text.c_str(), None, nullptr, 0, nullptr);
}
void X11DisplayWindow::SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images)
{
if (images.empty())
return;
double dpiscale = GetDpiScale();
int desiredSize = (int)std::round(32 * dpiscale);
Image* image = images.front().get();
for (const auto& i : images)
{
int curdist = std::abs(image->GetWidth() - desiredSize);
int dist = std::abs(i->GetWidth() - desiredSize);
if (dist < curdist)
image = i.get();
}
int width = image->GetWidth();
int height = image->GetHeight();
const uint32_t* s = (const uint32_t*)image->GetData();
unsigned int size = (width * height) + 2; // header is 2 ints
unsigned long* data = (unsigned long*)malloc(size * sizeof(unsigned long));
// set header
data[0] = width;
data[1] = height;
// on 64bit systems, the destination buffer is 64 bit per pixel
// thus, we have to copy each pixel individually (no memcpy)
// icon data is expected as ARGB
if (image->GetFormat() == ImageFormat::R8G8B8A8)
{
for (int y = 0; y < height; ++y)
{
const uint32_t* src = s + y * width;
unsigned long* dst = &data[2 + (y * width)];
for (int x = 0; x < width; ++x)
{
uint32_t r = src[x] & 0xff;
uint32_t g = (src[x] >> 8) & 0xff;
uint32_t b = (src[x] >> 16) & 0xff;
uint32_t a = (src[x] >> 24) & 0xff;
dst[x] = (a << 24) | (r << 16) | (g << 8) | b;
}
}
}
else if (image->GetFormat() == ImageFormat::B8G8R8A8)
{
for (int y = 0; y < height; ++y)
{
const uint32_t* src = s + y * width;
unsigned long* dst = &data[2 + (y * width)];
for (int x = 0; x < width; ++x)
{
dst[x] = src[x];
}
}
}
else
{
free(data);
return;
}
// set icon geometry
unsigned long* geom = (unsigned long*)malloc(4 * sizeof(unsigned long));
geom[0] = geom[1] = 0; // x, y
geom[2] = width;
geom[3] = height;
Atom propertyGeom = XInternAtom(display, "_NET_WM_ICON_GEOMETRY", 0);
XChangeProperty(display, window, propertyGeom, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)geom, 4);
// set icon data
Atom property = XInternAtom(display, "_NET_WM_ICON", 0);
XChangeProperty(display, window, property, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)data, size);
}
void X11DisplayWindow::SetWindowFrame(const Rect& box)
{
// To do: this requires cooperation with the window manager
@ -226,10 +268,11 @@ void X11DisplayWindow::ShowFullscreen()
{
Show();
if (GetAtom("_NET_WM_STATE") != None && GetAtom("_NET_WM_STATE_FULLSCREEN") != None)
auto connection = GetX11Connection();
if (connection->GetAtom("_NET_WM_STATE") != None && connection->GetAtom("_NET_WM_STATE_FULLSCREEN") != None)
{
Atom state = GetAtom("_NET_WM_STATE_FULLSCREEN");
XChangeProperty(display, window, GetAtom("_NET_WM_STATE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&state, 1);
Atom state = connection->GetAtom("_NET_WM_STATE_FULLSCREEN");
XChangeProperty(display, window, connection->GetAtom("_NET_WM_STATE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&state, 1);
isFullscreen = true;
}
}
@ -378,7 +421,7 @@ Rect X11DisplayWindow::GetWindowFrame() const
unsigned int height = 0;
unsigned int borderwidth = 0;
unsigned int depth = 0;
Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth);
/*Status status =*/ XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth);
return Rect::xywh(x / dpiscale, y / dpiscale, width / dpiscale, height / dpiscale);
}
@ -394,7 +437,7 @@ Size X11DisplayWindow::GetClientSize() const
unsigned int height = 0;
unsigned int borderwidth = 0;
unsigned int depth = 0;
Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth);
/*Status status =*/ XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth);
return Size(width / dpiscale, height / dpiscale);
}
@ -408,7 +451,7 @@ int X11DisplayWindow::GetPixelWidth() const
unsigned int height = 0;
unsigned int borderwidth = 0;
unsigned int depth = 0;
Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth);
/*Status status =*/ XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth);
return width;
}
@ -421,7 +464,7 @@ int X11DisplayWindow::GetPixelHeight() const
unsigned int height = 0;
unsigned int borderwidth = 0;
unsigned int depth = 0;
Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth);
/*Status status =*/ XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth);
return height;
}
@ -486,8 +529,8 @@ void X11DisplayWindow::SetCaptionTextColor(uint32_t bgra8)
std::vector<uint8_t> X11DisplayWindow::GetWindowProperty(Atom property, Atom &actual_type, int &actual_format, unsigned long &item_count)
{
long read_bytes = 0;
Atom _actual_type = actual_type;
int _actual_format = actual_format;
//Atom _actual_type = actual_type;
//int _actual_format = actual_format;
unsigned long _item_count = item_count;
unsigned long bytes_remaining = 0;
unsigned char *read_data = nullptr;
@ -516,7 +559,8 @@ std::vector<uint8_t> X11DisplayWindow::GetWindowProperty(Atom property, Atom &ac
std::string X11DisplayWindow::GetClipboardText()
{
Atom clipboard = GetAtom("CLIPBOARD");
auto connection = GetX11Connection();
Atom clipboard = connection->GetAtom("CLIPBOARD");
if (clipboard == None)
return {};
@ -529,7 +573,7 @@ std::string X11DisplayWindow::GetClipboardText()
{
if (XCheckTypedWindowEvent(display, window, SelectionNotify, &event))
break;
if (!WaitForEvents(500))
if (!connection->WaitForEvents(500))
return {};
}
@ -548,7 +592,8 @@ void X11DisplayWindow::SetClipboardText(const std::string& text)
{
clipboardText = text;
Atom clipboard = GetAtom("CLIPBOARD");
auto connection = GetX11Connection();
Atom clipboard = connection->GetAtom("CLIPBOARD");
if (clipboard == None)
return;
@ -565,7 +610,7 @@ Point X11DisplayWindow::MapFromGlobal(const Point& pos) const
int srcy = (int)std::round(pos.y * dpiscale);
int destx = 0;
int desty = 0;
Bool result = XTranslateCoordinates(display, root, window, srcx, srcy, &destx, &desty, &child);
/*Bool result =*/ XTranslateCoordinates(display, root, window, srcx, srcy, &destx, &desty, &child);
return Point(destx / dpiscale, desty / dpiscale);
}
@ -578,7 +623,7 @@ Point X11DisplayWindow::MapToGlobal(const Point& pos) const
int srcy = (int)std::round(pos.y * dpiscale);
int destx = 0;
int desty = 0;
Bool result = XTranslateCoordinates(display, window, root, srcx, srcy, &destx, &desty, &child);
/*Bool result =*/ XTranslateCoordinates(display, window, root, srcx, srcy, &destx, &desty, &child);
return Point(destx / dpiscale, desty / dpiscale);
}
@ -587,79 +632,6 @@ void* X11DisplayWindow::GetNativeHandle()
return reinterpret_cast<void*>(window);
}
bool X11DisplayWindow::WaitForEvents(int timeout)
{
Display* display = GetX11Connection()->display;
int fd = XConnectionNumber(display);
struct timeval tv;
if (timeout > 0)
{
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) / 1000;
}
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
int result = select(fd + 1, &rfds, nullptr, nullptr, timeout >= 0 ? &tv : nullptr);
return result > 0 && FD_ISSET(fd, &rfds);
}
void X11DisplayWindow::CheckNeedsUpdate()
{
for (auto& it : GetX11Connection()->windows)
{
if (it.second->needsUpdate)
{
it.second->needsUpdate = false;
it.second->windowHost->OnWindowPaint();
}
}
}
void X11DisplayWindow::ProcessEvents()
{
CheckNeedsUpdate();
Display* display = GetX11Connection()->display;
while (XPending(display) > 0)
{
XEvent event = {};
XNextEvent(display, &event);
DispatchEvent(&event);
}
}
void X11DisplayWindow::RunLoop()
{
X11Connection* connection = GetX11Connection();
connection->ExitRunLoop = false;
while (!connection->ExitRunLoop && !connection->windows.empty())
{
CheckNeedsUpdate();
XEvent event = {};
XNextEvent(connection->display, &event);
DispatchEvent(&event);
}
}
void X11DisplayWindow::ExitLoop()
{
X11Connection* connection = GetX11Connection();
connection->ExitRunLoop = true;
}
void X11DisplayWindow::DispatchEvent(XEvent* event)
{
X11Connection* connection = GetX11Connection();
auto it = connection->windows.find(event->xany.window);
if (it != connection->windows.end())
{
X11DisplayWindow* window = it->second;
window->OnEvent(event);
}
}
void X11DisplayWindow::OnEvent(XEvent* event)
{
if (event->type == ConfigureNotify)
@ -703,11 +675,12 @@ void X11DisplayWindow::OnConfigureNotify(XEvent* event)
void X11DisplayWindow::OnClientMessage(XEvent* event)
{
Atom protocolsAtom = GetAtom("WM_PROTOCOLS");
auto connection = GetX11Connection();
Atom protocolsAtom = connection->GetAtom("WM_PROTOCOLS");
if (protocolsAtom != None && event->xclient.message_type == protocolsAtom)
{
Atom deleteAtom = GetAtom("WM_DELETE_WINDOW");
Atom pingAtom = GetAtom("_NET_WM_PING");
Atom deleteAtom = connection->GetAtom("WM_DELETE_WINDOW");
Atom pingAtom = connection->GetAtom("_NET_WM_PING");
Atom protocol = event->xclient.data.l[0];
if (deleteAtom != None && protocol == deleteAtom)
@ -731,11 +704,13 @@ void X11DisplayWindow::OnFocusIn(XEvent* event)
if (xic)
XSetICFocus(xic);
RawInput.Focused = true;
windowHost->OnWindowActivated();
}
void X11DisplayWindow::OnFocusOut(XEvent* event)
{
RawInput.Focused = false;
windowHost->OnWindowDeactivated();
}
@ -949,6 +924,8 @@ void X11DisplayWindow::OnKeyRelease(XEvent* event)
void X11DisplayWindow::OnButtonPress(XEvent* event)
{
// Note: this only gets called if XInput is not available
InputKey key = GetInputKey(event);
keyState[key] = true;
windowHost->OnWindowMouseDown(GetMousePos(event), key);
@ -958,6 +935,8 @@ void X11DisplayWindow::OnButtonPress(XEvent* event)
void X11DisplayWindow::OnButtonRelease(XEvent* event)
{
// Note: this only gets called if XInput is not available
InputKey key = GetInputKey(event);
keyState[key] = false;
windowHost->OnWindowMouseUp(GetMousePos(event), key);
@ -965,6 +944,8 @@ void X11DisplayWindow::OnButtonRelease(XEvent* event)
void X11DisplayWindow::OnMotionNotify(XEvent* event)
{
// Note: this only gets called if XInput is not available
double dpiScale = GetDpiScale();
int x = event->xmotion.x;
int y = event->xmotion.y;
@ -974,18 +955,134 @@ void X11DisplayWindow::OnMotionNotify(XEvent* event)
}
else
{
MouseX = ClientSizeX / 2;
MouseY = ClientSizeY / 2;
if (MouseX != -1 && MouseY != -1)
int dx = x - RawInput.LastX;
int dy = y - RawInput.LastY;
RawInput.LastX = x;
RawInput.LastY = y;
int centerX = ClientSizeX / 2;
int centerY = ClientSizeY / 2;
if (x != centerX || y != centerY)
{
windowHost->OnWindowRawMouseMove(x - MouseX, y - MouseY);
// We still have to contain the mouse even if we are using XInput.
// Maybe we can do that in a simpler way if XInput is available?
XWarpPointer(display, window, window, 0, 0, ClientSizeX, ClientSizeY, centerX, centerY);
// Use the accelerated mouse cursor if there's no XInput 2.0 support
if (!GetX11Connection()->XInput2Supported)
{
if (dx != 0 || dy != 0)
windowHost->OnWindowRawMouseMove(dx, dy);
}
}
}
}
bool X11DisplayWindow::OnXInputEvent(XEvent* event)
{
// This API is so horrible it makes Win32 look attractive!
if (event->xcookie.evtype == XI_ButtonPress || event->xcookie.evtype == XI_ButtonRelease)
{
auto deviceEvent = (XIDeviceEvent*)event->xcookie.data;
if (deviceEvent->event != window)
return false;
InputKey key = {};
switch (deviceEvent->detail)
{
case 1: key = InputKey::LeftMouse; break;
case 2: key = InputKey::MiddleMouse; break;
case 3: key = InputKey::RightMouse; break;
case 4: key = InputKey::MouseWheelUp; break;
case 5: key = InputKey::MouseWheelDown; break;
// case 6: key = InputKey::XButton1; break;
// case 7: key = InputKey::XButton2; break;
default: return false;
}
// Warp pointer to the center of the window
XWarpPointer(display, window, window, 0, 0, ClientSizeX, ClientSizeY, ClientSizeX / 2, ClientSizeY / 2);
}
double dpiScale = GetDpiScale();
int x = (int)std::round(deviceEvent->event_x);
int y = (int)std::round(deviceEvent->event_y);
Point mousePos(x / dpiScale, y / dpiScale);
if (!isCursorEnabled)
{
// Raw input gets blocked until we ungrab the pointer. Worst design EVER.
XUngrabPointer(display, deviceEvent->time);
}
if (event->xcookie.evtype == XI_ButtonPress)
{
keyState[key] = true;
windowHost->OnWindowMouseDown(mousePos, key);
// if (lastClickWithin400ms)
// windowHost->OnWindowMouseDoubleclick(GetMousePos(event), InputKey::LeftMouse);
}
else
{
keyState[key] = false;
windowHost->OnWindowMouseUp(mousePos, key);
}
return true;
}
else if (event->xcookie.evtype == XI_Motion)
{
auto deviceEvent = (XIDeviceEvent*)event->xcookie.data;
if (deviceEvent->event != window)
return false;
if (isCursorEnabled)
{
double dpiScale = GetDpiScale();
int x = (int)std::round(deviceEvent->event_x);
int y = (int)std::round(deviceEvent->event_y);
Point mousePos(x / dpiScale, y / dpiScale);
windowHost->OnWindowMouseMove(Point(x / dpiScale, y / dpiScale));
}
else if (RawInput.Focused)
{
int x = (int)std::round(deviceEvent->event_x);
int y = (int)std::round(deviceEvent->event_y);
int centerX = ClientSizeX / 2;
int centerY = ClientSizeY / 2;
if (x != centerX || y != centerY)
{
// We still have to contain the mouse even if we are using XInput.
// Maybe we can do that in a simpler way?
XWarpPointer(display, window, window, 0, 0, ClientSizeX, ClientSizeY, centerX, centerY);
}
}
return true;
}
else if (!isCursorEnabled && RawInput.Focused && event->xcookie.evtype == XI_RawMotion)
{
std::vector<double> values;
values.reserve(2);
auto rawEvent = (XIRawEvent*)event->xcookie.data;
double *rawValuator = rawEvent->raw_values;
for (int i = 0; i < rawEvent->valuators.mask_len * 8; i++)
{
if (XIMaskIsSet(rawEvent->valuators.mask, i))
{
values.push_back(*rawValuator);
rawValuator++;
}
}
if (values.size() >= 2)
{
// Values seems to be integers for my mouse. Is that always the case?
int dx = (int)std::round(values[0]);
int dy = (int)std::round(values[1]);
if (dx != 0 || dy != 0)
windowHost->OnWindowRawMouseMove(dx, dy);
}
return true;
}
return false;
}
void X11DisplayWindow::OnLeaveNotify(XEvent* event)
@ -1009,8 +1106,9 @@ void X11DisplayWindow::OnSelectionRequest(XEvent* event)
if (requestor == window)
return;
Atom targetsAtom = GetAtom("TARGETS");
Atom multipleAtom = GetAtom("MULTIPLE");
X11Connection* connection = GetX11Connection();
Atom targetsAtom = connection->GetAtom("TARGETS");
Atom multipleAtom = connection->GetAtom("MULTIPLE");
struct Request { Window target; Atom property; };
std::vector<Request> requests;
@ -1067,31 +1165,6 @@ void X11DisplayWindow::OnSelectionRequest(XEvent* event)
}
}
Size X11DisplayWindow::GetScreenSize()
{
X11Connection* connection = GetX11Connection();
Display* display = connection->display;
int screen = XDefaultScreen(display);
int disp_width_px = XDisplayWidth(display, screen);
int disp_height_px = XDisplayHeight(display, screen);
int disp_width_mm = XDisplayWidthMM(display, screen);
double ppi = (disp_width_mm < 24) ? 96.0 : (25.4 * static_cast<double>(disp_width_px) / static_cast<double>(disp_width_mm));
double dpiScale = ppi / 96.0;
return Size(disp_width_px / dpiScale, disp_height_px / dpiScale);
}
void* X11DisplayWindow::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)
{
throw std::logic_error("unimplemented: X11DisplayWindow::StartTimer");
}
void X11DisplayWindow::StopTimer(void* timerID)
{
throw std::logic_error("unimplemented: X11DisplayWindow::StopTimer");
}
// This is to avoid needing all the Vulkan headers and the volk binding library just for this:
#ifndef VK_VERSION_1_0

View file

@ -7,6 +7,7 @@
#include <X11/cursorfont.h>
#include <X11/keysymdef.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XInput2.h>
#include <map>
class X11DisplayWindow : public DisplayWindow
@ -16,6 +17,7 @@ public:
~X11DisplayWindow();
void SetWindowTitle(const std::string& text) override;
void SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images) override;
void SetWindowFrame(const Rect& box) override;
void SetClientFrame(const Rect& box) override;
void Show() override;
@ -59,13 +61,6 @@ public:
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();
@ -85,6 +80,7 @@ private:
void OnSelectionClear(XEvent* event);
void OnSelectionNotify(XEvent* event);
void OnSelectionRequest(XEvent* event);
bool OnXInputEvent(XEvent* event);
void CreateBackbuffer(int width, int height);
void DestroyBackbuffer();
@ -92,11 +88,6 @@ private:
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;
@ -117,8 +108,13 @@ private:
int ClientSizeX = 0;
int ClientSizeY = 0;
int MouseX = -1;
int MouseY = -1;
struct
{
int LastX = -1;
int LastY = -1;
bool Focused = false;
} RawInput;
Pixmap cursor_bitmap = None;
Cursor hidden_cursor = None;
@ -138,5 +134,6 @@ private:
bool needsUpdate = false;
friend class X11Connection;
friend class X11DisplayBackend;
};

View file

@ -37,15 +37,17 @@ public:
// [Construction / Destruction]
// --------------------------------------------------------------------------
ASMJIT_INLINE CpuFeatures() noexcept { reset(); }
ASMJIT_INLINE CpuFeatures(const CpuFeatures& other) noexcept { init(other); }
ASMJIT_INLINE CpuFeatures() noexcept : _bits{} {}
ASMJIT_INLINE CpuFeatures(const CpuFeatures& other) noexcept = default;
ASMJIT_INLINE CpuFeatures& operator=(const CpuFeatures& other) noexcept = default;
// --------------------------------------------------------------------------
// [Init / Reset]
// --------------------------------------------------------------------------
ASMJIT_INLINE void init(const CpuFeatures& other) noexcept { ::memcpy(this, &other, sizeof(*this)); }
ASMJIT_INLINE void reset() noexcept { ::memset(this, 0, sizeof(*this)); }
ASMJIT_INLINE void init(const CpuFeatures& other) noexcept { *this = other; }
ASMJIT_INLINE void reset() noexcept { *this = CpuFeatures{}; }
// --------------------------------------------------------------------------
// [Ops]
@ -248,8 +250,21 @@ public:
// [Construction / Destruction]
// --------------------------------------------------------------------------
ASMJIT_INLINE CpuInfo() noexcept { reset(); }
ASMJIT_INLINE CpuInfo(const CpuInfo& other) noexcept { init(other); }
ASMJIT_INLINE CpuInfo() noexcept:
_archInfo{},
_vendorId{},
_family{},
_model{},
_stepping{},
_hwThreadsCount{},
_features{},
_vendorString{},
_brandString{},
_x86Data{}
{}
ASMJIT_INLINE CpuInfo(const CpuInfo& other) noexcept = default;
ASMJIT_INLINE CpuInfo& operator=(const CpuInfo& other) noexcept = default;
// --------------------------------------------------------------------------
// [Init / Reset]
@ -260,8 +275,8 @@ public:
_archInfo.init(archType, archMode);
}
ASMJIT_INLINE void init(const CpuInfo& other) noexcept { ::memcpy(this, &other, sizeof(*this)); }
ASMJIT_INLINE void reset() noexcept { ::memset(this, 0, sizeof(*this)); }
ASMJIT_INLINE void init(const CpuInfo& other) noexcept { *this = other; }
ASMJIT_INLINE void reset() noexcept { *this = CpuInfo{}; }
// --------------------------------------------------------------------------
// [Detect]

View file

@ -734,10 +734,18 @@ public:
// [Construction / Destruction]
// --------------------------------------------------------------------------
ASMJIT_INLINE FuncDetail() noexcept { reset(); }
ASMJIT_INLINE FuncDetail(const FuncDetail& other) noexcept {
::memcpy(this, &other, sizeof(*this));
}
ASMJIT_INLINE FuncDetail() noexcept:
_callConv{},
_argCount{},
_retCount{},
_usedRegs{},
_argStackSize{},
_rets{},
_args{}
{}
ASMJIT_INLINE FuncDetail(const FuncDetail& other) noexcept = default;
ASMJIT_INLINE FuncDetail& operator=(const FuncDetail& other) noexcept = default;
// --------------------------------------------------------------------------
// [Init / Reset]
@ -745,7 +753,7 @@ public:
//! Initialize this `FuncDetail` to the given signature.
ASMJIT_API Error init(const FuncSignature& sign);
ASMJIT_INLINE void reset() noexcept { ::memset(this, 0, sizeof(*this)); }
ASMJIT_INLINE void reset() noexcept { *this = FuncDetail{}; }
// --------------------------------------------------------------------------
// [Accessors - Calling Convention]
@ -878,20 +886,23 @@ struct FuncFrameInfo {
// [Construction / Destruction]
// --------------------------------------------------------------------------
ASMJIT_INLINE FuncFrameInfo() noexcept { reset(); }
ASMJIT_INLINE FuncFrameInfo() noexcept:
_attributes{},
_dirtyRegs{},
_stackFrameAlignment{},
_callFrameAlignment{},
_stackArgsRegId{Globals::kInvalidRegId},
_stackFrameSize{},
_callFrameSize{}
{}
ASMJIT_INLINE FuncFrameInfo(const FuncFrameInfo& other) noexcept {
::memcpy(this, &other, sizeof(*this));
}
ASMJIT_INLINE FuncFrameInfo(const FuncFrameInfo& other) noexcept = default;
// --------------------------------------------------------------------------
// [Init / Reset]
// --------------------------------------------------------------------------
ASMJIT_INLINE void reset() noexcept {
::memset(this, 0, sizeof(*this));
_stackArgsRegId = Globals::kInvalidRegId;
}
ASMJIT_INLINE void reset() noexcept { *this = FuncFrameInfo{}; }
// --------------------------------------------------------------------------
// [Accessors]
@ -1180,19 +1191,18 @@ public:
// [Construction / Destruction]
// --------------------------------------------------------------------------
explicit ASMJIT_INLINE FuncArgsMapper(const FuncDetail* fd) noexcept { reset(fd); }
ASMJIT_INLINE FuncArgsMapper(const FuncArgsMapper& other) noexcept {
::memcpy(this, &other, sizeof(*this));
}
explicit ASMJIT_INLINE FuncArgsMapper(const FuncDetail* fd = nullptr) noexcept:
_funcDetail(fd),
_args{}
{}
ASMJIT_INLINE FuncArgsMapper(const FuncArgsMapper& other) noexcept = default;
// --------------------------------------------------------------------------
// [Reset]
// --------------------------------------------------------------------------
ASMJIT_INLINE void reset(const FuncDetail* fd = nullptr) noexcept {
_funcDetail = fd;
::memset(_args, 0, sizeof(_args));
}
ASMJIT_INLINE void reset(const FuncDetail* fd = nullptr) noexcept { *this = FuncArgsMapper(fd); }
// --------------------------------------------------------------------------
// [Accessors]

View file

@ -101,7 +101,7 @@ struct TiedReg {
// --------------------------------------------------------------------------
ASMJIT_INLINE TiedReg& operator=(const TiedReg& other) {
::memcpy(this, &other, sizeof(TiedReg));
::memcpy((void*)this, &other, sizeof(TiedReg));
return *this;
}

View file

@ -221,7 +221,8 @@ void ZoneHeap::reset(Zone* zone) noexcept {
}
// Zero the entire class and initialize to the given `zone`.
::memset(this, 0, sizeof(*this));
_dynamicBlocks = nullptr;
::memset(_slots, 0, sizeof(_slots));
_zone = zone;
}

View file

@ -297,14 +297,19 @@ class ZoneHeap {
//! Create a new `ZoneHeap`.
//!
//! NOTE: To use it, you must first `init()` it.
ASMJIT_INLINE ZoneHeap() noexcept {
::memset(this, 0, sizeof(*this));
}
ASMJIT_INLINE ZoneHeap() noexcept:
_zone{},
_slots{},
_dynamicBlocks{}
{}
//! Create a new `ZoneHeap` initialized to use `zone`.
explicit ASMJIT_INLINE ZoneHeap(Zone* zone) noexcept {
::memset(this, 0, sizeof(*this));
_zone = zone;
}
explicit ASMJIT_INLINE ZoneHeap(Zone* zone) noexcept:
_zone(zone),
_slots{},
_dynamicBlocks{}
{}
//! Destroy the `ZoneHeap`.
ASMJIT_INLINE ~ZoneHeap() noexcept { reset(); }