aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml2
-rw-r--r--.gitignore1
-rw-r--r--CMakeLists.txt13
-rw-r--r--demos/CMakeLists.txt4
-rw-r--r--demos/Graphics/CMakeLists.txt8
-rw-r--r--demos/Graphics/DrawCircle.cpp31
-rw-r--r--demos/Graphics/SvgPath.cpp41
-rw-r--r--demos/ScrollView/CMakeLists.txt1
-rw-r--r--demos/main/CMakeLists.txt1
-rw-r--r--demos/platform/CMakeLists.txt2
-rw-r--r--demos/platform/graphics/Base.cpp37
-rw-r--r--demos/platform/graphics/Base.h25
-rw-r--r--demos/platform/graphics/CMakeLists.txt12
-rw-r--r--demos/platform/graphics/DrawCircle.cpp17
-rw-r--r--demos/platform/graphics/DrawText.cpp27
-rw-r--r--demos/platform/graphics/SvgPath.cpp28
-rw-r--r--demos/platform/gui/CMakeLists.txt5
-rw-r--r--demos/platform/gui/EmptyWindow.cpp25
-rw-r--r--include/cru/base/Guard.h26
-rw-r--r--include/cru/platform/GraphicsBase.h16
-rw-r--r--include/cru/platform/gui/Window.h3
-rw-r--r--include/cru/platform/gui/xcb/Base.h4
-rw-r--r--include/cru/platform/gui/xcb/Cursor.h44
-rw-r--r--include/cru/platform/gui/xcb/UiApplication.h30
-rw-r--r--include/cru/platform/gui/xcb/Window.h45
-rw-r--r--src/ThemeBuilder/CMakeLists.txt2
-rw-r--r--src/base/io/Resource.cpp22
-rw-r--r--src/base/platform/unix/EventLoop.cpp6
-rw-r--r--src/platform/bootstrap/Bootstrap.cpp6
-rw-r--r--src/platform/bootstrap/CMakeLists.txt10
-rw-r--r--src/platform/graphics/cairo/CairoPainter.cpp2
-rw-r--r--src/platform/graphics/cairo/PangoFont.cpp2
-rw-r--r--src/platform/graphics/cairo/PangoTextLayout.cpp52
-rw-r--r--src/platform/gui/CMakeLists.txt1
-rw-r--r--src/platform/gui/Window.cpp6
-rw-r--r--src/platform/gui/xcb/CMakeLists.txt8
-rw-r--r--src/platform/gui/xcb/Cursor.cpp54
-rw-r--r--src/platform/gui/xcb/Keyboard.cpp12
-rw-r--r--src/platform/gui/xcb/UiApplication.cpp38
-rw-r--r--src/platform/gui/xcb/Window.cpp227
40 files changed, 727 insertions, 169 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d745ba51..c2a624e2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -88,7 +88,7 @@ jobs:
- name: Install Libraries
run: |
sudo apt update
- sudo apt install -y libpng-dev libcairo2-dev libpango1.0-dev libxcb1-dev
+ sudo apt install -y libpng-dev libcairo2-dev libpango1.0-dev libxcb1-dev libxcb-cursor-dev
- name: Build
run: |
diff --git a/.gitignore b/.gitignore
index 0a928c07..e15a05ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
.DS_Store
.vs
+.vscode
.cache
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 64b0b936..6da50c5b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,6 +25,19 @@ if (MSVC)
endif()
set(CRU_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
+set(CRU_ASSETS_DIR ${PROJECT_SOURCE_DIR}/assets)
+
+function(target_add_resources target res_dir)
+ message("Add resources for target ${target} with files ${res_dir}.")
+
+ file(GLOB_RECURSE RES_SOURCES "${CRU_ASSETS_DIR}/${res_dir}/*")
+ target_sources(${target} PUBLIC ${RES_SOURCES})
+ foreach (RES_FILE ${RES_SOURCES})
+ file(RELATIVE_PATH RES_PATH ${CRU_ASSETS_DIR} ${RES_FILE})
+ cmake_path(GET RES_PATH PARENT_PATH RES_PATH)
+ set_property(SOURCE ${RES_FILE} PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${RES_PATH}")
+ endforeach(RES_FILE)
+endfunction()
# We don't use meson at present. Enable it in future.
# include(scripts/cmake/meson-projects.cmake)
diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt
index b38bc7a8..46eda9c4 100644
--- a/demos/CMakeLists.txt
+++ b/demos/CMakeLists.txt
@@ -2,7 +2,7 @@ add_library(CruDemoBase INTERFACE)
target_link_libraries(CruDemoBase INTERFACE CruPlatformBootstrap)
-add_subdirectory(Graphics)
+add_subdirectory(platform)
if(WIN32)
add_subdirectory(main)
@@ -13,6 +13,8 @@ elseif(APPLE)
add_subdirectory(ScrollView)
add_subdirectory(InputMethod)
elseif(UNIX)
+ add_subdirectory(main)
+ add_subdirectory(ScrollView)
add_subdirectory(xcb)
endif()
diff --git a/demos/Graphics/CMakeLists.txt b/demos/Graphics/CMakeLists.txt
deleted file mode 100644
index 64982a19..00000000
--- a/demos/Graphics/CMakeLists.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-add_library(CruDemoGraphicsBase INTERFACE)
-target_link_libraries(CruDemoGraphicsBase INTERFACE CruPlatformGraphicsBootstrap)
-
-add_executable(CruDemoGraphicsDrawCircle DrawCircle.cpp)
-target_link_libraries(CruDemoGraphicsDrawCircle PRIVATE CruDemoGraphicsBase)
-
-add_executable(CruDemoGraphicsSvgPath SvgPath.cpp)
-target_link_libraries(CruDemoGraphicsSvgPath PRIVATE CruDemoGraphicsBase)
diff --git a/demos/Graphics/DrawCircle.cpp b/demos/Graphics/DrawCircle.cpp
deleted file mode 100644
index db63c7dc..00000000
--- a/demos/Graphics/DrawCircle.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-#include "cru/base/io/CFileStream.h"
-#include "cru/platform/Color.h"
-#include "cru/platform/bootstrap/GraphicsBootstrap.h"
-#include "cru/platform/graphics/Factory.h"
-#include "cru/platform/graphics/ImageFactory.h"
-#include "cru/platform/graphics/Painter.h"
-
-#include <memory>
-
-int main() {
- std::unique_ptr<cru::platform::graphics::IGraphicsFactory> graphics_factory(
- cru::platform::bootstrap::CreateGraphicsFactory());
-
- auto image = graphics_factory->GetImageFactory()->CreateBitmap(500, 500);
-
- {
- auto brush =
- graphics_factory->CreateSolidColorBrush(cru::platform::colors::skyblue);
- auto painter = image->CreatePainter();
- painter->FillEllipse(cru::platform::Rect{200, 200, 100, 100}, brush.get());
- painter->EndDraw();
- }
-
- cru::io::CFileStream file_stream("draw-circle-demo.png", "w");
-
- graphics_factory->GetImageFactory()->EncodeToStream(
- image.get(), &file_stream, cru::platform::graphics::ImageFormat::Png,
- 1.0f);
-
- return 0;
-}
diff --git a/demos/Graphics/SvgPath.cpp b/demos/Graphics/SvgPath.cpp
deleted file mode 100644
index 44524d77..00000000
--- a/demos/Graphics/SvgPath.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-
-#include "cru/base/io/CFileStream.h"
-#include "cru/platform/Color.h"
-#include "cru/platform/Matrix.h"
-#include "cru/platform/bootstrap/GraphicsBootstrap.h"
-#include "cru/platform/graphics/Factory.h"
-#include "cru/platform/graphics/ImageFactory.h"
-#include "cru/platform/graphics/Painter.h"
-
-#include <memory>
-
-int main() {
- std::unique_ptr<cru::platform::graphics::IGraphicsFactory> graphics_factory(
- cru::platform::bootstrap::CreateGraphicsFactory());
-
- auto brush =
- graphics_factory->CreateSolidColorBrush(cru::platform::colors::black);
-
- auto geometry_builder = graphics_factory->CreateGeometryBuilder();
- geometry_builder->ParseAndApplySvgPathData(
- uR"(
-M8.5 5.5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5z
-M6.5 0a.5.5 0 0 0 0 1H7v1.07a7.001 7.001 0 0 0-3.273 12.474l-.602.602a.5.5 0 0 0 .707.708l.746-.746A6.97 6.97 0 0 0 8 16a6.97 6.97 0 0 0 3.422-.892l.746.746a.5.5 0 0 0 .707-.708l-.601-.602A7.001 7.001 0 0 0 9 2.07V1h.5a.5.5 0 0 0 0-1h-3zm1.038 3.018a6.093 6.093 0 0 1 .924 0 6 6 0 1 1-.924 0zM0 3.5c0 .753.333 1.429.86 1.887A8.035 8.035 0 0 1 4.387 1.86 2.5 2.5 0 0 0 0 3.5zM13.5 1c-.753 0-1.429.333-1.887.86a8.035 8.035 0 0 1 3.527 3.527A2.5 2.5 0 0 0 13.5 1z
- )");
- auto geometry = geometry_builder->Build();
-
- auto image = graphics_factory->GetImageFactory()->CreateBitmap(160, 160);
- auto painter = image->CreatePainter();
-
- painter->ConcatTransform(cru::platform::Matrix::Scale(10, 10));
- painter->FillGeometry(geometry.get(), brush.get());
- painter->EndDraw();
-
- cru::io::CFileStream file_stream("./svg-path-demo.png", "w");
-
- graphics_factory->GetImageFactory()->EncodeToStream(
- image.get(), &file_stream, cru::platform::graphics::ImageFormat::Png,
- 1.0f);
-
- return 0;
-}
diff --git a/demos/ScrollView/CMakeLists.txt b/demos/ScrollView/CMakeLists.txt
index cfa99ebd..8b34f5d8 100644
--- a/demos/ScrollView/CMakeLists.txt
+++ b/demos/ScrollView/CMakeLists.txt
@@ -8,4 +8,5 @@ if(APPLE)
)
endif()
+target_add_resources(CruDemoScrollView cru/ui)
target_link_libraries(CruDemoScrollView PRIVATE CruDemoBase CruUi)
diff --git a/demos/main/CMakeLists.txt b/demos/main/CMakeLists.txt
index 9a30e2f4..2da90405 100644
--- a/demos/main/CMakeLists.txt
+++ b/demos/main/CMakeLists.txt
@@ -8,4 +8,5 @@ if(APPLE)
)
endif()
+target_add_resources(CruDemoMain cru/ui)
target_link_libraries(CruDemoMain PRIVATE CruDemoBase CruUi)
diff --git a/demos/platform/CMakeLists.txt b/demos/platform/CMakeLists.txt
new file mode 100644
index 00000000..36673acd
--- /dev/null
+++ b/demos/platform/CMakeLists.txt
@@ -0,0 +1,2 @@
+add_subdirectory(graphics)
+add_subdirectory(gui)
diff --git a/demos/platform/graphics/Base.cpp b/demos/platform/graphics/Base.cpp
new file mode 100644
index 00000000..be39f7e0
--- /dev/null
+++ b/demos/platform/graphics/Base.cpp
@@ -0,0 +1,37 @@
+#include "Base.h"
+#include <cru/base/io/CFileStream.h>
+#include <cru/platform/bootstrap/GraphicsBootstrap.h>
+#include <cru/platform/graphics/Factory.h>
+#include <cru/platform/graphics/Image.h>
+#include <cru/platform/graphics/ImageFactory.h>
+
+CruPlatformGraphicsDemo::CruPlatformGraphicsDemo(std::string file_name,
+ int width, int height)
+ : file_name_(std::move(file_name)) {
+ factory_.reset(cru::platform::bootstrap::CreateGraphicsFactory());
+ image_ = factory_->GetImageFactory()->CreateBitmap(width, height);
+ painter_ = image_->CreatePainter();
+}
+
+CruPlatformGraphicsDemo::~CruPlatformGraphicsDemo() {
+ painter_->EndDraw();
+
+ cru::io::CFileStream file_stream(file_name_.c_str(), "wb");
+
+ factory_->GetImageFactory()->EncodeToStream(
+ image_.get(), &file_stream, cru::platform::graphics::ImageFormat::Png,
+ 1.0f);
+}
+
+cru::platform::graphics::IGraphicsFactory*
+CruPlatformGraphicsDemo::GetFactory() {
+ return factory_.get();
+}
+
+cru::platform::graphics::IImage* CruPlatformGraphicsDemo::GetImage() {
+ return image_.get();
+}
+
+cru::platform::graphics::IPainter* CruPlatformGraphicsDemo::GetPainter() {
+ return painter_.get();
+}
diff --git a/demos/platform/graphics/Base.h b/demos/platform/graphics/Base.h
new file mode 100644
index 00000000..6f4f4929
--- /dev/null
+++ b/demos/platform/graphics/Base.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <cru/base/Base.h>
+#include <cru/platform/graphics/Factory.h>
+#include <cru/platform/graphics/Image.h>
+#include <cru/platform/graphics/Painter.h>
+
+#include <memory>
+#include <string>
+
+class CruPlatformGraphicsDemo : public cru::Object2 {
+ public:
+ CruPlatformGraphicsDemo(std::string file_name, int width, int height);
+ ~CruPlatformGraphicsDemo() override;
+
+ cru::platform::graphics::IGraphicsFactory* GetFactory();
+ cru::platform::graphics::IImage* GetImage();
+ cru::platform::graphics::IPainter* GetPainter();
+
+ private:
+ std::string file_name_;
+ std::unique_ptr<cru::platform::graphics::IGraphicsFactory> factory_;
+ std::unique_ptr<cru::platform::graphics::IImage> image_;
+ std::unique_ptr<cru::platform::graphics::IPainter> painter_;
+};
diff --git a/demos/platform/graphics/CMakeLists.txt b/demos/platform/graphics/CMakeLists.txt
new file mode 100644
index 00000000..07758ee6
--- /dev/null
+++ b/demos/platform/graphics/CMakeLists.txt
@@ -0,0 +1,12 @@
+add_library(CruDemoPlatformGraphicsBase INTERFACE)
+target_link_libraries(CruDemoPlatformGraphicsBase INTERFACE CruPlatformGraphicsBootstrap)
+target_sources(CruDemoPlatformGraphicsBase INTERFACE Base.cpp)
+
+add_executable(CruDemoPlatformGraphicsDrawCircle DrawCircle.cpp)
+target_link_libraries(CruDemoPlatformGraphicsDrawCircle PRIVATE CruDemoPlatformGraphicsBase)
+
+add_executable(CruDemoPlatformGraphicsDrawText DrawText.cpp)
+target_link_libraries(CruDemoPlatformGraphicsDrawText PRIVATE CruDemoPlatformGraphicsBase)
+
+add_executable(CruDemoPlatformGraphicsSvgPath SvgPath.cpp)
+target_link_libraries(CruDemoPlatformGraphicsSvgPath PRIVATE CruDemoPlatformGraphicsBase)
diff --git a/demos/platform/graphics/DrawCircle.cpp b/demos/platform/graphics/DrawCircle.cpp
new file mode 100644
index 00000000..76855e0a
--- /dev/null
+++ b/demos/platform/graphics/DrawCircle.cpp
@@ -0,0 +1,17 @@
+#include "Base.h"
+#include "cru/platform/Color.h"
+#include "cru/platform/graphics/Factory.h"
+#include "cru/platform/graphics/Painter.h"
+
+#include <memory>
+
+int main() {
+ CruPlatformGraphicsDemo demo("draw-circle-demo.png", 500, 500);
+
+ auto brush =
+ demo.GetFactory()->CreateSolidColorBrush(cru::platform::colors::skyblue);
+ demo.GetPainter()->FillEllipse(cru::platform::Rect{200, 200, 100, 100},
+ brush.get());
+
+ return 0;
+}
diff --git a/demos/platform/graphics/DrawText.cpp b/demos/platform/graphics/DrawText.cpp
new file mode 100644
index 00000000..dfea18cd
--- /dev/null
+++ b/demos/platform/graphics/DrawText.cpp
@@ -0,0 +1,27 @@
+#include "Base.h"
+#include "cru/platform/Color.h"
+#include "cru/platform/graphics/Factory.h"
+#include "cru/platform/graphics/Font.h"
+#include "cru/platform/graphics/Painter.h"
+
+#include <iostream>
+#include <memory>
+
+int main() {
+ CruPlatformGraphicsDemo demo("draw-text-demo.png", 500, 200);
+
+ auto brush =
+ demo.GetFactory()->CreateSolidColorBrush(cru::platform::colors::skyblue);
+
+ std::shared_ptr<cru::platform::graphics::IFont> font(
+ demo.GetFactory()->CreateFont(u"", 24));
+ auto text_layout = demo.GetFactory()->CreateTextLayout(font, u"Hello world!");
+ demo.GetPainter()->DrawText({0, 0}, text_layout.get(), brush.get());
+
+ auto bounds = text_layout->GetTextBounds();
+ std::cout << "Bounds of text:\n\tx: " << bounds.left
+ << "\n\ty: " << bounds.top << "\n\twidth: " << bounds.width
+ << "\n\theight: " << bounds.height << std::endl;
+
+ return 0;
+}
diff --git a/demos/platform/graphics/SvgPath.cpp b/demos/platform/graphics/SvgPath.cpp
new file mode 100644
index 00000000..d0827878
--- /dev/null
+++ b/demos/platform/graphics/SvgPath.cpp
@@ -0,0 +1,28 @@
+
+#include "Base.h"
+#include "cru/platform/Color.h"
+#include "cru/platform/Matrix.h"
+#include "cru/platform/graphics/Factory.h"
+#include "cru/platform/graphics/Painter.h"
+
+#include <memory>
+
+int main() {
+ CruPlatformGraphicsDemo demo("svg-path-demo.png", 160, 160);
+
+ auto brush =
+ demo.GetFactory()->CreateSolidColorBrush(cru::platform::colors::black);
+
+ auto geometry_builder = demo.GetFactory()->CreateGeometryBuilder();
+ geometry_builder->ParseAndApplySvgPathData(
+ uR"(
+M8.5 5.5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5z
+M6.5 0a.5.5 0 0 0 0 1H7v1.07a7.001 7.001 0 0 0-3.273 12.474l-.602.602a.5.5 0 0 0 .707.708l.746-.746A6.97 6.97 0 0 0 8 16a6.97 6.97 0 0 0 3.422-.892l.746.746a.5.5 0 0 0 .707-.708l-.601-.602A7.001 7.001 0 0 0 9 2.07V1h.5a.5.5 0 0 0 0-1h-3zm1.038 3.018a6.093 6.093 0 0 1 .924 0 6 6 0 1 1-.924 0zM0 3.5c0 .753.333 1.429.86 1.887A8.035 8.035 0 0 1 4.387 1.86 2.5 2.5 0 0 0 0 3.5zM13.5 1c-.753 0-1.429.333-1.887.86a8.035 8.035 0 0 1 3.527 3.527A2.5 2.5 0 0 0 13.5 1z
+ )");
+ auto geometry = geometry_builder->Build();
+
+ demo.GetPainter()->ConcatTransform(cru::platform::Matrix::Scale(10, 10));
+ demo.GetPainter()->FillGeometry(geometry.get(), brush.get());
+
+ return 0;
+}
diff --git a/demos/platform/gui/CMakeLists.txt b/demos/platform/gui/CMakeLists.txt
new file mode 100644
index 00000000..b179fbc6
--- /dev/null
+++ b/demos/platform/gui/CMakeLists.txt
@@ -0,0 +1,5 @@
+add_library(CruDemoPlatformGuiBase INTERFACE)
+target_link_libraries(CruDemoPlatformGuiBase INTERFACE CruPlatformBootstrap)
+
+add_executable(CruDemoPlatformGuiEmptyWindow EmptyWindow.cpp)
+target_link_libraries(CruDemoPlatformGuiEmptyWindow PRIVATE CruDemoPlatformGuiBase)
diff --git a/demos/platform/gui/EmptyWindow.cpp b/demos/platform/gui/EmptyWindow.cpp
new file mode 100644
index 00000000..299ddd17
--- /dev/null
+++ b/demos/platform/gui/EmptyWindow.cpp
@@ -0,0 +1,25 @@
+#include "cru/platform/bootstrap/Bootstrap.h"
+#include "cru/platform/gui/Base.h"
+#include "cru/platform/gui/UiApplication.h"
+#include "cru/platform/gui/Window.h"
+
+using cru::platform::gui::INativeWindow;
+using cru::platform::gui::IUiApplication;
+using cru::platform::gui::WindowVisibilityType;
+
+int main() {
+ std::unique_ptr<IUiApplication> application(
+ cru::platform::bootstrap::CreateUiApplication());
+
+ application->SetQuitOnAllWindowClosed(true);
+
+ std::unique_ptr<INativeWindow> window1(application->CreateWindow());
+ window1->SetVisibility(WindowVisibilityType::Show);
+ window1->SetToForeground();
+
+ std::unique_ptr<INativeWindow> window2(application->CreateWindow());
+ window2->SetVisibility(WindowVisibilityType::Show);
+ window2->SetToForeground();
+
+ return application->Run();
+}
diff --git a/include/cru/base/Guard.h b/include/cru/base/Guard.h
index 5a9f9c5d..6b6cf851 100644
--- a/include/cru/base/Guard.h
+++ b/include/cru/base/Guard.h
@@ -1,5 +1,6 @@
#pragma once
+#include <cstdlib>
#include <functional>
namespace cru {
@@ -23,4 +24,29 @@ struct Guard {
ExitFunc on_exit;
};
+
+template <typename T>
+struct FreeLater {
+ FreeLater(T* ptr) : ptr(ptr) {}
+ ~FreeLater() { ::free(ptr); }
+
+ FreeLater(const FreeLater& other) = delete;
+ FreeLater& operator=(const FreeLater& other) = delete;
+
+ FreeLater(FreeLater&& other) : ptr(other.ptr) { other.ptr = nullptr; }
+ FreeLater& operator=(FreeLater&& other) {
+ if (this != &other) {
+ ::free(ptr);
+ ptr = other.ptr;
+ other.ptr = nullptr;
+ }
+ return *this;
+ }
+
+ operator T*() const { return ptr; }
+ T* operator->() { return ptr; }
+
+ T* ptr;
+};
+
} // namespace cru
diff --git a/include/cru/platform/GraphicsBase.h b/include/cru/platform/GraphicsBase.h
index b0f653ef..d5936476 100644
--- a/include/cru/platform/GraphicsBase.h
+++ b/include/cru/platform/GraphicsBase.h
@@ -206,10 +206,22 @@ struct Rect final {
height + thickness.GetVerticalTotal());
}
- constexpr Rect Shrink(const Thickness& thickness) const {
- return Rect(left + thickness.left, top + thickness.top,
+ constexpr Rect Shrink(const Thickness& thickness,
+ bool normalize = false) const {
+ Rect result(left + thickness.left, top + thickness.top,
width - thickness.GetHorizontalTotal(),
height - thickness.GetVerticalTotal());
+
+ if (normalize) {
+ if (result.width < 0) {
+ result.width = 0;
+ }
+ if (result.height < 0) {
+ result.height = 0;
+ }
+ }
+
+ return result;
}
constexpr bool IsPointInside(const Point& point) const {
diff --git a/include/cru/platform/gui/Window.h b/include/cru/platform/gui/Window.h
index 742ef798..885c1250 100644
--- a/include/cru/platform/gui/Window.h
+++ b/include/cru/platform/gui/Window.h
@@ -44,6 +44,7 @@ struct NativeKeyEventArgs {
// Represents a native window, which exposes some low-level events and
// operations.
struct INativeWindow : virtual IPlatformResource {
+ virtual bool IsCreated();
virtual void Close() = 0;
virtual INativeWindow* GetParent() = 0;
@@ -72,6 +73,8 @@ struct INativeWindow : virtual IPlatformResource {
// The lefttop of the rect is relative to screen lefttop.
virtual void SetWindowRect(const Rect& rect) = 0;
+ // Return true if window gained the focus. But the return value should be
+ // ignored, since it does not guarantee anything.
virtual bool RequestFocus() = 0;
// Relative to client lefttop.
diff --git a/include/cru/platform/gui/xcb/Base.h b/include/cru/platform/gui/xcb/Base.h
index f3bcfd01..ad571a40 100644
--- a/include/cru/platform/gui/xcb/Base.h
+++ b/include/cru/platform/gui/xcb/Base.h
@@ -7,13 +7,13 @@
namespace cru::platform::gui::xcb {
class XcbResource : public Object, public virtual IPlatformResource {
public:
- static String kPlatformId;
+ static constexpr const char16_t* kPlatformId = u"XCB";
protected:
XcbResource() = default;
public:
- String GetPlatformId() const final { return kPlatformId; }
+ String GetPlatformId() const final { return String(kPlatformId); }
};
class XcbException : public PlatformException {
diff --git a/include/cru/platform/gui/xcb/Cursor.h b/include/cru/platform/gui/xcb/Cursor.h
new file mode 100644
index 00000000..02ede7dd
--- /dev/null
+++ b/include/cru/platform/gui/xcb/Cursor.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include <cru/base/io/Stream.h>
+#include "../Cursor.h"
+#include "Base.h"
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_cursor.h>
+#include <memory>
+#include <string_view>
+#include <unordered_map>
+
+namespace cru::platform::gui::xcb {
+class XcbUiApplication;
+
+class XcbCursor : public XcbResource, public virtual ICursor {
+ public:
+ XcbCursor(XcbUiApplication* application, xcb_cursor_t cursor, bool auto_free);
+ ~XcbCursor() override;
+
+ xcb_cursor_t GetXcbCursor();
+
+ private:
+ XcbUiApplication* application_;
+ xcb_cursor_t cursor_;
+ bool auto_free_;
+};
+
+class XcbCursorManager : public XcbResource, public virtual ICursorManager {
+ public:
+ explicit XcbCursorManager(XcbUiApplication* application);
+ ~XcbCursorManager() override;
+
+ std::shared_ptr<ICursor> GetSystemCursor(SystemCursorType type) override;
+
+ private:
+ std::shared_ptr<XcbCursor> LoadXCursor(std::string_view name);
+
+ private:
+ XcbUiApplication* application_;
+ xcb_cursor_context_t* xcb_cursor_context_;
+ std::unordered_map<SystemCursorType, std::shared_ptr<XcbCursor>> cursors_;
+};
+} // namespace cru::platform::gui::xcb
diff --git a/include/cru/platform/gui/xcb/UiApplication.h b/include/cru/platform/gui/xcb/UiApplication.h
index 6063a8ab..b8de86f2 100644
--- a/include/cru/platform/gui/xcb/UiApplication.h
+++ b/include/cru/platform/gui/xcb/UiApplication.h
@@ -12,6 +12,7 @@
namespace cru::platform::gui::xcb {
class XcbWindow;
+class XcbCursorManager;
class XcbUiApplication : public XcbResource, public virtual IUiApplication {
friend XcbWindow;
@@ -26,6 +27,7 @@ class XcbUiApplication : public XcbResource, public virtual IUiApplication {
void CheckXcbConnectionError();
xcb_connection_t* GetXcbConnection();
+ void XcbFlush();
// This API is weird, but before we have correct screen API, we still use it.
xcb_screen_t* GetFirstXcbScreen();
@@ -41,6 +43,9 @@ class XcbUiApplication : public XcbResource, public virtual IUiApplication {
CRU_XCB_UI_APPLICATION_DEFINE_XCB_ATOM(_NET_WM_WINDOW_TYPE)
CRU_XCB_UI_APPLICATION_DEFINE_XCB_ATOM(_NET_WM_WINDOW_TYPE_NORMAL)
CRU_XCB_UI_APPLICATION_DEFINE_XCB_ATOM(_NET_WM_WINDOW_TYPE_UTILITY)
+ CRU_XCB_UI_APPLICATION_DEFINE_XCB_ATOM(_NET_FRAME_EXTENTS)
+ CRU_XCB_UI_APPLICATION_DEFINE_XCB_ATOM(WM_PROTOCOLS)
+ CRU_XCB_UI_APPLICATION_DEFINE_XCB_ATOM(WM_DELETE_WINDOW)
#undef CRU_XCB_UI_APPLICATION_DEFINE_XCB_ATOM
@@ -61,29 +66,18 @@ class XcbUiApplication : public XcbResource, public virtual IUiApplication {
std::function<void()> action) override;
void CancelTimer(long long id) override;
- virtual std::vector<INativeWindow*> GetAllWindow() = 0;
+ std::vector<INativeWindow*> GetAllWindow() override;
- virtual INativeWindow* CreateWindow() = 0;
+ INativeWindow* CreateWindow() override;
- virtual cru::platform::graphics::IGraphicsFactory* GetGraphicsFactory() = 0;
+ cru::platform::graphics::IGraphicsFactory* GetGraphicsFactory() override;
- virtual ICursorManager* GetCursorManager() = 0;
+ ICursorManager* GetCursorManager() override;
- virtual IClipboard* GetClipboard() = 0;
+ IClipboard* GetClipboard() override;
// If return nullptr, it means the menu is not supported.
- virtual IMenu* GetApplicationMenu();
-
- /**
- * \todo Implement on Windows.
- */
- virtual std::optional<String> ShowSaveDialog(SaveDialogOptions options);
-
- /**
- * \todo Implement on Windows.
- */
- virtual std::optional<std::vector<String>> ShowOpenDialog(
- OpenDialogOptions options);
+ IMenu* GetApplicationMenu() override;
private:
void HandleXEvents();
@@ -104,5 +98,7 @@ class XcbUiApplication : public XcbResource, public virtual IUiApplication {
bool is_quit_on_all_window_closed_;
std::vector<XcbWindow*> windows_;
+
+ XcbCursorManager* cursor_manager_;
};
} // namespace cru::platform::gui::xcb
diff --git a/include/cru/platform/gui/xcb/Window.h b/include/cru/platform/gui/xcb/Window.h
index 6d923666..61e4b616 100644
--- a/include/cru/platform/gui/xcb/Window.h
+++ b/include/cru/platform/gui/xcb/Window.h
@@ -1,5 +1,6 @@
#pragma once
+#include "../../GraphicsBase.h"
#include "../Window.h"
#include "Base.h"
@@ -10,6 +11,7 @@
namespace cru::platform::gui::xcb {
class XcbUiApplication;
+class XcbCursor;
class XcbWindow : public XcbResource, public virtual INativeWindow {
friend XcbUiApplication;
@@ -18,6 +20,7 @@ class XcbWindow : public XcbResource, public virtual INativeWindow {
explicit XcbWindow(XcbUiApplication* application);
~XcbWindow() override;
+ bool IsCreated() override;
void Close() override;
INativeWindow* GetParent() override;
@@ -32,33 +35,27 @@ class XcbWindow : public XcbResource, public virtual INativeWindow {
WindowVisibilityType GetVisibility() override;
void SetVisibility(WindowVisibilityType visibility) override;
- virtual Size GetClientSize() = 0;
- virtual void SetClientSize(const Size& size) = 0;
+ Size GetClientSize() override;
+ void SetClientSize(const Size& size) override;
- virtual Rect GetClientRect() = 0;
- virtual void SetClientRect(const Rect& rect) = 0;
+ Rect GetClientRect() override;
+ void SetClientRect(const Rect& rect) override;
- // Get the rect of the window containing frame.
- // The lefttop of the rect is relative to screen lefttop.
- virtual Rect GetWindowRect() = 0;
+ Rect GetWindowRect() override;
+ void SetWindowRect(const Rect& rect) override;
- // Set the rect of the window containing frame.
- // The lefttop of the rect is relative to screen lefttop.
- virtual void SetWindowRect(const Rect& rect) = 0;
+ bool RequestFocus() override;
- virtual bool RequestFocus() = 0;
+ Point GetMousePosition() override;
- // Relative to client lefttop.
- virtual Point GetMousePosition() = 0;
+ bool CaptureMouse() override;
+ bool ReleaseMouse() override;
- virtual bool CaptureMouse() = 0;
- virtual bool ReleaseMouse() = 0;
+ void SetCursor(std::shared_ptr<ICursor> cursor) override;
- virtual void SetCursor(std::shared_ptr<ICursor> cursor) = 0;
+ void SetToForeground() override;
- virtual void SetToForeground() = 0;
-
- virtual void RequestRepaint() = 0;
+ void RequestRepaint() override;
std::unique_ptr<graphics::IPainter> BeginPaint() override;
@@ -78,7 +75,7 @@ class XcbWindow : public XcbResource, public virtual INativeWindow {
IEvent<NativeKeyEventArgs>* KeyDownEvent() override;
IEvent<NativeKeyEventArgs>* KeyUpEvent() override;
- virtual IInputMethodContext* GetInputMethodContext() = 0;
+ IInputMethodContext* GetInputMethodContext() override;
public:
std::optional<xcb_window_t> GetXcbWindow();
@@ -91,12 +88,19 @@ class XcbWindow : public XcbResource, public virtual INativeWindow {
void DoSetParent(xcb_window_t window);
void DoSetStyleFlags(xcb_window_t window);
void DoSetTitle(xcb_window_t window);
+ void DoSetClientRect(xcb_window_t window, const Rect& rect);
+ void DoSetCursor(xcb_window_t window, XcbCursor* cursor);
void* XcbGetProperty(xcb_window_t window, xcb_atom_t property,
xcb_atom_t type, std::uint32_t offset,
std::uint32_t length,
std::uint32_t* out_length = nullptr);
+ // Relative to screen lefttop.
+ Point GetXcbWindowPosition(xcb_window_t window);
+
+ std::optional<Thickness> Get_NET_FRAME_EXTENTS(xcb_window_t window);
+
private:
XcbUiApplication* application_;
std::optional<xcb_window_t> xcb_window_;
@@ -105,6 +109,7 @@ class XcbWindow : public XcbResource, public virtual INativeWindow {
WindowStyleFlag style_;
std::string title_;
bool mapped_;
+ std::shared_ptr<XcbCursor> cursor_;
XcbWindow* parent_;
diff --git a/src/ThemeBuilder/CMakeLists.txt b/src/ThemeBuilder/CMakeLists.txt
index 5e49cf78..93b24e85 100644
--- a/src/ThemeBuilder/CMakeLists.txt
+++ b/src/ThemeBuilder/CMakeLists.txt
@@ -40,4 +40,6 @@ if(APPLE)
)
endif()
+target_add_resources(CruThemeBuilder cru/ui)
+target_add_resources(CruThemeBuilder cru/theme_builder)
target_link_libraries(CruThemeBuilder PRIVATE CruPlatformBootstrap CruUi)
diff --git a/src/base/io/Resource.cpp b/src/base/io/Resource.cpp
index d369b5f5..e599f8a9 100644
--- a/src/base/io/Resource.cpp
+++ b/src/base/io/Resource.cpp
@@ -1,19 +1,20 @@
#include "cru/base/io/Resource.h"
+#include "cru/base/Base.h"
#include "cru/base/Exception.h"
-#include "cru/base/log/Logger.h"
#if defined(CRU_PLATFORM_OSX)
#include <CoreFoundation/CoreFoundation.h>
#elif defined(CRU_PLATFORM_WINDOWS)
#include <Windows.h>
+#elif defined(__linux)
+#include <fstream>
+#include <sstream>
#endif
#include <filesystem>
namespace cru::io {
std::filesystem::path GetResourceDir() {
- constexpr auto kLogTag = u"GetResourceDir";
-
#if defined(CRU_PLATFORM_OSX)
CFBundleRef main_bundle = CFBundleGetMainBundle();
CFURLRef bundle_url = CFBundleCopyBundleURL(main_bundle);
@@ -26,10 +27,18 @@ std::filesystem::path GetResourceDir() {
CFRelease(cf_string_ref);
return bundle_path / "Contents/Resources";
-#elif defined(CRU_PLATFORM_WINDOWS)
+#elif defined(_WIN32) || defined(__linux)
+#if defined(_WIN32)
wchar_t buffer[MAX_PATH];
DWORD size = ::GetModuleFileNameW(nullptr, buffer, MAX_PATH);
std::filesystem::path module_path(buffer, buffer + size);
+#else // linux
+ std::ifstream file("/proc/self/cmdline");
+ std::stringstream buffer;
+ buffer << file.rdbuf();
+ auto str = buffer.str();
+ std::filesystem::path module_path(str.substr(0, str.find('\0')));
+#endif
auto p = module_path;
while (p.has_parent_path()) {
p = p.parent_path();
@@ -41,8 +50,9 @@ std::filesystem::path GetResourceDir() {
}
throw Exception(u"Failed to find resource directory.");
-#else
- throw Exception(u"Not implemented.");
+
#endif
+
+ NotImplemented();
}
} // namespace cru::io
diff --git a/src/base/platform/unix/EventLoop.cpp b/src/base/platform/unix/EventLoop.cpp
index 7c475697..6e8dc16e 100644
--- a/src/base/platform/unix/EventLoop.cpp
+++ b/src/base/platform/unix/EventLoop.cpp
@@ -34,15 +34,15 @@ int UnixEventLoop::Run() {
while (!exit_code_) {
int poll_timeout = -1;
- while (CheckPoll()) {
+ if (CheckPoll()) {
continue;
}
- while (CheckTimer()) {
+ if (CheckTimer()) {
continue;
}
- while (CheckActionPipe()) {
+ if (CheckActionPipe()) {
continue;
}
diff --git a/src/platform/bootstrap/Bootstrap.cpp b/src/platform/bootstrap/Bootstrap.cpp
index e9183550..bcf12613 100644
--- a/src/platform/bootstrap/Bootstrap.cpp
+++ b/src/platform/bootstrap/Bootstrap.cpp
@@ -1,10 +1,11 @@
#include "cru/platform/bootstrap/Bootstrap.h"
-#include "cru/base/Base.h"
#if defined(_WIN32)
#include "cru/platform/gui/win/UiApplication.h"
#elif defined(__APPLE__)
#include "cru/platform/gui/osx/UiApplication.h"
+#elif defined(__unix)
+#include "cru/platform/gui/xcb/UiApplication.h"
#else
#endif
@@ -15,7 +16,8 @@ cru::platform::gui::IUiApplication* CreateUiApplication() {
#elif defined(__APPLE__)
return new cru::platform::gui::osx::OsxUiApplication();
#else
- NotImplemented();
+ return new cru::platform::gui::xcb::XcbUiApplication();
#endif
+ NotImplemented();
}
} // namespace cru::platform::bootstrap
diff --git a/src/platform/bootstrap/CMakeLists.txt b/src/platform/bootstrap/CMakeLists.txt
index 41f1e3d3..24cdff2b 100644
--- a/src/platform/bootstrap/CMakeLists.txt
+++ b/src/platform/bootstrap/CMakeLists.txt
@@ -17,10 +17,10 @@ if(WIN32)
elseif(APPLE)
target_link_libraries(CruPlatformGraphicsBootstrap PUBLIC CruPlatformGraphicsQuartz)
target_link_libraries(CruPlatformBootstrap PUBLIC CruPlatformGuiOsx)
-elseif(EMSCRIPTEN)
- target_link_libraries(CruPlatformGraphicsBootstrap PUBLIC CruBase)
- target_link_libraries(CruPlatformBootstrap PUBLIC CruBase) # TODO: Remember to change this.
-else()
+elseif(UNIX)
target_link_libraries(CruPlatformGraphicsBootstrap PUBLIC CruPlatformGraphicsCairo)
- target_link_libraries(CruPlatformBootstrap PUBLIC CruPlatformGraphicsCairo)
+ target_link_libraries(CruPlatformBootstrap PUBLIC CruPlatformGuiXcb)
+else()
+ target_link_libraries(CruPlatformGraphicsBootstrap PUBLIC CruBase)
+ target_link_libraries(CruPlatformBootstrap PUBLIC CruBase)
endif()
diff --git a/src/platform/graphics/cairo/CairoPainter.cpp b/src/platform/graphics/cairo/CairoPainter.cpp
index b9ab50c4..8dd214cc 100644
--- a/src/platform/graphics/cairo/CairoPainter.cpp
+++ b/src/platform/graphics/cairo/CairoPainter.cpp
@@ -186,6 +186,8 @@ void CairoPainter::DrawText(const Point& offset, ITextLayout* text_layout,
cairo_save(cairo_);
cairo_set_source(cairo_, cairo_pattern);
+ cairo_move_to(cairo_, offset.x, offset.y);
+ pango_cairo_update_layout(cairo_, pango_text_layout->GetPangoLayout());
pango_cairo_show_layout(cairo_, pango_text_layout->GetPangoLayout());
cairo_restore(cairo_);
}
diff --git a/src/platform/graphics/cairo/PangoFont.cpp b/src/platform/graphics/cairo/PangoFont.cpp
index 0de17add..d5c1ad0b 100644
--- a/src/platform/graphics/cairo/PangoFont.cpp
+++ b/src/platform/graphics/cairo/PangoFont.cpp
@@ -10,7 +10,7 @@ PangoFont::PangoFont(CairoGraphicsFactory* factory, String font_family,
auto font_family_str = font_family_.ToUtf8();
pango_font_description_set_family(pango_font_description_,
font_family_str.c_str());
- pango_font_description_set_size(pango_font_description_, font_size);
+ pango_font_description_set_size(pango_font_description_, font_size * PANGO_SCALE);
}
PangoFont::~PangoFont() {
diff --git a/src/platform/graphics/cairo/PangoTextLayout.cpp b/src/platform/graphics/cairo/PangoTextLayout.cpp
index 1033ce9e..0ba7c806 100644
--- a/src/platform/graphics/cairo/PangoTextLayout.cpp
+++ b/src/platform/graphics/cairo/PangoTextLayout.cpp
@@ -6,13 +6,26 @@
#include "cru/platform/graphics/cairo/CairoGraphicsFactory.h"
#include "cru/platform/graphics/cairo/PangoFont.h"
+#include <pango/pangocairo.h>
+
namespace cru::platform::graphics::cairo {
+namespace {
+Rect ConvertFromPango(const Rect& rect) {
+ auto result = rect;
+ result.left /= PANGO_SCALE;
+ result.top /= PANGO_SCALE;
+ result.width /= PANGO_SCALE;
+ result.height /= PANGO_SCALE;
+ return result;
+}
+} // namespace
+
PangoTextLayout::PangoTextLayout(CairoGraphicsFactory* factory,
std::shared_ptr<IFont> font)
: CairoResource(factory) {
Expects(font);
font_ = CheckPlatform<PangoFont>(font, GetPlatformId());
- pango_layout_ = pango_layout_new(factory->GetDefaultPangoContext());
+ pango_layout_ = pango_cairo_create_layout(factory->GetDefaultCairo());
pango_layout_set_font_description(pango_layout_,
font_->GetPangoFontDescription());
};
@@ -37,11 +50,11 @@ void PangoTextLayout::SetFont(std::shared_ptr<IFont> font) {
}
void PangoTextLayout::SetMaxWidth(float max_width) {
- return pango_layout_set_width(pango_layout_, max_width);
+ return pango_layout_set_width(pango_layout_, max_width * PANGO_SCALE);
}
void PangoTextLayout::SetMaxHeight(float max_height) {
- return pango_layout_set_height(pango_layout_, max_height);
+ return pango_layout_set_height(pango_layout_, max_height * PANGO_SCALE);
}
bool PangoTextLayout::IsEditMode() { return edit_mode_; }
@@ -64,7 +77,7 @@ float PangoTextLayout::GetLineHeight(Index line_index) {
auto line = pango_layout_get_line_readonly(pango_layout_, line_index);
int height;
pango_layout_line_get_height(line, &height);
- return height;
+ return static_cast<float>(height) / PANGO_SCALE;
}
Index PangoTextLayout::FromUtf8IndexToUtf16Index(Index index) {
@@ -90,8 +103,9 @@ Index PangoTextLayout::FromUtf16IndexToUtf8Index(Index index) {
Rect PangoTextLayout::GetTextBounds(bool includingTrailingSpace) {
PangoRectangle rectangle;
- pango_layout_get_extents(pango_layout_, nullptr, &rectangle);
- return Rect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
+ pango_layout_get_extents(pango_layout_, &rectangle, nullptr);
+ return ConvertFromPango(
+ Rect(rectangle.x, rectangle.y, rectangle.width, rectangle.height));
}
std::vector<Rect> PangoTextLayout::TextRangeRect(const TextRange& text_range) {
@@ -108,32 +122,36 @@ std::vector<Rect> PangoTextLayout::TextRangeRect(const TextRange& text_range) {
if (start_line_index == end_line_index) {
auto line = pango_layout_get_line(pango_layout_, start_line_index);
PangoRectangle rectangle;
- pango_layout_line_get_extents(line, nullptr, &rectangle);
- return {Rect(rectangle.x + start_x_pos, rectangle.y,
- end_x_pos - start_x_pos, rectangle.height)};
+ pango_layout_line_get_extents(line, &rectangle, nullptr);
+ return {ConvertFromPango(Rect(rectangle.x + start_x_pos, rectangle.y,
+ end_x_pos - start_x_pos, rectangle.height))};
} else {
std::vector<Rect> result;
PangoRectangle rectangle;
auto start_line = pango_layout_get_line(pango_layout_, start_line_index);
- pango_layout_line_get_extents(start_line, nullptr, &rectangle);
+ pango_layout_line_get_extents(start_line, &rectangle, nullptr);
result.push_back(Rect(rectangle.x + start_x_pos, rectangle.y,
rectangle.width - start_x_pos, rectangle.height));
for (int line_index = start_line_index + 1; line_index < end_line_index;
line_index++) {
auto line = pango_layout_get_line(pango_layout_, line_index);
- pango_layout_line_get_extents(line, nullptr, &rectangle);
+ pango_layout_line_get_extents(line, &rectangle, nullptr);
result.push_back(
Rect(rectangle.x, rectangle.y, rectangle.width, rectangle.height));
}
auto end_line = pango_layout_get_line(pango_layout_, end_line_index);
- pango_layout_line_get_extents(end_line, nullptr, &rectangle);
+ pango_layout_line_get_extents(end_line, &rectangle, nullptr);
result.push_back(
Rect(rectangle.x, rectangle.y, end_x_pos, rectangle.height));
+ for (auto& r : result) {
+ r = ConvertFromPango(r);
+ }
+
return result;
}
}
@@ -146,15 +164,17 @@ Rect PangoTextLayout::TextSinglePoint(Index position, bool trailing) {
auto line = pango_layout_get_line(pango_layout_, line_index);
PangoRectangle rectangle;
- pango_layout_line_get_extents(line, nullptr, &rectangle);
+ pango_layout_line_get_extents(line, &rectangle, nullptr);
- return Rect(rectangle.x + x_pos, rectangle.y, 0, rectangle.height);
+ return ConvertFromPango(
+ Rect(rectangle.x + x_pos, rectangle.y, 0, rectangle.height));
}
TextHitTestResult PangoTextLayout::HitTest(const Point& point) {
int index, trailing;
- auto inside_text = pango_layout_xy_to_index(pango_layout_, point.x, point.y,
- &index, &trailing);
+ auto inside_text =
+ pango_layout_xy_to_index(pango_layout_, point.x * PANGO_SCALE,
+ point.y * PANGO_SCALE, &index, &trailing);
return TextHitTestResult{FromUtf8IndexToUtf16Index(index), trailing != 0,
inside_text != 0};
}
diff --git a/src/platform/gui/CMakeLists.txt b/src/platform/gui/CMakeLists.txt
index 808caf9c..b541428e 100644
--- a/src/platform/gui/CMakeLists.txt
+++ b/src/platform/gui/CMakeLists.txt
@@ -2,6 +2,7 @@ add_library(CruPlatformGui
Keyboard.cpp
Menu.cpp
UiApplication.cpp
+ Window.cpp
)
target_link_libraries(CruPlatformGui PUBLIC CruPlatformGraphics)
target_compile_definitions(CruPlatformGui PRIVATE CRU_PLATFORM_GUI_EXPORT_API)
diff --git a/src/platform/gui/Window.cpp b/src/platform/gui/Window.cpp
new file mode 100644
index 00000000..fdcfbae4
--- /dev/null
+++ b/src/platform/gui/Window.cpp
@@ -0,0 +1,6 @@
+#include "cru/platform/gui/Window.h"
+#include "cru/base/Base.h"
+
+namespace cru::platform::gui {
+bool INativeWindow::IsCreated() { NotImplemented(); }
+} // namespace cru::platform::gui
diff --git a/src/platform/gui/xcb/CMakeLists.txt b/src/platform/gui/xcb/CMakeLists.txt
index aa6116e4..fa5cc0bf 100644
--- a/src/platform/gui/xcb/CMakeLists.txt
+++ b/src/platform/gui/xcb/CMakeLists.txt
@@ -1,11 +1,13 @@
find_library(LIBRARY_CAIRO cairo REQUIRED)
find_library(LIBRARY_XCB xcb REQUIRED)
-add_library(CruPlatformGuiX11
+find_library(LIBRARY_XCB_CURSOR xcb-cursor REQUIRED)
+add_library(CruPlatformGuiXcb
+ Cursor.cpp
Keyboard.cpp
UiApplication.cpp
Window.cpp
)
-target_link_libraries(CruPlatformGuiX11 PUBLIC
+target_link_libraries(CruPlatformGuiXcb PUBLIC
CruPlatformGui CruPlatformGraphicsCairo
- ${LIBRARY_XCB} ${LIBRARY_CAIRO}
+ ${LIBRARY_XCB} ${LIBRARY_XCB_CURSOR} ${LIBRARY_CAIRO}
)
diff --git a/src/platform/gui/xcb/Cursor.cpp b/src/platform/gui/xcb/Cursor.cpp
new file mode 100644
index 00000000..5582c6a6
--- /dev/null
+++ b/src/platform/gui/xcb/Cursor.cpp
@@ -0,0 +1,54 @@
+#include "cru/platform/gui/xcb/Cursor.h"
+#include "cru/base/Exception.h"
+#include "cru/platform/gui/Cursor.h"
+#include "cru/platform/gui/xcb/UiApplication.h"
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_cursor.h>
+#include <memory>
+
+namespace cru::platform::gui::xcb {
+XcbCursor::XcbCursor(XcbUiApplication* application, xcb_cursor_t cursor,
+ bool auto_free)
+ : application_(application), cursor_(cursor), auto_free_(auto_free) {}
+
+XcbCursor::~XcbCursor() {
+ if (auto_free_) {
+ xcb_free_cursor(application_->GetXcbConnection(), cursor_);
+ }
+}
+
+xcb_cursor_t XcbCursor::GetXcbCursor() { return cursor_; }
+
+XcbCursorManager::XcbCursorManager(XcbUiApplication* application)
+ : application_(application) {
+ auto code = xcb_cursor_context_new(application->GetXcbConnection(),
+ application->GetFirstXcbScreen(),
+ &xcb_cursor_context_);
+ if (code != 0) {
+ throw PlatformException("Failed to call xcb_cursor_context_new.");
+ }
+
+ cursors_[SystemCursorType::Arrow] =
+ std::make_shared<XcbCursor>(application_, XCB_CURSOR_NONE, false);
+ cursors_[SystemCursorType::Hand] = LoadXCursor("pointer");
+ cursors_[SystemCursorType::IBeam] = LoadXCursor("ibeam");
+}
+
+XcbCursorManager::~XcbCursorManager() {
+ xcb_cursor_context_free(xcb_cursor_context_);
+}
+
+std::shared_ptr<ICursor> XcbCursorManager::GetSystemCursor(
+ SystemCursorType type) {
+ return cursors_[type];
+}
+
+std::shared_ptr<XcbCursor> XcbCursorManager::LoadXCursor(
+ std::string_view name) {
+ return std::make_shared<XcbCursor>(
+ application_, xcb_cursor_load_cursor(xcb_cursor_context_, name.data()),
+ true);
+}
+
+} // namespace cru::platform::gui::xcb
diff --git a/src/platform/gui/xcb/Keyboard.cpp b/src/platform/gui/xcb/Keyboard.cpp
index d7559062..e5f6da8e 100644
--- a/src/platform/gui/xcb/Keyboard.cpp
+++ b/src/platform/gui/xcb/Keyboard.cpp
@@ -1,4 +1,5 @@
#include "cru/platform/gui/xcb/Keyboard.h"
+#include "cru/base/Guard.h"
#include "cru/platform/gui/Keyboard.h"
#include "cru/platform/gui/xcb/UiApplication.h"
@@ -82,8 +83,8 @@ KeyCode XorgKeycodeToCruKeyCode(XcbUiApplication *application,
// Get keyboard mapping
auto mapping_cookie = xcb_get_keyboard_mapping(connection, keycode, 1);
- auto *mapping_reply =
- xcb_get_keyboard_mapping_reply(connection, mapping_cookie, NULL);
+ auto mapping_reply = FreeLater(
+ xcb_get_keyboard_mapping_reply(connection, mapping_cookie, NULL));
if (!mapping_reply) {
throw XcbException("Cannot get keyboard mapping.");
@@ -107,7 +108,8 @@ using KeymapBitset =
KeymapBitset GetXorgKeymap(xcb_connection_t *connection) {
auto keymap_cookie = xcb_query_keymap(connection);
- auto keymap_reply = xcb_query_keymap_reply(connection, keymap_cookie, NULL);
+ auto keymap_reply =
+ FreeLater(xcb_query_keymap_reply(connection, keymap_cookie, NULL));
if (!keymap_reply) {
throw XcbException("Cannot get keymap.");
@@ -136,8 +138,8 @@ std::unordered_map<KeyCode, bool> GetKeyboardState(
// Get keyboard mapping
auto mapping_cookie = xcb_get_keyboard_mapping(connection, min_keycode,
max_keycode - min_keycode + 1);
- auto *mapping_reply =
- xcb_get_keyboard_mapping_reply(connection, mapping_cookie, NULL);
+ auto mapping_reply = FreeLater(
+ xcb_get_keyboard_mapping_reply(connection, mapping_cookie, NULL));
if (!mapping_reply) {
throw XcbException("Cannot get keyboard mapping.");
diff --git a/src/platform/gui/xcb/UiApplication.cpp b/src/platform/gui/xcb/UiApplication.cpp
index 0a6ab07f..1e5613b4 100644
--- a/src/platform/gui/xcb/UiApplication.cpp
+++ b/src/platform/gui/xcb/UiApplication.cpp
@@ -1,10 +1,15 @@
#include "cru/platform/gui/xcb/UiApplication.h"
+#include "cru/base/Base.h"
+#include "cru/base/Guard.h"
#include "cru/platform/graphics/cairo/CairoGraphicsFactory.h"
+#include "cru/platform/gui/Window.h"
+#include "cru/platform/gui/xcb/Cursor.h"
#include "cru/platform/gui/xcb/Window.h"
#include <poll.h>
#include <xcb/xcb.h>
+#include <algorithm>
namespace cru::platform::gui::xcb {
XcbUiApplication::XcbUiApplication(
@@ -20,6 +25,7 @@ XcbUiApplication::XcbUiApplication(
int screen_num;
xcb_connection_t *connection = xcb_connect(NULL, &screen_num);
+ xcb_connection_ = connection;
this->CheckXcbConnectionError();
event_loop_.SetPoll(xcb_get_file_descriptor(connection), POLLIN,
@@ -28,15 +34,23 @@ XcbUiApplication::XcbUiApplication(
const xcb_setup_t *setup = xcb_get_setup(connection);
xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
this->screen_ = iter.data;
+
+ cursor_manager_ = new XcbCursorManager(this);
}
XcbUiApplication::~XcbUiApplication() {
+ delete cursor_manager_;
+
xcb_disconnect(this->xcb_connection_);
if (release_cairo_factory_) {
delete cairo_factory_;
}
}
+graphics::cairo::CairoGraphicsFactory *XcbUiApplication::GetCairoFactory() {
+ return cairo_factory_;
+}
+
void XcbUiApplication::CheckXcbConnectionError() {
if (xcb_connection_has_error(this->xcb_connection_)) {
throw XcbException("xcb_connection_has_error returned non-zero.");
@@ -47,6 +61,8 @@ xcb_connection_t *XcbUiApplication::GetXcbConnection() {
return xcb_connection_;
}
+void XcbUiApplication::XcbFlush() { xcb_flush(xcb_connection_); }
+
xcb_screen_t *XcbUiApplication::GetFirstXcbScreen() { return screen_; }
xcb_atom_t XcbUiApplication::GetOrCreateXcbAtom(std::string name) {
@@ -57,7 +73,8 @@ xcb_atom_t XcbUiApplication::GetOrCreateXcbAtom(std::string name) {
auto cookie =
xcb_intern_atom(xcb_connection_, false, name.size(), name.data());
- auto reply = xcb_intern_atom_reply(xcb_connection_, cookie, nullptr);
+ auto reply =
+ FreeLater(xcb_intern_atom_reply(xcb_connection_, cookie, nullptr));
auto atom = reply->atom;
xcb_atom_.emplace(std::move(name), atom);
return atom;
@@ -122,6 +139,25 @@ void XcbUiApplication::HandleXEvents() {
}
}
+std::vector<INativeWindow *> XcbUiApplication::GetAllWindow() {
+ std::vector<INativeWindow *> windows(windows_.size());
+ std::ranges::copy(windows_, windows.begin());
+ return windows;
+}
+
+INativeWindow *XcbUiApplication::CreateWindow() { return new XcbWindow(this); }
+
+cru::platform::graphics::IGraphicsFactory *
+XcbUiApplication::GetGraphicsFactory() {
+ return cairo_factory_;
+}
+
+ICursorManager *XcbUiApplication::GetCursorManager() { return cursor_manager_; }
+
+IClipboard *XcbUiApplication::GetClipboard() { NotImplemented(); }
+
+IMenu *XcbUiApplication::GetApplicationMenu() { return nullptr; }
+
void XcbUiApplication::RegisterWindow(XcbWindow *window) {
windows_.push_back(window);
}
diff --git a/src/platform/gui/xcb/Window.cpp b/src/platform/gui/xcb/Window.cpp
index 188f62b8..e6e92fbc 100644
--- a/src/platform/gui/xcb/Window.cpp
+++ b/src/platform/gui/xcb/Window.cpp
@@ -1,17 +1,23 @@
#include "cru/platform/gui/xcb/Window.h"
#include "cru/base/Base.h"
+#include "cru/base/Guard.h"
#include "cru/platform/Check.h"
+#include "cru/platform/GraphicsBase.h"
+#include "cru/platform/graphics/NullPainter.h"
#include "cru/platform/graphics/Painter.h"
#include "cru/platform/graphics/cairo/CairoPainter.h"
#include "cru/platform/gui/Base.h"
#include "cru/platform/gui/Keyboard.h"
#include "cru/platform/gui/Window.h"
+#include "cru/platform/gui/xcb/Cursor.h"
#include "cru/platform/gui/xcb/Keyboard.h"
#include "cru/platform/gui/xcb/UiApplication.h"
#include <cairo-xcb.h>
#include <cairo.h>
#include <xcb/xcb.h>
+#include <xcb/xproto.h>
+#include <algorithm>
#include <cassert>
#include <cinttypes>
#include <cstdint>
@@ -66,9 +72,12 @@ XcbWindow::XcbWindow(XcbUiApplication *application)
XcbWindow::~XcbWindow() { application_->UnregisterWindow(this); }
+bool XcbWindow::IsCreated() { return xcb_window_.has_value(); }
+
void XcbWindow::Close() {
if (xcb_window_) {
xcb_destroy_window(application_->GetXcbConnection(), *xcb_window_);
+ application_->XcbFlush();
}
}
@@ -79,6 +88,7 @@ void XcbWindow::SetParent(INativeWindow *parent) {
if (xcb_window_) {
DoSetParent(*xcb_window_);
}
+ application_->XcbFlush();
}
WindowStyleFlag XcbWindow::GetStyleFlag() { return style_; }
@@ -88,6 +98,7 @@ void XcbWindow::SetStyleFlag(WindowStyleFlag flag) {
if (xcb_window_) {
DoSetStyleFlags(*xcb_window_);
}
+ application_->XcbFlush();
}
String XcbWindow::GetTitle() { return String::FromUtf8(title_); }
@@ -97,6 +108,7 @@ void XcbWindow::SetTitle(String title) {
if (xcb_window_) {
DoSetTitle(*xcb_window_);
}
+ application_->XcbFlush();
}
namespace {
@@ -140,7 +152,6 @@ void XcbWindow::SetVisibility(WindowVisibilityType visibility) {
auto old_value = static_cast<std::uint32_t *>(
XcbGetProperty(*xcb_window_, atom, atom, 0, 2));
if (old_value) value[1] = old_value[1];
- UnreachableCode();
xcb_change_property(application_->GetXcbConnection(), XCB_PROP_MODE_REPLACE,
window, atom, atom, 32, sizeof(value) / sizeof(*value),
@@ -175,10 +186,125 @@ void XcbWindow::SetVisibility(WindowVisibilityType visibility) {
default:
UnreachableCode();
}
+
+ application_->XcbFlush();
+}
+
+Size XcbWindow::GetClientSize() { return GetClientRect().GetSize(); }
+
+void XcbWindow::SetClientSize(const Size &size) {
+ auto rect = GetClientRect();
+ SetClientRect(Rect(rect.GetLeftTop(), size));
+}
+
+Rect XcbWindow::GetClientRect() {
+ if (!xcb_window_) {
+ return Rect{};
+ }
+
+ auto window = *xcb_window_;
+
+ auto cookie = xcb_get_geometry(application_->GetXcbConnection(), window);
+ auto reply = FreeLater(xcb_get_geometry_reply(
+ application_->GetXcbConnection(), cookie, nullptr));
+ auto position = GetXcbWindowPosition(window);
+
+ return Rect(position.x, position.y, reply->width, reply->height);
+}
+
+void XcbWindow::SetClientRect(const Rect &rect) {
+ if (!xcb_window_) return;
+ DoSetClientRect(*xcb_window_, rect);
+ application_->XcbFlush();
+}
+
+Rect XcbWindow::GetWindowRect() {
+ if (!xcb_window_) return {};
+
+ auto client_rect = GetClientRect();
+ auto frame_properties = Get_NET_FRAME_EXTENTS(*xcb_window_);
+
+ if (frame_properties.has_value()) {
+ return client_rect.Expand(*frame_properties);
+ }
+
+ return client_rect;
+}
+
+void XcbWindow::SetWindowRect(const Rect &rect) {
+ if (!xcb_window_) return;
+
+ auto real_rect = rect;
+ auto frame_properties = Get_NET_FRAME_EXTENTS(*xcb_window_);
+
+ if (frame_properties.has_value()) {
+ real_rect = real_rect.Shrink(*frame_properties, true);
+ }
+
+ SetClientRect(real_rect);
+}
+
+bool XcbWindow::RequestFocus() {
+ if (!xcb_window_) return false;
+ xcb_set_input_focus(application_->GetXcbConnection(), XCB_NONE, *xcb_window_,
+ XCB_CURRENT_TIME);
+ application_->XcbFlush();
+ return true;
+}
+
+Point XcbWindow::GetMousePosition() {
+ auto window = xcb_window_.value_or(application_->GetFirstXcbScreen()->root);
+ auto cookie = xcb_query_pointer(application_->GetXcbConnection(), window);
+ auto reply = FreeLater(xcb_query_pointer_reply(
+ application_->GetXcbConnection(), cookie, nullptr));
+ return Point(reply->win_x, reply->win_y);
+}
+
+bool XcbWindow::CaptureMouse() {
+ if (!xcb_window_) return false;
+ // KDE handles grabbing automatically.
+ //
+ // xcb_grab_pointer(application_->GetXcbConnection(), TRUE, *xcb_window_, 0,
+ // XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_WINDOW_NONE,
+ // XCB_CURSOR_NONE, XCB_CURRENT_TIME);
+ // application_->XcbFlush();
+ return true;
+}
+
+bool XcbWindow::ReleaseMouse() {
+ if (!xcb_window_) return false;
+ // KDE handles grabbing automatically.
+ //
+ // xcb_ungrab_pointer(application_->xcb_connection_, XCB_CURRENT_TIME);
+ // application_->XcbFlush();
+ return true;
+}
+
+void XcbWindow::SetCursor(std::shared_ptr<ICursor> cursor) {
+ if (!xcb_window_) return;
+ auto xcb_cursor = CheckPlatform<XcbCursor>(cursor, GetPlatformId());
+ cursor_ = xcb_cursor;
+ DoSetCursor(*xcb_window_, xcb_cursor.get());
+}
+
+void XcbWindow::SetToForeground() {
+ SetVisibility(WindowVisibilityType::Show);
+ assert(xcb_window_.has_value());
+ const static uint32_t values[] = {XCB_STACK_MODE_ABOVE};
+ xcb_configure_window(application_->GetXcbConnection(), *xcb_window_,
+ XCB_CONFIG_WINDOW_STACK_MODE, values);
+ application_->XcbFlush();
+}
+
+void XcbWindow::RequestRepaint() {
+ // TODO: true throttle
+ paint_event_.Raise(nullptr);
}
std::unique_ptr<graphics::IPainter> XcbWindow::BeginPaint() {
- assert(cairo_surface_);
+ if (!xcb_window_.has_value()) {
+ return std::make_unique<graphics::NullPainter>();
+ }
auto factory = application_->GetCairoFactory();
cairo_t *cairo = cairo_create(cairo_surface_);
@@ -225,6 +351,8 @@ IEvent<NativeKeyEventArgs> *XcbWindow::KeyDownEvent() {
IEvent<NativeKeyEventArgs> *XcbWindow::KeyUpEvent() { return &key_up_event_; }
+IInputMethodContext *XcbWindow::GetInputMethodContext() { return nullptr; }
+
std::optional<xcb_window_t> XcbWindow::GetXcbWindow() { return xcb_window_; }
xcb_window_t XcbWindow::DoCreateWindow() {
@@ -253,8 +381,16 @@ xcb_window_t XcbWindow::DoCreateWindow() {
xcb_window_ = window;
+ std::vector<xcb_atom_t> wm_protocols{
+ application_->GetXcbAtomWM_DELETE_WINDOW()};
+ xcb_change_property(application_->GetXcbConnection(), XCB_PROP_MODE_REPLACE,
+ window, application_->GetXcbAtomWM_PROTOCOLS(),
+ XCB_ATOM_ATOM, 32, wm_protocols.size(),
+ wm_protocols.data());
+
DoSetStyleFlags(window);
DoSetParent(window);
+ DoSetCursor(window, cursor_.get());
xcb_visualtype_t *visual_type;
@@ -291,6 +427,14 @@ void XcbWindow::HandleEvent(xcb_generic_event_t *event) {
cairo_surface_destroy(cairo_surface_);
cairo_surface_ = nullptr;
xcb_window_ = std::nullopt;
+
+ if (application_->IsQuitOnAllWindowClosed() &&
+ std::ranges::none_of(
+ application_->GetAllWindow(),
+ [](INativeWindow *window) { return window->IsCreated(); })) {
+ application_->RequestQuit(0);
+ }
+
break;
}
case XCB_CONFIGURE_NOTIFY: {
@@ -388,6 +532,16 @@ void XcbWindow::HandleEvent(xcb_generic_event_t *event) {
key_up_event_.Raise(std::move(args));
break;
}
+ case XCB_CLIENT_MESSAGE: {
+ xcb_client_message_event_t *cm = (xcb_client_message_event_t *)event;
+ if (cm->data.data32[0] == application_->GetXcbAtomWM_DELETE_WINDOW() &&
+ xcb_window_.has_value()) {
+ xcb_destroy_window(application_->GetXcbConnection(),
+ xcb_window_.value());
+ xcb_flush(application_->GetXcbConnection());
+ }
+ break;
+ }
default:
/* Unknown event type, ignore it */
printf("Unknown event: %" PRIu8 "\n", event->response_type);
@@ -455,6 +609,10 @@ std::optional<xcb_window_t> XcbWindow::GetEventWindow(
xcb_key_release_event_t *kr = (xcb_key_release_event_t *)event;
return kr->event;
}
+ case XCB_CLIENT_MESSAGE: {
+ xcb_client_message_event_t *cm = (xcb_client_message_event_t *)event;
+ return cm->window;
+ }
default:
return std::nullopt;
}
@@ -493,14 +651,38 @@ void XcbWindow::DoSetTitle(xcb_window_t window) {
}
}
+void XcbWindow::DoSetClientRect(xcb_window_t window, const Rect &rect) {
+ auto tree_cookie = xcb_query_tree(application_->GetXcbConnection(), window);
+ auto tree_reply = FreeLater(xcb_query_tree_reply(
+ application_->GetXcbConnection(), tree_cookie, nullptr));
+ auto parent_position = GetXcbWindowPosition(tree_reply->parent);
+
+ std::uint32_t values[4]{
+ static_cast<std::uint32_t>(rect.left - parent_position.x),
+ static_cast<std::uint32_t>(rect.top - parent_position.y),
+ static_cast<std::uint32_t>(rect.width),
+ static_cast<std::uint32_t>(rect.height)};
+ xcb_configure_window(application_->GetXcbConnection(), window,
+ XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
+ XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
+ values);
+}
+
+void XcbWindow::DoSetCursor(xcb_window_t window, XcbCursor *cursor) {
+ std::uint32_t values[]{cursor ? cursor->GetXcbCursor() : XCB_CURSOR_NONE};
+ xcb_change_window_attributes(application_->GetXcbConnection(), window,
+ XCB_CW_CURSOR, values);
+ application_->XcbFlush();
+}
+
void *XcbWindow::XcbGetProperty(xcb_window_t window, xcb_atom_t property,
xcb_atom_t type, std::uint32_t offset,
std::uint32_t length,
std::uint32_t *out_length) {
auto cookie = xcb_get_property(application_->GetXcbConnection(), false,
window, property, type, offset, length);
- auto reply =
- xcb_get_property_reply(application_->GetXcbConnection(), cookie, NULL);
+ auto reply = FreeLater(
+ xcb_get_property_reply(application_->GetXcbConnection(), cookie, NULL));
if (reply->type == XCB_ATOM_NONE) {
return nullptr;
}
@@ -509,4 +691,41 @@ void *XcbWindow::XcbGetProperty(xcb_window_t window, xcb_atom_t property,
}
return xcb_get_property_value(reply);
}
+
+std::optional<Thickness> XcbWindow::Get_NET_FRAME_EXTENTS(xcb_window_t window) {
+ auto frame_properties = static_cast<std::uint32_t *>(
+ XcbGetProperty(window, application_->GetXcbAtom_NET_FRAME_EXTENTS(),
+ XCB_ATOM_CARDINAL, 0, 4));
+
+ if (frame_properties == nullptr) {
+ return std::nullopt;
+ }
+
+ return Thickness(frame_properties[0], frame_properties[2],
+ frame_properties[1], frame_properties[3]);
+}
+
+Point XcbWindow::GetXcbWindowPosition(xcb_window_t window) {
+ Point result;
+
+ while (true) {
+ auto cookie = xcb_get_geometry(application_->GetXcbConnection(), window);
+ auto reply = FreeLater(xcb_get_geometry_reply(
+ application_->GetXcbConnection(), cookie, nullptr));
+ result.x += reply->x;
+ result.y += reply->y;
+
+ auto tree_cookie = xcb_query_tree(application_->GetXcbConnection(), window);
+ auto tree_reply = FreeLater(xcb_query_tree_reply(
+ application_->GetXcbConnection(), tree_cookie, nullptr));
+ window = tree_reply->parent;
+ // TODO: Multi-screen offset?
+ if (tree_reply->root == window || window == XCB_WINDOW_NONE) {
+ break;
+ }
+ }
+
+ return result;
+}
+
} // namespace cru::platform::gui::xcb