From d9b2c00228bc2213564f6a0b75702bef34f15ea2 Mon Sep 17 00:00:00 2001 From: Rachael Alexanderson Date: Wed, 30 Jul 2025 00:09:28 -0400 Subject: [PATCH] Squashed 'libraries/ZWidget/' content from commit 96501b6ef1 git-subtree-dir: libraries/ZWidget git-subtree-split: 96501b6ef11e8737cd8ccb2451395115b810dfcc --- .github/workflows/build.yaml | 93 + .gitignore | 6 + CMakeLists.txt | 305 ++ LICENSE.md | 21 + README.md | 2 + example/OpenSans.ttf | Bin 0 -> 532636 bytes example/banner.png | Bin 0 -> 360469 bytes example/example.cpp | 283 ++ example/picopng.cpp | 593 ++++ example/picopng.h | 5 + include/zwidget/core/canvas.h | 116 + include/zwidget/core/colorf.h | 36 + include/zwidget/core/font.h | 15 + include/zwidget/core/image.h | 24 + include/zwidget/core/pathfill.h | 96 + include/zwidget/core/rect.h | 85 + include/zwidget/core/resourcedata.h | 14 + include/zwidget/core/span_layout.h | 238 ++ include/zwidget/core/theme.h | 81 + include/zwidget/core/timer.h | 26 + include/zwidget/core/utf8reader.h | 78 + include/zwidget/core/widget.h | 241 ++ .../zwidget/systemdialogs/open_file_dialog.h | 55 + .../systemdialogs/open_folder_dialog.h | 30 + .../zwidget/systemdialogs/save_file_dialog.h | 46 + .../widgets/checkboxlabel/checkboxlabel.h | 34 + include/zwidget/widgets/imagebox/imagebox.h | 31 + include/zwidget/widgets/lineedit/lineedit.h | 158 + include/zwidget/widgets/listview/listview.h | 46 + .../zwidget/widgets/mainwindow/mainwindow.h | 33 + include/zwidget/widgets/menubar/menubar.h | 121 + .../zwidget/widgets/pushbutton/pushbutton.h | 32 + include/zwidget/widgets/scrollbar/scrollbar.h | 99 + include/zwidget/widgets/statusbar/statusbar.h | 19 + include/zwidget/widgets/tabwidget/tabwidget.h | 129 + include/zwidget/widgets/textedit/textedit.h | 155 + include/zwidget/widgets/textlabel/textlabel.h | 33 + include/zwidget/widgets/toolbar/toolbar.h | 31 + .../zwidget/widgets/toolbar/toolbarbutton.h | 34 + include/zwidget/window/sdl2nativehandle.h | 12 + include/zwidget/window/waylandnativehandle.h | 10 + include/zwidget/window/win32nativehandle.h | 9 + include/zwidget/window/window.h | 243 ++ include/zwidget/window/x11nativehandle.h | 13 + src/core/canvas.cpp | 1084 ++++++ src/core/font.cpp | 28 + src/core/image.cpp | 110 + src/core/nanosvg/nanosvg.cpp | 6 + src/core/nanosvg/nanosvg.h | 3107 +++++++++++++++++ src/core/nanosvg/nanosvgrast.h | 1459 ++++++++ src/core/pathfill.cpp | 613 ++++ src/core/picopng/LICENSE.txt | 18 + src/core/picopng/picopng.cpp | 595 ++++ src/core/picopng/picopng.h | 5 + src/core/span_layout.cpp | 883 +++++ src/core/theme.cpp | 412 +++ src/core/timer.cpp | 45 + src/core/truetypefont.cpp | 1201 +++++++ src/core/truetypefont.h | 527 +++ src/core/utf8reader.cpp | 153 + src/core/widget.cpp | 957 +++++ src/systemdialogs/open_file_dialog.cpp | 12 + src/systemdialogs/open_folder_dialog.cpp | 12 + src/systemdialogs/save_file_dialog.cpp | 12 + src/widgets/checkboxlabel/checkboxlabel.cpp | 101 + src/widgets/imagebox/imagebox.cpp | 73 + src/widgets/lineedit/lineedit.cpp | 1171 +++++++ src/widgets/listview/listview.cpp | 286 ++ src/widgets/mainwindow/mainwindow.cpp | 44 + src/widgets/menubar/menubar.cpp | 390 +++ src/widgets/pushbutton/pushbutton.cpp | 88 + src/widgets/scrollbar/scrollbar.cpp | 409 +++ src/widgets/statusbar/statusbar.cpp | 21 + src/widgets/tabwidget/tabwidget.cpp | 345 ++ src/widgets/textedit/textedit.cpp | 1045 ++++++ src/widgets/textlabel/textlabel.cpp | 60 + src/widgets/toolbar/toolbar.cpp | 63 + src/widgets/toolbar/toolbarbutton.cpp | 114 + src/window/dbus/dbus_open_file_dialog.cpp | 288 ++ src/window/dbus/dbus_open_file_dialog.h | 38 + src/window/dbus/dbus_open_folder_dialog.cpp | 26 + src/window/dbus/dbus_open_folder_dialog.h | 21 + src/window/dbus/dbus_save_file_dialog.cpp | 54 + src/window/dbus/dbus_save_file_dialog.h | 37 + src/window/sdl2/sdl2_display_backend.cpp | 98 + src/window/sdl2/sdl2_display_backend.h | 24 + src/window/sdl2/sdl2_display_window.cpp | 792 +++++ src/window/sdl2/sdl2_display_window.h | 112 + src/window/stub/stub_open_file_dialog.cpp | 64 + src/window/stub/stub_open_file_dialog.h | 41 + src/window/stub/stub_open_folder_dialog.cpp | 26 + src/window/stub/stub_open_folder_dialog.h | 23 + src/window/stub/stub_save_file_dialog.cpp | 54 + src/window/stub/stub_save_file_dialog.h | 39 + .../wayland/wayland_display_backend.cpp | 1150 ++++++ src/window/wayland/wayland_display_backend.h | 168 + src/window/wayland/wayland_display_window.cpp | 472 +++ src/window/wayland/wayland_display_window.h | 198 ++ .../wl_fractional_scaling_protocol.cpp | 205 ++ .../wl_fractional_scaling_protocol.hpp | 126 + src/window/win32/win32_display_backend.cpp | 56 + src/window/win32/win32_display_backend.h | 23 + src/window/win32/win32_display_window.cpp | 788 +++++ src/window/win32/win32_display_window.h | 94 + src/window/win32/win32_open_file_dialog.cpp | 227 ++ src/window/win32/win32_open_file_dialog.h | 44 + src/window/win32/win32_open_folder_dialog.cpp | 111 + src/window/win32/win32_open_folder_dialog.h | 26 + src/window/win32/win32_save_file_dialog.cpp | 174 + src/window/win32/win32_save_file_dialog.h | 42 + src/window/win32/win32_util.h | 60 + src/window/window.cpp | 201 ++ src/window/x11/x11_display_backend.cpp | 70 + src/window/x11/x11_display_backend.h | 25 + src/window/x11/x11_display_window.cpp | 1184 +++++++ src/window/x11/x11_display_window.h | 142 + src/window/ztimer/ztimer.cpp | 73 + src/window/ztimer/ztimer.h | 41 + 118 files changed, 26617 insertions(+) create mode 100644 .github/workflows/build.yaml create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 example/OpenSans.ttf create mode 100644 example/banner.png create mode 100644 example/example.cpp create mode 100644 example/picopng.cpp create mode 100644 example/picopng.h create mode 100644 include/zwidget/core/canvas.h create mode 100644 include/zwidget/core/colorf.h create mode 100644 include/zwidget/core/font.h create mode 100644 include/zwidget/core/image.h create mode 100644 include/zwidget/core/pathfill.h create mode 100644 include/zwidget/core/rect.h create mode 100644 include/zwidget/core/resourcedata.h create mode 100644 include/zwidget/core/span_layout.h create mode 100644 include/zwidget/core/theme.h create mode 100644 include/zwidget/core/timer.h create mode 100644 include/zwidget/core/utf8reader.h create mode 100644 include/zwidget/core/widget.h create mode 100644 include/zwidget/systemdialogs/open_file_dialog.h create mode 100644 include/zwidget/systemdialogs/open_folder_dialog.h create mode 100644 include/zwidget/systemdialogs/save_file_dialog.h create mode 100644 include/zwidget/widgets/checkboxlabel/checkboxlabel.h create mode 100644 include/zwidget/widgets/imagebox/imagebox.h create mode 100644 include/zwidget/widgets/lineedit/lineedit.h create mode 100644 include/zwidget/widgets/listview/listview.h create mode 100644 include/zwidget/widgets/mainwindow/mainwindow.h create mode 100644 include/zwidget/widgets/menubar/menubar.h create mode 100644 include/zwidget/widgets/pushbutton/pushbutton.h create mode 100644 include/zwidget/widgets/scrollbar/scrollbar.h create mode 100644 include/zwidget/widgets/statusbar/statusbar.h create mode 100644 include/zwidget/widgets/tabwidget/tabwidget.h create mode 100644 include/zwidget/widgets/textedit/textedit.h create mode 100644 include/zwidget/widgets/textlabel/textlabel.h create mode 100644 include/zwidget/widgets/toolbar/toolbar.h create mode 100644 include/zwidget/widgets/toolbar/toolbarbutton.h create mode 100644 include/zwidget/window/sdl2nativehandle.h create mode 100644 include/zwidget/window/waylandnativehandle.h create mode 100644 include/zwidget/window/win32nativehandle.h create mode 100644 include/zwidget/window/window.h create mode 100644 include/zwidget/window/x11nativehandle.h create mode 100644 src/core/canvas.cpp create mode 100644 src/core/font.cpp create mode 100644 src/core/image.cpp create mode 100644 src/core/nanosvg/nanosvg.cpp create mode 100644 src/core/nanosvg/nanosvg.h create mode 100644 src/core/nanosvg/nanosvgrast.h create mode 100644 src/core/pathfill.cpp create mode 100644 src/core/picopng/LICENSE.txt create mode 100644 src/core/picopng/picopng.cpp create mode 100644 src/core/picopng/picopng.h create mode 100644 src/core/span_layout.cpp create mode 100644 src/core/theme.cpp create mode 100644 src/core/timer.cpp create mode 100644 src/core/truetypefont.cpp create mode 100644 src/core/truetypefont.h create mode 100644 src/core/utf8reader.cpp create mode 100644 src/core/widget.cpp create mode 100644 src/systemdialogs/open_file_dialog.cpp create mode 100644 src/systemdialogs/open_folder_dialog.cpp create mode 100644 src/systemdialogs/save_file_dialog.cpp create mode 100644 src/widgets/checkboxlabel/checkboxlabel.cpp create mode 100644 src/widgets/imagebox/imagebox.cpp create mode 100644 src/widgets/lineedit/lineedit.cpp create mode 100644 src/widgets/listview/listview.cpp create mode 100644 src/widgets/mainwindow/mainwindow.cpp create mode 100644 src/widgets/menubar/menubar.cpp create mode 100644 src/widgets/pushbutton/pushbutton.cpp create mode 100644 src/widgets/scrollbar/scrollbar.cpp create mode 100644 src/widgets/statusbar/statusbar.cpp create mode 100644 src/widgets/tabwidget/tabwidget.cpp create mode 100644 src/widgets/textedit/textedit.cpp create mode 100644 src/widgets/textlabel/textlabel.cpp create mode 100644 src/widgets/toolbar/toolbar.cpp create mode 100644 src/widgets/toolbar/toolbarbutton.cpp create mode 100644 src/window/dbus/dbus_open_file_dialog.cpp create mode 100644 src/window/dbus/dbus_open_file_dialog.h create mode 100644 src/window/dbus/dbus_open_folder_dialog.cpp create mode 100644 src/window/dbus/dbus_open_folder_dialog.h create mode 100644 src/window/dbus/dbus_save_file_dialog.cpp create mode 100644 src/window/dbus/dbus_save_file_dialog.h create mode 100644 src/window/sdl2/sdl2_display_backend.cpp create mode 100644 src/window/sdl2/sdl2_display_backend.h create mode 100644 src/window/sdl2/sdl2_display_window.cpp create mode 100644 src/window/sdl2/sdl2_display_window.h create mode 100644 src/window/stub/stub_open_file_dialog.cpp create mode 100644 src/window/stub/stub_open_file_dialog.h create mode 100644 src/window/stub/stub_open_folder_dialog.cpp create mode 100644 src/window/stub/stub_open_folder_dialog.h create mode 100644 src/window/stub/stub_save_file_dialog.cpp create mode 100644 src/window/stub/stub_save_file_dialog.h create mode 100644 src/window/wayland/wayland_display_backend.cpp create mode 100644 src/window/wayland/wayland_display_backend.h create mode 100644 src/window/wayland/wayland_display_window.cpp create mode 100644 src/window/wayland/wayland_display_window.h create mode 100644 src/window/wayland/wl_fractional_scaling_protocol.cpp create mode 100644 src/window/wayland/wl_fractional_scaling_protocol.hpp create mode 100644 src/window/win32/win32_display_backend.cpp create mode 100644 src/window/win32/win32_display_backend.h create mode 100644 src/window/win32/win32_display_window.cpp create mode 100644 src/window/win32/win32_display_window.h create mode 100644 src/window/win32/win32_open_file_dialog.cpp create mode 100644 src/window/win32/win32_open_file_dialog.h create mode 100644 src/window/win32/win32_open_folder_dialog.cpp create mode 100644 src/window/win32/win32_open_folder_dialog.h create mode 100644 src/window/win32/win32_save_file_dialog.cpp create mode 100644 src/window/win32/win32_save_file_dialog.h create mode 100644 src/window/win32/win32_util.h create mode 100644 src/window/window.cpp create mode 100644 src/window/x11/x11_display_backend.cpp create mode 100644 src/window/x11/x11_display_backend.h create mode 100644 src/window/x11/x11_display_window.cpp create mode 100644 src/window/x11/x11_display_window.h create mode 100644 src/window/ztimer/ztimer.cpp create mode 100644 src/window/ztimer/ztimer.h diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 000000000..396970061 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,93 @@ +name: build + +on: + push: + pull_request: + +env: + BUILD_TYPE: Release + +jobs: + linux: + runs-on: ubuntu-latest + name: 🐧 Ubuntu x64 + steps: + - name: 🧰 Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + submodules: true + + - name: ⬇️ Install dependencies + env: + DEBIAN_FRONTEND: noninteractive + run: | + sudo apt-get -qq update + sudo apt-get -qq install \ + build-essential \ + libsdl2-dev \ + libsdl2-mixer-dev + - 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: Build + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake --build . --config $BUILD_TYPE + + win: + runs-on: windows-latest + name: 🟦 Windows x64 + steps: + + - name: 🧰 Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + submodules: true + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake Windows + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE="C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DVCPKG_BIN_DIR="c:/vcpkg/installed/x64-windows/bin" + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + run: cmake --build . --config $BUILD_TYPE + +# macos-build: +# runs-on: macos-10.15 +# name: 🍎 macOS 10.15 +# steps: + +# - name: 🧰 Checkout +# uses: actions/checkout@v2 +# with: +# fetch-depth: 0 +# submodules: true + +# - name: ⬇️ Install dependencies +# run: brew install sdl2 sdl2_mixer + +# - 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: Build +# working-directory: ${{runner.workspace}}/build +# shell: bash +# run: | +# cmake --build . --config $BUILD_TYPE diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..6c41b1f58 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# build artifacts +/build + +# misc cache/backup/swap files +.cache +*~ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..894da17e2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,305 @@ +cmake_minimum_required(VERSION 3.11) +project(zwidget) + +if (UNIX AND NOT APPLE) + include(FindPkgConfig) + pkg_check_modules(DBUS dbus-1) + if (!DBUS_FOUND) + pkg_check_modules(DBUS REQUIRED dbus) # Fedora Linux looks for dbus instead + endif() + pkg_check_modules(WAYLANDPP + wayland-client + wayland-client++ + wayland-client-extra++ + wayland-client-unstable++ + wayland-cursor++ + xkbcommon>=0.5.0 + ) +endif() + +set(ZWIDGET_SOURCES + src/core/canvas.cpp + src/core/font.cpp + src/core/image.cpp + src/core/span_layout.cpp + src/core/timer.cpp + src/core/widget.cpp + src/core/theme.cpp + src/core/utf8reader.cpp + src/core/pathfill.cpp + src/core/truetypefont.cpp + src/core/truetypefont.h + src/core/picopng/picopng.cpp + src/core/picopng/picopng.h + src/core/nanosvg/nanosvg.cpp + src/core/nanosvg/nanosvg.h + src/core/nanosvg/nanosvgrast.h + src/widgets/lineedit/lineedit.cpp + src/widgets/mainwindow/mainwindow.cpp + src/widgets/menubar/menubar.cpp + src/widgets/scrollbar/scrollbar.cpp + src/widgets/statusbar/statusbar.cpp + src/widgets/textedit/textedit.cpp + src/widgets/toolbar/toolbar.cpp + src/widgets/toolbar/toolbarbutton.cpp + src/widgets/imagebox/imagebox.cpp + src/widgets/textlabel/textlabel.cpp + src/widgets/pushbutton/pushbutton.cpp + src/widgets/checkboxlabel/checkboxlabel.cpp + src/widgets/listview/listview.cpp + src/widgets/tabwidget/tabwidget.cpp + src/window/window.cpp + src/window/stub/stub_open_folder_dialog.cpp + src/window/stub/stub_open_folder_dialog.h + src/window/stub/stub_open_file_dialog.cpp + src/window/stub/stub_open_file_dialog.h + src/window/stub/stub_save_file_dialog.cpp + src/window/stub/stub_save_file_dialog.h + src/window/ztimer/ztimer.h + src/window/ztimer/ztimer.cpp + src/systemdialogs/open_folder_dialog.cpp + src/systemdialogs/open_file_dialog.cpp + src/systemdialogs/save_file_dialog.cpp +) + +set(ZWIDGET_INCLUDES + include/zwidget/core/canvas.h + include/zwidget/core/colorf.h + include/zwidget/core/font.h + include/zwidget/core/image.h + include/zwidget/core/rect.h + include/zwidget/core/pathfill.h + include/zwidget/core/span_layout.h + include/zwidget/core/timer.h + include/zwidget/core/widget.h + include/zwidget/core/theme.h + include/zwidget/core/utf8reader.h + include/zwidget/core/resourcedata.h + include/zwidget/widgets/lineedit/lineedit.h + include/zwidget/widgets/mainwindow/mainwindow.h + include/zwidget/widgets/menubar/menubar.h + include/zwidget/widgets/scrollbar/scrollbar.h + include/zwidget/widgets/statusbar/statusbar.h + include/zwidget/widgets/textedit/textedit.h + include/zwidget/widgets/toolbar/toolbar.h + include/zwidget/widgets/toolbar/toolbarbutton.h + include/zwidget/widgets/imagebox/imagebox.h + include/zwidget/widgets/textlabel/textlabel.h + include/zwidget/widgets/pushbutton/pushbutton.h + include/zwidget/widgets/checkboxlabel/checkboxlabel.h + include/zwidget/widgets/listview/listview.h + include/zwidget/widgets/tabwidget/tabwidget.h + include/zwidget/window/window.h + include/zwidget/window/x11nativehandle.h + include/zwidget/window/waylandnativehandle.h + include/zwidget/window/win32nativehandle.h + include/zwidget/window/sdl2nativehandle.h + include/zwidget/systemdialogs/open_folder_dialog.h + include/zwidget/systemdialogs/open_file_dialog.h + include/zwidget/systemdialogs/save_file_dialog.h +) + +set(ZWIDGET_WIN32_SOURCES + src/window/win32/win32_display_backend.cpp + src/window/win32/win32_display_backend.h + src/window/win32/win32_display_window.cpp + src/window/win32/win32_display_window.h + src/window/win32/win32_open_folder_dialog.cpp + src/window/win32/win32_open_folder_dialog.h + src/window/win32/win32_open_file_dialog.cpp + src/window/win32/win32_open_file_dialog.h + src/window/win32/win32_save_file_dialog.cpp + src/window/win32/win32_save_file_dialog.h + src/window/win32/win32_util.h +) + +set(ZWIDGET_DBUS_SOURCES + src/window/dbus/dbus_open_folder_dialog.cpp + src/window/dbus/dbus_open_folder_dialog.h + src/window/dbus/dbus_open_file_dialog.cpp + src/window/dbus/dbus_open_file_dialog.h + src/window/dbus/dbus_save_file_dialog.cpp + src/window/dbus/dbus_save_file_dialog.h +) + +set(ZWIDGET_COCOA_SOURCES +) + +set(ZWIDGET_SDL2_SOURCES + src/window/sdl2/sdl2_display_backend.cpp + src/window/sdl2/sdl2_display_backend.h + src/window/sdl2/sdl2_display_window.cpp + src/window/sdl2/sdl2_display_window.h +) + +set(ZWIDGET_X11_SOURCES + src/window/x11/x11_display_backend.cpp + src/window/x11/x11_display_backend.h + src/window/x11/x11_display_window.cpp + src/window/x11/x11_display_window.h +) + +set(ZWIDGET_WAYLAND_SOURCES + src/window/wayland/wayland_display_backend.cpp + src/window/wayland/wayland_display_backend.h + src/window/wayland/wayland_display_window.cpp + src/window/wayland/wayland_display_window.h + src/window/wayland/wl_fractional_scaling_protocol.cpp + src/window/wayland/wl_fractional_scaling_protocol.hpp +) + +source_group("src" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/.+") +source_group("src\\core" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/core/.+") +source_group("src\\core\\picopng" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/core/picopng/.+") +source_group("src\\core\\nanosvg" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/core/nanosvg/.+") +source_group("src\\widgets" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/.+") +source_group("src\\widgets\\lineedit" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/lineedit/.+") +source_group("src\\widgets\\mainwindow" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/mainwindow/.+") +source_group("src\\widgets\\menubar" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/menubar/.+") +source_group("src\\widgets\\scrollbar" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/scrollbar/.+") +source_group("src\\widgets\\statusbar" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/statusbar/.+") +source_group("src\\widgets\\textedit" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/textedit/.+") +source_group("src\\widgets\\toolbar" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/toolbar/.+") +source_group("src\\widgets\\imagebox" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/imagebox/.+") +source_group("src\\widgets\\textlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/textlabel/.+") +source_group("src\\widgets\\pushbutton" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/pushbutton/.+") +source_group("src\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/checkboxlabel/.+") +source_group("src\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/listview/.+") +source_group("src\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/tabwidget/.+") +source_group("src\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/.+") +source_group("src\\window\\stub" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/stub/.+") +source_group("src\\window\\win32" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/win32/.+") +source_group("src\\window\\sdl2" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/sdl2/.+") +source_group("src\\window\\x11" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/x11/.+") +source_group("src\\window\\wayland" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/wayland/.+") +source_group("src\\window\\dbus" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/dbus/.+") +source_group("src\\systemdialogs" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/systemdialogs/.+") +source_group("include" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/.+") +source_group("include\\core" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/core/.+") +source_group("include\\widgets" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/.+") +source_group("include\\widgets\\lineedit" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/lineedit/.+") +source_group("include\\widgets\\mainwindow" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/mainwindow/.+") +source_group("include\\widgets\\menubar" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/menubar/.+") +source_group("include\\widgets\\scrollbar" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/scrollbar/.+") +source_group("include\\widgets\\statusbar" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/statusbar/.+") +source_group("include\\widgets\\textedit" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/textedit/.+") +source_group("include\\widgets\\toolbar" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/toolbar/.+") +source_group("include\\widgets\\imagebox" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/imagebox/.+") +source_group("include\\widgets\\textlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/textlabel/.+") +source_group("include\\widgets\\pushbutton" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/pushbutton/.+") +source_group("include\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/checkboxlabel/.+") +source_group("include\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/listview/.+") +source_group("include\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/tabwidget/.+") +source_group("include\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/.+") +source_group("include\\systemdialogs" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/systemdialogs/.+") + +# Include directory to external projects using zwidget +include_directories(include) + +# Internal include dirs for building zwidget +set(ZWIDGET_INCLUDE_DIRS include/zwidget src) + +set(ZWIDGET_COMPILE_OPTIONS) + +if(WIN32) + set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_WIN32_SOURCES}) + set(ZWIDGET_DEFINES -DUNICODE -D_UNICODE) + set(ZWIDGET_LINK_OPTIONS) + if(MSVC) + # Use all cores for compilation + set(ZWIDGET_COMPILE_OPTIONS ${ZWIDGET_COMPILE_OPTIONS} /MP) + + # Ignore specific warnings + #set(ZWIDGET_COMPILE_OPTIONS ${ZWIDGET_COMPILE_OPTIONS} /wd4244 /wd4267 /wd4005 /wd4018) + + # Don't slow down std containers in debug builds + set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES}) + + # Ignore warning about legacy CRT functions + #set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES} -D_CRT_SECURE_NO_WARNINGS) + endif() +elseif(APPLE) + set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_COCOA_SOURCES}) + set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl) + 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_LINK_OPTIONS -pthread) + if (DBUS_FOUND) + set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_DBUS_SOURCES}) + set(ZWIDGET_INCLUDE_DIRS ${ZWIDGET_INCLUDE_DIRS} ${DBUS_INCLUDE_DIRS}) + set(ZWIDGET_LIBS ${ZWIDGET_LIBS} ${DBUS_LDFLAGS}) + set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES} -DUSE_DBUS) + endif() + if (WAYLANDPP_FOUND) + set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_WAYLAND_SOURCES}) + set(ZWIDGET_INCLUDE_DIRS ${ZWIDGET_INCLUDE_DIRS} ${WAYLANDPP_INCLUDE_DIRS}) + set(ZWIDGET_LIBS ${ZWIDGET_LIBS} ${WAYLANDPP_LDFLAGS}) + set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES} -DUSE_WAYLAND) + endif() +endif() + +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_compile_options(zwidget PRIVATE ${CXX_WARNING_FLAGS}) + +if(MSVC) + set_property(TARGET zwidget PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif() + +option(ZWIDGET_BUILD_EXAMPLE "Build the zwidget example application" ON) + +if(ZWIDGET_BUILD_EXAMPLE) + if (UNIX AND NOT APPLE) + find_package(SDL2 REQUIRED) + endif() + + 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} SDL2::SDL2) + endif() + + set_target_properties(zwidget_example PROPERTIES CXX_STANDARD 17) + + if(MSVC) + set_property(TARGET zwidget_example PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif() +endif() diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..fdc06221c --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# License information + +## License for ZWidget itself + + // Copyright (c) 2023 Magnus Norddahl + // + // This software is provided 'as-is', without any express or implied + // warranty. In no event will the authors be held liable for any damages + // arising from the use of this software. + // + // Permission is granted to anyone to use this software for any purpose, + // including commercial applications, and to alter it and redistribute it + // freely, subject to the following restrictions: + // + // 1. The origin of this software must not be misrepresented; you must not + // claim that you wrote the original software. If you use this software + // in a product, an acknowledgment in the product documentation would be + // appreciated but is not required. + // 2. Altered source versions must be plainly marked as such, and must not be + // misrepresented as being the original software. + // 3. This notice may not be removed or altered from any source distribution. diff --git a/README.md b/README.md new file mode 100644 index 000000000..c3bb8e219 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# ZWidget +A framework for building user interface applications diff --git a/example/OpenSans.ttf b/example/OpenSans.ttf new file mode 100644 index 0000000000000000000000000000000000000000..9db85693b027f3b05f6d77471d215f20707127c1 GIT binary patch literal 532636 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�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

+iC8meZ*`#f%ioWW9^Jop^~~Ph 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�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

pCf*|R^r{@wmbU*7ELd46y19Y6Kuw>>St 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%0AW4xA`sVEw)}^aZ8=q?_|K_wNv!9nEjTX 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�?fRK@b(i1XynZ;om*S|0A+EDSpT)PG%7 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 literal 0 HcmV?d00001 diff --git a/example/example.cpp b/example/example.cpp new file mode 100644 index 000000000..ec4eed91b --- /dev/null +++ b/example/example.cpp @@ -0,0 +1,283 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "picopng.h" + +static std::vector ReadAllBytes(const std::string& filename); + +class LauncherWindow : 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); + + 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"); + + try + { + auto filedata = ReadAllBytes("banner.png"); + std::vector 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, SelectLabel->GetPreferredHeight()); + y += SelectLabel->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; + TextLabel* WelcomeLabel = nullptr; + TextLabel* VersionLabel = nullptr; + TextLabel* SelectLabel = nullptr; + TextLabel* GeneralLabel = nullptr; + TextLabel* ExtrasLabel = nullptr; + CheckboxLabel* FullscreenCheckbox = nullptr; + CheckboxLabel* DisableAutoloadCheckbox = nullptr; + CheckboxLabel* DontAskAgainCheckbox = nullptr; + CheckboxLabel* LightsCheckbox = nullptr; + CheckboxLabel* BrightmapsCheckbox = nullptr; + CheckboxLabel* WidescreenCheckbox = nullptr; + PushButton* PlayButton = nullptr; + PushButton* ExitButton = nullptr; + ListView* GamesList = nullptr; +}; + +static std::vector ReadAllBytes(const std::string& filename); + +std::vector LoadWidgetFontData(const std::string& name) +{ + return { + {std::move(ReadAllBytes("OpenSans.ttf")), ""} + }; +} + +std::vector LoadWidgetData(const std::string& name) +{ + return ReadAllBytes(name); +} + +int example() +{ + WidgetTheme::SetTheme(std::make_unique()); + + // DisplayBackend::Set(DisplayBackend::TryCreateWin32()); + // DisplayBackend::Set(DisplayBackend::TryCreateSDL2()); + DisplayBackend::Set(DisplayBackend::TryCreateBackend()); + +#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; +} + +#ifdef WIN32 + +#include +#include + +#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") + +static std::wstring to_utf16(const std::string& str) +{ + if (str.empty()) return {}; + int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + std::wstring result; + result.resize(needed); + needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size()); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + return result; +} + +static std::vector ReadAllBytes(const std::string& filename) +{ + HANDLE handle = CreateFile(to_utf16(filename).c_str(), FILE_READ_ACCESS, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + if (handle == INVALID_HANDLE_VALUE) + throw std::runtime_error("Could not open " + filename); + + LARGE_INTEGER fileSize; + BOOL result = GetFileSizeEx(handle, &fileSize); + if (result == FALSE) + { + CloseHandle(handle); + throw std::runtime_error("GetFileSizeEx failed"); + } + + std::vector buffer(fileSize.QuadPart); + + DWORD bytesRead = 0; + result = ReadFile(handle, buffer.data(), (DWORD)buffer.size(), &bytesRead, nullptr); + if (result == FALSE || bytesRead != buffer.size()) + { + CloseHandle(handle); + throw std::runtime_error("ReadFile failed"); + } + + CloseHandle(handle); + + return buffer; +} + + +int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, PSTR cmdline, int cmdshow) +{ + example(); +} + +#else + +#include +#include +#include +#include + +static std::vector ReadAllBytes(const std::string& filename) +{ + std::ifstream file(filename, std::ios::binary | std::ios::ate); + if (!file) + throw std::runtime_error("ReadFile failed"); + + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + std::vector buffer(size); + if (!file.read(reinterpret_cast(buffer.data()), size)) + throw std::runtime_error("ReadFile failed "); + + return buffer; +} + +int main() +{ + example(); +} + +#endif diff --git a/example/picopng.cpp b/example/picopng.cpp new file mode 100644 index 000000000..c2943eb14 --- /dev/null +++ b/example/picopng.cpp @@ -0,0 +1,593 @@ +#include + +/* +decodePNG: The picoPNG function, decodes a PNG file buffer in memory, into a raw pixel buffer. +out_image: output parameter, this will contain the raw pixels after decoding. + By default the output is 32-bit RGBA color. + The std::vector is automatically resized to the correct size. +image_width: output_parameter, this will contain the width of the image in pixels. +image_height: output_parameter, this will contain the height of the image in pixels. +in_png: pointer to the buffer of the PNG file in memory. To get it from a file on + disk, load it and store it in a memory buffer yourself first. +in_size: size of the input PNG file in bytes. +convert_to_rgba32: optional parameter, true by default. + Set to true to get the output in RGBA 32-bit (8 bit per channel) color format + no matter what color type the original PNG image had. This gives predictable, + useable data from any random input PNG. + Set to false to do no color conversion at all. The result then has the same data + type as the PNG image, which can range from 1 bit to 64 bits per pixel. + Information about the color type or palette colors are not provided. You need + to know this information yourself to be able to use the data so this only + works for trusted PNG files. Use LodePNG instead of picoPNG if you need this information. +return: 0 if success, not 0 if some error occured. +*/ +int decodePNG(std::vector& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32 = true) +{ + // picoPNG version 20101224 + // Copyright (c) 2005-2010 Lode Vandevenne + // + // This software is provided 'as-is', without any express or implied + // warranty. In no event will the authors be held liable for any damages + // arising from the use of this software. + // + // Permission is granted to anyone to use this software for any purpose, + // including commercial applications, and to alter it and redistribute it + // freely, subject to the following restrictions: + // + // 1. The origin of this software must not be misrepresented; you must not + // claim that you wrote the original software. If you use this software + // in a product, an acknowledgment in the product documentation would be + // appreciated but is not required. + // 2. Altered source versions must be plainly marked as such, and must not be + // misrepresented as being the original software. + // 3. This notice may not be removed or altered from any source distribution. + + // picoPNG is a PNG decoder in one C++ function of around 500 lines. Use picoPNG for + // programs that need only 1 .cpp file. Since it's a single function, it's very limited, + // it can convert a PNG to raw pixel data either converted to 32-bit RGBA color or + // with no color conversion at all. For anything more complex, another tiny library + // is available: LodePNG (lodepng.c(pp)), which is a single source and header file. + // Apologies for the compact code style, it's to make this tiny. + + static const unsigned long LENBASE[29] = {3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258}; + static const unsigned long LENEXTRA[29] = {0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0}; + static const unsigned long DISTBASE[30] = {1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577}; + static const unsigned long DISTEXTRA[30] = {0,0,0,0,1,1,2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + static const unsigned long CLCL[19] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; //code length code lengths + struct Zlib //nested functions for zlib decompression + { + static unsigned long readBitFromStream(size_t& bitp, const unsigned char* bits) { unsigned long result = (bits[bitp >> 3] >> (bitp & 0x7)) & 1; bitp++; return result;} + static unsigned long readBitsFromStream(size_t& bitp, const unsigned char* bits, size_t nbits) + { + unsigned long result = 0; + for(size_t i = 0; i < nbits; i++) result += (readBitFromStream(bitp, bits)) << i; + return result; + } + struct HuffmanTree + { + int makeFromLengths(const std::vector& bitlen, unsigned long maxbitlen) + { //make tree given the lengths + unsigned long numcodes = (unsigned long)(bitlen.size()), treepos = 0, nodefilled = 0; + std::vector tree1d(numcodes), blcount(maxbitlen + 1, 0), nextcode(maxbitlen + 1, 0); + for(unsigned long bits = 0; bits < numcodes; bits++) blcount[bitlen[bits]]++; //count number of instances of each code length + for(unsigned long bits = 1; bits <= maxbitlen; bits++) nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1; + for(unsigned long n = 0; n < numcodes; n++) if(bitlen[n] != 0) tree1d[n] = nextcode[bitlen[n]]++; //generate all the codes + tree2d.clear(); tree2d.resize(numcodes * 2, 32767); //32767 here means the tree2d isn't filled there yet + for(unsigned long n = 0; n < numcodes; n++) //the codes + for(unsigned long i = 0; i < bitlen[n]; i++) //the bits for this code + { + unsigned long bit = (tree1d[n] >> (bitlen[n] - i - 1)) & 1; + if(treepos > numcodes - 2) return 55; + if(tree2d[2 * treepos + bit] == 32767) //not yet filled in + { + if(i + 1 == bitlen[n]) { tree2d[2 * treepos + bit] = n; treepos = 0; } //last bit + else { tree2d[2 * treepos + bit] = ++nodefilled + numcodes; treepos = nodefilled; } //addresses are encoded as values > numcodes + } + else treepos = tree2d[2 * treepos + bit] - numcodes; //subtract numcodes from address to get address value + } + return 0; + } + int decode(bool& decoded, unsigned long& result, size_t& treepos, unsigned long bit) const + { //Decodes a symbol from the tree + unsigned long numcodes = (unsigned long)tree2d.size() / 2; + if(treepos >= numcodes) return 11; //error: you appeared outside the codetree + result = tree2d[2 * treepos + bit]; + decoded = (result < numcodes); + treepos = decoded ? 0 : result - numcodes; + return 0; + } + std::vector tree2d; //2D representation of a huffman tree: The one dimension is "0" or "1", the other contains all nodes and leaves of the tree. + }; + struct Inflator + { + int error; + void inflate(std::vector& out, const std::vector& in, size_t inpos = 0) + { + size_t bp = 0, pos = 0; //bit pointer and byte pointer + error = 0; + unsigned long BFINAL = 0; + while(!BFINAL && !error) + { + if(bp >> 3 >= in.size()) { error = 52; return; } //error, bit pointer will jump past memory + BFINAL = readBitFromStream(bp, &in[inpos]); + unsigned long BTYPE = readBitFromStream(bp, &in[inpos]); BTYPE += 2 * readBitFromStream(bp, &in[inpos]); + if(BTYPE == 3) { error = 20; return; } //error: invalid BTYPE + else if(BTYPE == 0) inflateNoCompression(out, &in[inpos], bp, pos, in.size()); + else inflateHuffmanBlock(out, &in[inpos], bp, pos, in.size(), BTYPE); + } + if(!error) out.resize(pos); //Only now we know the true size of out, resize it to that + } + void generateFixedTrees(HuffmanTree& tree, HuffmanTree& treeD) //get the tree of a deflated block with fixed tree + { + std::vector bitlen(288, 8), bitlenD(32, 5);; + for(size_t i = 144; i <= 255; i++) bitlen[i] = 9; + for(size_t i = 256; i <= 279; i++) bitlen[i] = 7; + tree.makeFromLengths(bitlen, 15); + treeD.makeFromLengths(bitlenD, 15); + } + HuffmanTree codetree, codetreeD, codelengthcodetree; //the code tree for Huffman codes, dist codes, and code length codes + unsigned long huffmanDecodeSymbol(const unsigned char* in, size_t& bp, const HuffmanTree& codetree, size_t inlength) + { //decode a single symbol from given list of bits with given code tree. return value is the symbol + bool decoded; unsigned long ct; + for(size_t treepos = 0;;) + { + if((bp & 0x07) == 0 && (bp >> 3) > inlength) { error = 10; return 0; } //error: end reached without endcode + error = codetree.decode(decoded, ct, treepos, readBitFromStream(bp, in)); if(error) return 0; //stop, an error happened + if(decoded) return ct; + } + } + void getTreeInflateDynamic(HuffmanTree& tree, HuffmanTree& treeD, const unsigned char* in, size_t& bp, size_t inlength) + { //get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree + std::vector bitlen(288, 0), bitlenD(32, 0); + if(bp >> 3 >= inlength - 2) { error = 49; return; } //the bit pointer is or will go past the memory + size_t HLIT = readBitsFromStream(bp, in, 5) + 257; //number of literal/length codes + 257 + size_t HDIST = readBitsFromStream(bp, in, 5) + 1; //number of dist codes + 1 + size_t HCLEN = readBitsFromStream(bp, in, 4) + 4; //number of code length codes + 4 + std::vector codelengthcode(19); //lengths of tree to decode the lengths of the dynamic tree + for(size_t i = 0; i < 19; i++) codelengthcode[CLCL[i]] = (i < HCLEN) ? readBitsFromStream(bp, in, 3) : 0; + error = codelengthcodetree.makeFromLengths(codelengthcode, 7); if(error) return; + size_t i = 0, replength; + while(i < HLIT + HDIST) + { + unsigned long code = huffmanDecodeSymbol(in, bp, codelengthcodetree, inlength); if(error) return; + if(code <= 15) { if(i < HLIT) bitlen[i++] = code; else bitlenD[i++ - HLIT] = code; } //a length code + else if(code == 16) //repeat previous + { + if(bp >> 3 >= inlength) { error = 50; return; } //error, bit pointer jumps past memory + replength = 3 + readBitsFromStream(bp, in, 2); + unsigned long value; //set value to the previous code + if((i - 1) < HLIT) value = bitlen[i - 1]; + else value = bitlenD[i - HLIT - 1]; + for(size_t n = 0; n < replength; n++) //repeat this value in the next lengths + { + if(i >= HLIT + HDIST) { error = 13; return; } //error: i is larger than the amount of codes + if(i < HLIT) bitlen[i++] = value; else bitlenD[i++ - HLIT] = value; + } + } + else if(code == 17) //repeat "0" 3-10 times + { + if(bp >> 3 >= inlength) { error = 50; return; } //error, bit pointer jumps past memory + replength = 3 + readBitsFromStream(bp, in, 3); + for(size_t n = 0; n < replength; n++) //repeat this value in the next lengths + { + if(i >= HLIT + HDIST) { error = 14; return; } //error: i is larger than the amount of codes + if(i < HLIT) bitlen[i++] = 0; else bitlenD[i++ - HLIT] = 0; + } + } + else if(code == 18) //repeat "0" 11-138 times + { + if(bp >> 3 >= inlength) { error = 50; return; } //error, bit pointer jumps past memory + replength = 11 + readBitsFromStream(bp, in, 7); + for(size_t n = 0; n < replength; n++) //repeat this value in the next lengths + { + if(i >= HLIT + HDIST) { error = 15; return; } //error: i is larger than the amount of codes + if(i < HLIT) bitlen[i++] = 0; else bitlenD[i++ - HLIT] = 0; + } + } + else { error = 16; return; } //error: somehow an unexisting code appeared. This can never happen. + } + if(bitlen[256] == 0) { error = 64; return; } //the length of the end code 256 must be larger than 0 + error = tree.makeFromLengths(bitlen, 15); if(error) return; //now we've finally got HLIT and HDIST, so generate the code trees, and the function is done + error = treeD.makeFromLengths(bitlenD, 15); if(error) return; + } + void inflateHuffmanBlock(std::vector& out, const unsigned char* in, size_t& bp, size_t& pos, size_t inlength, unsigned long btype) + { + if(btype == 1) { generateFixedTrees(codetree, codetreeD); } + else if(btype == 2) { getTreeInflateDynamic(codetree, codetreeD, in, bp, inlength); if(error) return; } + for(;;) + { + unsigned long code = huffmanDecodeSymbol(in, bp, codetree, inlength); if(error) return; + if(code == 256) return; //end code + else if(code <= 255) //literal symbol + { + if(pos >= out.size()) out.resize((pos + 1) * 2); //reserve more room + out[pos++] = (unsigned char)(code); + } + else if(code >= 257 && code <= 285) //length code + { + size_t length = LENBASE[code - 257], numextrabits = LENEXTRA[code - 257]; + if((bp >> 3) >= inlength) { error = 51; return; } //error, bit pointer will jump past memory + length += readBitsFromStream(bp, in, numextrabits); + unsigned long codeD = huffmanDecodeSymbol(in, bp, codetreeD, inlength); if(error) return; + if(codeD > 29) { error = 18; return; } //error: invalid dist code (30-31 are never used) + unsigned long dist = DISTBASE[codeD], numextrabitsD = DISTEXTRA[codeD]; + if((bp >> 3) >= inlength) { error = 51; return; } //error, bit pointer will jump past memory + dist += readBitsFromStream(bp, in, numextrabitsD); + size_t start = pos, back = start - dist; //backwards + if(pos + length >= out.size()) out.resize((pos + length) * 2); //reserve more room + for(size_t i = 0; i < length; i++) { out[pos++] = out[back++]; if(back >= start) back = start - dist; } + } + } + } + void inflateNoCompression(std::vector& out, const unsigned char* in, size_t& bp, size_t& pos, size_t inlength) + { + while((bp & 0x7) != 0) bp++; //go to first boundary of byte + size_t p = bp / 8; + if(p >= inlength - 4) { error = 52; return; } //error, bit pointer will jump past memory + unsigned long LEN = in[p] + 256 * in[p + 1], NLEN = in[p + 2] + 256 * in[p + 3]; p += 4; + if(LEN + NLEN != 65535) { error = 21; return; } //error: NLEN is not one's complement of LEN + if(pos + LEN >= out.size()) out.resize(pos + LEN); + if(p + LEN > inlength) { error = 23; return; } //error: reading outside of in buffer + for(unsigned long n = 0; n < LEN; n++) out[pos++] = in[p++]; //read LEN bytes of literal data + bp = p * 8; + } + }; + int decompress(std::vector& out, const std::vector& in) //returns error value + { + Inflator inflator; + if(in.size() < 2) { return 53; } //error, size of zlib data too small + if((in[0] * 256 + in[1]) % 31 != 0) { return 24; } //error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way + unsigned long CM = in[0] & 15, CINFO = (in[0] >> 4) & 15, FDICT = (in[1] >> 5) & 1; + if(CM != 8 || CINFO > 7) { return 25; } //error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec + if(FDICT != 0) { return 26; } //error: the specification of PNG says about the zlib stream: "The additional flags shall not specify a preset dictionary." + inflator.inflate(out, in, 2); + return inflator.error; //note: adler32 checksum was skipped and ignored + } + }; + struct PNG //nested functions for PNG decoding + { + struct Info + { + unsigned long width, height, colorType, bitDepth, compressionMethod, filterMethod, interlaceMethod, key_r, key_g, key_b; + bool key_defined; //is a transparent color key given? + std::vector palette; + } info; + int error; + void decode(std::vector& out, const unsigned char* in, size_t size, bool convert_to_rgba32) + { + error = 0; + if(size == 0 || in == 0) { error = 48; return; } //the given data is empty + readPngHeader(&in[0], size); if(error) return; + size_t pos = 33; //first byte of the first chunk after the header + std::vector idat; //the data from idat chunks + 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 + { + if(pos + 8 >= size) { error = 30; return; } //error: size of the in buffer too small to contain next chunk + size_t chunkLength = read32bitInt(&in[pos]); pos += 4; + if(chunkLength > 2147483647) { error = 63; return; } + if(pos + chunkLength >= size) { error = 35; return; } //error: size of the in buffer too small to contain next chunk + if(in[pos + 0] == 'I' && in[pos + 1] == 'D' && in[pos + 2] == 'A' && in[pos + 3] == 'T') //IDAT chunk, containing compressed image data + { + idat.insert(idat.end(), &in[pos + 4], &in[pos + 4 + chunkLength]); + pos += (4 + chunkLength); + } + else if(in[pos + 0] == 'I' && in[pos + 1] == 'E' && in[pos + 2] == 'N' && in[pos + 3] == 'D') { pos += 4; IEND = true; } + else if(in[pos + 0] == 'P' && in[pos + 1] == 'L' && in[pos + 2] == 'T' && in[pos + 3] == 'E') //palette chunk (PLTE) + { + pos += 4; //go after the 4 letters + info.palette.resize(4 * (chunkLength / 3)); + if(info.palette.size() > (4 * 256)) { error = 38; return; } //error: palette too big + for(size_t i = 0; i < info.palette.size(); i += 4) + { + for(size_t j = 0; j < 3; j++) info.palette[i + j] = in[pos++]; //RGB + info.palette[i + 3] = 255; //alpha + } + } + else if(in[pos + 0] == 't' && in[pos + 1] == 'R' && in[pos + 2] == 'N' && in[pos + 3] == 'S') //palette transparency chunk (tRNS) + { + pos += 4; //go after the 4 letters + if(info.colorType == 3) + { + if(4 * chunkLength > info.palette.size()) { error = 39; return; } //error: more alpha values given than there are palette entries + for(size_t i = 0; i < chunkLength; i++) info.palette[4 * i + 3] = in[pos++]; + } + else if(info.colorType == 0) + { + if(chunkLength != 2) { error = 40; return; } //error: this chunk must be 2 bytes for greyscale image + info.key_defined = 1; info.key_r = info.key_g = info.key_b = 256 * in[pos] + in[pos + 1]; pos += 2; + } + else if(info.colorType == 2) + { + if(chunkLength != 6) { error = 41; return; } //error: this chunk must be 6 bytes for RGB image + info.key_defined = 1; + info.key_r = 256 * in[pos] + in[pos + 1]; pos += 2; + info.key_g = 256 * in[pos] + in[pos + 1]; pos += 2; + info.key_b = 256 * in[pos] + in[pos + 1]; pos += 2; + } + else { error = 42; return; } //error: tRNS chunk not allowed for other color models + } + else //it's not an implemented chunk type, so ignore it: skip over the data + { + 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; + } + pos += 4; //step over CRC (which is ignored) + } + unsigned long bpp = getBpp(info); + std::vector scanlines(((info.width * (info.height * bpp + 7)) / 8) + info.height); //now the out buffer will be filled + Zlib zlib; //decompress with the Zlib decompressor + error = zlib.decompress(scanlines, idat); if(error) return; //stop if the zlib decompressor returned an error + size_t bytewidth = (bpp + 7) / 8, outlength = (info.height * info.width * bpp + 7) / 8; + out.resize(outlength); //time to fill the out buffer + unsigned char* out_ = outlength ? &out[0] : 0; //use a regular pointer to the std::vector for faster code if compiled without optimization + if(info.interlaceMethod == 0) //no interlace, just filter + { + size_t linestart = 0, linelength = (info.width * bpp + 7) / 8; //length in bytes of a scanline, excluding the filtertype byte + if(bpp >= 8) //byte per byte + for(unsigned long y = 0; y < info.height; y++) + { + unsigned long filterType = scanlines[linestart]; + const unsigned char* prevline = (y == 0) ? 0 : &out_[(y - 1) * info.width * bytewidth]; + unFilterScanline(&out_[linestart - y], &scanlines[linestart + 1], prevline, bytewidth, filterType, linelength); if(error) return; + linestart += (1 + linelength); //go to start of next scanline + } + else //less than 8 bits per pixel, so fill it up bit per bit + { + std::vector templine((info.width * bpp + 7) >> 3); //only used if bpp < 8 + for(size_t y = 0, obp = 0; y < info.height; y++) + { + unsigned long filterType = scanlines[linestart]; + const unsigned char* prevline = (y == 0) ? 0 : &out_[(y - 1) * info.width * bytewidth]; + unFilterScanline(&templine[0], &scanlines[linestart + 1], prevline, bytewidth, filterType, linelength); if(error) return; + for(size_t bp = 0; bp < info.width * bpp;) setBitOfReversedStream(obp, out_, readBitFromReversedStream(bp, &templine[0])); + linestart += (1 + linelength); //go to start of next scanline + } + } + } + else //interlaceMethod is 1 (Adam7) + { + size_t passw[7] = { (info.width + 7) / 8, (info.width + 3) / 8, (info.width + 3) / 4, (info.width + 1) / 4, (info.width + 1) / 2, (info.width + 0) / 2, (info.width + 0) / 1 }; + size_t passh[7] = { (info.height + 7) / 8, (info.height + 7) / 8, (info.height + 3) / 8, (info.height + 3) / 4, (info.height + 1) / 4, (info.height + 1) / 2, (info.height + 0) / 2 }; + size_t passstart[7] = {0}; + size_t pattern[28] = {0,4,0,2,0,1,0,0,0,4,0,2,0,1,8,8,4,4,2,2,1,8,8,8,4,4,2,2}; //values for the adam7 passes + for(int i = 0; i < 6; i++) passstart[i + 1] = passstart[i] + passh[i] * ((passw[i] ? 1 : 0) + (passw[i] * bpp + 7) / 8); + std::vector scanlineo((info.width * bpp + 7) / 8), scanlinen((info.width * bpp + 7) / 8); //"old" and "new" scanline + for(int i = 0; i < 7; i++) + adam7Pass(&out_[0], &scanlinen[0], &scanlineo[0], &scanlines[passstart[i]], info.width, pattern[i], pattern[i + 7], pattern[i + 14], pattern[i + 21], passw[i], passh[i], bpp); + } + if(convert_to_rgba32 && (info.colorType != 6 || info.bitDepth != 8)) //conversion needed + { + std::vector data = out; + error = convert(out, &data[0], info, info.width, info.height); + } + } + void readPngHeader(const unsigned char* in, size_t inlength) //read the information from the header and store it in the Info + { + if(inlength < 29) { error = 27; return; } //error: the data length is smaller than the length of the header + if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { error = 28; return; } //no PNG signature + if(in[12] != 'I' || in[13] != 'H' || in[14] != 'D' || in[15] != 'R') { error = 29; return; } //error: it doesn't start with a IHDR chunk! + info.width = read32bitInt(&in[16]); info.height = read32bitInt(&in[20]); + info.bitDepth = in[24]; info.colorType = in[25]; + info.compressionMethod = in[26]; if(in[26] != 0) { error = 32; return; } //error: only compression method 0 is allowed in the specification + info.filterMethod = in[27]; if(in[27] != 0) { error = 33; return; } //error: only filter method 0 is allowed in the specification + info.interlaceMethod = in[28]; if(in[28] > 1) { error = 34; return; } //error: only interlace methods 0 and 1 exist in the specification + error = checkColorValidity(info.colorType, info.bitDepth); + } + void unFilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, size_t bytewidth, unsigned long filterType, size_t length) + { + switch(filterType) + { + case 0: for(size_t i = 0; i < length; i++) recon[i] = scanline[i]; break; + case 1: + for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i]; + for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth]; + break; + case 2: + if(precon) for(size_t i = 0; i < length; i++) recon[i] = scanline[i] + precon[i]; + else for(size_t i = 0; i < length; i++) recon[i] = scanline[i]; + break; + case 3: + if(precon) + { + for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i] + precon[i] / 2; + for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) / 2); + } + else + { + for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i]; + for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth] / 2; + } + break; + case 4: + if(precon) + { + for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i] + paethPredictor(0, precon[i], 0); + for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth]); + } + else + { + for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i]; + for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + paethPredictor(recon[i - bytewidth], 0, 0); + } + break; + default: error = 36; return; //error: unexisting filter type given + } + } + void adam7Pass(unsigned char* out, unsigned char* linen, unsigned char* lineo, const unsigned char* in, unsigned long w, size_t passleft, size_t passtop, size_t spacex, size_t spacey, size_t passw, size_t passh, unsigned long bpp) + { //filter and reposition the pixels into the output when the image is Adam7 interlaced. This function can only do it after the full image is already decoded. The out buffer must have the correct allocated memory size already. + if(passw == 0) return; + size_t bytewidth = (bpp + 7) / 8, linelength = 1 + ((bpp * passw + 7) / 8); + for(unsigned long y = 0; y < passh; y++) + { + unsigned char filterType = in[y * linelength], *prevline = (y == 0) ? 0 : lineo; + unFilterScanline(linen, &in[y * linelength + 1], prevline, bytewidth, filterType, (w * bpp + 7) / 8); if(error) return; + if(bpp >= 8) for(size_t i = 0; i < passw; i++) for(size_t b = 0; b < bytewidth; b++) //b = current byte of this pixel + out[bytewidth * w * (passtop + spacey * y) + bytewidth * (passleft + spacex * i) + b] = linen[bytewidth * i + b]; + else for(size_t i = 0; i < passw; i++) + { + size_t obp = bpp * w * (passtop + spacey * y) + bpp * (passleft + spacex * i), bp = i * bpp; + for(size_t b = 0; b < bpp; b++) setBitOfReversedStream(obp, out, readBitFromReversedStream(bp, &linen[0])); + } + unsigned char* temp = linen; linen = lineo; lineo = temp; //swap the two buffer pointers "line old" and "line new" + } + } + static unsigned long readBitFromReversedStream(size_t& bitp, const unsigned char* bits) { unsigned long result = (bits[bitp >> 3] >> (7 - (bitp & 0x7))) & 1; bitp++; return result;} + static unsigned long readBitsFromReversedStream(size_t& bitp, const unsigned char* bits, unsigned long nbits) + { + unsigned long result = 0; + for(size_t i = nbits - 1; i < nbits; i--) result += ((readBitFromReversedStream(bitp, bits)) << i); + return result; + } + void setBitOfReversedStream(size_t& bitp, unsigned char* bits, unsigned long bit) { bits[bitp >> 3] |= (bit << (7 - (bitp & 0x7))); bitp++; } + unsigned long read32bitInt(const unsigned char* buffer) { return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; } + int checkColorValidity(unsigned long colorType, unsigned long bd) //return type is a LodePNG error code + { + if((colorType == 2 || colorType == 4 || colorType == 6)) { if(!(bd == 8 || bd == 16)) return 37; else return 0; } + else if(colorType == 0) { if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; else return 0; } + else if(colorType == 3) { if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; else return 0; } + else return 31; //unexisting color type + } + unsigned long getBpp(const Info& info) + { + if(info.colorType == 2) return (3 * info.bitDepth); + else if(info.colorType >= 4) return (info.colorType - 2) * info.bitDepth; + else return info.bitDepth; + } + int convert(std::vector& out, const unsigned char* in, Info& infoIn, unsigned long w, unsigned long h) + { //converts from any color type to 32-bit. return value = LodePNG error code + size_t numpixels = w * h, bp = 0; + out.resize(numpixels * 4); + unsigned char* out_ = out.empty() ? 0 : &out[0]; //faster if compiled without optimization + if(infoIn.bitDepth == 8 && infoIn.colorType == 0) //greyscale + for(size_t i = 0; i < numpixels; i++) + { + out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[i]; + out_[4 * i + 3] = (infoIn.key_defined && in[i] == infoIn.key_r) ? 0 : 255; + } + else if(infoIn.bitDepth == 8 && infoIn.colorType == 2) //RGB color + for(size_t i = 0; i < numpixels; i++) + { + for(size_t c = 0; c < 3; c++) out_[4 * i + c] = in[3 * i + c]; + out_[4 * i + 3] = (infoIn.key_defined == 1 && in[3 * i + 0] == infoIn.key_r && in[3 * i + 1] == infoIn.key_g && in[3 * i + 2] == infoIn.key_b) ? 0 : 255; + } + else if(infoIn.bitDepth == 8 && infoIn.colorType == 3) //indexed color (palette) + for(size_t i = 0; i < numpixels; i++) + { + if(4U * in[i] >= infoIn.palette.size()) return 46; + for(size_t c = 0; c < 4; c++) out_[4 * i + c] = infoIn.palette[4 * in[i] + c]; //get rgb colors from the palette + } + else if(infoIn.bitDepth == 8 && infoIn.colorType == 4) //greyscale with alpha + for(size_t i = 0; i < numpixels; i++) + { + out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[2 * i + 0]; + out_[4 * i + 3] = in[2 * i + 1]; + } + else if(infoIn.bitDepth == 8 && infoIn.colorType == 6) for(size_t i = 0; i < numpixels; i++) for(size_t c = 0; c < 4; c++) out_[4 * i + c] = in[4 * i + c]; //RGB with alpha + else if(infoIn.bitDepth == 16 && infoIn.colorType == 0) //greyscale + for(size_t i = 0; i < numpixels; i++) + { + out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[2 * i]; + out_[4 * i + 3] = (infoIn.key_defined && 256U * in[i] + in[i + 1] == infoIn.key_r) ? 0 : 255; + } + else if(infoIn.bitDepth == 16 && infoIn.colorType == 2) //RGB color + for(size_t i = 0; i < numpixels; i++) + { + for(size_t c = 0; c < 3; c++) out_[4 * i + c] = in[6 * i + 2 * c]; + out_[4 * i + 3] = (infoIn.key_defined && 256U*in[6*i+0]+in[6*i+1] == infoIn.key_r && 256U*in[6*i+2]+in[6*i+3] == infoIn.key_g && 256U*in[6*i+4]+in[6*i+5] == infoIn.key_b) ? 0 : 255; + } + else if(infoIn.bitDepth == 16 && infoIn.colorType == 4) //greyscale with alpha + for(size_t i = 0; i < numpixels; i++) + { + out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[4 * i]; //most significant byte + out_[4 * i + 3] = in[4 * i + 2]; + } + else if(infoIn.bitDepth == 16 && infoIn.colorType == 6) for(size_t i = 0; i < numpixels; i++) for(size_t c = 0; c < 4; c++) out_[4 * i + c] = in[8 * i + 2 * c]; //RGB with alpha + else if(infoIn.bitDepth < 8 && infoIn.colorType == 0) //greyscale + for(size_t i = 0; i < numpixels; i++) + { + unsigned long value = (readBitsFromReversedStream(bp, in, infoIn.bitDepth) * 255) / ((1 << infoIn.bitDepth) - 1); //scale value from 0 to 255 + out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = (unsigned char)(value); + out_[4 * i + 3] = (infoIn.key_defined && value && ((1U << infoIn.bitDepth) - 1U) == infoIn.key_r && ((1U << infoIn.bitDepth) - 1U)) ? 0 : 255; + } + else if(infoIn.bitDepth < 8 && infoIn.colorType == 3) //palette + for(size_t i = 0; i < numpixels; i++) + { + unsigned long value = readBitsFromReversedStream(bp, in, infoIn.bitDepth); + if(4 * value >= infoIn.palette.size()) return 47; + for(size_t c = 0; c < 4; c++) out_[4 * i + c] = infoIn.palette[4 * value + c]; //get rgb colors from the palette + } + return 0; + } + unsigned char paethPredictor(short a, short b, short c) //Paeth predicter, used by PNG filter type 4 + { + short p = a + b - c, pa = p > a ? (p - a) : (a - p), pb = p > b ? (p - b) : (b - p), pc = p > c ? (p - c) : (c - p); + return (unsigned char)((pa <= pb && pa <= pc) ? a : pb <= pc ? b : c); + } + }; + PNG decoder; decoder.decode(out_image, in_png, in_size, convert_to_rgba32); + image_width = decoder.info.width; image_height = decoder.info.height; + return decoder.error; +} + +#if 0 + + + +//an example using the PNG loading function: + +#include +#include + +void loadFile(std::vector& buffer, const std::string& filename) //designed for loading files from hard disk in an std::vector +{ + std::ifstream file(filename.c_str(), std::ios::in|std::ios::binary|std::ios::ate); + + //get filesize + std::streamsize size = 0; + if(file.seekg(0, std::ios::end).good()) size = file.tellg(); + if(file.seekg(0, std::ios::beg).good()) size -= file.tellg(); + + //read contents of the file into the vector + if(size > 0) + { + buffer.resize((size_t)size); + file.read((char*)(&buffer[0]), size); + } + else buffer.clear(); +} + +int main(int argc, char *argv[]) +{ + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector buffer, image; + loadFile(buffer, filename); + unsigned long w, h; + int error = decodePNG(image, w, h, buffer.empty() ? 0 : &buffer[0], (unsigned long)buffer.size()); + + //if there's an error, display it + if(error != 0) std::cout << "error: " << error << std::endl; + + //the pixels are now in the vector "image", use it as texture, draw it, ... + + if(image.size() > 4) std::cout << "width: " << w << " height: " << h << " first pixel: " << std::hex << int(image[0]) << int(image[1]) << int(image[2]) << int(image[3]) << std::endl; +} + +/* + //this is test code, it displays the pixels of a 1 bit PNG. To use it, set the flag convert_to_rgba32 to false and load a 1-bit PNG image with a small size (so that its ASCII representation can fit in a console window) + for(int y = 0; y < h; y++) + { + for(int x = 0; x < w; x++) + { + int i = y * h + x; + std::cout << (((image[i/8] >> (7-i%8)) & 1) ? '.' : '#'); + } + std::cout << std::endl; + } +*/ + +#endif diff --git a/example/picopng.h b/example/picopng.h new file mode 100644 index 000000000..87dc961be --- /dev/null +++ b/example/picopng.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +int decodePNG(std::vector& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32 = true); diff --git a/include/zwidget/core/canvas.h b/include/zwidget/core/canvas.h new file mode 100644 index 000000000..c3ae7fea8 --- /dev/null +++ b/include/zwidget/core/canvas.h @@ -0,0 +1,116 @@ +#pragma once + +#include +#include +#include +#include "image.h" +#include "rect.h" +#include + +class Font; +class Point; +class Rect; +class Colorf; +class DisplayWindow; +class CanvasFontGroup; + +class CanvasTexture +{ +public: + virtual ~CanvasTexture() = default; + + int Width = 0; + int Height = 0; +}; + +class FontMetrics +{ +public: + double ascent = 0.0; + double descent = 0.0; + double external_leading = 0.0; + double height = 0.0; +}; + +struct VerticalTextPosition +{ + double top = 0.0; + double baseline = 0.0; + double bottom = 0.0; +}; + +class Canvas +{ +public: + static std::unique_ptr create(); + + Canvas(); + virtual ~Canvas(); + + virtual void attach(DisplayWindow* window); + virtual void detach(); + + virtual void begin(const Colorf& color); + virtual void end() { } + + virtual void begin3d() { } + virtual void end3d() { } + + Point getOrigin(); + void setOrigin(const Point& origin); + + void pushClip(const Rect& box); + void popClip(); + + void fillRect(const Rect& box, const Colorf& color); + void line(const Point& p0, const Point& p1, const Colorf& color); + + void drawText(const Point& pos, const Colorf& color, const std::string& text); + Rect measureText(const std::string& text); + VerticalTextPosition verticalTextAlign(); + + void drawText(const std::shared_ptr& font, const Point& pos, const std::string& text, const Colorf& color); + void drawTextEllipsis(const std::shared_ptr& font, const Point& pos, const Rect& clipBox, const std::string& text, const Colorf& color); + Rect measureText(const std::shared_ptr& font, const std::string& text); + FontMetrics getFontMetrics(const std::shared_ptr& font); + int getCharacterIndex(const std::shared_ptr& font, const std::string& text, const Point& hitPoint); + + void drawImage(const std::shared_ptr& image, const Point& pos); + void drawImage(const std::shared_ptr& image, const Rect& box); + +protected: + virtual std::unique_ptr createTexture(int width, int height, const void* pixels, ImageFormat format = ImageFormat::B8G8R8A8) = 0; + virtual void drawLineAntialiased(float x0, float y0, float x1, float y1, Colorf color) = 0; + virtual void fillTile(float x, float y, float width, float height, Colorf color) = 0; + virtual void drawTile(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) = 0; + virtual void drawGlyph(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) = 0; + + int getClipMinX() const; + int getClipMinY() const; + int getClipMaxX() const; + int getClipMaxY() const; + + template + static T clamp(T val, T minval, T maxval) { return std::max(std::min(val, maxval), minval); } + + DisplayWindow* window = nullptr; + int width = 0; + int height = 0; + double uiscale = 1.0f; + + std::unique_ptr whiteTexture; + +private: + void setLanguage(const char* lang) { language = lang; } + void drawLineUnclipped(const Point& p0, const Point& p1, const Colorf& color); + + std::unique_ptr font; + + Point origin; + std::vector clipStack; + + std::unordered_map, std::unique_ptr> imageTextures; + std::string language; + + friend class CanvasFont; +}; diff --git a/include/zwidget/core/colorf.h b/include/zwidget/core/colorf.h new file mode 100644 index 000000000..935214799 --- /dev/null +++ b/include/zwidget/core/colorf.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +class Colorf +{ +public: + Colorf() = default; + Colorf(float r, float g, float b, float a = 1.0f) : r(r), g(g), b(b), a(a) { } + + static Colorf transparent() { return { 0.0f, 0.0f, 0.0f, 0.0f }; } + + static Colorf fromRgba8(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) + { + float s = 1.0f / 255.0f; + return { r * s, g * s, b * s, a * s }; + } + + uint32_t toBgra8() const + { + uint32_t cr = (int)(std::max(std::min(r * 255.0f, 255.0f), 0.0f)); + uint32_t cg = (int)(std::max(std::min(g * 255.0f, 255.0f), 0.0f)); + uint32_t cb = (int)(std::max(std::min(b * 255.0f, 255.0f), 0.0f)); + uint32_t ca = (int)(std::max(std::min(a * 255.0f, 255.0f), 0.0f)); + return (ca << 24) | (cr << 16) | (cg << 8) | cb; + } + + bool operator==(const Colorf& v) const { return r == v.r && g == v.g && b == v.b && a == v.a; } + bool operator!=(const Colorf& v) const { return r != v.r || g != v.g || b != v.b || a != v.a; } + + float r = 0.0f; + float g = 0.0f; + float b = 0.0f; + float a = 1.0f; +}; diff --git a/include/zwidget/core/font.h b/include/zwidget/core/font.h new file mode 100644 index 000000000..d4af36061 --- /dev/null +++ b/include/zwidget/core/font.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +class Font +{ +public: + virtual ~Font() = default; + + virtual const std::string& GetName() const = 0; + virtual double GetHeight() const = 0; + + static std::shared_ptr Create(const std::string& name, double height); +}; diff --git a/include/zwidget/core/image.h b/include/zwidget/core/image.h new file mode 100644 index 000000000..4cffc195a --- /dev/null +++ b/include/zwidget/core/image.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +enum class ImageFormat +{ + R8G8B8A8, + B8G8R8A8 +}; + +class Image +{ +public: + virtual ~Image() = default; + + virtual int GetWidth() const = 0; + virtual int GetHeight() const = 0; + virtual ImageFormat GetFormat() const = 0; + virtual void* GetData() const = 0; + + static std::shared_ptr Create(int width, int height, ImageFormat format, const void* data); + static std::shared_ptr LoadResource(const std::string& resourcename, double dpiscale = 1.0); +}; diff --git a/include/zwidget/core/pathfill.h b/include/zwidget/core/pathfill.h new file mode 100644 index 000000000..a6d749806 --- /dev/null +++ b/include/zwidget/core/pathfill.h @@ -0,0 +1,96 @@ +#pragma once + +#include +#include +#include +#include +#include "core/rect.h" + +/* +// 3x3 transform matrix +class PathFillTransform +{ +public: + PathFillTransform() { for (int i = 0; i < 9; i++) matrix[i] = 0.0f; matrix[0] = matrix[4] = matrix[8] = 1.0; } + PathFillTransform(const float* mat3x3) { for (int i = 0; i < 9; i++) matrix[i] = (double)mat3x3[i]; } + PathFillTransform(const double* mat3x3) { for (int i = 0; i < 9; i++) matrix[i] = mat3x3[i]; } + + double matrix[9]; +}; +*/ + +enum class PathFillMode +{ + alternate, + winding +}; + +enum class PathFillCommand +{ + line, + quadradic, + cubic +}; + +class PathFillSubpath +{ +public: + PathFillSubpath() : points(1) { } + + std::vector points; + std::vector commands; + bool closed = false; +}; + +class PathFillDesc +{ +public: + PathFillDesc() : subpaths(1) { } + + PathFillMode fill_mode = PathFillMode::alternate; + std::vector subpaths; + + void MoveTo(const Point& point) + { + if (!subpaths.back().commands.empty()) + { + subpaths.push_back(PathFillSubpath()); + } + subpaths.back().points.front() = point; + } + + void LineTo(const Point& point) + { + auto& subpath = subpaths.back(); + subpath.points.push_back(point); + subpath.commands.push_back(PathFillCommand::line); + } + + void BezierTo(const Point& control, const Point& point) + { + auto& subpath = subpaths.back(); + subpath.points.push_back(control); + subpath.points.push_back(point); + subpath.commands.push_back(PathFillCommand::quadradic); + } + + void BezierTo(const Point& control1, const Point& control2, const Point& point) + { + auto& subpath = subpaths.back(); + subpath.points.push_back(control1); + subpath.points.push_back(control2); + subpath.points.push_back(point); + subpath.commands.push_back(PathFillCommand::cubic); + } + + void Close() + { + if (!subpaths.back().commands.empty()) + { + subpaths.back().closed = true; + subpaths.push_back(PathFillSubpath()); + } + } + + void Rasterize(uint8_t* dest, int width, int height, bool blend = false); +}; diff --git a/include/zwidget/core/rect.h b/include/zwidget/core/rect.h new file mode 100644 index 000000000..3b655cd2e --- /dev/null +++ b/include/zwidget/core/rect.h @@ -0,0 +1,85 @@ +#pragma once + +#include + +class Point +{ +public: + Point() = default; + Point(double x, double y) : x(x), y(y) { } + + double x = 0; + double y = 0; + + Point& operator+=(const Point& p) { x += p.x; y += p.y; return *this; } + Point& operator-=(const Point& p) { x -= p.x; y -= p.y; return *this; } +}; + +class Size +{ +public: + Size() = default; + Size(double width, double height) : width(width), height(height) { } + + double width = 0; + double height = 0; +}; + +class Rect +{ +public: + Rect() = default; + Rect(const Point& p, const Size& s) : x(p.x), y(p.y), width(s.width), height(s.height) { } + Rect(double x, double y, double width, double height) : x(x), y(y), width(width), height(height) { } + + Point pos() const { return { x, y }; } + Size size() const { return { width, height }; } + + Point topLeft() const { return { x, y }; } + Point topRight() const { return { x + width, y }; } + Point bottomLeft() const { return { x, y + height }; } + Point bottomRight() const { return { x + width, y + height }; } + + double left() const { return x; } + double top() const { return y; } + double right() const { return x + width; } + double bottom() const { return y + height; } + + static Rect xywh(double x, double y, double width, double height) { return Rect(x, y, width, height); } + static Rect ltrb(double left, double top, double right, double bottom) { return Rect(left, top, right - left, bottom - top); } + + static Rect shrink(Rect box, double left, double top, double right, double bottom) + { + box.x += left; + box.y += top; + box.width = std::max(box.width - left - right, 0.0); + box.height = std::max(box.height - bottom - top, 0.0); + return box; + } + + bool contains(const Point& p) const { return (p.x >= x && p.x < x + width) && (p.y >= y && p.y < y + height); } + + double x = 0; + double y = 0; + double width = 0; + double height = 0; +}; + +inline Point operator+(const Point& a, const Point& b) { return Point(a.x + b.x, a.y + b.y); } +inline Point operator-(const Point& a, const Point& b) { return Point(a.x - b.x, a.y - b.y); } +inline bool operator==(const Point& a, const Point& b) { return a.x == b.x && a.y == b.y; } +inline bool operator!=(const Point& a, const Point& b) { return a.x != b.x || a.y != b.y; } +inline bool operator==(const Size& a, const Size& b) { return a.width == b.width && a.height == b.height; } +inline bool operator!=(const Size& a, const Size& b) { return a.width != b.width || a.height != b.height; } +inline bool operator==(const Rect& a, const Rect& b) { return a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height; } +inline bool operator!=(const Rect& a, const Rect& b) { return a.x != b.x || a.y != b.y || a.width != b.width || a.height != b.height; } + +inline Point operator+(const Point& a, double b) { return Point(a.x + b, a.y + b); } +inline Point operator-(const Point& a, double b) { return Point(a.x - b, a.y - b); } +inline Point operator*(const Point& a, double b) { return Point(a.x * b, a.y * b); } +inline Point operator/(const Point& a, double b) { return Point(a.x / b, a.y / b); } + +inline Size operator+(const Size& a, double b) { return Size(a.width + b, a.height + b); } +inline Size operator-(const Size& a, double b) { return Size(a.width - b, a.height - b); } +inline Size operator*(const Size& a, double b) { return Size(a.width * b, a.height * b); } +inline Size operator/(const Size& a, double b) { return Size(a.width / b, a.height / b); } diff --git a/include/zwidget/core/resourcedata.h b/include/zwidget/core/resourcedata.h new file mode 100644 index 000000000..f29ab261e --- /dev/null +++ b/include/zwidget/core/resourcedata.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include +#include + +struct SingleFontData +{ + std::vector fontdata; + std::string language; +}; + +std::vector LoadWidgetFontData(const std::string& name); +std::vector LoadWidgetData(const std::string& name); diff --git a/include/zwidget/core/span_layout.h b/include/zwidget/core/span_layout.h new file mode 100644 index 000000000..974feca40 --- /dev/null +++ b/include/zwidget/core/span_layout.h @@ -0,0 +1,238 @@ +#pragma once + +#include +#include + +#include "colorf.h" +#include "rect.h" +#include "font.h" +#include "canvas.h" + +class Widget; +class Image; +class Canvas; + +enum SpanAlign +{ + span_left, + span_right, + span_center, + span_justify +}; + +class SpanLayout +{ +public: + SpanLayout(); + ~SpanLayout(); + + struct HitTestResult + { + enum Type + { + no_objects_available, + outside_top, + outside_left, + outside_right, + outside_bottom, + inside + }; + + Type type = {}; + int object_id = -1; + size_t offset = 0; + }; + + void Clear(); + + void AddText(const std::string& text, std::shared_ptr font, const Colorf& color = Colorf(), int id = -1); + void AddImage(const std::shared_ptr image, double baseline_offset = 0, int id = -1); + void AddWidget(Widget* component, double baseline_offset = 0, int id = -1); + + void Layout(Canvas* canvas, double max_width); + + void SetPosition(const Point& pos); + + Size GetSize() const; + Rect GetRect() const; + + std::vector GetRectById(int id) const; + + HitTestResult HitTest(Canvas* canvas, const Point& pos); + + void DrawLayout(Canvas* canvas); + + /// Draw layout generating ellipsis for clipped text + void DrawLayoutEllipsis(Canvas* canvas, const Rect& content_rect); + + void SetComponentGeometry(); + + Size FindPreferredSize(Canvas* canvas); + + void SetSelectionRange(std::string::size_type start, std::string::size_type end); + void SetSelectionColors(const Colorf& foreground, const Colorf& background); + + void ShowCursor(); + void HideCursor(); + + void SetCursorPos(std::string::size_type pos); + void SetCursorOverwriteMode(bool enable); + void SetCursorColor(const Colorf& color); + + std::string GetCombinedText() const; + + void SetAlign(SpanAlign align); + + double GetFirstBaselineOffset(); + double GetLastBaselineOffset(); + +private: + struct TextBlock + { + size_t start = 0; + size_t end = 0; + }; + + enum ObjectType + { + object_text, + object_image, + object_component + }; + + enum FloatType + { + float_none, + float_left, + float_right + }; + + struct SpanObject + { + ObjectType type = object_text; + FloatType float_type = float_none; + + std::shared_ptr font; + Colorf color; + size_t start = 0, end = 0; + + std::shared_ptr image; + Widget* component = nullptr; + double baseline_offset = 0; + + int id = -1; + }; + + struct LineSegment + { + ObjectType type = object_text; + + std::shared_ptr font; + Colorf color; + size_t start = 0; + size_t end = 0; + double ascender = 0; + double descender = 0; + + double x_position = 0; + double width = 0; + + std::shared_ptr image; + Widget* component = nullptr; + double baseline_offset = 0; + + int id = -1; + }; + + struct Line + { + double width = 0; // Width of the entire line (including spaces) + double height = 0; + double ascender = 0; + std::vector segments; + }; + + struct TextSizeResult + { + size_t start = 0; + size_t end = 0; + double width = 0; + double height = 0; + double ascender = 0; + double descender = 0; + int objects_traversed = 0; + std::vector segments; + }; + + struct CurrentLine + { + std::vector::size_type object_index = 0; + Line cur_line; + double x_position = 0; + double y_position = 0; + }; + + struct FloatBox + { + Rect rect; + ObjectType type = object_image; + std::shared_ptr image; + Widget* component = nullptr; + int id = -1; + }; + + TextSizeResult FindTextSize(Canvas* canvas, const TextBlock& block, size_t object_index); + std::vector FindTextBlocks(); + void LayoutLines(Canvas* canvas, double max_width); + void LayoutText(Canvas* canvas, std::vector blocks, std::vector::size_type block_index, CurrentLine& current_line, double max_width); + void LayoutBlock(CurrentLine& current_line, double max_width, std::vector& blocks, std::vector::size_type block_index); + void LayoutFloatBlock(CurrentLine& current_line, double max_width); + void LayoutInlineBlock(CurrentLine& current_line, double max_width, std::vector& blocks, std::vector::size_type block_index); + void ReflowLine(CurrentLine& current_line, double max_width); + FloatBox FloatBoxLeft(FloatBox float_box, double max_width); + FloatBox FloatBoxRight(FloatBox float_box, double max_width); + FloatBox FloatBoxAny(FloatBox box, double max_width, const std::vector& floats1); + bool BoxFitsOnLine(const FloatBox& box, double max_width); + void PlaceLineSegments(CurrentLine& current_line, TextSizeResult& text_size_result); + void ForcePlaceLineSegments(CurrentLine& current_line, TextSizeResult& text_size_result, double max_width); + void NextLine(CurrentLine& current_line); + bool IsNewline(const TextBlock& block); + bool IsWhitespace(const TextBlock& block); + bool FitsOnLine(double x_position, const TextSizeResult& text_size_result, double max_width); + bool LargerThanLine(const TextSizeResult& text_size_result, double max_width); + void AlignJustify(double max_width); + void AlignCenter(double max_width); + void AlignRight(double max_width); + void DrawLayoutImage(Canvas* canvas, Line& line, LineSegment& segment, double x, double y); + void DrawLayoutText(Canvas* canvas, Line& line, LineSegment& segment, double x, double y); + + bool cursor_visible = false; + std::string::size_type cursor_pos = 0; + bool cursor_overwrite_mode = false; + Colorf cursor_color; + + std::string::size_type sel_start = 0, sel_end = 0; + Colorf sel_foreground, sel_background = Colorf::fromRgba8(153, 201, 239); + + std::string text; + std::vector objects; + std::vector lines; + Point position; + + std::vector floats_left, floats_right; + + SpanAlign alignment = span_left; + + struct LayoutCache + { + int object_index = -1; + FontMetrics metrics; + }; + LayoutCache layout_cache; + + bool is_ellipsis_draw = false; + Rect ellipsis_content_rect; + + template + static T clamp(T val, T minval, T maxval) { return std::max(std::min(val, maxval), minval); } +}; diff --git a/include/zwidget/core/theme.h b/include/zwidget/core/theme.h new file mode 100644 index 000000000..5a5271bc3 --- /dev/null +++ b/include/zwidget/core/theme.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include +#include "rect.h" +#include "colorf.h" + +class Widget; +class Canvas; + +class WidgetStyle +{ +public: + WidgetStyle(WidgetStyle* parentStyle = nullptr) : ParentStyle(parentStyle) { } + virtual ~WidgetStyle() = default; + virtual void Paint(Widget* widget, Canvas* canvas, Size size) = 0; + + void SetBool(const std::string& state, const std::string& propertyName, bool value); + void SetInt(const std::string& state, const std::string& propertyName, int value); + void SetDouble(const std::string& state, const std::string& propertyName, double value); + void SetString(const std::string& state, const std::string& propertyName, const std::string& value); + void SetColor(const std::string& state, const std::string& propertyName, const Colorf& value); + + void SetBool(const std::string& propertyName, bool value) { SetBool(std::string(), propertyName, value); } + void SetInt(const std::string& propertyName, int value) { SetInt(std::string(), propertyName, value); } + void SetDouble(const std::string& propertyName, double value) { SetDouble(std::string(), propertyName, value); } + void SetString(const std::string& propertyName, const std::string& value) { SetString(std::string(), propertyName, value); } + void SetColor(const std::string& propertyName, const Colorf& value) { SetColor(std::string(), propertyName, value); } + +private: + // Note: do not call these directly. Use widget->GetStyleXX instead since a widget may explicitly override a class style + bool GetBool(const std::string& state, const std::string& propertyName) const; + int GetInt(const std::string& state, const std::string& propertyName) const; + double GetDouble(const std::string& state, const std::string& propertyName) const; + std::string GetString(const std::string& state, const std::string& propertyName) const; + Colorf GetColor(const std::string& state, const std::string& propertyName) const; + + WidgetStyle* ParentStyle = nullptr; + typedef std::variant PropertyVariant; + std::unordered_map> StyleProperties; + + const PropertyVariant* FindProperty(const std::string& state, const std::string& propertyName) const; + + friend class Widget; +}; + +class BasicWidgetStyle : public WidgetStyle +{ +public: + using WidgetStyle::WidgetStyle; + void Paint(Widget* widget, Canvas* canvas, Size size) override; +}; + +class WidgetTheme +{ +public: + virtual ~WidgetTheme() = default; + + WidgetStyle* RegisterStyle(std::unique_ptr widgetStyle, const std::string& widgetClass); + WidgetStyle* GetStyle(const std::string& widgetClass); + + static void SetTheme(std::unique_ptr theme); + static WidgetTheme* GetTheme(); + +private: + std::unordered_map> Styles; +}; + +class DarkWidgetTheme : public WidgetTheme +{ +public: + DarkWidgetTheme(); +}; + +class LightWidgetTheme : public WidgetTheme +{ +public: + LightWidgetTheme(); +}; diff --git a/include/zwidget/core/timer.h b/include/zwidget/core/timer.h new file mode 100644 index 000000000..25e6fb346 --- /dev/null +++ b/include/zwidget/core/timer.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +class Widget; + +class Timer +{ +public: + Timer(Widget* owner); + ~Timer(); + + void Start(int timeoutMilliseconds, bool repeat = true); + void Stop(); + + std::function FuncExpired; + +private: + Widget* OwnerObj = nullptr; + Timer* PrevTimerObj = nullptr; + Timer* NextTimerObj = nullptr; + + void* TimerId = nullptr; + + friend class Widget; +}; diff --git a/include/zwidget/core/utf8reader.h b/include/zwidget/core/utf8reader.h new file mode 100644 index 000000000..f9c186b47 --- /dev/null +++ b/include/zwidget/core/utf8reader.h @@ -0,0 +1,78 @@ +/* +** Copyright (c) 1997-2015 Mark Page +** +** This software is provided 'as-is', without any express or implied +** warranty. In no event will the authors be held liable for any damages +** arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, +** including commercial applications, and to alter it and redistribute it +** freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not +** claim that you wrote the original software. If you use this software +** in a product, an acknowledgment in the product documentation would be +** appreciated but is not required. +** 2. Altered source versions must be plainly marked as such, and must not be +** misrepresented as being the original software. +** 3. This notice may not be removed or altered from any source distribution. +** +*/ + +#pragma once + +#include + +/// \brief UTF8 reader helper functions. +class UTF8Reader +{ +public: + /// Important: text is not copied by this class and must remain valid during its usage. + UTF8Reader(const std::string::value_type *text, std::string::size_type length); + + /// \brief Returns true if the current position is at the end of the string + bool is_end(); + + /// \brief Get the character at the current position + unsigned int character(); + + /// \brief Returns the length of the current character + std::string::size_type char_length(); + + /// \brief Moves position to the previous character + void prev(); + + /// \brief Moves position to the next character + void next(); + + /// \brief Moves position to the lead byte of the character + void move_to_leadbyte(); + + /// \brief Get the current position of the reader + std::string::size_type position(); + + /// \brief Set the current position of the reader + void set_position(std::string::size_type position); + + static size_t utf8_length(const std::string& text) + { + return utf8_length(text.data(), text.size()); + } + + static size_t utf8_length(const std::string::value_type* text, std::string::size_type length) + { + UTF8Reader reader(text, length); + size_t i = 0; + while (!reader.is_end()) + { + reader.next(); + i++; + } + return i; + } + +private: + std::string::size_type current_position = 0; + std::string::size_type length = 0; + const unsigned char *data = nullptr; +}; diff --git a/include/zwidget/core/widget.h b/include/zwidget/core/widget.h new file mode 100644 index 000000000..6d87f72a0 --- /dev/null +++ b/include/zwidget/core/widget.h @@ -0,0 +1,241 @@ +#pragma once + +#include +#include +#include +#include +#include "canvas.h" +#include "rect.h" +#include "colorf.h" +#include "../window/window.h" + +class Canvas; +class Timer; + +enum class WidgetType +{ + Child, + Window, + Popup +}; + +class Widget : DisplayWindowHost +{ +public: + Widget(Widget* parent = nullptr, WidgetType type = WidgetType::Child, RenderAPI api = RenderAPI::Unspecified); + virtual ~Widget(); + + void SetParent(Widget* parent); + void MoveBefore(Widget* sibling); + + std::string GetWindowTitle() const; + void SetWindowTitle(const std::string& text); + + // Icon GetWindowIcon() const; + // void SetWindowIcon(const Icon& icon); + + // Widget content box + Size GetSize() const; + double GetWidth() const { return GetSize().width; } + double GetHeight() const { return GetSize().height; } + + // Widget noncontent area + void SetNoncontentSizes(double left, double top, double right, double bottom); + double GetNoncontentLeft() const { return GridFitSize(GetStyleDouble("noncontent-left")); } + double GetNoncontentTop() const { return GridFitSize(GetStyleDouble("noncontent-top")); } + double GetNoncontentRight() const { return GridFitSize(GetStyleDouble("noncontent-right")); } + double GetNoncontentBottom() const { return GridFitSize(GetStyleDouble("noncontent-bottom")); } + + // Get the DPI scale factor for the window the widget is located on + double GetDpiScale() const; + + // Align point to the nearest physical screen pixel + double GridFitPoint(double p) const; + Point GridFitPoint(double x, double y) const { return GridFitPoint(Point(x, y)); } + Point GridFitPoint(Point p) const { return Point(GridFitPoint(p.x), GridFitPoint(p.y)); } + + // Convert size to exactly covering physical screen pixels + double GridFitSize(double s) const; + Size GridFitSize(double w, double h) const { return GridFitSize(Size(w, h)); } + Size GridFitSize(Size s) const { return Size(GridFitSize(s.width), GridFitSize(s.height)); } + + // Widget frame box + Rect GetFrameGeometry() const; + void SetFrameGeometry(const Rect& geometry); + void SetFrameGeometry(double x, double y, double width, double height) { SetFrameGeometry(Rect::xywh(x, y, width, height)); } + + // Style properties + void SetStyleClass(const std::string& styleClass); + const std::string& GetStyleClass() const { return StyleClass; } + void SetStyleState(const std::string& state); + const std::string& GetStyleState() const { return StyleState; } + void SetStyleBool(const std::string& propertyName, bool value); + void SetStyleInt(const std::string& propertyName, int value); + void SetStyleDouble(const std::string& propertyName, double value); + void SetStyleString(const std::string& propertyName, const std::string& value); + void SetStyleColor(const std::string& propertyName, const Colorf& value); + bool GetStyleBool(const std::string& propertyName) const; + int GetStyleInt(const std::string& propertyName) const; + double GetStyleDouble(const std::string& propertyName) const; + std::string GetStyleString(const std::string& propertyName) const; + Colorf GetStyleColor(const std::string& propertyName) const; + + void SetWindowBackground(const Colorf& color); + void SetWindowBorderColor(const Colorf& color); + void SetWindowCaptionColor(const Colorf& color); + void SetWindowCaptionTextColor(const Colorf& color); + + void SetVisible(bool enable) { if (enable) Show(); else Hide(); } + void Show(); + void ShowFullscreen(); + void ShowMaximized(); + void ShowMinimized(); + void ShowNormal(); + void Hide(); + + void ActivateWindow(); + + void Close(); + + void Update(); + void Repaint(); + + bool HasFocus(); + bool IsEnabled(); + bool IsVisible(); + bool IsHidden(); + bool IsFullscreen(); + + void SetFocus(); + void SetEnabled(bool value); + void SetDisabled(bool value) { SetEnabled(!value); } + void SetHidden(bool value) { if (value) Hide(); else Show(); } + + void LockCursor(); + void UnlockCursor(); + void SetCursor(StandardCursor cursor); + + void SetPointerCapture(); + void ReleasePointerCapture(); + + void SetModalCapture(); + void ReleaseModalCapture(); + + bool GetKeyState(InputKey key); + + std::string GetClipboardText(); + void SetClipboardText(const std::string& text); + + Widget* Window() const; + Canvas* GetCanvas() const; + Widget* ChildAt(double x, double y) { return ChildAt(Point(x, y)); } + Widget* ChildAt(const Point& pos); + + bool IsParent(const Widget* w) const; + bool IsChild(const Widget* w) const; + + Widget* Parent() const { return ParentObj; } + Widget* PrevSibling() const { return PrevSiblingObj; } + Widget* NextSibling() const { return NextSiblingObj; } + Widget* FirstChild() const { return FirstChildObj; } + Widget* LastChild() const { return LastChildObj; } + + Point MapFrom(const Widget* parent, const Point& pos) const; + Point MapFromGlobal(const Point& pos) const; + Point MapFromParent(const Point& pos) const { return MapFrom(Parent(), pos); } + + Point MapTo(const Widget* parent, const Point& pos) const; + Point MapToGlobal(const Point& pos) const; + Point MapToParent(const Point& pos) const { return MapTo(Parent(), pos); } + + static Size GetScreenSize(); + + void SetCanvas(std::unique_ptr canvas); + void* GetNativeHandle(); + int GetNativePixelWidth(); + int GetNativePixelHeight(); + + // Vulkan support: + std::vector GetVulkanInstanceExtensions() { return Window()->DispWindow->GetVulkanInstanceExtensions(); } + VkSurfaceKHR CreateVulkanSurface(VkInstance instance) { return Window()->DispWindow->CreateVulkanSurface(instance); } + +protected: + virtual void OnPaintFrame(Canvas* canvas); + virtual void OnPaint(Canvas* canvas) { } + virtual bool OnMouseDown(const Point& pos, InputKey key) { return false; } + virtual bool OnMouseDoubleclick(const Point& pos, InputKey key) { return false; } + virtual bool OnMouseUp(const Point& pos, InputKey key) { return false; } + virtual bool OnMouseWheel(const Point& pos, InputKey key) { return false; } + virtual void OnMouseMove(const Point& pos) { } + virtual void OnMouseLeave() { } + virtual void OnRawMouseMove(int dx, int dy) { } + virtual void OnKeyChar(std::string chars) { } + virtual void OnKeyDown(InputKey key) { } + virtual void OnKeyUp(InputKey key) { } + virtual void OnGeometryChanged() { } + virtual void OnClose() { delete this; } + virtual void OnSetFocus() { } + virtual void OnLostFocus() { } + virtual void OnEnableChanged() { } + +private: + void DetachFromParent(); + + void Paint(Canvas* canvas); + + // DisplayWindowHost + void OnWindowPaint() override; + void OnWindowMouseMove(const Point& pos) override; + void OnWindowMouseLeave() override; + void OnWindowMouseDown(const Point& pos, InputKey key) override; + void OnWindowMouseDoubleclick(const Point& pos, InputKey key) override; + void OnWindowMouseUp(const Point& pos, InputKey key) override; + void OnWindowMouseWheel(const Point& pos, InputKey key) override; + void OnWindowRawMouseMove(int dx, int dy) override; + void OnWindowKeyChar(std::string chars) override; + void OnWindowKeyDown(InputKey key) override; + void OnWindowKeyUp(InputKey key) override; + void OnWindowGeometryChanged() override; + void OnWindowClose() override; + void OnWindowActivated() override; + void OnWindowDeactivated() override; + void OnWindowDpiScaleChanged() override; + + WidgetType Type = {}; + + Widget* ParentObj = nullptr; + Widget* PrevSiblingObj = nullptr; + Widget* NextSiblingObj = nullptr; + Widget* FirstChildObj = nullptr; + Widget* LastChildObj = nullptr; + + Timer* FirstTimerObj = nullptr; + + Rect FrameGeometry = Rect::xywh(0.0, 0.0, 0.0, 0.0); + Rect ContentGeometry = Rect::xywh(0.0, 0.0, 0.0, 0.0); + + Colorf WindowBackground = Colorf::fromRgba8(240, 240, 240); + + std::string WindowTitle; + std::unique_ptr DispWindow; + std::unique_ptr DispCanvas; + Widget* FocusWidget = nullptr; + Widget* CaptureWidget = nullptr; + Widget* HoverWidget = nullptr; + bool HiddenFlag = false; + + StandardCursor CurrentCursor = StandardCursor::arrow; + + std::string StyleClass = "widget"; + std::string StyleState; + typedef std::variant PropertyVariant; + std::unordered_map StyleProperties; + + Widget(const Widget&) = delete; + Widget& operator=(const Widget&) = delete; + + friend class Timer; + friend class OpenFileDialog; + friend class OpenFolderDialog; + friend class SaveFileDialog; +}; diff --git a/include/zwidget/systemdialogs/open_file_dialog.h b/include/zwidget/systemdialogs/open_file_dialog.h new file mode 100644 index 000000000..29ede966e --- /dev/null +++ b/include/zwidget/systemdialogs/open_file_dialog.h @@ -0,0 +1,55 @@ + +#pragma once + +#include +#include +#include + +class Widget; + +/// \brief Displays the system open file dialog. +class OpenFileDialog +{ +public: + /// \brief Constructs an open file dialog. + static std::unique_ptr Create(Widget* owner); + + virtual ~OpenFileDialog() = default; + + /// \brief Get the full path of the file selected. + /// + /// If multiple files are selected, this returns the first file. + virtual std::string Filename() const = 0; + + /// \brief Gets an array that contains one file name for each selected file. + virtual std::vector Filenames() const = 0; + + /// \brief Sets if multiple files can be selected or not. + /// \param multiselect = When true, multiple items can be selected. + virtual void SetMultiSelect(bool multiselect) = 0; + + /// \brief Sets a string containing the full path of the file selected. + virtual void SetFilename(const std::string &filename) = 0; + + /// \brief Sets the default extension to use. + virtual void SetDefaultExtension(const std::string& extension) = 0; + + /// \brief Add a filter that determines what types of files are displayed. + virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0; + + /// \brief Clears all filters. + virtual void ClearFilters() = 0; + + /// \brief Sets a default filter, on a 0-based index. + virtual void SetFilterIndex(int filter_index) = 0; + + /// \brief Sets the initial directory that is displayed. + virtual void SetInitialDirectory(const std::string &path) = 0; + + /// \brief Sets the text that appears in the title bar. + virtual void SetTitle(const std::string &title) = 0; + + /// \brief Shows the file dialog. + /// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise. + virtual bool Show() = 0; +}; diff --git a/include/zwidget/systemdialogs/open_folder_dialog.h b/include/zwidget/systemdialogs/open_folder_dialog.h new file mode 100644 index 000000000..9727f353b --- /dev/null +++ b/include/zwidget/systemdialogs/open_folder_dialog.h @@ -0,0 +1,30 @@ + +#pragma once + +#include +#include + +class Widget; + +/// \brief Displays the system folder browsing dialog +class OpenFolderDialog +{ +public: + /// \brief Constructs an browse folder dialog. + static std::unique_ptr Create(Widget*owner); + + virtual ~OpenFolderDialog() = default; + + /// \brief Get the full path of the directory selected. + virtual std::string SelectedPath() const = 0; + + /// \brief Sets the initial directory that is displayed. + virtual void SetInitialDirectory(const std::string& path) = 0; + + /// \brief Sets the text that appears in the title bar. + virtual void SetTitle(const std::string& title) = 0; + + /// \brief Shows the file dialog. + /// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise. + virtual bool Show() = 0; +}; diff --git a/include/zwidget/systemdialogs/save_file_dialog.h b/include/zwidget/systemdialogs/save_file_dialog.h new file mode 100644 index 000000000..ce10a127f --- /dev/null +++ b/include/zwidget/systemdialogs/save_file_dialog.h @@ -0,0 +1,46 @@ + +#pragma once + +#include +#include +#include + +class Widget; + +/// \brief Displays the system save file dialog. +class SaveFileDialog +{ +public: + /// \brief Constructs a save file dialog. + static std::unique_ptr Create(Widget *owner); + + virtual ~SaveFileDialog() = default; + + /// \brief Get the full path of the file selected. + virtual std::string Filename() const = 0; + + /// \brief Sets a string containing the full path of the file selected. + virtual void SetFilename(const std::string &filename) = 0; + + /// \brief Sets the default extension to use. + virtual void SetDefaultExtension(const std::string& extension) = 0; + + /// \brief Add a filter that determines what types of files are displayed. + virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0; + + /// \brief Clears all filters. + virtual void ClearFilters() = 0; + + /// \brief Sets a default filter, on a 0-based index. + virtual void SetFilterIndex(int filter_index) = 0; + + /// \brief Sets the initial directory that is displayed. + virtual void SetInitialDirectory(const std::string &path) = 0; + + /// \brief Sets the text that appears in the title bar. + virtual void SetTitle(const std::string &title) = 0; + + /// \brief Shows the file dialog. + /// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise. + virtual bool Show() = 0; +}; diff --git a/include/zwidget/widgets/checkboxlabel/checkboxlabel.h b/include/zwidget/widgets/checkboxlabel/checkboxlabel.h new file mode 100644 index 000000000..21ce0b697 --- /dev/null +++ b/include/zwidget/widgets/checkboxlabel/checkboxlabel.h @@ -0,0 +1,34 @@ + +#pragma once + +#include "../../core/widget.h" + +class CheckboxLabel : public Widget +{ +public: + CheckboxLabel(Widget* parent = nullptr); + + void SetText(const std::string& value); + const std::string& GetText() const; + + void SetChecked(bool value); + bool GetChecked() const; + void Toggle(); + + double GetPreferredHeight() const; + std::function FuncChanged; + void SetRadioStyle(bool on) { radiostyle = on; } + +protected: + void OnPaint(Canvas* canvas) override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; + void OnMouseLeave() override; + void OnKeyUp(InputKey key) override; + +private: + std::string text; + bool checked = false; + bool radiostyle = false; + bool mouseDownActive = false; +}; diff --git a/include/zwidget/widgets/imagebox/imagebox.h b/include/zwidget/widgets/imagebox/imagebox.h new file mode 100644 index 000000000..7cae08c24 --- /dev/null +++ b/include/zwidget/widgets/imagebox/imagebox.h @@ -0,0 +1,31 @@ + +#pragma once + +#include "../../core/widget.h" +#include "../../core/image.h" + +enum ImageBoxMode +{ + Center, + Contain, + Cover +}; + +class ImageBox : public Widget +{ +public: + ImageBox(Widget* parent); + + void SetImage(std::shared_ptr newImage); + void SetImageMode(ImageBoxMode mode); + + double GetPreferredWidth() const; + double GetPreferredHeight() const; + +protected: + void OnPaint(Canvas* canvas) override; + +private: + std::shared_ptr image; + ImageBoxMode mode = ImageBoxMode::Center; +}; diff --git a/include/zwidget/widgets/lineedit/lineedit.h b/include/zwidget/widgets/lineedit/lineedit.h new file mode 100644 index 000000000..9c5a4b198 --- /dev/null +++ b/include/zwidget/widgets/lineedit/lineedit.h @@ -0,0 +1,158 @@ + +#pragma once + +#include "../../core/widget.h" +#include "../../core/timer.h" +#include + +class LineEdit : public Widget +{ +public: + LineEdit(Widget* parent); + ~LineEdit(); + + enum Alignment + { + align_left, + align_center, + align_right + }; + + Alignment GetAlignment() const; + bool IsReadOnly() const; + bool IsLowercase() const; + bool IsUppercase() const; + bool IsPasswordMode() const; + int GetMaxLength() const; + + std::string GetText() const; + int GetTextInt() const; + float GetTextFloat() const; + + std::string GetSelection() const; + int GetSelectionStart() const; + int GetSelectionLength() const; + + int GetCursorPos() const; + Size GetTextSize(); + + Size GetTextSize(const std::string& str); + double GetPreferredContentWidth(); + double GetPreferredContentHeight(double width); + + void SetSelectAllOnFocusGain(bool enable); + void SelectAll(); + void SetAlignment(Alignment alignment); + void SetReadOnly(bool enable = true); + void SetLowercase(bool enable = true); + void SetUppercase(bool enable = true); + void SetPasswordMode(bool enable = true); + void SetNumericMode(bool enable = true, bool decimals = false); + void SetMaxLength(int length); + void SetText(const std::string& text); + void SetTextInt(int number); + void SetTextFloat(float number, int num_decimal_places = 6); + void SetSelection(int pos, int length); + void ClearSelection(); + void SetCursorPos(int pos); + void DeleteSelectedText(); + void SetInputMask(const std::string& mask); + void SetDecimalCharacter(const std::string& decimal_char); + + std::function FuncIgnoreKeyDown; + std::function FuncFilterKeyChar; + std::function FuncBeforeEditChanged; + std::function FuncAfterEditChanged; + std::function FuncSelectionChanged; + std::function FuncFocusGained; + std::function FuncFocusLost; + std::function FuncEnterPressed; + +protected: + void OnPaint(Canvas* canvas) override; + void OnMouseMove(const Point& pos) override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseDoubleclick(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; + void OnKeyChar(std::string chars) override; + void OnKeyDown(InputKey key) override; + void OnKeyUp(InputKey key) override; + void OnGeometryChanged() override; + void OnEnableChanged() override; + void OnSetFocus() override; + void OnLostFocus() override; + +private: + void OnTimerExpired(); + void OnScrollTimerExpired(); + void UpdateTextClipping(); + + void Move(int steps, bool ctrl, bool shift); + bool InsertText(int pos, const std::string& str); + void Backspace(); + void Del(); + int GetCharacterIndex(double x); + int FindNextBreakCharacter(int pos); + int FindPreviousBreakCharacter(int pos); + std::string GetVisibleTextBeforeSelection(); + std::string GetVisibleTextAfterSelection(); + std::string GetVisibleSelectedText(); + std::string CreatePassword(std::string::size_type num_letters) const; + Size GetVisualTextSize(Canvas* canvas, int pos, int npos) const; + Size GetVisualTextSize(Canvas* canvas) const; + Rect GetCursorRect(); + Rect GetSelectionRect(); + bool InputMaskAcceptsInput(int cursor_pos, const std::string& str); + void SetSelectionStart(int start); + void SetSelectionLength(int length); + void SetTextSelection(int start, int length); + + static std::string ToFixed(float number, int num_decimal_places); + static std::string ToLower(const std::string& text); + static std::string ToUpper(const std::string& text); + + Timer* timer = nullptr; + std::string text; + Alignment alignment = align_left; + int cursor_pos = 0; + int max_length = -1; + bool mouse_selecting = false; + bool lowercase = false; + bool uppercase = false; + bool password_mode = false; + bool numeric_mode = false; + bool numeric_mode_decimals = false; + bool readonly = false; + int selection_start = -1; + int selection_length = 0; + std::string input_mask; + std::string decimal_char = "."; + + VerticalTextPosition vertical_text_align; + Timer* scroll_timer = nullptr; + + bool mouse_moves_left = false; + bool cursor_blink_visible = true; + unsigned int blink_timer = 0; + int clip_start_offset = 0; + int clip_end_offset = 0; + + struct UndoInfo + { + /* set undo text when: + - added char after moving + - destructive block operation (del, cut etc) + - beginning erase + */ + std::string undo_text; + bool first_erase = false; + bool first_text_insert = false; + }; + + UndoInfo undo_info; + + bool select_all_on_focus_gain = true; + + static const std::string break_characters; + static const std::string numeric_mode_characters; +}; diff --git a/include/zwidget/widgets/listview/listview.h b/include/zwidget/widgets/listview/listview.h new file mode 100644 index 000000000..e1b08192a --- /dev/null +++ b/include/zwidget/widgets/listview/listview.h @@ -0,0 +1,46 @@ + +#pragma once + +#include "../../core/widget.h" +#include +#include + +class Scrollbar; + +class ListView : public Widget +{ +public: + ListView(Widget* parent = nullptr); + + void SetColumnWidths(const std::vector& widths); + void AddItem(const std::string& text, int index = -1, int column = 0); + void UpdateItem(const std::string& text, int index, int column = 0); + void RemoveItem(int index = -1); + size_t GetItemAmount() const { return items.size(); } + size_t GetColumnAmount() const { return columnwidths.size(); } + int GetSelectedItem() const { return selectedItem; } + void SetSelectedItem(int index); + void ScrollToItem(int index); + + void Activate(); + + std::function OnChanged; + std::function OnActivated; + +protected: + void OnPaint(Canvas* canvas) override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseDoubleclick(const Point& pos, InputKey key) override; + bool OnMouseWheel(const Point& pos, InputKey key) override; + void OnKeyDown(InputKey key) override; + void OnGeometryChanged() override; + void OnScrollbarScroll(); + + static double getItemHeight(); + + Scrollbar* scrollbar = nullptr; + + std::vector> items; + std::vector columnwidths; + int selectedItem = 0; +}; diff --git a/include/zwidget/widgets/mainwindow/mainwindow.h b/include/zwidget/widgets/mainwindow/mainwindow.h new file mode 100644 index 000000000..13498e56d --- /dev/null +++ b/include/zwidget/widgets/mainwindow/mainwindow.h @@ -0,0 +1,33 @@ + +#pragma once + +#include "../../core/widget.h" + +class Menubar; +class Toolbar; +class Statusbar; + +class MainWindow : public Widget +{ +public: + MainWindow(RenderAPI api = RenderAPI::Unspecified); + ~MainWindow(); + + Menubar* GetMenubar() const { return MenubarWidget; } + Toolbar* GetTopToolbar() const { return TopToolbarWidget; } + Toolbar* GetLeftToolbar() const { return LeftToolbarWidget; } + Statusbar* GetStatusbar() const { return StatusbarWidget; } + Widget* GetCentralWidget() const { return CentralWidget; } + + void SetCentralWidget(Widget* widget); + +protected: + void OnGeometryChanged() override; + +private: + Menubar* MenubarWidget = nullptr; + Toolbar* TopToolbarWidget = nullptr; + Toolbar* LeftToolbarWidget = nullptr; + Widget* CentralWidget = nullptr; + Statusbar* StatusbarWidget = nullptr; +}; diff --git a/include/zwidget/widgets/menubar/menubar.h b/include/zwidget/widgets/menubar/menubar.h new file mode 100644 index 000000000..1881111f4 --- /dev/null +++ b/include/zwidget/widgets/menubar/menubar.h @@ -0,0 +1,121 @@ + +#pragma once + +#include "../../core/widget.h" +#include "../textlabel/textlabel.h" +#include "../imagebox/imagebox.h" +#include + +class Menu; +class MenubarItem; +class MenuItem; +class MenuItemSeparator; + +class Menubar : public Widget +{ +public: + Menubar(Widget* parent); + ~Menubar(); + + MenubarItem* AddItem(std::string text, std::function onOpen, bool alignRight = false); + +protected: + void OnGeometryChanged() override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; + void OnMouseMove(const Point& pos) override; + void OnKeyDown(InputKey key) override; + +private: + void ShowMenu(MenubarItem* item); + void CloseMenu(); + + MenubarItem* GetMenubarItemAt(const Point& pos); + int GetItemIndex(MenubarItem* item); + + std::vector menuItems; + int currentMenubarItem = -1; + Menu* openMenu = nullptr; + bool modalMode = false; + + friend class MenubarItem; +}; + +class MenubarItem : public Widget +{ +public: + MenubarItem(Menubar* menubar, std::string text, bool alignRight); + + void SetOpenCallback(std::function callback) { onOpen = std::move(callback); } + const std::function& GetOpenCallback() const { return onOpen; } + + double GetPreferredWidth() const; + + bool AlignRight = false; + +protected: + void OnPaint(Canvas* canvas) override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; + void OnMouseMove(const Point& pos) override; + void OnMouseLeave() override; + +private: + Menubar* menubar = nullptr; + std::function onOpen; + std::string text; +}; + +class Menu : public Widget +{ +public: + Menu(Widget* parent); + + void SetLeftPosition(const Point& globalPos); + void SetRightPosition(const Point& globalPos); + MenuItem* AddItem(std::shared_ptr icon, std::string text, std::function onClick = {}); + MenuItemSeparator* AddSeparator(); + + double GetPreferredWidth() const; + double GetPreferredHeight() const; + + void SetSelected(MenuItem* item); + +protected: + void OnGeometryChanged() override; + + std::function onCloseMenu; + MenuItem* selectedItem = nullptr; + + friend class Menubar; +}; + +class MenuItem : public Widget +{ +public: + MenuItem(Menu* menu, std::function onClick); + + void Click(); + + Menu* menu = nullptr; + ImageBox* icon = nullptr; + TextLabel* text = nullptr; + +protected: + bool OnMouseUp(const Point& pos, InputKey key) override; + void OnMouseMove(const Point& pos) override; + void OnMouseLeave() override; + void OnGeometryChanged() override; + +private: + std::function onClick; +}; + +class MenuItemSeparator : public Widget +{ +public: + MenuItemSeparator(Widget* parent); + +protected: + void OnPaint(Canvas* canvas) override; +}; diff --git a/include/zwidget/widgets/pushbutton/pushbutton.h b/include/zwidget/widgets/pushbutton/pushbutton.h new file mode 100644 index 000000000..d3fe0295b --- /dev/null +++ b/include/zwidget/widgets/pushbutton/pushbutton.h @@ -0,0 +1,32 @@ + +#pragma once + +#include "../../core/widget.h" +#include + +class PushButton : public Widget +{ +public: + PushButton(Widget* parent = nullptr); + + void SetText(const std::string& value); + const std::string& GetText() const; + + double GetPreferredHeight() const; + + void Click(); + + std::function OnClick; + +protected: + void OnPaint(Canvas* canvas) override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; + void OnMouseMove(const Point& pos) override; + void OnMouseLeave() override; + void OnKeyDown(InputKey key) override; + void OnKeyUp(InputKey key) override; + +private: + std::string text; +}; diff --git a/include/zwidget/widgets/scrollbar/scrollbar.h b/include/zwidget/widgets/scrollbar/scrollbar.h new file mode 100644 index 000000000..6e169b1fe --- /dev/null +++ b/include/zwidget/widgets/scrollbar/scrollbar.h @@ -0,0 +1,99 @@ + +#pragma once + +#include "../../core/widget.h" +#include "../../core/timer.h" +#include + +class Scrollbar : public Widget +{ +public: + Scrollbar(Widget* parent); + ~Scrollbar(); + + bool IsVertical() const; + bool IsHorizontal() const; + double GetMin() const; + double GetMax() const; + double GetLineStep() const; + double GetPageStep() const; + double GetPosition() const; + + void SetVertical(); + void SetHorizontal(); + + void SetMin(double scroll_min); + void SetMax(double scroll_max); + void SetLineStep(double step); + void SetPageStep(double step); + + void SetRanges(double scroll_min, double scroll_max, double line_step, double page_step); + void SetRanges(double view_size, double total_size); + + void SetPosition(double pos); + + double GetPreferredWidth() const { return 16.0; } + double GetPreferredHeight() const { return 16.0; } + + std::function FuncScroll; + std::function FuncScrollMin; + std::function FuncScrollMax; + std::function FuncScrollLineDecrement; + std::function FuncScrollLineIncrement; + std::function FuncScrollPageDecrement; + std::function FuncScrollPageIncrement; + std::function FuncScrollThumbRelease; + std::function FuncScrollThumbTrack; + std::function FuncScrollEnd; + +protected: + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; + void OnMouseMove(const Point& pos) override; + void OnMouseLeave() override; + void OnPaint(Canvas* canvas) override; + void OnEnableChanged() override; + void OnGeometryChanged() override; + +private: + bool UpdatePartPositions(); + double CalculateThumbSize(double track_size); + double CalculateThumbPosition(double thumb_size, double track_size); + Rect CreateRect(double start, double end); + void InvokeScrollEvent(std::function* event_ptr); + void OnTimerExpired(); + + bool vertical = true; + double scroll_min = 0.0; + double scroll_max = 1.0; + double line_step = 1.0; + double page_step = 10.0; + double position = 0.0; + + bool showbuttons = false; + + enum MouseDownMode + { + mouse_down_none, + mouse_down_button_decr, + mouse_down_button_incr, + mouse_down_track_decr, + mouse_down_track_incr, + mouse_down_thumb_drag + } mouse_down_mode = mouse_down_none; + + double thumb_start_position = 0.0; + Point mouse_drag_start_pos; + double thumb_start_pixel_position = 0.0; + + Timer* mouse_down_timer = nullptr; + double last_step_size = 0.0; + + Rect rect_button_decrement; + Rect rect_track_decrement; + Rect rect_thumb; + Rect rect_track_increment; + Rect rect_button_increment; + + std::function* FuncScrollOnMouseDown = nullptr; +}; diff --git a/include/zwidget/widgets/statusbar/statusbar.h b/include/zwidget/widgets/statusbar/statusbar.h new file mode 100644 index 000000000..c03a3e282 --- /dev/null +++ b/include/zwidget/widgets/statusbar/statusbar.h @@ -0,0 +1,19 @@ + +#pragma once + +#include "../../core/widget.h" + +class LineEdit; + +class Statusbar : public Widget +{ +public: + Statusbar(Widget* parent); + ~Statusbar(); + +protected: + void OnPaint(Canvas* canvas) override; + +private: + LineEdit* CommandEdit = nullptr; +}; diff --git a/include/zwidget/widgets/tabwidget/tabwidget.h b/include/zwidget/widgets/tabwidget/tabwidget.h new file mode 100644 index 000000000..8682f1a73 --- /dev/null +++ b/include/zwidget/widgets/tabwidget/tabwidget.h @@ -0,0 +1,129 @@ + +#pragma once + +#include "../../core/widget.h" +#include +#include +#include + +class TabBar; +class TabBarTab; +class TabBarSpacer; +class TabWidgetStack; +class TextLabel; +class ImageBox; +class Image; + +class TabWidget : public Widget +{ +public: + TabWidget(Widget* parent); + + int AddTab(Widget* page, const std::string& label); + int AddTab(Widget* page, const std::shared_ptr& icon, const std::string& label); + + void SetTabText(int index, const std::string& text); + void SetTabText(Widget* page, const std::string& text); + void SetTabIcon(int index, const std::shared_ptr& icon); + void SetTabIcon(Widget* page, const std::shared_ptr& icon); + + int GetCurrentIndex() const; + Widget* GetCurrentWidget() const; + + int GetPageIndex(Widget* pageWidget) const; + + void SetCurrentIndex(int pageIndex); + void SetCurrentWidget(Widget* pageWidget); + + std::function OnCurrentChanged; + +protected: + void OnGeometryChanged() override; + +private: + void OnBarCurrentChanged(); + + TabBar* Bar = nullptr; + TabWidgetStack* PageStack = nullptr; + std::vector Pages; +}; + +class TabBar : public Widget +{ +public: + TabBar(Widget* parent); + + int AddTab(const std::string& label); + int AddTab(const std::shared_ptr& icon, const std::string& label); + + void SetTabText(int index, const std::string& text); + void SetTabIcon(int index, const std::shared_ptr& icon); + + int GetCurrentIndex() const; + void SetCurrentIndex(int pageIndex); + + double GetPreferredHeight() const { return 30.0; } + + std::function OnCurrentChanged; + +protected: + void OnGeometryChanged() override; + +private: + void OnTabClicked(TabBarTab* tab); + int GetTabIndex(TabBarTab* tab); + + int CurrentIndex = -1; + std::vector Tabs; + TabBarSpacer* leftSpacer = nullptr; + TabBarSpacer* rightSpacer = nullptr; +}; + +class TabBarSpacer : public Widget +{ +public: + TabBarSpacer(Widget* parent); +}; + +class TabBarTab : public Widget +{ +public: + TabBarTab(Widget* parent); + + void SetText(const std::string& text); + void SetIcon(const std::shared_ptr& icon); + void SetCurrent(bool value); + + double GetPreferredWidth() const; + + std::function OnClick; + +protected: + void OnGeometryChanged() override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; + void OnMouseMove(const Point& pos) override; + void OnMouseLeave() override; + +private: + bool IsCurrent = false; + + ImageBox* Icon = nullptr; + TextLabel* Label = nullptr; + bool hot = false; +}; + +class TabWidgetStack : public Widget +{ +public: + TabWidgetStack(Widget* parent); + + void SetCurrentWidget(Widget* widget); + Widget* GetCurrentWidget() const { return CurrentWidget; } + +protected: + void OnGeometryChanged() override; + +private: + Widget* CurrentWidget = nullptr; +}; diff --git a/include/zwidget/widgets/textedit/textedit.h b/include/zwidget/widgets/textedit/textedit.h new file mode 100644 index 000000000..f392f85d6 --- /dev/null +++ b/include/zwidget/widgets/textedit/textedit.h @@ -0,0 +1,155 @@ + +#pragma once + +#include "../../core/widget.h" +#include "../../core/timer.h" +#include "../../core/span_layout.h" +#include "../../core/font.h" +#include + +class Scrollbar; + +class TextEdit : public Widget +{ +public: + TextEdit(Widget* parent); + ~TextEdit(); + + bool IsReadOnly() const; + bool IsLowercase() const; + bool IsUppercase() const; + int GetMaxLength() const; + std::string GetText() const; + int GetLineCount() const; + std::string GetLineText(int line) const; + std::string GetSelection() const; + int GetSelectionStart() const; + int GetSelectionLength() const; + int GetCursorPos() const; + int GetCursorLineNumber() const; + double GetTotalHeight(); + + void SetSelectAllOnFocusGain(bool enable); + void SelectAll(); + void SetReadOnly(bool enable = true); + void SetLowercase(bool enable = true); + void SetUppercase(bool enable = true); + void SetMaxLength(int length); + void SetText(const std::string& text); + void AddText(const std::string& text); + void SetSelection(int pos, int length); + void ClearSelection(); + void SetCursorPos(int pos); + void DeleteSelectedText(); + void SetInputMask(const std::string& mask); + void SetCursorDrawingEnabled(bool enable); + + std::function FuncFilterKeyChar; + std::function FuncBeforeEditChanged; + std::function FuncAfterEditChanged; + std::function FuncSelectionChanged; + std::function FuncFocusGained; + std::function FuncFocusLost; + std::function FuncEnterPressed; + +protected: + void OnPaint(Canvas* canvas) override; + void OnMouseMove(const Point& pos) override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseDoubleclick(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; + void OnKeyChar(std::string chars) override; + void OnKeyDown(InputKey key) override; + void OnKeyUp(InputKey key) override; + void OnGeometryChanged() override; + void OnEnableChanged() override; + void OnSetFocus() override; + void OnLostFocus() override; + +private: + void LayoutLines(Canvas* canvas); + + void OnTimerExpired(); + void OnScrollTimerExpired(); + void CreateComponents(); + void OnVerticalScroll(); + void UpdateVerticalScroll(); + void MoveVerticalScroll(); + double GetTotalLineHeight(); + + struct Line + { + std::string text; + SpanLayout layout; + Rect box; + bool invalidated = true; + }; + + struct ivec2 + { + ivec2() = default; + ivec2(int x, int y) : x(x), y(y) { } + int x = 0; + int y = 0; + + bool operator==(const ivec2& b) const { return x == b.x && y == b.y; } + bool operator!=(const ivec2& b) const { return x != b.x || y != b.y; } + }; + + Scrollbar* vert_scrollbar; + Timer* timer = nullptr; + std::vector lines = { Line() }; + ivec2 cursor_pos = { 0, 0 }; + int max_length = -1; + bool mouse_selecting = false; + bool lowercase = false; + bool uppercase = false; + bool readonly = false; + ivec2 selection_start = { -1, 0 }; + int selection_length = 0; + std::string input_mask; + + static std::string break_characters; + + void Move(int steps, bool shift, bool ctrl); + void InsertText(ivec2 pos, const std::string& str); + void Backspace(); + void Del(); + ivec2 GetCharacterIndex(Point mouse_wincoords); + ivec2 FindNextBreakCharacter(ivec2 pos); + ivec2 FindPreviousBreakCharacter(ivec2 pos); + bool InputMaskAcceptsInput(ivec2 cursor_pos, const std::string& str); + + std::string::size_type ToOffset(ivec2 pos) const; + ivec2 FromOffset(std::string::size_type offset) const; + + VerticalTextPosition vertical_text_align; + Timer* scroll_timer = nullptr; + + bool mouse_moves_left = false; + bool cursor_blink_visible = true; + unsigned int blink_timer = 0; + int clip_start_offset = 0; + int clip_end_offset = 0; + bool ignore_mouse_events = false; + + struct UndoInfo + { + /* set undo text when: + - added char after moving + - destructive block operation (del, cut etc) + - beginning erase + */ + + std::string undo_text; + bool first_erase = false; + bool first_text_insert = false; + } undo_info; + + bool select_all_on_focus_gain = false; + + std::shared_ptr font = Font::Create("NotoSans", 12.0); + + template + static T clamp(T val, T minval, T maxval) { return std::max(std::min(val, maxval), minval); } +}; diff --git a/include/zwidget/widgets/textlabel/textlabel.h b/include/zwidget/widgets/textlabel/textlabel.h new file mode 100644 index 000000000..18cfcbf5f --- /dev/null +++ b/include/zwidget/widgets/textlabel/textlabel.h @@ -0,0 +1,33 @@ + +#pragma once + +#include "../../core/widget.h" + +enum class TextLabelAlignment +{ + Left, + Center, + Right +}; + +class TextLabel : public Widget +{ +public: + TextLabel(Widget* parent = nullptr); + + void SetText(const std::string& value); + const std::string& GetText() const; + + void SetTextAlignment(TextLabelAlignment alignment); + TextLabelAlignment GetTextAlignment() const; + + double GetPreferredWidth() const; + double GetPreferredHeight() const; + +protected: + void OnPaint(Canvas* canvas) override; + +private: + std::string text; + TextLabelAlignment textAlignment = TextLabelAlignment::Left; +}; diff --git a/include/zwidget/widgets/toolbar/toolbar.h b/include/zwidget/widgets/toolbar/toolbar.h new file mode 100644 index 000000000..fac66b97d --- /dev/null +++ b/include/zwidget/widgets/toolbar/toolbar.h @@ -0,0 +1,31 @@ + +#pragma once + +#include "../../core/widget.h" + +enum class ToolbarDirection +{ + Horizontal, + Vertical +}; + +class ToolbarButton; + +class Toolbar : public Widget +{ +public: + Toolbar(Widget* parent); + ~Toolbar(); + + void SetDirection(ToolbarDirection direction); + ToolbarDirection GetDirection(ToolbarDirection) const { return direction; } + + ToolbarButton* AddButton(std::string icon, std::string text, std::function onClicked = {}); + +protected: + void OnGeometryChanged() override; + +private: + ToolbarDirection direction = ToolbarDirection::Horizontal; + std::vector buttons; +}; diff --git a/include/zwidget/widgets/toolbar/toolbarbutton.h b/include/zwidget/widgets/toolbar/toolbarbutton.h new file mode 100644 index 000000000..f6e1557a1 --- /dev/null +++ b/include/zwidget/widgets/toolbar/toolbarbutton.h @@ -0,0 +1,34 @@ + +#pragma once + +#include "../../core/widget.h" +#include "../textlabel/textlabel.h" +#include "../imagebox/imagebox.h" + +class ToolbarButton : public Widget +{ +public: + ToolbarButton(Widget* parent); + ~ToolbarButton(); + + void SetIcon(std::string icon); + void SetText(std::string text); + + void Click(); + + double GetPreferredWidth(); + double GetPreferredHeight(); + + std::function OnClick; + +protected: + void OnMouseMove(const Point& pos) override; + void OnMouseLeave() override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; + void OnGeometryChanged() override; + +private: + ImageBox* image = nullptr; + TextLabel* label = nullptr; +}; diff --git a/include/zwidget/window/sdl2nativehandle.h b/include/zwidget/window/sdl2nativehandle.h new file mode 100644 index 000000000..9e396a80b --- /dev/null +++ b/include/zwidget/window/sdl2nativehandle.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +struct SDL_Window; + +class SDL2NativeHandle +{ +public: + SDL_Window* window = nullptr; +}; diff --git a/include/zwidget/window/waylandnativehandle.h b/include/zwidget/window/waylandnativehandle.h new file mode 100644 index 000000000..b7358d782 --- /dev/null +++ b/include/zwidget/window/waylandnativehandle.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class WaylandNativeHandle +{ +public: + wl_display* display = nullptr; + wl_surface* surface = nullptr; +}; diff --git a/include/zwidget/window/win32nativehandle.h b/include/zwidget/window/win32nativehandle.h new file mode 100644 index 000000000..979ac63a1 --- /dev/null +++ b/include/zwidget/window/win32nativehandle.h @@ -0,0 +1,9 @@ +#pragma once + +typedef struct HWND__* HWND; + +class Win32NativeHandle +{ +public: + HWND hwnd = {}; +}; diff --git a/include/zwidget/window/window.h b/include/zwidget/window/window.h new file mode 100644 index 000000000..ad59f1f02 --- /dev/null +++ b/include/zwidget/window/window.h @@ -0,0 +1,243 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "../core/rect.h" + +#ifndef VULKAN_H_ + +#define VK_DEFINE_HANDLE(object) typedef struct object##_T* object; + +#if defined(__LP64__) || defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) +#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object; +#else +#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object; +#endif + +VK_DEFINE_HANDLE(VkInstance) +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSurfaceKHR) + +#endif + +class Widget; +class OpenFileDialog; +class SaveFileDialog; +class OpenFolderDialog; + +enum class StandardCursor +{ + arrow, + appstarting, + cross, + hand, + ibeam, + no, + size_all, + size_nesw, + size_ns, + size_nwse, + size_we, + uparrow, + wait +}; + +enum class InputKey : uint32_t +{ + None, LeftMouse, RightMouse, Cancel, + MiddleMouse, Unknown05, Unknown06, Unknown07, + Backspace, Tab, Unknown0A, Unknown0B, + Unknown0C, Enter, Unknown0E, Unknown0F, + Shift, Ctrl, Alt, Pause, + CapsLock, Unknown15, Unknown16, Unknown17, + Unknown18, Unknown19, Unknown1A, Escape, + Unknown1C, Unknown1D, Unknown1E, Unknown1F, + Space, PageUp, PageDown, End, + Home, Left, Up, Right, + Down, Select, Print, Execute, + PrintScrn, Insert, Delete, Help, + _0, _1, _2, _3, + _4, _5, _6, _7, + _8, _9, Unknown3A, Unknown3B, + Unknown3C, Unknown3D, Unknown3E, Unknown3F, + Unknown40, A, B, C, + D, E, F, G, + H, I, J, K, + L, M, N, O, + P, Q, R, S, + T, U, V, W, + X, Y, Z, Unknown5B, + Unknown5C, Unknown5D, Unknown5E, Unknown5F, + NumPad0, NumPad1, NumPad2, NumPad3, + NumPad4, NumPad5, NumPad6, NumPad7, + NumPad8, NumPad9, GreyStar, GreyPlus, + Separator, GreyMinus, NumPadPeriod, GreySlash, + F1, F2, F3, F4, + F5, F6, F7, F8, + F9, F10, F11, F12, + F13, F14, F15, F16, + F17, F18, F19, F20, + F21, F22, F23, F24, + Unknown88, Unknown89, Unknown8A, Unknown8B, + Unknown8C, Unknown8D, Unknown8E, Unknown8F, + NumLock, ScrollLock, Unknown92, Unknown93, + Unknown94, Unknown95, Unknown96, Unknown97, + Unknown98, Unknown99, Unknown9A, Unknown9B, + Unknown9C, Unknown9D, Unknown9E, Unknown9F, + LShift, RShift, LControl, RControl, + UnknownA4, UnknownA5, UnknownA6, UnknownA7, + UnknownA8, UnknownA9, UnknownAA, UnknownAB, + UnknownAC, UnknownAD, UnknownAE, UnknownAF, + UnknownB0, UnknownB1, UnknownB2, UnknownB3, + UnknownB4, UnknownB5, UnknownB6, UnknownB7, + UnknownB8, UnknownB9, Semicolon, Equals, + Comma, Minus, Period, Slash, + Tilde, UnknownC1, UnknownC2, UnknownC3, + UnknownC4, UnknownC5, UnknownC6, UnknownC7, + Joy1, Joy2, Joy3, Joy4, + Joy5, Joy6, Joy7, Joy8, + Joy9, Joy10, Joy11, Joy12, + Joy13, Joy14, Joy15, Joy16, + UnknownD8, UnknownD9, UnknownDA, LeftBracket, + Backslash, RightBracket, SingleQuote, UnknownDF, + JoyX, JoyY, JoyZ, JoyR, + MouseX, MouseY, MouseZ, MouseW, + JoyU, JoyV, UnknownEA, UnknownEB, + MouseWheelUp, MouseWheelDown, Unknown10E, Unknown10F, + JoyPovUp, JoyPovDown, JoyPovLeft, JoyPovRight, + UnknownF4, UnknownF5, Attn, CrSel, + ExSel, ErEof, Play, Zoom, + NoName, PA1, OEMClear +}; + +enum class RenderAPI +{ + Unspecified, + Bitmap, + Vulkan, + OpenGL, + D3D11, + D3D12, + Metal +}; + +class DisplayWindow; + +class DisplayWindowHost +{ +public: + virtual void OnWindowPaint() = 0; + virtual void OnWindowMouseMove(const Point& pos) = 0; + virtual void OnWindowMouseLeave() = 0; + virtual void OnWindowMouseDown(const Point& pos, InputKey key) = 0; + virtual void OnWindowMouseDoubleclick(const Point& pos, InputKey key) = 0; + virtual void OnWindowMouseUp(const Point& pos, InputKey key) = 0; + virtual void OnWindowMouseWheel(const Point& pos, InputKey key) = 0; + virtual void OnWindowRawMouseMove(int dx, int dy) = 0; + virtual void OnWindowKeyChar(std::string chars) = 0; + virtual void OnWindowKeyDown(InputKey key) = 0; + virtual void OnWindowKeyUp(InputKey key) = 0; + virtual void OnWindowGeometryChanged() = 0; + virtual void OnWindowClose() = 0; + virtual void OnWindowActivated() = 0; + virtual void OnWindowDeactivated() = 0; + virtual void OnWindowDpiScaleChanged() = 0; +}; + +class DisplayWindow +{ +public: + static std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI); + + static void ProcessEvents(); + static void RunLoop(); + static void ExitLoop(); + + static void* StartTimer(int timeoutMilliseconds, std::function onTimer); + static void StopTimer(void* timerID); + + static Size GetScreenSize(); + + virtual ~DisplayWindow() = default; + + virtual void SetWindowTitle(const std::string& text) = 0; + virtual void SetWindowFrame(const Rect& box) = 0; + virtual void SetClientFrame(const Rect& box) = 0; + virtual void Show() = 0; + virtual void ShowFullscreen() = 0; + virtual void ShowMaximized() = 0; + virtual void ShowMinimized() = 0; + virtual void ShowNormal() = 0; + virtual bool IsWindowFullscreen() = 0; + virtual void Hide() = 0; + virtual void Activate() = 0; + virtual void ShowCursor(bool enable) = 0; + virtual void LockCursor() = 0; + virtual void UnlockCursor() = 0; + virtual void CaptureMouse() = 0; + virtual void ReleaseMouseCapture() = 0; + virtual void Update() = 0; + virtual bool GetKeyState(InputKey key) = 0; + + virtual void SetCursor(StandardCursor cursor) = 0; + + virtual Rect GetWindowFrame() const = 0; + virtual Size GetClientSize() const = 0; + virtual int GetPixelWidth() const = 0; + virtual int GetPixelHeight() const = 0; + virtual double GetDpiScale() const = 0; + + virtual Point MapFromGlobal(const Point& pos) const = 0; + virtual Point MapToGlobal(const Point& pos) const = 0; + + virtual void SetBorderColor(uint32_t bgra8) = 0; + virtual void SetCaptionColor(uint32_t bgra8) = 0; + virtual void SetCaptionTextColor(uint32_t bgra8) = 0; + + virtual void PresentBitmap(int width, int height, const uint32_t* pixels) = 0; + + virtual std::string GetClipboardText() = 0; + virtual void SetClipboardText(const std::string& text) = 0; + + virtual void* GetNativeHandle() = 0; + + virtual std::vector GetVulkanInstanceExtensions() = 0; + virtual VkSurfaceKHR CreateVulkanSurface(VkInstance instance) = 0; +}; + +class DisplayBackend +{ +public: + static DisplayBackend* Get(); + static void Set(std::unique_ptr instance); + + static std::unique_ptr TryCreateWin32(); + static std::unique_ptr TryCreateSDL2(); + static std::unique_ptr TryCreateX11(); + static std::unique_ptr TryCreateWayland(); + + static std::unique_ptr TryCreateBackend(); + + virtual ~DisplayBackend() = default; + + virtual bool IsWin32() { return false; } + virtual bool IsSDL2() { return false; } + virtual bool IsX11() { return false; } + virtual bool IsWayland() { return false; } + + virtual std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) = 0; + virtual void ProcessEvents() = 0; + virtual void RunLoop() = 0; + virtual void ExitLoop() = 0; + + virtual void* StartTimer(int timeoutMilliseconds, std::function onTimer) = 0; + virtual void StopTimer(void* timerID) = 0; + + virtual Size GetScreenSize() = 0; + + virtual std::unique_ptr CreateOpenFileDialog(DisplayWindow* owner); + virtual std::unique_ptr CreateSaveFileDialog(DisplayWindow* owner); + virtual std::unique_ptr CreateOpenFolderDialog(DisplayWindow* owner); +}; diff --git a/include/zwidget/window/x11nativehandle.h b/include/zwidget/window/x11nativehandle.h new file mode 100644 index 000000000..f78247b2f --- /dev/null +++ b/include/zwidget/window/x11nativehandle.h @@ -0,0 +1,13 @@ +#pragma once + +typedef struct _XDisplay Display; +typedef unsigned long XID; +typedef unsigned long VisualID; +typedef XID Window; + +class X11NativeHandle +{ +public: + Display* display = nullptr; + Window window = 0; +}; diff --git a/src/core/canvas.cpp b/src/core/canvas.cpp new file mode 100644 index 000000000..bc72b8f19 --- /dev/null +++ b/src/core/canvas.cpp @@ -0,0 +1,1084 @@ + +#include "core/canvas.h" +#include "core/rect.h" +#include "core/colorf.h" +#include "core/utf8reader.h" +#include "core/resourcedata.h" +#include "core/image.h" +#include "core/truetypefont.h" +#include "core/pathfill.h" +#include "window/window.h" +#include +#include +#include +#include + +#if defined(__SSE2__) || defined(_M_X64) +#include +#define USE_SSE2 +#endif + +//////////////////////////////////////////////////////////////////////////// + +class CanvasGlyph +{ +public: + struct + { + double leftSideBearing = 0.0; + double yOffset = 0.0; + double advanceWidth = 0.0; + } metrics; + + double u = 0.0; + double v = 0.0; + double uvwidth = 0.0f; + double uvheight = 0.0f; + std::shared_ptr texture; +}; + +class CanvasFont +{ +public: + CanvasFont(const std::string& fontname, double height, std::vector data); + ~CanvasFont(); + + CanvasGlyph* getGlyph(Canvas* canvas, uint32_t utfchar); + +private: + std::unique_ptr ttf; + + std::string fontname; + double height = 0.0; + + TrueTypeTextMetrics textmetrics; + std::unordered_map> glyphs; + + friend class CanvasFontGroup; +}; + +class CanvasFontGroup +{ +public: + struct SingleFont + { + std::unique_ptr font; + std::string language; + }; + + CanvasFontGroup(const std::string& fontname, double height); + + CanvasGlyph* getGlyph(Canvas* canvas, uint32_t utfchar, const char* lang = nullptr); + TrueTypeTextMetrics& GetTextMetrics(); + + double height; + std::vector fonts; +}; + +//////////////////////////////////////////////////////////////////////////// + +CanvasFont::CanvasFont(const std::string& fontname, double height, std::vector data) : fontname(fontname), height(height) +{ + auto tdata = std::make_shared(std::move(data)); + ttf = std::make_unique(tdata); + textmetrics = ttf->GetTextMetrics(height); +} + +CanvasFont::~CanvasFont() +{ +} + +CanvasGlyph* CanvasFont::getGlyph(Canvas* canvas, uint32_t utfchar) +{ + uint32_t glyphIndex = ttf->GetGlyphIndex(utfchar); + if (glyphIndex == 0) return nullptr; + + auto& glyph = glyphs[glyphIndex]; + if (glyph) + return glyph.get(); + + glyph = std::make_unique(); + + TrueTypeGlyph ttfglyph = ttf->LoadGlyph(glyphIndex, height); + + // Create final subpixel version + int w = ttfglyph.width; + int h = ttfglyph.height; + int destwidth = (w + 2) / 3; + auto texture = std::make_shared(); + std::vector data(destwidth * h); + + uint8_t* grayscale = ttfglyph.grayscale.get(); + uint32_t* dest = data.data(); + for (int y = 0; y < h; y++) + { + uint8_t* sline = grayscale + y * w; + uint32_t* dline = dest + y * destwidth; + for (int x = 0; x < w; x += 3) + { + uint32_t values[5] = + { + x > 0 ? sline[x - 1] : 0U, + sline[x], + x + 1 < w ? sline[x + 1] : 0U, + x + 2 < w ? sline[x + 2] : 0U, + x + 3 < w ? sline[x + 3] : 0U + }; + + uint32_t red = (values[0] + values[1] + values[1] + values[2] + 2) >> 2; + uint32_t green = (values[1] + values[2] + values[2] + values[3] + 2) >> 2; + uint32_t blue = (values[2] + values[3] + values[3] + values[4] + 2) >> 2; + uint32_t alpha = (red | green | blue) ? 255 : 0; + + *(dline++) = (alpha << 24) | (red << 16) | (green << 8) | blue; + } + } + + glyph->u = 0.0; + glyph->v = 0.0; + glyph->uvwidth = destwidth; + glyph->uvheight = h; + glyph->texture = canvas->createTexture(destwidth, h, data.data(), ImageFormat::B8G8R8A8); + + glyph->metrics.advanceWidth = (ttfglyph.advanceWidth + 2) / 3; + glyph->metrics.leftSideBearing = (ttfglyph.leftSideBearing + 2) / 3; + glyph->metrics.yOffset = ttfglyph.yOffset; + + return glyph.get(); +} + +//////////////////////////////////////////////////////////////////////////// + +CanvasFontGroup::CanvasFontGroup(const std::string& fontname, double height) : height(height) +{ + auto fontdata = LoadWidgetFontData(fontname); + fonts.resize(fontdata.size()); + for (size_t i = 0; i < fonts.size(); i++) + { + fonts[i].font = std::make_unique(fontname, height, fontdata[i].fontdata); + fonts[i].language = fontdata[i].language; + } +} + +CanvasGlyph* CanvasFontGroup::getGlyph(Canvas* canvas, uint32_t utfchar, const char* lang) +{ + for (int i = 0; i < 2; i++) + { + for (auto& fd : fonts) + { + if (i == 1 || lang == nullptr || *lang == 0 || fd.language.empty() || fd.language == lang) + { + auto g = fd.font->getGlyph(canvas, utfchar); + if (g) return g; + } + } + } + + return nullptr; +} + +TrueTypeTextMetrics& CanvasFontGroup::GetTextMetrics() +{ + return fonts[0].font->textmetrics; +} + +//////////////////////////////////////////////////////////////////////////// + +Canvas::Canvas() +{ +} + +Canvas::~Canvas() +{ +} + +void Canvas::attach(DisplayWindow* newWindow) +{ + window = newWindow; + uiscale = window->GetDpiScale(); + uint32_t white = 0xffffffff; + whiteTexture = createTexture(1, 1, &white); + font = std::make_unique("NotoSans", 13.0 * uiscale); +} + +void Canvas::detach() +{ +} + +void Canvas::begin(const Colorf& color) +{ + uiscale = window->GetDpiScale(); + width = window->GetPixelWidth(); + height = window->GetPixelHeight(); +} + +Point Canvas::getOrigin() +{ + return origin; +} + +void Canvas::setOrigin(const Point& newOrigin) +{ + origin = newOrigin; +} + +void Canvas::pushClip(const Rect& box) +{ + if (!clipStack.empty()) + { + const Rect& clip = clipStack.back(); + + double x0 = box.x + origin.x; + double y0 = box.y + origin.y; + double x1 = x0 + box.width; + double y1 = y0 + box.height; + + x0 = std::max(x0, clip.x); + y0 = std::max(y0, clip.y); + x1 = std::min(x1, clip.x + clip.width); + y1 = std::min(y1, clip.y + clip.height); + + if (x0 < x1 && y0 < y1) + clipStack.push_back(Rect::ltrb(x0, y0, x1, y1)); + else + clipStack.push_back(Rect::xywh(0.0, 0.0, 0.0, 0.0)); + } + else + { + clipStack.push_back(box); + } +} + +void Canvas::popClip() +{ + clipStack.pop_back(); +} + +void Canvas::fillRect(const Rect& box, const Colorf& color) +{ + fillTile((float)((origin.x + box.x) * uiscale), (float)((origin.y + box.y) * uiscale), (float)(box.width * uiscale), (float)(box.height * uiscale), color); +} + +void Canvas::drawImage(const std::shared_ptr& image, const Point& pos) +{ + auto& texture = imageTextures[image]; + if (!texture) + { + texture = createTexture(image->GetWidth(), image->GetHeight(), image->GetData(), image->GetFormat()); + } + Colorf color(1.0f, 1.0f, 1.0f); + drawTile(texture.get(), (float)((origin.x + pos.x) * uiscale), (float)((origin.y + pos.y) * uiscale), (float)(texture->Width * uiscale), (float)(texture->Height * uiscale), 0.0, 0.0, (float)texture->Width, (float)texture->Height, color); +} + +void Canvas::drawImage(const std::shared_ptr& image, const Rect& box) +{ + auto& texture = imageTextures[image]; + if (!texture) + { + texture = createTexture(image->GetWidth(), image->GetHeight(), image->GetData(), image->GetFormat()); + } + Colorf color(1.0f, 1.0f, 1.0f); + drawTile(texture.get(), (float)((origin.x + box.x) * uiscale), (float)((origin.y + box.y) * uiscale), (float)(box.width * uiscale), (float)(box.height * uiscale), 0.0, 0.0, (float)texture->Width, (float)texture->Height, color); +} + +void Canvas::line(const Point& p0, const Point& p1, const Colorf& color) +{ + double x0 = origin.x + p0.x; + double y0 = origin.y + p0.y; + double x1 = origin.x + p1.x; + double y1 = origin.y + p1.y; + + if (clipStack.empty())// || (clipStack.back().contains({ x0, y0 }) && clipStack.back().contains({ x1, y1 }))) + { + drawLineUnclipped({ x0, y0 }, { x1, y1 }, color); + } + else + { + const Rect& clip = clipStack.back(); + + if (x0 > x1) + { + std::swap(x0, x1); + std::swap(y0, y1); + } + + if (x1 < clip.x || x0 >= clip.x + clip.width) + return; + + // Clip left edge + if (x0 < clip.x) + { + double dx = x1 - x0; + double dy = y1 - y0; + if (std::abs(dx) < 0.0001) + return; + y0 = y0 + (clip.x - x0) * dy / dx; + x0 = clip.x; + } + + // Clip right edge + if (x1 > clip.x + clip.width) + { + double dx = x1 - x0; + double dy = y1 - y0; + if (std::abs(dx) < 0.0001) + return; + y1 = y1 + (clip.x + clip.width - x1) * dy / dx; + x1 = clip.x + clip.width; + } + + if (y0 > y1) + { + std::swap(x0, x1); + std::swap(y0, y1); + } + + if (y1 < clip.y || y0 >= clip.y + clip.height) + return; + + // Clip top edge + if (y0 < clip.y) + { + double dx = x1 - x0; + double dy = y1 - y0; + if (std::abs(dy) < 0.0001) + return; + x0 = x0 + (clip.y - y0) * dx / dy; + y0 = clip.y; + } + + // Clip bottom edge + if (y1 > clip.y + clip.height) + { + double dx = x1 - x0; + double dy = y1 - y0; + if (std::abs(dy) < 0.0001) + return; + x1 = x1 + (clip.y + clip.height - y1) * dx / dy; + y1 = clip.y + clip.height; + } + + x0 = clamp(x0, clip.x, clip.x + clip.width); + x1 = clamp(x1, clip.x, clip.x + clip.width); + y0 = clamp(y0, clip.y, clip.y + clip.height); + y1 = clamp(y1, clip.y, clip.y + clip.height); + + if (x0 != x1 || y0 != y1) + drawLineUnclipped({ x0, y0 }, { x1, y1 }, color); + } +} + +void Canvas::drawText(const Point& pos, const Colorf& color, const std::string& text) +{ + double x = std::round((origin.x + pos.x) * uiscale); + double y = std::round((origin.y + pos.y) * uiscale); + + UTF8Reader reader(text.data(), text.size()); + while (!reader.is_end()) + { + CanvasGlyph* glyph = font->getGlyph(this, reader.character(), language.c_str()); + if (!glyph || !glyph->texture) + { + glyph = font->getGlyph(this, 32); + } + + if (glyph->texture) + { + double gx = std::round(x + glyph->metrics.leftSideBearing); + double gy = std::round(y + glyph->metrics.yOffset); + drawGlyph(glyph->texture.get(), (float)std::round(gx), (float)std::round(gy), (float)glyph->uvwidth, (float)glyph->uvheight, (float)glyph->u, (float)glyph->v, (float)glyph->uvwidth, (float)glyph->uvheight, color); + } + + x += std::round(glyph->metrics.advanceWidth); + reader.next(); + } +} + +void Canvas::drawText(const std::shared_ptr& font, const Point& pos, const std::string& text, const Colorf& color) +{ + drawText(pos, color, text); +} + +void Canvas::drawTextEllipsis(const std::shared_ptr& font, const Point& pos, const Rect& clipBox, const std::string& text, const Colorf& color) +{ + drawText(pos, color, text); +} + +Rect Canvas::measureText(const std::shared_ptr& font, const std::string& text) +{ + return measureText(text); +} + +Rect Canvas::measureText(const std::string& text) +{ + double x = 0.0; + double y = font->GetTextMetrics().ascender - font->GetTextMetrics().descender; + + UTF8Reader reader(text.data(), text.size()); + while (!reader.is_end()) + { + CanvasGlyph* glyph = font->getGlyph(this, reader.character(), language.c_str()); + if (!glyph || !glyph->texture) + { + glyph = font->getGlyph(this, 32); + } + + x += std::round(glyph->metrics.advanceWidth); + reader.next(); + } + + return Rect::xywh(0.0, 0.0, x / uiscale, y / uiscale); +} + +FontMetrics Canvas::getFontMetrics(const std::shared_ptr& font) +{ + VerticalTextPosition vtp = verticalTextAlign(); + FontMetrics metrics; + metrics.external_leading = vtp.top; + metrics.ascent = vtp.baseline - vtp.top; + metrics.descent = vtp.bottom - vtp.baseline; + metrics.height = metrics.ascent + metrics.descent; + return metrics; +} + +int Canvas::getCharacterIndex(const std::shared_ptr& font, const std::string& text, const Point& hitPoint) +{ + return 0; +} + +VerticalTextPosition Canvas::verticalTextAlign() +{ + VerticalTextPosition align; + align.top = 0.0f; + auto tm = font->GetTextMetrics(); + align.baseline = tm.ascender / uiscale; + align.bottom = (tm.ascender - tm.descender) / uiscale; + return align; +} + +void Canvas::drawLineUnclipped(const Point& p0, const Point& p1, const Colorf& color) +{ + if (p0.x == p1.x) + { + fillTile((float)((p0.x - 0.5) * uiscale), (float)(p0.y * uiscale), (float)uiscale, (float)((p1.y - p0.y) * uiscale), color); + } + else if (p0.y == p1.y) + { + fillTile((float)(p0.x * uiscale), (float)((p0.y - 0.5) * uiscale), (float)((p1.x - p0.x) * uiscale), (float)uiscale, color); + } + else + { + drawLineAntialiased((float)(p0.x * uiscale), (float)(p0.y * uiscale), (float)(p1.x * uiscale), (float)(p1.y * uiscale), color); + } +} + +int Canvas::getClipMinX() const +{ + return clipStack.empty() ? 0 : (int)std::round(std::max(clipStack.back().x * uiscale, 0.0)); +} + +int Canvas::getClipMinY() const +{ + return clipStack.empty() ? 0 : (int)std::round(std::max(clipStack.back().y * uiscale, 0.0)); +} + +int Canvas::getClipMaxX() const +{ + return clipStack.empty() ? width : (int)std::round(std::min((clipStack.back().x + clipStack.back().width) * uiscale, (double)width)); +} + +int Canvas::getClipMaxY() const +{ + return clipStack.empty() ? height : (int)std::round(std::min((clipStack.back().y + clipStack.back().height) * uiscale, (double)height)); +} + +///////////////////////////////////////////////////////////////////////////// + +class BitmapTexture : public CanvasTexture +{ +public: + std::vector Data; +}; + +class BitmapCanvas : public Canvas +{ +public: + void begin(const Colorf& color) override; + void end() override; + + void fillTile(float x, float y, float width, float height, Colorf color) override; + void drawTile(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) override; + void drawGlyph(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) override; + void drawLineAntialiased(float x0, float y0, float x1, float y1, Colorf color) override; + void plot(float x, float y, float alpha, const Colorf& color); + + std::unique_ptr createTexture(int width, int height, const void* pixels, ImageFormat format = ImageFormat::B8G8R8A8) override; + + std::vector pixels; +}; + +std::unique_ptr BitmapCanvas::createTexture(int width, int height, const void* pixels, ImageFormat format) +{ + auto texture = std::make_unique(); + texture->Width = width; + texture->Height = height; + texture->Data.resize(width * height); + if (format == ImageFormat::B8G8R8A8) + { + memcpy(texture->Data.data(), pixels, width * height * sizeof(uint32_t)); + } + else + { + const uint32_t* src = (const uint32_t*)pixels; + uint32_t* dest = texture->Data.data(); + int count = width * height; + for (int i = 0; i < count; i++) + { + uint32_t a = (src[i] >> 24) & 0xff; + uint32_t b = (src[i] >> 16) & 0xff; + uint32_t g = (src[i] >> 8) & 0xff; + uint32_t r = src[i] & 0xff; + dest[i] = (a << 24) | (r << 16) | (g << 8) | b; + } + } + return texture; +} + +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 cred = (int32_t)clamp(color.r * 256.0f, 0.0f, 256.0f); + uint32_t cgreen = (int32_t)clamp(color.g * 256.0f, 0.0f, 256.0f); + uint32_t cblue = (int32_t)clamp(color.b * 256.0f, 0.0f, 256.0f); + uint32_t calpha = (int32_t)clamp(color.a * alpha * 256.0f, 0.0f, 256.0f); + uint32_t invalpha = 256 - calpha; + + uint32_t dpixel = *dest; + uint32_t dalpha = dpixel >> 24; + uint32_t dred = (dpixel >> 16) & 0xff; + uint32_t dgreen = (dpixel >> 8) & 0xff; + uint32_t dblue = dpixel & 0xff; + + // dest.rgba = color.rgba + dest.rgba * (1-color.a) + uint32_t a = (calpha * calpha + dalpha * invalpha + 127) >> 8; + uint32_t r = (cred * calpha + dred * invalpha + 127) >> 8; + uint32_t g = (cgreen * calpha + dgreen * invalpha + 127) >> 8; + uint32_t b = (cblue * calpha + dblue * invalpha + 127) >> 8; + *dest = (a << 24) | (r << 16) | (g << 8) | b; +} + +static float fpart(float x) +{ + return x - std::floor(x); +} + +static float rfpart(float x) +{ + return 1 - fpart(x); +} + +void BitmapCanvas::drawLineAntialiased(float x0, float y0, float x1, float y1, Colorf color) +{ + bool steep = std::abs(y1 - y0) > std::abs(x1 - x0); + + if (steep) + { + std::swap(x0, y0); + std::swap(x1, y1); + } + + if (x0 > x1) + { + std::swap(x0, x1); + std::swap(y0, y1); + } + + float dx = x1 - x0; + float dy = y1 - y0; + float gradient = (dx == 0.0f) ? 1.0f : dy / dx; + + // handle first endpoint + float xend = std::round(x0); + float yend = y0 + gradient * (xend - x0); + float xgap = rfpart(x0 + 0.5f); + float xpxl1 = xend; // this will be used in the main loop + float ypxl1 = std::floor(yend); + if (steep) + { + plot(ypxl1, xpxl1, rfpart(yend) * xgap, color); + plot(ypxl1 + 1, xpxl1, fpart(yend) * xgap, color); + } + else + { + plot(xpxl1, ypxl1, rfpart(yend) * xgap, color); + plot(xpxl1, ypxl1 + 1, fpart(yend) * xgap, color); + } + float intery = yend + gradient; // first y-intersection for the main loop + + // handle second endpoint + xend = std::floor(x1 + 0.5f); + yend = y1 + gradient * (xend - x1); + xgap = fpart(x1 + 0.5f); + float xpxl2 = xend; // this will be used in the main loop + float ypxl2 = std::floor(yend); + if (steep) + { + plot(ypxl2, xpxl2, rfpart(yend) * xgap, color); + plot(ypxl2 + 1.0f, xpxl2, fpart(yend) * xgap, color); + } + else + { + plot(xpxl2, ypxl2, rfpart(yend) * xgap, color); + plot(xpxl2, ypxl2 + 1.0f, fpart(yend) * xgap, color); + } + + // main loop + if (steep) + { + for (float x = xpxl1 + 1.0f; x <= xpxl2 - 1.0f; x++) + { + plot(std::floor(intery), x, rfpart(intery), color); + plot(std::floor(intery) + 1.0f, x, fpart(intery), color); + intery = intery + gradient; + } + } + else + { + for (float x = xpxl1 + 1.0f; x <= xpxl2 - 1.0f; x++) + { + plot(x, std::floor(intery), rfpart(intery), color); + plot(x, std::floor(intery) + 1, fpart(intery), color); + intery = intery + gradient; + } + } +} + +void BitmapCanvas::fillTile(float left, float top, float width, float height, Colorf color) +{ + if (width <= 0.0f || height <= 0.0f || color.a <= 0.0f) + return; + + int dwidth = this->width; + int dheight = this->height; + uint32_t* dest = this->pixels.data(); + + int x0 = (int)left; + int x1 = (int)(left + width); + int y0 = (int)top; + int y1 = (int)(top + height); + + x0 = std::max(x0, getClipMinX()); + y0 = std::max(y0, getClipMinY()); + x1 = std::min(x1, getClipMaxX()); + y1 = std::min(y1, getClipMaxY()); + if (x1 <= x0 || y1 <= y0) + return; + + uint32_t cred = (int32_t)clamp(color.r * 255.0f, 0.0f, 255.0f); + uint32_t cgreen = (int32_t)clamp(color.g * 255.0f, 0.0f, 255.0f); + uint32_t cblue = (int32_t)clamp(color.b * 255.0f, 0.0f, 255.0f); + uint32_t calpha = (int32_t)clamp(color.a * 255.0f, 0.0f, 255.0f); + uint32_t invalpha = 256 - (calpha + (calpha >> 7)); + + if (invalpha == 0) // Solid fill + { + uint32_t c = (calpha << 24) | (cred << 16) | (cgreen << 8) | cblue; +#ifdef USE_SSE2 + __m128i cargb = _mm_set1_epi32(c); +#endif + + for (int y = y0; y < y1; y++) + { + uint32_t* dline = dest + y * dwidth; + + int x = x0; +#ifdef USE_SSE2 + int ssex1 = x0 + (((x1 - x0) >> 2) << 2); + while (x < ssex1) + { + _mm_storeu_si128((__m128i*)(dline + x), cargb); + x += 4; + } +#endif + + while (x < x1) + { + dline[x] = c; + x++; + } + } + } + else // Alpha blended fill + { + cred <<= 8; + cgreen <<= 8; + cblue <<= 8; + calpha <<= 8; +#ifdef USE_SSE2 + __m128i cargb = _mm_set_epi16(calpha, cred, cgreen, cblue, calpha, cred, cgreen, cblue); + __m128i cinvalpha = _mm_set1_epi16(invalpha); +#endif + + for (int y = y0; y < y1; y++) + { + uint32_t* dline = dest + y * dwidth; + + int x = x0; +#ifdef USE_SSE2 + int ssex1 = x0 + (((x1 - x0) >> 1) << 1); + while (x < ssex1) + { + __m128i dpixel = _mm_loadl_epi64((const __m128i*)(dline + x)); + dpixel = _mm_unpacklo_epi8(dpixel, _mm_setzero_si128()); + + // dest.rgba = color.rgba + dest.rgba * (1-color.a) + __m128i result = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(cargb, _mm_mullo_epi16(dpixel, cinvalpha)), _mm_set1_epi16(127)), 8); + _mm_storel_epi64((__m128i*)(dline + x), _mm_packus_epi16(result, _mm_setzero_si128())); + x += 2; + } +#endif + + while (x < x1) + { + uint32_t dpixel = dline[x]; + uint32_t dalpha = dpixel >> 24; + uint32_t dred = (dpixel >> 16) & 0xff; + uint32_t dgreen = (dpixel >> 8) & 0xff; + uint32_t dblue = dpixel & 0xff; + + // dest.rgba = color.rgba + dest.rgba * (1-color.a) + uint32_t a = (calpha + dalpha * invalpha + 127) >> 8; + uint32_t r = (cred + dred * invalpha + 127) >> 8; + uint32_t g = (cgreen + dgreen * invalpha + 127) >> 8; + uint32_t b = (cblue + dblue * invalpha + 127) >> 8; + dline[x] = (a << 24) | (r << 16) | (g << 8) | b; + x++; + } + } + } +} + +void BitmapCanvas::drawTile(CanvasTexture* tex, float left, float top, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) +{ + if (width <= 0.0f || height <= 0.0f || color.a <= 0.0f) + return; + + auto texture = static_cast(tex); + int swidth = texture->Width; + int sheight = texture->Height; + const uint32_t* src = texture->Data.data(); + + int dwidth = this->width; + int dheight = this->height; + uint32_t* dest = this->pixels.data(); + + int x0 = (int)left; + int x1 = (int)(left + width); + int y0 = (int)top; + int y1 = (int)(top + height); + + x0 = std::max(x0, getClipMinX()); + y0 = std::max(y0, getClipMinY()); + x1 = std::min(x1, getClipMaxX()); + y1 = std::min(y1, getClipMaxY()); + if (x1 <= x0 || y1 <= y0) + return; + + uint32_t cred = (int32_t)clamp(color.r * 256.0f, 0.0f, 256.0f); + uint32_t cgreen = (int32_t)clamp(color.g * 256.0f, 0.0f, 256.0f); + uint32_t cblue = (int32_t)clamp(color.b * 256.0f, 0.0f, 256.0f); + uint32_t calpha = (int32_t)clamp(color.a * 256.0f, 0.0f, 256.0f); +#ifdef USE_SSE2 + __m128i cargb = _mm_set_epi16(calpha, cred, cgreen, cblue, calpha, cred, cgreen, cblue); +#endif + + float uscale = uvwidth / width; + float vscale = uvheight / height; + + for (int y = y0; y < y1; y++) + { + float vpix = v + vscale * (y + 0.5f - top); + const uint32_t* sline = src + ((int)vpix) * swidth; + uint32_t* dline = dest + y * dwidth; + + int x = x0; +#ifdef USE_SSE2 + int ssex1 = x0 + (((x1 - x0) >> 1) << 1); + while (x < ssex1) + { + float upix0 = u + uscale * (x + 0.5f - left); + float upix1 = u + uscale * (x + 1 + 0.5f - left); + uint32_t spixel0 = sline[(int)upix0]; + uint32_t spixel1 = sline[(int)upix1]; + __m128i spixel = _mm_set_epi32(0, 0, spixel1, spixel0); + spixel = _mm_unpacklo_epi8(spixel, _mm_setzero_si128()); + + __m128i dpixel = _mm_loadl_epi64((const __m128i*)(dline + x)); + dpixel = _mm_unpacklo_epi8(dpixel, _mm_setzero_si128()); + + // Pixel shade + spixel = _mm_srli_epi16(_mm_add_epi16(_mm_mullo_epi16(spixel, cargb), _mm_set1_epi16(127)), 8); + + // Rescale from [0,255] to [0,256] + __m128i sa = _mm_shufflehi_epi16(_mm_shufflelo_epi16(spixel, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)); + sa = _mm_add_epi16(sa, _mm_srli_epi16(sa, 7)); + __m128i sinva = _mm_sub_epi16(_mm_set1_epi16(256), sa); + + // dest.rgba = color.rgba * src.rgba * src.a + dest.rgba * (1-src.a) + __m128i result = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(spixel, sa), _mm_mullo_epi16(dpixel, sinva)), _mm_set1_epi16(127)), 8); + _mm_storel_epi64((__m128i*)(dline + x), _mm_packus_epi16(result, _mm_setzero_si128())); + x += 2; + } +#endif + + while (x < x1) + { + float upix = u + uscale * (x + 0.5f - left); + uint32_t spixel = sline[(int)upix]; + uint32_t salpha = spixel >> 24; + uint32_t sred = (spixel >> 16) & 0xff; + uint32_t sgreen = (spixel >> 8) & 0xff; + uint32_t sblue = spixel & 0xff; + + uint32_t dpixel = dline[x]; + uint32_t dalpha = dpixel >> 24; + uint32_t dred = (dpixel >> 16) & 0xff; + uint32_t dgreen = (dpixel >> 8) & 0xff; + uint32_t dblue = dpixel & 0xff; + + // Pixel shade + sred = (cred * sred + 127) >> 8; + sgreen = (cgreen * sgreen + 127) >> 8; + sblue = (cblue * sblue + 127) >> 8; + salpha = (calpha * salpha + 127) >> 8; + + // Rescale from [0,255] to [0,256] + uint32_t sa = salpha + (salpha >> 7); + uint32_t sinva = 256 - sa; + + // dest.rgba = color.rgba * src.rgba * src.a + dest.rgba * (1-src.a) + uint32_t a = (salpha * sa + dalpha * sinva + 127) >> 8; + uint32_t r = (sred * sa + dred * sinva + 127) >> 8; + uint32_t g = (sgreen * sa + dgreen * sinva + 127) >> 8; + uint32_t b = (sblue * sa + dblue * sinva + 127) >> 8; + dline[x] = (a << 24) | (r << 16) | (g << 8) | b; + x++; + } + } +} + +void BitmapCanvas::drawGlyph(CanvasTexture* tex, float left, float top, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) +{ + if (width <= 0.0f || height <= 0.0f) + return; + + auto texture = static_cast(tex); + int swidth = texture->Width; + int sheight = texture->Height; + const uint32_t* src = texture->Data.data(); + + int dwidth = this->width; + int dheight = this->height; + uint32_t* dest = this->pixels.data(); + + int x0 = (int)left; + int x1 = (int)(left + width); + int y0 = (int)top; + int y1 = (int)(top + height); + + x0 = std::max(x0, getClipMinX()); + y0 = std::max(y0, getClipMinY()); + x1 = std::min(x1, getClipMaxX()); + y1 = std::min(y1, getClipMaxY()); + if (x1 <= x0 || y1 <= y0) + return; + +#if 1 // Use gamma correction + + // To linear + float cred = color.r * color.r; // std::pow(color.r, 2.2f); + float cgreen = color.g * color.g; // std::pow(color.g, 2.2f); + float cblue = color.b * color.b; // std::pow(color.b, 2.2f); +#ifdef USE_SSE2 + __m128 crgba = _mm_set_ps(0.0f, cred, cgreen, cblue); +#endif + + float uscale = uvwidth / width; + float vscale = uvheight / height; + + for (int y = y0; y < y1; y++) + { + float vpix = v + vscale * (y + 0.5f - top); + const uint32_t* sline = src + ((int)vpix) * swidth; + uint32_t* dline = dest + y * dwidth; + + int x = x0; +#ifdef USE_SSE2 + while (x < x1) + { + float upix = u + uscale * (x + 0.5f - left); + __m128i spixel = _mm_cvtsi32_si128(sline[(int)upix]); + spixel = _mm_unpacklo_epi8(spixel, _mm_setzero_si128()); + spixel = _mm_unpacklo_epi16(spixel, _mm_setzero_si128()); + __m128 srgba = _mm_mul_ps(_mm_cvtepi32_ps(spixel), _mm_set_ps1(1.0f / 255.0f)); + + __m128i dpixel = _mm_cvtsi32_si128(dline[x]); + dpixel = _mm_unpacklo_epi8(dpixel, _mm_setzero_si128()); + dpixel = _mm_unpacklo_epi16(dpixel, _mm_setzero_si128()); + __m128 drgba = _mm_mul_ps(_mm_cvtepi32_ps(dpixel), _mm_set_ps1(1.0f / 255.0f)); + + // To linear + drgba = _mm_mul_ps(drgba, drgba); + + // dest.rgb = color.rgb * src.rgb + dest.rgb * (1-src.rgb) + __m128 frgba = _mm_add_ps(_mm_mul_ps(crgba, srgba), _mm_mul_ps(drgba, _mm_sub_ps(_mm_set_ps1(1.0f), srgba))); + + // To srgb + frgba = _mm_sqrt_ps(frgba); + + __m128i rgba = _mm_cvtps_epi32(_mm_add_ps(_mm_mul_ps(frgba, _mm_set_ps1(255.0f)), _mm_set_ps1(0.5f))); + rgba = _mm_packs_epi32(rgba, _mm_setzero_si128()); + rgba = _mm_packus_epi16(rgba, _mm_setzero_si128()); + dline[x] = ((uint32_t)_mm_cvtsi128_si32(rgba)) | 0xff000000; + x++; + } +#else + while (x < x1) + { + float upix = u + uscale * (x + 0.5f - left); + uint32_t spixel = sline[(int)upix]; + float sred = ((spixel >> 16) & 0xff) * (1.0f / 255.0f); + float sgreen = ((spixel >> 8) & 0xff) * (1.0f / 255.0f); + float sblue = (spixel & 0xff) * (1.0f / 255.0f); + + uint32_t dpixel = dline[x]; + float dred = ((dpixel >> 16) & 0xff) * (1.0f / 255.0f); + float dgreen = ((dpixel >> 8) & 0xff) * (1.0f / 255.0f); + float dblue = (dpixel & 0xff) * (1.0f / 255.0f); + + // To linear + dred = dred * dred; // std::pow(dred, 2.2f); + dgreen = dgreen * dgreen; // std::pow(dgreen, 2.2f); + dblue = dblue * dblue; // std::pow(dblue, 2.2f); + + // dest.rgb = color.rgb * src.rgb + dest.rgb * (1-src.rgb) + double fr = cred * sred + dred * (1.0f - sred); + double fg = cgreen * sgreen + dgreen * (1.0f - sgreen); + double fb = cblue * sblue + dblue * (1.0f - sblue); + + // To srgb + fr = std::sqrt(fr); // std::pow(fr, 1.0f / 2.2f); + fg = std::sqrt(fg); // std::pow(fg, 1.0f / 2.2f); + fb = std::sqrt(fb); // std::pow(fb, 1.0f / 2.2f); + + uint32_t r = (int)(fr * 255.0f + 0.5f); + uint32_t g = (int)(fg * 255.0f + 0.5f); + uint32_t b = (int)(fb * 255.0f + 0.5f); + dline[x] = 0xff000000 | (r << 16) | (g << 8) | b; + x++; + } +#endif + } + +#else + + uint32_t cred = (int32_t)clamp(color.r * 255.0f, 0.0f, 255.0f); + uint32_t cgreen = (int32_t)clamp(color.g * 255.0f, 0.0f, 255.0f); + uint32_t cblue = (int32_t)clamp(color.b * 255.0f, 0.0f, 255.0f); +#ifdef USE_SSE2 + __m128i crgba = _mm_set_epi16(0, cred, cgreen, cblue, 0, cred, cgreen, cblue); +#endif + + float uscale = uvwidth / width; + float vscale = uvheight / height; + + for (int y = y0; y < y1; y++) + { + float vpix = v + vscale * (y + 0.5f - top); + const uint32_t* sline = src + ((int)vpix) * swidth; + uint32_t* dline = dest + y * dwidth; + + int x = x0; +#ifdef USE_SSE2 + int ssex1 = x0 + (((x1 - x0) >> 1) << 1); + while (x < ssex1) + { + float upix0 = u + uscale * (x + 0.5f - left); + float upix1 = u + uscale * (x + 1 + 0.5f - left); + uint32_t spixel0 = sline[(int)upix0]; + uint32_t spixel1 = sline[(int)upix1]; + __m128i spixel = _mm_set_epi32(0, 0, spixel1, spixel0); + spixel = _mm_unpacklo_epi8(spixel, _mm_setzero_si128()); + + __m128i dpixel = _mm_loadl_epi64((const __m128i*)(dline + x)); + dpixel = _mm_unpacklo_epi8(dpixel, _mm_setzero_si128()); + + // Rescale from [0,255] to [0,256] + spixel = _mm_add_epi16(spixel, _mm_srli_epi16(spixel, 7)); + + // dest.rgb = color.rgb * src.rgb + dest.rgb * (1-src.rgb) + __m128i result = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(crgba, spixel), _mm_mullo_epi16(dpixel, _mm_sub_epi16(_mm_set1_epi16(256), spixel))), _mm_set1_epi16(127)), 8); + _mm_storel_epi64((__m128i*)(dline + x), _mm_or_si128(_mm_packus_epi16(result, _mm_setzero_si128()), _mm_set1_epi32(0xff000000))); + x += 2; + } +#endif + + while (x < x1) + { + float upix = u + uscale * (x + 0.5f - left); + uint32_t spixel = sline[(int)upix]; + uint32_t sred = (spixel >> 16) & 0xff; + uint32_t sgreen = (spixel >> 8) & 0xff; + uint32_t sblue = spixel & 0xff; + + uint32_t dpixel = dline[x]; + uint32_t dred = (dpixel >> 16) & 0xff; + uint32_t dgreen = (dpixel >> 8) & 0xff; + uint32_t dblue = dpixel & 0xff; + + // Rescale from [0,255] to [0,256] + sred += sred >> 7; + sgreen += sgreen >> 7; + sblue += sblue >> 7; + + // dest.rgb = color.rgb * src.rgb + dest.rgb * (1-src.rgb) + uint32_t r = (cred * sred + dred * (256 - sred) + 127) >> 8; + uint32_t g = (cgreen * sgreen + dgreen * (256 - sgreen) + 127) >> 8; + uint32_t b = (cblue * sblue + dblue * (256 - sblue) + 127) >> 8; + dline[x] = 0xff000000 | (r << 16) | (g << 8) | b; + x++; + } + } +#endif +} + +void BitmapCanvas::begin(const Colorf& color) +{ + Canvas::begin(color); + + uint32_t r = (int32_t)clamp(color.r * 255.0f, 0.0f, 255.0f); + uint32_t g = (int32_t)clamp(color.g * 255.0f, 0.0f, 255.0f); + uint32_t b = (int32_t)clamp(color.b * 255.0f, 0.0f, 255.0f); + uint32_t a = (int32_t)clamp(color.a * 255.0f, 0.0f, 255.0f); + uint32_t bgcolor = (a << 24) | (r << 16) | (g << 8) | b; + pixels.clear(); + pixels.resize(width * height, bgcolor); +} + +void BitmapCanvas::end() +{ + window->PresentBitmap(width, height, pixels.data()); +} + +///////////////////////////////////////////////////////////////////////////// + +std::unique_ptr Canvas::create() +{ + return std::make_unique(); +} diff --git a/src/core/font.cpp b/src/core/font.cpp new file mode 100644 index 000000000..f646912a9 --- /dev/null +++ b/src/core/font.cpp @@ -0,0 +1,28 @@ + +#include "core/font.h" + +class FontImpl : public Font +{ +public: + FontImpl(const std::string& name, double height) : Name(name), Height(height) + { + } + + const std::string& GetName() const override + { + return Name; + } + + double GetHeight() const override + { + return Height; + } + + std::string Name; + double Height = 0.0; +}; + +std::shared_ptr Font::Create(const std::string& name, double height) +{ + return std::make_shared(name, height); +} diff --git a/src/core/image.cpp b/src/core/image.cpp new file mode 100644 index 000000000..3998d78e8 --- /dev/null +++ b/src/core/image.cpp @@ -0,0 +1,110 @@ + +#include "core/image.h" +#include "core/resourcedata.h" +#include "picopng/picopng.h" +#include "nanosvg/nanosvg.h" +#include "nanosvg/nanosvgrast.h" +#include +#include + +class ImageImpl : public Image +{ +public: + ImageImpl(int width, int height, ImageFormat format, const void* data) : Width(width), Height(height), Format(format) + { + Data = std::make_unique(width * height); + if (data) + memcpy(Data.get(), data, width * height * sizeof(uint32_t)); + } + + int GetWidth() const override + { + return Width; + } + + int GetHeight() const override + { + return Height; + } + + ImageFormat GetFormat() const override + { + return Format; + } + + void* GetData() const override + { + return Data.get(); + } + + int Width = 0; + int Height = 0; + ImageFormat Format = {}; + std::unique_ptr Data; +}; + +std::shared_ptr Image::Create(int width, int height, ImageFormat format, const void* data) +{ + return std::make_shared(width, height, format, data); +} + +std::shared_ptr Image::LoadResource(const std::string& resourcename, double dpiscale) +{ + size_t extensionpos = resourcename.find_last_of("./\\"); + if (extensionpos == std::string::npos || resourcename[extensionpos] != '.') + throw std::runtime_error("Unsupported image format"); + std::string extension = resourcename.substr(extensionpos + 1); + for (char& c : extension) + { + if (c >= 'A' && c <= 'Z') + c = c - 'A' + 'a'; + } + + if (extension == "png") + { + auto filedata = LoadWidgetData(resourcename); + + std::vector pixels; + unsigned long width = 0, height = 0; + int result = decodePNG(pixels, width, height, (const unsigned char*)filedata.data(), filedata.size(), true); + if (result != 0) + throw std::runtime_error("Could not decode PNG file"); + + return Image::Create(width, height, ImageFormat::R8G8B8A8, pixels.data()); + } + else if (extension == "svg") + { + auto filedata = LoadWidgetData(resourcename); + filedata.push_back(0); + + NSVGimage* svgimage = nsvgParse((char*)filedata.data(), "px", (float)(96.0 * dpiscale)); + if (!svgimage) + throw std::runtime_error("Could not parse SVG file"); + + try + { + int width = (int)(svgimage->width * dpiscale); + int height = (int)(svgimage->height * dpiscale); + std::shared_ptr image = Image::Create(width, height, ImageFormat::R8G8B8A8, nullptr); + + NSVGrasterizer* rast = nsvgCreateRasterizer(); + if (!rast) + throw std::runtime_error("Could not create SVG rasterizer"); + + nsvgRasterize(rast, svgimage, 0.0f, 0.0f, (float)dpiscale, (unsigned char*)image->GetData(), width, height, width * 4); + + nsvgDeleteRasterizer(rast); + nsvgDelete(svgimage); + return image; + } + catch (...) + { + nsvgDelete(svgimage); + throw; + } + } + else + { + throw std::runtime_error("Unsupported image format"); + } +} diff --git a/src/core/nanosvg/nanosvg.cpp b/src/core/nanosvg/nanosvg.cpp new file mode 100644 index 000000000..764b854b0 --- /dev/null +++ b/src/core/nanosvg/nanosvg.cpp @@ -0,0 +1,6 @@ + +#define NANOSVG_IMPLEMENTATION +#include "nanosvg.h" + +#define NANOSVGRAST_IMPLEMENTATION +#include "nanosvgrast.h" diff --git a/src/core/nanosvg/nanosvg.h b/src/core/nanosvg/nanosvg.h new file mode 100644 index 000000000..55f9bc5ea --- /dev/null +++ b/src/core/nanosvg/nanosvg.h @@ -0,0 +1,3107 @@ +/* + * Copyright (c) 2013-14 Mikko Mononen memon@inside.org + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example + * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/) + * + * Arc calculation code based on canvg (https://code.google.com/p/canvg/) + * + * Bounding box calculation based on http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html + * + */ + +#ifndef NANOSVG_H +#define NANOSVG_H + +#ifndef NANOSVG_CPLUSPLUS +#ifdef __cplusplus +extern "C" { +#endif +#endif + +// NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of cubic bezier shapes. +// +// The library suits well for anything from rendering scalable icons in your editor application to prototyping a game. +// +// NanoSVG supports a wide range of SVG features, but something may be missing, feel free to create a pull request! +// +// The shapes in the SVG images are transformed by the viewBox and converted to specified units. +// That is, you should get the same looking data as your designed in your favorite app. +// +// NanoSVG can return the paths in few different units. For example if you want to render an image, you may choose +// to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you may want to use millimeters. +// +// The units passed to NanoSVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'. +// DPI (dots-per-inch) controls how the unit conversion is done. +// +// If you don't know or care about the units stuff, "px" and 96 should get you going. + + +/* Example Usage: + // Load SVG + NSVGimage* image; + image = nsvgParseFromFile("test.svg", "px", 96); + printf("size: %f x %f\n", image->width, image->height); + // Use... + for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) { + for (NSVGpath *path = shape->paths; path != NULL; path = path->next) { + for (int i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + drawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]); + } + } + } + // Delete + nsvgDelete(image); +*/ + +enum NSVGpaintType { + NSVG_PAINT_UNDEF = -1, + NSVG_PAINT_NONE = 0, + NSVG_PAINT_COLOR = 1, + NSVG_PAINT_LINEAR_GRADIENT = 2, + NSVG_PAINT_RADIAL_GRADIENT = 3 +}; + +enum NSVGspreadType { + NSVG_SPREAD_PAD = 0, + NSVG_SPREAD_REFLECT = 1, + NSVG_SPREAD_REPEAT = 2 +}; + +enum NSVGlineJoin { + NSVG_JOIN_MITER = 0, + NSVG_JOIN_ROUND = 1, + NSVG_JOIN_BEVEL = 2 +}; + +enum NSVGlineCap { + NSVG_CAP_BUTT = 0, + NSVG_CAP_ROUND = 1, + NSVG_CAP_SQUARE = 2 +}; + +enum NSVGfillRule { + NSVG_FILLRULE_NONZERO = 0, + NSVG_FILLRULE_EVENODD = 1 +}; + +enum NSVGflags { + NSVG_FLAGS_VISIBLE = 0x01 +}; + +typedef struct NSVGgradientStop { + unsigned int color; + float offset; +} NSVGgradientStop; + +typedef struct NSVGgradient { + float xform[6]; + char spread; + float fx, fy; + int nstops; + NSVGgradientStop stops[1]; +} NSVGgradient; + +typedef struct NSVGpaint { + signed char type; + union { + unsigned int color; + NSVGgradient* gradient; + }; +} NSVGpaint; + +typedef struct NSVGpath +{ + float* pts; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ... + int npts; // Total number of bezier points. + char closed; // Flag indicating if shapes should be treated as closed. + float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. + struct NSVGpath* next; // Pointer to next path, or NULL if last element. +} NSVGpath; + +typedef struct NSVGshape +{ + char id[64]; // Optional 'id' attr of the shape or its group + NSVGpaint fill; // Fill paint + NSVGpaint stroke; // Stroke paint + float opacity; // Opacity of the shape. + float strokeWidth; // Stroke width (scaled). + float strokeDashOffset; // Stroke dash offset (scaled). + float strokeDashArray[8]; // Stroke dash array (scaled). + char strokeDashCount; // Number of dash values in dash array. + char strokeLineJoin; // Stroke join type. + char strokeLineCap; // Stroke cap type. + float miterLimit; // Miter limit + char fillRule; // Fill rule, see NSVGfillRule. + unsigned char flags; // Logical or of NSVG_FLAGS_* flags + float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. + char fillGradient[64]; // Optional 'id' of fill gradient + char strokeGradient[64]; // Optional 'id' of stroke gradient + float xform[6]; // Root transformation for fill/stroke gradient + NSVGpath* paths; // Linked list of paths in the image. + struct NSVGshape* next; // Pointer to next shape, or NULL if last element. +} NSVGshape; + +typedef struct NSVGimage +{ + float width; // Width of the image. + float height; // Height of the image. + NSVGshape* shapes; // Linked list of shapes in the image. +} NSVGimage; + +// Parses SVG file from a file, returns SVG image as paths. +NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi); + +// Parses SVG file from a null terminated string, returns SVG image as paths. +// Important note: changes the string. +NSVGimage* nsvgParse(char* input, const char* units, float dpi); + +// Duplicates a path. +NSVGpath* nsvgDuplicatePath(NSVGpath* p); + +// Deletes an image. +void nsvgDelete(NSVGimage* image); + +#ifndef NANOSVG_CPLUSPLUS +#ifdef __cplusplus +} +#endif +#endif + +#ifdef NANOSVG_IMPLEMENTATION + +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4244) +#endif + +#define NSVG_PI (3.14159265358979323846264338327f) +#define NSVG_KAPPA90 (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs. + +#define NSVG_ALIGN_MIN 0 +#define NSVG_ALIGN_MID 1 +#define NSVG_ALIGN_MAX 2 +#define NSVG_ALIGN_NONE 0 +#define NSVG_ALIGN_MEET 1 +#define NSVG_ALIGN_SLICE 2 + +#define NSVG_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0) +#define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16)) + +#ifdef _MSC_VER + #pragma warning (disable: 4996) // Switch off security warnings + #pragma warning (disable: 4100) // Switch off unreferenced formal parameter warnings + #ifdef __cplusplus + #define NSVG_INLINE inline + #else + #define NSVG_INLINE + #endif +#else + #define NSVG_INLINE inline +#endif + + +static int nsvg__isspace(char c) +{ + return strchr(" \t\n\v\f\r", c) != 0; +} + +static int nsvg__isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static NSVG_INLINE float nsvg__minf(float a, float b) { return a < b ? a : b; } +static NSVG_INLINE float nsvg__maxf(float a, float b) { return a > b ? a : b; } + + +// Simple XML parser + +#define NSVG_XML_TAG 1 +#define NSVG_XML_CONTENT 2 +#define NSVG_XML_MAX_ATTRIBS 256 + +static void nsvg__parseContent(char* s, + void (*contentCb)(void* ud, const char* s), + void* ud) +{ + // Trim start white spaces + while (*s && nsvg__isspace(*s)) s++; + if (!*s) return; + + if (contentCb) + (*contentCb)(ud, s); +} + +static void nsvg__parseElement(char* s, + void (*startelCb)(void* ud, const char* el, const char** attr), + void (*endelCb)(void* ud, const char* el), + void* ud) +{ + const char* attr[NSVG_XML_MAX_ATTRIBS]; + int nattr = 0; + char* name; + int start = 0; + int end = 0; + char quote; + + // Skip white space after the '<' + while (*s && nsvg__isspace(*s)) s++; + + // Check if the tag is end tag + if (*s == '/') { + s++; + end = 1; + } else { + start = 1; + } + + // Skip comments, data and preprocessor stuff. + if (!*s || *s == '?' || *s == '!') + return; + + // Get tag name + name = s; + while (*s && !nsvg__isspace(*s)) s++; + if (*s) { *s++ = '\0'; } + + // Get attribs + while (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS-3) { + char* name = NULL; + char* value = NULL; + + // Skip white space before the attrib name + while (*s && nsvg__isspace(*s)) s++; + if (!*s) break; + if (*s == '/') { + end = 1; + break; + } + name = s; + // Find end of the attrib name. + while (*s && !nsvg__isspace(*s) && *s != '=') s++; + if (*s) { *s++ = '\0'; } + // Skip until the beginning of the value. + while (*s && *s != '\"' && *s != '\'') s++; + if (!*s) break; + quote = *s; + s++; + // Store value and find the end of it. + value = s; + while (*s && *s != quote) s++; + if (*s) { *s++ = '\0'; } + + // Store only well formed attributes + if (name && value) { + attr[nattr++] = name; + attr[nattr++] = value; + } + } + + // List terminator + attr[nattr++] = 0; + attr[nattr++] = 0; + + // Call callbacks. + if (start && startelCb) + (*startelCb)(ud, name, attr); + if (end && endelCb) + (*endelCb)(ud, name); +} + +int nsvg__parseXML(char* input, + void (*startelCb)(void* ud, const char* el, const char** attr), + void (*endelCb)(void* ud, const char* el), + void (*contentCb)(void* ud, const char* s), + void* ud) +{ + char* s = input; + char* mark = s; + int state = NSVG_XML_CONTENT; + while (*s) { + if (*s == '<' && state == NSVG_XML_CONTENT) { + // Start of a tag + *s++ = '\0'; + nsvg__parseContent(mark, contentCb, ud); + mark = s; + state = NSVG_XML_TAG; + } else if (*s == '>' && state == NSVG_XML_TAG) { + // Start of a content or new tag. + *s++ = '\0'; + nsvg__parseElement(mark, startelCb, endelCb, ud); + mark = s; + state = NSVG_XML_CONTENT; + } else { + s++; + } + } + + return 1; +} + + +/* Simple SVG parser. */ + +#define NSVG_MAX_ATTR 128 + +enum NSVGgradientUnits { + NSVG_USER_SPACE = 0, + NSVG_OBJECT_SPACE = 1 +}; + +#define NSVG_MAX_DASHES 8 + +enum NSVGunits { + NSVG_UNITS_USER, + NSVG_UNITS_PX, + NSVG_UNITS_PT, + NSVG_UNITS_PC, + NSVG_UNITS_MM, + NSVG_UNITS_CM, + NSVG_UNITS_IN, + NSVG_UNITS_PERCENT, + NSVG_UNITS_EM, + NSVG_UNITS_EX +}; + +typedef struct NSVGcoordinate { + float value; + int units; +} NSVGcoordinate; + +typedef struct NSVGlinearData { + NSVGcoordinate x1, y1, x2, y2; +} NSVGlinearData; + +typedef struct NSVGradialData { + NSVGcoordinate cx, cy, r, fx, fy; +} NSVGradialData; + +typedef struct NSVGgradientData +{ + char id[64]; + char ref[64]; + signed char type; + union { + NSVGlinearData linear; + NSVGradialData radial; + }; + char spread; + char units; + float xform[6]; + int nstops; + NSVGgradientStop* stops; + struct NSVGgradientData* next; +} NSVGgradientData; + +typedef struct NSVGattrib +{ + char id[64]; + float xform[6]; + unsigned int fillColor; + unsigned int strokeColor; + float opacity; + float fillOpacity; + float strokeOpacity; + char fillGradient[64]; + char strokeGradient[64]; + float strokeWidth; + float strokeDashOffset; + float strokeDashArray[NSVG_MAX_DASHES]; + int strokeDashCount; + char strokeLineJoin; + char strokeLineCap; + float miterLimit; + char fillRule; + float fontSize; + unsigned int stopColor; + float stopOpacity; + float stopOffset; + char hasFill; + char hasStroke; + char visible; +} NSVGattrib; + +typedef struct NSVGparser +{ + NSVGattrib attr[NSVG_MAX_ATTR]; + int attrHead; + float* pts; + int npts; + int cpts; + NSVGpath* plist; + NSVGimage* image; + NSVGgradientData* gradients; + NSVGshape* shapesTail; + float viewMinx, viewMiny, viewWidth, viewHeight; + int alignX, alignY, alignType; + float dpi; + char pathFlag; + char defsFlag; +} NSVGparser; + +static void nsvg__xformIdentity(float* t) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = 0.0f; t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformSetTranslation(float* t, float tx, float ty) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = 0.0f; t[3] = 1.0f; + t[4] = tx; t[5] = ty; +} + +static void nsvg__xformSetScale(float* t, float sx, float sy) +{ + t[0] = sx; t[1] = 0.0f; + t[2] = 0.0f; t[3] = sy; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformSetSkewX(float* t, float a) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = tanf(a); t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformSetSkewY(float* t, float a) +{ + t[0] = 1.0f; t[1] = tanf(a); + t[2] = 0.0f; t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformSetRotation(float* t, float a) +{ + float cs = cosf(a), sn = sinf(a); + t[0] = cs; t[1] = sn; + t[2] = -sn; t[3] = cs; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformMultiply(float* t, float* s) +{ + float t0 = t[0] * s[0] + t[1] * s[2]; + float t2 = t[2] * s[0] + t[3] * s[2]; + float t4 = t[4] * s[0] + t[5] * s[2] + s[4]; + t[1] = t[0] * s[1] + t[1] * s[3]; + t[3] = t[2] * s[1] + t[3] * s[3]; + t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; + t[0] = t0; + t[2] = t2; + t[4] = t4; +} + +static void nsvg__xformInverse(float* inv, float* t) +{ + double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1]; + if (det > -1e-6 && det < 1e-6) { + nsvg__xformIdentity(t); + return; + } + invdet = 1.0 / det; + inv[0] = (float)(t[3] * invdet); + inv[2] = (float)(-t[2] * invdet); + inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet); + inv[1] = (float)(-t[1] * invdet); + inv[3] = (float)(t[0] * invdet); + inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet); +} + +static void nsvg__xformPremultiply(float* t, float* s) +{ + float s2[6]; + memcpy(s2, s, sizeof(float)*6); + nsvg__xformMultiply(s2, t); + memcpy(t, s2, sizeof(float)*6); +} + +static void nsvg__xformPoint(float* dx, float* dy, float x, float y, float* t) +{ + *dx = x*t[0] + y*t[2] + t[4]; + *dy = x*t[1] + y*t[3] + t[5]; +} + +static void nsvg__xformVec(float* dx, float* dy, float x, float y, float* t) +{ + *dx = x*t[0] + y*t[2]; + *dy = x*t[1] + y*t[3]; +} + +#define NSVG_EPSILON (1e-12) + +static int nsvg__ptInBounds(float* pt, float* bounds) +{ + return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3]; +} + + +static double nsvg__evalBezier(double t, double p0, double p1, double p2, double p3) +{ + double it = 1.0-t; + return it*it*it*p0 + 3.0*it*it*t*p1 + 3.0*it*t*t*p2 + t*t*t*p3; +} + +static void nsvg__curveBounds(float* bounds, float* curve) +{ + int i, j, count; + double roots[2], a, b, c, b2ac, t, v; + float* v0 = &curve[0]; + float* v1 = &curve[2]; + float* v2 = &curve[4]; + float* v3 = &curve[6]; + + // Start the bounding box by end points + bounds[0] = nsvg__minf(v0[0], v3[0]); + bounds[1] = nsvg__minf(v0[1], v3[1]); + bounds[2] = nsvg__maxf(v0[0], v3[0]); + bounds[3] = nsvg__maxf(v0[1], v3[1]); + + // Bezier curve fits inside the convex hull of it's control points. + // If control points are inside the bounds, we're done. + if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds)) + return; + + // Add bezier curve inflection points in X and Y. + for (i = 0; i < 2; i++) { + a = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i]; + b = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i]; + c = 3.0 * v1[i] - 3.0 * v0[i]; + count = 0; + if (fabs(a) < NSVG_EPSILON) { + if (fabs(b) > NSVG_EPSILON) { + t = -c / b; + if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) + roots[count++] = t; + } + } else { + b2ac = b*b - 4.0*c*a; + if (b2ac > NSVG_EPSILON) { + t = (-b + sqrt(b2ac)) / (2.0 * a); + if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) + roots[count++] = t; + t = (-b - sqrt(b2ac)) / (2.0 * a); + if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) + roots[count++] = t; + } + } + for (j = 0; j < count; j++) { + v = nsvg__evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]); + bounds[0+i] = nsvg__minf(bounds[0+i], (float)v); + bounds[2+i] = nsvg__maxf(bounds[2+i], (float)v); + } + } +} + +static NSVGparser* nsvg__createParser(void) +{ + NSVGparser* p; + p = (NSVGparser*)malloc(sizeof(NSVGparser)); + if (p == NULL) goto error; + memset(p, 0, sizeof(NSVGparser)); + + p->image = (NSVGimage*)malloc(sizeof(NSVGimage)); + if (p->image == NULL) goto error; + memset(p->image, 0, sizeof(NSVGimage)); + + // Init style + nsvg__xformIdentity(p->attr[0].xform); + memset(p->attr[0].id, 0, sizeof p->attr[0].id); + p->attr[0].fillColor = NSVG_RGB(0,0,0); + p->attr[0].strokeColor = NSVG_RGB(0,0,0); + p->attr[0].opacity = 1; + p->attr[0].fillOpacity = 1; + p->attr[0].strokeOpacity = 1; + p->attr[0].stopOpacity = 1; + p->attr[0].strokeWidth = 1; + p->attr[0].strokeLineJoin = NSVG_JOIN_MITER; + p->attr[0].strokeLineCap = NSVG_CAP_BUTT; + p->attr[0].miterLimit = 4; + p->attr[0].fillRule = NSVG_FILLRULE_NONZERO; + p->attr[0].hasFill = 1; + p->attr[0].visible = 1; + + return p; + +error: + if (p) { + if (p->image) free(p->image); + free(p); + } + return NULL; +} + +static void nsvg__deletePaths(NSVGpath* path) +{ + while (path) { + NSVGpath *next = path->next; + if (path->pts != NULL) + free(path->pts); + free(path); + path = next; + } +} + +static void nsvg__deletePaint(NSVGpaint* paint) +{ + if (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT) + free(paint->gradient); +} + +static void nsvg__deleteGradientData(NSVGgradientData* grad) +{ + NSVGgradientData* next; + while (grad != NULL) { + next = grad->next; + free(grad->stops); + free(grad); + grad = next; + } +} + +static void nsvg__deleteParser(NSVGparser* p) +{ + if (p != NULL) { + nsvg__deletePaths(p->plist); + nsvg__deleteGradientData(p->gradients); + nsvgDelete(p->image); + free(p->pts); + free(p); + } +} + +static void nsvg__resetPath(NSVGparser* p) +{ + p->npts = 0; +} + +static void nsvg__addPoint(NSVGparser* p, float x, float y) +{ + if (p->npts+1 > p->cpts) { + p->cpts = p->cpts ? p->cpts*2 : 8; + p->pts = (float*)realloc(p->pts, p->cpts*2*sizeof(float)); + if (!p->pts) return; + } + p->pts[p->npts*2+0] = x; + p->pts[p->npts*2+1] = y; + p->npts++; +} + +static void nsvg__moveTo(NSVGparser* p, float x, float y) +{ + if (p->npts > 0) { + p->pts[(p->npts-1)*2+0] = x; + p->pts[(p->npts-1)*2+1] = y; + } else { + nsvg__addPoint(p, x, y); + } +} + +static void nsvg__lineTo(NSVGparser* p, float x, float y) +{ + float px,py, dx,dy; + if (p->npts > 0) { + px = p->pts[(p->npts-1)*2+0]; + py = p->pts[(p->npts-1)*2+1]; + dx = x - px; + dy = y - py; + nsvg__addPoint(p, px + dx/3.0f, py + dy/3.0f); + nsvg__addPoint(p, x - dx/3.0f, y - dy/3.0f); + nsvg__addPoint(p, x, y); + } +} + +static void nsvg__cubicBezTo(NSVGparser* p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y) +{ + if (p->npts > 0) { + nsvg__addPoint(p, cpx1, cpy1); + nsvg__addPoint(p, cpx2, cpy2); + nsvg__addPoint(p, x, y); + } +} + +static NSVGattrib* nsvg__getAttr(NSVGparser* p) +{ + return &p->attr[p->attrHead]; +} + +static void nsvg__pushAttr(NSVGparser* p) +{ + if (p->attrHead < NSVG_MAX_ATTR-1) { + p->attrHead++; + memcpy(&p->attr[p->attrHead], &p->attr[p->attrHead-1], sizeof(NSVGattrib)); + } +} + +static void nsvg__popAttr(NSVGparser* p) +{ + if (p->attrHead > 0) + p->attrHead--; +} + +static float nsvg__actualOrigX(NSVGparser* p) +{ + return p->viewMinx; +} + +static float nsvg__actualOrigY(NSVGparser* p) +{ + return p->viewMiny; +} + +static float nsvg__actualWidth(NSVGparser* p) +{ + return p->viewWidth; +} + +static float nsvg__actualHeight(NSVGparser* p) +{ + return p->viewHeight; +} + +static float nsvg__actualLength(NSVGparser* p) +{ + float w = nsvg__actualWidth(p), h = nsvg__actualHeight(p); + return sqrtf(w*w + h*h) / sqrtf(2.0f); +} + +static float nsvg__convertToPixels(NSVGparser* p, NSVGcoordinate c, float orig, float length) +{ + NSVGattrib* attr = nsvg__getAttr(p); + switch (c.units) { + case NSVG_UNITS_USER: return c.value; + case NSVG_UNITS_PX: return c.value; + case NSVG_UNITS_PT: return c.value / 72.0f * p->dpi; + case NSVG_UNITS_PC: return c.value / 6.0f * p->dpi; + case NSVG_UNITS_MM: return c.value / 25.4f * p->dpi; + case NSVG_UNITS_CM: return c.value / 2.54f * p->dpi; + case NSVG_UNITS_IN: return c.value * p->dpi; + case NSVG_UNITS_EM: return c.value * attr->fontSize; + case NSVG_UNITS_EX: return c.value * attr->fontSize * 0.52f; // x-height of Helvetica. + case NSVG_UNITS_PERCENT: return orig + c.value / 100.0f * length; + default: return c.value; + } + return c.value; +} + +static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id) +{ + NSVGgradientData* grad = p->gradients; + if (id == NULL || *id == '\0') + return NULL; + while (grad != NULL) { + if (strcmp(grad->id, id) == 0) + return grad; + grad = grad->next; + } + return NULL; +} + +static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, float *xform, signed char* paintType) +{ + NSVGgradientData* data = NULL; + NSVGgradientData* ref = NULL; + NSVGgradientStop* stops = NULL; + NSVGgradient* grad; + float ox, oy, sw, sh, sl; + int nstops = 0; + int refIter; + + data = nsvg__findGradientData(p, id); + if (data == NULL) return NULL; + + // TODO: use ref to fill in all unset values too. + ref = data; + refIter = 0; + while (ref != NULL) { + NSVGgradientData* nextRef = NULL; + if (stops == NULL && ref->stops != NULL) { + stops = ref->stops; + nstops = ref->nstops; + break; + } + nextRef = nsvg__findGradientData(p, ref->ref); + if (nextRef == ref) break; // prevent infite loops on malformed data + ref = nextRef; + refIter++; + if (refIter > 32) break; // prevent infite loops on malformed data + } + if (stops == NULL) return NULL; + + grad = (NSVGgradient*)malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop)*(nstops-1)); + if (grad == NULL) return NULL; + + // The shape width and height. + if (data->units == NSVG_OBJECT_SPACE) { + ox = localBounds[0]; + oy = localBounds[1]; + sw = localBounds[2] - localBounds[0]; + sh = localBounds[3] - localBounds[1]; + } else { + ox = nsvg__actualOrigX(p); + oy = nsvg__actualOrigY(p); + sw = nsvg__actualWidth(p); + sh = nsvg__actualHeight(p); + } + sl = sqrtf(sw*sw + sh*sh) / sqrtf(2.0f); + + if (data->type == NSVG_PAINT_LINEAR_GRADIENT) { + float x1, y1, x2, y2, dx, dy; + x1 = nsvg__convertToPixels(p, data->linear.x1, ox, sw); + y1 = nsvg__convertToPixels(p, data->linear.y1, oy, sh); + x2 = nsvg__convertToPixels(p, data->linear.x2, ox, sw); + y2 = nsvg__convertToPixels(p, data->linear.y2, oy, sh); + // Calculate transform aligned to the line + dx = x2 - x1; + dy = y2 - y1; + grad->xform[0] = dy; grad->xform[1] = -dx; + grad->xform[2] = dx; grad->xform[3] = dy; + grad->xform[4] = x1; grad->xform[5] = y1; + } else { + float cx, cy, fx, fy, r; + cx = nsvg__convertToPixels(p, data->radial.cx, ox, sw); + cy = nsvg__convertToPixels(p, data->radial.cy, oy, sh); + fx = nsvg__convertToPixels(p, data->radial.fx, ox, sw); + fy = nsvg__convertToPixels(p, data->radial.fy, oy, sh); + r = nsvg__convertToPixels(p, data->radial.r, 0, sl); + // Calculate transform aligned to the circle + grad->xform[0] = r; grad->xform[1] = 0; + grad->xform[2] = 0; grad->xform[3] = r; + grad->xform[4] = cx; grad->xform[5] = cy; + grad->fx = fx / r; + grad->fy = fy / r; + } + + nsvg__xformMultiply(grad->xform, data->xform); + nsvg__xformMultiply(grad->xform, xform); + + grad->spread = data->spread; + memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop)); + grad->nstops = nstops; + + *paintType = data->type; + + return grad; +} + +static float nsvg__getAverageScale(float* t) +{ + float sx = sqrtf(t[0]*t[0] + t[2]*t[2]); + float sy = sqrtf(t[1]*t[1] + t[3]*t[3]); + return (sx + sy) * 0.5f; +} + +static void nsvg__getLocalBounds(float* bounds, NSVGshape *shape, float* xform) +{ + NSVGpath* path; + float curve[4*2], curveBounds[4]; + int i, first = 1; + for (path = shape->paths; path != NULL; path = path->next) { + nsvg__xformPoint(&curve[0], &curve[1], path->pts[0], path->pts[1], xform); + for (i = 0; i < path->npts-1; i += 3) { + nsvg__xformPoint(&curve[2], &curve[3], path->pts[(i+1)*2], path->pts[(i+1)*2+1], xform); + nsvg__xformPoint(&curve[4], &curve[5], path->pts[(i+2)*2], path->pts[(i+2)*2+1], xform); + nsvg__xformPoint(&curve[6], &curve[7], path->pts[(i+3)*2], path->pts[(i+3)*2+1], xform); + nsvg__curveBounds(curveBounds, curve); + if (first) { + bounds[0] = curveBounds[0]; + bounds[1] = curveBounds[1]; + bounds[2] = curveBounds[2]; + bounds[3] = curveBounds[3]; + first = 0; + } else { + bounds[0] = nsvg__minf(bounds[0], curveBounds[0]); + bounds[1] = nsvg__minf(bounds[1], curveBounds[1]); + bounds[2] = nsvg__maxf(bounds[2], curveBounds[2]); + bounds[3] = nsvg__maxf(bounds[3], curveBounds[3]); + } + curve[0] = curve[6]; + curve[1] = curve[7]; + } + } +} + +static void nsvg__addShape(NSVGparser* p) +{ + NSVGattrib* attr = nsvg__getAttr(p); + float scale = 1.0f; + NSVGshape* shape; + NSVGpath* path; + int i; + + if (p->plist == NULL) + return; + + shape = (NSVGshape*)malloc(sizeof(NSVGshape)); + if (shape == NULL) goto error; + memset(shape, 0, sizeof(NSVGshape)); + + memcpy(shape->id, attr->id, sizeof shape->id); + memcpy(shape->fillGradient, attr->fillGradient, sizeof shape->fillGradient); + memcpy(shape->strokeGradient, attr->strokeGradient, sizeof shape->strokeGradient); + memcpy(shape->xform, attr->xform, sizeof shape->xform); + scale = nsvg__getAverageScale(attr->xform); + shape->strokeWidth = attr->strokeWidth * scale; + shape->strokeDashOffset = attr->strokeDashOffset * scale; + shape->strokeDashCount = (char)attr->strokeDashCount; + for (i = 0; i < attr->strokeDashCount; i++) + shape->strokeDashArray[i] = attr->strokeDashArray[i] * scale; + shape->strokeLineJoin = attr->strokeLineJoin; + shape->strokeLineCap = attr->strokeLineCap; + shape->miterLimit = attr->miterLimit; + shape->fillRule = attr->fillRule; + shape->opacity = attr->opacity; + + shape->paths = p->plist; + p->plist = NULL; + + // Calculate shape bounds + shape->bounds[0] = shape->paths->bounds[0]; + shape->bounds[1] = shape->paths->bounds[1]; + shape->bounds[2] = shape->paths->bounds[2]; + shape->bounds[3] = shape->paths->bounds[3]; + for (path = shape->paths->next; path != NULL; path = path->next) { + shape->bounds[0] = nsvg__minf(shape->bounds[0], path->bounds[0]); + shape->bounds[1] = nsvg__minf(shape->bounds[1], path->bounds[1]); + shape->bounds[2] = nsvg__maxf(shape->bounds[2], path->bounds[2]); + shape->bounds[3] = nsvg__maxf(shape->bounds[3], path->bounds[3]); + } + + // Set fill + if (attr->hasFill == 0) { + shape->fill.type = NSVG_PAINT_NONE; + } else if (attr->hasFill == 1) { + shape->fill.type = NSVG_PAINT_COLOR; + shape->fill.color = attr->fillColor; + shape->fill.color |= (unsigned int)(attr->fillOpacity*255) << 24; + } else if (attr->hasFill == 2) { + shape->fill.type = NSVG_PAINT_UNDEF; + } + + // Set stroke + if (attr->hasStroke == 0) { + shape->stroke.type = NSVG_PAINT_NONE; + } else if (attr->hasStroke == 1) { + shape->stroke.type = NSVG_PAINT_COLOR; + shape->stroke.color = attr->strokeColor; + shape->stroke.color |= (unsigned int)(attr->strokeOpacity*255) << 24; + } else if (attr->hasStroke == 2) { + shape->stroke.type = NSVG_PAINT_UNDEF; + } + + // Set flags + shape->flags = (attr->visible ? NSVG_FLAGS_VISIBLE : 0x00); + + // Add to tail + if (p->image->shapes == NULL) + p->image->shapes = shape; + else + p->shapesTail->next = shape; + p->shapesTail = shape; + + return; + +error: + if (shape) free(shape); +} + +static void nsvg__addPath(NSVGparser* p, char closed) +{ + NSVGattrib* attr = nsvg__getAttr(p); + NSVGpath* path = NULL; + float bounds[4]; + float* curve; + int i; + + if (p->npts < 4) + return; + + if (closed) + nsvg__lineTo(p, p->pts[0], p->pts[1]); + + // Expect 1 + N*3 points (N = number of cubic bezier segments). + if ((p->npts % 3) != 1) + return; + + path = (NSVGpath*)malloc(sizeof(NSVGpath)); + if (path == NULL) goto error; + memset(path, 0, sizeof(NSVGpath)); + + path->pts = (float*)malloc(p->npts*2*sizeof(float)); + if (path->pts == NULL) goto error; + path->closed = closed; + path->npts = p->npts; + + // Transform path. + for (i = 0; i < p->npts; ++i) + nsvg__xformPoint(&path->pts[i*2], &path->pts[i*2+1], p->pts[i*2], p->pts[i*2+1], attr->xform); + + // Find bounds + for (i = 0; i < path->npts-1; i += 3) { + curve = &path->pts[i*2]; + nsvg__curveBounds(bounds, curve); + if (i == 0) { + path->bounds[0] = bounds[0]; + path->bounds[1] = bounds[1]; + path->bounds[2] = bounds[2]; + path->bounds[3] = bounds[3]; + } else { + path->bounds[0] = nsvg__minf(path->bounds[0], bounds[0]); + path->bounds[1] = nsvg__minf(path->bounds[1], bounds[1]); + path->bounds[2] = nsvg__maxf(path->bounds[2], bounds[2]); + path->bounds[3] = nsvg__maxf(path->bounds[3], bounds[3]); + } + } + + path->next = p->plist; + p->plist = path; + + return; + +error: + if (path != NULL) { + if (path->pts != NULL) free(path->pts); + free(path); + } +} + +// We roll our own string to float because the std library one uses locale and messes things up. +static double nsvg__atof(const char* s) +{ + char* cur = (char*)s; + char* end = NULL; + double res = 0.0, sign = 1.0; + long long intPart = 0, fracPart = 0; + char hasIntPart = 0, hasFracPart = 0; + + // Parse optional sign + if (*cur == '+') { + cur++; + } else if (*cur == '-') { + sign = -1; + cur++; + } + + // Parse integer part + if (nsvg__isdigit(*cur)) { + // Parse digit sequence + intPart = strtoll(cur, &end, 10); + if (cur != end) { + res = (double)intPart; + hasIntPart = 1; + cur = end; + } + } + + // Parse fractional part. + if (*cur == '.') { + cur++; // Skip '.' + if (nsvg__isdigit(*cur)) { + // Parse digit sequence + fracPart = strtoll(cur, &end, 10); + if (cur != end) { + res += (double)fracPart / pow(10.0, (double)(end - cur)); + hasFracPart = 1; + cur = end; + } + } + } + + // A valid number should have integer or fractional part. + if (!hasIntPart && !hasFracPart) + return 0.0; + + // Parse optional exponent + if (*cur == 'e' || *cur == 'E') { + long expPart = 0; + cur++; // skip 'E' + expPart = strtol(cur, &end, 10); // Parse digit sequence with sign + if (cur != end) { + res *= pow(10.0, (double)expPart); + } + } + + return res * sign; +} + + +static const char* nsvg__parseNumber(const char* s, char* it, const int size) +{ + const int last = size-1; + int i = 0; + + // sign + if (*s == '-' || *s == '+') { + if (i < last) it[i++] = *s; + s++; + } + // integer part + while (*s && nsvg__isdigit(*s)) { + if (i < last) it[i++] = *s; + s++; + } + if (*s == '.') { + // decimal point + if (i < last) it[i++] = *s; + s++; + // fraction part + while (*s && nsvg__isdigit(*s)) { + if (i < last) it[i++] = *s; + s++; + } + } + // exponent + if ((*s == 'e' || *s == 'E') && (s[1] != 'm' && s[1] != 'x')) { + if (i < last) it[i++] = *s; + s++; + if (*s == '-' || *s == '+') { + if (i < last) it[i++] = *s; + s++; + } + while (*s && nsvg__isdigit(*s)) { + if (i < last) it[i++] = *s; + s++; + } + } + it[i] = '\0'; + + return s; +} + +static const char* nsvg__getNextPathItemWhenArcFlag(const char* s, char* it) +{ + it[0] = '\0'; + while (*s && (nsvg__isspace(*s) || *s == ',')) s++; + if (!*s) return s; + if (*s == '0' || *s == '1') { + it[0] = *s++; + it[1] = '\0'; + return s; + } + return s; +} + +static const char* nsvg__getNextPathItem(const char* s, char* it) +{ + it[0] = '\0'; + // Skip white spaces and commas + while (*s && (nsvg__isspace(*s) || *s == ',')) s++; + if (!*s) return s; + if (*s == '-' || *s == '+' || *s == '.' || nsvg__isdigit(*s)) { + s = nsvg__parseNumber(s, it, 64); + } else { + // Parse command + it[0] = *s++; + it[1] = '\0'; + return s; + } + + return s; +} + +static unsigned int nsvg__parseColorHex(const char* str) +{ + unsigned int r=0, g=0, b=0; + if (sscanf(str, "#%2x%2x%2x", &r, &g, &b) == 3 ) // 2 digit hex + return NSVG_RGB(r, g, b); + if (sscanf(str, "#%1x%1x%1x", &r, &g, &b) == 3 ) // 1 digit hex, e.g. #abc -> 0xccbbaa + return NSVG_RGB(r*17, g*17, b*17); // same effect as (r<<4|r), (g<<4|g), .. + return NSVG_RGB(128, 128, 128); +} + +// Parse rgb color. The pointer 'str' must point at "rgb(" (4+ characters). +// This function returns gray (rgb(128, 128, 128) == '#808080') on parse errors +// for backwards compatibility. Note: other image viewers return black instead. + +static unsigned int nsvg__parseColorRGB(const char* str) +{ + int i; + unsigned int rgbi[3]; + float rgbf[3]; + // try decimal integers first + if (sscanf(str, "rgb(%u, %u, %u)", &rgbi[0], &rgbi[1], &rgbi[2]) != 3) { + // integers failed, try percent values (float, locale independent) + const char delimiter[3] = {',', ',', ')'}; + str += 4; // skip "rgb(" + for (i = 0; i < 3; i++) { + while (*str && (nsvg__isspace(*str))) str++; // skip leading spaces + if (*str == '+') str++; // skip '+' (don't allow '-') + if (!*str) break; + rgbf[i] = nsvg__atof(str); + + // Note 1: it would be great if nsvg__atof() returned how many + // bytes it consumed but it doesn't. We need to skip the number, + // the '%' character, spaces, and the delimiter ',' or ')'. + + // Note 2: The following code does not allow values like "33.%", + // i.e. a decimal point w/o fractional part, but this is consistent + // with other image viewers, e.g. firefox, chrome, eog, gimp. + + while (*str && nsvg__isdigit(*str)) str++; // skip integer part + if (*str == '.') { + str++; + if (!nsvg__isdigit(*str)) break; // error: no digit after '.' + while (*str && nsvg__isdigit(*str)) str++; // skip fractional part + } + if (*str == '%') str++; else break; + while (nsvg__isspace(*str)) str++; + if (*str == delimiter[i]) str++; + else break; + } + if (i == 3) { + rgbi[0] = roundf(rgbf[0] * 2.55f); + rgbi[1] = roundf(rgbf[1] * 2.55f); + rgbi[2] = roundf(rgbf[2] * 2.55f); + } else { + rgbi[0] = rgbi[1] = rgbi[2] = 128; + } + } + // clip values as the CSS spec requires + for (i = 0; i < 3; i++) { + if (rgbi[i] > 255) rgbi[i] = 255; + } + return NSVG_RGB(rgbi[0], rgbi[1], rgbi[2]); +} + +typedef struct NSVGNamedColor { + const char* name; + unsigned int color; +} NSVGNamedColor; + +NSVGNamedColor nsvg__colors[] = { + + { "red", NSVG_RGB(255, 0, 0) }, + { "green", NSVG_RGB( 0, 128, 0) }, + { "blue", NSVG_RGB( 0, 0, 255) }, + { "yellow", NSVG_RGB(255, 255, 0) }, + { "cyan", NSVG_RGB( 0, 255, 255) }, + { "magenta", NSVG_RGB(255, 0, 255) }, + { "black", NSVG_RGB( 0, 0, 0) }, + { "grey", NSVG_RGB(128, 128, 128) }, + { "gray", NSVG_RGB(128, 128, 128) }, + { "white", NSVG_RGB(255, 255, 255) }, + +#ifdef NANOSVG_ALL_COLOR_KEYWORDS + { "aliceblue", NSVG_RGB(240, 248, 255) }, + { "antiquewhite", NSVG_RGB(250, 235, 215) }, + { "aqua", NSVG_RGB( 0, 255, 255) }, + { "aquamarine", NSVG_RGB(127, 255, 212) }, + { "azure", NSVG_RGB(240, 255, 255) }, + { "beige", NSVG_RGB(245, 245, 220) }, + { "bisque", NSVG_RGB(255, 228, 196) }, + { "blanchedalmond", NSVG_RGB(255, 235, 205) }, + { "blueviolet", NSVG_RGB(138, 43, 226) }, + { "brown", NSVG_RGB(165, 42, 42) }, + { "burlywood", NSVG_RGB(222, 184, 135) }, + { "cadetblue", NSVG_RGB( 95, 158, 160) }, + { "chartreuse", NSVG_RGB(127, 255, 0) }, + { "chocolate", NSVG_RGB(210, 105, 30) }, + { "coral", NSVG_RGB(255, 127, 80) }, + { "cornflowerblue", NSVG_RGB(100, 149, 237) }, + { "cornsilk", NSVG_RGB(255, 248, 220) }, + { "crimson", NSVG_RGB(220, 20, 60) }, + { "darkblue", NSVG_RGB( 0, 0, 139) }, + { "darkcyan", NSVG_RGB( 0, 139, 139) }, + { "darkgoldenrod", NSVG_RGB(184, 134, 11) }, + { "darkgray", NSVG_RGB(169, 169, 169) }, + { "darkgreen", NSVG_RGB( 0, 100, 0) }, + { "darkgrey", NSVG_RGB(169, 169, 169) }, + { "darkkhaki", NSVG_RGB(189, 183, 107) }, + { "darkmagenta", NSVG_RGB(139, 0, 139) }, + { "darkolivegreen", NSVG_RGB( 85, 107, 47) }, + { "darkorange", NSVG_RGB(255, 140, 0) }, + { "darkorchid", NSVG_RGB(153, 50, 204) }, + { "darkred", NSVG_RGB(139, 0, 0) }, + { "darksalmon", NSVG_RGB(233, 150, 122) }, + { "darkseagreen", NSVG_RGB(143, 188, 143) }, + { "darkslateblue", NSVG_RGB( 72, 61, 139) }, + { "darkslategray", NSVG_RGB( 47, 79, 79) }, + { "darkslategrey", NSVG_RGB( 47, 79, 79) }, + { "darkturquoise", NSVG_RGB( 0, 206, 209) }, + { "darkviolet", NSVG_RGB(148, 0, 211) }, + { "deeppink", NSVG_RGB(255, 20, 147) }, + { "deepskyblue", NSVG_RGB( 0, 191, 255) }, + { "dimgray", NSVG_RGB(105, 105, 105) }, + { "dimgrey", NSVG_RGB(105, 105, 105) }, + { "dodgerblue", NSVG_RGB( 30, 144, 255) }, + { "firebrick", NSVG_RGB(178, 34, 34) }, + { "floralwhite", NSVG_RGB(255, 250, 240) }, + { "forestgreen", NSVG_RGB( 34, 139, 34) }, + { "fuchsia", NSVG_RGB(255, 0, 255) }, + { "gainsboro", NSVG_RGB(220, 220, 220) }, + { "ghostwhite", NSVG_RGB(248, 248, 255) }, + { "gold", NSVG_RGB(255, 215, 0) }, + { "goldenrod", NSVG_RGB(218, 165, 32) }, + { "greenyellow", NSVG_RGB(173, 255, 47) }, + { "honeydew", NSVG_RGB(240, 255, 240) }, + { "hotpink", NSVG_RGB(255, 105, 180) }, + { "indianred", NSVG_RGB(205, 92, 92) }, + { "indigo", NSVG_RGB( 75, 0, 130) }, + { "ivory", NSVG_RGB(255, 255, 240) }, + { "khaki", NSVG_RGB(240, 230, 140) }, + { "lavender", NSVG_RGB(230, 230, 250) }, + { "lavenderblush", NSVG_RGB(255, 240, 245) }, + { "lawngreen", NSVG_RGB(124, 252, 0) }, + { "lemonchiffon", NSVG_RGB(255, 250, 205) }, + { "lightblue", NSVG_RGB(173, 216, 230) }, + { "lightcoral", NSVG_RGB(240, 128, 128) }, + { "lightcyan", NSVG_RGB(224, 255, 255) }, + { "lightgoldenrodyellow", NSVG_RGB(250, 250, 210) }, + { "lightgray", NSVG_RGB(211, 211, 211) }, + { "lightgreen", NSVG_RGB(144, 238, 144) }, + { "lightgrey", NSVG_RGB(211, 211, 211) }, + { "lightpink", NSVG_RGB(255, 182, 193) }, + { "lightsalmon", NSVG_RGB(255, 160, 122) }, + { "lightseagreen", NSVG_RGB( 32, 178, 170) }, + { "lightskyblue", NSVG_RGB(135, 206, 250) }, + { "lightslategray", NSVG_RGB(119, 136, 153) }, + { "lightslategrey", NSVG_RGB(119, 136, 153) }, + { "lightsteelblue", NSVG_RGB(176, 196, 222) }, + { "lightyellow", NSVG_RGB(255, 255, 224) }, + { "lime", NSVG_RGB( 0, 255, 0) }, + { "limegreen", NSVG_RGB( 50, 205, 50) }, + { "linen", NSVG_RGB(250, 240, 230) }, + { "maroon", NSVG_RGB(128, 0, 0) }, + { "mediumaquamarine", NSVG_RGB(102, 205, 170) }, + { "mediumblue", NSVG_RGB( 0, 0, 205) }, + { "mediumorchid", NSVG_RGB(186, 85, 211) }, + { "mediumpurple", NSVG_RGB(147, 112, 219) }, + { "mediumseagreen", NSVG_RGB( 60, 179, 113) }, + { "mediumslateblue", NSVG_RGB(123, 104, 238) }, + { "mediumspringgreen", NSVG_RGB( 0, 250, 154) }, + { "mediumturquoise", NSVG_RGB( 72, 209, 204) }, + { "mediumvioletred", NSVG_RGB(199, 21, 133) }, + { "midnightblue", NSVG_RGB( 25, 25, 112) }, + { "mintcream", NSVG_RGB(245, 255, 250) }, + { "mistyrose", NSVG_RGB(255, 228, 225) }, + { "moccasin", NSVG_RGB(255, 228, 181) }, + { "navajowhite", NSVG_RGB(255, 222, 173) }, + { "navy", NSVG_RGB( 0, 0, 128) }, + { "oldlace", NSVG_RGB(253, 245, 230) }, + { "olive", NSVG_RGB(128, 128, 0) }, + { "olivedrab", NSVG_RGB(107, 142, 35) }, + { "orange", NSVG_RGB(255, 165, 0) }, + { "orangered", NSVG_RGB(255, 69, 0) }, + { "orchid", NSVG_RGB(218, 112, 214) }, + { "palegoldenrod", NSVG_RGB(238, 232, 170) }, + { "palegreen", NSVG_RGB(152, 251, 152) }, + { "paleturquoise", NSVG_RGB(175, 238, 238) }, + { "palevioletred", NSVG_RGB(219, 112, 147) }, + { "papayawhip", NSVG_RGB(255, 239, 213) }, + { "peachpuff", NSVG_RGB(255, 218, 185) }, + { "peru", NSVG_RGB(205, 133, 63) }, + { "pink", NSVG_RGB(255, 192, 203) }, + { "plum", NSVG_RGB(221, 160, 221) }, + { "powderblue", NSVG_RGB(176, 224, 230) }, + { "purple", NSVG_RGB(128, 0, 128) }, + { "rosybrown", NSVG_RGB(188, 143, 143) }, + { "royalblue", NSVG_RGB( 65, 105, 225) }, + { "saddlebrown", NSVG_RGB(139, 69, 19) }, + { "salmon", NSVG_RGB(250, 128, 114) }, + { "sandybrown", NSVG_RGB(244, 164, 96) }, + { "seagreen", NSVG_RGB( 46, 139, 87) }, + { "seashell", NSVG_RGB(255, 245, 238) }, + { "sienna", NSVG_RGB(160, 82, 45) }, + { "silver", NSVG_RGB(192, 192, 192) }, + { "skyblue", NSVG_RGB(135, 206, 235) }, + { "slateblue", NSVG_RGB(106, 90, 205) }, + { "slategray", NSVG_RGB(112, 128, 144) }, + { "slategrey", NSVG_RGB(112, 128, 144) }, + { "snow", NSVG_RGB(255, 250, 250) }, + { "springgreen", NSVG_RGB( 0, 255, 127) }, + { "steelblue", NSVG_RGB( 70, 130, 180) }, + { "tan", NSVG_RGB(210, 180, 140) }, + { "teal", NSVG_RGB( 0, 128, 128) }, + { "thistle", NSVG_RGB(216, 191, 216) }, + { "tomato", NSVG_RGB(255, 99, 71) }, + { "turquoise", NSVG_RGB( 64, 224, 208) }, + { "violet", NSVG_RGB(238, 130, 238) }, + { "wheat", NSVG_RGB(245, 222, 179) }, + { "whitesmoke", NSVG_RGB(245, 245, 245) }, + { "yellowgreen", NSVG_RGB(154, 205, 50) }, +#endif +}; + +static unsigned int nsvg__parseColorName(const char* str) +{ + int i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor); + + for (i = 0; i < ncolors; i++) { + if (strcmp(nsvg__colors[i].name, str) == 0) { + return nsvg__colors[i].color; + } + } + + return NSVG_RGB(128, 128, 128); +} + +static unsigned int nsvg__parseColor(const char* str) +{ + size_t len = 0; + while(*str == ' ') ++str; + len = strlen(str); + if (len >= 1 && *str == '#') + return nsvg__parseColorHex(str); + else if (len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(') + return nsvg__parseColorRGB(str); + return nsvg__parseColorName(str); +} + +static float nsvg__parseOpacity(const char* str) +{ + float val = nsvg__atof(str); + if (val < 0.0f) val = 0.0f; + if (val > 1.0f) val = 1.0f; + return val; +} + +static float nsvg__parseMiterLimit(const char* str) +{ + float val = nsvg__atof(str); + if (val < 0.0f) val = 0.0f; + return val; +} + +static int nsvg__parseUnits(const char* units) +{ + if (units[0] == 'p' && units[1] == 'x') + return NSVG_UNITS_PX; + else if (units[0] == 'p' && units[1] == 't') + return NSVG_UNITS_PT; + else if (units[0] == 'p' && units[1] == 'c') + return NSVG_UNITS_PC; + else if (units[0] == 'm' && units[1] == 'm') + return NSVG_UNITS_MM; + else if (units[0] == 'c' && units[1] == 'm') + return NSVG_UNITS_CM; + else if (units[0] == 'i' && units[1] == 'n') + return NSVG_UNITS_IN; + else if (units[0] == '%') + return NSVG_UNITS_PERCENT; + else if (units[0] == 'e' && units[1] == 'm') + return NSVG_UNITS_EM; + else if (units[0] == 'e' && units[1] == 'x') + return NSVG_UNITS_EX; + return NSVG_UNITS_USER; +} + +static int nsvg__isCoordinate(const char* s) +{ + // optional sign + if (*s == '-' || *s == '+') + s++; + // must have at least one digit, or start by a dot + return (nsvg__isdigit(*s) || *s == '.'); +} + +static NSVGcoordinate nsvg__parseCoordinateRaw(const char* str) +{ + NSVGcoordinate coord = {0, NSVG_UNITS_USER}; + char buf[64]; + coord.units = nsvg__parseUnits(nsvg__parseNumber(str, buf, 64)); + coord.value = nsvg__atof(buf); + return coord; +} + +static NSVGcoordinate nsvg__coord(float v, int units) +{ + NSVGcoordinate coord = {v, units}; + return coord; +} + +static float nsvg__parseCoordinate(NSVGparser* p, const char* str, float orig, float length) +{ + NSVGcoordinate coord = nsvg__parseCoordinateRaw(str); + return nsvg__convertToPixels(p, coord, orig, length); +} + +static int nsvg__parseTransformArgs(const char* str, float* args, int maxNa, int* na) +{ + const char* end; + const char* ptr; + char it[64]; + + *na = 0; + ptr = str; + while (*ptr && *ptr != '(') ++ptr; + if (*ptr == 0) + return 1; + end = ptr; + while (*end && *end != ')') ++end; + if (*end == 0) + return 1; + + while (ptr < end) { + if (*ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit(*ptr)) { + if (*na >= maxNa) return 0; + ptr = nsvg__parseNumber(ptr, it, 64); + args[(*na)++] = (float)nsvg__atof(it); + } else { + ++ptr; + } + } + return (int)(end - str); +} + + +static int nsvg__parseMatrix(float* xform, const char* str) +{ + float t[6]; + int na = 0; + int len = nsvg__parseTransformArgs(str, t, 6, &na); + if (na != 6) return len; + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseTranslate(float* xform, const char* str) +{ + float args[2]; + float t[6]; + int na = 0; + int len = nsvg__parseTransformArgs(str, args, 2, &na); + if (na == 1) args[1] = 0.0; + + nsvg__xformSetTranslation(t, args[0], args[1]); + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseScale(float* xform, const char* str) +{ + float args[2]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 2, &na); + if (na == 1) args[1] = args[0]; + nsvg__xformSetScale(t, args[0], args[1]); + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseSkewX(float* xform, const char* str) +{ + float args[1]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 1, &na); + nsvg__xformSetSkewX(t, args[0]/180.0f*NSVG_PI); + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseSkewY(float* xform, const char* str) +{ + float args[1]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 1, &na); + nsvg__xformSetSkewY(t, args[0]/180.0f*NSVG_PI); + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseRotate(float* xform, const char* str) +{ + float args[3]; + int na = 0; + float m[6]; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 3, &na); + if (na == 1) + args[1] = args[2] = 0.0f; + nsvg__xformIdentity(m); + + if (na > 1) { + nsvg__xformSetTranslation(t, -args[1], -args[2]); + nsvg__xformMultiply(m, t); + } + + nsvg__xformSetRotation(t, args[0]/180.0f*NSVG_PI); + nsvg__xformMultiply(m, t); + + if (na > 1) { + nsvg__xformSetTranslation(t, args[1], args[2]); + nsvg__xformMultiply(m, t); + } + + memcpy(xform, m, sizeof(float)*6); + + return len; +} + +static void nsvg__parseTransform(float* xform, const char* str) +{ + float t[6]; + int len; + nsvg__xformIdentity(xform); + while (*str) + { + if (strncmp(str, "matrix", 6) == 0) + len = nsvg__parseMatrix(t, str); + else if (strncmp(str, "translate", 9) == 0) + len = nsvg__parseTranslate(t, str); + else if (strncmp(str, "scale", 5) == 0) + len = nsvg__parseScale(t, str); + else if (strncmp(str, "rotate", 6) == 0) + len = nsvg__parseRotate(t, str); + else if (strncmp(str, "skewX", 5) == 0) + len = nsvg__parseSkewX(t, str); + else if (strncmp(str, "skewY", 5) == 0) + len = nsvg__parseSkewY(t, str); + else{ + ++str; + continue; + } + if (len != 0) { + str += len; + } else { + ++str; + continue; + } + + nsvg__xformPremultiply(xform, t); + } +} + +static void nsvg__parseUrl(char* id, const char* str) +{ + int i = 0; + str += 4; // "url("; + if (*str && *str == '#') + str++; + while (i < 63 && *str && *str != ')') { + id[i] = *str++; + i++; + } + id[i] = '\0'; +} + +static char nsvg__parseLineCap(const char* str) +{ + if (strcmp(str, "butt") == 0) + return NSVG_CAP_BUTT; + else if (strcmp(str, "round") == 0) + return NSVG_CAP_ROUND; + else if (strcmp(str, "square") == 0) + return NSVG_CAP_SQUARE; + // TODO: handle inherit. + return NSVG_CAP_BUTT; +} + +static char nsvg__parseLineJoin(const char* str) +{ + if (strcmp(str, "miter") == 0) + return NSVG_JOIN_MITER; + else if (strcmp(str, "round") == 0) + return NSVG_JOIN_ROUND; + else if (strcmp(str, "bevel") == 0) + return NSVG_JOIN_BEVEL; + // TODO: handle inherit. + return NSVG_JOIN_MITER; +} + +static char nsvg__parseFillRule(const char* str) +{ + if (strcmp(str, "nonzero") == 0) + return NSVG_FILLRULE_NONZERO; + else if (strcmp(str, "evenodd") == 0) + return NSVG_FILLRULE_EVENODD; + // TODO: handle inherit. + return NSVG_FILLRULE_NONZERO; +} + +static const char* nsvg__getNextDashItem(const char* s, char* it) +{ + int n = 0; + it[0] = '\0'; + // Skip white spaces and commas + while (*s && (nsvg__isspace(*s) || *s == ',')) s++; + // Advance until whitespace, comma or end. + while (*s && (!nsvg__isspace(*s) && *s != ',')) { + if (n < 63) + it[n++] = *s; + s++; + } + it[n++] = '\0'; + return s; +} + +static int nsvg__parseStrokeDashArray(NSVGparser* p, const char* str, float* strokeDashArray) +{ + char item[64]; + int count = 0, i; + float sum = 0.0f; + + // Handle "none" + if (str[0] == 'n') + return 0; + + // Parse dashes + while (*str) { + str = nsvg__getNextDashItem(str, item); + if (!*item) break; + if (count < NSVG_MAX_DASHES) + strokeDashArray[count++] = fabsf(nsvg__parseCoordinate(p, item, 0.0f, nsvg__actualLength(p))); + } + + for (i = 0; i < count; i++) + sum += strokeDashArray[i]; + if (sum <= 1e-6f) + count = 0; + + return count; +} + +static void nsvg__parseStyle(NSVGparser* p, const char* str); + +static int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value) +{ + float xform[6]; + NSVGattrib* attr = nsvg__getAttr(p); + if (!attr) return 0; + + if (strcmp(name, "style") == 0) { + nsvg__parseStyle(p, value); + } else if (strcmp(name, "display") == 0) { + if (strcmp(value, "none") == 0) + attr->visible = 0; + // Don't reset ->visible on display:inline, one display:none hides the whole subtree + + } else if (strcmp(name, "fill") == 0) { + if (strcmp(value, "none") == 0) { + attr->hasFill = 0; + } else if (strncmp(value, "url(", 4) == 0) { + attr->hasFill = 2; + nsvg__parseUrl(attr->fillGradient, value); + } else { + attr->hasFill = 1; + attr->fillColor = nsvg__parseColor(value); + } + } else if (strcmp(name, "opacity") == 0) { + attr->opacity = nsvg__parseOpacity(value); + } else if (strcmp(name, "fill-opacity") == 0) { + attr->fillOpacity = nsvg__parseOpacity(value); + } else if (strcmp(name, "stroke") == 0) { + if (strcmp(value, "none") == 0) { + attr->hasStroke = 0; + } else if (strncmp(value, "url(", 4) == 0) { + attr->hasStroke = 2; + nsvg__parseUrl(attr->strokeGradient, value); + } else { + attr->hasStroke = 1; + attr->strokeColor = nsvg__parseColor(value); + } + } else if (strcmp(name, "stroke-width") == 0) { + attr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); + } else if (strcmp(name, "stroke-dasharray") == 0) { + attr->strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr->strokeDashArray); + } else if (strcmp(name, "stroke-dashoffset") == 0) { + attr->strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); + } else if (strcmp(name, "stroke-opacity") == 0) { + attr->strokeOpacity = nsvg__parseOpacity(value); + } else if (strcmp(name, "stroke-linecap") == 0) { + attr->strokeLineCap = nsvg__parseLineCap(value); + } else if (strcmp(name, "stroke-linejoin") == 0) { + attr->strokeLineJoin = nsvg__parseLineJoin(value); + } else if (strcmp(name, "stroke-miterlimit") == 0) { + attr->miterLimit = nsvg__parseMiterLimit(value); + } else if (strcmp(name, "fill-rule") == 0) { + attr->fillRule = nsvg__parseFillRule(value); + } else if (strcmp(name, "font-size") == 0) { + attr->fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); + } else if (strcmp(name, "transform") == 0) { + nsvg__parseTransform(xform, value); + nsvg__xformPremultiply(attr->xform, xform); + } else if (strcmp(name, "stop-color") == 0) { + attr->stopColor = nsvg__parseColor(value); + } else if (strcmp(name, "stop-opacity") == 0) { + attr->stopOpacity = nsvg__parseOpacity(value); + } else if (strcmp(name, "offset") == 0) { + attr->stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f); + } else if (strcmp(name, "id") == 0) { + strncpy(attr->id, value, 63); + attr->id[63] = '\0'; + } else { + return 0; + } + return 1; +} + +static int nsvg__parseNameValue(NSVGparser* p, const char* start, const char* end) +{ + const char* str; + const char* val; + char name[512]; + char value[512]; + int n; + + str = start; + while (str < end && *str != ':') ++str; + + val = str; + + // Right Trim + while (str > start && (*str == ':' || nsvg__isspace(*str))) --str; + ++str; + + n = (int)(str - start); + if (n > 511) n = 511; + if (n) memcpy(name, start, n); + name[n] = 0; + + while (val < end && (*val == ':' || nsvg__isspace(*val))) ++val; + + n = (int)(end - val); + if (n > 511) n = 511; + if (n) memcpy(value, val, n); + value[n] = 0; + + return nsvg__parseAttr(p, name, value); +} + +static void nsvg__parseStyle(NSVGparser* p, const char* str) +{ + const char* start; + const char* end; + + while (*str) { + // Left Trim + while(*str && nsvg__isspace(*str)) ++str; + start = str; + while(*str && *str != ';') ++str; + end = str; + + // Right Trim + while (end > start && (*end == ';' || nsvg__isspace(*end))) --end; + ++end; + + nsvg__parseNameValue(p, start, end); + if (*str) ++str; + } +} + +static void nsvg__parseAttribs(NSVGparser* p, const char** attr) +{ + int i; + for (i = 0; attr[i]; i += 2) + { + if (strcmp(attr[i], "style") == 0) + nsvg__parseStyle(p, attr[i + 1]); + else + nsvg__parseAttr(p, attr[i], attr[i + 1]); + } +} + +static int nsvg__getArgsPerElement(char cmd) +{ + switch (cmd) { + case 'v': + case 'V': + case 'h': + case 'H': + return 1; + case 'm': + case 'M': + case 'l': + case 'L': + case 't': + case 'T': + return 2; + case 'q': + case 'Q': + case 's': + case 'S': + return 4; + case 'c': + case 'C': + return 6; + case 'a': + case 'A': + return 7; + case 'z': + case 'Z': + return 0; + } + return -1; +} + +static void nsvg__pathMoveTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) { + *cpx += args[0]; + *cpy += args[1]; + } else { + *cpx = args[0]; + *cpy = args[1]; + } + nsvg__moveTo(p, *cpx, *cpy); +} + +static void nsvg__pathLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) { + *cpx += args[0]; + *cpy += args[1]; + } else { + *cpx = args[0]; + *cpy = args[1]; + } + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathHLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) + *cpx += args[0]; + else + *cpx = args[0]; + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathVLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) + *cpy += args[0]; + else + *cpy = args[0]; + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathCubicBezTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x2, y2, cx1, cy1, cx2, cy2; + + if (rel) { + cx1 = *cpx + args[0]; + cy1 = *cpy + args[1]; + cx2 = *cpx + args[2]; + cy2 = *cpy + args[3]; + x2 = *cpx + args[4]; + y2 = *cpy + args[5]; + } else { + cx1 = args[0]; + cy1 = args[1]; + cx2 = args[2]; + cy2 = args[3]; + x2 = args[4]; + y2 = args[5]; + } + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx2; + *cpy2 = cy2; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathCubicBezShortTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x1, y1, x2, y2, cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + cx2 = *cpx + args[0]; + cy2 = *cpy + args[1]; + x2 = *cpx + args[2]; + y2 = *cpy + args[3]; + } else { + cx2 = args[0]; + cy2 = args[1]; + x2 = args[2]; + y2 = args[3]; + } + + cx1 = 2*x1 - *cpx2; + cy1 = 2*y1 - *cpy2; + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx2; + *cpy2 = cy2; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathQuadBezTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x1, y1, x2, y2, cx, cy; + float cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + cx = *cpx + args[0]; + cy = *cpy + args[1]; + x2 = *cpx + args[2]; + y2 = *cpy + args[3]; + } else { + cx = args[0]; + cy = args[1]; + x2 = args[2]; + y2 = args[3]; + } + + // Convert to cubic bezier + cx1 = x1 + 2.0f/3.0f*(cx - x1); + cy1 = y1 + 2.0f/3.0f*(cy - y1); + cx2 = x2 + 2.0f/3.0f*(cx - x2); + cy2 = y2 + 2.0f/3.0f*(cy - y2); + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx; + *cpy2 = cy; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathQuadBezShortTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x1, y1, x2, y2, cx, cy; + float cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + x2 = *cpx + args[0]; + y2 = *cpy + args[1]; + } else { + x2 = args[0]; + y2 = args[1]; + } + + cx = 2*x1 - *cpx2; + cy = 2*y1 - *cpy2; + + // Convert to cubix bezier + cx1 = x1 + 2.0f/3.0f*(cx - x1); + cy1 = y1 + 2.0f/3.0f*(cy - y1); + cx2 = x2 + 2.0f/3.0f*(cx - x2); + cy2 = y2 + 2.0f/3.0f*(cy - y2); + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx; + *cpy2 = cy; + *cpx = x2; + *cpy = y2; +} + +static float nsvg__sqr(float x) { return x*x; } +static float nsvg__vmag(float x, float y) { return sqrtf(x*x + y*y); } + +static float nsvg__vecrat(float ux, float uy, float vx, float vy) +{ + return (ux*vx + uy*vy) / (nsvg__vmag(ux,uy) * nsvg__vmag(vx,vy)); +} + +static float nsvg__vecang(float ux, float uy, float vx, float vy) +{ + float r = nsvg__vecrat(ux,uy, vx,vy); + if (r < -1.0f) r = -1.0f; + if (r > 1.0f) r = 1.0f; + return ((ux*vy < uy*vx) ? -1.0f : 1.0f) * acosf(r); +} + +static void nsvg__pathArcTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + // Ported from canvg (https://code.google.com/p/canvg/) + float rx, ry, rotx; + float x1, y1, x2, y2, cx, cy, dx, dy, d; + float x1p, y1p, cxp, cyp, s, sa, sb; + float ux, uy, vx, vy, a1, da; + float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6]; + float sinrx, cosrx; + int fa, fs; + int i, ndivs; + float hda, kappa; + + rx = fabsf(args[0]); // y radius + ry = fabsf(args[1]); // x radius + rotx = args[2] / 180.0f * NSVG_PI; // x rotation angle + fa = fabsf(args[3]) > 1e-6 ? 1 : 0; // Large arc + fs = fabsf(args[4]) > 1e-6 ? 1 : 0; // Sweep direction + x1 = *cpx; // start point + y1 = *cpy; + if (rel) { // end point + x2 = *cpx + args[5]; + y2 = *cpy + args[6]; + } else { + x2 = args[5]; + y2 = args[6]; + } + + dx = x1 - x2; + dy = y1 - y2; + d = sqrtf(dx*dx + dy*dy); + if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) { + // The arc degenerates to a line + nsvg__lineTo(p, x2, y2); + *cpx = x2; + *cpy = y2; + return; + } + + sinrx = sinf(rotx); + cosrx = cosf(rotx); + + // Convert to center point parameterization. + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + // 1) Compute x1', y1' + x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f; + y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f; + d = nsvg__sqr(x1p)/nsvg__sqr(rx) + nsvg__sqr(y1p)/nsvg__sqr(ry); + if (d > 1) { + d = sqrtf(d); + rx *= d; + ry *= d; + } + // 2) Compute cx', cy' + s = 0.0f; + sa = nsvg__sqr(rx)*nsvg__sqr(ry) - nsvg__sqr(rx)*nsvg__sqr(y1p) - nsvg__sqr(ry)*nsvg__sqr(x1p); + sb = nsvg__sqr(rx)*nsvg__sqr(y1p) + nsvg__sqr(ry)*nsvg__sqr(x1p); + if (sa < 0.0f) sa = 0.0f; + if (sb > 0.0f) + s = sqrtf(sa / sb); + if (fa == fs) + s = -s; + cxp = s * rx * y1p / ry; + cyp = s * -ry * x1p / rx; + + // 3) Compute cx,cy from cx',cy' + cx = (x1 + x2)/2.0f + cosrx*cxp - sinrx*cyp; + cy = (y1 + y2)/2.0f + sinrx*cxp + cosrx*cyp; + + // 4) Calculate theta1, and delta theta. + ux = (x1p - cxp) / rx; + uy = (y1p - cyp) / ry; + vx = (-x1p - cxp) / rx; + vy = (-y1p - cyp) / ry; + a1 = nsvg__vecang(1.0f,0.0f, ux,uy); // Initial angle + da = nsvg__vecang(ux,uy, vx,vy); // Delta angle + +// if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI; +// if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0; + + if (fs == 0 && da > 0) + da -= 2 * NSVG_PI; + else if (fs == 1 && da < 0) + da += 2 * NSVG_PI; + + // Approximate the arc using cubic spline segments. + t[0] = cosrx; t[1] = sinrx; + t[2] = -sinrx; t[3] = cosrx; + t[4] = cx; t[5] = cy; + + // Split arc into max 90 degree segments. + // The loop assumes an iteration per end point (including start and end), this +1. + ndivs = (int)(fabsf(da) / (NSVG_PI*0.5f) + 1.0f); + hda = (da / (float)ndivs) / 2.0f; + // Fix for ticket #179: division by 0: avoid cotangens around 0 (infinite) + if ((hda < 1e-3f) && (hda > -1e-3f)) + hda *= 0.5f; + else + hda = (1.0f - cosf(hda)) / sinf(hda); + kappa = fabsf(4.0f / 3.0f * hda); + if (da < 0.0f) + kappa = -kappa; + + for (i = 0; i <= ndivs; i++) { + a = a1 + da * ((float)i/(float)ndivs); + dx = cosf(a); + dy = sinf(a); + nsvg__xformPoint(&x, &y, dx*rx, dy*ry, t); // position + nsvg__xformVec(&tanx, &tany, -dy*rx * kappa, dx*ry * kappa, t); // tangent + if (i > 0) + nsvg__cubicBezTo(p, px+ptanx,py+ptany, x-tanx, y-tany, x, y); + px = x; + py = y; + ptanx = tanx; + ptany = tany; + } + + *cpx = x2; + *cpy = y2; +} + +static void nsvg__parsePath(NSVGparser* p, const char** attr) +{ + const char* s = NULL; + char cmd = '\0'; + float args[10]; + int nargs; + int rargs = 0; + char initPoint; + float cpx, cpy, cpx2, cpy2; + const char* tmp[4]; + char closedFlag; + int i; + char item[64]; + + for (i = 0; attr[i]; i += 2) { + if (strcmp(attr[i], "d") == 0) { + s = attr[i + 1]; + } else { + tmp[0] = attr[i]; + tmp[1] = attr[i + 1]; + tmp[2] = 0; + tmp[3] = 0; + nsvg__parseAttribs(p, tmp); + } + } + + if (s) { + nsvg__resetPath(p); + cpx = 0; cpy = 0; + cpx2 = 0; cpy2 = 0; + initPoint = 0; + closedFlag = 0; + nargs = 0; + + while (*s) { + item[0] = '\0'; + if ((cmd == 'A' || cmd == 'a') && (nargs == 3 || nargs == 4)) + s = nsvg__getNextPathItemWhenArcFlag(s, item); + if (!*item) + s = nsvg__getNextPathItem(s, item); + if (!*item) break; + if (cmd != '\0' && nsvg__isCoordinate(item)) { + if (nargs < 10) + args[nargs++] = (float)nsvg__atof(item); + if (nargs >= rargs) { + switch (cmd) { + case 'm': + case 'M': + nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0); + // Moveto can be followed by multiple coordinate pairs, + // which should be treated as linetos. + cmd = (cmd == 'm') ? 'l' : 'L'; + rargs = nsvg__getArgsPerElement(cmd); + cpx2 = cpx; cpy2 = cpy; + initPoint = 1; + break; + case 'l': + case 'L': + nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + case 'H': + case 'h': + nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + case 'V': + case 'v': + nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + case 'C': + case 'c': + nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0); + break; + case 'S': + case 's': + nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); + break; + case 'Q': + case 'q': + nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0); + break; + case 'T': + case 't': + nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0); + break; + case 'A': + case 'a': + nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + default: + if (nargs >= 2) { + cpx = args[nargs-2]; + cpy = args[nargs-1]; + cpx2 = cpx; cpy2 = cpy; + } + break; + } + nargs = 0; + } + } else { + cmd = item[0]; + if (cmd == 'M' || cmd == 'm') { + // Commit path. + if (p->npts > 0) + nsvg__addPath(p, closedFlag); + // Start new subpath. + nsvg__resetPath(p); + closedFlag = 0; + nargs = 0; + } else if (initPoint == 0) { + // Do not allow other commands until initial point has been set (moveTo called once). + cmd = '\0'; + } + if (cmd == 'Z' || cmd == 'z') { + closedFlag = 1; + // Commit path. + if (p->npts > 0) { + // Move current point to first point + cpx = p->pts[0]; + cpy = p->pts[1]; + cpx2 = cpx; cpy2 = cpy; + nsvg__addPath(p, closedFlag); + } + // Start new subpath. + nsvg__resetPath(p); + nsvg__moveTo(p, cpx, cpy); + closedFlag = 0; + nargs = 0; + } + rargs = nsvg__getArgsPerElement(cmd); + if (rargs == -1) { + // Command not recognized + cmd = '\0'; + rargs = 0; + } + } + } + // Commit path. + if (p->npts) + nsvg__addPath(p, closedFlag); + } + + nsvg__addShape(p); +} + +static void nsvg__parseRect(NSVGparser* p, const char** attr) +{ + float x = 0.0f; + float y = 0.0f; + float w = 0.0f; + float h = 0.0f; + float rx = -1.0f; // marks not set + float ry = -1.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "x") == 0) x = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y") == 0) y = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "width") == 0) w = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)); + if (strcmp(attr[i], "height") == 0) h = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)); + if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); + if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); + } + } + + if (rx < 0.0f && ry > 0.0f) rx = ry; + if (ry < 0.0f && rx > 0.0f) ry = rx; + if (rx < 0.0f) rx = 0.0f; + if (ry < 0.0f) ry = 0.0f; + if (rx > w/2.0f) rx = w/2.0f; + if (ry > h/2.0f) ry = h/2.0f; + + if (w != 0.0f && h != 0.0f) { + nsvg__resetPath(p); + + if (rx < 0.00001f || ry < 0.0001f) { + nsvg__moveTo(p, x, y); + nsvg__lineTo(p, x+w, y); + nsvg__lineTo(p, x+w, y+h); + nsvg__lineTo(p, x, y+h); + } else { + // Rounded rectangle + nsvg__moveTo(p, x+rx, y); + nsvg__lineTo(p, x+w-rx, y); + nsvg__cubicBezTo(p, x+w-rx*(1-NSVG_KAPPA90), y, x+w, y+ry*(1-NSVG_KAPPA90), x+w, y+ry); + nsvg__lineTo(p, x+w, y+h-ry); + nsvg__cubicBezTo(p, x+w, y+h-ry*(1-NSVG_KAPPA90), x+w-rx*(1-NSVG_KAPPA90), y+h, x+w-rx, y+h); + nsvg__lineTo(p, x+rx, y+h); + nsvg__cubicBezTo(p, x+rx*(1-NSVG_KAPPA90), y+h, x, y+h-ry*(1-NSVG_KAPPA90), x, y+h-ry); + nsvg__lineTo(p, x, y+ry); + nsvg__cubicBezTo(p, x, y+ry*(1-NSVG_KAPPA90), x+rx*(1-NSVG_KAPPA90), y, x+rx, y); + } + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseCircle(NSVGparser* p, const char** attr) +{ + float cx = 0.0f; + float cy = 0.0f; + float r = 0.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "r") == 0) r = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualLength(p))); + } + } + + if (r > 0.0f) { + nsvg__resetPath(p); + + nsvg__moveTo(p, cx+r, cy); + nsvg__cubicBezTo(p, cx+r, cy+r*NSVG_KAPPA90, cx+r*NSVG_KAPPA90, cy+r, cx, cy+r); + nsvg__cubicBezTo(p, cx-r*NSVG_KAPPA90, cy+r, cx-r, cy+r*NSVG_KAPPA90, cx-r, cy); + nsvg__cubicBezTo(p, cx-r, cy-r*NSVG_KAPPA90, cx-r*NSVG_KAPPA90, cy-r, cx, cy-r); + nsvg__cubicBezTo(p, cx+r*NSVG_KAPPA90, cy-r, cx+r, cy-r*NSVG_KAPPA90, cx+r, cy); + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseEllipse(NSVGparser* p, const char** attr) +{ + float cx = 0.0f; + float cy = 0.0f; + float rx = 0.0f; + float ry = 0.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); + if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); + } + } + + if (rx > 0.0f && ry > 0.0f) { + + nsvg__resetPath(p); + + nsvg__moveTo(p, cx+rx, cy); + nsvg__cubicBezTo(p, cx+rx, cy+ry*NSVG_KAPPA90, cx+rx*NSVG_KAPPA90, cy+ry, cx, cy+ry); + nsvg__cubicBezTo(p, cx-rx*NSVG_KAPPA90, cy+ry, cx-rx, cy+ry*NSVG_KAPPA90, cx-rx, cy); + nsvg__cubicBezTo(p, cx-rx, cy-ry*NSVG_KAPPA90, cx-rx*NSVG_KAPPA90, cy-ry, cx, cy-ry); + nsvg__cubicBezTo(p, cx+rx*NSVG_KAPPA90, cy-ry, cx+rx, cy-ry*NSVG_KAPPA90, cx+rx, cy); + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseLine(NSVGparser* p, const char** attr) +{ + float x1 = 0.0; + float y1 = 0.0; + float x2 = 0.0; + float y2 = 0.0; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "x1") == 0) x1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y1") == 0) y1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "x2") == 0) x2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y2") == 0) y2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + } + } + + nsvg__resetPath(p); + + nsvg__moveTo(p, x1, y1); + nsvg__lineTo(p, x2, y2); + + nsvg__addPath(p, 0); + + nsvg__addShape(p); +} + +static void nsvg__parsePoly(NSVGparser* p, const char** attr, int closeFlag) +{ + int i; + const char* s; + float args[2]; + int nargs, npts = 0; + char item[64]; + + nsvg__resetPath(p); + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "points") == 0) { + s = attr[i + 1]; + nargs = 0; + while (*s) { + s = nsvg__getNextPathItem(s, item); + args[nargs++] = (float)nsvg__atof(item); + if (nargs >= 2) { + if (npts == 0) + nsvg__moveTo(p, args[0], args[1]); + else + nsvg__lineTo(p, args[0], args[1]); + nargs = 0; + npts++; + } + } + } + } + } + + nsvg__addPath(p, (char)closeFlag); + + nsvg__addShape(p); +} + +static void nsvg__parseSVG(NSVGparser* p, const char** attr) +{ + int i; + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "width") == 0) { + p->image->width = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); + } else if (strcmp(attr[i], "height") == 0) { + p->image->height = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); + } else if (strcmp(attr[i], "viewBox") == 0) { + const char *s = attr[i + 1]; + char buf[64]; + s = nsvg__parseNumber(s, buf, 64); + p->viewMinx = nsvg__atof(buf); + while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; + if (!*s) return; + s = nsvg__parseNumber(s, buf, 64); + p->viewMiny = nsvg__atof(buf); + while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; + if (!*s) return; + s = nsvg__parseNumber(s, buf, 64); + p->viewWidth = nsvg__atof(buf); + while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; + if (!*s) return; + s = nsvg__parseNumber(s, buf, 64); + p->viewHeight = nsvg__atof(buf); + } else if (strcmp(attr[i], "preserveAspectRatio") == 0) { + if (strstr(attr[i + 1], "none") != 0) { + // No uniform scaling + p->alignType = NSVG_ALIGN_NONE; + } else { + // Parse X align + if (strstr(attr[i + 1], "xMin") != 0) + p->alignX = NSVG_ALIGN_MIN; + else if (strstr(attr[i + 1], "xMid") != 0) + p->alignX = NSVG_ALIGN_MID; + else if (strstr(attr[i + 1], "xMax") != 0) + p->alignX = NSVG_ALIGN_MAX; + // Parse X align + if (strstr(attr[i + 1], "yMin") != 0) + p->alignY = NSVG_ALIGN_MIN; + else if (strstr(attr[i + 1], "yMid") != 0) + p->alignY = NSVG_ALIGN_MID; + else if (strstr(attr[i + 1], "yMax") != 0) + p->alignY = NSVG_ALIGN_MAX; + // Parse meet/slice + p->alignType = NSVG_ALIGN_MEET; + if (strstr(attr[i + 1], "slice") != 0) + p->alignType = NSVG_ALIGN_SLICE; + } + } + } + } +} + +static void nsvg__parseGradient(NSVGparser* p, const char** attr, signed char type) +{ + int i; + NSVGgradientData* grad = (NSVGgradientData*)malloc(sizeof(NSVGgradientData)); + if (grad == NULL) return; + memset(grad, 0, sizeof(NSVGgradientData)); + grad->units = NSVG_OBJECT_SPACE; + grad->type = type; + if (grad->type == NSVG_PAINT_LINEAR_GRADIENT) { + grad->linear.x1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); + grad->linear.y1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); + grad->linear.x2 = nsvg__coord(100.0f, NSVG_UNITS_PERCENT); + grad->linear.y2 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); + } else if (grad->type == NSVG_PAINT_RADIAL_GRADIENT) { + grad->radial.cx = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); + grad->radial.cy = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); + grad->radial.r = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); + } + + nsvg__xformIdentity(grad->xform); + + for (i = 0; attr[i]; i += 2) { + if (strcmp(attr[i], "id") == 0) { + strncpy(grad->id, attr[i+1], 63); + grad->id[63] = '\0'; + } else if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "gradientUnits") == 0) { + if (strcmp(attr[i+1], "objectBoundingBox") == 0) + grad->units = NSVG_OBJECT_SPACE; + else + grad->units = NSVG_USER_SPACE; + } else if (strcmp(attr[i], "gradientTransform") == 0) { + nsvg__parseTransform(grad->xform, attr[i + 1]); + } else if (strcmp(attr[i], "cx") == 0) { + grad->radial.cx = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "cy") == 0) { + grad->radial.cy = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "r") == 0) { + grad->radial.r = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "fx") == 0) { + grad->radial.fx = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "fy") == 0) { + grad->radial.fy = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "x1") == 0) { + grad->linear.x1 = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "y1") == 0) { + grad->linear.y1 = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "x2") == 0) { + grad->linear.x2 = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "y2") == 0) { + grad->linear.y2 = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "spreadMethod") == 0) { + if (strcmp(attr[i+1], "pad") == 0) + grad->spread = NSVG_SPREAD_PAD; + else if (strcmp(attr[i+1], "reflect") == 0) + grad->spread = NSVG_SPREAD_REFLECT; + else if (strcmp(attr[i+1], "repeat") == 0) + grad->spread = NSVG_SPREAD_REPEAT; + } else if (strcmp(attr[i], "xlink:href") == 0) { + const char *href = attr[i+1]; + strncpy(grad->ref, href+1, 62); + grad->ref[62] = '\0'; + } + } + } + + grad->next = p->gradients; + p->gradients = grad; +} + +static void nsvg__parseGradientStop(NSVGparser* p, const char** attr) +{ + NSVGattrib* curAttr = nsvg__getAttr(p); + NSVGgradientData* grad; + NSVGgradientStop* stop; + int i, idx; + + curAttr->stopOffset = 0; + curAttr->stopColor = 0; + curAttr->stopOpacity = 1.0f; + + for (i = 0; attr[i]; i += 2) { + nsvg__parseAttr(p, attr[i], attr[i + 1]); + } + + // Add stop to the last gradient. + grad = p->gradients; + if (grad == NULL) return; + + grad->nstops++; + grad->stops = (NSVGgradientStop*)realloc(grad->stops, sizeof(NSVGgradientStop)*grad->nstops); + if (grad->stops == NULL) return; + + // Insert + idx = grad->nstops-1; + for (i = 0; i < grad->nstops-1; i++) { + if (curAttr->stopOffset < grad->stops[i].offset) { + idx = i; + break; + } + } + if (idx != grad->nstops-1) { + for (i = grad->nstops-1; i > idx; i--) + grad->stops[i] = grad->stops[i-1]; + } + + stop = &grad->stops[idx]; + stop->color = curAttr->stopColor; + stop->color |= (unsigned int)(curAttr->stopOpacity*255) << 24; + stop->offset = curAttr->stopOffset; +} + +static void nsvg__startElement(void* ud, const char* el, const char** attr) +{ + NSVGparser* p = (NSVGparser*)ud; + + if (p->defsFlag) { + // Skip everything but gradients in defs + if (strcmp(el, "linearGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); + } else if (strcmp(el, "radialGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); + } else if (strcmp(el, "stop") == 0) { + nsvg__parseGradientStop(p, attr); + } + return; + } + + if (strcmp(el, "g") == 0) { + nsvg__pushAttr(p); + nsvg__parseAttribs(p, attr); + } else if (strcmp(el, "path") == 0) { + if (p->pathFlag) // Do not allow nested paths. + return; + nsvg__pushAttr(p); + nsvg__parsePath(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "rect") == 0) { + nsvg__pushAttr(p); + nsvg__parseRect(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "circle") == 0) { + nsvg__pushAttr(p); + nsvg__parseCircle(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "ellipse") == 0) { + nsvg__pushAttr(p); + nsvg__parseEllipse(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "line") == 0) { + nsvg__pushAttr(p); + nsvg__parseLine(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "polyline") == 0) { + nsvg__pushAttr(p); + nsvg__parsePoly(p, attr, 0); + nsvg__popAttr(p); + } else if (strcmp(el, "polygon") == 0) { + nsvg__pushAttr(p); + nsvg__parsePoly(p, attr, 1); + nsvg__popAttr(p); + } else if (strcmp(el, "linearGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); + } else if (strcmp(el, "radialGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); + } else if (strcmp(el, "stop") == 0) { + nsvg__parseGradientStop(p, attr); + } else if (strcmp(el, "defs") == 0) { + p->defsFlag = 1; + } else if (strcmp(el, "svg") == 0) { + nsvg__parseSVG(p, attr); + } +} + +static void nsvg__endElement(void* ud, const char* el) +{ + NSVGparser* p = (NSVGparser*)ud; + + if (strcmp(el, "g") == 0) { + nsvg__popAttr(p); + } else if (strcmp(el, "path") == 0) { + p->pathFlag = 0; + } else if (strcmp(el, "defs") == 0) { + p->defsFlag = 0; + } +} + +static void nsvg__content(void* ud, const char* s) +{ + NSVG_NOTUSED(ud); + NSVG_NOTUSED(s); + // empty +} + +static void nsvg__imageBounds(NSVGparser* p, float* bounds) +{ + NSVGshape* shape; + shape = p->image->shapes; + if (shape == NULL) { + bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0; + return; + } + bounds[0] = shape->bounds[0]; + bounds[1] = shape->bounds[1]; + bounds[2] = shape->bounds[2]; + bounds[3] = shape->bounds[3]; + for (shape = shape->next; shape != NULL; shape = shape->next) { + bounds[0] = nsvg__minf(bounds[0], shape->bounds[0]); + bounds[1] = nsvg__minf(bounds[1], shape->bounds[1]); + bounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]); + bounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]); + } +} + +static float nsvg__viewAlign(float content, float container, int type) +{ + if (type == NSVG_ALIGN_MIN) + return 0; + else if (type == NSVG_ALIGN_MAX) + return container - content; + // mid + return (container - content) * 0.5f; +} + +static void nsvg__scaleGradient(NSVGgradient* grad, float tx, float ty, float sx, float sy) +{ + float t[6]; + nsvg__xformSetTranslation(t, tx, ty); + nsvg__xformMultiply (grad->xform, t); + + nsvg__xformSetScale(t, sx, sy); + nsvg__xformMultiply (grad->xform, t); +} + +static void nsvg__scaleToViewbox(NSVGparser* p, const char* units) +{ + NSVGshape* shape; + NSVGpath* path; + float tx, ty, sx, sy, us, bounds[4], t[6], avgs; + int i; + float* pt; + + // Guess image size if not set completely. + nsvg__imageBounds(p, bounds); + + if (p->viewWidth == 0) { + if (p->image->width > 0) { + p->viewWidth = p->image->width; + } else { + p->viewMinx = bounds[0]; + p->viewWidth = bounds[2] - bounds[0]; + } + } + if (p->viewHeight == 0) { + if (p->image->height > 0) { + p->viewHeight = p->image->height; + } else { + p->viewMiny = bounds[1]; + p->viewHeight = bounds[3] - bounds[1]; + } + } + if (p->image->width == 0) + p->image->width = p->viewWidth; + if (p->image->height == 0) + p->image->height = p->viewHeight; + + tx = -p->viewMinx; + ty = -p->viewMiny; + sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0; + sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0; + // Unit scaling + us = 1.0f / nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f); + + // Fix aspect ratio + if (p->alignType == NSVG_ALIGN_MEET) { + // fit whole image into viewbox + sx = sy = nsvg__minf(sx, sy); + tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; + ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; + } else if (p->alignType == NSVG_ALIGN_SLICE) { + // fill whole viewbox with image + sx = sy = nsvg__maxf(sx, sy); + tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; + ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; + } + + // Transform + sx *= us; + sy *= us; + avgs = (sx+sy) / 2.0f; + for (shape = p->image->shapes; shape != NULL; shape = shape->next) { + shape->bounds[0] = (shape->bounds[0] + tx) * sx; + shape->bounds[1] = (shape->bounds[1] + ty) * sy; + shape->bounds[2] = (shape->bounds[2] + tx) * sx; + shape->bounds[3] = (shape->bounds[3] + ty) * sy; + for (path = shape->paths; path != NULL; path = path->next) { + path->bounds[0] = (path->bounds[0] + tx) * sx; + path->bounds[1] = (path->bounds[1] + ty) * sy; + path->bounds[2] = (path->bounds[2] + tx) * sx; + path->bounds[3] = (path->bounds[3] + ty) * sy; + for (i =0; i < path->npts; i++) { + pt = &path->pts[i*2]; + pt[0] = (pt[0] + tx) * sx; + pt[1] = (pt[1] + ty) * sy; + } + } + + if (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT || shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) { + nsvg__scaleGradient(shape->fill.gradient, tx,ty, sx,sy); + memcpy(t, shape->fill.gradient->xform, sizeof(float)*6); + nsvg__xformInverse(shape->fill.gradient->xform, t); + } + if (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT || shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) { + nsvg__scaleGradient(shape->stroke.gradient, tx,ty, sx,sy); + memcpy(t, shape->stroke.gradient->xform, sizeof(float)*6); + nsvg__xformInverse(shape->stroke.gradient->xform, t); + } + + shape->strokeWidth *= avgs; + shape->strokeDashOffset *= avgs; + for (i = 0; i < shape->strokeDashCount; i++) + shape->strokeDashArray[i] *= avgs; + } +} + +static void nsvg__createGradients(NSVGparser* p) +{ + NSVGshape* shape; + + for (shape = p->image->shapes; shape != NULL; shape = shape->next) { + if (shape->fill.type == NSVG_PAINT_UNDEF) { + if (shape->fillGradient[0] != '\0') { + float inv[6], localBounds[4]; + nsvg__xformInverse(inv, shape->xform); + nsvg__getLocalBounds(localBounds, shape, inv); + shape->fill.gradient = nsvg__createGradient(p, shape->fillGradient, localBounds, shape->xform, &shape->fill.type); + } + if (shape->fill.type == NSVG_PAINT_UNDEF) { + shape->fill.type = NSVG_PAINT_NONE; + } + } + if (shape->stroke.type == NSVG_PAINT_UNDEF) { + if (shape->strokeGradient[0] != '\0') { + float inv[6], localBounds[4]; + nsvg__xformInverse(inv, shape->xform); + nsvg__getLocalBounds(localBounds, shape, inv); + shape->stroke.gradient = nsvg__createGradient(p, shape->strokeGradient, localBounds, shape->xform, &shape->stroke.type); + } + if (shape->stroke.type == NSVG_PAINT_UNDEF) { + shape->stroke.type = NSVG_PAINT_NONE; + } + } + } +} + +NSVGimage* nsvgParse(char* input, const char* units, float dpi) +{ + NSVGparser* p; + NSVGimage* ret = 0; + + p = nsvg__createParser(); + if (p == NULL) { + return NULL; + } + p->dpi = dpi; + + nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p); + + // Create gradients after all definitions have been parsed + nsvg__createGradients(p); + + // Scale to viewBox + nsvg__scaleToViewbox(p, units); + + ret = p->image; + p->image = NULL; + + nsvg__deleteParser(p); + + return ret; +} + +NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) +{ + FILE* fp = NULL; + size_t size; + char* data = NULL; + NSVGimage* image = NULL; + + fp = fopen(filename, "rb"); + if (!fp) goto error; + fseek(fp, 0, SEEK_END); + size = ftell(fp); + fseek(fp, 0, SEEK_SET); + data = (char*)malloc(size+1); + if (data == NULL) goto error; + if (fread(data, 1, size, fp) != size) goto error; + data[size] = '\0'; // Must be null terminated. + fclose(fp); + image = nsvgParse(data, units, dpi); + free(data); + + return image; + +error: + if (fp) fclose(fp); + if (data) free(data); + if (image) nsvgDelete(image); + return NULL; +} + +NSVGpath* nsvgDuplicatePath(NSVGpath* p) +{ + NSVGpath* res = NULL; + + if (p == NULL) + return NULL; + + res = (NSVGpath*)malloc(sizeof(NSVGpath)); + if (res == NULL) goto error; + memset(res, 0, sizeof(NSVGpath)); + + res->pts = (float*)malloc(p->npts*2*sizeof(float)); + if (res->pts == NULL) goto error; + memcpy(res->pts, p->pts, p->npts * sizeof(float) * 2); + res->npts = p->npts; + + memcpy(res->bounds, p->bounds, sizeof(p->bounds)); + + res->closed = p->closed; + + return res; + +error: + if (res != NULL) { + free(res->pts); + free(res); + } + return NULL; +} + +void nsvgDelete(NSVGimage* image) +{ + NSVGshape *snext, *shape; + if (image == NULL) return; + shape = image->shapes; + while (shape != NULL) { + snext = shape->next; + nsvg__deletePaths(shape->paths); + nsvg__deletePaint(&shape->fill); + nsvg__deletePaint(&shape->stroke); + free(shape); + shape = snext; + } + free(image); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // NANOSVG_IMPLEMENTATION + +#endif // NANOSVG_H diff --git a/src/core/nanosvg/nanosvgrast.h b/src/core/nanosvg/nanosvgrast.h new file mode 100644 index 000000000..a3295098c --- /dev/null +++ b/src/core/nanosvg/nanosvgrast.h @@ -0,0 +1,1459 @@ +/* + * Copyright (c) 2013-14 Mikko Mononen memon@inside.org + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * The polygon rasterization is heavily based on stb_truetype rasterizer + * by Sean Barrett - http://nothings.org/ + * + */ + +#ifndef NANOSVGRAST_H +#define NANOSVGRAST_H + +#include "nanosvg.h" + +#ifndef NANOSVGRAST_CPLUSPLUS +#ifdef __cplusplus +extern "C" { +#endif +#endif + +typedef struct NSVGrasterizer NSVGrasterizer; + +/* Example Usage: + // Load SVG + NSVGimage* image; + image = nsvgParseFromFile("test.svg", "px", 96); + + // Create rasterizer (can be used to render multiple images). + struct NSVGrasterizer* rast = nsvgCreateRasterizer(); + // Allocate memory for image + unsigned char* img = malloc(w*h*4); + // Rasterize + nsvgRasterize(rast, image, 0,0,1, img, w, h, w*4); +*/ + +// Allocated rasterizer context. +NSVGrasterizer* nsvgCreateRasterizer(void); + +// Rasterizes SVG image, returns RGBA image (non-premultiplied alpha) +// r - pointer to rasterizer context +// image - pointer to image to rasterize +// tx,ty - image offset (applied after scaling) +// scale - image scale +// dst - pointer to destination image data, 4 bytes per pixel (RGBA) +// w - width of the image to render +// h - height of the image to render +// stride - number of bytes per scaleline in the destination buffer +void nsvgRasterize(NSVGrasterizer* r, + NSVGimage* image, float tx, float ty, float scale, + unsigned char* dst, int w, int h, int stride); + +// Deletes rasterizer context. +void nsvgDeleteRasterizer(NSVGrasterizer*); + + +#ifndef NANOSVGRAST_CPLUSPLUS +#ifdef __cplusplus +} +#endif +#endif + +#ifdef NANOSVGRAST_IMPLEMENTATION + +#include +#include +#include + +#define NSVG__SUBSAMPLES 5 +#define NSVG__FIXSHIFT 10 +#define NSVG__FIX (1 << NSVG__FIXSHIFT) +#define NSVG__FIXMASK (NSVG__FIX-1) +#define NSVG__MEMPAGE_SIZE 1024 + +typedef struct NSVGedge { + float x0,y0, x1,y1; + int dir; + struct NSVGedge* next; +} NSVGedge; + +typedef struct NSVGpoint { + float x, y; + float dx, dy; + float len; + float dmx, dmy; + unsigned char flags; +} NSVGpoint; + +typedef struct NSVGactiveEdge { + int x,dx; + float ey; + int dir; + struct NSVGactiveEdge *next; +} NSVGactiveEdge; + +typedef struct NSVGmemPage { + unsigned char mem[NSVG__MEMPAGE_SIZE]; + int size; + struct NSVGmemPage* next; +} NSVGmemPage; + +typedef struct NSVGcachedPaint { + signed char type; + char spread; + float xform[6]; + unsigned int colors[256]; +} NSVGcachedPaint; + +struct NSVGrasterizer +{ + float px, py; + + float tessTol; + float distTol; + + NSVGedge* edges; + int nedges; + int cedges; + + NSVGpoint* points; + int npoints; + int cpoints; + + NSVGpoint* points2; + int npoints2; + int cpoints2; + + NSVGactiveEdge* freelist; + NSVGmemPage* pages; + NSVGmemPage* curpage; + + unsigned char* scanline; + int cscanline; + + unsigned char* bitmap; + int width, height, stride; +}; + +NSVGrasterizer* nsvgCreateRasterizer(void) +{ + NSVGrasterizer* r = (NSVGrasterizer*)malloc(sizeof(NSVGrasterizer)); + if (r == NULL) goto error; + memset(r, 0, sizeof(NSVGrasterizer)); + + r->tessTol = 0.25f; + r->distTol = 0.01f; + + return r; + +error: + nsvgDeleteRasterizer(r); + return NULL; +} + +void nsvgDeleteRasterizer(NSVGrasterizer* r) +{ + NSVGmemPage* p; + + if (r == NULL) return; + + p = r->pages; + while (p != NULL) { + NSVGmemPage* next = p->next; + free(p); + p = next; + } + + if (r->edges) free(r->edges); + if (r->points) free(r->points); + if (r->points2) free(r->points2); + if (r->scanline) free(r->scanline); + + free(r); +} + +static NSVGmemPage* nsvg__nextPage(NSVGrasterizer* r, NSVGmemPage* cur) +{ + NSVGmemPage *newp; + + // If using existing chain, return the next page in chain + if (cur != NULL && cur->next != NULL) { + return cur->next; + } + + // Alloc new page + newp = (NSVGmemPage*)malloc(sizeof(NSVGmemPage)); + if (newp == NULL) return NULL; + memset(newp, 0, sizeof(NSVGmemPage)); + + // Add to linked list + if (cur != NULL) + cur->next = newp; + else + r->pages = newp; + + return newp; +} + +static void nsvg__resetPool(NSVGrasterizer* r) +{ + NSVGmemPage* p = r->pages; + while (p != NULL) { + p->size = 0; + p = p->next; + } + r->curpage = r->pages; +} + +static unsigned char* nsvg__alloc(NSVGrasterizer* r, int size) +{ + unsigned char* buf; + if (size > NSVG__MEMPAGE_SIZE) return NULL; + if (r->curpage == NULL || r->curpage->size+size > NSVG__MEMPAGE_SIZE) { + r->curpage = nsvg__nextPage(r, r->curpage); + } + buf = &r->curpage->mem[r->curpage->size]; + r->curpage->size += size; + return buf; +} + +static int nsvg__ptEquals(float x1, float y1, float x2, float y2, float tol) +{ + float dx = x2 - x1; + float dy = y2 - y1; + return dx*dx + dy*dy < tol*tol; +} + +static void nsvg__addPathPoint(NSVGrasterizer* r, float x, float y, int flags) +{ + NSVGpoint* pt; + + if (r->npoints > 0) { + pt = &r->points[r->npoints-1]; + if (nsvg__ptEquals(pt->x,pt->y, x,y, r->distTol)) { + pt->flags = (unsigned char)(pt->flags | flags); + return; + } + } + + if (r->npoints+1 > r->cpoints) { + r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; + r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints); + if (r->points == NULL) return; + } + + pt = &r->points[r->npoints]; + pt->x = x; + pt->y = y; + pt->flags = (unsigned char)flags; + r->npoints++; +} + +static void nsvg__appendPathPoint(NSVGrasterizer* r, NSVGpoint pt) +{ + if (r->npoints+1 > r->cpoints) { + r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; + r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints); + if (r->points == NULL) return; + } + r->points[r->npoints] = pt; + r->npoints++; +} + +static void nsvg__duplicatePoints(NSVGrasterizer* r) +{ + if (r->npoints > r->cpoints2) { + r->cpoints2 = r->npoints; + r->points2 = (NSVGpoint*)realloc(r->points2, sizeof(NSVGpoint) * r->cpoints2); + if (r->points2 == NULL) return; + } + + memcpy(r->points2, r->points, sizeof(NSVGpoint) * r->npoints); + r->npoints2 = r->npoints; +} + +static void nsvg__addEdge(NSVGrasterizer* r, float x0, float y0, float x1, float y1) +{ + NSVGedge* e; + + // Skip horizontal edges + if (y0 == y1) + return; + + if (r->nedges+1 > r->cedges) { + r->cedges = r->cedges > 0 ? r->cedges * 2 : 64; + r->edges = (NSVGedge*)realloc(r->edges, sizeof(NSVGedge) * r->cedges); + if (r->edges == NULL) return; + } + + e = &r->edges[r->nedges]; + r->nedges++; + + if (y0 < y1) { + e->x0 = x0; + e->y0 = y0; + e->x1 = x1; + e->y1 = y1; + e->dir = 1; + } else { + e->x0 = x1; + e->y0 = y1; + e->x1 = x0; + e->y1 = y0; + e->dir = -1; + } +} + +static float nsvg__normalize(float *x, float* y) +{ + float d = sqrtf((*x)*(*x) + (*y)*(*y)); + if (d > 1e-6f) { + float id = 1.0f / d; + *x *= id; + *y *= id; + } + return d; +} + +static float nsvg__absf(float x) { return x < 0 ? -x : x; } +static float nsvg__roundf(float x) { return (x >= 0) ? (float)floorf(x + 0.5f) : (float)ceilf(x - 0.5f); } + +static void nsvg__flattenCubicBez(NSVGrasterizer* r, + float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4, + int level, int type) +{ + float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234; + float dx,dy,d2,d3; + + if (level > 10) return; + + x12 = (x1+x2)*0.5f; + y12 = (y1+y2)*0.5f; + x23 = (x2+x3)*0.5f; + y23 = (y2+y3)*0.5f; + x34 = (x3+x4)*0.5f; + y34 = (y3+y4)*0.5f; + x123 = (x12+x23)*0.5f; + y123 = (y12+y23)*0.5f; + + dx = x4 - x1; + dy = y4 - y1; + d2 = nsvg__absf(((x2 - x4) * dy - (y2 - y4) * dx)); + d3 = nsvg__absf(((x3 - x4) * dy - (y3 - y4) * dx)); + + if ((d2 + d3)*(d2 + d3) < r->tessTol * (dx*dx + dy*dy)) { + nsvg__addPathPoint(r, x4, y4, type); + return; + } + + x234 = (x23+x34)*0.5f; + y234 = (y23+y34)*0.5f; + x1234 = (x123+x234)*0.5f; + y1234 = (y123+y234)*0.5f; + + nsvg__flattenCubicBez(r, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0); + nsvg__flattenCubicBez(r, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type); +} + +static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale) +{ + int i, j; + NSVGpath* path; + + for (path = shape->paths; path != NULL; path = path->next) { + r->npoints = 0; + // Flatten path + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); + for (i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, 0); + } + // Close path + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); + // Build edges + for (i = 0, j = r->npoints-1; i < r->npoints; j = i++) + nsvg__addEdge(r, r->points[j].x, r->points[j].y, r->points[i].x, r->points[i].y); + } +} + +enum NSVGpointFlags +{ + NSVG_PT_CORNER = 0x01, + NSVG_PT_BEVEL = 0x02, + NSVG_PT_LEFT = 0x04 +}; + +static void nsvg__initClosed(NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + float len = nsvg__normalize(&dx, &dy); + float px = p0->x + dx*len*0.5f, py = p0->y + dy*len*0.5f; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__buttCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) +{ + float w = lineWidth * 0.5f; + float px = p->x, py = p->y; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + + nsvg__addEdge(r, lx, ly, rx, ry); + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__squareCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) +{ + float w = lineWidth * 0.5f; + float px = p->x - dx*w, py = p->y - dy*w; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + + nsvg__addEdge(r, lx, ly, rx, ry); + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +#ifndef NSVG_PI +#define NSVG_PI (3.14159265358979323846264338327f) +#endif + +static void nsvg__roundCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int ncap, int connect) +{ + int i; + float w = lineWidth * 0.5f; + float px = p->x, py = p->y; + float dlx = dy, dly = -dx; + float lx = 0, ly = 0, rx = 0, ry = 0, prevx = 0, prevy = 0; + + for (i = 0; i < ncap; i++) { + float a = (float)i/(float)(ncap-1)*NSVG_PI; + float ax = cosf(a) * w, ay = sinf(a) * w; + float x = px - dlx*ax - dx*ay; + float y = py - dly*ax - dy*ay; + + if (i > 0) + nsvg__addEdge(r, prevx, prevy, x, y); + + prevx = x; + prevy = y; + + if (i == 0) { + lx = x; ly = y; + } else if (i == ncap-1) { + rx = x; ry = y; + } + } + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__bevelJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float lx0 = p1->x - (dlx0 * w), ly0 = p1->y - (dly0 * w); + float rx0 = p1->x + (dlx0 * w), ry0 = p1->y + (dly0 * w); + float lx1 = p1->x - (dlx1 * w), ly1 = p1->y - (dly1 * w); + float rx1 = p1->x + (dlx1 * w), ry1 = p1->y + (dly1 * w); + + nsvg__addEdge(r, lx0, ly0, left->x, left->y); + nsvg__addEdge(r, lx1, ly1, lx0, ly0); + + nsvg__addEdge(r, right->x, right->y, rx0, ry0); + nsvg__addEdge(r, rx0, ry0, rx1, ry1); + + left->x = lx1; left->y = ly1; + right->x = rx1; right->y = ry1; +} + +static void nsvg__miterJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float lx0, rx0, lx1, rx1; + float ly0, ry0, ly1, ry1; + + if (p1->flags & NSVG_PT_LEFT) { + lx0 = lx1 = p1->x - p1->dmx * w; + ly0 = ly1 = p1->y - p1->dmy * w; + nsvg__addEdge(r, lx1, ly1, left->x, left->y); + + rx0 = p1->x + (dlx0 * w); + ry0 = p1->y + (dly0 * w); + rx1 = p1->x + (dlx1 * w); + ry1 = p1->y + (dly1 * w); + nsvg__addEdge(r, right->x, right->y, rx0, ry0); + nsvg__addEdge(r, rx0, ry0, rx1, ry1); + } else { + lx0 = p1->x - (dlx0 * w); + ly0 = p1->y - (dly0 * w); + lx1 = p1->x - (dlx1 * w); + ly1 = p1->y - (dly1 * w); + nsvg__addEdge(r, lx0, ly0, left->x, left->y); + nsvg__addEdge(r, lx1, ly1, lx0, ly0); + + rx0 = rx1 = p1->x + p1->dmx * w; + ry0 = ry1 = p1->y + p1->dmy * w; + nsvg__addEdge(r, right->x, right->y, rx1, ry1); + } + + left->x = lx1; left->y = ly1; + right->x = rx1; right->y = ry1; +} + +static void nsvg__roundJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth, int ncap) +{ + int i, n; + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float a0 = atan2f(dly0, dlx0); + float a1 = atan2f(dly1, dlx1); + float da = a1 - a0; + float lx, ly, rx, ry; + + if (da < NSVG_PI) da += NSVG_PI*2; + if (da > NSVG_PI) da -= NSVG_PI*2; + + n = (int)ceilf((nsvg__absf(da) / NSVG_PI) * (float)ncap); + if (n < 2) n = 2; + if (n > ncap) n = ncap; + + lx = left->x; + ly = left->y; + rx = right->x; + ry = right->y; + + for (i = 0; i < n; i++) { + float u = (float)i/(float)(n-1); + float a = a0 + u*da; + float ax = cosf(a) * w, ay = sinf(a) * w; + float lx1 = p1->x - ax, ly1 = p1->y - ay; + float rx1 = p1->x + ax, ry1 = p1->y + ay; + + nsvg__addEdge(r, lx1, ly1, lx, ly); + nsvg__addEdge(r, rx, ry, rx1, ry1); + + lx = lx1; ly = ly1; + rx = rx1; ry = ry1; + } + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__straightJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float lx = p1->x - (p1->dmx * w), ly = p1->y - (p1->dmy * w); + float rx = p1->x + (p1->dmx * w), ry = p1->y + (p1->dmy * w); + + nsvg__addEdge(r, lx, ly, left->x, left->y); + nsvg__addEdge(r, right->x, right->y, rx, ry); + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static int nsvg__curveDivs(float r, float arc, float tol) +{ + float da = acosf(r / (r + tol)) * 2.0f; + int divs = (int)ceilf(arc / da); + if (divs < 2) divs = 2; + return divs; +} + +static void nsvg__expandStroke(NSVGrasterizer* r, NSVGpoint* points, int npoints, int closed, int lineJoin, int lineCap, float lineWidth) +{ + int ncap = nsvg__curveDivs(lineWidth*0.5f, NSVG_PI, r->tessTol); // Calculate divisions per half circle. + NSVGpoint left = {0,0,0,0,0,0,0,0}, right = {0,0,0,0,0,0,0,0}, firstLeft = {0,0,0,0,0,0,0,0}, firstRight = {0,0,0,0,0,0,0,0}; + NSVGpoint* p0, *p1; + int j, s, e; + + // Build stroke edges + if (closed) { + // Looping + p0 = &points[npoints-1]; + p1 = &points[0]; + s = 0; + e = npoints; + } else { + // Add cap + p0 = &points[0]; + p1 = &points[1]; + s = 1; + e = npoints-1; + } + + if (closed) { + nsvg__initClosed(&left, &right, p0, p1, lineWidth); + firstLeft = left; + firstRight = right; + } else { + // Add cap + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + nsvg__normalize(&dx, &dy); + if (lineCap == NSVG_CAP_BUTT) + nsvg__buttCap(r, &left, &right, p0, dx, dy, lineWidth, 0); + else if (lineCap == NSVG_CAP_SQUARE) + nsvg__squareCap(r, &left, &right, p0, dx, dy, lineWidth, 0); + else if (lineCap == NSVG_CAP_ROUND) + nsvg__roundCap(r, &left, &right, p0, dx, dy, lineWidth, ncap, 0); + } + + for (j = s; j < e; ++j) { + if (p1->flags & NSVG_PT_CORNER) { + if (lineJoin == NSVG_JOIN_ROUND) + nsvg__roundJoin(r, &left, &right, p0, p1, lineWidth, ncap); + else if (lineJoin == NSVG_JOIN_BEVEL || (p1->flags & NSVG_PT_BEVEL)) + nsvg__bevelJoin(r, &left, &right, p0, p1, lineWidth); + else + nsvg__miterJoin(r, &left, &right, p0, p1, lineWidth); + } else { + nsvg__straightJoin(r, &left, &right, p1, lineWidth); + } + p0 = p1++; + } + + if (closed) { + // Loop it + nsvg__addEdge(r, firstLeft.x, firstLeft.y, left.x, left.y); + nsvg__addEdge(r, right.x, right.y, firstRight.x, firstRight.y); + } else { + // Add cap + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + nsvg__normalize(&dx, &dy); + if (lineCap == NSVG_CAP_BUTT) + nsvg__buttCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); + else if (lineCap == NSVG_CAP_SQUARE) + nsvg__squareCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); + else if (lineCap == NSVG_CAP_ROUND) + nsvg__roundCap(r, &right, &left, p1, -dx, -dy, lineWidth, ncap, 1); + } +} + +static void nsvg__prepareStroke(NSVGrasterizer* r, float miterLimit, int lineJoin) +{ + int i, j; + NSVGpoint* p0, *p1; + + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + for (i = 0; i < r->npoints; i++) { + // Calculate segment direction and length + p0->dx = p1->x - p0->x; + p0->dy = p1->y - p0->y; + p0->len = nsvg__normalize(&p0->dx, &p0->dy); + // Advance + p0 = p1++; + } + + // calculate joins + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + for (j = 0; j < r->npoints; j++) { + float dlx0, dly0, dlx1, dly1, dmr2, cross; + dlx0 = p0->dy; + dly0 = -p0->dx; + dlx1 = p1->dy; + dly1 = -p1->dx; + // Calculate extrusions + p1->dmx = (dlx0 + dlx1) * 0.5f; + p1->dmy = (dly0 + dly1) * 0.5f; + dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy; + if (dmr2 > 0.000001f) { + float s2 = 1.0f / dmr2; + if (s2 > 600.0f) { + s2 = 600.0f; + } + p1->dmx *= s2; + p1->dmy *= s2; + } + + // Clear flags, but keep the corner. + p1->flags = (p1->flags & NSVG_PT_CORNER) ? NSVG_PT_CORNER : 0; + + // Keep track of left turns. + cross = p1->dx * p0->dy - p0->dx * p1->dy; + if (cross > 0.0f) + p1->flags |= NSVG_PT_LEFT; + + // Check to see if the corner needs to be beveled. + if (p1->flags & NSVG_PT_CORNER) { + if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NSVG_JOIN_BEVEL || lineJoin == NSVG_JOIN_ROUND) { + p1->flags |= NSVG_PT_BEVEL; + } + } + + p0 = p1++; + } +} + +static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale) +{ + int i, j, closed; + NSVGpath* path; + NSVGpoint* p0, *p1; + float miterLimit = shape->miterLimit; + int lineJoin = shape->strokeLineJoin; + int lineCap = shape->strokeLineCap; + float lineWidth = shape->strokeWidth * scale; + + for (path = shape->paths; path != NULL; path = path->next) { + // Flatten path + r->npoints = 0; + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER); + for (i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, NSVG_PT_CORNER); + } + if (r->npoints < 2) + continue; + + closed = path->closed; + + // If the first and last points are the same, remove the last, mark as closed path. + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + if (nsvg__ptEquals(p0->x,p0->y, p1->x,p1->y, r->distTol)) { + r->npoints--; + p0 = &r->points[r->npoints-1]; + closed = 1; + } + + if (shape->strokeDashCount > 0) { + int idash = 0, dashState = 1; + float totalDist = 0, dashLen, allDashLen, dashOffset; + NSVGpoint cur; + + if (closed) + nsvg__appendPathPoint(r, r->points[0]); + + // Duplicate points -> points2. + nsvg__duplicatePoints(r); + + r->npoints = 0; + cur = r->points2[0]; + nsvg__appendPathPoint(r, cur); + + // Figure out dash offset. + allDashLen = 0; + for (j = 0; j < shape->strokeDashCount; j++) + allDashLen += shape->strokeDashArray[j]; + if (shape->strokeDashCount & 1) + allDashLen *= 2.0f; + // Find location inside pattern + dashOffset = fmodf(shape->strokeDashOffset, allDashLen); + if (dashOffset < 0.0f) + dashOffset += allDashLen; + + while (dashOffset > shape->strokeDashArray[idash]) { + dashOffset -= shape->strokeDashArray[idash]; + idash = (idash + 1) % shape->strokeDashCount; + } + dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale; + + for (j = 1; j < r->npoints2; ) { + float dx = r->points2[j].x - cur.x; + float dy = r->points2[j].y - cur.y; + float dist = sqrtf(dx*dx + dy*dy); + + if ((totalDist + dist) > dashLen) { + // Calculate intermediate point + float d = (dashLen - totalDist) / dist; + float x = cur.x + dx * d; + float y = cur.y + dy * d; + nsvg__addPathPoint(r, x, y, NSVG_PT_CORNER); + + // Stroke + if (r->npoints > 1 && dashState) { + nsvg__prepareStroke(r, miterLimit, lineJoin); + nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); + } + // Advance dash pattern + dashState = !dashState; + idash = (idash+1) % shape->strokeDashCount; + dashLen = shape->strokeDashArray[idash] * scale; + // Restart + cur.x = x; + cur.y = y; + cur.flags = NSVG_PT_CORNER; + totalDist = 0.0f; + r->npoints = 0; + nsvg__appendPathPoint(r, cur); + } else { + totalDist += dist; + cur = r->points2[j]; + nsvg__appendPathPoint(r, cur); + j++; + } + } + // Stroke any leftover path + if (r->npoints > 1 && dashState) + nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); + } else { + nsvg__prepareStroke(r, miterLimit, lineJoin); + nsvg__expandStroke(r, r->points, r->npoints, closed, lineJoin, lineCap, lineWidth); + } + } +} + +static int nsvg__cmpEdge(const void *p, const void *q) +{ + const NSVGedge* a = (const NSVGedge*)p; + const NSVGedge* b = (const NSVGedge*)q; + + if (a->y0 < b->y0) return -1; + if (a->y0 > b->y0) return 1; + return 0; +} + + +static NSVGactiveEdge* nsvg__addActive(NSVGrasterizer* r, NSVGedge* e, float startPoint) +{ + NSVGactiveEdge* z; + + if (r->freelist != NULL) { + // Restore from freelist. + z = r->freelist; + r->freelist = z->next; + } else { + // Alloc new edge. + z = (NSVGactiveEdge*)nsvg__alloc(r, sizeof(NSVGactiveEdge)); + if (z == NULL) return NULL; + } + + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); +// STBTT_assert(e->y0 <= start_point); + // round dx down to avoid going too far + if (dxdy < 0) + z->dx = (int)(-nsvg__roundf(NSVG__FIX * -dxdy)); + else + z->dx = (int)nsvg__roundf(NSVG__FIX * dxdy); + z->x = (int)nsvg__roundf(NSVG__FIX * (e->x0 + dxdy * (startPoint - e->y0))); +// z->x -= off_x * FIX; + z->ey = e->y1; + z->next = 0; + z->dir = e->dir; + + return z; +} + +static void nsvg__freeActive(NSVGrasterizer* r, NSVGactiveEdge* z) +{ + z->next = r->freelist; + r->freelist = z; +} + +static void nsvg__fillScanline(unsigned char* scanline, int len, int x0, int x1, int maxWeight, int* xmin, int* xmax) +{ + int i = x0 >> NSVG__FIXSHIFT; + int j = x1 >> NSVG__FIXSHIFT; + if (i < *xmin) *xmin = i; + if (j > *xmax) *xmax = j; + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = (unsigned char)(scanline[i] + ((x1 - x0) * maxWeight >> NSVG__FIXSHIFT)); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = (unsigned char)(scanline[i] + (((NSVG__FIX - (x0 & NSVG__FIXMASK)) * maxWeight) >> NSVG__FIXSHIFT)); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = (unsigned char)(scanline[j] + (((x1 & NSVG__FIXMASK) * maxWeight) >> NSVG__FIXSHIFT)); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = (unsigned char)(scanline[i] + maxWeight); + } + } +} + +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void nsvg__fillActiveEdges(unsigned char* scanline, int len, NSVGactiveEdge* e, int maxWeight, int* xmin, int* xmax, char fillRule) +{ + // non-zero winding fill + int x0 = 0, w = 0; + + if (fillRule == NSVG_FILLRULE_NONZERO) { + // Non-zero + while (e != NULL) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->dir; + } else { + int x1 = e->x; w += e->dir; + // if we went to zero, we need to draw + if (w == 0) + nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); + } + e = e->next; + } + } else if (fillRule == NSVG_FILLRULE_EVENODD) { + // Even-odd + while (e != NULL) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w = 1; + } else { + int x1 = e->x; w = 0; + nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); + } + e = e->next; + } + } +} + +static float nsvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a > mx ? mx : a); } + +static unsigned int nsvg__RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return ((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16) | ((unsigned int)a << 24); +} + +static unsigned int nsvg__lerpRGBA(unsigned int c0, unsigned int c1, float u) +{ + int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); + int r = (((c0) & 0xff)*(256-iu) + (((c1) & 0xff)*iu)) >> 8; + int g = (((c0>>8) & 0xff)*(256-iu) + (((c1>>8) & 0xff)*iu)) >> 8; + int b = (((c0>>16) & 0xff)*(256-iu) + (((c1>>16) & 0xff)*iu)) >> 8; + int a = (((c0>>24) & 0xff)*(256-iu) + (((c1>>24) & 0xff)*iu)) >> 8; + return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); +} + +static unsigned int nsvg__applyOpacity(unsigned int c, float u) +{ + int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); + int r = (c) & 0xff; + int g = (c>>8) & 0xff; + int b = (c>>16) & 0xff; + int a = (((c>>24) & 0xff)*iu) >> 8; + return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); +} + +static inline int nsvg__div255(int x) +{ + return ((x+1) * 257) >> 16; +} + +static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* cover, int x, int y, + float tx, float ty, float scale, NSVGcachedPaint* cache) +{ + + if (cache->type == NSVG_PAINT_COLOR) { + int i, cr, cg, cb, ca; + cr = cache->colors[0] & 0xff; + cg = (cache->colors[0] >> 8) & 0xff; + cb = (cache->colors[0] >> 16) & 0xff; + ca = (cache->colors[0] >> 24) & 0xff; + + for (i = 0; i < count; i++) { + int r,g,b; + int a = nsvg__div255((int)cover[0] * ca); + int ia = 255 - a; + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + } + } else if (cache->type == NSVG_PAINT_LINEAR_GRADIENT) { + // TODO: spread modes. + // TODO: plenty of opportunities to optimize. + float fx, fy, dx, gy; + float* t = cache->xform; + int i, cr, cg, cb, ca; + unsigned int c; + + fx = ((float)x - tx) / scale; + fy = ((float)y - ty) / scale; + dx = 1.0f / scale; + + for (i = 0; i < count; i++) { + int r,g,b,a,ia; + gy = fx*t[1] + fy*t[3] + t[5]; + c = cache->colors[(int)nsvg__clampf(gy*255.0f, 0, 255.0f)]; + cr = (c) & 0xff; + cg = (c >> 8) & 0xff; + cb = (c >> 16) & 0xff; + ca = (c >> 24) & 0xff; + + a = nsvg__div255((int)cover[0] * ca); + ia = 255 - a; + + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + fx += dx; + } + } else if (cache->type == NSVG_PAINT_RADIAL_GRADIENT) { + // TODO: spread modes. + // TODO: plenty of opportunities to optimize. + // TODO: focus (fx,fy) + float fx, fy, dx, gx, gy, gd; + float* t = cache->xform; + int i, cr, cg, cb, ca; + unsigned int c; + + fx = ((float)x - tx) / scale; + fy = ((float)y - ty) / scale; + dx = 1.0f / scale; + + for (i = 0; i < count; i++) { + int r,g,b,a,ia; + gx = fx*t[0] + fy*t[2] + t[4]; + gy = fx*t[1] + fy*t[3] + t[5]; + gd = sqrtf(gx*gx + gy*gy); + c = cache->colors[(int)nsvg__clampf(gd*255.0f, 0, 255.0f)]; + cr = (c) & 0xff; + cg = (c >> 8) & 0xff; + cb = (c >> 16) & 0xff; + ca = (c >> 24) & 0xff; + + a = nsvg__div255((int)cover[0] * ca); + ia = 255 - a; + + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + fx += dx; + } + } +} + +static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float scale, NSVGcachedPaint* cache, char fillRule) +{ + NSVGactiveEdge *active = NULL; + int y, s; + int e = 0; + int maxWeight = (255 / NSVG__SUBSAMPLES); // weight per vertical scanline + int xmin, xmax; + + for (y = 0; y < r->height; y++) { + memset(r->scanline, 0, r->width); + xmin = r->width; + xmax = 0; + for (s = 0; s < NSVG__SUBSAMPLES; ++s) { + // find center of pixel for this scanline + float scany = (float)(y*NSVG__SUBSAMPLES + s) + 0.5f; + NSVGactiveEdge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + NSVGactiveEdge *z = *step; + if (z->ey <= scany) { + *step = z->next; // delete from list +// NSVG__assert(z->valid); + nsvg__freeActive(r, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for (;;) { + int changed = 0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + NSVGactiveEdge* t = *step; + NSVGactiveEdge* q = t->next; + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e < r->nedges && r->edges[e].y0 <= scany) { + if (r->edges[e].y1 > scany) { + NSVGactiveEdge* z = nsvg__addActive(r, &r->edges[e], scany); + if (z == NULL) break; + // find insertion point + if (active == NULL) { + active = z; + } else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + NSVGactiveEdge* p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + e++; + } + + // now process all active edges in non-zero fashion + if (active != NULL) + nsvg__fillActiveEdges(r->scanline, r->width, active, maxWeight, &xmin, &xmax, fillRule); + } + // Blit + if (xmin < 0) xmin = 0; + if (xmax > r->width-1) xmax = r->width-1; + if (xmin <= xmax) { + nsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, scale, cache); + } + } + +} + +static void nsvg__unpremultiplyAlpha(unsigned char* image, int w, int h, int stride) +{ + int x,y; + + // Unpremultiply + for (y = 0; y < h; y++) { + unsigned char *row = &image[y*stride]; + for (x = 0; x < w; x++) { + int r = row[0], g = row[1], b = row[2], a = row[3]; + if (a != 0) { + row[0] = (unsigned char)(r*255/a); + row[1] = (unsigned char)(g*255/a); + row[2] = (unsigned char)(b*255/a); + } + row += 4; + } + } + + // Defringe + for (y = 0; y < h; y++) { + unsigned char *row = &image[y*stride]; + for (x = 0; x < w; x++) { + int r = 0, g = 0, b = 0, a = row[3], n = 0; + if (a == 0) { + if (x-1 > 0 && row[-1] != 0) { + r += row[-4]; + g += row[-3]; + b += row[-2]; + n++; + } + if (x+1 < w && row[7] != 0) { + r += row[4]; + g += row[5]; + b += row[6]; + n++; + } + if (y-1 > 0 && row[-stride+3] != 0) { + r += row[-stride]; + g += row[-stride+1]; + b += row[-stride+2]; + n++; + } + if (y+1 < h && row[stride+3] != 0) { + r += row[stride]; + g += row[stride+1]; + b += row[stride+2]; + n++; + } + if (n > 0) { + row[0] = (unsigned char)(r/n); + row[1] = (unsigned char)(g/n); + row[2] = (unsigned char)(b/n); + } + } + row += 4; + } + } +} + + +static void nsvg__initPaint(NSVGcachedPaint* cache, NSVGpaint* paint, float opacity) +{ + int i, j; + NSVGgradient* grad; + + cache->type = paint->type; + + if (paint->type == NSVG_PAINT_COLOR) { + cache->colors[0] = nsvg__applyOpacity(paint->color, opacity); + return; + } + + grad = paint->gradient; + + cache->spread = grad->spread; + memcpy(cache->xform, grad->xform, sizeof(float)*6); + + if (grad->nstops == 0) { + for (i = 0; i < 256; i++) + cache->colors[i] = 0; + } else if (grad->nstops == 1) { + for (i = 0; i < 256; i++) + cache->colors[i] = nsvg__applyOpacity(grad->stops[i].color, opacity); + } else { + unsigned int ca, cb = 0; + float ua, ub, du, u; + int ia, ib, count; + + ca = nsvg__applyOpacity(grad->stops[0].color, opacity); + ua = nsvg__clampf(grad->stops[0].offset, 0, 1); + ub = nsvg__clampf(grad->stops[grad->nstops-1].offset, ua, 1); + ia = (int)(ua * 255.0f); + ib = (int)(ub * 255.0f); + for (i = 0; i < ia; i++) { + cache->colors[i] = ca; + } + + for (i = 0; i < grad->nstops-1; i++) { + ca = nsvg__applyOpacity(grad->stops[i].color, opacity); + cb = nsvg__applyOpacity(grad->stops[i+1].color, opacity); + ua = nsvg__clampf(grad->stops[i].offset, 0, 1); + ub = nsvg__clampf(grad->stops[i+1].offset, 0, 1); + ia = (int)(ua * 255.0f); + ib = (int)(ub * 255.0f); + count = ib - ia; + if (count <= 0) continue; + u = 0; + du = 1.0f / (float)count; + for (j = 0; j < count; j++) { + cache->colors[ia+j] = nsvg__lerpRGBA(ca,cb,u); + u += du; + } + } + + for (i = ib; i < 256; i++) + cache->colors[i] = cb; + } + +} + +/* +static void dumpEdges(NSVGrasterizer* r, const char* name) +{ + float xmin = 0, xmax = 0, ymin = 0, ymax = 0; + NSVGedge *e = NULL; + int i; + if (r->nedges == 0) return; + FILE* fp = fopen(name, "w"); + if (fp == NULL) return; + + xmin = xmax = r->edges[0].x0; + ymin = ymax = r->edges[0].y0; + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + xmin = nsvg__minf(xmin, e->x0); + xmin = nsvg__minf(xmin, e->x1); + xmax = nsvg__maxf(xmax, e->x0); + xmax = nsvg__maxf(xmax, e->x1); + ymin = nsvg__minf(ymin, e->y0); + ymin = nsvg__minf(ymin, e->y1); + ymax = nsvg__maxf(ymax, e->y0); + ymax = nsvg__maxf(ymax, e->y1); + } + + fprintf(fp, "", xmin, ymin, (xmax - xmin), (ymax - ymin)); + + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + fprintf(fp ,"", e->x0,e->y0, e->x1,e->y1); + } + + for (i = 0; i < r->npoints; i++) { + if (i+1 < r->npoints) + fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i+1].x, r->points[i+1].y); + fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i].flags == 0 ? "#f00" : "#0f0"); + } + + fprintf(fp, ""); + fclose(fp); +} +*/ + +void nsvgRasterize(NSVGrasterizer* r, + NSVGimage* image, float tx, float ty, float scale, + unsigned char* dst, int w, int h, int stride) +{ + NSVGshape *shape = NULL; + NSVGedge *e = NULL; + NSVGcachedPaint cache; + int i; + + r->bitmap = dst; + r->width = w; + r->height = h; + r->stride = stride; + + if (w > r->cscanline) { + r->cscanline = w; + r->scanline = (unsigned char*)realloc(r->scanline, w); + if (r->scanline == NULL) return; + } + + for (i = 0; i < h; i++) + memset(&dst[i*stride], 0, w*4); + + for (shape = image->shapes; shape != NULL; shape = shape->next) { + if (!(shape->flags & NSVG_FLAGS_VISIBLE)) + continue; + + if (shape->fill.type != NSVG_PAINT_NONE) { + nsvg__resetPool(r); + r->freelist = NULL; + r->nedges = 0; + + nsvg__flattenShape(r, shape, scale); + + // Scale and translate edges + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + e->x0 = tx + e->x0; + e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; + e->x1 = tx + e->x1; + e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; + } + + // Rasterize edges + if (r->nedges != 0) + qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); + + // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule + nsvg__initPaint(&cache, &shape->fill, shape->opacity); + + nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, shape->fillRule); + } + if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * scale) > 0.01f) { + nsvg__resetPool(r); + r->freelist = NULL; + r->nedges = 0; + + nsvg__flattenShapeStroke(r, shape, scale); + +// dumpEdges(r, "edge.svg"); + + // Scale and translate edges + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + e->x0 = tx + e->x0; + e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; + e->x1 = tx + e->x1; + e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; + } + + // Rasterize edges + if (r->nedges != 0) + qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); + + // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule + nsvg__initPaint(&cache, &shape->stroke, shape->opacity); + + nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, NSVG_FILLRULE_NONZERO); + } + } + + nsvg__unpremultiplyAlpha(dst, w, h, stride); + + r->bitmap = NULL; + r->width = 0; + r->height = 0; + r->stride = 0; +} + +#endif // NANOSVGRAST_IMPLEMENTATION + +#endif // NANOSVGRAST_H diff --git a/src/core/pathfill.cpp b/src/core/pathfill.cpp new file mode 100644 index 000000000..cb604ef3f --- /dev/null +++ b/src/core/pathfill.cpp @@ -0,0 +1,613 @@ + +#include "core/pathfill.h" +#include +#include + +static const int AntialiasLevel = 8; +static const int MaskBlockSize = 16; +static const int MaskBufferSize = MaskBlockSize * MaskBlockSize; +static const int ScanlineBlockSize = MaskBlockSize * AntialiasLevel; + +class PathScanlineEdge +{ +public: + PathScanlineEdge() = default; + PathScanlineEdge(double x, bool up_direction) : x(x), up_direction(up_direction) { } + + double x = 0.0; + bool up_direction = false; +}; + +class PathScanline +{ +public: + std::vector edges; + std::vector pixels; + + void insert_sorted(PathScanlineEdge edge) + { + edges.push_back(edge); + + for (size_t pos = edges.size() - 1; pos > 0 && edges[pos - 1].x >= edge.x; pos--) + { + PathScanlineEdge temp = edges[pos - 1]; + edges[pos - 1] = edges[pos]; + edges[pos] = temp; + } + } +}; + +class PathRasterRange +{ +public: + void Begin(const PathScanline* scanline, PathFillMode mode); + void Next(); + + bool found = false; + int x0; + int x1; + +private: + const PathScanline* scanline = nullptr; + PathFillMode mode; + size_t i = 0; + int nonzero_rule = 0; +}; + +enum class PathFillBlockResult +{ + Empty, + Partial, + Full +}; + +class PathMaskBuffer +{ +public: + void BeginRow(PathScanline* scanlines, PathFillMode mode); + PathFillBlockResult FillBlock(int xpos); + + unsigned char MaskBufferData[MaskBufferSize]; + +private: + bool IsFullBlock(int xpos) const; + + PathRasterRange Range[ScanlineBlockSize]; +}; + +class PathFillRasterizer +{ +public: + void Rasterize(const PathFillDesc& path, uint8_t* dest, int width, int height); + +private: + void Clear(); + + void Begin(double x, double y); + void QuadraticBezier(double cp1_x, double cp1_y, double cp2_x, double cp2_y); + void CubicBezier(double cp1_x, double cp1_y, double cp2_x, double cp2_y, double cp3_x, double cp3_y); + void Line(double x, double y); + void End(bool close); + + void SubdivideBezier(int level, double cp0_x, double cp0_y, double cp1_x, double cp1_y, double cp2_x, double cp2_y, double cp3_x, double cp3_y, double t0, double t1); + static Point PointOnBezier(double cp0_x, double cp0_y, double cp1_x, double cp1_y, double cp2_x, double cp2_y, double cp3_x, double cp3_y, double t); + + void Fill(PathFillMode mode, uint8_t* dest, int dest_width, int dest_height); + + struct Extent + { + Extent() : left(INT_MAX), right(0) {} + int left; + int right; + }; + + Extent FindExtent(const PathScanline* scanline, int max_width); + + double start_x = 0.0; + double start_y = 0.0; + double last_x = 0.0; + double last_y = 0.0; + + int first_scanline = 0; + int last_scanline = 0; + + int width = 0; + int height = 0; + std::vector scanlines; + + PathMaskBuffer mask_blocks; +}; + +///////////////////////////////////////////////////////////////////////////// + +void PathFillDesc::Rasterize(uint8_t* dest, int width, int height, bool blend) +{ + if (!blend) + { + memset(dest, 0, width * height); + } + PathFillRasterizer rasterizer; + rasterizer.Rasterize(*this, dest, width, height); +} + +///////////////////////////////////////////////////////////////////////////// + +void PathFillRasterizer::Rasterize(const PathFillDesc& path, uint8_t* dest, int dest_width, int dest_height) +{ + Clear(); + + // For simplicity of the code, ensure the mask is always a multiple of MaskBlockSize + int block_width = ScanlineBlockSize * ((dest_width + MaskBlockSize - 1) / MaskBlockSize); + int block_height = ScanlineBlockSize * ((dest_height + MaskBlockSize - 1) / MaskBlockSize); + + if (width != block_width || height != block_height) + { + width = block_width; + height = block_height; + + scanlines.resize(block_height); + first_scanline = (int)scanlines.size(); + last_scanline = 0; + } + + for (const auto& subpath : path.subpaths) + { + Point start_point = subpath.points[0]; + Begin(start_point.x, start_point.y); + + size_t i = 1; + for (PathFillCommand command : subpath.commands) + { + if (command == PathFillCommand::line) + { + const Point& next_point = subpath.points[i]; + i++; + + Line(next_point.x, next_point.y); + } + else if (command == PathFillCommand::quadradic) + { + const Point& control = subpath.points[i]; + const Point& next_point = subpath.points[i + 1]; + i += 2; + + QuadraticBezier(control.x, control.y, next_point.x, next_point.y); + } + else if (command == PathFillCommand::cubic) + { + const Point& control1 = subpath.points[i]; + const Point& control2 = subpath.points[i + 1]; + const Point& next_point = subpath.points[i + 2]; + i += 3; + + CubicBezier(control1.x, control1.y, control2.x, control2.y, next_point.x, next_point.y); + } + } + + End(subpath.closed); + } + + Fill(path.fill_mode, dest, dest_width, dest_height); +} + +void PathFillRasterizer::Fill(PathFillMode mode, uint8_t* dest, int dest_width, int dest_height) +{ + if (scanlines.empty()) return; + + int start_y = first_scanline / ScanlineBlockSize * ScanlineBlockSize; + int end_y = (last_scanline + ScanlineBlockSize - 1) / ScanlineBlockSize * ScanlineBlockSize; + + for (int ypos = start_y; ypos < end_y; ypos += ScanlineBlockSize) + { + mask_blocks.BeginRow(&scanlines[ypos], mode); + + Extent extent = FindExtent(&scanlines[ypos], width); + for (int xpos = extent.left; xpos < extent.right; xpos += ScanlineBlockSize) + { + PathFillBlockResult result = mask_blocks.FillBlock(xpos); + + int dest_x = xpos / AntialiasLevel; + int dest_y = ypos / AntialiasLevel; + int count_x = std::min(dest_x + MaskBlockSize, dest_width) - dest_x; + int count_y = std::min(dest_y + MaskBlockSize, dest_height) - dest_y; + + if (result == PathFillBlockResult::Full) + { + for (int i = 0; i < count_y; i++) + { + uint8_t* dline = dest + dest_x + (dest_y + i) * dest_width; + memset(dline, 255, count_x); + } + } + else if (result == PathFillBlockResult::Partial) + { + for (int i = 0; i < count_y; i++) + { + const uint8_t* sline = mask_blocks.MaskBufferData + i * MaskBlockSize; + uint8_t* dline = dest + dest_x + (dest_y + i) * dest_width; + for (int j = 0; j < count_x; j++) + { + dline[j] = std::min((int)dline[j] + (int)sline[j], 255); + } + } + } + } + } +} + +PathFillRasterizer::Extent PathFillRasterizer::FindExtent(const PathScanline* scanline, int max_width) +{ + // Find scanline extents + Extent extent; + for (unsigned int cnt = 0; cnt < ScanlineBlockSize; cnt++, scanline++) + { + if (scanline->edges.empty()) + continue; + + extent.left = std::min(extent.left, (int)scanline->edges.front().x); + extent.right = std::max(extent.right, (int)scanline->edges.back().x); + } + extent.left = std::max(extent.left, 0); + extent.right = std::min(extent.right, max_width); + + return extent; +} + +void PathFillRasterizer::Clear() +{ + for (size_t y = first_scanline; y < last_scanline; y++) + { + auto& scanline = scanlines[y]; + if (!scanline.edges.empty()) + { + scanline.edges.clear(); + } + } + + first_scanline = (int)scanlines.size(); + last_scanline = 0; +} + +void PathFillRasterizer::Begin(double x, double y) +{ + start_x = last_x = x; + start_y = last_y = y; +} + +void PathFillRasterizer::End(bool close) +{ + if (close) + { + Line(start_x, start_y); + } +} + +void PathFillRasterizer::QuadraticBezier(double qcp1_x, double qcp1_y, double qcp2_x, double qcp2_y) +{ + double qcp0_x = last_x; + double qcp0_y = last_y; + + // Convert to cubic: + double cp1_x = qcp0_x + 2.0 * (qcp1_x - qcp0_x) / 3.0; + double cp1_y = qcp0_y + 2.0 * (qcp1_y - qcp0_y) / 3.0; + double cp2_x = qcp1_x + (qcp2_x - qcp1_x) / 3.0; + double cp2_y = qcp1_y + (qcp2_y - qcp1_y) / 3.0; + double cp3_x = qcp2_x; + double cp3_y = qcp2_y; + CubicBezier(cp1_x, cp1_y, cp2_x, cp2_y, cp3_x, cp3_y); +} + +void PathFillRasterizer::CubicBezier(double cp1_x, double cp1_y, double cp2_x, double cp2_y, double cp3_x, double cp3_y) +{ + double cp0_x = last_x; + double cp0_y = last_y; + + double estimated_length = + std::sqrt((cp1_x - cp0_x) * (cp1_x - cp0_x) + (cp1_y - cp0_y) * (cp1_y - cp0_y)) + + std::sqrt((cp1_x - cp0_x) * (cp1_x - cp0_x) + (cp1_y - cp0_y) * (cp1_y - cp0_y)) + + std::sqrt((cp1_x - cp0_x) * (cp1_x - cp0_x) + (cp1_y - cp0_y) * (cp1_y - cp0_y)); + + double min_segs = 10.0; + double segs = estimated_length / 5.0; + int steps = (int)std::ceil(std::sqrt(segs * segs * 0.3f + min_segs)); + for (int i = 0; i < steps; i++) + { + //Point sp = PointOnBezier(cp0_x, cp0_y, cp1_x, cp1_y, cp2_x, cp2_y, cp3_x, cp3_y, i / (double)steps); + Point ep = PointOnBezier(cp0_x, cp0_y, cp1_x, cp1_y, cp2_x, cp2_y, cp3_x, cp3_y, (i + 1) / (double)steps); + Line(ep.x, ep.y); + } + + // http://ciechanowski.me/blog/2014/02/18/drawing-bezier-curves/ + // http://antigrain.com/research/adaptive_bezier/ (best method, unfortunately GPL example code) +} + +void PathFillRasterizer::SubdivideBezier(int level, double cp0_x, double cp0_y, double cp1_x, double cp1_y, double cp2_x, double cp2_y, double cp3_x, double cp3_y, double t0, double t1) +{ + const double split_angle_cos = 0.99f; + + double tc = (t0 + t1) * 0.5f; + + Point sp = PointOnBezier(cp0_x, cp0_y, cp1_x, cp1_y, cp2_x, cp2_y, cp3_x, cp3_y, t0); + Point cp = PointOnBezier(cp0_x, cp0_y, cp1_x, cp1_y, cp2_x, cp2_y, cp3_x, cp3_y, tc); + Point ep = PointOnBezier(cp0_x, cp0_y, cp1_x, cp1_y, cp2_x, cp2_y, cp3_x, cp3_y, t1); + + Point sp2cp(cp.x - sp.x, cp.y - sp.y); + Point cp2ep(ep.x - cp.x, ep.y - cp.y); + + // Normalize + double len_sp2cp = std::sqrt(sp2cp.x * sp2cp.x + sp2cp.y * sp2cp.y); + double len_cp2ep = std::sqrt(cp2ep.x * cp2ep.x + cp2ep.y * cp2ep.y); + if (len_sp2cp > 0.0) { sp2cp.x /= len_sp2cp; sp2cp.y /= len_sp2cp; } + if (len_cp2ep > 0.0) { cp2ep.x /= len_cp2ep; cp2ep.y /= len_cp2ep; } + + double dot = sp2cp.x * cp2ep.x + sp2cp.y * cp2ep.y; + if (dot < split_angle_cos && level < 15) + { + SubdivideBezier(level + 1, cp0_x, cp0_y, cp1_x, cp1_y, cp2_x, cp2_y, cp3_x, cp3_y, t0, tc); + SubdivideBezier(level + 1, cp0_x, cp0_y, cp1_x, cp1_y, cp2_x, cp2_y, cp3_x, cp3_y, tc, t1); + } + else + { + Line(ep.x, ep.y); + } +} + +Point PathFillRasterizer::PointOnBezier(double cp0_x, double cp0_y, double cp1_x, double cp1_y, double cp2_x, double cp2_y, double cp3_x, double cp3_y, double t) +{ + const int num_cp = 4; + + double cp_x[4] = { cp0_x, cp1_x, cp2_x, cp3_x }; + double cp_y[4] = { cp0_y, cp1_y, cp2_y, cp3_y }; + + // Perform deCasteljau iterations: + // (linear interpolate between the control points) + double a = 1.0 - t; + double b = t; + for (int j = num_cp - 1; j > 0; j--) + { + for (int i = 0; i < j; i++) + { + cp_x[i] = a * cp_x[i] + b * cp_x[i + 1]; + cp_y[i] = a * cp_y[i] + b * cp_y[i + 1]; + } + } + + return Point(cp_x[0], cp_y[0]); +} + +void PathFillRasterizer::Line(double x1, double y1) +{ + double x0 = last_x; + double y0 = last_y; + + last_x = x1; + last_y = y1; + + x0 *= static_cast(AntialiasLevel); + x1 *= static_cast(AntialiasLevel); + y0 *= static_cast(AntialiasLevel); + y1 *= static_cast(AntialiasLevel); + + bool up_direction = y1 < y0; + double dy = y1 - y0; + + constexpr const double epsilon = std::numeric_limits::epsilon(); + if (dy < -epsilon || dy > epsilon) + { + int start_y = static_cast(std::floor(std::min(y0, y1) + 0.5f)); + int end_y = static_cast(std::floor(std::max(y0, y1) - 0.5f)) + 1; + + start_y = std::max(start_y, 0); + end_y = std::min(end_y, height); + + double rcp_dy = 1.0 / dy; + + first_scanline = std::min(first_scanline, start_y); + last_scanline = std::max(last_scanline, end_y); + + for (int y = start_y; y < end_y; y++) + { + double ypos = y + 0.5f; + double x = x0 + (x1 - x0) * (ypos - y0) * rcp_dy; + scanlines[y].insert_sorted(PathScanlineEdge(x, up_direction)); + } + } +} + +///////////////////////////////////////////////////////////////////////////// + +void PathRasterRange::Begin(const PathScanline* new_scanline, PathFillMode new_mode) +{ + scanline = new_scanline; + mode = new_mode; + found = false; + i = 0; + nonzero_rule = 0; + Next(); +} + +void PathRasterRange::Next() +{ + if (i + 1 >= scanline->edges.size()) + { + found = false; + return; + } + + if (mode == PathFillMode::alternate) + { + x0 = static_cast(scanline->edges[i].x + 0.5f); + x1 = static_cast(scanline->edges[i + 1].x - 0.5f) + 1; + i += 2; + found = true; + } + else + { + x0 = static_cast(scanline->edges[i].x + 0.5f); + nonzero_rule += scanline->edges[i].up_direction ? 1 : -1; + i++; + + while (i < scanline->edges.size()) + { + nonzero_rule += scanline->edges[i].up_direction ? 1 : -1; + x1 = static_cast(scanline->edges[i].x - 0.5f) + 1; + i++; + + if (nonzero_rule == 0) + { + found = true; + return; + } + } + found = false; + } +} + +///////////////////////////////////////////////////////////////////////// + +void PathMaskBuffer::BeginRow(PathScanline* scanlines, PathFillMode mode) +{ + for (unsigned int cnt = 0; cnt < ScanlineBlockSize; cnt++) + { + Range[cnt].Begin(&scanlines[cnt], mode); + } +} + +#if 0 +PathFillBlockResult PathMaskBuffer::FillBlock(int xpos) +{ + if (IsFullBlock(xpos)) + { + return PathFillBlockResult::Full; + } + + const int block_size = MaskBlockSize / 16 * MaskBlockSize; + __m128i block[block_size]; + + for (auto& elem : block) + elem = _mm_setzero_si128(); + + for (unsigned int cnt = 0; cnt < ScanlineBlockSize; cnt++) + { + __m128i* line = &block[MaskBlockSize / 16 * (cnt / AntialiasLevel)]; + + while (range[cnt].found) + { + int x0 = range[cnt].x0; + if (x0 >= xpos + ScanlineBlockSize) + break; + int x1 = range[cnt].x1; + + x0 = max(x0, xpos); + x1 = min(x1, xpos + ScanlineBlockSize); + + if (x0 >= x1) // Done segment + { + range[cnt].next(); + } + else + { + for (int sse_block = 0; sse_block < MaskBlockSize / 16; sse_block++) + { + for (int alias_cnt = 0; alias_cnt < (AntialiasLevel); alias_cnt++) + { + __m128i start = _mm_set1_epi8((x0 + alias_cnt - xpos) / AntialiasLevel - 16 * sse_block); + __m128i end = _mm_set1_epi8((x1 + alias_cnt - xpos) / AntialiasLevel - 16 * sse_block); + __m128i x = _mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + + __m128i left = _mm_cmplt_epi8(x, start); + __m128i right = _mm_cmplt_epi8(x, end); + __m128i mask = _mm_andnot_si128(left, right); + __m128i add_value = _mm_and_si128(mask, _mm_set1_epi8(256 / (AntialiasLevel * AntialiasLevel))); + + line[sse_block] = _mm_adds_epu8(line[sse_block], add_value); + } + } + + range[cnt].x0 = x1; // For next time + } + } + } + + __m128i empty_status = _mm_setzero_si128(); + for (auto& elem : block) + empty_status = _mm_or_si128(empty_status, elem); + + bool empty_block = _mm_movemask_epi8(_mm_cmpeq_epi32(empty_status, _mm_setzero_si128())) == 0xffff; + if (empty_block) return PathFillBlockResult::Empty; + + for (unsigned int cnt = 0; cnt < MaskBlockSize; cnt++) + { + __m128i* input = &block[MaskBlockSize / 16 * cnt]; + __m128i* output = (__m128i*)(MaskBufferData + cnt * mask_texture_size); + + for (int sse_block = 0; sse_block < MaskBlockSize / 16; sse_block++) + _mm_storeu_si128(&output[sse_block], input[sse_block]); + } + + return PathFillBlockResult::Partial; +} + +#else + +PathFillBlockResult PathMaskBuffer::FillBlock(int xpos) +{ + if (IsFullBlock(xpos)) + { + return PathFillBlockResult::Full; + } + + memset(MaskBufferData, 0, MaskBufferSize); + + bool empty_block = true; + for (unsigned int cnt = 0; cnt < ScanlineBlockSize; cnt++) + { + unsigned char* line = MaskBufferData + MaskBlockSize * (cnt / AntialiasLevel); + while (Range[cnt].found) + { + int x0 = Range[cnt].x0; + if (x0 >= xpos + ScanlineBlockSize) + break; + int x1 = Range[cnt].x1; + + x0 = std::max(x0, xpos); + x1 = std::min(x1, xpos + ScanlineBlockSize); + + if (x0 >= x1) // Done segment + { + Range[cnt].Next(); + } + else + { + empty_block = false; + for (int x = x0 - xpos; x < x1 - xpos; x++) + { + int pixel = line[x / AntialiasLevel]; + pixel = std::min(pixel + (256 / (AntialiasLevel * AntialiasLevel)), 255); + line[x / AntialiasLevel] = pixel; + } + Range[cnt].x0 = x1; // For next time + } + } + } + + return empty_block ? PathFillBlockResult::Empty : PathFillBlockResult::Partial; +} + +#endif + +bool PathMaskBuffer::IsFullBlock(int xpos) const +{ + for (auto& elem : Range) + { + if (!elem.found) + { + return false; + } + if ((elem.x0 > xpos) || (elem.x1 < (xpos + ScanlineBlockSize - 1))) + { + return false; + } + } + return true; +} diff --git a/src/core/picopng/LICENSE.txt b/src/core/picopng/LICENSE.txt new file mode 100644 index 000000000..a1f522f51 --- /dev/null +++ b/src/core/picopng/LICENSE.txt @@ -0,0 +1,18 @@ + // picoPNG version 20101224 + // Copyright (c) 2005-2010 Lode Vandevenne + // + // This software is provided 'as-is', without any express or implied + // warranty. In no event will the authors be held liable for any damages + // arising from the use of this software. + // + // Permission is granted to anyone to use this software for any purpose, + // including commercial applications, and to alter it and redistribute it + // freely, subject to the following restrictions: + // + // 1. The origin of this software must not be misrepresented; you must not + // claim that you wrote the original software. If you use this software + // in a product, an acknowledgment in the product documentation would be + // appreciated but is not required. + // 2. Altered source versions must be plainly marked as such, and must not be + // misrepresented as being the original software. + // 3. This notice may not be removed or altered from any source distribution. diff --git a/src/core/picopng/picopng.cpp b/src/core/picopng/picopng.cpp new file mode 100644 index 000000000..97eb3b500 --- /dev/null +++ b/src/core/picopng/picopng.cpp @@ -0,0 +1,595 @@ +#include +#include +#include + +/* +decodePNG: The picoPNG function, decodes a PNG file buffer in memory, into a raw pixel buffer. +out_image: output parameter, this will contain the raw pixels after decoding. + By default the output is 32-bit RGBA color. + The std::vector is automatically resized to the correct size. +image_width: output_parameter, this will contain the width of the image in pixels. +image_height: output_parameter, this will contain the height of the image in pixels. +in_png: pointer to the buffer of the PNG file in memory. To get it from a file on + disk, load it and store it in a memory buffer yourself first. +in_size: size of the input PNG file in bytes. +convert_to_rgba32: optional parameter, true by default. + Set to true to get the output in RGBA 32-bit (8 bit per channel) color format + no matter what color type the original PNG image had. This gives predictable, + useable data from any random input PNG. + Set to false to do no color conversion at all. The result then has the same data + type as the PNG image, which can range from 1 bit to 64 bits per pixel. + Information about the color type or palette colors are not provided. You need + to know this information yourself to be able to use the data so this only + works for trusted PNG files. Use LodePNG instead of picoPNG if you need this information. +return: 0 if success, not 0 if some error occured. +*/ +int decodePNG(std::vector& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32 = true) +{ + // picoPNG version 20101224 + // Copyright (c) 2005-2010 Lode Vandevenne + // + // This software is provided 'as-is', without any express or implied + // warranty. In no event will the authors be held liable for any damages + // arising from the use of this software. + // + // Permission is granted to anyone to use this software for any purpose, + // including commercial applications, and to alter it and redistribute it + // freely, subject to the following restrictions: + // + // 1. The origin of this software must not be misrepresented; you must not + // claim that you wrote the original software. If you use this software + // in a product, an acknowledgment in the product documentation would be + // appreciated but is not required. + // 2. Altered source versions must be plainly marked as such, and must not be + // misrepresented as being the original software. + // 3. This notice may not be removed or altered from any source distribution. + + // picoPNG is a PNG decoder in one C++ function of around 500 lines. Use picoPNG for + // programs that need only 1 .cpp file. Since it's a single function, it's very limited, + // it can convert a PNG to raw pixel data either converted to 32-bit RGBA color or + // with no color conversion at all. For anything more complex, another tiny library + // is available: LodePNG (lodepng.c(pp)), which is a single source and header file. + // Apologies for the compact code style, it's to make this tiny. + + static const unsigned long LENBASE[29] = {3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258}; + static const unsigned long LENEXTRA[29] = {0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0}; + static const unsigned long DISTBASE[30] = {1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577}; + static const unsigned long DISTEXTRA[30] = {0,0,0,0,1,1,2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + static const unsigned long CLCL[19] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; //code length code lengths + struct Zlib //nested functions for zlib decompression + { + static unsigned long readBitFromStream(size_t& bitp, const unsigned char* bits) { unsigned long result = (bits[bitp >> 3] >> (bitp & 0x7)) & 1; bitp++; return result;} + static unsigned long readBitsFromStream(size_t& bitp, const unsigned char* bits, size_t nbits) + { + unsigned long result = 0; + for(size_t i = 0; i < nbits; i++) result += (readBitFromStream(bitp, bits)) << i; + return result; + } + struct HuffmanTree + { + int makeFromLengths(const std::vector& bitlen, unsigned long maxbitlen) + { //make tree given the lengths + unsigned long numcodes = (unsigned long)(bitlen.size()), treepos = 0, nodefilled = 0; + std::vector tree1d(numcodes), blcount(maxbitlen + 1, 0), nextcode(maxbitlen + 1, 0); + for(unsigned long bits = 0; bits < numcodes; bits++) blcount[bitlen[bits]]++; //count number of instances of each code length + for(unsigned long bits = 1; bits <= maxbitlen; bits++) nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1; + for(unsigned long n = 0; n < numcodes; n++) if(bitlen[n] != 0) tree1d[n] = nextcode[bitlen[n]]++; //generate all the codes + tree2d.clear(); tree2d.resize(numcodes * 2, 32767); //32767 here means the tree2d isn't filled there yet + for(unsigned long n = 0; n < numcodes; n++) //the codes + for(unsigned long i = 0; i < bitlen[n]; i++) //the bits for this code + { + unsigned long bit = (tree1d[n] >> (bitlen[n] - i - 1)) & 1; + if(treepos > numcodes - 2) return 55; + if(tree2d[2 * treepos + bit] == 32767) //not yet filled in + { + if(i + 1 == bitlen[n]) { tree2d[2 * treepos + bit] = n; treepos = 0; } //last bit + else { tree2d[2 * treepos + bit] = ++nodefilled + numcodes; treepos = nodefilled; } //addresses are encoded as values > numcodes + } + else treepos = tree2d[2 * treepos + bit] - numcodes; //subtract numcodes from address to get address value + } + return 0; + } + int decode(bool& decoded, unsigned long& result, size_t& treepos, unsigned long bit) const + { //Decodes a symbol from the tree + unsigned long numcodes = (unsigned long)tree2d.size() / 2; + if(treepos >= numcodes) return 11; //error: you appeared outside the codetree + result = tree2d[2 * treepos + bit]; + decoded = (result < numcodes); + treepos = decoded ? 0 : result - numcodes; + return 0; + } + std::vector tree2d; //2D representation of a huffman tree: The one dimension is "0" or "1", the other contains all nodes and leaves of the tree. + }; + struct Inflator + { + int error; + void inflate(std::vector& out, const std::vector& in, size_t inpos = 0) + { + size_t bp = 0, pos = 0; //bit pointer and byte pointer + error = 0; + unsigned long BFINAL = 0; + while(!BFINAL && !error) + { + if(bp >> 3 >= in.size()) { error = 52; return; } //error, bit pointer will jump past memory + BFINAL = readBitFromStream(bp, &in[inpos]); + unsigned long BTYPE = readBitFromStream(bp, &in[inpos]); BTYPE += 2 * readBitFromStream(bp, &in[inpos]); + if(BTYPE == 3) { error = 20; return; } //error: invalid BTYPE + else if(BTYPE == 0) inflateNoCompression(out, &in[inpos], bp, pos, in.size()); + else inflateHuffmanBlock(out, &in[inpos], bp, pos, in.size(), BTYPE); + } + if(!error) out.resize(pos); //Only now we know the true size of out, resize it to that + } + void generateFixedTrees(HuffmanTree& tree, HuffmanTree& treeD) //get the tree of a deflated block with fixed tree + { + std::vector bitlen(288, 8), bitlenD(32, 5);; + for(size_t i = 144; i <= 255; i++) bitlen[i] = 9; + for(size_t i = 256; i <= 279; i++) bitlen[i] = 7; + tree.makeFromLengths(bitlen, 15); + treeD.makeFromLengths(bitlenD, 15); + } + HuffmanTree codetree, codetreeD, codelengthcodetree; //the code tree for Huffman codes, dist codes, and code length codes + unsigned long huffmanDecodeSymbol(const unsigned char* in, size_t& bp, const HuffmanTree& codetree, size_t inlength) + { //decode a single symbol from given list of bits with given code tree. return value is the symbol + bool decoded; unsigned long ct; + for(size_t treepos = 0;;) + { + if((bp & 0x07) == 0 && (bp >> 3) > inlength) { error = 10; return 0; } //error: end reached without endcode + error = codetree.decode(decoded, ct, treepos, readBitFromStream(bp, in)); if(error) return 0; //stop, an error happened + if(decoded) return ct; + } + } + void getTreeInflateDynamic(HuffmanTree& tree, HuffmanTree& treeD, const unsigned char* in, size_t& bp, size_t inlength) + { //get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree + std::vector bitlen(288, 0), bitlenD(32, 0); + if(bp >> 3 >= inlength - 2) { error = 49; return; } //the bit pointer is or will go past the memory + size_t HLIT = readBitsFromStream(bp, in, 5) + 257; //number of literal/length codes + 257 + size_t HDIST = readBitsFromStream(bp, in, 5) + 1; //number of dist codes + 1 + size_t HCLEN = readBitsFromStream(bp, in, 4) + 4; //number of code length codes + 4 + std::vector codelengthcode(19); //lengths of tree to decode the lengths of the dynamic tree + for(size_t i = 0; i < 19; i++) codelengthcode[CLCL[i]] = (i < HCLEN) ? readBitsFromStream(bp, in, 3) : 0; + error = codelengthcodetree.makeFromLengths(codelengthcode, 7); if(error) return; + size_t i = 0, replength; + while(i < HLIT + HDIST) + { + unsigned long code = huffmanDecodeSymbol(in, bp, codelengthcodetree, inlength); if(error) return; + if(code <= 15) { if(i < HLIT) bitlen[i++] = code; else bitlenD[i++ - HLIT] = code; } //a length code + else if(code == 16) //repeat previous + { + if(bp >> 3 >= inlength) { error = 50; return; } //error, bit pointer jumps past memory + replength = 3 + readBitsFromStream(bp, in, 2); + unsigned long value; //set value to the previous code + if((i - 1) < HLIT) value = bitlen[i - 1]; + else value = bitlenD[i - HLIT - 1]; + for(size_t n = 0; n < replength; n++) //repeat this value in the next lengths + { + if(i >= HLIT + HDIST) { error = 13; return; } //error: i is larger than the amount of codes + if(i < HLIT) bitlen[i++] = value; else bitlenD[i++ - HLIT] = value; + } + } + else if(code == 17) //repeat "0" 3-10 times + { + if(bp >> 3 >= inlength) { error = 50; return; } //error, bit pointer jumps past memory + replength = 3 + readBitsFromStream(bp, in, 3); + for(size_t n = 0; n < replength; n++) //repeat this value in the next lengths + { + if(i >= HLIT + HDIST) { error = 14; return; } //error: i is larger than the amount of codes + if(i < HLIT) bitlen[i++] = 0; else bitlenD[i++ - HLIT] = 0; + } + } + else if(code == 18) //repeat "0" 11-138 times + { + if(bp >> 3 >= inlength) { error = 50; return; } //error, bit pointer jumps past memory + replength = 11 + readBitsFromStream(bp, in, 7); + for(size_t n = 0; n < replength; n++) //repeat this value in the next lengths + { + if(i >= HLIT + HDIST) { error = 15; return; } //error: i is larger than the amount of codes + if(i < HLIT) bitlen[i++] = 0; else bitlenD[i++ - HLIT] = 0; + } + } + else { error = 16; return; } //error: somehow an unexisting code appeared. This can never happen. + } + if(bitlen[256] == 0) { error = 64; return; } //the length of the end code 256 must be larger than 0 + error = tree.makeFromLengths(bitlen, 15); if(error) return; //now we've finally got HLIT and HDIST, so generate the code trees, and the function is done + error = treeD.makeFromLengths(bitlenD, 15); if(error) return; + } + void inflateHuffmanBlock(std::vector& out, const unsigned char* in, size_t& bp, size_t& pos, size_t inlength, unsigned long btype) + { + if(btype == 1) { generateFixedTrees(codetree, codetreeD); } + else if(btype == 2) { getTreeInflateDynamic(codetree, codetreeD, in, bp, inlength); if(error) return; } + for(;;) + { + unsigned long code = huffmanDecodeSymbol(in, bp, codetree, inlength); if(error) return; + if(code == 256) return; //end code + else if(code <= 255) //literal symbol + { + if(pos >= out.size()) out.resize((pos + 1) * 2); //reserve more room + out[pos++] = (unsigned char)(code); + } + else if(code >= 257 && code <= 285) //length code + { + size_t length = LENBASE[code - 257], numextrabits = LENEXTRA[code - 257]; + if((bp >> 3) >= inlength) { error = 51; return; } //error, bit pointer will jump past memory + length += readBitsFromStream(bp, in, numextrabits); + unsigned long codeD = huffmanDecodeSymbol(in, bp, codetreeD, inlength); if(error) return; + if(codeD > 29) { error = 18; return; } //error: invalid dist code (30-31 are never used) + unsigned long dist = DISTBASE[codeD], numextrabitsD = DISTEXTRA[codeD]; + if((bp >> 3) >= inlength) { error = 51; return; } //error, bit pointer will jump past memory + dist += readBitsFromStream(bp, in, numextrabitsD); + size_t start = pos, back = start - dist; //backwards + if(pos + length >= out.size()) out.resize((pos + length) * 2); //reserve more room + for(size_t i = 0; i < length; i++) { out[pos++] = out[back++]; if(back >= start) back = start - dist; } + } + } + } + void inflateNoCompression(std::vector& out, const unsigned char* in, size_t& bp, size_t& pos, size_t inlength) + { + while((bp & 0x7) != 0) bp++; //go to first boundary of byte + size_t p = bp / 8; + if(p >= inlength - 4) { error = 52; return; } //error, bit pointer will jump past memory + unsigned long LEN = in[p] + 256 * in[p + 1], NLEN = in[p + 2] + 256 * in[p + 3]; p += 4; + if(LEN + NLEN != 65535) { error = 21; return; } //error: NLEN is not one's complement of LEN + if(pos + LEN >= out.size()) out.resize(pos + LEN); + if(p + LEN > inlength) { error = 23; return; } //error: reading outside of in buffer + for(unsigned long n = 0; n < LEN; n++) out[pos++] = in[p++]; //read LEN bytes of literal data + bp = p * 8; + } + }; + int decompress(std::vector& out, const std::vector& in) //returns error value + { + Inflator inflator; + if(in.size() < 2) { return 53; } //error, size of zlib data too small + if((in[0] * 256 + in[1]) % 31 != 0) { return 24; } //error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way + unsigned long CM = in[0] & 15, CINFO = (in[0] >> 4) & 15, FDICT = (in[1] >> 5) & 1; + if(CM != 8 || CINFO > 7) { return 25; } //error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec + if(FDICT != 0) { return 26; } //error: the specification of PNG says about the zlib stream: "The additional flags shall not specify a preset dictionary." + inflator.inflate(out, in, 2); + return inflator.error; //note: adler32 checksum was skipped and ignored + } + }; + struct PNG //nested functions for PNG decoding + { + struct Info + { + unsigned long width, height, colorType, bitDepth, compressionMethod, filterMethod, interlaceMethod, key_r, key_g, key_b; + bool key_defined; //is a transparent color key given? + std::vector palette; + } info; + int error; + void decode(std::vector& out, const unsigned char* in, size_t size, bool convert_to_rgba32) + { + error = 0; + if(size == 0 || in == 0) { error = 48; return; } //the given data is empty + readPngHeader(&in[0], size); if(error) return; + size_t pos = 33; //first byte of the first chunk after the header + std::vector idat; //the data from idat chunks + 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 + { + if(pos + 8 >= size) { error = 30; return; } //error: size of the in buffer too small to contain next chunk + size_t chunkLength = read32bitInt(&in[pos]); pos += 4; + if(chunkLength > 2147483647) { error = 63; return; } + if(pos + chunkLength >= size) { error = 35; return; } //error: size of the in buffer too small to contain next chunk + if(in[pos + 0] == 'I' && in[pos + 1] == 'D' && in[pos + 2] == 'A' && in[pos + 3] == 'T') //IDAT chunk, containing compressed image data + { + idat.insert(idat.end(), &in[pos + 4], &in[pos + 4 + chunkLength]); + pos += (4 + chunkLength); + } + else if(in[pos + 0] == 'I' && in[pos + 1] == 'E' && in[pos + 2] == 'N' && in[pos + 3] == 'D') { pos += 4; IEND = true; } + else if(in[pos + 0] == 'P' && in[pos + 1] == 'L' && in[pos + 2] == 'T' && in[pos + 3] == 'E') //palette chunk (PLTE) + { + pos += 4; //go after the 4 letters + info.palette.resize(4 * (chunkLength / 3)); + if(info.palette.size() > (4 * 256)) { error = 38; return; } //error: palette too big + for(size_t i = 0; i < info.palette.size(); i += 4) + { + for(size_t j = 0; j < 3; j++) info.palette[i + j] = in[pos++]; //RGB + info.palette[i + 3] = 255; //alpha + } + } + else if(in[pos + 0] == 't' && in[pos + 1] == 'R' && in[pos + 2] == 'N' && in[pos + 3] == 'S') //palette transparency chunk (tRNS) + { + pos += 4; //go after the 4 letters + if(info.colorType == 3) + { + if(4 * chunkLength > info.palette.size()) { error = 39; return; } //error: more alpha values given than there are palette entries + for(size_t i = 0; i < chunkLength; i++) info.palette[4 * i + 3] = in[pos++]; + } + else if(info.colorType == 0) + { + if(chunkLength != 2) { error = 40; return; } //error: this chunk must be 2 bytes for greyscale image + info.key_defined = 1; info.key_r = info.key_g = info.key_b = 256 * in[pos] + in[pos + 1]; pos += 2; + } + else if(info.colorType == 2) + { + if(chunkLength != 6) { error = 41; return; } //error: this chunk must be 6 bytes for RGB image + info.key_defined = 1; + info.key_r = 256 * in[pos] + in[pos + 1]; pos += 2; + info.key_g = 256 * in[pos] + in[pos + 1]; pos += 2; + info.key_b = 256 * in[pos] + in[pos + 1]; pos += 2; + } + else { error = 42; return; } //error: tRNS chunk not allowed for other color models + } + else //it's not an implemented chunk type, so ignore it: skip over the data + { + 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; + } + pos += 4; //step over CRC (which is ignored) + } + unsigned long bpp = getBpp(info); + std::vector scanlines(((info.width * (info.height * bpp + 7)) / 8) + info.height); //now the out buffer will be filled + Zlib zlib; //decompress with the Zlib decompressor + error = zlib.decompress(scanlines, idat); if(error) return; //stop if the zlib decompressor returned an error + size_t bytewidth = (bpp + 7) / 8, outlength = (info.height * info.width * bpp + 7) / 8; + out.resize(outlength); //time to fill the out buffer + unsigned char* out_ = outlength ? &out[0] : 0; //use a regular pointer to the std::vector for faster code if compiled without optimization + if(info.interlaceMethod == 0) //no interlace, just filter + { + size_t linestart = 0, linelength = (info.width * bpp + 7) / 8; //length in bytes of a scanline, excluding the filtertype byte + if(bpp >= 8) //byte per byte + for(unsigned long y = 0; y < info.height; y++) + { + unsigned long filterType = scanlines[linestart]; + const unsigned char* prevline = (y == 0) ? 0 : &out_[(y - 1) * info.width * bytewidth]; + unFilterScanline(&out_[linestart - y], &scanlines[linestart + 1], prevline, bytewidth, filterType, linelength); if(error) return; + linestart += (1 + linelength); //go to start of next scanline + } + else //less than 8 bits per pixel, so fill it up bit per bit + { + std::vector templine((info.width * bpp + 7) >> 3); //only used if bpp < 8 + for(size_t y = 0, obp = 0; y < info.height; y++) + { + unsigned long filterType = scanlines[linestart]; + const unsigned char* prevline = (y == 0) ? 0 : &out_[(y - 1) * info.width * bytewidth]; + unFilterScanline(&templine[0], &scanlines[linestart + 1], prevline, bytewidth, filterType, linelength); if(error) return; + for(size_t bp = 0; bp < info.width * bpp;) setBitOfReversedStream(obp, out_, readBitFromReversedStream(bp, &templine[0])); + linestart += (1 + linelength); //go to start of next scanline + } + } + } + else //interlaceMethod is 1 (Adam7) + { + size_t passw[7] = { (info.width + 7) / 8, (info.width + 3) / 8, (info.width + 3) / 4, (info.width + 1) / 4, (info.width + 1) / 2, (info.width + 0) / 2, (info.width + 0) / 1 }; + size_t passh[7] = { (info.height + 7) / 8, (info.height + 7) / 8, (info.height + 3) / 8, (info.height + 3) / 4, (info.height + 1) / 4, (info.height + 1) / 2, (info.height + 0) / 2 }; + size_t passstart[7] = {0}; + size_t pattern[28] = {0,4,0,2,0,1,0,0,0,4,0,2,0,1,8,8,4,4,2,2,1,8,8,8,4,4,2,2}; //values for the adam7 passes + for(int i = 0; i < 6; i++) passstart[i + 1] = passstart[i] + passh[i] * ((passw[i] ? 1 : 0) + (passw[i] * bpp + 7) / 8); + std::vector scanlineo((info.width * bpp + 7) / 8), scanlinen((info.width * bpp + 7) / 8); //"old" and "new" scanline + for(int i = 0; i < 7; i++) + adam7Pass(&out_[0], &scanlinen[0], &scanlineo[0], &scanlines[passstart[i]], info.width, pattern[i], pattern[i + 7], pattern[i + 14], pattern[i + 21], passw[i], passh[i], bpp); + } + if(convert_to_rgba32 && (info.colorType != 6 || info.bitDepth != 8)) //conversion needed + { + std::vector data = out; + error = convert(out, &data[0], info, info.width, info.height); + } + } + void readPngHeader(const unsigned char* in, size_t inlength) //read the information from the header and store it in the Info + { + if(inlength < 29) { error = 27; return; } //error: the data length is smaller than the length of the header + if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { error = 28; return; } //no PNG signature + if(in[12] != 'I' || in[13] != 'H' || in[14] != 'D' || in[15] != 'R') { error = 29; return; } //error: it doesn't start with a IHDR chunk! + info.width = read32bitInt(&in[16]); info.height = read32bitInt(&in[20]); + info.bitDepth = in[24]; info.colorType = in[25]; + info.compressionMethod = in[26]; if(in[26] != 0) { error = 32; return; } //error: only compression method 0 is allowed in the specification + info.filterMethod = in[27]; if(in[27] != 0) { error = 33; return; } //error: only filter method 0 is allowed in the specification + info.interlaceMethod = in[28]; if(in[28] > 1) { error = 34; return; } //error: only interlace methods 0 and 1 exist in the specification + error = checkColorValidity(info.colorType, info.bitDepth); + } + void unFilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, size_t bytewidth, unsigned long filterType, size_t length) + { + switch(filterType) + { + case 0: for(size_t i = 0; i < length; i++) recon[i] = scanline[i]; break; + case 1: + for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i]; + for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth]; + break; + case 2: + if(precon) for(size_t i = 0; i < length; i++) recon[i] = scanline[i] + precon[i]; + else for(size_t i = 0; i < length; i++) recon[i] = scanline[i]; + break; + case 3: + if(precon) + { + for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i] + precon[i] / 2; + for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) / 2); + } + else + { + for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i]; + for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth] / 2; + } + break; + case 4: + if(precon) + { + for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i] + paethPredictor(0, precon[i], 0); + for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth]); + } + else + { + for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i]; + for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + paethPredictor(recon[i - bytewidth], 0, 0); + } + break; + default: error = 36; return; //error: unexisting filter type given + } + } + void adam7Pass(unsigned char* out, unsigned char* linen, unsigned char* lineo, const unsigned char* in, unsigned long w, size_t passleft, size_t passtop, size_t spacex, size_t spacey, size_t passw, size_t passh, unsigned long bpp) + { //filter and reposition the pixels into the output when the image is Adam7 interlaced. This function can only do it after the full image is already decoded. The out buffer must have the correct allocated memory size already. + if(passw == 0) return; + size_t bytewidth = (bpp + 7) / 8, linelength = 1 + ((bpp * passw + 7) / 8); + for(unsigned long y = 0; y < passh; y++) + { + unsigned char filterType = in[y * linelength], *prevline = (y == 0) ? 0 : lineo; + unFilterScanline(linen, &in[y * linelength + 1], prevline, bytewidth, filterType, (w * bpp + 7) / 8); if(error) return; + if(bpp >= 8) for(size_t i = 0; i < passw; i++) for(size_t b = 0; b < bytewidth; b++) //b = current byte of this pixel + out[bytewidth * w * (passtop + spacey * y) + bytewidth * (passleft + spacex * i) + b] = linen[bytewidth * i + b]; + else for(size_t i = 0; i < passw; i++) + { + size_t obp = bpp * w * (passtop + spacey * y) + bpp * (passleft + spacex * i), bp = i * bpp; + for(size_t b = 0; b < bpp; b++) setBitOfReversedStream(obp, out, readBitFromReversedStream(bp, &linen[0])); + } + unsigned char* temp = linen; linen = lineo; lineo = temp; //swap the two buffer pointers "line old" and "line new" + } + } + static unsigned long readBitFromReversedStream(size_t& bitp, const unsigned char* bits) { unsigned long result = (bits[bitp >> 3] >> (7 - (bitp & 0x7))) & 1; bitp++; return result;} + static unsigned long readBitsFromReversedStream(size_t& bitp, const unsigned char* bits, unsigned long nbits) + { + unsigned long result = 0; + for(size_t i = nbits - 1; i < nbits; i--) result += ((readBitFromReversedStream(bitp, bits)) << i); + return result; + } + void setBitOfReversedStream(size_t& bitp, unsigned char* bits, unsigned long bit) { bits[bitp >> 3] |= (bit << (7 - (bitp & 0x7))); bitp++; } + unsigned long read32bitInt(const unsigned char* buffer) { return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; } + int checkColorValidity(unsigned long colorType, unsigned long bd) //return type is a LodePNG error code + { + if((colorType == 2 || colorType == 4 || colorType == 6)) { if(!(bd == 8 || bd == 16)) return 37; else return 0; } + else if(colorType == 0) { if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; else return 0; } + else if(colorType == 3) { if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; else return 0; } + else return 31; //unexisting color type + } + unsigned long getBpp(const Info& info) + { + if(info.colorType == 2) return (3 * info.bitDepth); + else if(info.colorType >= 4) return (info.colorType - 2) * info.bitDepth; + else return info.bitDepth; + } + int convert(std::vector& out, const unsigned char* in, Info& infoIn, unsigned long w, unsigned long h) + { //converts from any color type to 32-bit. return value = LodePNG error code + size_t numpixels = w * h, bp = 0; + out.resize(numpixels * 4); + unsigned char* out_ = out.empty() ? 0 : &out[0]; //faster if compiled without optimization + if(infoIn.bitDepth == 8 && infoIn.colorType == 0) //greyscale + for(size_t i = 0; i < numpixels; i++) + { + out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[i]; + out_[4 * i + 3] = (infoIn.key_defined && in[i] == infoIn.key_r) ? 0 : 255; + } + else if(infoIn.bitDepth == 8 && infoIn.colorType == 2) //RGB color + for(size_t i = 0; i < numpixels; i++) + { + for(size_t c = 0; c < 3; c++) out_[4 * i + c] = in[3 * i + c]; + out_[4 * i + 3] = (infoIn.key_defined == 1 && in[3 * i + 0] == infoIn.key_r && in[3 * i + 1] == infoIn.key_g && in[3 * i + 2] == infoIn.key_b) ? 0 : 255; + } + else if(infoIn.bitDepth == 8 && infoIn.colorType == 3) //indexed color (palette) + for(size_t i = 0; i < numpixels; i++) + { + if(4U * in[i] >= infoIn.palette.size()) return 46; + for(size_t c = 0; c < 4; c++) out_[4 * i + c] = infoIn.palette[4 * in[i] + c]; //get rgb colors from the palette + } + else if(infoIn.bitDepth == 8 && infoIn.colorType == 4) //greyscale with alpha + for(size_t i = 0; i < numpixels; i++) + { + out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[2 * i + 0]; + out_[4 * i + 3] = in[2 * i + 1]; + } + else if(infoIn.bitDepth == 8 && infoIn.colorType == 6) for(size_t i = 0; i < numpixels; i++) for(size_t c = 0; c < 4; c++) out_[4 * i + c] = in[4 * i + c]; //RGB with alpha + else if(infoIn.bitDepth == 16 && infoIn.colorType == 0) //greyscale + for(size_t i = 0; i < numpixels; i++) + { + out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[2 * i]; + out_[4 * i + 3] = (infoIn.key_defined && 256U * in[i] + in[i + 1] == infoIn.key_r) ? 0 : 255; + } + else if(infoIn.bitDepth == 16 && infoIn.colorType == 2) //RGB color + for(size_t i = 0; i < numpixels; i++) + { + for(size_t c = 0; c < 3; c++) out_[4 * i + c] = in[6 * i + 2 * c]; + out_[4 * i + 3] = (infoIn.key_defined && 256U*in[6*i+0]+in[6*i+1] == infoIn.key_r && 256U*in[6*i+2]+in[6*i+3] == infoIn.key_g && 256U*in[6*i+4]+in[6*i+5] == infoIn.key_b) ? 0 : 255; + } + else if(infoIn.bitDepth == 16 && infoIn.colorType == 4) //greyscale with alpha + for(size_t i = 0; i < numpixels; i++) + { + out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[4 * i]; //most significant byte + out_[4 * i + 3] = in[4 * i + 2]; + } + else if(infoIn.bitDepth == 16 && infoIn.colorType == 6) for(size_t i = 0; i < numpixels; i++) for(size_t c = 0; c < 4; c++) out_[4 * i + c] = in[8 * i + 2 * c]; //RGB with alpha + else if(infoIn.bitDepth < 8 && infoIn.colorType == 0) //greyscale + for(size_t i = 0; i < numpixels; i++) + { + unsigned long value = (readBitsFromReversedStream(bp, in, infoIn.bitDepth) * 255) / ((1 << infoIn.bitDepth) - 1); //scale value from 0 to 255 + out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = (unsigned char)(value); + out_[4 * i + 3] = (infoIn.key_defined && value && ((1U << infoIn.bitDepth) - 1U) == infoIn.key_r && ((1U << infoIn.bitDepth) - 1U)) ? 0 : 255; + } + else if(infoIn.bitDepth < 8 && infoIn.colorType == 3) //palette + for(size_t i = 0; i < numpixels; i++) + { + unsigned long value = readBitsFromReversedStream(bp, in, infoIn.bitDepth); + if(4 * value >= infoIn.palette.size()) return 47; + for(size_t c = 0; c < 4; c++) out_[4 * i + c] = infoIn.palette[4 * value + c]; //get rgb colors from the palette + } + return 0; + } + unsigned char paethPredictor(short a, short b, short c) //Paeth predicter, used by PNG filter type 4 + { + short p = a + b - c, pa = p > a ? (p - a) : (a - p), pb = p > b ? (p - b) : (b - p), pc = p > c ? (p - c) : (c - p); + return (unsigned char)((pa <= pb && pa <= pc) ? a : pb <= pc ? b : c); + } + }; + PNG decoder; decoder.decode(out_image, in_png, in_size, convert_to_rgba32); + image_width = decoder.info.width; image_height = decoder.info.height; + return decoder.error; +} + +#if 0 + + + +//an example using the PNG loading function: + +#include +#include + +void loadFile(std::vector& buffer, const std::string& filename) //designed for loading files from hard disk in an std::vector +{ + std::ifstream file(filename.c_str(), std::ios::in|std::ios::binary|std::ios::ate); + + //get filesize + std::streamsize size = 0; + if(file.seekg(0, std::ios::end).good()) size = file.tellg(); + if(file.seekg(0, std::ios::beg).good()) size -= file.tellg(); + + //read contents of the file into the vector + if(size > 0) + { + buffer.resize((size_t)size); + file.read((char*)(&buffer[0]), size); + } + else buffer.clear(); +} + +int main(int argc, char *argv[]) +{ + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector buffer, image; + loadFile(buffer, filename); + unsigned long w, h; + int error = decodePNG(image, w, h, buffer.empty() ? 0 : &buffer[0], (unsigned long)buffer.size()); + + //if there's an error, display it + if(error != 0) std::cout << "error: " << error << std::endl; + + //the pixels are now in the vector "image", use it as texture, draw it, ... + + if(image.size() > 4) std::cout << "width: " << w << " height: " << h << " first pixel: " << std::hex << int(image[0]) << int(image[1]) << int(image[2]) << int(image[3]) << std::endl; +} + +/* + //this is test code, it displays the pixels of a 1 bit PNG. To use it, set the flag convert_to_rgba32 to false and load a 1-bit PNG image with a small size (so that its ASCII representation can fit in a console window) + for(int y = 0; y < h; y++) + { + for(int x = 0; x < w; x++) + { + int i = y * h + x; + std::cout << (((image[i/8] >> (7-i%8)) & 1) ? '.' : '#'); + } + std::cout << std::endl; + } +*/ + +#endif diff --git a/src/core/picopng/picopng.h b/src/core/picopng/picopng.h new file mode 100644 index 000000000..87dc961be --- /dev/null +++ b/src/core/picopng/picopng.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +int decodePNG(std::vector& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32 = true); diff --git a/src/core/span_layout.cpp b/src/core/span_layout.cpp new file mode 100644 index 000000000..1b68f6ef7 --- /dev/null +++ b/src/core/span_layout.cpp @@ -0,0 +1,883 @@ + +#include "core/span_layout.h" +#include "core/canvas.h" +#include "core/widget.h" +#include "core/font.h" +#include "core/image.h" + +SpanLayout::SpanLayout() +{ +} + +SpanLayout::~SpanLayout() +{ +} + +void SpanLayout::Clear() +{ + objects.clear(); + text.clear(); + lines.clear(); +} + +std::vector SpanLayout::GetRectById(int id) const +{ + std::vector segment_rects; + + double x = position.x; + double y = position.y; + for (std::vector::size_type line_index = 0; line_index < lines.size(); line_index++) + { + const Line& line = lines[line_index]; + for (std::vector::size_type segment_index = 0; segment_index < line.segments.size(); segment_index++) + { + const LineSegment& segment = line.segments[segment_index]; + if (segment.id == id) + { + segment_rects.push_back(Rect(x + segment.x_position, y, segment.width, y + line.height)); + } + } + y += line.height; + } + + return segment_rects; +} + +void SpanLayout::DrawLayout(Canvas* canvas) +{ + double x = position.x; + double y = position.y; + for (std::vector::size_type line_index = 0; line_index < lines.size(); line_index++) + { + Line& line = lines[line_index]; + for (std::vector::size_type segment_index = 0; segment_index < line.segments.size(); segment_index++) + { + LineSegment& segment = line.segments[segment_index]; + switch (segment.type) + { + case object_text: + DrawLayoutText(canvas, line, segment, x, y); + break; + case object_image: + DrawLayoutImage(canvas, line, segment, x, y); + break; + case object_component: + break; + } + + } + + if (line_index + 1 == lines.size() && !line.segments.empty()) + { + LineSegment& segment = line.segments.back(); + if (cursor_visible && segment.end <= cursor_pos) + { + switch (segment.type) + { + case 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; + } + } + } + + y += line.height; + } +} + +void SpanLayout::DrawLayoutEllipsis(Canvas* canvas, const Rect& content_rect) +{ + is_ellipsis_draw = true; + ellipsis_content_rect = content_rect; + try + { + is_ellipsis_draw = false; + DrawLayout(canvas); + } + catch (...) + { + is_ellipsis_draw = false; + throw; + } +} + +void SpanLayout::DrawLayoutImage(Canvas* canvas, Line& line, LineSegment& segment, double x, double y) +{ + canvas->drawImage(segment.image, Point(x + segment.x_position, y + line.ascender - segment.ascender)); +} + +void SpanLayout::DrawLayoutText(Canvas* canvas, Line& line, LineSegment& segment, double x, double y) +{ + std::string segment_text = text.substr(segment.start, segment.end - segment.start); + + int length = (int)segment_text.length(); + int s1 = clamp((int)sel_start - (int)segment.start, 0, length); + int s2 = clamp((int)sel_end - (int)segment.start, 0, length); + + if (s1 != s2) + { + double xx = x + segment.x_position; + double xx0 = xx + canvas->measureText(segment.font, segment_text.substr(0, s1)).width; + double xx1 = xx0 + canvas->measureText(segment.font, segment_text.substr(s1, s2 - s1)).width; + double sel_width = canvas->measureText(segment.font, segment_text.substr(s1, s2 - s1)).width; + + canvas->fillRect(Rect::ltrb(xx0, y + line.ascender - segment.ascender, xx1, y + line.ascender + segment.descender), sel_background); + + if (cursor_visible && cursor_pos >= segment.start && cursor_pos < segment.end) + { + double cursor_x = x + segment.x_position + canvas->measureText(segment.font, text.substr(segment.start, cursor_pos - segment.start)).width; + double cursor_width = cursor_overwrite_mode ? canvas->measureText(segment.font, text.substr(cursor_pos, 1)).width : 1; + canvas->fillRect(Rect::ltrb(cursor_x, y + line.ascender - segment.ascender, cursor_x + cursor_width, y + line.ascender + segment.descender), cursor_color); + } + + if (s1 > 0) + { + if (is_ellipsis_draw) + canvas->drawTextEllipsis(segment.font, Point(xx, y + line.ascender), ellipsis_content_rect, segment_text.substr(0, s1), segment.color); + else + canvas->drawText(segment.font, Point(xx, y + line.ascender), segment_text.substr(0, s1), segment.color); + } + if (is_ellipsis_draw) + canvas->drawTextEllipsis(segment.font, Point(xx0, y + line.ascender), ellipsis_content_rect, segment_text.substr(s1, s2 - s1), sel_foreground); + else + canvas->drawText(segment.font, Point(xx0, y + line.ascender), segment_text.substr(s1, s2 - s1), sel_foreground); + xx += sel_width; + if (s2 < length) + { + if (is_ellipsis_draw) + canvas->drawTextEllipsis(segment.font, Point(xx1, y + line.ascender), ellipsis_content_rect, segment_text.substr(s2), segment.color); + else + canvas->drawText(segment.font, Point(xx1, y + line.ascender), segment_text.substr(s2), segment.color); + } + } + else + { + if (cursor_visible && cursor_pos >= segment.start && cursor_pos < segment.end) + { + double cursor_x = x + segment.x_position + canvas->measureText(segment.font, text.substr(segment.start, cursor_pos - segment.start)).width; + double cursor_width = cursor_overwrite_mode ? canvas->measureText(segment.font, text.substr(cursor_pos, 1)).width : 1; + canvas->fillRect(Rect::ltrb(cursor_x, y + line.ascender - segment.ascender, cursor_x + cursor_width, y + line.ascender + segment.descender), cursor_color); + } + + if (is_ellipsis_draw) + canvas->drawTextEllipsis(segment.font, Point(x + segment.x_position, y + line.ascender), ellipsis_content_rect, segment_text, segment.color); + else + canvas->drawText(segment.font, Point(x + segment.x_position, y + line.ascender), segment_text, segment.color); + } +} + +SpanLayout::HitTestResult SpanLayout::HitTest(Canvas* canvas, const Point& pos) +{ + SpanLayout::HitTestResult result; + + if (lines.empty()) + { + result.type = SpanLayout::HitTestResult::no_objects_available; + return result; + } + + double x = position.x; + double y = position.y; + + // Check if we are outside to the top + if (pos.y < y) + { + result.type = SpanLayout::HitTestResult::outside_top; + result.object_id = lines[0].segments[0].id; + result.offset = 0; + return result; + } + + for (std::vector::size_type line_index = 0; line_index < lines.size(); line_index++) + { + Line& line = lines[line_index]; + + // Check if we found current line + if (pos.y >= y && pos.y <= y + line.height) + { + for (std::vector::size_type segment_index = 0; segment_index < line.segments.size(); segment_index++) + { + LineSegment& segment = line.segments[segment_index]; + + // Check if we are outside to the left + if (segment_index == 0 && pos.x < x) + { + result.type = SpanLayout::HitTestResult::outside_left; + result.object_id = segment.id; + result.offset = segment.start; + return result; + } + + // Check if we are inside a segment + if (pos.x >= x + segment.x_position && pos.x <= x + segment.x_position + segment.width) + { + std::string segment_text = text.substr(segment.start, segment.end - segment.start); + Point hit_point(pos.x - x - segment.x_position, 0); + size_t offset = segment.start + canvas->getCharacterIndex(segment.font, segment_text, hit_point); + + result.type = SpanLayout::HitTestResult::inside; + result.object_id = segment.id; + result.offset = offset; + return result; + } + + // Check if we are outside to the right + if (segment_index == line.segments.size() - 1 && pos.x > x + segment.x_position + segment.width) + { + result.type = SpanLayout::HitTestResult::outside_right; + result.object_id = segment.id; + result.offset = segment.end; + return result; + } + } + } + + y += line.height; + } + + // We are outside to the bottom + const Line& last_line = lines[lines.size() - 1]; + const LineSegment& last_segment = last_line.segments[last_line.segments.size() - 1]; + + result.type = SpanLayout::HitTestResult::outside_bottom; + result.object_id = last_segment.id; + result.offset = last_segment.end; + return result; +} + +Size SpanLayout::GetSize() const +{ + return GetRect().size(); +} + +Rect SpanLayout::GetRect() const +{ + double x = position.x; + double y = position.y; + + const double max_value = 0x70000000; + double left = max_value; + double top = max_value; + double right = -max_value; + double bottom = -max_value; + + for (std::vector::size_type line_index = 0; line_index < lines.size(); line_index++) + { + const Line& line = lines[line_index]; + for (std::vector::size_type segment_index = 0; segment_index < line.segments.size(); segment_index++) + { + const LineSegment& segment = line.segments[segment_index]; + Rect area(Point(x + segment.x_position, y), Size(segment.width, line.height)); + + left = std::min(left, area.left()); + right = std::max(right, area.right()); + top = std::min(top, area.top()); + bottom = std::max(bottom, area.bottom()); + } + y += line.height; + } + if (left > right) + left = right = position.x; + + if (top > bottom) + top = bottom = position.y; + + return Rect::ltrb(left, top, right, bottom); +} + +void SpanLayout::AddText(const std::string& more_text, std::shared_ptr font, const Colorf& color, int id) +{ + SpanObject object; + object.type = object_text; + object.start = text.length(); + object.end = object.start + more_text.length(); + object.font = font; + object.color = color; + object.id = id; + objects.push_back(object); + text += more_text; +} + +void SpanLayout::AddImage(std::shared_ptr image, double baseline_offset, int id) +{ + SpanObject object; + object.type = object_image; + object.image = image; + object.baseline_offset = baseline_offset; + object.id = id; + object.start = text.length(); + object.end = object.start + 1; + objects.push_back(object); + text += "*"; +} + +void SpanLayout::AddWidget(Widget* component, double baseline_offset, int id) +{ + SpanObject object; + object.type = object_component; + object.component = component; + object.baseline_offset = baseline_offset; + object.id = id; + object.start = text.length(); + object.end = object.start + 1; + objects.push_back(object); + text += "*"; +} + +void SpanLayout::Layout(Canvas* canvas, double max_width) +{ + LayoutLines(canvas, max_width); + + switch (alignment) + { + case span_right: AlignRight(max_width); break; + case span_center: AlignCenter(max_width); break; + case span_justify: AlignJustify(max_width); break; + case span_left: + default: break; + } +} + +void SpanLayout::SetPosition(const Point& pos) +{ + position = pos; +} + +SpanLayout::TextSizeResult SpanLayout::FindTextSize(Canvas* canvas, const TextBlock& block, size_t object_index) +{ + std::shared_ptr font = objects[object_index].font; + if (layout_cache.object_index != (int)object_index) + { + layout_cache.object_index = (int)object_index; + layout_cache.metrics = canvas->getFontMetrics(font); + } + + TextSizeResult result; + result.start = block.start; + size_t pos = block.start; + double x_position = 0; + while (pos != block.end) + { + size_t end = std::min(objects[object_index].end, block.end); + std::string subtext = text.substr(pos, end - pos); + + Size text_size = canvas->measureText(font, subtext).size(); + + result.width += text_size.width; + result.height = std::max(result.height, layout_cache.metrics.height + layout_cache.metrics.external_leading); + result.ascender = std::max(result.ascender, layout_cache.metrics.ascent); + result.descender = std::max(result.descender, layout_cache.metrics.descent); + + LineSegment segment; + segment.type = object_text; + segment.start = pos; + segment.end = end; + segment.font = objects[object_index].font; + segment.color = objects[object_index].color; + segment.id = objects[object_index].id; + segment.x_position = x_position; + segment.width = text_size.width; + segment.ascender = layout_cache.metrics.ascent; + segment.descender = layout_cache.metrics.descent; + x_position += text_size.width; + result.segments.push_back(segment); + + pos = end; + if (pos == objects[object_index].end) + { + object_index++; + result.objects_traversed++; + + if (object_index < objects.size()) + { + layout_cache.object_index = (int)object_index; + font = objects[object_index].font; + layout_cache.metrics = canvas->getFontMetrics(font); + } + } + } + result.end = pos; + return result; +} + +std::vector SpanLayout::FindTextBlocks() +{ + std::vector blocks; + std::vector::iterator block_object_it; + + // Find first object that is not text: + for (block_object_it = objects.begin(); block_object_it != objects.end() && (*block_object_it).type == object_text; ++block_object_it); + + std::string::size_type pos = 0; + while (pos < text.size()) + { + // Find end of text block: + std::string::size_type end_pos; + switch (text[pos]) + { + case ' ': + case '\t': + case '\n': + end_pos = text.find_first_not_of(text[pos], pos); + break; + default: + end_pos = text.find_first_of(" \t\n", pos); + break; + } + + if (end_pos == std::string::npos) + end_pos = text.length(); + + // If we traversed past an object that is not text: + if (block_object_it != objects.end() && (*block_object_it).start < end_pos) + { + // End text block + end_pos = (*block_object_it).start; + if (end_pos > pos) + { + TextBlock block; + block.start = pos; + block.end = end_pos; + blocks.push_back(block); + } + + // Create object block: + pos = end_pos; + end_pos = pos + 1; + + TextBlock block; + block.start = pos; + block.end = end_pos; + blocks.push_back(block); + + // Find next object that is not text: + for (++block_object_it; block_object_it != objects.end() && (*block_object_it).type == object_text; ++block_object_it); + } + else + { + if (end_pos > pos) + { + TextBlock block; + block.start = pos; + block.end = end_pos; + blocks.push_back(block); + } + } + + pos = end_pos; + } + + return blocks; +} + +void SpanLayout::SetAlign(SpanAlign align) +{ + alignment = align; +} + +void SpanLayout::LayoutLines(Canvas* canvas, double max_width) +{ + lines.clear(); + if (objects.empty()) + return; + + layout_cache.metrics = {}; + layout_cache.object_index = -1; + + CurrentLine current_line; + std::vector blocks = FindTextBlocks(); + for (std::vector::size_type block_index = 0; block_index < blocks.size(); block_index++) + { + if (objects[current_line.object_index].type == object_text) + LayoutText(canvas, blocks, block_index, current_line, max_width); + else + LayoutBlock(current_line, max_width, blocks, block_index); + } + NextLine(current_line); +} + +void SpanLayout::LayoutBlock(CurrentLine& current_line, double max_width, std::vector& blocks, std::vector::size_type block_index) +{ + if (objects[current_line.object_index].float_type == float_none) + LayoutInlineBlock(current_line, max_width, blocks, block_index); + else + LayoutFloatBlock(current_line, max_width); + + current_line.object_index++; +} + +void SpanLayout::LayoutInlineBlock(CurrentLine& current_line, double max_width, std::vector& blocks, std::vector::size_type block_index) +{ + Size size; + LineSegment segment; + if (objects[current_line.object_index].type == object_image) + { + size = Size(objects[current_line.object_index].image->GetWidth(), objects[current_line.object_index].image->GetHeight()); + segment.type = object_image; + segment.image = objects[current_line.object_index].image; + } + else if (objects[current_line.object_index].type == object_component) + { + size = objects[current_line.object_index].component->GetSize(); + segment.type = object_component; + segment.component = objects[current_line.object_index].component; + } + + if (current_line.x_position + size.width > max_width) + NextLine(current_line); + + segment.x_position = current_line.x_position; + segment.width = size.width; + segment.start = blocks[block_index].start; + segment.end = blocks[block_index].end; + segment.id = objects[current_line.object_index].id; + segment.ascender = size.height - objects[current_line.object_index].baseline_offset; + current_line.cur_line.segments.push_back(segment); + current_line.cur_line.height = std::max(current_line.cur_line.height, size.height + objects[current_line.object_index].baseline_offset); + current_line.cur_line.ascender = std::max(current_line.cur_line.ascender, segment.ascender); + current_line.x_position += size.width; +} + +void SpanLayout::LayoutFloatBlock(CurrentLine& current_line, double max_width) +{ + FloatBox floatbox; + floatbox.type = objects[current_line.object_index].type; + floatbox.image = objects[current_line.object_index].image; + floatbox.component = objects[current_line.object_index].component; + floatbox.id = objects[current_line.object_index].id; + if (objects[current_line.object_index].type == object_image) + floatbox.rect = Rect::xywh(0, current_line.y_position, floatbox.image->GetWidth(), floatbox.image->GetHeight()); + else if (objects[current_line.object_index].type == object_component) + floatbox.rect = Rect::xywh(0, current_line.y_position, floatbox.component->GetWidth(), floatbox.component->GetHeight()); + + if (objects[current_line.object_index].float_type == float_left) + floats_left.push_back(FloatBoxLeft(floatbox, max_width)); + else + floats_right.push_back(FloatBoxRight(floatbox, max_width)); + + ReflowLine(current_line, max_width); +} + +void SpanLayout::ReflowLine(CurrentLine& step, double max_width) +{ +} + +SpanLayout::FloatBox SpanLayout::FloatBoxLeft(FloatBox box, double max_width) +{ + return FloatBoxAny(box, max_width, floats_left); +} + +SpanLayout::FloatBox SpanLayout::FloatBoxRight(FloatBox box, double max_width) +{ + return FloatBoxAny(box, max_width, floats_right); +} + +SpanLayout::FloatBox SpanLayout::FloatBoxAny(FloatBox box, double max_width, const std::vector& floats1) +{ + bool restart; + do + { + restart = false; + for (size_t i = 0; i < floats1.size(); i++) + { + double top = std::max(floats1[i].rect.top(), box.rect.top()); + double bottom = std::min(floats1[i].rect.bottom(), box.rect.bottom()); + if (bottom > top && box.rect.left() < floats1[i].rect.right()) + { + Size s = box.rect.size(); + box.rect.x = floats1[i].rect.x; + box.rect.width = s.width; + + if (!BoxFitsOnLine(box, max_width)) + { + box.rect.x = 0; + box.rect.width = s.width; + box.rect.y = floats1[i].rect.bottom(); + box.rect.height = s.height; + restart = true; + break; + } + } + } + } while (restart); + return box; +} + +bool SpanLayout::BoxFitsOnLine(const FloatBox& box, double max_width) +{ + for (size_t i = 0; i < floats_right.size(); i++) + { + double top = std::max(floats_right[i].rect.top(), box.rect.top()); + double bottom = std::min(floats_right[i].rect.bottom(), box.rect.bottom()); + if (bottom > top) + { + if (box.rect.right() + floats_right[i].rect.right() > max_width) + return false; + } + } + return true; +} + +void SpanLayout::LayoutText(Canvas* canvas, std::vector blocks, std::vector::size_type block_index, CurrentLine& current_line, double max_width) +{ + TextSizeResult text_size_result = FindTextSize(canvas, blocks[block_index], current_line.object_index); + current_line.object_index += text_size_result.objects_traversed; + + current_line.cur_line.width = current_line.x_position; + + if (IsNewline(blocks[block_index])) + { + current_line.cur_line.height = std::max(current_line.cur_line.height, text_size_result.height); + current_line.cur_line.ascender = std::max(current_line.cur_line.ascender, text_size_result.ascender); + NextLine(current_line); + } + else + { + if (!FitsOnLine(current_line.x_position, text_size_result, max_width) && !IsWhitespace(blocks[block_index])) + { + if (LargerThanLine(text_size_result, max_width)) + { + // force line breaks to make it fit + ForcePlaceLineSegments(current_line, text_size_result, max_width); + } + else + { + NextLine(current_line); + PlaceLineSegments(current_line, text_size_result); + } + } + else + { + PlaceLineSegments(current_line, text_size_result); + } + } +} + +void SpanLayout::NextLine(CurrentLine& current_line) +{ + current_line.cur_line.width = current_line.x_position; + for (std::vector::reverse_iterator it = current_line.cur_line.segments.rbegin(); it != current_line.cur_line.segments.rend(); ++it) + { + LineSegment& segment = *it; + if (segment.type == object_text) + { + std::string s = text.substr(segment.start, segment.end - segment.start); + if (s.find_first_not_of(" \t\r\n") != std::string::npos) + { + current_line.cur_line.width = segment.x_position + segment.width; + break; + } + else + { + // We remove the width so that GetRect() reports the correct sizes + segment.width = 0; + } + } + else + { + current_line.cur_line.width = segment.x_position + segment.width; + break; + } + } + + double height = current_line.cur_line.height; + lines.push_back(current_line.cur_line); + current_line.cur_line = Line(); + current_line.x_position = 0; + current_line.y_position += height; +} + +void SpanLayout::PlaceLineSegments(CurrentLine& current_line, TextSizeResult& text_size_result) +{ + for (std::vector::iterator it = text_size_result.segments.begin(); it != text_size_result.segments.end(); ++it) + { + LineSegment segment = *it; + segment.x_position += current_line.x_position; + current_line.cur_line.segments.push_back(segment); + } + current_line.x_position += text_size_result.width; + current_line.cur_line.height = std::max(current_line.cur_line.height, text_size_result.height); + current_line.cur_line.ascender = std::max(current_line.cur_line.ascender, text_size_result.ascender); +} + +void SpanLayout::ForcePlaceLineSegments(CurrentLine& current_line, TextSizeResult& text_size_result, double max_width) +{ + if (current_line.x_position != 0) + NextLine(current_line); + + // to do: do this properly - for now we just place the entire block on one line + PlaceLineSegments(current_line, text_size_result); +} + +bool SpanLayout::IsNewline(const TextBlock& block) +{ + return block.start != block.end && text[block.start] == '\n'; +} + +bool SpanLayout::IsWhitespace(const TextBlock& block) +{ + return block.start != block.end && text[block.start] == ' '; +} + +bool SpanLayout::FitsOnLine(double x_position, const TextSizeResult& text_size_result, double max_width) +{ + return x_position + text_size_result.width <= max_width; +} + +bool SpanLayout::LargerThanLine(const TextSizeResult& text_size_result, double max_width) +{ + return text_size_result.width > max_width; +} + +void SpanLayout::AlignRight(double max_width) +{ + for (std::vector::size_type line_index = 0; line_index < lines.size(); line_index++) + { + Line& line = lines[line_index]; + double offset = max_width - line.width; + if (offset < 0) offset = 0; + + for (std::vector::size_type segment_index = 0; segment_index < line.segments.size(); segment_index++) + { + LineSegment& segment = line.segments[segment_index]; + segment.x_position += offset; + } + } +} + +void SpanLayout::AlignCenter(double max_width) +{ + for (std::vector::size_type line_index = 0; line_index < lines.size(); line_index++) + { + Line& line = lines[line_index]; + double offset = (max_width - line.width) / 2; + if (offset < 0) offset = 0; + + for (std::vector::size_type segment_index = 0; segment_index < line.segments.size(); segment_index++) + { + LineSegment& segment = line.segments[segment_index]; + segment.x_position += offset; + } + } +} + +void SpanLayout::AlignJustify(double max_width) +{ + // Note, we do not justify the last line + for (std::vector::size_type line_index = 0; line_index + 1 < lines.size(); line_index++) + { + Line& line = lines[line_index]; + double offset = max_width - line.width; + if (offset < 0) offset = 0; + + if (line.segments.size() <= 1) // Do not justify line if only one word exists + continue; + + for (std::vector::size_type segment_index = 0; segment_index < line.segments.size(); segment_index++) + { + LineSegment& segment = line.segments[segment_index]; + segment.x_position += (offset * segment_index) / (line.segments.size() - 1); + } + } +} + +Size SpanLayout::FindPreferredSize(Canvas* canvas) +{ + LayoutLines(canvas, 0x70000000); // Feed it with a very long length so it ends up on one line + return GetRect().size(); +} + +void SpanLayout::SetSelectionRange(std::string::size_type start, std::string::size_type end) +{ + sel_start = start; + sel_end = end; + if (sel_end < sel_start) + sel_end = sel_start; +} + +void SpanLayout::SetSelectionColors(const Colorf& foreground, const Colorf& background) +{ + sel_foreground = foreground; + sel_background = background; +} + +void SpanLayout::ShowCursor() +{ + cursor_visible = true; +} + +void SpanLayout::HideCursor() +{ + cursor_visible = false; +} + +void SpanLayout::SetCursorPos(std::string::size_type pos) +{ + cursor_pos = pos; +} + +void SpanLayout::SetCursorOverwriteMode(bool enable) +{ + cursor_overwrite_mode = enable; +} + +void SpanLayout::SetCursorColor(const Colorf& color) +{ + cursor_color = color; +} + +std::string SpanLayout::GetCombinedText() const +{ + return text; +} + +void SpanLayout::SetComponentGeometry() +{ + double x = position.x; + double y = position.y; + for (size_t i = 0; i < lines.size(); i++) + { + for (size_t j = 0; j < lines[i].segments.size(); j++) + { + if (lines[i].segments[j].type == object_component) + { + Point pos(x + lines[i].segments[j].x_position, y + lines[i].ascender - lines[i].segments[j].ascender); + Size size = lines[i].segments[j].component->GetSize(); + Rect rect(pos, size); + lines[i].segments[j].component->SetFrameGeometry(rect); + } + } + y += lines[i].height; + } +} + +double SpanLayout::GetFirstBaselineOffset() +{ + if (!lines.empty()) + { + return lines.front().ascender; + } + else + { + return 0; + } +} + +double SpanLayout::GetLastBaselineOffset() +{ + if (!lines.empty()) + { + double y = 0; + for (size_t i = 0; i + 1 < lines.size(); i++) + y += lines[i].height; + return y + lines.back().ascender; + } + else + { + return 0; + } +} diff --git a/src/core/theme.cpp b/src/core/theme.cpp new file mode 100644 index 000000000..cd553315c --- /dev/null +++ b/src/core/theme.cpp @@ -0,0 +1,412 @@ + +#include "core/theme.h" +#include "core/widget.h" +#include "core/canvas.h" + +void WidgetStyle::SetBool(const std::string& state, const std::string& propertyName, bool value) +{ + StyleProperties[state][propertyName] = value; +} + +void WidgetStyle::SetInt(const std::string& state, const std::string& propertyName, int value) +{ + StyleProperties[state][propertyName] = value; +} + +void WidgetStyle::SetDouble(const std::string& state, const std::string& propertyName, double value) +{ + StyleProperties[state][propertyName] = value; +} + +void WidgetStyle::SetString(const std::string& state, const std::string& propertyName, const std::string& value) +{ + StyleProperties[state][propertyName] = value; +} + +void WidgetStyle::SetColor(const std::string& state, const std::string& propertyName, const Colorf& value) +{ + StyleProperties[state][propertyName] = value; +} + +const WidgetStyle::PropertyVariant* WidgetStyle::FindProperty(const std::string& state, const std::string& propertyName) const +{ + const WidgetStyle* style = this; + do + { + // Look for property in the specific state + auto stateIt = style->StyleProperties.find(state); + if (stateIt != style->StyleProperties.end()) + { + auto it = stateIt->second.find(propertyName); + if (it != stateIt->second.end()) + return &it->second; + } + + // Fall back to the widget main style + if (state != std::string()) + { + stateIt = style->StyleProperties.find(std::string()); + if (stateIt != style->StyleProperties.end()) + { + auto it = stateIt->second.find(propertyName); + if (it != stateIt->second.end()) + return &it->second; + } + } + + style = style->ParentStyle; + } while (style); + return nullptr; +} + +bool WidgetStyle::GetBool(const std::string& state, const std::string& propertyName) const +{ + const PropertyVariant* prop = FindProperty(state, propertyName); + return prop ? std::get(*prop) : false; +} + +int WidgetStyle::GetInt(const std::string& state, const std::string& propertyName) const +{ + const PropertyVariant* prop = FindProperty(state, propertyName); + return prop ? std::get(*prop) : 0; +} + +double WidgetStyle::GetDouble(const std::string& state, const std::string& propertyName) const +{ + const PropertyVariant* prop = FindProperty(state, propertyName); + return prop ? std::get(*prop) : 0.0; +} + +std::string WidgetStyle::GetString(const std::string& state, const std::string& propertyName) const +{ + const PropertyVariant* prop = FindProperty(state, propertyName); + return prop ? std::get(*prop) : std::string(); +} + +Colorf WidgetStyle::GetColor(const std::string& state, const std::string& propertyName) const +{ + const PropertyVariant* prop = FindProperty(state, propertyName); + return prop ? std::get(*prop) : Colorf::transparent(); +} + +///////////////////////////////////////////////////////////////////////////// + +void BasicWidgetStyle::Paint(Widget* widget, Canvas* canvas, Size size) +{ + Colorf bgcolor = widget->GetStyleColor("background-color"); + if (bgcolor.a > 0.0f) + canvas->fillRect(Rect::xywh(0.0, 0.0, size.width, size.height), bgcolor); + + Colorf borderleft = widget->GetStyleColor("border-left-color"); + Colorf bordertop = widget->GetStyleColor("border-top-color"); + Colorf borderright = widget->GetStyleColor("border-right-color"); + Colorf borderbottom = widget->GetStyleColor("border-bottom-color"); + + double borderwidth = widget->GridFitSize(1.0); + + if (bordertop.a > 0.0f) + canvas->fillRect(Rect::xywh(0.0, 0.0, size.width, borderwidth), bordertop); + if (borderbottom.a > 0.0f) + canvas->fillRect(Rect::xywh(0.0, size.height - borderwidth, size.width, borderwidth), borderbottom); + if (borderleft.a > 0.0f) + canvas->fillRect(Rect::xywh(0.0, 0.0, borderwidth, size.height), borderleft); + if (borderright.a > 0.0f) + canvas->fillRect(Rect::xywh(size.width - borderwidth, 0.0, borderwidth, size.height), borderright); +} + +///////////////////////////////////////////////////////////////////////////// + +static std::unique_ptr CurrentTheme; + +WidgetStyle* WidgetTheme::RegisterStyle(std::unique_ptr widgetStyle, const std::string& widgetClass) +{ + auto& style = Styles[widgetClass]; + style = std::move(widgetStyle); + return style.get(); +} + +WidgetStyle* WidgetTheme::GetStyle(const std::string& widgetClass) +{ + auto it = Styles.find(widgetClass); + return it != Styles.end() ? it->second.get() : nullptr; +} + +void WidgetTheme::SetTheme(std::unique_ptr theme) +{ + CurrentTheme = std::move(theme); +} + +WidgetTheme* WidgetTheme::GetTheme() +{ + return CurrentTheme.get(); +} + +///////////////////////////////////////////////////////////////////////////// + +DarkWidgetTheme::DarkWidgetTheme() +{ + auto widget = RegisterStyle(std::make_unique(), "widget"); + auto textlabel = RegisterStyle(std::make_unique(widget), "textlabel"); + auto pushbutton = RegisterStyle(std::make_unique(widget), "pushbutton"); + auto lineedit = RegisterStyle(std::make_unique(widget), "lineedit"); + auto textedit = RegisterStyle(std::make_unique(widget), "textedit"); + auto listview = RegisterStyle(std::make_unique(widget), "listview"); + auto scrollbar = RegisterStyle(std::make_unique(widget), "scrollbar"); + auto tabbar = RegisterStyle(std::make_unique(widget), "tabbar"); + auto tabbar_tab = RegisterStyle(std::make_unique(widget), "tabbar-tab"); + auto tabbar_spacer = RegisterStyle(std::make_unique(widget), "tabbar-spacer"); + auto tabwidget_stack = RegisterStyle(std::make_unique(widget), "tabwidget-stack"); + auto checkbox_label = RegisterStyle(std::make_unique(widget), "checkbox-label"); + auto menubar = RegisterStyle(std::make_unique(widget), "menubar"); + auto menubaritem = RegisterStyle(std::make_unique(widget), "menubaritem"); + auto menu = RegisterStyle(std::make_unique(widget), "menu"); + auto menuitem = RegisterStyle(std::make_unique(widget), "menuitem"); + auto toolbar = RegisterStyle(std::make_unique(widget), "toolbar"); + auto toolbarbutton = RegisterStyle(std::make_unique(widget), "toolbarbutton"); + auto statusbar = RegisterStyle(std::make_unique(widget), "statusbar"); + + widget->SetString("font-family", "NotoSans"); + widget->SetColor("color", Colorf::fromRgba8(226, 223, 219)); + widget->SetColor("window-background", Colorf::fromRgba8(51, 51, 51)); + widget->SetColor("window-border", Colorf::fromRgba8(51, 51, 51)); + widget->SetColor("window-caption-color", Colorf::fromRgba8(33, 33, 33)); + widget->SetColor("window-caption-text-color", Colorf::fromRgba8(226, 223, 219)); + + pushbutton->SetDouble("noncontent-left", 10.0); + pushbutton->SetDouble("noncontent-top", 5.0); + pushbutton->SetDouble("noncontent-right", 10.0); + pushbutton->SetDouble("noncontent-bottom", 5.0); + pushbutton->SetColor("background-color", Colorf::fromRgba8(68, 68, 68)); + pushbutton->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); + pushbutton->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); + pushbutton->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); + pushbutton->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); + pushbutton->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78)); + pushbutton->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88)); + + lineedit->SetDouble("noncontent-left", 5.0); + lineedit->SetDouble("noncontent-top", 3.0); + lineedit->SetDouble("noncontent-right", 5.0); + lineedit->SetDouble("noncontent-bottom", 3.0); + lineedit->SetColor("background-color", Colorf::fromRgba8(38, 38, 38)); + lineedit->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); + lineedit->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); + lineedit->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); + lineedit->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); + lineedit->SetColor("selection-color", Colorf::fromRgba8(100, 100, 100)); + lineedit->SetColor("no-focus-selection-color", Colorf::fromRgba8(68, 68, 68)); + + textedit->SetDouble("noncontent-left", 8.0); + textedit->SetDouble("noncontent-top", 8.0); + textedit->SetDouble("noncontent-right", 8.0); + textedit->SetDouble("noncontent-bottom", 8.0); + textedit->SetColor("background-color", Colorf::fromRgba8(38, 38, 38)); + textedit->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); + textedit->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); + textedit->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); + textedit->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); + + listview->SetDouble("noncontent-left", 10.0); + listview->SetDouble("noncontent-top", 10.0); + listview->SetDouble("noncontent-right", 3.0); + listview->SetDouble("noncontent-bottom", 10.0); + listview->SetColor("background-color", Colorf::fromRgba8(38, 38, 38)); + listview->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); + listview->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); + listview->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); + listview->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); + listview->SetColor("selection-color", Colorf::fromRgba8(100, 100, 100)); + + scrollbar->SetColor("track-color", Colorf::fromRgba8(33, 33, 33)); + scrollbar->SetColor("thumb-color", Colorf::fromRgba8(58, 58, 58)); + + tabbar->SetDouble("spacer-left", 20.0); + tabbar->SetDouble("spacer-right", 20.0); + tabbar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33)); + + 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_spacer->SetDouble("noncontent-bottom", 1.0); + tabbar_spacer->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); + + tabwidget_stack->SetDouble("noncontent-left", 20.0); + tabwidget_stack->SetDouble("noncontent-top", 5.0); + tabwidget_stack->SetDouble("noncontent-right", 20.0); + tabwidget_stack->SetDouble("noncontent-bottom", 5.0); + + checkbox_label->SetColor("checked-outer-border-color", Colorf::fromRgba8(100, 100, 100)); + checkbox_label->SetColor("checked-inner-border-color", Colorf::fromRgba8(51, 51, 51)); + checkbox_label->SetColor("checked-color", Colorf::fromRgba8(226, 223, 219)); + checkbox_label->SetColor("unchecked-outer-border-color", Colorf::fromRgba8(99, 99, 99)); + checkbox_label->SetColor("unchecked-inner-border-color", Colorf::fromRgba8(51, 51, 51)); + + menubar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33)); + toolbar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33)); + statusbar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33)); + + toolbarbutton->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78)); + toolbarbutton->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88)); + + menubaritem->SetColor("color", Colorf::fromRgba8(226, 223, 219)); + menubaritem->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78)); + menubaritem->SetColor("hover", "color", Colorf::fromRgba8(0, 0, 0)); + menubaritem->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88)); + menubaritem->SetColor("down", "color", Colorf::fromRgba8(0, 0, 0)); + + menu->SetDouble("noncontent-left", 5.0); + menu->SetDouble("noncontent-top", 5.0); + menu->SetDouble("noncontent-right", 5.0); + menu->SetDouble("noncontent-bottom", 5.0); + menu->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); + menu->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); + menu->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); + menu->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); + + menuitem->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78)); + menuitem->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88)); +} + +///////////////////////////////////////////////////////////////////////////// + +LightWidgetTheme::LightWidgetTheme() +{ + auto widget = RegisterStyle(std::make_unique(), "widget"); + auto textlabel = RegisterStyle(std::make_unique(widget), "textlabel"); + auto pushbutton = RegisterStyle(std::make_unique(widget), "pushbutton"); + auto lineedit = RegisterStyle(std::make_unique(widget), "lineedit"); + auto textedit = RegisterStyle(std::make_unique(widget), "textedit"); + auto listview = RegisterStyle(std::make_unique(widget), "listview"); + auto scrollbar = RegisterStyle(std::make_unique(widget), "scrollbar"); + auto tabbar = RegisterStyle(std::make_unique(widget), "tabbar"); + auto tabbar_tab = RegisterStyle(std::make_unique(widget), "tabbar-tab"); + auto tabbar_spacer = RegisterStyle(std::make_unique(widget), "tabbar-spacer"); + auto tabwidget_stack = RegisterStyle(std::make_unique(widget), "tabwidget-stack"); + auto checkbox_label = RegisterStyle(std::make_unique(widget), "checkbox-label"); + auto menubar = RegisterStyle(std::make_unique(widget), "menubar"); + auto menubaritem = RegisterStyle(std::make_unique(widget), "menubaritem"); + auto menu = RegisterStyle(std::make_unique(widget), "menu"); + auto menuitem = RegisterStyle(std::make_unique(widget), "menuitem"); + + widget->SetString("font-family", "NotoSans"); + widget->SetColor("color", Colorf::fromRgba8(0, 0, 0)); + widget->SetColor("window-background", Colorf::fromRgba8(240, 240, 240)); + widget->SetColor("window-border", Colorf::fromRgba8(100, 100, 100)); + widget->SetColor("window-caption-color", Colorf::fromRgba8(70, 70, 70)); + widget->SetColor("window-caption-text-color", Colorf::fromRgba8(226, 223, 219)); + + pushbutton->SetDouble("noncontent-left", 10.0); + pushbutton->SetDouble("noncontent-top", 5.0); + pushbutton->SetDouble("noncontent-right", 10.0); + pushbutton->SetDouble("noncontent-bottom", 5.0); + pushbutton->SetColor("background-color", Colorf::fromRgba8(210, 210, 210)); + pushbutton->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); + pushbutton->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); + pushbutton->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); + pushbutton->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); + pushbutton->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200)); + pushbutton->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190)); + + lineedit->SetDouble("noncontent-left", 5.0); + lineedit->SetDouble("noncontent-top", 3.0); + lineedit->SetDouble("noncontent-right", 5.0); + lineedit->SetDouble("noncontent-bottom", 3.0); + lineedit->SetColor("background-color", Colorf::fromRgba8(255, 255, 255)); + lineedit->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); + lineedit->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); + lineedit->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); + lineedit->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); + lineedit->SetColor("selection-color", Colorf::fromRgba8(210, 210, 255)); + lineedit->SetColor("no-focus-selection-color", Colorf::fromRgba8(240, 240, 255)); + + textedit->SetDouble("noncontent-left", 8.0); + textedit->SetDouble("noncontent-top", 8.0); + textedit->SetDouble("noncontent-right", 8.0); + textedit->SetDouble("noncontent-bottom", 8.0); + textedit->SetColor("background-color", Colorf::fromRgba8(255, 255, 255)); + textedit->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); + textedit->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); + textedit->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); + textedit->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); + + listview->SetDouble("noncontent-left", 10.0); + listview->SetDouble("noncontent-top", 10.0); + listview->SetDouble("noncontent-right", 3.0); + listview->SetDouble("noncontent-bottom", 10.0); + listview->SetColor("background-color", Colorf::fromRgba8(230, 230, 230)); + listview->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); + listview->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); + listview->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); + listview->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); + listview->SetColor("selection-color", Colorf::fromRgba8(200, 200, 200)); + + scrollbar->SetColor("track-color", Colorf::fromRgba8(210, 210, 220)); + scrollbar->SetColor("thumb-color", Colorf::fromRgba8(180, 180, 180)); + + tabbar->SetDouble("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)); +} diff --git a/src/core/timer.cpp b/src/core/timer.cpp new file mode 100644 index 000000000..5ebe2612f --- /dev/null +++ b/src/core/timer.cpp @@ -0,0 +1,45 @@ + +#include "core/timer.h" +#include "core/widget.h" +#include "window/window.h" + +Timer::Timer(Widget* owner) : OwnerObj(owner) +{ + PrevTimerObj = owner->FirstTimerObj; + if (PrevTimerObj) + PrevTimerObj->PrevTimerObj = this; + owner->FirstTimerObj = this; +} + +Timer::~Timer() +{ + Stop(); + + if (PrevTimerObj) + PrevTimerObj->NextTimerObj = NextTimerObj; + if (NextTimerObj) + NextTimerObj->PrevTimerObj = PrevTimerObj; + if (OwnerObj->FirstTimerObj == this) + OwnerObj->FirstTimerObj = NextTimerObj; +} + +void Timer::Start(int timeoutMilliseconds, bool repeat) +{ + Stop(); + + TimerId = DisplayWindow::StartTimer(timeoutMilliseconds, [=]() { + if (!repeat) + Stop(); + if (FuncExpired) + FuncExpired(); + }); +} + +void Timer::Stop() +{ + if (TimerId != 0) + { + DisplayWindow::StopTimer(TimerId); + TimerId = 0; + } +} diff --git a/src/core/truetypefont.cpp b/src/core/truetypefont.cpp new file mode 100644 index 000000000..757929e3e --- /dev/null +++ b/src/core/truetypefont.cpp @@ -0,0 +1,1201 @@ + +// #define DUMP_GLYPH + +#include "core/truetypefont.h" +#include "core/pathfill.h" +#include +#include +#include + +#ifdef DUMP_GLYPH +#include +#endif + +TrueTypeFont::TrueTypeFont(std::shared_ptr initdata, int ttcFontIndex) : data(std::move(initdata)) +{ + if (data->size() > 0x7fffffff) + throw std::runtime_error("TTF file is larger than 2 gigabytes!"); + + TrueTypeFileReader reader(data->data(), data->size()); + ttf_Tag versionTag = reader.ReadTag(); + reader.Seek(0); + + if (memcmp(versionTag.data(), "ttcf", 4) == 0) // TTC header + { + ttcHeader.Load(reader); + if (ttcFontIndex >= (int)ttcHeader.numFonts) + throw std::runtime_error("TTC font index out of bounds"); + reader.Seek(ttcHeader.tableDirectoryOffsets[ttcFontIndex]); + } + + directory.Load(reader); + + if (!directory.ContainsTTFOutlines()) + throw std::runtime_error("Only truetype outline fonts are supported"); + + // Load required tables: + + reader = directory.GetReader(data->data(), data->size(), "head"); + head.Load(reader); + + reader = directory.GetReader(data->data(), data->size(), "hhea"); + hhea.Load(reader); + + reader = directory.GetReader(data->data(), data->size(), "maxp"); + maxp.Load(reader); + + reader = directory.GetReader(data->data(), data->size(), "hmtx"); + hmtx.Load(hhea, maxp, reader); + + reader = directory.GetReader(data->data(), data->size(), "name"); + name.Load(reader); + + reader = directory.GetReader(data->data(), data->size(), "OS/2"); + os2.Load(reader); + + reader = directory.GetReader(data->data(), data->size(), "cmap"); + cmap.Load(reader); + + LoadCharacterMapEncoding(reader); + + // Load TTF Outlines: + + reader = directory.GetReader(data->data(), data->size(), "loca"); + loca.Load(head, maxp, reader); + + glyf = directory.GetRecord("glyf"); + +#ifdef DUMP_GLYPH + LoadGlyph(GetGlyphIndex('6'), 13.0); +#endif +} + +TrueTypeTextMetrics TrueTypeFont::GetTextMetrics(double height) const +{ + double scale = height / head.unitsPerEm; + + TrueTypeTextMetrics metrics; + metrics.ascender = os2.sTypoAscender * scale; + metrics.descender = os2.sTypoDescender * scale; + metrics.lineGap = os2.sTypoLineGap * scale; + return metrics; +} + +TrueTypeGlyph TrueTypeFont::LoadGlyph(uint32_t glyphIndex, double height) const +{ + double scale = height / head.unitsPerEm; + double scaleX = 3.0f; + double scaleY = -1.0f; + + ttf_uint16 advanceWidth = 0; + ttf_int16 lsb = 0; + if (glyphIndex >= hhea.numberOfHMetrics) + { + advanceWidth = hmtx.hMetrics[hhea.numberOfHMetrics - 1].advanceWidth; + lsb = hmtx.leftSideBearings[glyphIndex - hhea.numberOfHMetrics]; + } + else + { + advanceWidth = hmtx.hMetrics[glyphIndex].advanceWidth; + lsb = hmtx.hMetrics[glyphIndex].lsb; + } + + // Glyph is missing if the offset is the same as the next glyph (0 bytes glyph length) + bool missing = glyphIndex + 1 < loca.offsets.size() ? loca.offsets[glyphIndex] == loca.offsets[glyphIndex + 1] : false; + if (missing) + { + TrueTypeGlyph glyph; + + // TBD: gridfit or not? + glyph.advanceWidth = (int)std::round(advanceWidth * scale * scaleX); + glyph.leftSideBearing = (int)std::round(lsb * scale * scaleX); + glyph.yOffset = 0; + + return glyph; + } + + TTF_SimpleGlyph g; + LoadGlyph(g, glyphIndex); + + // Create glyph path: + PathFillDesc path; + path.fill_mode = PathFillMode::winding; + + int startPoint = 0; + int numberOfContours = (int)g.endPtsOfContours.size(); + for (int i = 0; i < numberOfContours; i++) + { + int endPoint = g.endPtsOfContours[i]; + if (endPoint < startPoint) + throw std::runtime_error("Invalid glyph"); + + bool prevIsControlPoint; + bool isControlPoint = !(g.flags[endPoint] & TTF_ON_CURVE_POINT); + bool nextIsControlPoint = !(g.flags[startPoint] & TTF_ON_CURVE_POINT); + if (isControlPoint) + { + Point nextpoint(g.points[startPoint].x, g.points[startPoint].y); + if (nextIsControlPoint) + { + Point curpoint(g.points[endPoint].x, g.points[endPoint].y); + Point midpoint = (curpoint + nextpoint) / 2; + path.MoveTo(midpoint * scale); + prevIsControlPoint = isControlPoint; + } + else + { + path.MoveTo(Point(g.points[startPoint].x, g.points[startPoint].y) * scale); + prevIsControlPoint = false; + } + } + else + { + path.MoveTo(Point(g.points[endPoint].x, g.points[endPoint].y) * scale); + prevIsControlPoint = isControlPoint; + } + + int pos = startPoint; + while (pos <= endPoint) + { + int nextpos = pos + 1 <= endPoint ? pos + 1 : startPoint; + bool isControlPoint = !(g.flags[pos] & TTF_ON_CURVE_POINT); + bool nextIsControlPoint = !(g.flags[nextpos] & TTF_ON_CURVE_POINT); + Point curpoint(g.points[pos].x, g.points[pos].y); + if (isControlPoint) + { + Point nextpoint(g.points[nextpos].x, g.points[nextpos].y); + if (nextIsControlPoint) + { + Point midpoint = (curpoint + nextpoint) / 2; + path.BezierTo(curpoint * scale, midpoint * scale); + prevIsControlPoint = isControlPoint; + pos++; + } + else + { + path.BezierTo(curpoint * scale, nextpoint * scale); + prevIsControlPoint = false; + pos += 2; + } + } + else + { + if (!prevIsControlPoint) + path.LineTo(curpoint * scale); + prevIsControlPoint = isControlPoint; + pos++; + } + } + path.Close(); + + startPoint = endPoint + 1; + } + + // Transform and find the final bounding box + Point bboxMin, bboxMax; + if (!path.subpaths.front().points.empty()) + { + bboxMin = path.subpaths.front().points.front(); + bboxMax = path.subpaths.front().points.front(); + bboxMin.x *= scaleX; + bboxMin.y *= scaleY; + bboxMax.x *= scaleX; + bboxMax.y *= scaleY; + for (auto& subpath : path.subpaths) + { + for (auto& point : subpath.points) + { + point.x *= scaleX; + point.y *= scaleY; + bboxMin.x = std::min(bboxMin.x, point.x); + bboxMin.y = std::min(bboxMin.y, point.y); + bboxMax.x = std::max(bboxMax.x, point.x); + bboxMax.y = std::max(bboxMax.y, point.y); + } + } + } + + bboxMin.x = std::floor(bboxMin.x); + bboxMin.y = std::floor(bboxMin.y); + + // Reposition glyph to bitmap so it begins at (0,0) for our bitmap + for (auto& subpath : path.subpaths) + { + for (auto& point : subpath.points) + { + point.x -= bboxMin.x; + point.y -= bboxMin.y; + } + } + +#ifdef DUMP_GLYPH + std::string svgxmlstart = R"( + + + +)"; + + std::ofstream out("c:\\development\\glyph.svg"); + out << svgxmlstart; + + for (auto& subpath : path.subpaths) + { + size_t pos = 0; + out << "M" << subpath.points[pos].x << " " << subpath.points[pos].y << " "; + pos++; + for (PathFillCommand cmd : subpath.commands) + { + int count = 0; + if (cmd == PathFillCommand::line) + { + out << "L"; + count = 1; + } + else if (cmd == PathFillCommand::quadradic) + { + out << "Q"; + count = 2; + } + else if (cmd == PathFillCommand::cubic) + { + out << "C"; + count = 3; + } + + for (int i = 0; i < count; i++) + { + out << subpath.points[pos].x << " " << subpath.points[pos].y << " "; + pos++; + } + } + if (subpath.closed) + out << "Z"; + } + + out << svgxmlend; + out.close(); +#endif + + TrueTypeGlyph glyph; + + // Rasterize the glyph + glyph.width = (int)std::floor(bboxMax.x - bboxMin.x) + 1; + glyph.height = (int)std::floor(bboxMax.y - bboxMin.y) + 1; + glyph.grayscale.reset(new uint8_t[glyph.width * glyph.height]); + uint8_t* grayscale = glyph.grayscale.get(); + path.Rasterize(grayscale, glyph.width, glyph.height); + + // TBD: gridfit or not? + glyph.advanceWidth = (int)std::round(advanceWidth * scale * scaleX); + glyph.leftSideBearing = (int)std::round(bboxMin.x); + glyph.yOffset = (int)std::round(bboxMin.y); + + return glyph; +} + +void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compositeDepth) const +{ + if (glyphIndex >= loca.offsets.size()) + throw std::runtime_error("Glyph index out of bounds"); + + TrueTypeFileReader reader = glyf.GetReader(data->data(), data->size()); + 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(); + + if (numberOfContours > 0) // Simple glyph + { + int pointsOffset = (int)g.points.size(); + for (ttf_uint16 i = 0; i < numberOfContours; i++) + g.endPtsOfContours.push_back(pointsOffset + reader.ReadUInt16()); + + ttf_uint16 instructionLength = reader.ReadUInt16(); + std::vector instructions; + instructions.resize(instructionLength); + reader.Read(instructions.data(), instructions.size()); + + int numPoints = (int)g.endPtsOfContours.back() - pointsOffset + 1; + g.points.resize(pointsOffset + numPoints); + + while (g.flags.size() < g.points.size()) + { + ttf_uint8 flag = reader.ReadUInt8(); + if (flag & TTF_REPEAT_FLAG) + { + ttf_uint8 repeatcount = reader.ReadUInt8(); + for (ttf_uint8 i = 0; i < repeatcount; i++) + g.flags.push_back(flag); + } + g.flags.push_back(flag); + } + + for (int i = pointsOffset; i < pointsOffset + numPoints; i++) + { + if (g.flags[i] & TTF_X_SHORT_VECTOR) + { + ttf_int16 x = reader.ReadUInt8(); + g.points[i].x = (float)((g.flags[i] & TTF_X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) ? x : -x); + } + else if (g.flags[i] & TTF_X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) + { + g.points[i].x = 0.0f; + } + else + { + g.points[i].x = (float)reader.ReadInt16(); + } + } + + for (int i = pointsOffset; i < pointsOffset + numPoints; i++) + { + if (g.flags[i] & TTF_Y_SHORT_VECTOR) + { + ttf_int16 y = reader.ReadUInt8(); + g.points[i].y = (float)((g.flags[i] & TTF_Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) ? y : -y); + } + else if (g.flags[i] & TTF_Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) + { + g.points[i].y = 0.0f; + } + else + { + g.points[i].y = (float)reader.ReadInt16(); + } + } + + // Convert from relative coordinates to absolute + for (int i = pointsOffset + 1; i < pointsOffset + numPoints; i++) + { + g.points[i].x += g.points[i - 1].x; + g.points[i].y += g.points[i - 1].y; + } + } + else if (numberOfContours < 0) // Composite glyph + { + if (compositeDepth == 8) + throw std::runtime_error("Composite glyph recursion exceeded"); + + int parentPointsOffset = (int)g.points.size(); + + bool weHaveInstructions = false; + while (true) + { + ttf_uint16 flags = reader.ReadUInt16(); + ttf_uint16 childGlyphIndex = reader.ReadUInt16(); + + int argument1, argument2; + if (flags & TTF_ARG_1_AND_2_ARE_WORDS) + { + if (flags & TTF_ARGS_ARE_XY_VALUES) + { + argument1 = reader.ReadInt16(); + argument2 = reader.ReadInt16(); + } + else + { + argument1 = reader.ReadUInt16(); + argument2 = reader.ReadUInt16(); + } + } + else + { + if (flags & TTF_ARGS_ARE_XY_VALUES) + { + argument1 = reader.ReadInt8(); + argument2 = reader.ReadInt8(); + } + else + { + argument1 = reader.ReadUInt8(); + argument2 = reader.ReadUInt8(); + } + } + + float mat2x2[4]; + bool transform = true; + if (flags & TTF_WE_HAVE_A_SCALE) + { + float scale = F2DOT14_ToFloat(reader.ReadF2DOT14()); + mat2x2[0] = scale; + mat2x2[1] = 0; + mat2x2[2] = 0; + mat2x2[3] = scale; + } + else if (flags & TTF_WE_HAVE_AN_X_AND_Y_SCALE) + { + mat2x2[0] = F2DOT14_ToFloat(reader.ReadF2DOT14()); + mat2x2[1] = 0; + mat2x2[2] = 0; + mat2x2[3] = F2DOT14_ToFloat(reader.ReadF2DOT14()); + } + else if (flags & TTF_WE_HAVE_A_TWO_BY_TWO) + { + mat2x2[0] = F2DOT14_ToFloat(reader.ReadF2DOT14()); + mat2x2[1] = F2DOT14_ToFloat(reader.ReadF2DOT14()); + mat2x2[2] = F2DOT14_ToFloat(reader.ReadF2DOT14()); + mat2x2[3] = F2DOT14_ToFloat(reader.ReadF2DOT14()); + } + else + { + transform = false; + } + + int childPointsOffset = (int)g.points.size(); + LoadGlyph(g, childGlyphIndex, compositeDepth + 1); + + if (transform) + { + for (int i = childPointsOffset; i < 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]; + g.points[i].x = x; + g.points[i].y = y; + } + } + + float dx, dy; + + if (flags & TTF_ARGS_ARE_XY_VALUES) + { + dx = (float)argument1; + dy = (float)argument2; + + // Spec states we must fall back to TTF_UNSCALED_COMPONENT_OFFSET if both flags are set + if ((flags & (TTF_SCALED_COMPONENT_OFFSET | TTF_UNSCALED_COMPONENT_OFFSET)) == TTF_SCALED_COMPONENT_OFFSET) + { + float x = dx * mat2x2[0] + dy * mat2x2[1]; + float y = dx * mat2x2[2] + dy * mat2x2[3]; + dx = x; + dy = y; + } + + if (flags & TTF_ROUND_XY_TO_GRID) + { + // To do: round the offset to the pixel grid + } + } + else + { + int parentPointIndex = parentPointsOffset + argument1; + int childPointIndex = childPointsOffset + argument2; + + if ((size_t)parentPointIndex >= g.points.size() || (size_t)childPointIndex >= g.points.size()) + throw std::runtime_error("Invalid glyph offset"); + + dx = g.points[parentPointIndex].x - g.points[childPointIndex].x; + dy = g.points[parentPointIndex].y - g.points[childPointIndex].y; + } + + for (int i = childPointsOffset; i < g.points.size(); i++) + { + g.points[i].x += dx; + g.points[i].y += dy; + } + + if (flags & TTF_USE_MY_METRICS) + { + // To do: this affects lsb + rsb calculations somehow + } + + if (flags & TTF_WE_HAVE_INSTRUCTIONS) + { + weHaveInstructions = true; + } + + if (!(flags & TTF_MORE_COMPONENTS)) + break; + } + + if (weHaveInstructions) + { + ttf_uint16 instructionLength = reader.ReadUInt16(); + std::vector instructions; + instructions.resize(instructionLength); + reader.Read(instructions.data(), instructions.size()); + } + } +} + +float TrueTypeFont::F2DOT14_ToFloat(ttf_F2DOT14 v) +{ + int a = ((ttf_int16)v) >> 14; + int b = (v & 0x3fff); + return a + b / (float)0x4000; +} + +uint32_t TrueTypeFont::GetGlyphIndex(uint32_t c) const +{ + auto it = std::lower_bound(Ranges.begin(), Ranges.end(), c, [](const TTF_GlyphRange& range, uint32_t c) { return range.endCharCode < c; }); + if (it != Ranges.end() && c >= it->startCharCode && c <= it->endCharCode) + { + return it->startGlyphID + (c - it->startCharCode); + } + + it = std::lower_bound(ManyToOneRanges.begin(), ManyToOneRanges.end(), c, [](const TTF_GlyphRange& range, uint32_t c) { return range.endCharCode < c; }); + if (it != ManyToOneRanges.end() && c >= it->startCharCode && c <= it->endCharCode) + { + return it->startGlyphID; + } + return 0; +} + +void TrueTypeFont::LoadCharacterMapEncoding(TrueTypeFileReader& reader) +{ + // Look for the best encoding available that we support + + TTF_EncodingRecord record; + if (!record.subtableOffset) record = cmap.GetEncoding(3, 12); + if (!record.subtableOffset) record = cmap.GetEncoding(0, 4); + if (!record.subtableOffset) record = cmap.GetEncoding(3, 1); + if (!record.subtableOffset) record = cmap.GetEncoding(0, 3); + if (!record.subtableOffset) + throw std::runtime_error("No supported cmap encoding found in truetype file"); + + reader.Seek(record.subtableOffset); + + ttf_uint16 format = reader.ReadUInt16(); + if (format == 4) + { + TTF_CMapSubtable4 subformat; + subformat.Load(reader); + + for (uint16_t i = 0; i < subformat.segCount; i++) + { + ttf_uint16 startCode = subformat.startCode[i]; + ttf_uint16 endCode = subformat.endCode[i]; + ttf_uint16 idDelta = subformat.idDelta[i]; + ttf_uint16 idRangeOffset = subformat.idRangeOffsets[i]; + if (idRangeOffset == 0) + { + ttf_uint16 glyphId = startCode + idDelta; // Note: relies on modulo 65536 + + TTF_GlyphRange range; + range.startCharCode = startCode; + range.endCharCode = endCode; + range.startGlyphID = glyphId; + Ranges.push_back(range); + } + else if (startCode <= endCode) + { + TTF_GlyphRange range; + range.startCharCode = startCode; + bool firstGlyph = true; + 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()) + { + int glyphId = subformat.glyphIdArray[offset]; + if (firstGlyph) + { + range.startGlyphID = glyphId; + firstGlyph = false; + } + else if (range.startGlyphID + (c - range.startCharCode) != glyphId) + { + range.endCharCode = c - 1; + Ranges.push_back(range); + range.startCharCode = c; + range.startGlyphID = glyphId; + } + } + } + if (!firstGlyph) + { + range.endCharCode = endCode; + Ranges.push_back(range); + } + } + } + } + else if (format == 12 || format == 3) + { + TTF_CMapSubtable12 subformat; + subformat.Load(reader); + Ranges = std::move(subformat.groups); + } + else if (format == 13 || format == 3) + { + TTF_CMapSubtable13 subformat; + subformat.Load(reader); + ManyToOneRanges = std::move(subformat.groups); + } +} + +std::vector TrueTypeFont::GetFontNames(const std::shared_ptr& data) +{ + if (data->size() > 0x7fffffff) + throw std::runtime_error("TTF file is larger than 2 gigabytes!"); + + TrueTypeFileReader reader(data->data(), data->size()); + ttf_Tag versionTag = reader.ReadTag(); + reader.Seek(0); + + std::vector names; + if (memcmp(versionTag.data(), "ttcf", 4) == 0) // TTC header + { + TTC_Header ttcHeader; + ttcHeader.Load(reader); + + for (size_t i = 0; i < ttcHeader.tableDirectoryOffsets.size(); i++) + { + reader.Seek(ttcHeader.tableDirectoryOffsets[i]); + + TTF_TableDirectory directory; + directory.Load(reader); + + TTF_NamingTable name; + auto name_reader = directory.GetReader(data->data(), data->size(), "name"); + name.Load(name_reader); + + names.push_back(name.GetFontName()); + } + } + else + { + TTF_TableDirectory directory; + directory.Load(reader); + + TTF_NamingTable name; + auto name_reader = directory.GetReader(data->data(), data->size(), "name"); + name.Load(name_reader); + + names.push_back(name.GetFontName()); + } + return names; +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_CMapSubtable0::Load(TrueTypeFileReader& reader) +{ + length = reader.ReadUInt16(); + language = reader.ReadUInt16(); + glyphIdArray.resize(256); + reader.Read(glyphIdArray.data(), glyphIdArray.size()); +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_CMapSubtable4::Load(TrueTypeFileReader& reader) +{ + length = reader.ReadUInt16(); + language = reader.ReadUInt16(); + + segCount = reader.ReadUInt16() / 2; + ttf_uint16 searchRange = reader.ReadUInt16(); + ttf_uint16 entrySelector = reader.ReadUInt16(); + ttf_uint16 rangeShift = reader.ReadUInt16(); + + endCode.reserve(segCount); + startCode.reserve(segCount); + idDelta.reserve(segCount); + idRangeOffsets.reserve(segCount); + for (ttf_uint16 i = 0; i < segCount; i++) endCode.push_back(reader.ReadUInt16()); + reservedPad = reader.ReadUInt16(); + for (ttf_uint16 i = 0; i < segCount; i++) startCode.push_back(reader.ReadUInt16()); + for (ttf_uint16 i = 0; i < segCount; i++) idDelta.push_back(reader.ReadInt16()); + for (ttf_uint16 i = 0; i < segCount; i++) idRangeOffsets.push_back(reader.ReadUInt16()); + + int glyphIdArraySize = ((int)length - (8 + (int)segCount * 4) * sizeof(ttf_uint16)) / 2; + if (glyphIdArraySize < 0) + throw std::runtime_error("Invalid TTF cmap subtable 4 length"); + glyphIdArray.reserve(glyphIdArraySize); + for (int i = 0; i < glyphIdArraySize; i++) glyphIdArray.push_back(reader.ReadUInt16()); +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_CMapSubtable12::Load(TrueTypeFileReader& reader) +{ + reserved = reader.ReadUInt16(); + length = reader.ReadUInt32(); + language = reader.ReadUInt32(); + numGroups = reader.ReadUInt32(); + for (ttf_uint32 i = 0; i < numGroups; i++) + { + TTF_GlyphRange range; + range.startCharCode = reader.ReadUInt32(); + range.endCharCode = reader.ReadUInt32(); + range.startGlyphID = reader.ReadUInt32(); + groups.push_back(range); + } +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_TableRecord::Load(TrueTypeFileReader& reader) +{ + tableTag = reader.ReadTag(); + checksum = reader.ReadUInt32(); + offset = reader.ReadOffset32(); + length = reader.ReadUInt32(); +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_TableDirectory::Load(TrueTypeFileReader& reader) +{ + sfntVersion = reader.ReadUInt32(); + 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(); + + for (ttf_uint16 i = 0; i < numTables; i++) + { + TTF_TableRecord record; + record.Load(reader); + tableRecords.push_back(record); + } +} + +///////////////////////////////////////////////////////////////////////////// + +void TTC_Header::Load(TrueTypeFileReader& reader) +{ + ttcTag = reader.ReadTag(); + majorVersion = reader.ReadUInt16(); + minorVersion = reader.ReadUInt16(); + + if (!(majorVersion == 1 && minorVersion == 0) && !(majorVersion == 2 && minorVersion == 0)) + throw std::runtime_error("Unsupported TTC header version"); + + numFonts = reader.ReadUInt32(); + for (ttf_uint16 i = 0; i < numFonts; i++) + { + tableDirectoryOffsets.push_back(reader.ReadOffset32()); + } + + if (majorVersion == 2 && minorVersion == 0) + { + dsigTag = reader.ReadUInt32(); + dsigLength = reader.ReadUInt32(); + dsigOffset = reader.ReadUInt32(); + } +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_CMap::Load(TrueTypeFileReader& reader) +{ + version = reader.ReadUInt16(); + numTables = reader.ReadUInt16(); + + for (ttf_uint16 i = 0; i < numTables; i++) + { + TTF_EncodingRecord record; + record.platformID = reader.ReadUInt16(); + record.encodingID = reader.ReadUInt16(); + record.subtableOffset = reader.ReadOffset32(); + encodingRecords.push_back(record); + } +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_FontHeader::Load(TrueTypeFileReader& reader) +{ + majorVersion = reader.ReadUInt16(); + minorVersion = reader.ReadUInt16(); + fontRevision = reader.ReadFixed(); + checksumAdjustment = reader.ReadUInt32(); + magicNumber = reader.ReadUInt32(); + flags = reader.ReadUInt16(); + unitsPerEm = reader.ReadUInt16(); + created = reader.ReadLONGDATETIME(); + modified = reader.ReadLONGDATETIME(); + xMin = reader.ReadInt16(); + yMin = reader.ReadInt16(); + xMax = reader.ReadInt16(); + yMax = reader.ReadInt16(); + macStyle = reader.ReadUInt16(); + lowestRecPPEM = reader.ReadUInt16(); + fontDirectionHint = reader.ReadInt16(); + indexToLocFormat = reader.ReadInt16(); + glyphDataFormat = reader.ReadInt16(); +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_HorizontalHeader::Load(TrueTypeFileReader& reader) +{ + majorVersion = reader.ReadUInt16(); + minorVersion = reader.ReadUInt16(); + ascender = reader.ReadFWORD(); + descender = reader.ReadFWORD(); + lineGap = reader.ReadFWORD(); + advanceWidthMax = reader.ReadUFWORD(); + minLeftSideBearing = reader.ReadFWORD(); + minRightSideBearing = reader.ReadFWORD(); + xMaxExtent = reader.ReadFWORD(); + caretSlopeRise = reader.ReadInt16(); + caretSlopeRun = reader.ReadInt16(); + caretOffset = reader.ReadInt16(); + reserved0 = reader.ReadInt16(); + reserved1 = reader.ReadInt16(); + reserved2 = reader.ReadInt16(); + reserved3 = reader.ReadInt16(); + metricDataFormat = reader.ReadInt16(); + numberOfHMetrics = reader.ReadUInt16(); +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_HorizontalMetrics::Load(const TTF_HorizontalHeader& hhea, const TTF_MaximumProfile& maxp, TrueTypeFileReader& reader) +{ + for (ttf_uint16 i = 0; i < hhea.numberOfHMetrics; i++) + { + longHorMetric metric; + metric.advanceWidth = reader.ReadUInt16(); + metric.lsb = reader.ReadInt16(); + hMetrics.push_back(metric); + } + + int count = (int)maxp.numGlyphs - (int)hhea.numberOfHMetrics; + if (count < 0) + throw std::runtime_error("Invalid TTF file"); + + for (int i = 0; i < count; i++) + { + leftSideBearings.push_back(reader.ReadInt16()); + } +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_MaximumProfile::Load(TrueTypeFileReader& reader) +{ + version = reader.ReadVersion16Dot16(); + numGlyphs = reader.ReadUInt16(); + + if (version == (1 << 16)) // v1 only + { + maxPoints = reader.ReadUInt16(); + maxContours = reader.ReadUInt16(); + maxCompositePoints = reader.ReadUInt16(); + maxCompositeContours = reader.ReadUInt16(); + maxZones = reader.ReadUInt16(); + maxTwilightPoints = reader.ReadUInt16(); + maxStorage = reader.ReadUInt16(); + maxFunctionDefs = reader.ReadUInt16(); + maxInstructionDefs = reader.ReadUInt16(); + maxStackElements = reader.ReadUInt16(); + maxSizeOfInstructions = reader.ReadUInt16(); + maxComponentElements = reader.ReadUInt16(); + maxComponentDepth = reader.ReadUInt16(); + } +} + +///////////////////////////////////////////////////////////////////////////// + +static std::string unicode_to_utf8(unsigned int value) +{ + char text[8]; + + if ((value < 0x80) && (value > 0)) + { + text[0] = (char)value; + text[1] = 0; + } + else if (value < 0x800) + { + text[0] = (char)(0xc0 | (value >> 6)); + text[1] = (char)(0x80 | (value & 0x3f)); + text[2] = 0; + } + else if (value < 0x10000) + { + text[0] = (char)(0xe0 | (value >> 12)); + text[1] = (char)(0x80 | ((value >> 6) & 0x3f)); + text[2] = (char)(0x80 | (value & 0x3f)); + text[3] = 0; + } + else if (value < 0x200000) + { + text[0] = (char)(0xf0 | (value >> 18)); + text[1] = (char)(0x80 | ((value >> 12) & 0x3f)); + text[2] = (char)(0x80 | ((value >> 6) & 0x3f)); + text[3] = (char)(0x80 | (value & 0x3f)); + text[4] = 0; + } + else if (value < 0x4000000) + { + text[0] = (char)(0xf8 | (value >> 24)); + text[1] = (char)(0x80 | ((value >> 18) & 0x3f)); + text[2] = (char)(0x80 | ((value >> 12) & 0x3f)); + text[3] = (char)(0x80 | ((value >> 6) & 0x3f)); + text[4] = (char)(0x80 | (value & 0x3f)); + text[5] = 0; + } + else if (value < 0x80000000) + { + text[0] = (char)(0xfc | (value >> 30)); + text[1] = (char)(0x80 | ((value >> 24) & 0x3f)); + text[2] = (char)(0x80 | ((value >> 18) & 0x3f)); + text[3] = (char)(0x80 | ((value >> 12) & 0x3f)); + text[4] = (char)(0x80 | ((value >> 6) & 0x3f)); + text[5] = (char)(0x80 | (value & 0x3f)); + text[6] = 0; + } + else + { + text[0] = 0; // Invalid wchar value + } + return text; +} + +void TTF_NamingTable::Load(TrueTypeFileReader& reader) +{ + version = reader.ReadUInt16(); + count = reader.ReadUInt16(); + storageOffset = reader.ReadOffset16(); + for (ttf_uint16 i = 0; i < count; i++) + { + NameRecord record; + record.platformID = reader.ReadUInt16(); + record.encodingID = reader.ReadUInt16(); + record.languageID = reader.ReadUInt16(); + record.nameID = reader.ReadUInt16(); + record.length = reader.ReadUInt16(); + record.stringOffset = reader.ReadOffset16(); + nameRecord.push_back(record); + } + + if (version == 1) + { + langTagCount = reader.ReadUInt16(); + for (ttf_uint16 i = 0; i < langTagCount; i++) + { + LangTagRecord record; + record.length = reader.ReadUInt16(); + record.langTagOffset = reader.ReadOffset16(); + langTagRecord.push_back(record); + } + } + + for (NameRecord& record : nameRecord) + { + if (record.length > 0 && record.platformID == 3 && record.encodingID == 1) + { + reader.Seek(storageOffset + record.stringOffset); + ttf_uint16 ucs2len = record.length / 2; + for (ttf_uint16 i = 0; i < ucs2len; i++) + { + record.text += unicode_to_utf8(reader.ReadUInt16()); + } + } + } +} + +TTCFontName TTF_NamingTable::GetFontName() const +{ + TTCFontName fname; + for (const auto& record : nameRecord) + { + if (record.nameID == 1) fname.FamilyName = record.text; + if (record.nameID == 2) fname.SubfamilyName = record.text; + if (record.nameID == 3) fname.UniqueID = record.text; + if (record.nameID == 4) fname.FullName = record.text; + if (record.nameID == 5) fname.VersionString = record.text; + if (record.nameID == 6) fname.PostscriptName = record.text; + } + return fname; +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_OS2Windows::Load(TrueTypeFileReader& reader) +{ + version = reader.ReadUInt16(); + xAvgCharWidth = reader.ReadInt16(); + usWeightClass = reader.ReadUInt16(); + usWidthClass = reader.ReadUInt16(); + fsType = reader.ReadUInt16(); + ySubscriptXSize = reader.ReadInt16(); + ySubscriptYSize = reader.ReadInt16(); + ySubscriptXOffset = reader.ReadInt16(); + ySubscriptYOffset = reader.ReadInt16(); + ySuperscriptXSize = reader.ReadInt16(); + ySuperscriptYSize = reader.ReadInt16(); + ySuperscriptXOffset = reader.ReadInt16(); + ySuperscriptYOffset = reader.ReadInt16(); + yStrikeoutSize = reader.ReadInt16(); + yStrikeoutPosition = reader.ReadInt16(); + sFamilyClass = reader.ReadInt16(); + for (int i = 0; i < 10; i++) + { + panose[i] = reader.ReadUInt8(); + } + ulUnicodeRange1 = reader.ReadUInt32(); + ulUnicodeRange2 = reader.ReadUInt32(); + ulUnicodeRange3 = reader.ReadUInt32(); + ulUnicodeRange4 = reader.ReadUInt32(); + achVendID = reader.ReadTag(); + fsSelection = reader.ReadUInt16(); + usFirstCharIndex = reader.ReadUInt16(); + usLastCharIndex = reader.ReadUInt16(); + sTypoAscender = reader.ReadInt16(); + sTypoDescender = reader.ReadInt16(); + sTypoLineGap = reader.ReadInt16(); + if (!reader.IsEndOfData()) // may be missing in v0 due to a bug in Apple's TTF documentation + { + usWinAscent = reader.ReadUInt16(); + usWinDescent = reader.ReadUInt16(); + } + if (version >= 1) + { + ulCodePageRange1 = reader.ReadUInt32(); + ulCodePageRange2 = reader.ReadUInt32(); + } + if (version >= 2) + { + sxHeight = reader.ReadInt16(); + sCapHeight = reader.ReadInt16(); + usDefaultChar = reader.ReadUInt16(); + usBreakChar = reader.ReadUInt16(); + usMaxContext = reader.ReadUInt16(); + } + if (version >= 5) + { + usLowerOpticalPointSize = reader.ReadUInt16(); + usUpperOpticalPointSize = reader.ReadUInt16(); + } +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_IndexToLocation::Load(const TTF_FontHeader& head, const TTF_MaximumProfile& maxp, TrueTypeFileReader& reader) +{ + int count = (int)maxp.numGlyphs + 1; + if (head.indexToLocFormat == 0) + { + offsets.reserve(count); + for (int i = 0; i < count; i++) + { + offsets.push_back((ttf_Offset32)reader.ReadOffset16() * 2); + } + } + else + { + offsets.reserve(count); + for (int i = 0; i < count; i++) + { + offsets.push_back(reader.ReadOffset32()); + } + } +} + +///////////////////////////////////////////////////////////////////////////// + +ttf_uint8 TrueTypeFileReader::ReadUInt8() +{ + ttf_uint8 v; Read(&v, 1); return v; +} + +ttf_uint16 TrueTypeFileReader::ReadUInt16() +{ + ttf_uint8 v[2]; Read(v, 2); return (((ttf_uint16)v[0]) << 8) | (ttf_uint16)v[1]; +} + +ttf_uint24 TrueTypeFileReader::ReadUInt24() +{ + ttf_uint8 v[3]; Read(v, 3); return (((ttf_uint32)v[0]) << 16) | (((ttf_uint32)v[1]) << 8) | (ttf_uint32)v[2]; +} + +ttf_uint32 TrueTypeFileReader::ReadUInt32() +{ + ttf_uint8 v[4]; Read(v, 4); return (((ttf_uint32)v[0]) << 24) | (((ttf_uint32)v[1]) << 16) | (((ttf_uint32)v[2]) << 8) | (ttf_uint32)v[3]; +} + +ttf_int8 TrueTypeFileReader::ReadInt8() +{ + return ReadUInt8(); +} + +ttf_int16 TrueTypeFileReader::ReadInt16() +{ + return ReadUInt16(); +} + +ttf_int32 TrueTypeFileReader::ReadInt32() +{ + return ReadUInt32(); +} + +ttf_Fixed TrueTypeFileReader::ReadFixed() +{ + return ReadUInt32(); +} + +ttf_UFWORD TrueTypeFileReader::ReadUFWORD() +{ + return ReadUInt16(); +} + +ttf_FWORD TrueTypeFileReader::ReadFWORD() +{ + return ReadUInt16(); +} + +ttf_F2DOT14 TrueTypeFileReader::ReadF2DOT14() +{ + return ReadUInt16(); +} + +ttf_LONGDATETIME TrueTypeFileReader::ReadLONGDATETIME() +{ + ttf_uint8 v[8]; Read(v, 8); + return + (((ttf_LONGDATETIME)v[0]) << 56) | (((ttf_LONGDATETIME)v[1]) << 48) | (((ttf_LONGDATETIME)v[2]) << 40) | (((ttf_LONGDATETIME)v[3]) << 32) | + (((ttf_LONGDATETIME)v[4]) << 24) | (((ttf_LONGDATETIME)v[5]) << 16) | (((ttf_LONGDATETIME)v[6]) << 8) | (ttf_LONGDATETIME)v[7]; +} + +ttf_Tag TrueTypeFileReader::ReadTag() +{ + ttf_Tag v; Read(v.data(), v.size()); return v; +} + +ttf_Offset16 TrueTypeFileReader::ReadOffset16() +{ + return ReadUInt16(); +} + +ttf_Offset24 TrueTypeFileReader::ReadOffset24() +{ + return ReadUInt24(); +} + +ttf_Offset32 TrueTypeFileReader::ReadOffset32() +{ + return ReadUInt32(); +} + +ttf_Version16Dot16 TrueTypeFileReader::ReadVersion16Dot16() +{ + return ReadUInt32(); +} + +void TrueTypeFileReader::Seek(size_t newpos) +{ + if (newpos > size) + throw std::runtime_error("Invalid TTF file"); + + pos = newpos; +} + +void TrueTypeFileReader::Read(void* output, size_t count) +{ + if (pos + count > size) + throw std::runtime_error("Unexpected end of TTF file"); + memcpy(output, data + pos, count); + pos += count; +} diff --git a/src/core/truetypefont.h b/src/core/truetypefont.h new file mode 100644 index 000000000..53d8837fc --- /dev/null +++ b/src/core/truetypefont.h @@ -0,0 +1,527 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +typedef uint8_t ttf_uint8; +typedef uint16_t ttf_uint16; +typedef uint32_t ttf_uint24; // 24-bit unsigned integer +typedef uint32_t ttf_uint32; + +typedef int8_t ttf_int8; +typedef int16_t ttf_int16; +typedef int32_t ttf_int32; + +typedef uint32_t ttf_Fixed; // 32-bit signed fixed-point number (16.16) +typedef uint16_t ttf_UFWORD; // uint16 that describes a quantity in font design units +typedef int16_t ttf_FWORD; // int16 that describes a quantity in font design units +typedef uint16_t ttf_F2DOT14; // 16-bit signed fixed number with the low 14 bits of fraction (2.14) +typedef uint64_t ttf_LONGDATETIME; // number of seconds since 12:00 midnight, January 1, 1904, UTC + +typedef std::array ttf_Tag; // 4 byte identifier + +typedef uint16_t ttf_Offset16; // Short offset to a table, same as uint16, NULL offset = 0x0000 +typedef uint32_t ttf_Offset24; // 24-bit offset to a table, same as uint24, NULL offset = 0x000000 +typedef uint32_t ttf_Offset32; // Long offset to a table, same as uint32, NULL offset = 0x00000000 + +typedef uint32_t ttf_Version16Dot16; // Packed 32-bit value with major and minor version numbers + +class TrueTypeFileReader +{ +public: + TrueTypeFileReader() = default; + TrueTypeFileReader(const void* data, size_t size) : data(static_cast(data)), size(size) { } + + bool IsEndOfData() const { return pos == size; } + + ttf_uint8 ReadUInt8(); + ttf_uint16 ReadUInt16(); + ttf_uint24 ReadUInt24(); + ttf_uint32 ReadUInt32(); + ttf_int8 ReadInt8(); + ttf_int16 ReadInt16(); + ttf_int32 ReadInt32(); + ttf_Fixed ReadFixed(); + ttf_UFWORD ReadUFWORD(); + ttf_FWORD ReadFWORD(); + ttf_F2DOT14 ReadF2DOT14(); + ttf_LONGDATETIME ReadLONGDATETIME(); + ttf_Tag ReadTag(); + ttf_Offset16 ReadOffset16(); + ttf_Offset24 ReadOffset24(); + ttf_Offset32 ReadOffset32(); + ttf_Version16Dot16 ReadVersion16Dot16(); + + void Seek(size_t newpos); + void Read(void* output, size_t count); + +private: + const uint8_t* data = nullptr; + size_t size = 0; + size_t pos = 0; +}; + +struct TTF_TableRecord +{ + ttf_Tag tableTag = {}; + ttf_uint32 checksum = {}; + ttf_Offset32 offset = {}; + ttf_uint32 length = {}; + + void Load(TrueTypeFileReader& reader); + + TrueTypeFileReader GetReader(const void* filedata, size_t filesize) const + { + if ((size_t)offset + length > filesize) + throw std::runtime_error("Invalid TTF table directory record"); + + return TrueTypeFileReader((uint8_t*)filedata + offset, length); + } +}; + +struct TTF_TableDirectory +{ + ttf_uint32 sfntVersion = {}; + ttf_uint16 numTables = {}; + std::vector tableRecords; + + // To do: Apple TTF fonts allow 'true' and 'typ1' for sfntVersion as well. + bool ContainsTTFOutlines() const { return sfntVersion == 0x00010000; } + bool ContainsCFFData() const { return sfntVersion == 0x4F54544F; } + + void Load(TrueTypeFileReader& reader); + + const TTF_TableRecord& GetRecord(const char* tag) const + { + for (const auto& record : tableRecords) + { + if (memcmp(record.tableTag.data(), tag, 4) == 0) + { + return record; + } + } + throw std::runtime_error(std::string("Could not find required '") + tag + "' table entry"); + } + + TrueTypeFileReader GetReader(const void* filedata, size_t filesize, const char* tag) const + { + return GetRecord(tag).GetReader(filedata, filesize); + } +}; + +struct TTC_Header +{ + ttf_Tag ttcTag = {}; + ttf_uint16 majorVersion = {}; + ttf_uint16 minorVersion = {}; + ttf_uint32 numFonts = {}; + std::vector tableDirectoryOffsets; + + // majorVersion = 2, minorVersion = 0: + ttf_uint32 dsigTag = {}; + ttf_uint32 dsigLength = {}; + ttf_uint32 dsigOffset = {}; + + void Load(TrueTypeFileReader& reader); +}; + +struct TTF_EncodingRecord +{ + ttf_uint16 platformID = {}; + ttf_uint16 encodingID = {}; + ttf_Offset32 subtableOffset = {}; +}; + +struct TTF_CMap // 'cmap' Character to glyph mapping +{ + ttf_uint16 version = {}; + ttf_uint16 numTables = {}; + std::vector encodingRecords; // [numTables] + + void Load(TrueTypeFileReader& reader); + + TTF_EncodingRecord GetEncoding(ttf_uint16 platformID, ttf_uint16 encodingID) const + { + for (const TTF_EncodingRecord& record : encodingRecords) + { + if (record.platformID == platformID && record.encodingID == encodingID) + { + return record; + } + } + return {}; + } +}; + +struct TTF_GlyphRange +{ + ttf_uint32 startCharCode = 0; + ttf_uint32 endCharCode = 0; + ttf_uint32 startGlyphID = 0; +}; + +struct TTF_CMapSubtable0 // Byte encoding table +{ + ttf_uint16 length = {}; + ttf_uint16 language = {}; + std::vector glyphIdArray; + + void Load(TrueTypeFileReader& reader); +}; + +struct TTF_CMapSubtable4 // Segment mapping to delta values (U+0000 to U+FFFF) +{ + ttf_uint16 length = {}; + ttf_uint16 language = {}; + ttf_uint16 segCount = {}; + std::vector endCode; + ttf_uint16 reservedPad = {}; + std::vector startCode; + std::vector idDelta; + std::vector idRangeOffsets; + std::vector glyphIdArray; + + void Load(TrueTypeFileReader& reader); +}; + +struct TTF_CMapSubtable12 // Segmented coverage (U+0000 to U+10FFFF) +{ + ttf_uint16 reserved; + ttf_uint32 length; + ttf_uint32 language; + ttf_uint32 numGroups; + std::vector groups; + + void Load(TrueTypeFileReader& reader); +}; + +typedef TTF_CMapSubtable12 TTF_CMapSubtable13; // Many-to-one range mappings + +struct TTF_FontHeader // 'head' Font header +{ + ttf_uint16 majorVersion = {}; + ttf_uint16 minorVersion = {}; + ttf_Fixed fontRevision = {}; + ttf_uint32 checksumAdjustment = {}; + ttf_uint32 magicNumber = {}; + ttf_uint16 flags = {}; + ttf_uint16 unitsPerEm = {}; + ttf_LONGDATETIME created = {}; + ttf_LONGDATETIME modified = {}; + ttf_int16 xMin = {}; + ttf_int16 yMin = {}; + ttf_int16 xMax = {}; + ttf_int16 yMax = {}; + ttf_uint16 macStyle = {}; + ttf_uint16 lowestRecPPEM = {}; + ttf_int16 fontDirectionHint = {}; + ttf_int16 indexToLocFormat = {}; + ttf_int16 glyphDataFormat = {}; + + void Load(TrueTypeFileReader& reader); +}; + +struct TTF_HorizontalHeader // 'hhea' Horizontal header +{ + ttf_uint16 majorVersion = {}; + ttf_uint16 minorVersion = {}; + ttf_FWORD ascender = {}; + ttf_FWORD descender = {}; + ttf_FWORD lineGap = {}; + ttf_UFWORD advanceWidthMax = {}; + ttf_FWORD minLeftSideBearing = {}; + ttf_FWORD minRightSideBearing = {}; + ttf_FWORD xMaxExtent = {}; + ttf_int16 caretSlopeRise = {}; + ttf_int16 caretSlopeRun = {}; + ttf_int16 caretOffset = {}; + ttf_int16 reserved0 = {}; + ttf_int16 reserved1 = {}; + ttf_int16 reserved2 = {}; + ttf_int16 reserved3 = {}; + ttf_int16 metricDataFormat = {}; + ttf_uint16 numberOfHMetrics = {}; + + void Load(TrueTypeFileReader& reader); +}; + +struct TTF_MaximumProfile; + +struct TTF_HorizontalMetrics // 'hmtx' Horizontal metrics +{ + struct longHorMetric + { + ttf_uint16 advanceWidth = {}; + ttf_int16 lsb = {}; + }; + std::vector hMetrics; // [hhea.numberOfHMetrics] + std::vector leftSideBearings; // [maxp.numGlyphs - hhea.numberOfHMetrics] + + void Load(const TTF_HorizontalHeader& hhea, const TTF_MaximumProfile& maxp, TrueTypeFileReader& reader); +}; + +struct TTF_MaximumProfile // 'maxp' Maximum profile +{ + // v0.5 and v1: + ttf_Version16Dot16 version = {}; + ttf_uint16 numGlyphs = {}; + + // v1 only: + ttf_uint16 maxPoints = {}; + ttf_uint16 maxContours = {}; + ttf_uint16 maxCompositePoints = {}; + ttf_uint16 maxCompositeContours = {}; + ttf_uint16 maxZones = {}; + ttf_uint16 maxTwilightPoints = {}; + ttf_uint16 maxStorage = {}; + ttf_uint16 maxFunctionDefs = {}; + ttf_uint16 maxInstructionDefs = {}; + ttf_uint16 maxStackElements = {}; + ttf_uint16 maxSizeOfInstructions = {}; + ttf_uint16 maxComponentElements = {}; + ttf_uint16 maxComponentDepth = {}; + + void Load(TrueTypeFileReader& reader); +}; + +class TTCFontName +{ +public: + std::string FamilyName; // Arial + std::string SubfamilyName; // Regular + std::string FullName; // Arial Regular + std::string UniqueID; + std::string VersionString; + std::string PostscriptName; +}; + +struct TTF_NamingTable // 'name' Naming table +{ + struct NameRecord + { + ttf_uint16 platformID = {}; + ttf_uint16 encodingID = {}; + ttf_uint16 languageID = {}; + ttf_uint16 nameID = {}; + ttf_uint16 length = {}; + ttf_Offset16 stringOffset = {}; + std::string text; + }; + + struct LangTagRecord + { + ttf_uint16 length = {}; + ttf_Offset16 langTagOffset = {}; + }; + + // v0 and v1: + ttf_uint16 version = {}; + ttf_uint16 count = {}; + ttf_Offset16 storageOffset = {}; + std::vector nameRecord; // [count] + + // v1 only: + ttf_uint16 langTagCount = {}; + std::vector langTagRecord; // [langTagCount] + + void Load(TrueTypeFileReader& reader); + + TTCFontName GetFontName() const; +}; + +struct TTF_OS2Windows // 'OS/2' Windows specific metrics +{ + ttf_uint16 version = {}; + ttf_int16 xAvgCharWidth = {}; + ttf_uint16 usWeightClass = {}; + ttf_uint16 usWidthClass = {}; + ttf_uint16 fsType = {}; + ttf_int16 ySubscriptXSize = {}; + ttf_int16 ySubscriptYSize = {}; + ttf_int16 ySubscriptXOffset = {}; + ttf_int16 ySubscriptYOffset = {}; + ttf_int16 ySuperscriptXSize = {}; + ttf_int16 ySuperscriptYSize = {}; + ttf_int16 ySuperscriptXOffset = {}; + ttf_int16 ySuperscriptYOffset = {}; + ttf_int16 yStrikeoutSize = {}; + ttf_int16 yStrikeoutPosition = {}; + ttf_int16 sFamilyClass = {}; + ttf_uint8 panose[10] = {}; + ttf_uint32 ulUnicodeRange1 = {}; + ttf_uint32 ulUnicodeRange2 = {}; + ttf_uint32 ulUnicodeRange3 = {}; + ttf_uint32 ulUnicodeRange4 = {}; + ttf_Tag achVendID = {}; + ttf_uint16 fsSelection = {}; + ttf_uint16 usFirstCharIndex = {}; + ttf_uint16 usLastCharIndex = {}; + ttf_int16 sTypoAscender = {}; + ttf_int16 sTypoDescender = {}; + ttf_int16 sTypoLineGap = {}; + ttf_uint16 usWinAscent = {}; // may be missing in v0 due to bugs in Apple docs + ttf_uint16 usWinDescent = {}; // may be missing in v0 due to bugs in Apple docs + ttf_uint32 ulCodePageRange1 = {}; // v1 + ttf_uint32 ulCodePageRange2 = {}; + ttf_int16 sxHeight = {}; // v2, v3 and v4 + ttf_int16 sCapHeight = {}; + ttf_uint16 usDefaultChar = {}; + ttf_uint16 usBreakChar = {}; + ttf_uint16 usMaxContext = {}; + ttf_uint16 usLowerOpticalPointSize = {}; // v5 + ttf_uint16 usUpperOpticalPointSize = {}; + + void Load(TrueTypeFileReader& reader); +}; + +// Simple glyph flags: +#define TTF_ON_CURVE_POINT 0x01 +#define TTF_X_SHORT_VECTOR 0x02 +#define TTF_Y_SHORT_VECTOR 0x04 +#define TTF_REPEAT_FLAG 0x08 +#define TTF_X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR 0x10 +#define TTF_Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR 0x20 +#define TTF_OVERLAP_SIMPLE = 0x40 + +// Composite glyph flags: +#define TTF_ARG_1_AND_2_ARE_WORDS 0x0001 +#define TTF_ARGS_ARE_XY_VALUES 0x0002 +#define TTF_ROUND_XY_TO_GRID 0x0004 +#define TTF_WE_HAVE_A_SCALE 0x0008 +#define TTF_MORE_COMPONENTS 0x0020 +#define TTF_WE_HAVE_AN_X_AND_Y_SCALE 0x0040 +#define TTF_WE_HAVE_A_TWO_BY_TWO 0x0080 +#define TTF_WE_HAVE_INSTRUCTIONS 0x0100 +#define TTF_USE_MY_METRICS 0x0200 +#define TTF_OVERLAP_COMPOUND 0x0400 +#define TTF_SCALED_COMPONENT_OFFSET 0x0800 +#define TTF_UNSCALED_COMPONENT_OFFSET 0x1000 + +struct TTF_IndexToLocation // 'loca' Index to location +{ + std::vector offsets; + + void Load(const TTF_FontHeader& head, const TTF_MaximumProfile& maxp, TrueTypeFileReader& reader); +}; + +struct TTF_Point +{ + float x; + float y; +}; + +struct TTF_SimpleGlyph +{ + std::vector endPtsOfContours; + std::vector flags; + std::vector points; +}; + +class TrueTypeGlyph +{ +public: + int advanceWidth = 0; + int leftSideBearing = 0; + int yOffset = 0; + + int width = 0; + int height = 0; + std::unique_ptr grayscale; +}; + +class TrueTypeTextMetrics +{ +public: + double ascender = 0.0; + double descender = 0.0; + double lineGap = 0.0; +}; + +class TrueTypeFontFileData +{ +public: + TrueTypeFontFileData(std::vector data) : dataVector(std::move(data)) + { + dataPtr = dataVector.data(); + dataSize = dataVector.size(); + } + + TrueTypeFontFileData(const void* data, size_t size, bool copyData = true) + { + dataSize = size; + if (copyData) + { + dataPtr = new uint8_t[size]; + deleteDataPtr = true; + memcpy(const_cast(dataPtr), data, size); + } + else + { + dataPtr = data; + } + } + + ~TrueTypeFontFileData() + { + if (deleteDataPtr) + { + delete[](uint8_t*)dataPtr; + } + dataPtr = nullptr; + } + + const void* data() const { return dataPtr; } + size_t size() const { return dataSize; } + +private: + std::vector dataVector; + const void* dataPtr = nullptr; + size_t dataSize = 0; + bool deleteDataPtr = false; + + TrueTypeFontFileData(const TrueTypeFontFileData&) = delete; + TrueTypeFontFileData& operator=(const TrueTypeFontFileData&) = delete; +}; + +class TrueTypeFont +{ +public: + TrueTypeFont(std::shared_ptr data, int ttcFontIndex = 0); + + static std::vector GetFontNames(const std::shared_ptr& data); + + TrueTypeTextMetrics GetTextMetrics(double height) const; + uint32_t GetGlyphIndex(uint32_t codepoint) const; + TrueTypeGlyph LoadGlyph(uint32_t glyphIndex, double height) const; + +private: + void LoadCharacterMapEncoding(TrueTypeFileReader& reader); + void LoadGlyph(TTF_SimpleGlyph& glyph, uint32_t glyphIndex, int compositeDepth = 0) const; + static float F2DOT14_ToFloat(ttf_F2DOT14 v); + + std::shared_ptr data; + + TTC_Header ttcHeader; + TTF_TableDirectory directory; + + // Required for all OpenType fonts: + TTF_CMap cmap; + TTF_FontHeader head; + TTF_HorizontalHeader hhea; + TTF_HorizontalMetrics hmtx; + TTF_MaximumProfile maxp; + TTF_NamingTable name; + TTF_OS2Windows os2; + + // TrueType outlines: + TTF_TableRecord glyf; // Parsed on a per glyph basis using offsets from other tables + TTF_IndexToLocation loca; + + std::vector Ranges; + std::vector ManyToOneRanges; +}; diff --git a/src/core/utf8reader.cpp b/src/core/utf8reader.cpp new file mode 100644 index 000000000..200da9fdc --- /dev/null +++ b/src/core/utf8reader.cpp @@ -0,0 +1,153 @@ +/* +** Copyright (c) 1997-2015 Mark Page +** +** This software is provided 'as-is', without any express or implied +** warranty. In no event will the authors be held liable for any damages +** arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, +** including commercial applications, and to alter it and redistribute it +** freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not +** claim that you wrote the original software. If you use this software +** in a product, an acknowledgment in the product documentation would be +** appreciated but is not required. +** 2. Altered source versions must be plainly marked as such, and must not be +** misrepresented as being the original software. +** 3. This notice may not be removed or altered from any source distribution. +** +*/ + +#include "core/utf8reader.h" + +namespace +{ + static const char trailing_bytes_for_utf8[256] = + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5 + }; + + static const unsigned char bitmask_leadbyte_for_utf8[6] = + { + 0x7f, + 0x1f, + 0x0f, + 0x07, + 0x03, + 0x01 + }; +} + +UTF8Reader::UTF8Reader(const std::string::value_type *text, std::string::size_type length) : length(length), data((unsigned char *)text) +{ +} + +bool UTF8Reader::is_end() +{ + return current_position >= length; +} + +unsigned int UTF8Reader::character() +{ + if (current_position >= length) + return 0; + + int trailing_bytes = trailing_bytes_for_utf8[data[current_position]]; + if (trailing_bytes == 0 && (data[current_position] & 0x80) == 0x80) + return '?'; + + if (current_position + 1 + trailing_bytes > length) + { + return '?'; + } + else + { + unsigned int ucs4 = (data[current_position] & bitmask_leadbyte_for_utf8[trailing_bytes]); + for (std::string::size_type i = 0; i < trailing_bytes; i++) + { + if ((data[current_position + 1 + i] & 0xC0) == 0x80) + ucs4 = (ucs4 << 6) + (data[current_position + 1 + i] & 0x3f); + else + return '?'; + } + + // To do: verify that the ucs4 value is in the range for the trailing_bytes specified in the lead byte. + + return ucs4; + } + +} + +std::string::size_type UTF8Reader::char_length() +{ + if (current_position < length) + { + int trailing_bytes = trailing_bytes_for_utf8[data[current_position]]; + if (current_position + 1 + trailing_bytes > length) + return 1; + + for (std::string::size_type i = 0; i < trailing_bytes; i++) + { + if ((data[current_position + 1 + i] & 0xC0) != 0x80) + return 1; + } + + return 1 + trailing_bytes; + } + else + { + return 0; + } +} + +void UTF8Reader::prev() +{ + if (current_position > length) + current_position = length; + + if (current_position > 0) + { + current_position--; + move_to_leadbyte(); + } +} + +void UTF8Reader::next() +{ + current_position += char_length(); + +} + +void UTF8Reader::move_to_leadbyte() +{ + if (current_position < length) + { + int lead_position = (int)current_position; + + while (lead_position > 0 && (data[lead_position] & 0xC0) == 0x80) + lead_position--; + + int trailing_bytes = trailing_bytes_for_utf8[data[lead_position]]; + if (lead_position + trailing_bytes >= current_position) + current_position = lead_position; + } + +} + +std::string::size_type UTF8Reader::position() +{ + return current_position; +} + +void UTF8Reader::set_position(std::string::size_type position) +{ + current_position = position; +} diff --git a/src/core/widget.cpp b/src/core/widget.cpp new file mode 100644 index 000000000..f431a6917 --- /dev/null +++ b/src/core/widget.cpp @@ -0,0 +1,957 @@ + +#include "core/widget.h" +#include "core/timer.h" +#include "core/colorf.h" +#include "core/theme.h" +#include +#include +#include + +Widget::Widget(Widget* parent, WidgetType type, RenderAPI renderAPI) : Type(type) +{ + if (type != WidgetType::Child) + { + Widget* owner = parent ? parent->Window() : nullptr; + DispWindow = DisplayWindow::Create(this, type == WidgetType::Popup, owner ? owner->DispWindow.get() : nullptr, renderAPI); + if (renderAPI == RenderAPI::Unspecified || renderAPI == RenderAPI::Bitmap) + { + DispCanvas = Canvas::create(); + DispCanvas->attach(DispWindow.get()); + } + SetStyleState("root"); + + SetWindowBackground(GetStyleColor("window-background")); + if (GetStyleColor("window-border").a > 0.0f) + SetWindowBorderColor(GetStyleColor("window-border")); + if (GetStyleColor("window-caption-color").a > 0.0f) + SetWindowCaptionColor(GetStyleColor("window-caption-color")); + if (GetStyleColor("window-caption-text-color").a > 0.0f) + SetWindowCaptionTextColor(GetStyleColor("window-caption-text-color")); + } + + SetParent(parent); +} + +Widget::~Widget() +{ + if (DispCanvas) + DispCanvas->detach(); + + while (LastChildObj) + delete LastChildObj; + + while (FirstTimerObj) + delete FirstTimerObj; + + DetachFromParent(); +} + +void Widget::SetCanvas(std::unique_ptr canvas) +{ + if (DispWindow) + { + if (DispCanvas) + DispCanvas->detach(); + DispCanvas = std::move(canvas); + DispCanvas->attach(DispWindow.get()); + } +} + +void Widget::SetParent(Widget* newParent) +{ + if (ParentObj != newParent) + { + if (ParentObj) + DetachFromParent(); + + if (newParent) + { + PrevSiblingObj = newParent->LastChildObj; + if (PrevSiblingObj) PrevSiblingObj->NextSiblingObj = this; + newParent->LastChildObj = this; + if (!newParent->FirstChildObj) newParent->FirstChildObj = this; + ParentObj = newParent; + } + } +} + +void Widget::MoveBefore(Widget* sibling) +{ + if (sibling && sibling->ParentObj != ParentObj) throw std::runtime_error("Invalid sibling passed to Widget.MoveBefore"); + if (!ParentObj) throw std::runtime_error("Widget must have a parent before it can be moved"); + + if (NextSiblingObj != sibling) + { + Widget* p = ParentObj; + DetachFromParent(); + + ParentObj = p; + if (sibling) + { + NextSiblingObj = sibling; + PrevSiblingObj = sibling->PrevSiblingObj; + sibling->PrevSiblingObj = this; + if (PrevSiblingObj) PrevSiblingObj->NextSiblingObj = this; + if (ParentObj->FirstChildObj == sibling) ParentObj->FirstChildObj = this; + } + else + { + PrevSiblingObj = ParentObj->LastChildObj; + if (PrevSiblingObj) PrevSiblingObj->NextSiblingObj = this; + ParentObj->LastChildObj = this; + if (!ParentObj->FirstChildObj) ParentObj->FirstChildObj = this; + } + } +} + +void Widget::DetachFromParent() +{ + for (Widget* cur = ParentObj; cur; cur = cur->ParentObj) + { + if (cur->FocusWidget == this) + cur->FocusWidget = nullptr; + if (cur->CaptureWidget == this) + cur->CaptureWidget = nullptr; + if (cur->HoverWidget == this) + cur->HoverWidget = nullptr; + + if (cur->DispWindow) + break; + } + + if (PrevSiblingObj) + PrevSiblingObj->NextSiblingObj = NextSiblingObj; + if (NextSiblingObj) + NextSiblingObj->PrevSiblingObj = PrevSiblingObj; + if (ParentObj) + { + if (ParentObj->FirstChildObj == this) + ParentObj->FirstChildObj = NextSiblingObj; + if (ParentObj->LastChildObj == this) + ParentObj->LastChildObj = PrevSiblingObj; + } + PrevSiblingObj = nullptr; + NextSiblingObj = nullptr; + ParentObj = nullptr; +} + +std::string Widget::GetWindowTitle() const +{ + return WindowTitle; +} + +void Widget::SetWindowTitle(const std::string& text) +{ + if (WindowTitle != text) + { + WindowTitle = text; + if (DispWindow) + DispWindow->SetWindowTitle(WindowTitle); + } +} + +Size Widget::GetSize() const +{ + return ContentGeometry.size(); +} + +Rect Widget::GetFrameGeometry() const +{ + if (Type == WidgetType::Child) + { + return FrameGeometry; + } + else + { + return DispWindow->GetWindowFrame(); + } +} + +void Widget::SetNoncontentSizes(double left, double top, double right, double bottom) +{ + SetStyleDouble("noncontent-left", left); + SetStyleDouble("noncontent-top", top); + SetStyleDouble("noncontent-right", right); + SetStyleDouble("noncontent-bottom", bottom); +} + +void Widget::SetFrameGeometry(const Rect& geometry) +{ + if (Type == WidgetType::Child) + { + FrameGeometry = geometry; + double left = FrameGeometry.left() + GetNoncontentLeft(); + double top = FrameGeometry.top() + GetNoncontentTop(); + double right = FrameGeometry.right() - GetNoncontentRight(); + double bottom = FrameGeometry.bottom() - GetNoncontentBottom(); + left = std::min(left, FrameGeometry.right()); + top = std::min(top, FrameGeometry.bottom()); + right = std::max(right, FrameGeometry.left()); + bottom = std::max(bottom, FrameGeometry.top()); + left = GridFitPoint(left); + top = GridFitPoint(top); + right = GridFitPoint(right); + bottom = GridFitPoint(bottom); + ContentGeometry = Rect::ltrb(left, top, right, bottom); + OnGeometryChanged(); + } + else + { + DispWindow->SetWindowFrame(geometry); + } +} + +void Widget::Show() +{ + if (Type != WidgetType::Child) + { + DispWindow->Show(); + } + else if (HiddenFlag) + { + HiddenFlag = false; + Update(); + } +} + +void Widget::ShowFullscreen() +{ + if (Type != WidgetType::Child) + { + DispWindow->ShowFullscreen(); + } +} + +bool Widget::IsFullscreen() +{ + if (Type != WidgetType::Child) + { + return DispWindow->IsWindowFullscreen(); + } + return false; +} + +void Widget::ShowMaximized() +{ + if (Type != WidgetType::Child) + { + DispWindow->ShowMaximized(); + } +} + +void Widget::ShowMinimized() +{ + if (Type != WidgetType::Child) + { + DispWindow->ShowMinimized(); + } +} + +void Widget::ShowNormal() +{ + if (Type != WidgetType::Child) + { + DispWindow->ShowNormal(); + } +} + +void Widget::Hide() +{ + if (Type != WidgetType::Child) + { + if (DispWindow) + DispWindow->Hide(); + } + else if (!HiddenFlag) + { + HiddenFlag = true; + Update(); + } +} + +void Widget::ActivateWindow() +{ + if (Type != WidgetType::Child) + { + DispWindow->Activate(); + } +} + +void Widget::Close() +{ + OnClose(); +} + +void Widget::SetWindowBackground(const Colorf& color) +{ + Widget* w = Window(); + if (w && w->WindowBackground != color) + { + w->WindowBackground = color; + Update(); + } +} + +void Widget::SetWindowBorderColor(const Colorf& color) +{ + Widget* w = Window(); + if (w) + { + w->DispWindow->SetBorderColor(color.toBgra8()); + w->DispWindow->Update(); + } +} + +void Widget::SetWindowCaptionColor(const Colorf& color) +{ + Widget* w = Window(); + if (w) + { + w->DispWindow->SetCaptionColor(color.toBgra8()); + w->DispWindow->Update(); + } +} + +void Widget::SetWindowCaptionTextColor(const Colorf& color) +{ + Widget* w = Window(); + if (w) + { + w->DispWindow->SetCaptionTextColor(color.toBgra8()); + w->DispWindow->Update(); + } +} + +void Widget::Update() +{ + Widget* w = Window(); + if (w) + { + w->DispWindow->Update(); + } +} + +void Widget::Repaint() +{ + Widget* w = Window(); + if (w->DispCanvas) + { + w->DispCanvas->begin(WindowBackground); + w->Paint(w->DispCanvas.get()); + w->DispCanvas->end(); + } +} + +void Widget::Paint(Canvas* canvas) +{ + Point oldOrigin = canvas->getOrigin(); + canvas->pushClip(FrameGeometry); + canvas->setOrigin(oldOrigin + FrameGeometry.topLeft()); + OnPaintFrame(canvas); + canvas->setOrigin(oldOrigin); + canvas->popClip(); + + canvas->pushClip(ContentGeometry); + canvas->setOrigin(oldOrigin + ContentGeometry.topLeft()); + OnPaint(canvas); + for (Widget* w = FirstChild(); w != nullptr; w = w->NextSibling()) + { + if (w->Type == WidgetType::Child && !w->HiddenFlag) + w->Paint(canvas); + } + canvas->setOrigin(oldOrigin); + canvas->popClip(); +} + +void Widget::OnPaintFrame(Canvas* canvas) +{ + WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass); + if (style) + { + style->Paint(this, canvas, GetFrameGeometry().size()); + } +} + +bool Widget::GetKeyState(InputKey key) +{ + Widget* window = Window(); + return window ? window->DispWindow->GetKeyState(key) : false; +} + +bool Widget::HasFocus() +{ + Widget* window = Window(); + return window ? window->FocusWidget == this : false; +} + +bool Widget::IsEnabled() +{ + return true; +} + +bool Widget::IsHidden() +{ + return !IsVisible(); +} + +bool Widget::IsVisible() +{ + if (Type != WidgetType::Child) + { + return true; // DispWindow->IsVisible(); + } + else + { + return !HiddenFlag; + } +} + +void Widget::SetFocus() +{ + Widget* window = Window(); + if (window && window->FocusWidget != this) + { + if (window->FocusWidget) + window->FocusWidget->OnLostFocus(); + window->FocusWidget = this; + window->FocusWidget->OnSetFocus(); + window->ActivateWindow(); + } +} + +void Widget::SetEnabled(bool value) +{ +} + +void Widget::LockCursor() +{ + Widget* w = Window(); + if (w && w->CaptureWidget != this) + { + w->CaptureWidget = this; + w->DispWindow->LockCursor(); + } +} + +void Widget::UnlockCursor() +{ + Widget* w = Window(); + if (w && w->CaptureWidget != nullptr) + { + w->CaptureWidget = nullptr; + w->DispWindow->UnlockCursor(); + } +} + +void Widget::SetCursor(StandardCursor cursor) +{ + if (CurrentCursor != cursor) + { + CurrentCursor = cursor; + if (HoverWidget == this || CaptureWidget == this) + { + Widget* w = Window(); + if (w) + { + w->DispWindow->SetCursor(CurrentCursor); + } + } + } +} + +void Widget::SetPointerCapture() +{ + Widget* w = Window(); + if (w && w->CaptureWidget != this) + { + w->CaptureWidget = this; + w->DispWindow->CaptureMouse(); + } +} + +void Widget::ReleasePointerCapture() +{ + Widget* w = Window(); + if (w && w->CaptureWidget != nullptr) + { + w->CaptureWidget = nullptr; + w->DispWindow->ReleaseMouseCapture(); + } +} + +void Widget::SetModalCapture() +{ + Widget* w = Window(); + if (w && w->CaptureWidget != this) + { + w->CaptureWidget = this; + } +} + +void Widget::ReleaseModalCapture() +{ + Widget* w = Window(); + if (w && w->CaptureWidget != nullptr) + { + w->CaptureWidget = nullptr; + } +} + +std::string Widget::GetClipboardText() +{ + Widget* w = Window(); + if (w) + return w->DispWindow->GetClipboardText(); + else + return {}; +} + +void Widget::SetClipboardText(const std::string& text) +{ + Widget* w = Window(); + if (w) + w->DispWindow->SetClipboardText(text); +} + +Widget* Widget::Window() const +{ + for (const Widget* w = this; w != nullptr; w = w->Parent()) + { + if (w->DispWindow) + return const_cast(w); + } + return nullptr; +} + +Canvas* Widget::GetCanvas() const +{ + for (const Widget* w = this; w != nullptr; w = w->Parent()) + { + if (w->DispCanvas) + return w->DispCanvas.get(); + } + return nullptr; +} + +bool Widget::IsParent(const Widget* w) const +{ + while (w) + { + w = w->Parent(); + if (w == this) + return true; + } + return false; +} + +bool Widget::IsChild(const Widget* w) const +{ + if (!w) + return false; + return w->IsParent(this); +} + +Widget* Widget::ChildAt(const Point& pos) +{ + for (Widget* cur = LastChild(); cur != nullptr; cur = cur->PrevSibling()) + { + if (cur->Type == WidgetType::Child && !cur->HiddenFlag && cur->FrameGeometry.contains(pos)) + { + Widget* cur2 = cur->ChildAt(pos - cur->ContentGeometry.topLeft()); + return cur2 ? cur2 : cur; + } + } + return nullptr; +} + +Point Widget::MapFrom(const Widget* parent, const Point& pos) const +{ + Point p = pos; + for (const Widget* cur = this; cur != nullptr; cur = cur->Parent()) + { + if (cur == parent) + return p; + p -= cur->ContentGeometry.topLeft(); + } + throw std::runtime_error("MapFrom: not a parent of widget"); +} + +Point Widget::MapFromGlobal(const Point& pos) const +{ + Point p = pos; + for (const Widget* cur = this; cur != nullptr; cur = cur->Parent()) + { + if (cur->DispWindow) + { + return cur->DispWindow->MapFromGlobal(p); + } + p -= cur->ContentGeometry.topLeft(); + } + throw std::runtime_error("MapFromGlobal: no window widget found"); +} + +Point Widget::MapTo(const Widget* parent, const Point& pos) const +{ + Point p = pos; + for (const Widget* cur = this; cur != nullptr; cur = cur->Parent()) + { + if (cur == parent) + return p; + p += cur->ContentGeometry.topLeft(); + } + throw std::runtime_error("MapTo: not a parent of widget"); +} + +Point Widget::MapToGlobal(const Point& pos) const +{ + Point p = pos; + for (const Widget* cur = this; cur != nullptr; cur = cur->Parent()) + { + if (cur->DispWindow) + { + return cur->DispWindow->MapToGlobal(p); + } + p += cur->ContentGeometry.topLeft(); + } + throw std::runtime_error("MapFromGlobal: no window widget found"); +} + +void Widget::OnWindowPaint() +{ + Repaint(); +} + +void Widget::OnWindowMouseMove(const Point& pos) +{ + if (CaptureWidget) + { + DispWindow->SetCursor(CaptureWidget->CurrentCursor); + CaptureWidget->OnMouseMove(CaptureWidget->MapFrom(this, pos)); + } + else + { + Widget* widget = ChildAt(pos); + if (!widget) + widget = this; + + if (HoverWidget != widget) + { + if (HoverWidget) + { + for (Widget* w = HoverWidget; w != widget && w != this; w = w->Parent()) + { + Widget* p = w->Parent(); + if (!w->FrameGeometry.contains(p->MapFrom(this, pos))) + { + w->OnMouseLeave(); + } + } + } + HoverWidget = widget; + } + + DispWindow->SetCursor(widget->CurrentCursor); + + do + { + widget->OnMouseMove(widget->MapFrom(this, pos)); + if (widget == this) + break; + widget = widget->Parent(); + } while (widget); + } +} + +void Widget::OnWindowMouseLeave() +{ + if (HoverWidget) + { + for (Widget* w = HoverWidget; w; w = w->Parent()) + { + w->OnMouseLeave(); + } + HoverWidget = nullptr; + } +} + +void Widget::OnWindowMouseDown(const Point& pos, InputKey key) +{ + if (CaptureWidget) + { + CaptureWidget->OnMouseDown(CaptureWidget->MapFrom(this, pos), key); + } + else + { + Widget* widget = ChildAt(pos); + if (!widget) + widget = this; + while (widget) + { + bool stopPropagation = widget->OnMouseDown(widget->MapFrom(this, pos), key); + if (stopPropagation || widget == this) + break; + widget = widget->Parent(); + } + } +} + +void Widget::OnWindowMouseDoubleclick(const Point& pos, InputKey key) +{ + if (CaptureWidget) + { + CaptureWidget->OnMouseDoubleclick(CaptureWidget->MapFrom(this, pos), key); + } + else + { + Widget* widget = ChildAt(pos); + if (!widget) + widget = this; + while (widget) + { + bool stopPropagation = widget->OnMouseDoubleclick(widget->MapFrom(this, pos), key); + if (stopPropagation || widget == this) + break; + widget = widget->Parent(); + } + } +} + +void Widget::OnWindowMouseUp(const Point& pos, InputKey key) +{ + if (CaptureWidget) + { + CaptureWidget->OnMouseUp(CaptureWidget->MapFrom(this, pos), key); + } + else + { + Widget* widget = ChildAt(pos); + if (!widget) + widget = this; + while (widget) + { + bool stopPropagation = widget->OnMouseUp(widget->MapFrom(this, pos), key); + if (stopPropagation || widget == this) + break; + widget = widget->Parent(); + } + } +} + +void Widget::OnWindowMouseWheel(const Point& pos, InputKey key) +{ + if (CaptureWidget) + { + CaptureWidget->OnMouseWheel(CaptureWidget->MapFrom(this, pos), key); + } + else + { + Widget* widget = ChildAt(pos); + if (!widget) + widget = this; + while (widget) + { + bool stopPropagation = widget->OnMouseWheel(widget->MapFrom(this, pos), key); + if (stopPropagation || widget == this) + break; + widget = widget->Parent(); + } + } +} + +void Widget::OnWindowRawMouseMove(int dx, int dy) +{ + if (CaptureWidget) + { + CaptureWidget->OnRawMouseMove(dx, dy); + } + else if (FocusWidget) + { + FocusWidget->OnRawMouseMove(dx, dy); + } +} + +void Widget::OnWindowKeyChar(std::string chars) +{ + if (FocusWidget) + FocusWidget->OnKeyChar(chars); +} + +void Widget::OnWindowKeyDown(InputKey key) +{ + if (FocusWidget) + FocusWidget->OnKeyDown(key); +} + +void Widget::OnWindowKeyUp(InputKey key) +{ + if (FocusWidget) + FocusWidget->OnKeyUp(key); +} + +void Widget::OnWindowGeometryChanged() +{ + if (!DispWindow) + return; + Size size = DispWindow->GetClientSize(); + FrameGeometry = Rect::xywh(0.0, 0.0, size.width, size.height); + + double left = FrameGeometry.left() + GetNoncontentLeft(); + double top = FrameGeometry.top() + GetNoncontentTop(); + double right = FrameGeometry.right() - GetNoncontentRight(); + double bottom = FrameGeometry.bottom() - GetNoncontentBottom(); + left = std::min(left, FrameGeometry.right()); + top = std::min(top, FrameGeometry.bottom()); + right = std::max(right, FrameGeometry.left()); + bottom = std::max(bottom, FrameGeometry.top()); + ContentGeometry = Rect::ltrb(left, top, right, bottom); + + OnGeometryChanged(); +} + +void Widget::OnWindowClose() +{ + Close(); +} + +void Widget::OnWindowActivated() +{ +} + +void Widget::OnWindowDeactivated() +{ +} + +void Widget::OnWindowDpiScaleChanged() +{ +} + +double Widget::GetDpiScale() const +{ + Widget* w = Window(); + return w ? w->DispWindow->GetDpiScale() : 1.0; +} + +double Widget::GridFitPoint(double p) const +{ + double dpiscale = GetDpiScale(); + return std::round(p * dpiscale) / dpiscale; +} + +double Widget::GridFitSize(double s) const +{ + if (s <= 0.0) + return 0.0; + double dpiscale = GetDpiScale(); + return std::max(std::floor(s * dpiscale + 0.25), 1.0) / dpiscale; +} + +Size Widget::GetScreenSize() +{ + return DisplayWindow::GetScreenSize(); +} + +void* Widget::GetNativeHandle() +{ + Widget* w = Window(); + return w ? w->DispWindow->GetNativeHandle() : nullptr; +} + +int Widget::GetNativePixelWidth() +{ + Widget* w = Window(); + return w ? w->DispWindow->GetPixelWidth() : 0; +} + +int Widget::GetNativePixelHeight() +{ + Widget* w = Window(); + return w ? w->DispWindow->GetPixelHeight() : 0; +} + +void Widget::SetStyleClass(const std::string& themeClass) +{ + if (StyleClass != themeClass) + { + StyleClass = themeClass; + Update(); + } +} + +void Widget::SetStyleState(const std::string& state) +{ + if (StyleState != state) + { + StyleState = state; + Update(); + } +} + +void Widget::SetStyleBool(const std::string& propertyName, bool value) +{ + StyleProperties[propertyName] = value; +} + +void Widget::SetStyleInt(const std::string& propertyName, int value) +{ + StyleProperties[propertyName] = value; +} + +void Widget::SetStyleDouble(const std::string& propertyName, double value) +{ + StyleProperties[propertyName] = value; +} + +void Widget::SetStyleString(const std::string& propertyName, const std::string& value) +{ + StyleProperties[propertyName] = value; +} + +void Widget::SetStyleColor(const std::string& propertyName, const Colorf& value) +{ + StyleProperties[propertyName] = value; +} + +bool Widget::GetStyleBool(const std::string& propertyName) const +{ + auto it = StyleProperties.find(propertyName); + if (it != StyleProperties.end()) + return std::get(it->second); + WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass); + return style ? style->GetBool(StyleState, propertyName) : false; +} + +int Widget::GetStyleInt(const std::string& propertyName) const +{ + auto it = StyleProperties.find(propertyName); + if (it != StyleProperties.end()) + return std::get(it->second); + WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass); + return style ? style->GetInt(StyleState, propertyName) : 0; +} + +double Widget::GetStyleDouble(const std::string& propertyName) const +{ + auto it = StyleProperties.find(propertyName); + if (it != StyleProperties.end()) + return std::get(it->second); + WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass); + return style ? style->GetDouble(StyleState, propertyName) : 0.0; +} + +std::string Widget::GetStyleString(const std::string& propertyName) const +{ + auto it = StyleProperties.find(propertyName); + if (it != StyleProperties.end()) + return std::get(it->second); + WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass); + return style ? style->GetString(StyleState, propertyName) : std::string(); +} + +Colorf Widget::GetStyleColor(const std::string& propertyName) const +{ + auto it = StyleProperties.find(propertyName); + if (it != StyleProperties.end()) + return std::get(it->second); + WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass); + return style ? style->GetColor(StyleState, propertyName) : Colorf::transparent(); +} diff --git a/src/systemdialogs/open_file_dialog.cpp b/src/systemdialogs/open_file_dialog.cpp new file mode 100644 index 000000000..6d0ce3931 --- /dev/null +++ b/src/systemdialogs/open_file_dialog.cpp @@ -0,0 +1,12 @@ + +#include "systemdialogs/open_file_dialog.h" +#include "window/window.h" +#include "core/widget.h" + +std::unique_ptr OpenFileDialog::Create(Widget* owner) +{ + DisplayWindow* windowOwner = nullptr; + if (owner && owner->Window()) + windowOwner = owner->Window()->DispWindow.get(); + return DisplayBackend::Get()->CreateOpenFileDialog(windowOwner); +} diff --git a/src/systemdialogs/open_folder_dialog.cpp b/src/systemdialogs/open_folder_dialog.cpp new file mode 100644 index 000000000..8ccf66237 --- /dev/null +++ b/src/systemdialogs/open_folder_dialog.cpp @@ -0,0 +1,12 @@ + +#include "systemdialogs/open_folder_dialog.h" +#include "window/window.h" +#include "core/widget.h" + +std::unique_ptr OpenFolderDialog::Create(Widget* owner) +{ + DisplayWindow* windowOwner = nullptr; + if (owner && owner->Window()) + windowOwner = owner->Window()->DispWindow.get(); + return DisplayBackend::Get()->CreateOpenFolderDialog(windowOwner); +} diff --git a/src/systemdialogs/save_file_dialog.cpp b/src/systemdialogs/save_file_dialog.cpp new file mode 100644 index 000000000..0163fe7cb --- /dev/null +++ b/src/systemdialogs/save_file_dialog.cpp @@ -0,0 +1,12 @@ + +#include "systemdialogs/save_file_dialog.h" +#include "window/window.h" +#include "core/widget.h" + +std::unique_ptr SaveFileDialog::Create(Widget* owner) +{ + DisplayWindow* windowOwner = nullptr; + if (owner && owner->Window()) + windowOwner = owner->Window()->DispWindow.get(); + return DisplayBackend::Get()->CreateSaveFileDialog(windowOwner); +} diff --git a/src/widgets/checkboxlabel/checkboxlabel.cpp b/src/widgets/checkboxlabel/checkboxlabel.cpp new file mode 100644 index 000000000..77c1bd645 --- /dev/null +++ b/src/widgets/checkboxlabel/checkboxlabel.cpp @@ -0,0 +1,101 @@ + +#include "widgets/checkboxlabel/checkboxlabel.h" + +CheckboxLabel::CheckboxLabel(Widget* parent) : Widget(parent) +{ + SetStyleClass("checkbox-label"); +} + +void CheckboxLabel::SetText(const std::string& value) +{ + if (text != value) + { + text = value; + Update(); + } +} + +const std::string& CheckboxLabel::GetText() const +{ + return text; +} + +void CheckboxLabel::SetChecked(bool value) +{ + if (value != checked) + { + checked = value; + Update(); + } +} + +bool CheckboxLabel::GetChecked() const +{ + return checked; +} + +double CheckboxLabel::GetPreferredHeight() const +{ + return 20.0; +} + +void CheckboxLabel::OnPaint(Canvas* canvas) +{ + // To do: add and use GetStyleImage for the checkbox + + double center = GridFitPoint(GetHeight() * 0.5); + double borderwidth = GridFitSize(1.0); + double outerboxsize = GridFitSize(10.0); + double innerboxsize = outerboxsize - 2.0 * borderwidth; + double checkedsize = innerboxsize - 2.0 * borderwidth; + + if (checked) + { + canvas->fillRect(Rect::xywh(0.0, center - 6.0 * borderwidth, outerboxsize, outerboxsize), GetStyleColor("checked-outer-border-color")); + canvas->fillRect(Rect::xywh(1.0 * borderwidth, center - 5.0 * borderwidth, innerboxsize, innerboxsize), GetStyleColor("checked-inner-border-color")); + canvas->fillRect(Rect::xywh(2.0 * borderwidth, center - 4.0 * borderwidth, checkedsize, checkedsize), GetStyleColor("checked-color")); + } + else + { + canvas->fillRect(Rect::xywh(0.0, center - 6.0 * borderwidth, outerboxsize, outerboxsize), GetStyleColor("unchecked-outer-border-color")); + canvas->fillRect(Rect::xywh(1.0 * borderwidth, center - 5.0 * borderwidth, innerboxsize, innerboxsize), GetStyleColor("unchecked-inner-border-color")); + } + + canvas->drawText(Point(14.0, GetHeight() - 5.0), GetStyleColor("color"), text); +} + +bool CheckboxLabel::OnMouseDown(const Point& pos, InputKey key) +{ + mouseDownActive = true; + SetFocus(); + return true; +} + +bool CheckboxLabel::OnMouseUp(const Point& pos, InputKey key) +{ + if (mouseDownActive) + { + Toggle(); + } + mouseDownActive = false; + return true; +} + +void CheckboxLabel::OnMouseLeave() +{ + mouseDownActive = false; +} + +void CheckboxLabel::OnKeyUp(InputKey key) +{ + if (key == InputKey::Space) + Toggle(); +} + +void CheckboxLabel::Toggle() +{ + bool oldchecked = checked; + checked = radiostyle? true : !checked; + Update(); + if (checked != oldchecked && FuncChanged) FuncChanged(checked); +} diff --git a/src/widgets/imagebox/imagebox.cpp b/src/widgets/imagebox/imagebox.cpp new file mode 100644 index 000000000..70323cc89 --- /dev/null +++ b/src/widgets/imagebox/imagebox.cpp @@ -0,0 +1,73 @@ + +#include "widgets/imagebox/imagebox.h" + +ImageBox::ImageBox(Widget* parent) : Widget(parent) +{ +} + +double ImageBox::GetPreferredWidth() const +{ + if (image) + return (double)image->GetWidth(); + else + return 0.0; +} + +double ImageBox::GetPreferredHeight() const +{ + if (image) + return (double)image->GetHeight(); + else + return 0.0; +} + +void ImageBox::SetImage(std::shared_ptr newImage) +{ + if (image != newImage) + { + image = newImage; + Update(); + } +} + +void ImageBox::SetImageMode(ImageBoxMode newMode) +{ + if (mode != newMode) + { + mode = newMode; + Update(); + } +} + +void ImageBox::OnPaint(Canvas* canvas) +{ + if (image) + { + if (mode == ImageBoxMode::Center) + { + canvas->drawImage(image, Point((GetWidth() - (double)image->GetWidth()) * 0.5, (GetHeight() - (double)image->GetHeight()) * 0.5)); + } + else if (mode == ImageBoxMode::Contain) + { + double bw = GetWidth(); + double bh = GetHeight(); + double iw = image->GetWidth(); + double ih = image->GetHeight(); + double xscale = bw / iw; + double yscale = bh / ih; + double scale = std::min(xscale, yscale); + canvas->drawImage(image, Rect::xywh((bw - iw * scale) * 0.5, (bh - ih * scale) * 0.5, iw * scale, ih * scale)); + } + else if (mode == ImageBoxMode::Cover) + { + double bw = GetWidth(); + double bh = GetHeight(); + double iw = image->GetWidth(); + double ih = image->GetHeight(); + double xscale = bw / iw; + double yscale = bh / ih; + double scale = std::max(xscale, yscale); + canvas->drawImage(image, Rect::xywh((bw - iw * scale) * 0.5, (bh - ih * scale) * 0.5, iw * scale, ih * scale)); + } + } +} diff --git a/src/widgets/lineedit/lineedit.cpp b/src/widgets/lineedit/lineedit.cpp new file mode 100644 index 000000000..336ac9882 --- /dev/null +++ b/src/widgets/lineedit/lineedit.cpp @@ -0,0 +1,1171 @@ + +#include "widgets/lineedit/lineedit.h" +#include "core/utf8reader.h" +#include "core/colorf.h" +#include + +LineEdit::LineEdit(Widget* parent) : Widget(parent) +{ + SetStyleClass("lineedit"); + + timer = new Timer(this); + timer->FuncExpired = [=]() { OnTimerExpired(); }; + + scroll_timer = new Timer(this); + scroll_timer->FuncExpired = [=]() { OnScrollTimerExpired(); }; + + SetCursor(StandardCursor::ibeam); +} + +LineEdit::~LineEdit() +{ +} + +bool LineEdit::IsReadOnly() const +{ + return readonly; +} + +LineEdit::Alignment LineEdit::GetAlignment() const +{ + return align_left; +} + +bool LineEdit::IsLowercase() const +{ + return lowercase; +} + +bool LineEdit::IsUppercase() const +{ + return uppercase; +} + +bool LineEdit::IsPasswordMode() const +{ + return password_mode; +} + +int LineEdit::GetMaxLength() const +{ + return max_length; +} + +std::string LineEdit::GetText() const +{ + return text; +} + +int LineEdit::GetTextInt() const +{ + return std::atoi(text.c_str()); +} + +float LineEdit::GetTextFloat() const +{ + return (float)std::atof(text.c_str()); +} + +std::string LineEdit::GetSelection() const +{ + int start = std::min(selection_start, selection_start + selection_length); + return text.substr(start, std::abs(selection_length)); +} + +int LineEdit::GetSelectionStart() const +{ + return selection_start; +} + +int LineEdit::GetSelectionLength() const +{ + return selection_length; +} + +int LineEdit::GetCursorPos() const +{ + return cursor_pos; +} + +Size LineEdit::GetTextSize() +{ + Canvas* canvas = GetCanvas(); + return GetVisualTextSize(canvas); +} + +Size LineEdit::GetTextSize(const std::string& str) +{ + Canvas* canvas = GetCanvas(); + return canvas->measureText(str).size(); +} + +double LineEdit::GetPreferredContentWidth() +{ + return GetTextSize().width; +} + +double LineEdit::GetPreferredContentHeight(double width) +{ + return GetTextSize().height; +} + +void LineEdit::SelectAll() +{ + SetTextSelection(0, (int)text.size()); + Update(); +} + +void LineEdit::SetReadOnly(bool enable) +{ + if (readonly != enable) + { + readonly = enable; + Update(); + } +} + +void LineEdit::SetAlignment(Alignment newalignment) +{ + if (alignment != newalignment) + { + alignment = newalignment; + Update(); + } +} + +void LineEdit::SetLowercase(bool enable) +{ + if (lowercase != enable) + { + lowercase = enable; + text = ToLower(text); + Update(); + } +} + +void LineEdit::SetUppercase(bool enable) +{ + if (uppercase != enable) + { + uppercase = enable; + text = ToUpper(text); + Update(); + } +} + +void LineEdit::SetPasswordMode(bool enable) +{ + if (password_mode != enable) + { + password_mode = enable; + Update(); + } +} + +void LineEdit::SetMaxLength(int length) +{ + if (max_length != length) + { + max_length = length; + if ((int)text.length() > length) + { + if (FuncBeforeEditChanged) + FuncBeforeEditChanged(); + text = text.substr(0, length); + if (FuncAfterEditChanged) + FuncAfterEditChanged(); + } + Update(); + } +} + +void LineEdit::SetText(const std::string& newtext) +{ + if (lowercase) + text = ToLower(newtext); + else if (uppercase) + text = ToUpper(newtext); + else + text = newtext; + + clip_start_offset = 0; + UpdateTextClipping(); + SetCursorPos((int)text.size()); + SetTextSelection(0, 0); + Update(); +} + +void LineEdit::SetTextInt(int number) +{ + text = std::to_string(number); + clip_start_offset = 0; + UpdateTextClipping(); + SetCursorPos((int)text.size()); + SetTextSelection(0, 0); + Update(); +} + +void LineEdit::SetTextFloat(float number, int num_decimal_places) +{ + text = ToFixed(number, num_decimal_places); + clip_start_offset = 0; + UpdateTextClipping(); + SetCursorPos((int)text.size()); + SetTextSelection(0, 0); + Update(); +} + +void LineEdit::SetSelection(int pos, int length) +{ + //don't call FuncSelectionChanged() here, because this + //member is for public usage + selection_start = pos; + selection_length = length; + Update(); +} + +void LineEdit::ClearSelection() +{ + //don't call FuncSelectionChanged() here, because this + //member is for public usage + SetSelection(0, 0); + Update(); +} + +void LineEdit::DeleteSelectedText() +{ + if (GetSelectionLength() == 0) + return; + + int sel_start = selection_start; + int sel_end = selection_start + selection_length; + if (sel_start > sel_end) + std::swap(sel_start, sel_end); + + text = text.substr(0, sel_start) + text.substr(sel_end, text.size()); + cursor_pos = sel_start; + SetTextSelection(0, 0); + int old_pos = GetCursorPos(); + SetCursorPos(0); + SetCursorPos(old_pos); +} + +void LineEdit::SetCursorPos(int pos) +{ + cursor_pos = pos; + UpdateTextClipping(); + Update(); +} + +void LineEdit::SetInputMask(const std::string& mask) +{ + input_mask = mask; +} + +void LineEdit::SetNumericMode(bool enable, bool decimals) +{ + numeric_mode = enable; + numeric_mode_decimals = decimals; +} + +void LineEdit::SetDecimalCharacter(const std::string& new_decimal_char) +{ + decimal_char = new_decimal_char; +} + +void LineEdit::SetSelectAllOnFocusGain(bool enable) +{ + select_all_on_focus_gain = enable; +} + +void LineEdit::OnMouseMove(const Point& pos) +{ + if (mouse_selecting) + { + if (pos.x < 0.0 || pos.x >= GetWidth()) + { + if (pos.x < 0.0) + mouse_moves_left = true; + else + mouse_moves_left = false; + + if (!readonly) + scroll_timer->Start(50, true); + } + else + { + scroll_timer->Stop(); + cursor_pos = GetCharacterIndex(pos.x); + SetSelectionLength(cursor_pos - selection_start); + Update(); + } + } +} + +bool LineEdit::OnMouseDown(const Point& pos, InputKey key) +{ + if (key == InputKey::LeftMouse) + { + if (HasFocus()) + { + SetPointerCapture(); + mouse_selecting = true; + cursor_pos = GetCharacterIndex(pos.x); + SetTextSelection(cursor_pos, 0); + } + else + { + SetFocus(); + } + Update(); + } + return true; +} + +bool LineEdit::OnMouseDoubleclick(const Point& pos, InputKey key) +{ + return true; +} + +bool LineEdit::OnMouseUp(const Point& pos, InputKey key) +{ + if (mouse_selecting && key == InputKey::LeftMouse) + { + scroll_timer->Stop(); + ReleasePointerCapture(); + mouse_selecting = false; + int sel_end = GetCharacterIndex(pos.x); + SetSelectionLength(sel_end - selection_start); + cursor_pos = sel_end; + Update(); + } + return true; +} + +void LineEdit::OnKeyChar(std::string chars) +{ + if (FuncFilterKeyChar) + { + chars = FuncFilterKeyChar(chars); + if (chars.empty()) + return; + } + + if (!chars.empty() && !(chars[0] >= 0 && chars[0] < 32)) + { + if (FuncBeforeEditChanged) + FuncBeforeEditChanged(); + + DeleteSelectedText(); + if (input_mask.empty()) + { + if (numeric_mode) + { + // '-' can only be added once, and only as the first character. + if (chars == "-" && cursor_pos == 0 && text.find("-") == std::string::npos) + { + if (InsertText(cursor_pos, chars)) + cursor_pos += (int)chars.size(); + } + else if (numeric_mode_decimals && chars == decimal_char && cursor_pos > 0) // add decimal char + { + if (text.find(decimal_char) == std::string::npos) // allow only one decimal char. + { + if (InsertText(cursor_pos, chars)) + cursor_pos += (int)chars.size(); + } + } + else if (numeric_mode_characters.find(chars) != std::string::npos) // 0-9 + { + if (InsertText(cursor_pos, chars)) + cursor_pos += (int)chars.size(); + } + } + else + { + // not in any special mode, just insert the string. + if (InsertText(cursor_pos, chars)) + cursor_pos += (int)chars.size(); + } + } + else + { + if (InputMaskAcceptsInput(cursor_pos, chars)) + { + if (InsertText(cursor_pos, chars)) + cursor_pos += (int)chars.size(); + } + } + UpdateTextClipping(); + + if (FuncAfterEditChanged) + FuncAfterEditChanged(); + } +} + +void LineEdit::OnKeyDown(InputKey key) +{ + if (FuncIgnoreKeyDown && FuncIgnoreKeyDown(key)) + return; + + if (key == InputKey::Enter) + { + if (FuncEnterPressed) + FuncEnterPressed(); + return; + } + + if (!readonly) // Do not flash cursor when readonly + { + cursor_blink_visible = true; + timer->Start(500); // don't blink cursor when moving or typing. + } + + if (key == InputKey::Enter || key == InputKey::Escape || key == InputKey::Tab) + { + // Do not consume these. + return; + } + else if (key == InputKey::A && GetKeyState(InputKey::Ctrl)) + { + // select all + SetTextSelection(0, (int)text.size()); + cursor_pos = selection_length; + UpdateTextClipping(); + Update(); + } + else if (key == InputKey::C && GetKeyState(InputKey::Ctrl)) + { + if (!password_mode) // Do not allow copying the password to clipboard + { + std::string str = GetSelection(); + SetClipboardText(str); + } + } + else if (readonly) + { + // Do not consume messages on read only component (only allow CTRL-A and CTRL-C) + return; + } + else if (key == InputKey::Left) + { + Move(-1, GetKeyState(InputKey::Ctrl), GetKeyState(InputKey::Shift)); + } + else if (key == InputKey::Right) + { + Move(1, GetKeyState(InputKey::Ctrl), GetKeyState(InputKey::Shift)); + } + else if (key == InputKey::Backspace) + { + Backspace(); + UpdateTextClipping(); + } + else if (key == InputKey::Delete) + { + Del(); + UpdateTextClipping(); + } + else if (key == InputKey::Home) + { + SetSelectionStart(cursor_pos); + cursor_pos = 0; + if (GetKeyState(InputKey::Shift)) + SetSelectionLength(-selection_start); + else + SetTextSelection(0, 0); + UpdateTextClipping(); + Update(); + } + else if (key == InputKey::End) + { + SetSelectionStart(cursor_pos); + cursor_pos = (int)text.size(); + if (GetKeyState(InputKey::Shift)) + SetSelectionLength((int)text.size() - selection_start); + else + SetTextSelection(0, 0); + UpdateTextClipping(); + Update(); + } + else if (key == InputKey::X && GetKeyState(InputKey::Ctrl)) + { + std::string str = GetSelection(); + DeleteSelectedText(); + SetClipboardText(str); + UpdateTextClipping(); + } + else if (key == InputKey::V && GetKeyState(InputKey::Ctrl)) + { + std::string str = GetClipboardText(); + std::string::const_iterator end_str = std::remove(str.begin(), str.end(), '\n'); + str.resize(end_str - str.begin()); + end_str = std::remove(str.begin(), str.end(), '\r'); + str.resize(end_str - str.begin()); + DeleteSelectedText(); + + if (input_mask.empty()) + { + if (numeric_mode) + { + std::string present_text = GetText(); + + bool present_minus = present_text.find('-') != std::string::npos; + bool str_minus = str.find('-') != std::string::npos; + + if (!present_minus || !str_minus) + { + if ((!present_minus && !str_minus) || //if no minus found + (str_minus && cursor_pos == 0 && str[0] == '-') || //if there's minus in text to paste + (present_minus && cursor_pos > 0)) //if there's minus in the beginning of control's text + { + if (numeric_mode_decimals) + { + std::string::size_type decimal_point_pos; + if ((decimal_point_pos = str.find_first_not_of(numeric_mode_characters, str[0] == '-' ? 1 : 0)) == std::string::npos) //no decimal char inside string to paste + { //we don't look at the position of decimal char inside of text in the textbox, if it's present + if (InsertText(cursor_pos, str)) + SetCursorPos(cursor_pos + (int)str.length()); + } + else + { + if (present_text.find(decimal_char) == std::string::npos && + str[decimal_point_pos] == decimal_char[0] && + str.find_first_not_of(numeric_mode_characters, decimal_point_pos + 1) == std::string::npos) //allow only one decimal char in the string to paste + { + if (InsertText(cursor_pos, str)) + SetCursorPos(cursor_pos + (int)str.length()); + } + } + } + else + { + if (str.find_first_not_of(numeric_mode_characters, str[0] == '-' ? 1 : 0) == std::string::npos) + { + if (InsertText(cursor_pos, str)) + SetCursorPos(cursor_pos + (int)str.length()); + } + } + } + } + } + else + { + if (InsertText(cursor_pos, str)) + SetCursorPos(cursor_pos + (int)str.length()); + } + } + else + { + if (InputMaskAcceptsInput(cursor_pos, str)) + { + if (InsertText(cursor_pos, str)) + SetCursorPos(cursor_pos + (int)str.length()); + } + } + + UpdateTextClipping(); + } + else if (GetKeyState(InputKey::Ctrl) && key == InputKey::Z) + { + if (!readonly) + { + std::string tmp = undo_info.undo_text; + undo_info.undo_text = GetText(); + SetText(tmp); + } + } + else if (key == InputKey::Shift) + { + if (selection_start == -1) + SetTextSelection(cursor_pos, 0); + } + + if (FuncAfterEditChanged) + FuncAfterEditChanged(); +} + +void LineEdit::OnKeyUp(InputKey key) +{ +} + +void LineEdit::OnSetFocus() +{ + if (!readonly) + timer->Start(500); + if (select_all_on_focus_gain) + SelectAll(); + cursor_pos = (int)text.length(); + + Update(); + + if (FuncFocusGained) + FuncFocusGained(); +} + +void LineEdit::OnLostFocus() +{ + timer->Stop(); + SetTextSelection(0, 0); + + Update(); + + if (FuncFocusLost) + FuncFocusLost(); +} + +void LineEdit::Move(int steps, bool ctrl, bool shift) +{ + if (shift && selection_length == 0) + SetSelectionStart(cursor_pos); + + // Jump over words if control is pressed. + if (ctrl) + { + if (steps < 0) + steps = FindPreviousBreakCharacter(cursor_pos - 1) - cursor_pos; + else + steps = FindNextBreakCharacter(cursor_pos + 1) - cursor_pos; + + cursor_pos += steps; + if (cursor_pos < 0) + cursor_pos = 0; + if (cursor_pos > (int)text.size()) + cursor_pos = (int)text.size(); + } + else + { + UTF8Reader utf8_reader(text.data(), text.length()); + utf8_reader.set_position(cursor_pos); + if (steps > 0) + { + for (int i = 0; i < steps; i++) + utf8_reader.next(); + } + else if (steps < 0) + { + for (int i = 0; i < -steps; i++) + utf8_reader.prev(); + } + + cursor_pos = (int)utf8_reader.position(); + } + + + // Clear the selection if a cursor key is pressed but shift isn't down. + if (shift) + SetSelectionLength(cursor_pos - selection_start); + else + SetTextSelection(-1, 0); + + UpdateTextClipping(); + + Update(); + + undo_info.first_text_insert = true; +} + +bool LineEdit::InsertText(int pos, const std::string& str) +{ + undo_info.first_erase = false; + if (undo_info.first_text_insert) + { + undo_info.undo_text = GetText(); + undo_info.first_text_insert = false; + } + + // checking if insert exceeds max length + if (UTF8Reader::utf8_length(text) + UTF8Reader::utf8_length(str) > max_length) + { + return false; + } + + if (lowercase) + text.insert(pos, ToLower(str)); + else if (uppercase) + text.insert(pos, ToUpper(str)); + else + text.insert(pos, str); + + UpdateTextClipping(); + Update(); + return true; +} + +void LineEdit::Backspace() +{ + if (undo_info.first_erase) + { + undo_info.first_erase = false; + undo_info.undo_text = GetText(); + } + + if (GetSelectionLength() != 0) + { + DeleteSelectedText(); + Update(); + } + else + { + if (cursor_pos > 0) + { + UTF8Reader utf8_reader(text.data(), text.length()); + utf8_reader.set_position(cursor_pos); + utf8_reader.prev(); + size_t length = utf8_reader.char_length(); + text.erase(cursor_pos - length, length); + cursor_pos -= (int)length; + Update(); + } + } + + int old_pos = GetCursorPos(); + SetCursorPos(0); + SetCursorPos(old_pos); +} + +void LineEdit::Del() +{ + if (undo_info.first_erase) + { + undo_info.first_erase = false; + undo_info.undo_text = GetText(); + } + + if (GetSelectionLength() != 0) + { + DeleteSelectedText(); + Update(); + } + else + { + if (cursor_pos < (int)text.size()) + { + UTF8Reader utf8_reader(text.data(), text.length()); + utf8_reader.set_position(cursor_pos); + size_t length = utf8_reader.char_length(); + text.erase(cursor_pos, length); + Update(); + } + } +} + +int LineEdit::GetCharacterIndex(double mouse_x) +{ + if (text.size() <= 1) + { + return (int)text.size(); + } + + Canvas* canvas = GetCanvas(); + UTF8Reader utf8_reader(text.data(), text.length()); + + int seek_start = clip_start_offset; + int seek_end = (int)text.size(); + int seek_center = (seek_start + seek_end) / 2; + + //fast search + while (true) + { + utf8_reader.set_position(seek_center); + utf8_reader.move_to_leadbyte(); + + seek_center = (int)utf8_reader.position(); + + Size text_size = GetVisualTextSize(canvas, clip_start_offset, seek_center - clip_start_offset); + + if (text_size.width > mouse_x) + seek_end = seek_center; + else + seek_start = seek_center; + + if (seek_end - seek_start < 7) + break; //go to accurate search + + seek_center = (seek_start + seek_end) / 2; + } + + utf8_reader.set_position(seek_start); + utf8_reader.move_to_leadbyte(); + + //accurate search + while (true) + { + seek_center = (int)utf8_reader.position(); + + Size text_size = GetVisualTextSize(canvas, clip_start_offset, seek_center - clip_start_offset); + if (text_size.width > mouse_x || utf8_reader.is_end()) + break; + + utf8_reader.next(); + } + + return seek_center; +} + +void LineEdit::UpdateTextClipping() +{ + Canvas* canvas = GetCanvas(); + if (!canvas) + return; + + 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; + + Rect cursor_rect = GetCursorRect(); + + UTF8Reader utf8_reader(text.data(), text.length()); + double width = GetWidth(); + while (cursor_rect.x + cursor_rect.width > width) + { + utf8_reader.set_position(clip_start_offset); + utf8_reader.next(); + clip_start_offset = (int)utf8_reader.position(); + if (clip_start_offset == text.size()) + break; + cursor_rect = GetCursorRect(); + } + + // Get number of chars of current text fitting in the lineedit. + int search_upper = (int)text.size(); + int search_lower = clip_start_offset; + + while (true) + { + int midpoint = (search_lower + search_upper) / 2; + + utf8_reader.set_position(midpoint); + utf8_reader.move_to_leadbyte(); + if (midpoint != utf8_reader.position()) + utf8_reader.next(); + midpoint = (int)utf8_reader.position(); + + if (midpoint == search_lower || midpoint == search_upper) + break; + + Size midpoint_size = GetVisualTextSize(canvas, clip_start_offset, midpoint - clip_start_offset); + + if (width < midpoint_size.width) + search_upper = midpoint; + else + search_lower = midpoint; + } + clip_end_offset = search_upper; + + if (cursor_rect.x < 0.0) + { + clip_start_offset = cursor_pos; + } +} + +Rect LineEdit::GetCursorRect() +{ + Canvas* canvas = GetCanvas(); + + int substr_end = cursor_pos - clip_start_offset; + if (substr_end < 0) + substr_end = 0; + + std::string clipped_text = text.substr(clip_start_offset, substr_end); + + if (password_mode) + { + // If we are in password mode, we gonna return the right characters + clipped_text = CreatePassword(UTF8Reader::utf8_length(clipped_text)); + } + + Size text_size_before_cursor = canvas->measureText(clipped_text).size(); + + Rect cursor_rect; + cursor_rect.x = text_size_before_cursor.width; + cursor_rect.width = 1.0f; + + cursor_rect.y = vertical_text_align.top; + cursor_rect.height = vertical_text_align.bottom - vertical_text_align.top; + + return cursor_rect; +} + +Rect LineEdit::GetSelectionRect() +{ + Canvas* canvas = GetCanvas(); + + // text before selection: + + std::string txt_before = GetVisibleTextBeforeSelection(); + Size text_size_before_selection = canvas->measureText(txt_before).size(); + + // selection text: + std::string txt_selected = GetVisibleSelectedText(); + Size text_size_selection = canvas->measureText(txt_selected).size(); + + Rect selection_rect; + selection_rect.x = text_size_before_selection.width; + selection_rect.width = text_size_selection.width; + selection_rect.y = vertical_text_align.top; + selection_rect.height = vertical_text_align.bottom - vertical_text_align.top; + return selection_rect; +} + +int LineEdit::FindNextBreakCharacter(int search_start) +{ + if (search_start >= int(text.size()) - 1) + return (int)text.size(); + + size_t pos = text.find_first_of(break_characters, search_start); + if (pos == std::string::npos) + return (int)text.size(); + return (int)pos; +} + +int LineEdit::FindPreviousBreakCharacter(int search_start) +{ + if (search_start <= 0) + return 0; + size_t pos = text.find_last_of(break_characters, search_start); + if (pos == std::string::npos) + return 0; + return (int)pos; +} + +void LineEdit::OnTimerExpired() +{ + if (!IsVisible()) + { + timer->Stop(); + return; + } + + if (cursor_blink_visible) + timer->Start(500); + else + timer->Start(500); + + cursor_blink_visible = !cursor_blink_visible; + Update(); +} + +void LineEdit::OnGeometryChanged() +{ + Canvas* canvas = GetCanvas(); + + vertical_text_align = canvas->verticalTextAlign(); + + clip_start_offset = 0; + cursor_pos = 0; + UpdateTextClipping(); +} + +std::string LineEdit::GetVisibleTextBeforeSelection() +{ + std::string ret; + int sel_start = std::min(selection_start, selection_start + selection_length); + int start = std::min(sel_start, clip_start_offset); + + if (start < clip_start_offset) + return ret; + + int end = std::min(sel_start, clip_end_offset); + + ret = text.substr(start, end - start); + + // If we are in password mode, we gonna return the right characters + if (password_mode) + ret = CreatePassword(UTF8Reader::utf8_length(ret)); + + return ret; +} + +std::string LineEdit::GetVisibleSelectedText() +{ + std::string ret; + + if (selection_length == 0) + return ret; + + int sel_start = std::min(selection_start, selection_start + selection_length); + int sel_end = std::max(selection_start, selection_start + selection_length); + int end = std::min(clip_end_offset, sel_end); + int start = std::max(clip_start_offset, sel_start); + + if (start > end) + return ret; + + if (start == end) + return ret; + + ret = text.substr(start, end - start); + + // If we are in password mode, we gonna return the right characters + if (password_mode) + ret = CreatePassword(UTF8Reader::utf8_length(ret)); + + return ret; +} + +void LineEdit::SetSelectionStart(int start) +{ + if (FuncSelectionChanged && selection_length && selection_start != start) + FuncSelectionChanged(); + + selection_start = start; +} + +void LineEdit::SetSelectionLength(int length) +{ + if (FuncSelectionChanged && selection_length != length) + FuncSelectionChanged(); + + selection_length = length; +} + +void LineEdit::SetTextSelection(int start, int length) +{ + if (FuncSelectionChanged && (selection_length != length || (selection_length && selection_start != start))) + FuncSelectionChanged(); + + selection_start = start; + selection_length = length; +} + +std::string LineEdit::GetVisibleTextAfterSelection() +{ + // returns the whole visible string if there is no selection. + std::string ret; + + int sel_end = std::max(selection_start, selection_start + selection_length); + int start = std::max(clip_start_offset, sel_end); + + int end = clip_end_offset; + if (start > end) + return ret; + + if (clip_end_offset == sel_end) + return ret; + + if (sel_end <= 0) + return ret; + else + { + ret = text.substr(start, end - start); + // If we are in password mode, we gonna return the right characters + if (password_mode) + ret = CreatePassword(UTF8Reader::utf8_length(ret)); + + return ret; + } +} + +void LineEdit::OnPaint(Canvas* canvas) +{ + std::string txt_before = GetVisibleTextBeforeSelection(); + std::string txt_selected = GetVisibleSelectedText(); + std::string txt_after = GetVisibleTextAfterSelection(); + + if (txt_before.empty() && txt_selected.empty() && txt_after.empty()) + { + txt_after = text.substr(clip_start_offset, clip_end_offset - clip_start_offset); + + // If we are in password mode, we gonna return the right characters + if (password_mode) + txt_after = CreatePassword(UTF8Reader::utf8_length(txt_after)); + } + + Size size_before = canvas->measureText(txt_before).size(); + Size size_selected = canvas->measureText(txt_selected).size(); + + if (!txt_selected.empty()) + { + // Draw selection box. + Rect selection_rect = GetSelectionRect(); + canvas->fillRect(selection_rect, HasFocus() ? GetStyleColor("selection-color") : GetStyleColor("no-focus-selection-color")); + } + + // Draw text before selection + if (!txt_before.empty()) + { + canvas->drawText(Point(0.0, canvas->verticalTextAlign().baseline), GetStyleColor("color"), txt_before); + } + if (!txt_selected.empty()) + { + canvas->drawText(Point(size_before.width, canvas->verticalTextAlign().baseline), GetStyleColor("color"), txt_selected); + } + if (!txt_after.empty()) + { + canvas->drawText(Point(size_before.width + size_selected.width, canvas->verticalTextAlign().baseline), GetStyleColor("color"), txt_after); + } + + // draw cursor + if (HasFocus()) + { + if (cursor_blink_visible) + { + Rect cursor_rect = GetCursorRect(); + canvas->fillRect(cursor_rect, GetStyleColor("color")); + } + } +} + +void LineEdit::OnScrollTimerExpired() +{ + if (mouse_moves_left) + Move(-1, false, false); + else + Move(1, false, false); +} + +void LineEdit::OnEnableChanged() +{ + bool enabled = IsEnabled(); + + if (!enabled) + { + cursor_blink_visible = false; + timer->Stop(); + } + Update(); +} + +bool LineEdit::InputMaskAcceptsInput(int cursor_pos, const std::string& str) +{ + return str.find_first_not_of(input_mask) == std::string::npos; +} + +std::string LineEdit::CreatePassword(std::string::size_type num_letters) const +{ + return std::string(num_letters, '*'); +} + +Size LineEdit::GetVisualTextSize(Canvas* canvas, int pos, int npos) const +{ + return canvas->measureText(password_mode ? CreatePassword(UTF8Reader::utf8_length(text.substr(pos, npos))) : text.substr(pos, npos)).size(); +} + +Size LineEdit::GetVisualTextSize(Canvas* canvas) const +{ + return canvas->measureText(password_mode ? CreatePassword(UTF8Reader::utf8_length(text)) : text).size(); +} + +std::string LineEdit::ToFixed(float number, int num_decimal_places) +{ + for (int i = 0; i < num_decimal_places; i++) + number *= 10.0f; + std::string val = std::to_string((int)std::round(number)); + if ((int)val.size() < num_decimal_places) + val.resize(num_decimal_places + 1, 0); + return val.substr(0, val.size() - num_decimal_places) + "." + val.substr(val.size() - num_decimal_places); +} + +std::string LineEdit::ToLower(const std::string& text) +{ + return text; +} + +std::string LineEdit::ToUpper(const std::string& text) +{ + return text; +} + +const std::string LineEdit::break_characters = " ::;,.-"; +const std::string LineEdit::numeric_mode_characters = "0123456789"; diff --git a/src/widgets/listview/listview.cpp b/src/widgets/listview/listview.cpp new file mode 100644 index 000000000..1dd9fc9c1 --- /dev/null +++ b/src/widgets/listview/listview.cpp @@ -0,0 +1,286 @@ + +#include "widgets/listview/listview.h" +#include "widgets/scrollbar/scrollbar.h" + +ListView::ListView(Widget* parent) : Widget(parent) +{ + SetStyleClass("listview"); + + scrollbar = new Scrollbar(this); + scrollbar->FuncScroll = [=]() { OnScrollbarScroll(); }; + + SetColumnWidths({ 0.0 }); +} + +void ListView::SetColumnWidths(const std::vector& widths) +{ + columnwidths = widths; + + bool updated = false; + const size_t newWidth = columnwidths.size(); + for (std::vector& column : items) + { + while (column.size() < newWidth) + { + updated = true; + column.push_back(""); + } + while (column.size() > newWidth) + { + updated = true; + column.pop_back(); + } + } + + if (updated) + Update(); +} + +void ListView::AddItem(const std::string& text, int index, int column) +{ + if (column < 0 || column >= columnwidths.size()) + return; + + std::vector newEntry; + for (size_t i = 0u; i < columnwidths.size(); ++i) + newEntry.push_back(""); + + newEntry[column] = text; + if (index >= 0) + { + if (index >= items.size()) + { + newEntry[column] = ""; + while (items.size() < index) + items.push_back(newEntry); + + newEntry[column] = text; + items.push_back(newEntry); + } + else + { + items.insert(items.begin() + index, newEntry); + } + } + else + { + items.push_back(newEntry); + } + scrollbar->SetRanges(GetHeight(), items.size() * getItemHeight()); + Update(); +} + +void ListView::UpdateItem(const std::string& text, int index, int column) +{ + if (index < 0 || index >= items.size() || column < 0 || column >= columnwidths.size()) + return; + + items[index][column] = text; + Update(); +} + +void ListView::RemoveItem(int index) +{ + if (!items.size() || index >= items.size()) + return; + + if (index < 0) + index = static_cast(items.size()) - 1; + + if (selectedItem == index) + SetSelectedItem(0); + + items.erase(items.begin() + index); + scrollbar->SetRanges(GetHeight(), items.size() * getItemHeight()); + Update(); +} + +void ListView::Activate() +{ + if (OnActivated) + OnActivated(); +} + +void ListView::SetSelectedItem(int index) +{ + if (selectedItem != index && index >= 0 && index < items.size()) + { + selectedItem = index; + if (OnChanged) OnChanged(selectedItem); + Update(); + } +} + +double ListView::getItemHeight() +{ + return 20.0; +} + +void ListView::ScrollToItem(int index) +{ + double itemHeight = getItemHeight(); + double y = itemHeight * index; + if (y < scrollbar->GetPosition()) + { + scrollbar->SetPosition(y); + } + else if (y + itemHeight > scrollbar->GetPosition() + GetHeight()) + { + scrollbar->SetPosition(std::max(y + itemHeight - GetHeight(), 0.0)); + } +} + +void ListView::OnScrollbarScroll() +{ + Update(); +} + +void ListView::OnGeometryChanged() +{ + double w = GetWidth(); + double h = GetHeight(); + double sw = scrollbar->GetPreferredWidth(); + scrollbar->SetFrameGeometry(Rect::xywh(w - sw, 0.0, sw, h)); + scrollbar->SetRanges(h, items.size() * getItemHeight()); +} + +void ListView::OnPaint(Canvas* canvas) +{ + double y = -scrollbar->GetPosition(); + double x = 2.0; + double w = GetWidth() - scrollbar->GetPreferredWidth() - 2.0; + double h = getItemHeight(); + + Colorf textColor = GetStyleColor("color"); + Colorf selectionColor = GetStyleColor("selection-color"); + + // Make sure the text doesn't enter the scrollbar's area. + canvas->pushClip({ 0.0, 0.0, w, GetHeight() }); + + int index = 0; + for (const std::vector& item : items) + { + double itemY = y; + if (itemY + h >= 0.0 && itemY < GetHeight()) + { + if (index == selectedItem) + { + canvas->fillRect(Rect::xywh(x - 2.0, itemY, w, h), selectionColor); + } + double cx = x; + for (size_t entry = 0u; entry < item.size(); ++entry) + { + canvas->drawText(Point(cx, y + 15.0), textColor, item[entry]); + cx += columnwidths[entry]; + } + } + y += h; + index++; + } + + canvas->popClip(); +} + +bool ListView::OnMouseDown(const Point& pos, InputKey key) +{ + SetFocus(); + + if (key == InputKey::LeftMouse) + { + int index = (int)((pos.y - 5.0 + scrollbar->GetPosition()) / getItemHeight()); + if (index >= 0 && (size_t)index < items.size()) + { + SetSelectedItem(index); + ScrollToItem(selectedItem); + } + } + return true; +} + +bool ListView::OnMouseDoubleclick(const Point& pos, InputKey key) +{ + if (key == InputKey::LeftMouse) + { + Activate(); + } + return true; +} + +bool ListView::OnMouseWheel(const Point& pos, InputKey key) +{ + if (key == InputKey::MouseWheelUp) + { + scrollbar->SetPosition(std::max(scrollbar->GetPosition() - getItemHeight(), 0.0)); + } + else if (key == InputKey::MouseWheelDown) + { + scrollbar->SetPosition(std::min(scrollbar->GetPosition() + getItemHeight(), scrollbar->GetMax())); + } + return true; +} + +void ListView::OnKeyDown(InputKey key) +{ + if (key == InputKey::Down) + { + if (selectedItem + 1 < (int)items.size()) + { + SetSelectedItem(selectedItem + 1); + } + ScrollToItem(selectedItem); + } + else if (key == InputKey::Up) + { + if (selectedItem > 0) + { + SetSelectedItem(selectedItem - 1); + } + ScrollToItem(selectedItem); + } + else if (key == InputKey::Enter) + { + Activate(); + } + else if (key == InputKey::Home) + { + if (selectedItem > 0) + { + SetSelectedItem(0); + } + ScrollToItem(selectedItem); + } + else if (key == InputKey::End) + { + if (selectedItem + 1 < (int)items.size()) + { + SetSelectedItem((int)items.size() - 1); + } + ScrollToItem(selectedItem); + } + else if (key == InputKey::PageUp) + { + double h = GetHeight(); + if (h <= 0.0 || items.empty()) + return; + int itemsPerPage = (int)std::max(std::round(h / getItemHeight()), 1.0); + int nextItem = std::max(selectedItem - itemsPerPage, 0); + if (nextItem != selectedItem) + { + SetSelectedItem(nextItem); + ScrollToItem(selectedItem); + } + } + else if (key == InputKey::PageDown) + { + double h = GetHeight(); + if (h <= 0.0 || items.empty()) + return; + int itemsPerPage = (int)std::max(std::round(h / getItemHeight()), 1.0); + int prevItem = std::min(selectedItem + itemsPerPage, (int)items.size() - 1); + if (prevItem != selectedItem) + { + SetSelectedItem(prevItem); + ScrollToItem(selectedItem); + } + } +} diff --git a/src/widgets/mainwindow/mainwindow.cpp b/src/widgets/mainwindow/mainwindow.cpp new file mode 100644 index 000000000..a59680a4e --- /dev/null +++ b/src/widgets/mainwindow/mainwindow.cpp @@ -0,0 +1,44 @@ + +#include "widgets/mainwindow/mainwindow.h" +#include "widgets/menubar/menubar.h" +#include "widgets/toolbar/toolbar.h" +#include "widgets/statusbar/statusbar.h" + +MainWindow::MainWindow(RenderAPI api) : Widget(nullptr, WidgetType::Window, api) +{ + MenubarWidget = new Menubar(this); + TopToolbarWidget = new Toolbar(this); + TopToolbarWidget->SetDirection(ToolbarDirection::Horizontal); + LeftToolbarWidget = new Toolbar(this); + LeftToolbarWidget->SetDirection(ToolbarDirection::Vertical); + StatusbarWidget = new Statusbar(this); +} + +MainWindow::~MainWindow() +{ +} + +void MainWindow::SetCentralWidget(Widget* widget) +{ + if (CentralWidget != widget) + { + delete CentralWidget; + CentralWidget = widget; + if (CentralWidget) + CentralWidget->SetParent(this); + OnGeometryChanged(); + } +} + +void MainWindow::OnGeometryChanged() +{ + Size s = GetSize(); + + MenubarWidget->SetFrameGeometry(0.0, 0.0, s.width, 32.0); + TopToolbarWidget->SetFrameGeometry(0.0, 32.0, s.width, 32.0); + LeftToolbarWidget->SetFrameGeometry(0.0, 64.0, 32.0, s.height - 64.0); + StatusbarWidget->SetFrameGeometry(0.0, s.height - 32.0, s.width, 32.0); + + if (CentralWidget) + CentralWidget->SetFrameGeometry(32.0, 64.0, s.width - 32.0, s.height - 64.0 - 32.0); +} diff --git a/src/widgets/menubar/menubar.cpp b/src/widgets/menubar/menubar.cpp new file mode 100644 index 000000000..37353756f --- /dev/null +++ b/src/widgets/menubar/menubar.cpp @@ -0,0 +1,390 @@ + +#include "widgets/menubar/menubar.h" +#include "core/colorf.h" + +Menubar::Menubar(Widget* parent) : Widget(parent) +{ + SetStyleClass("menubar"); +} + +Menubar::~Menubar() +{ +} + +MenubarItem* Menubar::AddItem(std::string text, std::function onOpen, bool alignRight) +{ + auto item = new MenubarItem(this, text, alignRight); + item->SetOpenCallback(std::move(onOpen)); + menuItems.push_back(item); + OnGeometryChanged(); + return item; +} + +void Menubar::ShowMenu(MenubarItem* item) +{ + int index = GetItemIndex(item); + if (index == currentMenubarItem) + return; + + CloseMenu(); + SetFocus(); + SetModalCapture(); + currentMenubarItem = index; + if (currentMenubarItem != -1) + menuItems[currentMenubarItem]->SetStyleState("hover"); + modalMode = true; + if (item->GetOpenCallback()) + { + openMenu = new Menu(this); + openMenu->onCloseMenu = [=]() { CloseMenu(); }; + item->GetOpenCallback()(openMenu); + if (item->AlignRight) + openMenu->SetRightPosition(item->MapToGlobal(Point(item->GetWidth(), item->GetHeight()))); + else + openMenu->SetLeftPosition(item->MapToGlobal(Point(0.0, item->GetHeight()))); + openMenu->Show(); + } +} + +void Menubar::CloseMenu() +{ + if (currentMenubarItem != -1) + menuItems[currentMenubarItem]->SetStyleState(""); + currentMenubarItem = -1; + delete openMenu; + openMenu = nullptr; + ReleaseModalCapture(); + modalMode = false; +} + +void Menubar::OnGeometryChanged() +{ + double w = GetWidth(); + double h = GetHeight(); + double left = 0.0; + double right = w; + for (MenubarItem* item : menuItems) + { + double itemwidth = item->GetPreferredWidth(); + itemwidth += 16.0; + if (!item->AlignRight) + { + item->SetFrameGeometry(left, 0.0, itemwidth, h); + left += itemwidth; + } + else + { + right -= itemwidth; + item->SetFrameGeometry(right, 0.0, itemwidth, h); + } + } +} + +MenubarItem* Menubar::GetMenubarItemAt(const Point& pos) +{ + Widget* widget = ChildAt(pos); + for (MenubarItem* item : menuItems) + { + if (widget == item) + return item; + } + return nullptr; +} + +bool Menubar::OnMouseDown(const Point& pos, InputKey key) +{ + if (!modalMode) + return Widget::OnMouseDown(pos, key); + + MenubarItem* item = GetMenubarItemAt(pos); + if (item) + ShowMenu(item); + else + CloseMenu(); + return true; +} + +bool Menubar::OnMouseUp(const Point& pos, InputKey key) +{ + if (!modalMode) + return Widget::OnMouseUp(pos, key); + + MenubarItem* item = GetMenubarItemAt(pos); + if (!item) + CloseMenu(); + return true; +} + +void Menubar::OnMouseMove(const Point& pos) +{ + if (!modalMode) + return Widget::OnMouseMove(pos); + + MenubarItem* item = GetMenubarItemAt(pos); + if (item) + ShowMenu(item); +} + +int Menubar::GetItemIndex(MenubarItem* item) +{ + int i = 0; + for (MenubarItem* cur : menuItems) + { + if (cur == item) + return i; + i++; + } + return -1; +} + +void Menubar::OnKeyDown(InputKey key) +{ + if (!modalMode) + return Widget::OnKeyDown(key); + + if (key == InputKey::Left) + { + if (!menuItems.empty()) + { + int index = currentMenubarItem - 1; + if (index < 0) + index = (int)menuItems.size() - 1; + ShowMenu(menuItems[index]); + } + } + else if (key == InputKey::Right) + { + if (!menuItems.empty()) + { + int index = currentMenubarItem + 1; + if (index == (int)menuItems.size()) + index = 0; + ShowMenu(menuItems[index]); + } + } + else if (key == InputKey::Up) + { + if (openMenu) + { + // Keep trying until we don't find a separator + for (int i = 0; i < 10; i++) + { + if (!openMenu->selectedItem || !openMenu->selectedItem->PrevSibling()) + { + if (openMenu->LastChild()) + openMenu->SetSelected(static_cast(openMenu->LastChild())); + } + else + { + openMenu->SetSelected(static_cast(openMenu->selectedItem->PrevSibling())); + } + + if (openMenu->selectedItem && openMenu->selectedItem->GetStyleClass() != "menuitemseparator") + break; + } + } + } + else if (key == InputKey::Down) + { + if (openMenu) + { + // Keep trying until we don't find a separator + for (int i = 0; i < 10; i++) + { + if (!openMenu->selectedItem || !openMenu->selectedItem->NextSibling()) + { + if (openMenu->FirstChild()) + openMenu->SetSelected(static_cast(openMenu->FirstChild())); + } + else + { + openMenu->SetSelected(static_cast(openMenu->selectedItem->NextSibling())); + } + + if (openMenu->selectedItem && openMenu->selectedItem->GetStyleClass() != "menuitemseparator") + break; + } + } + } + else if (key == InputKey::Enter) + { + if (openMenu && openMenu->selectedItem) + openMenu->selectedItem->Click(); + } +} + +///////////////////////////////////////////////////////////////////////////// + +MenubarItem::MenubarItem(Menubar* menubar, std::string text, bool alignRight) : Widget(menubar), menubar(menubar), text(text), AlignRight(alignRight) +{ + SetStyleClass("menubaritem"); +} + +bool MenubarItem::OnMouseDown(const Point& pos, InputKey key) +{ + menubar->ShowMenu(this); + return true; +} + +bool MenubarItem::OnMouseUp(const Point& pos, InputKey key) +{ + return true; +} + +void MenubarItem::OnMouseMove(const Point& pos) +{ + if (GetStyleState().empty()) + { + SetStyleState("hover"); + } +} + +void MenubarItem::OnMouseLeave() +{ + SetStyleState(""); +} + +double MenubarItem::GetPreferredWidth() const +{ + Canvas* canvas = GetCanvas(); + return canvas->measureText(text).width; +} + +void MenubarItem::OnPaint(Canvas* canvas) +{ + double x = (GetWidth() - canvas->measureText(text).width) * 0.5; + canvas->drawText(Point(x, 21.0), GetStyleColor("color"), text); +} + +///////////////////////////////////////////////////////////////////////////// + +Menu::Menu(Widget* parent) : Widget(parent, WidgetType::Popup) +{ + SetStyleClass("menu"); +} + +void Menu::SetLeftPosition(const Point& pos) +{ + SetFrameGeometry(Rect::xywh(pos.x, pos.y, GetPreferredWidth() + GetNoncontentLeft() + GetNoncontentRight(), GetPreferredHeight() + GetNoncontentTop() + GetNoncontentBottom())); +} + +void Menu::SetRightPosition(const Point& pos) +{ + SetFrameGeometry(Rect::xywh(pos.x - GetWidth() - GetNoncontentLeft() - GetNoncontentRight(), pos.y, GetWidth() + GetNoncontentLeft() + GetNoncontentRight(), GetHeight() + GetNoncontentTop() + GetNoncontentBottom())); +} + +MenuItem* Menu::AddItem(std::shared_ptr icon, std::string text, std::function onClick) +{ + auto item = new MenuItem(this, [this, onClick]() { if (onCloseMenu) onCloseMenu(); if (onClick) onClick(); }); + if (icon) + item->icon->SetImage(icon); + item->text->SetText(text); + return item; +} + +MenuItemSeparator* Menu::AddSeparator() +{ + auto sep = new MenuItemSeparator(this); + return sep; +} + +double Menu::GetPreferredWidth() const +{ + return GridFitSize(200.0); +} + +double Menu::GetPreferredHeight() const +{ + double h = 0.0; + for (Widget* item = FirstChild(); item != nullptr; item = item->NextSibling()) + { + double itemheight = GridFitSize((item->GetStyleClass() == "menuitemseparator") ? 7.0 : 30.0); + h += itemheight; + } + return h; +} + +void Menu::OnGeometryChanged() +{ + double w = GetWidth(); + double y = 0.0; + for (Widget* item = FirstChild(); item != nullptr; item = item->NextSibling()) + { + double itemheight = GridFitSize((item->GetStyleClass() == "menuitemseparator") ? 7.0 : 30.0); + item->SetFrameGeometry(Rect::xywh(0.0, y, w, itemheight)); + y += itemheight; + } +} + +void Menu::SetSelected(MenuItem* item) +{ + if (selectedItem) + { + selectedItem->SetStyleState(""); + } + selectedItem = item; + if (selectedItem) + { + selectedItem->SetStyleState("hover"); + } +} + +///////////////////////////////////////////////////////////////////////////// + +MenuItem::MenuItem(Menu* menu, std::function onClick) : Widget(menu), menu(menu), onClick(onClick) +{ + SetStyleClass("menuitem"); + icon = new ImageBox(this); + text = new TextLabel(this); +} + +void MenuItem::OnMouseMove(const Point& pos) +{ + menu->SetSelected(this); +} + +bool MenuItem::OnMouseUp(const Point& pos, InputKey key) +{ + Click(); + return true; +} + +void MenuItem::Click() +{ + if (onClick) + { + // We have to make a copy of the handler as it may delete 'this' + auto handler = onClick; + handler(); + } +} + +void MenuItem::OnMouseLeave() +{ + menu->SetSelected(nullptr); +} + +void MenuItem::OnGeometryChanged() +{ + double iconwidth = GridFitSize(icon->GetPreferredWidth()); + double iconheight = GridFitSize(icon->GetPreferredHeight()); + double w = GetWidth(); + double h = GetHeight(); + double textheight = 19.0; + double x0 = GridFitPoint(5.0); + double x1 = GridFitPoint(5.0 + iconwidth); + icon->SetFrameGeometry(Rect::xywh(x0, GridFitPoint((h - iconheight) * 0.5), iconwidth, iconheight)); + text->SetFrameGeometry(Rect::xywh(x1, GridFitPoint((h - textheight) * 0.5), w - x1, textheight)); +} + +///////////////////////////////////////////////////////////////////////////// + +MenuItemSeparator::MenuItemSeparator(Widget* parent) : Widget(parent) +{ + SetStyleClass("menuitemseparator"); +} + +void MenuItemSeparator::OnPaint(Canvas* canvas) +{ + canvas->fillRect(Rect::xywh(0.0, GridFitPoint(GetHeight() * 0.5), GetWidth(), GridFitSize(1.0)), Colorf::fromRgba8(75, 75, 75)); +} diff --git a/src/widgets/pushbutton/pushbutton.cpp b/src/widgets/pushbutton/pushbutton.cpp new file mode 100644 index 000000000..e85b2c557 --- /dev/null +++ b/src/widgets/pushbutton/pushbutton.cpp @@ -0,0 +1,88 @@ + +#include "widgets/pushbutton/pushbutton.h" + +PushButton::PushButton(Widget* parent) : Widget(parent) +{ + SetStyleClass("pushbutton"); +} + +void PushButton::SetText(const std::string& value) +{ + if (text != value) + { + text = value; + Update(); + } +} + +const std::string& PushButton::GetText() const +{ + return text; +} + +double PushButton::GetPreferredHeight() const +{ + return 30.0; +} + +void PushButton::OnPaint(Canvas* canvas) +{ + Rect box = canvas->measureText(text); + canvas->drawText(Point((GetWidth() - box.width) * 0.5, GetHeight() - 5.0), GetStyleColor("color"), text); +} + +void PushButton::OnMouseMove(const Point& pos) +{ + if (GetStyleState().empty()) + { + SetStyleState("hover"); + } +} + +bool PushButton::OnMouseDown(const Point& pos, InputKey key) +{ + SetFocus(); + SetStyleState("down"); + return true; +} + +bool PushButton::OnMouseUp(const Point& pos, InputKey key) +{ + if (GetStyleState() == "down") + { + SetStyleState(""); + Repaint(); + Click(); + } + return true; +} + +void PushButton::OnMouseLeave() +{ + SetStyleState(""); +} + +void PushButton::OnKeyDown(InputKey key) +{ + if (key == InputKey::Space || key == InputKey::Enter) + { + SetStyleState("down"); + Update(); + } +} + +void PushButton::OnKeyUp(InputKey key) +{ + if (key == InputKey::Space || key == InputKey::Enter) + { + SetStyleState(""); + Repaint(); + Click(); + } +} + +void PushButton::Click() +{ + if (OnClick) + OnClick(); +} diff --git a/src/widgets/scrollbar/scrollbar.cpp b/src/widgets/scrollbar/scrollbar.cpp new file mode 100644 index 000000000..c4e29329a --- /dev/null +++ b/src/widgets/scrollbar/scrollbar.cpp @@ -0,0 +1,409 @@ + +#include "widgets/scrollbar/scrollbar.h" +#include "core/colorf.h" +#include + +Scrollbar::Scrollbar(Widget* parent) : Widget(parent) +{ + SetStyleClass("scrollbar"); + UpdatePartPositions(); + + mouse_down_timer = new Timer(this); + mouse_down_timer->FuncExpired = [=]() { OnTimerExpired(); }; +} + +Scrollbar::~Scrollbar() +{ +} + +bool Scrollbar::IsVertical() const +{ + return vertical; +} + +bool Scrollbar::IsHorizontal() const +{ + return !vertical; +} + +double Scrollbar::GetMin() const +{ + return scroll_min; +} + +double Scrollbar::GetMax() const +{ + return scroll_max; +} + +double Scrollbar::GetLineStep() const +{ + return line_step; +} + +double Scrollbar::GetPageStep() const +{ + return page_step; +} + +double Scrollbar::GetPosition() const +{ + return position; +} + +void Scrollbar::SetVertical() +{ + vertical = true; + if (UpdatePartPositions()) + Update(); +} + +void Scrollbar::SetHorizontal() +{ + vertical = false; + if (UpdatePartPositions()) + Update(); +} + +void Scrollbar::SetMin(double new_scroll_min) +{ + SetRanges(new_scroll_min, scroll_max, line_step, page_step); +} + +void Scrollbar::SetMax(double new_scroll_max) +{ + SetRanges(scroll_min, new_scroll_max, line_step, page_step); +} + +void Scrollbar::SetLineStep(double step) +{ + SetRanges(scroll_min, scroll_max, step, page_step); +} + +void Scrollbar::SetPageStep(double step) +{ + SetRanges(scroll_min, scroll_max, line_step, step); +} + +void Scrollbar::SetRanges(double new_scroll_min, double new_scroll_max, double new_line_step, double new_page_step) +{ + if (new_scroll_min >= new_scroll_max || new_line_step <= 0.0 || new_page_step <= 0.0) + throw std::runtime_error("Scrollbar ranges out of bounds!"); + scroll_min = new_scroll_min; + scroll_max = new_scroll_max; + line_step = new_line_step; + page_step = new_page_step; + if (position >= scroll_max) + position = scroll_max - 1.0; + if (position < scroll_min) + position = scroll_min; + if (UpdatePartPositions()) + Update(); +} + +void Scrollbar::SetRanges(double view_size, double total_size) +{ + if (view_size <= 0.0 || total_size <= 0.0) + { + SetRanges(0.0, 1.0, 1.0, 1.0); + } + else + { + double scroll_max = std::max(1.0, total_size - view_size + 1.0); + double page_step = std::max(1.0, view_size); + SetRanges(0.0, scroll_max, 10, page_step); + } +} + +void Scrollbar::SetPosition(double pos) +{ + position = pos; + if (pos >= scroll_max) + position = scroll_max - 1.0; + if (pos < scroll_min) + position = scroll_min; + + if (UpdatePartPositions()) + Update(); +} + +void Scrollbar::OnMouseMove(const Point& pos) +{ + if (mouse_down_mode == mouse_down_thumb_drag) + { + double last_position = position; + + if (pos.x < -100.0 || pos.x > GetWidth() + 100.0 || pos.y < -100.0 || pos.y > GetHeight() + 100.0) + { + position = thumb_start_position; + } + else + { + double delta = vertical ? (pos.y - mouse_drag_start_pos.y) : (pos.x - mouse_drag_start_pos.x); + double position_pixels = thumb_start_pixel_position + delta; + + double track_height = 0; + if (vertical) + track_height = rect_track_decrement.height + rect_track_increment.height; + else + track_height = rect_track_decrement.width + rect_track_increment.width; + + if (track_height != 0.0) + position = scroll_min + position_pixels * (scroll_max - scroll_min) / track_height; + else + position = 0; + + if (position >= scroll_max) + position = scroll_max - 1; + if (position < scroll_min) + position = scroll_min; + + } + + if (position != last_position) + { + InvokeScrollEvent(&FuncScrollThumbTrack); + UpdatePartPositions(); + } + } + + Update(); +} + +bool Scrollbar::OnMouseDown(const Point& pos, InputKey key) +{ + mouse_drag_start_pos = pos; + + if (rect_button_decrement.contains(pos)) + { + mouse_down_mode = mouse_down_button_decr; + FuncScrollOnMouseDown = &FuncScrollLineDecrement; + + double last_position = position; + + position -= line_step; + last_step_size = -line_step; + if (position >= scroll_max) + position = scroll_max - 1.0; + if (position < scroll_min) + position = scroll_min; + + if (last_position != position) + InvokeScrollEvent(&FuncScrollLineDecrement); + } + else if (rect_button_increment.contains(pos)) + { + mouse_down_mode = mouse_down_button_incr; + FuncScrollOnMouseDown = &FuncScrollLineIncrement; + + double last_position = position; + + position += line_step; + last_step_size = line_step; + if (position >= scroll_max) + position = scroll_max - 1.0; + if (position < scroll_min) + position = scroll_min; + + if (last_position != position) + InvokeScrollEvent(&FuncScrollLineIncrement); + } + else if (rect_thumb.contains(pos)) + { + mouse_down_mode = mouse_down_thumb_drag; + thumb_start_position = position; + thumb_start_pixel_position = vertical ? (rect_thumb.y - rect_track_decrement.y) : (rect_thumb.x - rect_track_decrement.x); + } + else if (rect_track_decrement.contains(pos)) + { + mouse_down_mode = mouse_down_track_decr; + FuncScrollOnMouseDown = &FuncScrollPageDecrement; + + double last_position = position; + + position -= page_step; + last_step_size = -page_step; + if (position >= scroll_max) + position = scroll_max - 1.0; + if (position < scroll_min) + position = scroll_min; + + if (last_position != position) + InvokeScrollEvent(&FuncScrollPageDecrement); + } + else if (rect_track_increment.contains(pos)) + { + mouse_down_mode = mouse_down_track_incr; + FuncScrollOnMouseDown = &FuncScrollPageIncrement; + + double last_position = position; + + position += page_step; + last_step_size = page_step; + if (position >= scroll_max) + position = scroll_max - 1.0; + if (position < scroll_min) + position = scroll_min; + + if (last_position != position) + InvokeScrollEvent(&FuncScrollPageIncrement); + } + + mouse_down_timer->Start(100, false); + + UpdatePartPositions(); + + Update(); + SetPointerCapture(); + return true; +} + +bool Scrollbar::OnMouseUp(const Point& pos, InputKey key) +{ + if (mouse_down_mode == mouse_down_thumb_drag) + { + if (FuncScrollThumbRelease) + FuncScrollThumbRelease(); + } + + mouse_down_mode = mouse_down_none; + mouse_down_timer->Stop(); + + Update(); + ReleasePointerCapture(); + return true; +} + +void Scrollbar::OnMouseLeave() +{ + Update(); +} + +void Scrollbar::OnGeometryChanged() +{ + UpdatePartPositions(); +} + +void Scrollbar::OnPaint(Canvas* canvas) +{ + /* + part_button_decrement.render_box(canvas, rect_button_decrement); + part_track_decrement.render_box(canvas, rect_track_decrement); + part_thumb.render_box(canvas, rect_thumb); + part_thumb_gripper.render_box(canvas, rect_thumb); + part_track_increment.render_box(canvas, rect_track_increment); + 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")); + canvas->fillRect(Rect::shrink(rect_thumb, 4.0, 0.0, 4.0, 0.0), GetStyleColor("thumb-color")); +} + +// Calculates positions of all parts. Returns true if thumb position was changed compared to previously, false otherwise. +bool Scrollbar::UpdatePartPositions() +{ + double decr_height = showbuttons ? 16.0 : 0.0; + double incr_height = showbuttons ? 16.0 : 0.0; + + double total_height = vertical ? GetHeight() : GetWidth(); + double track_height = std::max(0.0, total_height - decr_height - incr_height); + double thumb_height = CalculateThumbSize(track_height); + + double thumb_offset = decr_height + CalculateThumbPosition(thumb_height, track_height); + + Rect previous_rect_thumb = rect_thumb; + + rect_button_decrement = CreateRect(0.0, decr_height); + rect_track_decrement = CreateRect(decr_height, thumb_offset); + rect_thumb = CreateRect(thumb_offset, thumb_offset + thumb_height); + rect_track_increment = CreateRect(thumb_offset + thumb_height, decr_height + track_height); + rect_button_increment = CreateRect(decr_height + track_height, decr_height + track_height + incr_height); + + return (previous_rect_thumb != rect_thumb); +} + +double Scrollbar::CalculateThumbSize(double track_size) +{ + double minimum_thumb_size = 20.0; + double range = scroll_max - scroll_min; + double length = range + page_step - 1; + double thumb_size = page_step * track_size / length; + if (thumb_size < minimum_thumb_size) + thumb_size = minimum_thumb_size; + if (thumb_size > track_size) + thumb_size = track_size; + return thumb_size; +} + +double Scrollbar::CalculateThumbPosition(double thumb_size, double track_size) +{ + double relative_pos = position - scroll_min; + double range = scroll_max - scroll_min - 1; + if (range != 0) + { + double available_area = std::max(0.0, track_size - thumb_size); + return relative_pos * available_area / range; + } + else + { + return 0; + } +} + +Rect Scrollbar::CreateRect(double start, double end) +{ + if (vertical) + return Rect(0.0, start, GetWidth(), end - start); + else + return Rect(start, 0.0, end - start, GetHeight()); +} + +void Scrollbar::OnTimerExpired() +{ + if (mouse_down_mode == mouse_down_thumb_drag) + return; + + mouse_down_timer->Start(100, false); + + double last_position = position; + position += last_step_size; + if (position >= scroll_max) + position = scroll_max - 1; + + if (position < scroll_min) + position = scroll_min; + + if (position != last_position) + { + InvokeScrollEvent(FuncScrollOnMouseDown); + + if (UpdatePartPositions()) + Update(); + } +} + +void Scrollbar::OnEnableChanged() +{ + Update(); +} + +void Scrollbar::InvokeScrollEvent(std::function* event_ptr) +{ + if (position == scroll_max - 1) + { + if (FuncScrollMax) + FuncScrollMax(); + } + + if (position == scroll_min) + { + if (FuncScrollMin) + FuncScrollMin(); + } + + if (FuncScroll) + FuncScroll(); + + if (event_ptr && *event_ptr) + (*event_ptr)(); +} diff --git a/src/widgets/statusbar/statusbar.cpp b/src/widgets/statusbar/statusbar.cpp new file mode 100644 index 000000000..33faa15c3 --- /dev/null +++ b/src/widgets/statusbar/statusbar.cpp @@ -0,0 +1,21 @@ + +#include "widgets/statusbar/statusbar.h" +#include "widgets/lineedit/lineedit.h" +#include "core/colorf.h" + +Statusbar::Statusbar(Widget* parent) : Widget(parent) +{ + SetStyleClass("statusbar"); + + CommandEdit = new LineEdit(this); + CommandEdit->SetFrameGeometry(Rect::xywh(90.0, 4.0, 400.0, 23.0)); +} + +Statusbar::~Statusbar() +{ +} + +void Statusbar::OnPaint(Canvas* canvas) +{ + canvas->drawText(Point(16.0, 21.0), Colorf::fromRgba8(226, 223, 219), "Command:"); +} diff --git a/src/widgets/tabwidget/tabwidget.cpp b/src/widgets/tabwidget/tabwidget.cpp new file mode 100644 index 000000000..ef935a9e8 --- /dev/null +++ b/src/widgets/tabwidget/tabwidget.cpp @@ -0,0 +1,345 @@ + +#include "widgets/tabwidget/tabwidget.h" +#include "widgets/textlabel/textlabel.h" +#include "widgets/imagebox/imagebox.h" +#include + +TabWidget::TabWidget(Widget* parent) : Widget(parent) +{ + Bar = new TabBar(this); + PageStack = new TabWidgetStack(this); + + Bar->OnCurrentChanged = [=]() { OnBarCurrentChanged(); }; +} + +int TabWidget::AddTab(Widget* page, const std::string& label) +{ + return AddTab(page, nullptr, label); +} + +int TabWidget::AddTab(Widget* page, const std::shared_ptr& icon, const std::string& label) +{ + int pageIndex = Bar->AddTab(label); + page->SetParent(PageStack); + page->SetVisible(false); + Pages.push_back(page); + if (Pages.size() == 1) + { + PageStack->SetCurrentWidget(page); + } + return pageIndex; +} + +void TabWidget::SetTabText(int index, const std::string& text) +{ + Bar->SetTabText(index, text); +} + +void TabWidget::SetTabText(Widget* page, const std::string& text) +{ + int index = GetPageIndex(page); + if (index != -1) + SetTabText(index, text); +} + +void TabWidget::SetTabIcon(int index, const std::shared_ptr& icon) +{ + Bar->SetTabIcon(index, icon); +} + +void TabWidget::SetTabIcon(Widget* page, const std::shared_ptr& icon) +{ + int index = GetPageIndex(page); + if (index != -1) + SetTabIcon(index, icon); +} + +int TabWidget::GetCurrentIndex() const +{ + return Bar->GetCurrentIndex(); +} + +Widget* TabWidget::GetCurrentWidget() const +{ + return Pages[Bar->GetCurrentIndex()]; +} + +void TabWidget::SetCurrentIndex(int pageIndex) +{ + if (Bar->GetCurrentIndex() != pageIndex) + { + Bar->SetCurrentIndex(pageIndex); + PageStack->SetCurrentWidget(Pages[pageIndex]); + } +} + +void TabWidget::SetCurrentWidget(Widget* pageWidget) +{ + int pageIndex = GetPageIndex(pageWidget); + if (pageIndex != -1) + SetCurrentIndex(pageIndex); +} + +int TabWidget::GetPageIndex(Widget* pageWidget) const +{ + for (size_t i = 0; i < Pages.size(); i++) + { + if (Pages[i] == pageWidget) + return (int)i; + } + return -1; +} + +void TabWidget::OnBarCurrentChanged() +{ + int pageIndex = Bar->GetCurrentIndex(); + PageStack->SetCurrentWidget(Pages[pageIndex]); + if (OnCurrentChanged) + OnCurrentChanged(); +} + +void TabWidget::OnGeometryChanged() +{ + double w = GetWidth(); + double h = GetHeight(); + double barHeight = Bar->GetPreferredHeight(); + Bar->SetFrameGeometry(Rect::xywh(0.0, 0.0, w, barHeight)); + PageStack->SetFrameGeometry(Rect::xywh(0.0, barHeight, w, std::max(h - barHeight, 0.0))); +} + +///////////////////////////////////////////////////////////////////////////// + +TabBar::TabBar(Widget* parent) : Widget(parent) +{ + SetStyleClass("tabbar"); + + leftSpacer = new TabBarSpacer(this); + rightSpacer = new TabBarSpacer(this); +} + +int TabBar::AddTab(const std::string& label) +{ + return AddTab(nullptr, label); +} + +int TabBar::AddTab(const std::shared_ptr& icon, const std::string& label) +{ + TabBarTab* tab = new TabBarTab(this); + tab->SetIcon(icon); + tab->SetText(label); + tab->OnClick = [=]() { OnTabClicked(tab); }; + int pageIndex = (int)Tabs.size(); + Tabs.push_back(tab); + if (CurrentIndex == -1) + SetCurrentIndex(pageIndex); + OnGeometryChanged(); + return pageIndex; +} + +void TabBar::SetTabText(int index, const std::string& text) +{ + Tabs[index]->SetText(text); + OnGeometryChanged(); +} + +void TabBar::SetTabIcon(int index, const std::shared_ptr& icon) +{ + Tabs[index]->SetIcon(icon); + OnGeometryChanged(); +} + +int TabBar::GetCurrentIndex() const +{ + return CurrentIndex; +} + +void TabBar::SetCurrentIndex(int pageIndex) +{ + if (CurrentIndex != pageIndex) + { + if (CurrentIndex != -1) + Tabs[CurrentIndex]->SetCurrent(false); + CurrentIndex = pageIndex; + if (CurrentIndex != -1) + Tabs[CurrentIndex]->SetCurrent(true); + } +} + +void TabBar::OnTabClicked(TabBarTab* tab) +{ + int pageIndex = GetTabIndex(tab); + if (CurrentIndex != pageIndex) + { + SetCurrentIndex(pageIndex); + if (OnCurrentChanged) + OnCurrentChanged(); + } +} + +int TabBar::GetTabIndex(TabBarTab* tab) +{ + for (size_t i = 0; i < Tabs.size(); i++) + { + if (Tabs[i] == tab) + return (int)i; + } + return -1; +} + +void TabBar::OnGeometryChanged() +{ + double w = GetWidth(); + double h = GetHeight(); + double x = 0.0; + + leftSpacer->SetFrameGeometry(Rect::xywh(x, 0.0, GetStyleDouble("spacer-left"), h)); + x += GetStyleDouble("spacer-left"); + + for (TabBarTab* tab : Tabs) + { + double tabWidth = tab->GetNoncontentLeft() + tab->GetPreferredWidth() + tab->GetNoncontentRight(); + tab->SetFrameGeometry(Rect::xywh(x, 0.0, tabWidth, h)); + x += tabWidth; + } + + rightSpacer->SetFrameGeometry(Rect::xywh(x, 0.0, std::max(w - x, 0.0), h)); +} + +///////////////////////////////////////////////////////////////////////////// + +TabBarSpacer::TabBarSpacer(Widget* parent) : Widget(parent) +{ + SetStyleClass("tabbar-spacer"); +} + +///////////////////////////////////////////////////////////////////////////// + +TabBarTab::TabBarTab(Widget* parent) : Widget(parent) +{ + SetStyleClass("tabbar-tab"); +} + +void TabBarTab::SetText(const std::string& text) +{ + if (!text.empty()) + { + if (!Label) + { + Label = new TextLabel(this); + OnGeometryChanged(); + } + Label->SetText(text); + } + else + { + delete Label; + Label = nullptr; + OnGeometryChanged(); + } +} + +void TabBarTab::SetIcon(const std::shared_ptr& image) +{ + if (image) + { + if (!Icon) + { + Icon = new ImageBox(this); + OnGeometryChanged(); + } + Icon->SetImage(image); + } + else + { + delete Icon; + Icon = nullptr; + OnGeometryChanged(); + } +} + +void TabBarTab::SetCurrent(bool value) +{ + if (IsCurrent != value) + { + IsCurrent = value; + SetStyleState(value ? "active" : ""); + } +} + +double TabBarTab::GetPreferredWidth() const +{ + double x = Icon ? 32.0 + 5.0 : 0.0; + if (Label) x += Label->GetPreferredWidth(); + return x; +} + +void TabBarTab::OnGeometryChanged() +{ + double x = 0.0; + double w = GetWidth(); + double h = GetHeight(); + if (Icon) + { + Icon->SetFrameGeometry(Rect::xywh(x, (h - 32.0) * 0.5, 32.0, 32.0)); + x = 32.0 + 5.0; + } + if (Label) + { + Label->SetFrameGeometry(Rect::xywh(x, (h - Label->GetPreferredHeight()) * 0.5, std::max(w - x, 0.0), Label->GetPreferredHeight())); + } +} + +void TabBarTab::OnMouseMove(const Point& pos) +{ + if (GetStyleState().empty()) + { + SetStyleState("hover"); + } +} + +bool TabBarTab::OnMouseDown(const Point& pos, InputKey key) +{ + if (OnClick) + OnClick(); + return true; +} + +bool TabBarTab::OnMouseUp(const Point& pos, InputKey key) +{ + return true; +} + +void TabBarTab::OnMouseLeave() +{ + if (GetStyleState() == "hover") + SetStyleState(""); + Update(); +} + +///////////////////////////////////////////////////////////////////////////// + +TabWidgetStack::TabWidgetStack(Widget* parent) : Widget(parent) +{ + SetStyleClass("tabwidget-stack"); +} + +void TabWidgetStack::SetCurrentWidget(Widget* widget) +{ + if (widget != CurrentWidget) + { + if (CurrentWidget) + CurrentWidget->SetVisible(false); + CurrentWidget = widget; + if (CurrentWidget) + { + CurrentWidget->SetVisible(true); + OnGeometryChanged(); + } + } +} + +void TabWidgetStack::OnGeometryChanged() +{ + if (CurrentWidget) + CurrentWidget->SetFrameGeometry(Rect::xywh(0.0, 0.0, GetWidth(), GetHeight())); +} diff --git a/src/widgets/textedit/textedit.cpp b/src/widgets/textedit/textedit.cpp new file mode 100644 index 000000000..2381332a1 --- /dev/null +++ b/src/widgets/textedit/textedit.cpp @@ -0,0 +1,1045 @@ + +#include "widgets/textedit/textedit.h" +#include "widgets/scrollbar/scrollbar.h" +#include "core/utf8reader.h" +#include "core/colorf.h" +#include + +#ifdef _MSC_VER +#pragma warning(disable: 4267) // warning C4267: 'initializing': conversion from 'size_t' to 'int', possible loss of data +#endif + +TextEdit::TextEdit(Widget* parent) : Widget(parent) +{ + SetStyleClass("textedit"); + + timer = new Timer(this); + timer->FuncExpired = [=]() { OnTimerExpired(); }; + + scroll_timer = new Timer(this); + scroll_timer->FuncExpired = [=]() { OnScrollTimerExpired(); }; + + SetCursor(StandardCursor::ibeam); + + CreateComponents(); +} + +TextEdit::~TextEdit() +{ +} + +bool TextEdit::IsReadOnly() const +{ + return readonly; +} + +bool TextEdit::IsLowercase() const +{ + return lowercase; +} + +bool TextEdit::IsUppercase() const +{ + return uppercase; +} + +int TextEdit::GetMaxLength() const +{ + return max_length; +} + +std::string TextEdit::GetLineText(int line) const +{ + if (line >= 0 && line < (int)lines.size()) + return lines[line].text; + else + return std::string(); +} + +std::string TextEdit::GetText() const +{ + std::string::size_type size = 0; + for (size_t i = 0; i < lines.size(); i++) + size += lines[i].text.size(); + size += lines.size() - 1; + + std::string text; + text.reserve(size); + + for (size_t i = 0; i < lines.size(); i++) + { + if (i > 0) + text.push_back('\n'); + text += lines[i].text; + } + + return text; +} + +int TextEdit::GetLineCount() const +{ + return (int)lines.size(); +} + +std::string TextEdit::GetSelection() const +{ + std::string::size_type offset = ToOffset(selection_start); + int start = (int)std::min(offset, offset + selection_length); + return GetText().substr(start, abs(selection_length)); +} + +int TextEdit::GetSelectionStart() const +{ + return (int)ToOffset(selection_start); +} + +int TextEdit::GetSelectionLength() const +{ + return selection_length; +} + +int TextEdit::GetCursorPos() const +{ + return (int)ToOffset(cursor_pos); +} + +int TextEdit::GetCursorLineNumber() const +{ + return cursor_pos.y; +} + +void TextEdit::SelectAll() +{ + std::string::size_type size = 0; + for (size_t i = 0; i < lines.size(); i++) + size += lines[i].text.size(); + size += lines.size() - 1; + SetSelection(0, (int)size); +} + +void TextEdit::SetReadOnly(bool enable) +{ + if (readonly != enable) + { + readonly = enable; + Update(); + } +} + +void TextEdit::SetLowercase(bool enable) +{ + if (lowercase != enable) + { + lowercase = enable; + Update(); + } +} + +void TextEdit::SetUppercase(bool enable) +{ + if (uppercase != enable) + { + uppercase = enable; + Update(); + } +} + +void TextEdit::SetMaxLength(int length) +{ + if (max_length != length) + { + max_length = length; + + std::string::size_type size = 0; + for (size_t i = 0; i < lines.size(); i++) + size += lines[i].text.size(); + size += lines.size() - 1; + + if ((int)size > length) + { + if (FuncBeforeEditChanged) + FuncBeforeEditChanged(); + SetSelection(length, (int)size - length); + DeleteSelectedText(); + if (FuncAfterEditChanged) + FuncAfterEditChanged(); + } + Update(); + } +} + +void TextEdit::SetText(const std::string& text) +{ + lines.clear(); + std::string::size_type start = 0; + std::string::size_type end = text.find('\n'); + while (end != std::string::npos) + { + TextEdit::Line line; + line.text = text.substr(start, end - start); + lines.push_back(line); + start = end + 1; + end = text.find('\n', start); + } + TextEdit::Line line; + line.text = text.substr(start); + lines.push_back(line); + + clip_start_offset = 0; + SetCursorPos(0); + ClearSelection(); + Update(); +} + +void TextEdit::AddText(const std::string& text) +{ + std::string::size_type start = 0; + std::string::size_type end = text.find('\n'); + while (end != std::string::npos) + { + TextEdit::Line line; + line.text = text.substr(start, end - start); + lines.push_back(line); + start = end + 1; + end = text.find('\n', start); + } + TextEdit::Line line; + line.text = text.substr(start); + lines.push_back(line); + + // clip_start_offset = 0; + // SetCursorPos(0); + ClearSelection(); + Update(); +} + +void TextEdit::SetSelection(int pos, int length) +{ + selection_start = ivec2(pos, 0); + selection_length = length; + Update(); +} + +void TextEdit::ClearSelection() +{ + SetSelection(0, 0); + Update(); +} + +void TextEdit::DeleteSelectedText() +{ + if (GetSelectionLength() == 0) + return; + + std::string::size_type offset = ToOffset(selection_start); + int start = (int)std::min(offset, offset + selection_length); + int length = std::abs(selection_length); + + ClearSelection(); + std::string text = GetText(); + SetText(text.substr(0, start) + text.substr(start + length)); + SetCursorPos(start); +} + +void TextEdit::SetCursorPos(int pos) +{ + cursor_pos = FromOffset(pos); + Update(); +} + +void TextEdit::SetInputMask(const std::string& mask) +{ + input_mask = mask; +} + +void TextEdit::SetCursorDrawingEnabled(bool enable) +{ + if (!readonly) + timer->Start(500); +} + +void TextEdit::SetSelectAllOnFocusGain(bool enable) +{ + select_all_on_focus_gain = enable; +} + +void TextEdit::OnMouseMove(const Point& pos) +{ + if (mouse_selecting && !ignore_mouse_events) + { + if (pos.x < 0.0 || pos.x > GetWidth()) + { + if (pos.x < 0.0) + mouse_moves_left = true; + else + mouse_moves_left = false; + + if (!readonly) + scroll_timer->Start(50, true); + } + else + { + scroll_timer->Stop(); + cursor_pos = GetCharacterIndex(pos); + selection_length = ToOffset(cursor_pos) - ToOffset(selection_start); + Update(); + } + } +} + +bool TextEdit::OnMouseDown(const Point& pos, InputKey key) +{ + if (key == InputKey::LeftMouse) + { + SetPointerCapture(); + mouse_selecting = true; + cursor_pos = GetCharacterIndex(pos); + selection_start = cursor_pos; + selection_length = 0; + + Update(); + } + return true; +} + +bool TextEdit::OnMouseDoubleclick(const Point& pos, InputKey key) +{ + return true; +} + +bool TextEdit::OnMouseUp(const Point& pos, InputKey key) +{ + if (mouse_selecting && key == InputKey::LeftMouse) + { + if (ignore_mouse_events) // This prevents text selection from changing from what was set when focus was gained. + { + ReleasePointerCapture(); + ignore_mouse_events = false; + mouse_selecting = false; + } + else + { + scroll_timer->Stop(); + ReleasePointerCapture(); + mouse_selecting = false; + ivec2 sel_end = GetCharacterIndex(pos); + selection_length = ToOffset(sel_end) - ToOffset(selection_start); + cursor_pos = sel_end; + SetFocus(); + Update(); + } + } + return true; +} + +void TextEdit::OnKeyChar(std::string chars) +{ + if (!chars.empty() && !(chars[0] >= 0 && chars[0] < 32)) + { + if (FuncBeforeEditChanged) + FuncBeforeEditChanged(); + + DeleteSelectedText(); + ClearSelection(); + if (input_mask.empty()) + { + // not in any special mode, just insert the string. + InsertText(cursor_pos, chars); + cursor_pos.x += chars.size(); + } + else + { + if (InputMaskAcceptsInput(cursor_pos, chars)) + { + InsertText(cursor_pos, chars); + cursor_pos.x += chars.size(); + } + } + + if (FuncAfterEditChanged) + FuncAfterEditChanged(); + } +} + +void TextEdit::OnKeyDown(InputKey key) +{ + if (!readonly && key == InputKey::Enter) + { + if (FuncEnterPressed) + { + FuncEnterPressed(); + } + else + { + ClearSelection(); + InsertText(cursor_pos, "\n"); + SetCursorPos(GetCursorPos() + 1); + } + return; + } + + if (!readonly) // Do not flash cursor when readonly + { + cursor_blink_visible = true; + timer->Start(500); // don't blink cursor when moving or typing. + } + + if (key == InputKey::Enter || key == InputKey::Escape || key == InputKey::Tab) + { + // Do not consume these. + return; + } + else if (key == InputKey::A && GetKeyState(InputKey::Ctrl)) + { + // select all + SelectAll(); + } + else if (key == InputKey::C && GetKeyState(InputKey::Ctrl)) + { + std::string str = GetSelection(); + SetClipboardText(str); + } + else if (readonly) + { + // Do not consume messages on read only component (only allow CTRL-A and CTRL-C) + return; + } + else if (key == InputKey::Up) + { + if (GetKeyState(InputKey::Shift) && selection_length == 0) + selection_start = cursor_pos; + + if (cursor_pos.y > 0) + { + cursor_pos.y--; + cursor_pos.x = std::min(lines[cursor_pos.y].text.size(), (size_t)cursor_pos.x); + } + + if (GetKeyState(InputKey::Shift)) + { + selection_length = ToOffset(cursor_pos) - ToOffset(selection_start); + } + else + { + // Clear the selection if a cursor key is pressed but shift isn't down. + selection_start = ivec2(0, 0); + selection_length = 0; + } + MoveVerticalScroll(); + Update(); + undo_info.first_text_insert = true; + } + else if (key == InputKey::Down) + { + if (GetKeyState(InputKey::Shift) && selection_length == 0) + selection_start = cursor_pos; + + if (cursor_pos.y < lines.size() - 1) + { + cursor_pos.y++; + cursor_pos.x = std::min(lines[cursor_pos.y].text.size(), (size_t)cursor_pos.x); + } + + if (GetKeyState(InputKey::Shift)) + { + selection_length = ToOffset(cursor_pos) - ToOffset(selection_start); + } + else + { + // Clear the selection if a cursor key is pressed but shift isn't down. + selection_start = ivec2(0, 0); + selection_length = 0; + } + MoveVerticalScroll(); + + Update(); + undo_info.first_text_insert = true; + } + else if (key == InputKey::Left) + { + Move(-1, GetKeyState(InputKey::Shift), GetKeyState(InputKey::Ctrl)); + } + else if (key == InputKey::Right) + { + Move(1, GetKeyState(InputKey::Shift), GetKeyState(InputKey::Ctrl)); + } + else if (key == InputKey::Backspace) + { + Backspace(); + } + else if (key == InputKey::Delete) + { + Del(); + } + else if (key == InputKey::Home) + { + if (GetKeyState(InputKey::Ctrl)) + cursor_pos = ivec2(0, 0); + else + cursor_pos.x = 0; + if (GetKeyState(InputKey::Shift)) + selection_length = ToOffset(cursor_pos) - ToOffset(selection_start); + else + ClearSelection(); + Update(); + MoveVerticalScroll(); + } + else if (key == InputKey::End) + { + if (GetKeyState(InputKey::Ctrl)) + cursor_pos = ivec2(lines.back().text.length(), lines.size() - 1); + else + cursor_pos.x = lines[cursor_pos.y].text.size(); + + if (GetKeyState(InputKey::Shift)) + selection_length = ToOffset(cursor_pos) - ToOffset(selection_start); + else + ClearSelection(); + Update(); + } + else if (key == InputKey::X && GetKeyState(InputKey::Ctrl)) + { + std::string str = GetSelection(); + DeleteSelectedText(); + SetClipboardText(str); + } + else if (key == InputKey::V && GetKeyState(InputKey::Ctrl)) + { + std::string str = GetClipboardText(); + std::string::const_iterator end_str = std::remove(str.begin(), str.end(), '\r'); + str.resize(end_str - str.begin()); + DeleteSelectedText(); + + if (input_mask.empty()) + { + InsertText(cursor_pos, str); + SetCursorPos(GetCursorPos() + str.length()); + } + else + { + if (InputMaskAcceptsInput(cursor_pos, str)) + { + InsertText(cursor_pos, str); + SetCursorPos(GetCursorPos() + str.length()); + } + } + MoveVerticalScroll(); + } + else if (GetKeyState(InputKey::Ctrl) && key == InputKey::Z) + { + if (!readonly) + { + std::string tmp = undo_info.undo_text; + undo_info.undo_text = GetText(); + SetText(tmp); + } + } + else if (key == InputKey::Shift) + { + if (selection_length == 0) + selection_start = cursor_pos; + } + + if (FuncAfterEditChanged) + FuncAfterEditChanged(); +} + +void TextEdit::OnKeyUp(InputKey key) +{ +} + +void TextEdit::OnSetFocus() +{ + if (!readonly) + timer->Start(500); + if (select_all_on_focus_gain) + SelectAll(); + ignore_mouse_events = true; + cursor_pos.y = lines.size() - 1; + cursor_pos.x = lines[cursor_pos.y].text.length(); + + Update(); + + if (FuncFocusGained) + FuncFocusGained(); +} + +void TextEdit::OnLostFocus() +{ + timer->Stop(); + ClearSelection(); + + Update(); + + if (FuncFocusLost) + FuncFocusLost(); +} + +void TextEdit::CreateComponents() +{ + vert_scrollbar = new Scrollbar(this); + vert_scrollbar->FuncScroll = [=]() { OnVerticalScroll(); }; + vert_scrollbar->SetVisible(false); + vert_scrollbar->SetVertical(); +} + +void TextEdit::OnVerticalScroll() +{ +} + +void TextEdit::UpdateVerticalScroll() +{ + Rect rect( + GetWidth() - 16.0/*vert_scrollbar->GetWidth()*/, + 0.0, + 16.0/*vert_scrollbar->GetWidth()*/, + GetHeight()); + + vert_scrollbar->SetFrameGeometry(rect); + + double total_height = GetTotalLineHeight(); + double height_per_line = std::max(1.0, total_height / std::max(1.0, (double)lines.size())); + bool visible = total_height > GetHeight(); + vert_scrollbar->SetRanges((int)std::round(GetHeight() / height_per_line), (int)std::round(total_height / height_per_line)); + vert_scrollbar->SetLineStep(1); + vert_scrollbar->SetVisible(visible); + + if (visible == false) + vert_scrollbar->SetPosition(0); +} + +void TextEdit::MoveVerticalScroll() +{ + double total_height = GetTotalLineHeight(); + double height_per_line = std::max(1.0, total_height / std::max((size_t)1, lines.size())); + int lines_fit = (int)(GetHeight() / height_per_line); + if (cursor_pos.y >= vert_scrollbar->GetPosition() + lines_fit) + { + vert_scrollbar->SetPosition(cursor_pos.y - lines_fit + 1); + } + else if (cursor_pos.y < vert_scrollbar->GetPosition()) + { + vert_scrollbar->SetPosition(cursor_pos.y); + } +} + +double TextEdit::GetTotalLineHeight() +{ + double total = 0; + for (std::vector::const_iterator iter = lines.begin(); iter != lines.end(); iter++) + { + total += iter->layout.GetSize().height; + } + return total; +} + +void TextEdit::Move(int steps, bool shift, bool ctrl) +{ + if (shift && selection_length == 0) + selection_start = cursor_pos; + + // Jump over words if control is pressed. + if (ctrl) + { + if (steps < 0 && cursor_pos.x == 0 && cursor_pos.y > 0) + { + cursor_pos.x = (int)lines[cursor_pos.y - 1].text.size(); + cursor_pos.y--; + } + else if (steps > 0 && cursor_pos.x == (int)lines[cursor_pos.y].text.size() && cursor_pos.y + 1 < (int)lines.size()) + { + cursor_pos.x = 0; + cursor_pos.y++; + } + + ivec2 new_pos; + if (steps < 0) + new_pos = FindPreviousBreakCharacter(cursor_pos); + else + new_pos = FindNextBreakCharacter(cursor_pos); + + cursor_pos = new_pos; + } + else if (steps < 0 && cursor_pos.x == 0 && cursor_pos.y > 0) + { + cursor_pos.x = (int)lines[cursor_pos.y - 1].text.size(); + cursor_pos.y--; + } + else if (steps > 0 && cursor_pos.x == (int)lines[cursor_pos.y].text.size() && cursor_pos.y + 1 < (int)lines.size()) + { + cursor_pos.x = 0; + cursor_pos.y++; + } + else + { + UTF8Reader utf8_reader(lines[cursor_pos.y].text.data(), lines[cursor_pos.y].text.length()); + utf8_reader.set_position(cursor_pos.x); + if (steps > 0) + { + for (int i = 0; i < steps; i++) + utf8_reader.next(); + } + else if (steps < 0) + { + for (int i = 0; i < -steps; i++) + utf8_reader.prev(); + } + + cursor_pos.x = (int)utf8_reader.position(); + } + + if (shift) + { + selection_length = (int)ToOffset(cursor_pos) - (int)ToOffset(selection_start); + } + else + { + // Clear the selection if a cursor key is pressed but shift isn't down. + selection_start = ivec2(0, 0); + selection_length = 0; + } + + + MoveVerticalScroll(); + Update(); + + undo_info.first_text_insert = true; +} + +std::string TextEdit::break_characters = " ::;,.-"; + +TextEdit::ivec2 TextEdit::FindNextBreakCharacter(ivec2 search_start) +{ + search_start.x++; + 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); + if (pos == std::string::npos) + return ivec2(lines[search_start.y].text.size(), search_start.y); + return ivec2(pos, search_start.y); +} + +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); + if (pos == std::string::npos) + return ivec2(0, search_start.y); + return ivec2(pos, search_start.y); +} + +void TextEdit::InsertText(ivec2 pos, const std::string& str) +{ + undo_info.first_erase = false; + if (undo_info.first_text_insert) + { + undo_info.undo_text = GetText(); + undo_info.first_text_insert = false; + } + + // checking if insert exceeds max length + if (ToOffset(ivec2(lines[lines.size() - 1].text.size(), lines.size() - 1)) + str.length() > max_length) + { + return; + } + + std::string::size_type start = 0; + while (true) + { + std::string::size_type next_newline = str.find('\n', start); + + lines[pos.y].text.insert(pos.x, str.substr(start, next_newline - start)); + lines[pos.y].invalidated = true; + + if (next_newline == std::string::npos) + break; + + pos.x += next_newline - start; + + Line line; + 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); + lines[pos.y].invalidated = true; + pos = ivec2(0, pos.y + 1); + + start = next_newline + 1; + } + + MoveVerticalScroll(); + + Update(); +} + +void TextEdit::Backspace() +{ + if (undo_info.first_erase) + { + undo_info.first_erase = false; + undo_info.undo_text = GetText(); + } + + if (GetSelectionLength() != 0) + { + DeleteSelectedText(); + ClearSelection(); + Update(); + } + else + { + if (cursor_pos.x > 0) + { + UTF8Reader utf8_reader(lines[cursor_pos.y].text.data(), lines[cursor_pos.y].text.length()); + utf8_reader.set_position(cursor_pos.x); + utf8_reader.prev(); + int length = utf8_reader.char_length(); + lines[cursor_pos.y].text.erase(cursor_pos.x - length, length); + lines[cursor_pos.y].invalidated = true; + cursor_pos.x -= length; + Update(); + } + else if (cursor_pos.y > 0) + { + selection_start = ivec2(lines[cursor_pos.y - 1].text.length(), cursor_pos.y - 1); + selection_length = 1; + DeleteSelectedText(); + } + } + MoveVerticalScroll(); +} + +void TextEdit::Del() +{ + if (undo_info.first_erase) + { + undo_info.first_erase = false; + undo_info.undo_text = GetText(); + } + + if (GetSelectionLength() != 0) + { + DeleteSelectedText(); + ClearSelection(); + Update(); + } + else + { + if (cursor_pos.x < (int)lines[cursor_pos.y].text.size()) + { + UTF8Reader utf8_reader(lines[cursor_pos.y].text.data(), lines[cursor_pos.y].text.length()); + utf8_reader.set_position(cursor_pos.x); + int length = utf8_reader.char_length(); + lines[cursor_pos.y].text.erase(cursor_pos.x, length); + lines[cursor_pos.y].invalidated = true; + Update(); + } + else if (cursor_pos.y + 1 < lines.size()) + { + selection_start = ivec2(lines[cursor_pos.y].text.length(), cursor_pos.y); + selection_length = 1; + DeleteSelectedText(); + } + } + MoveVerticalScroll(); +} + +void TextEdit::OnTimerExpired() +{ + if (IsVisible() == false) + { + timer->Stop(); + return; + } + + if (cursor_blink_visible) + timer->Start(500); + else + timer->Start(500); + + cursor_blink_visible = !cursor_blink_visible; + Update(); +} + +void TextEdit::OnGeometryChanged() +{ + Canvas* canvas = GetCanvas(); + + for (auto& line : lines) + { + line.invalidated = true; + } + + vertical_text_align = canvas->verticalTextAlign(); + + clip_start_offset = 0; + UpdateVerticalScroll(); +} + +void TextEdit::OnScrollTimerExpired() +{ + if (mouse_moves_left) + Move(-1, false, false); + else + Move(1, false, false); +} + +void TextEdit::OnEnableChanged() +{ + bool enabled = IsEnabled(); + if (!enabled) + { + cursor_blink_visible = false; + timer->Stop(); + } + Update(); +} + +bool TextEdit::InputMaskAcceptsInput(ivec2 cursor_pos, const std::string& str) +{ + return str.find_first_not_of(input_mask) == std::string::npos; +} + +std::string::size_type TextEdit::ToOffset(ivec2 pos) const +{ + if (pos.y < lines.size()) + { + std::string::size_type offset = 0; + for (int line = 0; line < pos.y; line++) + { + offset += lines[line].text.size() + 1; + } + return offset + std::min((size_t)pos.x, lines[pos.y].text.size()); + } + else + { + std::string::size_type offset = 0; + for (size_t line = 0; line < lines.size(); line++) + { + offset += lines[line].text.size() + 1; + } + return offset - 1; + } +} + +TextEdit::ivec2 TextEdit::FromOffset(std::string::size_type offset) const +{ + int line_offset = 0; + for (int line = 0; line < lines.size(); line++) + { + if (offset <= line_offset + lines[line].text.size()) + { + return ivec2(offset - line_offset, line); + } + line_offset += lines[line].text.size() + 1; + } + return ivec2(lines.back().text.size(), lines.size() - 1); +} + +double TextEdit::GetTotalHeight() +{ + Canvas* canvas = GetCanvas(); + LayoutLines(canvas); + if (!lines.empty()) + { + return lines.back().box.bottom(); + } + else + { + return GetHeight(); + } +} + +void TextEdit::LayoutLines(Canvas* canvas) +{ + ivec2 sel_start; + ivec2 sel_end; + if (selection_length > 0) + { + sel_start = selection_start; + sel_end = FromOffset(ToOffset(selection_start) + selection_length); + } + else if (selection_length < 0) + { + sel_start = FromOffset(ToOffset(selection_start) + selection_length); + sel_end = selection_start; + } + + Colorf textColor = GetStyleColor("color"); + Point draw_pos; + for (size_t i = (size_t)vert_scrollbar->GetPosition(); i < lines.size(); i++) + { + Line& line = lines[i]; + if (line.invalidated) + { + line.layout.Clear(); + if (!line.text.empty()) + line.layout.AddText(line.text, font, textColor); + else + line.layout.AddText(" ", font, textColor); // Draw one space character to get the correct height + line.layout.Layout(canvas, GetWidth()); + line.box = Rect(draw_pos, line.layout.GetSize()); + line.invalidated = false; + } + + if (sel_start != sel_end && sel_start.y <= i && sel_end.y >= i) + { + line.layout.SetSelectionRange(sel_start.y < i ? 0 : sel_start.x, sel_end.y > i ? line.text.size() : sel_end.x); + } + else + { + line.layout.SetSelectionRange(0, 0); + } + + line.layout.HideCursor(); + if (HasFocus()) + { + if (cursor_blink_visible && cursor_pos.y == i) + { + line.layout.SetCursorPos(cursor_pos.x); + line.layout.SetCursorColor(textColor); + line.layout.ShowCursor(); + } + } + + line.box.x = draw_pos.x; + line.box.y = draw_pos.y; + line.layout.SetPosition(line.box.topLeft()); + + draw_pos = line.box.bottomLeft(); + } + UpdateVerticalScroll(); +} + +void TextEdit::OnPaint(Canvas* canvas) +{ + LayoutLines(canvas); + for (size_t i = (size_t)vert_scrollbar->GetPosition(); i < lines.size(); i++) + lines[i].layout.DrawLayout(canvas); +} + +TextEdit::ivec2 TextEdit::GetCharacterIndex(Point mouse_wincoords) +{ + Canvas* canvas = GetCanvas(); + for (size_t i = 0; i < lines.size(); i++) + { + Line& line = lines[i]; + if (line.box.top() <= mouse_wincoords.y && line.box.bottom() > mouse_wincoords.y) + { + SpanLayout::HitTestResult result = line.layout.HitTest(canvas, mouse_wincoords); + switch (result.type) + { + case SpanLayout::HitTestResult::inside: + return ivec2(clamp(result.offset, (size_t)0, line.text.size()), i); + case SpanLayout::HitTestResult::outside_left: + return ivec2(0, i); + case SpanLayout::HitTestResult::outside_right: + return ivec2(line.text.size(), i); + } + } + } + + return ivec2(lines.back().text.size(), lines.size() - 1); +} diff --git a/src/widgets/textlabel/textlabel.cpp b/src/widgets/textlabel/textlabel.cpp new file mode 100644 index 000000000..aa6e7e739 --- /dev/null +++ b/src/widgets/textlabel/textlabel.cpp @@ -0,0 +1,60 @@ + +#include "widgets/textlabel/textlabel.h" + +TextLabel::TextLabel(Widget* parent) : Widget(parent) +{ +} + +void TextLabel::SetText(const std::string& value) +{ + if (text != value) + { + text = value; + Update(); + } +} + +const std::string& TextLabel::GetText() const +{ + return text; +} + +void TextLabel::SetTextAlignment(TextLabelAlignment alignment) +{ + if (textAlignment != alignment) + { + textAlignment = alignment; + Update(); + } +} + +TextLabelAlignment TextLabel::GetTextAlignment() const +{ + return textAlignment; +} + +double TextLabel::GetPreferredWidth() const +{ + Canvas* canvas = GetCanvas(); + return canvas->measureText(text).width; +} + +double TextLabel::GetPreferredHeight() const +{ + return 20.0; +} + +void TextLabel::OnPaint(Canvas* canvas) +{ + double x = 0.0; + if (textAlignment == TextLabelAlignment::Center) + { + x = (GetWidth() - canvas->measureText(text).width) * 0.5; + } + else if (textAlignment == TextLabelAlignment::Right) + { + x = GetWidth() - canvas->measureText(text).width; + } + + canvas->drawText(Point(x, GetHeight() - 5.0), GetStyleColor("color"), text); +} diff --git a/src/widgets/toolbar/toolbar.cpp b/src/widgets/toolbar/toolbar.cpp new file mode 100644 index 000000000..4629f3a1e --- /dev/null +++ b/src/widgets/toolbar/toolbar.cpp @@ -0,0 +1,63 @@ + +#include "widgets/toolbar/toolbar.h" +#include "widgets/toolbar/toolbarbutton.h" + +Toolbar::Toolbar(Widget* parent) : Widget(parent) +{ + SetStyleClass("toolbar"); +} + +Toolbar::~Toolbar() +{ +} + +void Toolbar::SetDirection(ToolbarDirection newDirection) +{ + if (direction != newDirection) + { + direction = newDirection; + Update(); + } +} + +ToolbarButton* Toolbar::AddButton(std::string icon, std::string text, std::function onClicked) +{ + ToolbarButton* button = new ToolbarButton(this); + if (!icon.empty()) + button->SetIcon(icon); + if (!text.empty()) + button->SetText(text); + button->OnClick = std::move(onClicked); + buttons.push_back(button); + return button; +} + +void Toolbar::OnGeometryChanged() +{ + if (direction == ToolbarDirection::Horizontal) + { + double x = 7.0; + double barHeight = GetHeight(); + double gap = 7.0; + for (ToolbarButton* button : buttons) + { + double width = button->GetPreferredWidth(); + double height = button->GetPreferredHeight(); + button->SetFrameGeometry(Rect::xywh(x, (barHeight - height) * 0.5, width, height)); + x += width + gap; + } + } + else + { + double y = 7.0; + double barWidth = GetWidth(); + double gap = 7.0; + for (ToolbarButton* button : buttons) + { + double width = button->GetPreferredWidth(); + double height = button->GetPreferredHeight(); + button->SetFrameGeometry(Rect::xywh((barWidth - width) * 0.5, y, width, height)); + y += height + gap; + } + } +} diff --git a/src/widgets/toolbar/toolbarbutton.cpp b/src/widgets/toolbar/toolbarbutton.cpp new file mode 100644 index 000000000..5eda9a13d --- /dev/null +++ b/src/widgets/toolbar/toolbarbutton.cpp @@ -0,0 +1,114 @@ + +#include "widgets/toolbar/toolbarbutton.h" + +ToolbarButton::ToolbarButton(Widget* parent) : Widget(parent) +{ + SetStyleClass("toolbarbutton"); +} + +ToolbarButton::~ToolbarButton() +{ +} + +void ToolbarButton::SetIcon(std::string icon) +{ + if (!icon.empty()) + { + if (!image) + image = new ImageBox(this); + image->SetImage(Image::LoadResource(icon, GetDpiScale())); + image->SetImageMode(ImageBoxMode::Contain); + } + else + { + delete image; + image = nullptr; + } +} + +void ToolbarButton::SetText(std::string text) +{ + if (!text.empty()) + { + if (!label) + label = new TextLabel(this); + label->SetText(text); + } + else + { + delete label; + label = nullptr; + } +} + +void ToolbarButton::Click() +{ + if (OnClick) + OnClick(); +} + +double ToolbarButton::GetPreferredWidth() +{ + double w = 0.0; + if (image) + w = 26.0; + if (label) + w += label->GetPreferredWidth(); + return w; +} + +double ToolbarButton::GetPreferredHeight() +{ + double h = 0.0; + if (image) + h = 24.0; + if (label) + h = std::max(h, label->GetPreferredHeight()); + return h; +} + +void ToolbarButton::OnMouseMove(const Point& pos) +{ + if (GetStyleState().empty()) + { + SetStyleState("hover"); + } +} + +void ToolbarButton::OnMouseLeave() +{ + SetStyleState(""); +} + +bool ToolbarButton::OnMouseDown(const Point& pos, InputKey key) +{ + SetStyleState("down"); + return true; +} + +bool ToolbarButton::OnMouseUp(const Point& pos, InputKey key) +{ + if (GetStyleState() == "down") + { + SetStyleState(""); + Repaint(); + Click(); + } + return true; +} + +void ToolbarButton::OnGeometryChanged() +{ + double totalHeight = GetPreferredHeight(); + double x = 0.0; + if (image) + { + image->SetFrameGeometry(Rect::xywh(0.0, (totalHeight - 24.0) * 0.5, 24.0, 24.0)); + x += 26.0; + } + if (label) + { + double labelHeight = label->GetPreferredHeight(); + label->SetFrameGeometry(Rect::xywh(x, (totalHeight - labelHeight) * 0.5, GetWidth() - x, labelHeight)); + } +} diff --git a/src/window/dbus/dbus_open_file_dialog.cpp b/src/window/dbus/dbus_open_file_dialog.cpp new file mode 100644 index 000000000..1ebc0668f --- /dev/null +++ b/src/window/dbus/dbus_open_file_dialog.cpp @@ -0,0 +1,288 @@ + +#include "dbus_open_file_dialog.h" +#include + +DBusOpenFileDialog::DBusOpenFileDialog(std::string ownerHandle) : ownerHandle(ownerHandle) +{ +} + +std::string DBusOpenFileDialog::Filename() const +{ + return outputFilenames.empty() ? std::string() : outputFilenames.front(); +} + +std::vector DBusOpenFileDialog::Filenames() const +{ + return outputFilenames; +} + +void DBusOpenFileDialog::SetMultiSelect(bool multiselect) +{ + this->multiSelect = multiSelect; +} + +void DBusOpenFileDialog::SetFilename(const std::string &filename) +{ + inputFilename = filename; +} + +void DBusOpenFileDialog::SetDefaultExtension(const std::string& extension) +{ + defaultExt = extension; +} + +void DBusOpenFileDialog::AddFilter(const std::string &filter_description, const std::string &filter_extension) +{ + filters.push_back({ filter_description, filter_extension }); +} + +void DBusOpenFileDialog::ClearFilters() +{ + filters.clear(); +} + +void DBusOpenFileDialog::SetFilterIndex(int filter_index) +{ + this->filter_index = filter_index; +} + +void DBusOpenFileDialog::SetInitialDirectory(const std::string &path) +{ + initialDirectory = path; +} + +void DBusOpenFileDialog::SetTitle(const std::string &title) +{ + this->title = title; +} + +bool DBusOpenFileDialog::Show() +{ + // https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.FileChooser.html + + dbus_bool_t bresult = {}; + DBusError error = {}; + dbus_error_init(&error); + + DBusConnection* connection = dbus_bus_get(DBUS_BUS_SESSION, &error); + if (!connection) + { + dbus_error_free(&error); + return false; + } + + std::string busname = dbus_bus_get_unique_name(connection); + + DBusMessage* request = dbus_message_new_method_call("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.FileChooser", "OpenFile"); + if (!request) + { + dbus_connection_unref(connection); + dbus_error_free(&error); + return false; + } + + const char* parentWindow = ownerHandle.c_str(); + const char* title = this->title.c_str(); + + DBusMessageIter requestArgs = {}; + dbus_message_iter_init_append(request, &requestArgs); + + bresult = dbus_message_iter_append_basic(&requestArgs, DBUS_TYPE_STRING, &parentWindow); + bresult = dbus_message_iter_append_basic(&requestArgs, DBUS_TYPE_STRING, &title); + + DBusMessageIter requestOptions = {}; + bresult = dbus_message_iter_open_container(&requestArgs, DBUS_TYPE_ARRAY, "{sv}", &requestOptions); + + // handle_token - s race condition prevention for signal (/org/freedesktop/portal/desktop/request/SENDER/TOKEN) + // accept_label - s text label for the OK button + // modal - b makes dialog modal. Defaults to true. Not really sure what it means since nothing stayed modal on my computer + // directory - b open folder mode, added in version 3 + // filters - a(sa(us)) [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])] + // current_filter - sa(us) filter from filters that should be current filter + // choices - a(ssa(ss)s) list of serialized combo boxes to add to the file chooser + // current_name - s suggested name + + // current_folder - ay + if (!initialDirectory.empty()) + { + // Note: the docs unfortunately says "The portal implementation is free to ignore this option" + + const char* key = "current_folder"; + const char* value = initialDirectory.c_str(); + int valueCount = (int)initialDirectory.size() + 1; + + DBusMessageIter entry = {}; + bresult = dbus_message_iter_open_container(&requestOptions, DBUS_TYPE_DICT_ENTRY, nullptr, &entry); + bresult = dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); + + DBusMessageIter variant = {}; + DBusMessageIter array = {}; + bresult = dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "ay", &variant); + bresult = dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &array); + bresult = dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &value, valueCount); + bresult = dbus_message_iter_close_container(&variant, &array); + bresult = dbus_message_iter_close_container(&entry, &variant); + + bresult = dbus_message_iter_close_container(&requestOptions, &entry); + } + + // multiple - b + { + const char* key = "multiple"; + dbus_bool_t value = multiSelect; + + DBusMessageIter entry = {}; + bresult = dbus_message_iter_open_container(&requestOptions, DBUS_TYPE_DICT_ENTRY, nullptr, &entry); + bresult = dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); + + DBusMessageIter variant = {}; + bresult = dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &variant); + bresult = dbus_message_iter_append_basic(&variant, DBUS_TYPE_BOOLEAN, &value); + bresult = dbus_message_iter_close_container(&entry, &variant); + + bresult = dbus_message_iter_close_container(&requestOptions, &entry); + } + + bresult = dbus_message_iter_close_container(&requestArgs, &requestOptions); + + DBusMessage* response = dbus_connection_send_with_reply_and_block(connection, request, DBUS_TIMEOUT_USE_DEFAULT, &error); + if (!response) + { + dbus_message_unref(request); + dbus_connection_unref(connection); + dbus_error_free(&error); + return false; + } + + const char* handle = nullptr; + bresult = dbus_message_get_args(response, &error, DBUS_TYPE_OBJECT_PATH, &handle, DBUS_TYPE_INVALID); + if (!bresult) + { + dbus_message_unref(response); + dbus_message_unref(request); + dbus_connection_unref(connection); + dbus_error_free(&error); + return false; + } + + std::string signalObjectPath = handle; + + dbus_message_unref(response); + dbus_message_unref(request); + + std::string rule = "type='signal',interface='org.freedesktop.portal.Request',member='Response',path='" + signalObjectPath + "'"; + dbus_bus_add_match(connection, rule.c_str(), &error); + + // Wait for the response signal + // + // To do: process the run loop while we wait + // + DBusMessage* signalmsg = nullptr; + while (!signalmsg && dbus_connection_read_write(connection, -1)) + { + while (true) + { + DBusMessage* message = dbus_connection_pop_message(connection); + if (!message) + break; + + if (dbus_message_is_signal(message, "org.freedesktop.portal.Request", "Response") && dbus_message_get_path(message) == signalObjectPath) + { + signalmsg = message; + break; + } + else + { + dbus_message_unref(message); + } + } + } + dbus_bus_remove_match(connection, rule.c_str(), &error); + + // Read the response + + dbus_uint32_t responseCode = 0; + std::vector uris; + + DBusMessageIter signalArgs; + bresult = dbus_message_iter_init(signalmsg, &signalArgs); + + // response code - u + if (dbus_message_iter_get_arg_type(&signalArgs) == DBUS_TYPE_UINT32) + { + dbus_message_iter_get_basic(&signalArgs, &responseCode); + } + dbus_message_iter_next(&signalArgs); + + // results - a{sv} + if (dbus_message_iter_get_arg_type(&signalArgs) == DBUS_TYPE_ARRAY) + { + DBusMessageIter resultsArray = {}; + dbus_message_iter_recurse(&signalArgs, &resultsArray); + while (true) + { + int type = dbus_message_iter_get_arg_type(&resultsArray); + if (type != DBUS_TYPE_DICT_ENTRY) + break; + + DBusMessageIter entry = {}; + dbus_message_iter_recurse(&resultsArray, &entry); + + const char* key = nullptr; + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + + DBusMessageIter value = {}; + dbus_message_iter_recurse(&entry, &value); + + std::string k = key; + if (k == "uris" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_ARRAY) // as + { + DBusMessageIter uriArray = {}; + dbus_message_iter_recurse(&value, &uriArray); + while (true) + { + int type = dbus_message_iter_get_arg_type(&uriArray); + if (type != DBUS_TYPE_STRING) + break; + + const char* uri = nullptr; + dbus_message_iter_get_basic(&uriArray, &uri); + uris.push_back(uri); + + dbus_message_iter_next(&uriArray); + } + } + else if (k == "choices" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_ARRAY) // a(ss) + { + // An array of pairs of strings, + // the first string being the ID of a combobox that was passed into this call, + // the second string being the selected option. + } + else if (k == "current_filter" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_STRUCT) // sa(us) + { + // The filter that was selected. + // This may match a filter in the filter list or another filter that was applied unconditionally. + } + + dbus_message_iter_next(&entry); + dbus_message_iter_next(&resultsArray); + } + } + dbus_message_iter_next(&signalArgs); + + dbus_message_unref(signalmsg); + dbus_connection_unref(connection); + dbus_error_free(&error); + + if (responseCode != 0) // User cancelled + return false; + + for (const std::string& uri : uris) + { + if (uri.size() > 7 && uri.substr(0, 7) == "file://") + outputFilenames.push_back(uri.substr(7)); + } + + return !uris.empty(); +} diff --git a/src/window/dbus/dbus_open_file_dialog.h b/src/window/dbus/dbus_open_file_dialog.h new file mode 100644 index 000000000..f4efd2663 --- /dev/null +++ b/src/window/dbus/dbus_open_file_dialog.h @@ -0,0 +1,38 @@ +#pragma once + +#include "systemdialogs/open_file_dialog.h" + +class DBusOpenFileDialog : public OpenFileDialog +{ +public: + DBusOpenFileDialog(std::string ownerHandle); + + std::string Filename() const override; + std::vector Filenames() const override; + void SetMultiSelect(bool multiselect) override; + void SetFilename(const std::string &filename) override; + void SetDefaultExtension(const std::string& extension) override; + void AddFilter(const std::string &filter_description, const std::string &filter_extension) override; + void ClearFilters() override; + void SetFilterIndex(int filter_index) override; + void SetInitialDirectory(const std::string &path) override; + void SetTitle(const std::string& newtitle) override; + bool Show() override; + +private: + std::string ownerHandle; + std::string title; + std::string initialDirectory; + std::string inputFilename; + std::string defaultExt; + std::vector outputFilenames; + bool multiSelect = false; + + struct Filter + { + std::string description; + std::string extension; + }; + std::vector filters; + int filter_index = 0; +}; diff --git a/src/window/dbus/dbus_open_folder_dialog.cpp b/src/window/dbus/dbus_open_folder_dialog.cpp new file mode 100644 index 000000000..7bcd94953 --- /dev/null +++ b/src/window/dbus/dbus_open_folder_dialog.cpp @@ -0,0 +1,26 @@ + +#include "dbus_open_folder_dialog.h" + +DBusOpenFolderDialog::DBusOpenFolderDialog(std::string ownerHandle) : ownerHandle(ownerHandle) +{ +} + +bool DBusOpenFolderDialog::Show() +{ + return false; +} + +std::string DBusOpenFolderDialog::SelectedPath() const +{ + return selected_path; +} + +void DBusOpenFolderDialog::SetInitialDirectory(const std::string& path) +{ + initial_directory = path; +} + +void DBusOpenFolderDialog::SetTitle(const std::string& newtitle) +{ + title = newtitle; +} diff --git a/src/window/dbus/dbus_open_folder_dialog.h b/src/window/dbus/dbus_open_folder_dialog.h new file mode 100644 index 000000000..3e40cc7bc --- /dev/null +++ b/src/window/dbus/dbus_open_folder_dialog.h @@ -0,0 +1,21 @@ +#pragma once + +#include "systemdialogs/open_folder_dialog.h" + +class DBusOpenFolderDialog : public OpenFolderDialog +{ +public: + DBusOpenFolderDialog(std::string ownerHandle); + + bool Show() override; + std::string SelectedPath() const override; + void SetInitialDirectory(const std::string& path) override; + void SetTitle(const std::string& newtitle) override; + +private: + std::string ownerHandle; + + std::string selected_path; + std::string initial_directory; + std::string title; +}; diff --git a/src/window/dbus/dbus_save_file_dialog.cpp b/src/window/dbus/dbus_save_file_dialog.cpp new file mode 100644 index 000000000..ba6b6d1f4 --- /dev/null +++ b/src/window/dbus/dbus_save_file_dialog.cpp @@ -0,0 +1,54 @@ + +#include "dbus_save_file_dialog.h" + +DBusSaveFileDialog::DBusSaveFileDialog(std::string ownerHandle) : ownerHandle(ownerHandle) +{ +} + +bool DBusSaveFileDialog::Show() +{ + return false; +} + +std::string DBusSaveFileDialog::Filename() const +{ + return filename; +} + +void DBusSaveFileDialog::SetFilename(const std::string& filename) +{ + initial_filename = filename; +} + +void DBusSaveFileDialog::AddFilter(const std::string& filter_description, const std::string& filter_extension) +{ + Filter f; + f.description = filter_description; + f.extension = filter_extension; + filters.push_back(std::move(f)); +} + +void DBusSaveFileDialog::ClearFilters() +{ + filters.clear(); +} + +void DBusSaveFileDialog::SetFilterIndex(int filter_index) +{ + filterindex = filter_index; +} + +void DBusSaveFileDialog::SetInitialDirectory(const std::string& path) +{ + initial_directory = path; +} + +void DBusSaveFileDialog::SetTitle(const std::string& newtitle) +{ + title = newtitle; +} + +void DBusSaveFileDialog::SetDefaultExtension(const std::string& extension) +{ + defaultext = extension; +} diff --git a/src/window/dbus/dbus_save_file_dialog.h b/src/window/dbus/dbus_save_file_dialog.h new file mode 100644 index 000000000..835789b2c --- /dev/null +++ b/src/window/dbus/dbus_save_file_dialog.h @@ -0,0 +1,37 @@ +#pragma once + +#include "systemdialogs/save_file_dialog.h" + +class DBusSaveFileDialog : public SaveFileDialog +{ +public: + DBusSaveFileDialog(std::string ownerHandle); + + bool Show() override; + std::string Filename() const override; + void SetFilename(const std::string& filename) override; + void AddFilter(const std::string& filter_description, const std::string& filter_extension) override; + void ClearFilters() override; + void SetFilterIndex(int filter_index) override; + void SetInitialDirectory(const std::string& path) override; + void SetTitle(const std::string& newtitle) override; + void SetDefaultExtension(const std::string& extension) override; + +private: + std::string ownerHandle; + + std::string filename; + std::string initial_directory; + std::string initial_filename; + std::string title; + std::vector filenames; + + struct Filter + { + std::string description; + std::string extension; + }; + std::vector filters; + int filterindex = 0; + std::string defaultext; +}; diff --git a/src/window/sdl2/sdl2_display_backend.cpp b/src/window/sdl2/sdl2_display_backend.cpp new file mode 100644 index 000000000..d67699eda --- /dev/null +++ b/src/window/sdl2/sdl2_display_backend.cpp @@ -0,0 +1,98 @@ + +#include "sdl2_display_backend.h" +#include "sdl2_display_window.h" +#include +#include +#ifndef WIN32 +#include +#endif + +namespace X11DPI +{ + typedef struct _XDisplay Display; + typedef Display *(*XOpenDisplayPtr)(const char*); + typedef char *(*XGetDefaultPtr)(Display*, const char*, const char*); + typedef int (*XCloseDisplayPtr)(Display*); +} + +SDL2DisplayBackend::SDL2DisplayBackend() +{ + int result = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); + if (result != 0) + throw std::runtime_error(std::string("Unable to initialize SDL:") + SDL_GetError()); + + SDL2DisplayWindow::PaintEventNumber = SDL_RegisterEvents(1); + + // SDL2 doesn't have proper native hidpi support for Linux. Cheat a bit here by asking X11 ourselves. +#if !defined(WIN32) && !defined(__APPLE__) + const char* driver = SDL_GetCurrentVideoDriver(); + if (driver && strcmp(driver, "x11") == 0) + { + void* module = dlopen("libX11.so", RTLD_NOW | RTLD_LOCAL); + if (module) + { + using namespace X11DPI; + auto XOpenDisplay = (XOpenDisplayPtr)dlsym(module, "XOpenDisplay"); + auto XGetDefault = (XGetDefaultPtr)dlsym(module, "XGetDefault"); + auto XCloseDisplay = (XCloseDisplayPtr)dlsym(module, "XCloseDisplay"); + if (XOpenDisplay && XGetDefault && XCloseDisplay) + { + auto display = XOpenDisplay(nullptr); + if (display) + { + char* value = XGetDefault(display, "Xft", "dpi"); + if (value) + { + int dpi = std::atoi(value); + if (dpi != 0) + { + UIScale = dpi / 96.0; + } + } + XCloseDisplay(display); + } + } + } + } +#endif +} + +std::unique_ptr SDL2DisplayBackend::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) +{ + return std::make_unique(windowHost, popupWindow, static_cast(owner), renderAPI, UIScale); +} + +void SDL2DisplayBackend::ProcessEvents() +{ + SDL2DisplayWindow::ProcessEvents(); +} + +void SDL2DisplayBackend::RunLoop() +{ + SDL2DisplayWindow::RunLoop(); +} + +void SDL2DisplayBackend::ExitLoop() +{ + SDL2DisplayWindow::ExitLoop(); +} + +Size SDL2DisplayBackend::GetScreenSize() +{ + SDL_Rect rect = {}; + int result = SDL_GetDisplayBounds(0, &rect); + if (result != 0) + throw std::runtime_error(std::string("Unable to get screen size:") + SDL_GetError()); + + return Size(rect.w / UIScale, rect.h / UIScale); +} + +void* SDL2DisplayBackend::StartTimer(int timeoutMilliseconds, std::function onTimer) +{ + return SDL2DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer)); +} + +void SDL2DisplayBackend::StopTimer(void* timerID) +{ + SDL2DisplayWindow::StopTimer(timerID); +} diff --git a/src/window/sdl2/sdl2_display_backend.h b/src/window/sdl2/sdl2_display_backend.h new file mode 100644 index 000000000..53b17b0eb --- /dev/null +++ b/src/window/sdl2/sdl2_display_backend.h @@ -0,0 +1,24 @@ +#pragma once + +#include "window/window.h" + +class SDL2DisplayBackend : public DisplayBackend +{ +public: + SDL2DisplayBackend(); + + std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) override; + void ProcessEvents() override; + void RunLoop() override; + void ExitLoop() override; + + void* StartTimer(int timeoutMilliseconds, std::function onTimer) override; + void StopTimer(void* timerID) override; + + Size GetScreenSize() override; + + bool IsSDL2() override { return true; } + +private: + double UIScale = 1.0; +}; diff --git a/src/window/sdl2/sdl2_display_window.cpp b/src/window/sdl2/sdl2_display_window.cpp new file mode 100644 index 000000000..a71a4a1ca --- /dev/null +++ b/src/window/sdl2/sdl2_display_window.cpp @@ -0,0 +1,792 @@ + +#include "sdl2_display_window.h" +#include +#include + +Uint32 SDL2DisplayWindow::PaintEventNumber = 0xffffffff; +bool SDL2DisplayWindow::ExitRunLoop; +std::unordered_map SDL2DisplayWindow::WindowList; + +SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, SDL2DisplayWindow* owner, RenderAPI renderAPI, double uiscale) : WindowHost(windowHost), UIScale(uiscale) +{ + unsigned int flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE /*| SDL_WINDOW_ALLOW_HIGHDPI*/; + if (renderAPI == RenderAPI::Vulkan) + flags |= SDL_WINDOW_VULKAN; + else if (renderAPI == RenderAPI::OpenGL) + flags |= SDL_WINDOW_OPENGL; +#if defined(__APPLE__) + else if (renderAPI == RenderAPI::Metal) + flags |= SDL_WINDOW_METAL; +#endif + if (popupWindow) + flags |= SDL_WINDOW_BORDERLESS; + + if (renderAPI == RenderAPI::Vulkan || renderAPI == RenderAPI::OpenGL || renderAPI == RenderAPI::Metal) + { + Handle.window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 320, 200, flags); + if (!Handle.window) + throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError()); + } + else + { + int result = SDL_CreateWindowAndRenderer(320, 200, flags, &Handle.window, &RendererHandle); + if (result != 0) + throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError()); + } + + WindowList[SDL_GetWindowID(Handle.window)] = this; +} + +SDL2DisplayWindow::~SDL2DisplayWindow() +{ + UnlockCursor(); + + WindowList.erase(WindowList.find(SDL_GetWindowID(Handle.window))); + + if (BackBufferTexture) + { + SDL_DestroyTexture(BackBufferTexture); + BackBufferTexture = nullptr; + } + + if (RendererHandle) + SDL_DestroyRenderer(RendererHandle); + SDL_DestroyWindow(Handle.window); + RendererHandle = nullptr; + Handle.window = nullptr; +} + +std::vector SDL2DisplayWindow::GetVulkanInstanceExtensions() +{ + unsigned int extCount = 0; + SDL_Vulkan_GetInstanceExtensions(Handle.window, &extCount, nullptr); + std::vector extNames(extCount); + SDL_Vulkan_GetInstanceExtensions(Handle.window, &extCount, extNames.data()); + + std::vector result; + result.reserve(extNames.size()); + for (const char* ext : extNames) + result.emplace_back(ext); + return result; +} + +VkSurfaceKHR SDL2DisplayWindow::CreateVulkanSurface(VkInstance instance) +{ + VkSurfaceKHR surfaceHandle = {}; + SDL_Vulkan_CreateSurface(Handle.window, instance, &surfaceHandle); + if (surfaceHandle) + throw std::runtime_error("Could not create vulkan surface"); + return surfaceHandle; +} + +void SDL2DisplayWindow::SetWindowTitle(const std::string& text) +{ + SDL_SetWindowTitle(Handle.window, text.c_str()); +} + +void SDL2DisplayWindow::SetWindowFrame(const Rect& box) +{ + // SDL2 doesn't really seem to have an API for this. + // The docs aren't clear what you're setting when calling SDL_SetWindowSize. + SetClientFrame(box); +} + +void SDL2DisplayWindow::SetClientFrame(const Rect& box) +{ + // Is there a way to set both in one call? + + double uiscale = GetDpiScale(); + int x = (int)std::round(box.x * uiscale); + int y = (int)std::round(box.y * uiscale); + int w = (int)std::round(box.width * uiscale); + int h = (int)std::round(box.height * uiscale); + + SDL_SetWindowSize(Handle.window, w, h); + SDL_SetWindowPosition(Handle.window, x, y); +} + +void SDL2DisplayWindow::Show() +{ + SDL_ShowWindow(Handle.window); +} + +void SDL2DisplayWindow::ShowFullscreen() +{ + SDL_ShowWindow(Handle.window); + SDL_SetWindowFullscreen(Handle.window, SDL_WINDOW_FULLSCREEN_DESKTOP); + isFullscreen = true; +} + +void SDL2DisplayWindow::ShowMaximized() +{ + SDL_ShowWindow(Handle.window); + SDL_MaximizeWindow(Handle.window); +} + +void SDL2DisplayWindow::ShowMinimized() +{ + SDL_ShowWindow(Handle.window); + SDL_MinimizeWindow(Handle.window); +} + +void SDL2DisplayWindow::ShowNormal() +{ + SDL_ShowWindow(Handle.window); + SDL_SetWindowFullscreen(Handle.window, 0); + isFullscreen = false; +} + +bool SDL2DisplayWindow::IsWindowFullscreen() +{ + return isFullscreen; +} + +void SDL2DisplayWindow::Hide() +{ + SDL_HideWindow(Handle.window); +} + +void SDL2DisplayWindow::Activate() +{ + SDL_RaiseWindow(Handle.window); +} + +void SDL2DisplayWindow::ShowCursor(bool enable) +{ + SDL_ShowCursor(enable); +} + +void SDL2DisplayWindow::LockCursor() +{ + if (!CursorLocked) + { + SDL_SetRelativeMouseMode(SDL_TRUE); + CursorLocked = true; + } +} + +void SDL2DisplayWindow::UnlockCursor() +{ + if (CursorLocked) + { + SDL_SetRelativeMouseMode(SDL_FALSE); + CursorLocked = false; + } +} + +void SDL2DisplayWindow::CaptureMouse() +{ +} + +void SDL2DisplayWindow::ReleaseMouseCapture() +{ +} + +void SDL2DisplayWindow::SetCursor(StandardCursor cursor) +{ +} + +void SDL2DisplayWindow::Update() +{ + SDL_Event event = {}; + event.type = PaintEventNumber; + event.user.windowID = SDL_GetWindowID(Handle.window); + SDL_PushEvent(&event); +} + +bool SDL2DisplayWindow::GetKeyState(InputKey key) +{ + int numkeys = 0; + const Uint8* state = SDL_GetKeyboardState(&numkeys); + if (!state) return false; + + SDL_Scancode index = InputKeyToScancode(key); + return (index < numkeys) ? state[index] != 0 : false; +} + +Rect SDL2DisplayWindow::GetWindowFrame() const +{ + int x = 0; + int y = 0; + int w = 0; + int h = 0; + double uiscale = GetDpiScale(); + SDL_GetWindowPosition(Handle.window, &x, &y); + SDL_GetWindowSize(Handle.window, &w, &h); + return Rect::xywh(x / uiscale, y / uiscale, w / uiscale, h / uiscale); +} + +Point SDL2DisplayWindow::MapFromGlobal(const Point& pos) const +{ + int x = 0; + int y = 0; + double uiscale = GetDpiScale(); + SDL_GetWindowPosition(Handle.window, &x, &y); + return Point(pos.x - x / uiscale, pos.y - y / uiscale); +} + +Point SDL2DisplayWindow::MapToGlobal(const Point& pos) const +{ + int x = 0; + int y = 0; + double uiscale = GetDpiScale(); + SDL_GetWindowPosition(Handle.window, &x, &y); + return Point(pos.x + x / uiscale, pos.y + y / uiscale); +} + +Size SDL2DisplayWindow::GetClientSize() const +{ + int w = 0; + int h = 0; + double uiscale = GetDpiScale(); + SDL_GetWindowSize(Handle.window, &w, &h); + return Size(w / uiscale, h / uiscale); +} + +int SDL2DisplayWindow::GetPixelWidth() const +{ + int w = 0; + int h = 0; + if (RendererHandle) + SDL_GetRendererOutputSize(RendererHandle, &w, &h); + else + SDL_GL_GetDrawableSize(Handle.window, &w, &h); + return w; +} + +int SDL2DisplayWindow::GetPixelHeight() const +{ + int w = 0; + int h = 0; + if (RendererHandle) + SDL_GetRendererOutputSize(RendererHandle, &w, &h); + else + SDL_GL_GetDrawableSize(Handle.window, &w, &h); + return h; +} + +double SDL2DisplayWindow::GetDpiScale() const +{ + // SDL2 doesn't really support this properly. SDL_GetDisplayDPI returns the wrong information according to the docs. + // We currently cheat by asking X11 directly. See the SDL2DisplayBackend constructor. + return UIScale; +} + +void SDL2DisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels) +{ + if (!RendererHandle) + return; + + if (!BackBufferTexture || BackBufferWidth != width || BackBufferHeight != height) + { + if (BackBufferTexture) + { + SDL_DestroyTexture(BackBufferTexture); + BackBufferTexture = nullptr; + } + + BackBufferTexture = SDL_CreateTexture(RendererHandle, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height); + if (!BackBufferTexture) + return; + + BackBufferWidth = width; + BackBufferHeight = height; + } + + int destpitch = 0; + void* dest = nullptr; + int result = SDL_LockTexture(BackBufferTexture, nullptr, &dest, &destpitch); + if (result != 0) return; + for (int y = 0; y < height; y++) + { + const void* sline = pixels + y * width; + void* dline = (uint8_t*)dest + y * destpitch; + memcpy(dline, sline, width << 2); + } + SDL_UnlockTexture(BackBufferTexture); + + SDL_RenderCopy(RendererHandle, BackBufferTexture, nullptr, nullptr); + SDL_RenderPresent(RendererHandle); +} + +void SDL2DisplayWindow::SetBorderColor(uint32_t bgra8) +{ + // SDL doesn't have this +} + +void SDL2DisplayWindow::SetCaptionColor(uint32_t bgra8) +{ + // SDL doesn't have this +} + +void SDL2DisplayWindow::SetCaptionTextColor(uint32_t bgra8) +{ + // SDL doesn't have this +} + +std::string SDL2DisplayWindow::GetClipboardText() +{ + char* buffer = SDL_GetClipboardText(); + if (!buffer) + return {}; + std::string text = buffer; + SDL_free(buffer); + return text; +} + +void SDL2DisplayWindow::SetClipboardText(const std::string& text) +{ + SDL_SetClipboardText(text.c_str()); +} + +void SDL2DisplayWindow::ProcessEvents() +{ + SDL_Event event; + while (SDL_PollEvent(&event) != 0) + { + DispatchEvent(event); + } +} + +void SDL2DisplayWindow::RunLoop() +{ + ExitRunLoop = false; + while (!ExitRunLoop) + { + SDL_Event event = {}; + int result = SDL_WaitEvent(&event); + if (result == 1) + DispatchEvent(event); // Silently ignore if it fails and pray it doesn't busy loop, because SDL and Linux utterly sucks! + } +} + +void SDL2DisplayWindow::ExitLoop() +{ + ExitRunLoop = true; +} + +std::unordered_map> SDL2DisplayWindow::Timers; +std::unordered_map SDL2DisplayWindow::TimerHandles; +unsigned long SDL2DisplayWindow::TimerIDs = 0; +Uint32 TimerEventID = SDL_RegisterEvents(1); + +Uint32 SDL2DisplayWindow::ExecTimer(Uint32 interval, void* execID) +{ + // cancel event and stop loop if function not found + if (Timers.find(execID) == Timers.end()) + return 0; + + SDL_Event timerEvent; + SDL_zero(timerEvent); + + timerEvent.user.type = TimerEventID; + timerEvent.user.data1 = execID; + + SDL_PushEvent(&timerEvent); + + return interval; +} + +void* SDL2DisplayWindow::StartTimer(int timeoutMilliseconds, std::function onTimer) +{ + // is this guard needed? + // CheckInitSDL(); + + void* execID = (void*)(uintptr_t)++TimerIDs; + void* id = (void*)(uintptr_t)SDL_AddTimer(timeoutMilliseconds, SDL2DisplayWindow::ExecTimer, execID); + + if (!id) return id; + + Timers.insert({execID, onTimer}); + TimerHandles.insert({id, execID}); + + return id; +} + +void SDL2DisplayWindow::StopTimer(void* timerID) +{ + // is this guard needed? + // CheckInitSDL(); + + SDL_RemoveTimer((SDL_TimerID)(uintptr_t)timerID); + + auto execID = TimerHandles.find(timerID); + if (execID == TimerHandles.end()) + return; + + Timers.erase(execID->second); + TimerHandles.erase(timerID); +} + +SDL2DisplayWindow* SDL2DisplayWindow::FindEventWindow(const SDL_Event& event) +{ + int windowID; + switch (event.type) + { + case SDL_WINDOWEVENT: windowID = event.window.windowID; break; + case SDL_TEXTINPUT: windowID = event.text.windowID; break; + case SDL_KEYUP: windowID = event.key.windowID; break; + case SDL_KEYDOWN: windowID = event.key.windowID; break; + case SDL_MOUSEBUTTONUP: windowID = event.button.windowID; break; + case SDL_MOUSEBUTTONDOWN: windowID = event.button.windowID; break; + case SDL_MOUSEWHEEL: windowID = event.wheel.windowID; break; + case SDL_MOUSEMOTION: windowID = event.motion.windowID; break; + default: + if (event.type == PaintEventNumber) windowID = event.user.windowID; + else return nullptr; + } + + auto it = WindowList.find(windowID); + return it != WindowList.end() ? it->second : nullptr; +} + +void SDL2DisplayWindow::DispatchEvent(const SDL_Event& event) +{ + // timers are created in a non-window context + if (event.type == TimerEventID) + return OnTimerEvent(event.user); + + SDL2DisplayWindow* window = FindEventWindow(event); + if (!window) return; + + switch (event.type) + { + case SDL_WINDOWEVENT: window->OnWindowEvent(event.window); break; + case SDL_TEXTINPUT: window->OnTextInput(event.text); break; + case SDL_KEYUP: window->OnKeyUp(event.key); break; + case SDL_KEYDOWN: window->OnKeyDown(event.key); break; + case SDL_MOUSEBUTTONUP: window->OnMouseButtonUp(event.button); break; + case SDL_MOUSEBUTTONDOWN: window->OnMouseButtonDown(event.button); break; + case SDL_MOUSEWHEEL: window->OnMouseWheel(event.wheel); break; + case SDL_MOUSEMOTION: window->OnMouseMotion(event.motion); break; + default: + if (event.type == PaintEventNumber) window->OnPaintEvent(); + } +} + +void SDL2DisplayWindow::OnWindowEvent(const SDL_WindowEvent& event) +{ + switch (event.event) + { + case SDL_WINDOWEVENT_CLOSE: WindowHost->OnWindowClose(); break; + case SDL_WINDOWEVENT_MOVED: WindowHost->OnWindowGeometryChanged(); break; + case SDL_WINDOWEVENT_RESIZED: WindowHost->OnWindowGeometryChanged(); break; + case SDL_WINDOWEVENT_SHOWN: WindowHost->OnWindowPaint(); break; + case SDL_WINDOWEVENT_EXPOSED: WindowHost->OnWindowPaint(); break; + case SDL_WINDOWEVENT_FOCUS_GAINED: WindowHost->OnWindowActivated(); break; + case SDL_WINDOWEVENT_FOCUS_LOST: WindowHost->OnWindowDeactivated(); break; + } +} + +void SDL2DisplayWindow::OnTextInput(const SDL_TextInputEvent& event) +{ + WindowHost->OnWindowKeyChar(event.text); +} + +void SDL2DisplayWindow::OnKeyUp(const SDL_KeyboardEvent& event) +{ + WindowHost->OnWindowKeyUp(ScancodeToInputKey(event.keysym.scancode)); +} + +void SDL2DisplayWindow::OnKeyDown(const SDL_KeyboardEvent& event) +{ + WindowHost->OnWindowKeyDown(ScancodeToInputKey(event.keysym.scancode)); +} + +InputKey SDL2DisplayWindow::GetMouseButtonKey(const SDL_MouseButtonEvent& event) +{ + switch (event.button) + { + case SDL_BUTTON_LEFT: return InputKey::LeftMouse; + case SDL_BUTTON_MIDDLE: return InputKey::MiddleMouse; + case SDL_BUTTON_RIGHT: return InputKey::RightMouse; + // case SDL_BUTTON_X1: return InputKey::XButton1; + // case SDL_BUTTON_X2: return InputKey::XButton2; + default: return InputKey::None; + } +} + +void SDL2DisplayWindow::OnMouseButtonUp(const SDL_MouseButtonEvent& event) +{ + InputKey key = GetMouseButtonKey(event); + if (key != InputKey::None) + { + WindowHost->OnWindowMouseUp(GetMousePos(event), key); + } +} + +void SDL2DisplayWindow::OnMouseButtonDown(const SDL_MouseButtonEvent& event) +{ + InputKey key = GetMouseButtonKey(event); + if (key != InputKey::None) + { + WindowHost->OnWindowMouseDown(GetMousePos(event), key); + } +} + +void SDL2DisplayWindow::OnMouseWheel(const SDL_MouseWheelEvent& event) +{ + int x = 0, y = 0; + SDL_GetMouseState(&x, &y); + double uiscale = GetDpiScale(); + Point mousepos(x / uiscale, y / uiscale); + + if (event.y > 0) + WindowHost->OnWindowMouseWheel(mousepos, InputKey::MouseWheelUp); + else if (event.y < 0) + WindowHost->OnWindowMouseWheel(mousepos, InputKey::MouseWheelDown); +} + +void SDL2DisplayWindow::OnMouseMotion(const SDL_MouseMotionEvent& event) +{ + if (CursorLocked) + { + WindowHost->OnWindowRawMouseMove(event.xrel, event.yrel); + } + else + { + WindowHost->OnWindowMouseMove(GetMousePos(event)); + } +} + +void SDL2DisplayWindow::OnPaintEvent() +{ + WindowHost->OnWindowPaint(); +} + +void SDL2DisplayWindow::OnTimerEvent(const SDL_UserEvent& event) +{ + auto func = Timers.find(event.data1); + + // incase timer was cancelled before we get here + if (func == Timers.end()) + return; + + func->second(); +} + +InputKey SDL2DisplayWindow::ScancodeToInputKey(SDL_Scancode keycode) +{ + switch (keycode) + { + case SDL_SCANCODE_BACKSPACE: return InputKey::Backspace; + case SDL_SCANCODE_TAB: return InputKey::Tab; + case SDL_SCANCODE_CLEAR: return InputKey::OEMClear; + case SDL_SCANCODE_RETURN: return InputKey::Enter; + case SDL_SCANCODE_MENU: return InputKey::Alt; + case SDL_SCANCODE_PAUSE: return InputKey::Pause; + case SDL_SCANCODE_ESCAPE: return InputKey::Escape; + case SDL_SCANCODE_SPACE: return InputKey::Space; + case SDL_SCANCODE_END: return InputKey::End; + case SDL_SCANCODE_PAGEDOWN: return InputKey::PageDown; + case SDL_SCANCODE_HOME: return InputKey::Home; + case SDL_SCANCODE_PAGEUP: return InputKey::PageUp; + case SDL_SCANCODE_LEFT: return InputKey::Left; + case SDL_SCANCODE_UP: return InputKey::Up; + case SDL_SCANCODE_RIGHT: return InputKey::Right; + case SDL_SCANCODE_DOWN: return InputKey::Down; + case SDL_SCANCODE_SELECT: return InputKey::Select; + case SDL_SCANCODE_PRINTSCREEN: return InputKey::Print; + case SDL_SCANCODE_EXECUTE: return InputKey::Execute; + case SDL_SCANCODE_INSERT: return InputKey::Insert; + case SDL_SCANCODE_DELETE: return InputKey::Delete; + case SDL_SCANCODE_HELP: return InputKey::Help; + case SDL_SCANCODE_0: return InputKey::_0; + case SDL_SCANCODE_1: return InputKey::_1; + case SDL_SCANCODE_2: return InputKey::_2; + case SDL_SCANCODE_3: return InputKey::_3; + case SDL_SCANCODE_4: return InputKey::_4; + case SDL_SCANCODE_5: return InputKey::_5; + case SDL_SCANCODE_6: return InputKey::_6; + case SDL_SCANCODE_7: return InputKey::_7; + case SDL_SCANCODE_8: return InputKey::_8; + case SDL_SCANCODE_9: return InputKey::_9; + case SDL_SCANCODE_A: return InputKey::A; + case SDL_SCANCODE_B: return InputKey::B; + case SDL_SCANCODE_C: return InputKey::C; + case SDL_SCANCODE_D: return InputKey::D; + case SDL_SCANCODE_E: return InputKey::E; + case SDL_SCANCODE_F: return InputKey::F; + case SDL_SCANCODE_G: return InputKey::G; + case SDL_SCANCODE_H: return InputKey::H; + case SDL_SCANCODE_I: return InputKey::I; + case SDL_SCANCODE_J: return InputKey::J; + case SDL_SCANCODE_K: return InputKey::K; + case SDL_SCANCODE_L: return InputKey::L; + case SDL_SCANCODE_M: return InputKey::M; + case SDL_SCANCODE_N: return InputKey::N; + case SDL_SCANCODE_O: return InputKey::O; + case SDL_SCANCODE_P: return InputKey::P; + case SDL_SCANCODE_Q: return InputKey::Q; + case SDL_SCANCODE_R: return InputKey::R; + case SDL_SCANCODE_S: return InputKey::S; + case SDL_SCANCODE_T: return InputKey::T; + case SDL_SCANCODE_U: return InputKey::U; + case SDL_SCANCODE_V: return InputKey::V; + case SDL_SCANCODE_W: return InputKey::W; + case SDL_SCANCODE_X: return InputKey::X; + case SDL_SCANCODE_Y: return InputKey::Y; + case SDL_SCANCODE_Z: return InputKey::Z; + case SDL_SCANCODE_KP_0: return InputKey::NumPad0; + case SDL_SCANCODE_KP_1: return InputKey::NumPad1; + case SDL_SCANCODE_KP_2: return InputKey::NumPad2; + case SDL_SCANCODE_KP_3: return InputKey::NumPad3; + case SDL_SCANCODE_KP_4: return InputKey::NumPad4; + case SDL_SCANCODE_KP_5: return InputKey::NumPad5; + case SDL_SCANCODE_KP_6: return InputKey::NumPad6; + case SDL_SCANCODE_KP_7: return InputKey::NumPad7; + case SDL_SCANCODE_KP_8: return InputKey::NumPad8; + case SDL_SCANCODE_KP_9: return InputKey::NumPad9; + // case SDL_SCANCODE_KP_ENTER: return InputKey::NumPadEnter; + // case SDL_SCANCODE_KP_MULTIPLY: return InputKey::Multiply; + // case SDL_SCANCODE_KP_PLUS: return InputKey::Add; + case SDL_SCANCODE_SEPARATOR: return InputKey::Separator; + // case SDL_SCANCODE_KP_MINUS: return InputKey::Subtract; + case SDL_SCANCODE_KP_PERIOD: return InputKey::NumPadPeriod; + // case SDL_SCANCODE_KP_DIVIDE: return InputKey::Divide; + case SDL_SCANCODE_F1: return InputKey::F1; + case SDL_SCANCODE_F2: return InputKey::F2; + case SDL_SCANCODE_F3: return InputKey::F3; + case SDL_SCANCODE_F4: return InputKey::F4; + case SDL_SCANCODE_F5: return InputKey::F5; + case SDL_SCANCODE_F6: return InputKey::F6; + case SDL_SCANCODE_F7: return InputKey::F7; + case SDL_SCANCODE_F8: return InputKey::F8; + case SDL_SCANCODE_F9: return InputKey::F9; + case SDL_SCANCODE_F10: return InputKey::F10; + case SDL_SCANCODE_F11: return InputKey::F11; + case SDL_SCANCODE_F12: return InputKey::F12; + case SDL_SCANCODE_F13: return InputKey::F13; + case SDL_SCANCODE_F14: return InputKey::F14; + case SDL_SCANCODE_F15: return InputKey::F15; + case SDL_SCANCODE_F16: return InputKey::F16; + case SDL_SCANCODE_F17: return InputKey::F17; + case SDL_SCANCODE_F18: return InputKey::F18; + case SDL_SCANCODE_F19: return InputKey::F19; + case SDL_SCANCODE_F20: return InputKey::F20; + case SDL_SCANCODE_F21: return InputKey::F21; + case SDL_SCANCODE_F22: return InputKey::F22; + case SDL_SCANCODE_F23: return InputKey::F23; + case SDL_SCANCODE_F24: return InputKey::F24; + case SDL_SCANCODE_NUMLOCKCLEAR: return InputKey::NumLock; + case SDL_SCANCODE_SCROLLLOCK: return InputKey::ScrollLock; + case SDL_SCANCODE_LSHIFT: return InputKey::LShift; + case SDL_SCANCODE_RSHIFT: return InputKey::RShift; + case SDL_SCANCODE_LCTRL: return InputKey::LControl; + case SDL_SCANCODE_RCTRL: return InputKey::RControl; + case SDL_SCANCODE_GRAVE: return InputKey::Tilde; + default: return InputKey::None; + } +} + +SDL_Scancode SDL2DisplayWindow::InputKeyToScancode(InputKey inputkey) +{ + switch (inputkey) + { + case InputKey::Backspace: return SDL_SCANCODE_BACKSPACE; + case InputKey::Tab: return SDL_SCANCODE_TAB; + case InputKey::OEMClear: return SDL_SCANCODE_CLEAR; + case InputKey::Enter: return SDL_SCANCODE_RETURN; + case InputKey::Alt: return SDL_SCANCODE_MENU; + case InputKey::Pause: return SDL_SCANCODE_PAUSE; + case InputKey::Escape: return SDL_SCANCODE_ESCAPE; + case InputKey::Space: return SDL_SCANCODE_SPACE; + case InputKey::End: return SDL_SCANCODE_END; + case InputKey::Home: return SDL_SCANCODE_HOME; + case InputKey::Left: return SDL_SCANCODE_LEFT; + case InputKey::Up: return SDL_SCANCODE_UP; + case InputKey::Right: return SDL_SCANCODE_RIGHT; + case InputKey::Down: return SDL_SCANCODE_DOWN; + case InputKey::Select: return SDL_SCANCODE_SELECT; + case InputKey::Print: return SDL_SCANCODE_PRINTSCREEN; + case InputKey::Execute: return SDL_SCANCODE_EXECUTE; + case InputKey::Insert: return SDL_SCANCODE_INSERT; + case InputKey::Delete: return SDL_SCANCODE_DELETE; + case InputKey::Help: return SDL_SCANCODE_HELP; + case InputKey::_0: return SDL_SCANCODE_0; + case InputKey::_1: return SDL_SCANCODE_1; + case InputKey::_2: return SDL_SCANCODE_2; + case InputKey::_3: return SDL_SCANCODE_3; + case InputKey::_4: return SDL_SCANCODE_4; + case InputKey::_5: return SDL_SCANCODE_5; + case InputKey::_6: return SDL_SCANCODE_6; + case InputKey::_7: return SDL_SCANCODE_7; + case InputKey::_8: return SDL_SCANCODE_8; + case InputKey::_9: return SDL_SCANCODE_9; + case InputKey::A: return SDL_SCANCODE_A; + case InputKey::B: return SDL_SCANCODE_B; + case InputKey::C: return SDL_SCANCODE_C; + case InputKey::D: return SDL_SCANCODE_D; + case InputKey::E: return SDL_SCANCODE_E; + case InputKey::F: return SDL_SCANCODE_F; + case InputKey::G: return SDL_SCANCODE_G; + case InputKey::H: return SDL_SCANCODE_H; + case InputKey::I: return SDL_SCANCODE_I; + case InputKey::J: return SDL_SCANCODE_J; + case InputKey::K: return SDL_SCANCODE_K; + case InputKey::L: return SDL_SCANCODE_L; + case InputKey::M: return SDL_SCANCODE_M; + case InputKey::N: return SDL_SCANCODE_N; + case InputKey::O: return SDL_SCANCODE_O; + case InputKey::P: return SDL_SCANCODE_P; + case InputKey::Q: return SDL_SCANCODE_Q; + case InputKey::R: return SDL_SCANCODE_R; + case InputKey::S: return SDL_SCANCODE_S; + case InputKey::T: return SDL_SCANCODE_T; + case InputKey::U: return SDL_SCANCODE_U; + case InputKey::V: return SDL_SCANCODE_V; + case InputKey::W: return SDL_SCANCODE_W; + case InputKey::X: return SDL_SCANCODE_X; + case InputKey::Y: return SDL_SCANCODE_Y; + case InputKey::Z: return SDL_SCANCODE_Z; + case InputKey::NumPad0: return SDL_SCANCODE_KP_0; + case InputKey::NumPad1: return SDL_SCANCODE_KP_1; + case InputKey::NumPad2: return SDL_SCANCODE_KP_2; + case InputKey::NumPad3: return SDL_SCANCODE_KP_3; + case InputKey::NumPad4: return SDL_SCANCODE_KP_4; + case InputKey::NumPad5: return SDL_SCANCODE_KP_5; + case InputKey::NumPad6: return SDL_SCANCODE_KP_6; + case InputKey::NumPad7: return SDL_SCANCODE_KP_7; + case InputKey::NumPad8: return SDL_SCANCODE_KP_8; + case InputKey::NumPad9: return SDL_SCANCODE_KP_9; + // case InputKey::NumPadEnter: return SDL_SCANCODE_KP_ENTER; + // case InputKey::Multiply return SDL_SCANCODE_KP_MULTIPLY:; + // case InputKey::Add: return SDL_SCANCODE_KP_PLUS; + case InputKey::Separator: return SDL_SCANCODE_SEPARATOR; + // case InputKey::Subtract: return SDL_SCANCODE_KP_MINUS; + case InputKey::NumPadPeriod: return SDL_SCANCODE_KP_PERIOD; + // case InputKey::Divide: return SDL_SCANCODE_KP_DIVIDE; + case InputKey::F1: return SDL_SCANCODE_F1; + case InputKey::F2: return SDL_SCANCODE_F2; + case InputKey::F3: return SDL_SCANCODE_F3; + case InputKey::F4: return SDL_SCANCODE_F4; + case InputKey::F5: return SDL_SCANCODE_F5; + case InputKey::F6: return SDL_SCANCODE_F6; + case InputKey::F7: return SDL_SCANCODE_F7; + case InputKey::F8: return SDL_SCANCODE_F8; + case InputKey::F9: return SDL_SCANCODE_F9; + case InputKey::F10: return SDL_SCANCODE_F10; + case InputKey::F11: return SDL_SCANCODE_F11; + case InputKey::F12: return SDL_SCANCODE_F12; + case InputKey::F13: return SDL_SCANCODE_F13; + case InputKey::F14: return SDL_SCANCODE_F14; + case InputKey::F15: return SDL_SCANCODE_F15; + case InputKey::F16: return SDL_SCANCODE_F16; + case InputKey::F17: return SDL_SCANCODE_F17; + case InputKey::F18: return SDL_SCANCODE_F18; + case InputKey::F19: return SDL_SCANCODE_F19; + case InputKey::F20: return SDL_SCANCODE_F20; + case InputKey::F21: return SDL_SCANCODE_F21; + case InputKey::F22: return SDL_SCANCODE_F22; + case InputKey::F23: return SDL_SCANCODE_F23; + case InputKey::F24: return SDL_SCANCODE_F24; + case InputKey::NumLock: return SDL_SCANCODE_NUMLOCKCLEAR; + case InputKey::ScrollLock: return SDL_SCANCODE_SCROLLLOCK; + case InputKey::LShift: return SDL_SCANCODE_LSHIFT; + case InputKey::RShift: return SDL_SCANCODE_RSHIFT; + case InputKey::LControl: return SDL_SCANCODE_LCTRL; + case InputKey::RControl: return SDL_SCANCODE_RCTRL; + case InputKey::Tilde: return SDL_SCANCODE_GRAVE; + default: return (SDL_Scancode)0; + } +} diff --git a/src/window/sdl2/sdl2_display_window.h b/src/window/sdl2/sdl2_display_window.h new file mode 100644 index 000000000..5a510878e --- /dev/null +++ b/src/window/sdl2/sdl2_display_window.h @@ -0,0 +1,112 @@ +#pragma once + +#include +#include +#include +#include +#include + +class SDL2DisplayWindow : public DisplayWindow +{ +public: + SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, SDL2DisplayWindow* owner, RenderAPI renderAPI, double uiscale); + ~SDL2DisplayWindow(); + + void SetWindowTitle(const std::string& text) override; + void SetWindowFrame(const Rect& box) override; + void SetClientFrame(const Rect& box) override; + void Show() override; + void ShowFullscreen() override; + void ShowMaximized() override; + void ShowMinimized() override; + void ShowNormal() override; + bool IsWindowFullscreen() override; + void Hide() override; + void Activate() override; + void ShowCursor(bool enable) override; + void LockCursor() override; + void UnlockCursor() override; + void CaptureMouse() override; + void ReleaseMouseCapture() override; + void Update() override; + bool GetKeyState(InputKey key) override; + void SetCursor(StandardCursor cursor) override; + + Rect GetWindowFrame() const override; + Size GetClientSize() const override; + int GetPixelWidth() const override; + int GetPixelHeight() const override; + double GetDpiScale() const override; + + void PresentBitmap(int width, int height, const uint32_t* pixels) override; + + void SetBorderColor(uint32_t bgra8) override; + void SetCaptionColor(uint32_t bgra8) override; + void SetCaptionTextColor(uint32_t bgra8) override; + + std::string GetClipboardText() override; + void SetClipboardText(const std::string& text) override; + + Point MapFromGlobal(const Point& pos) const override; + Point MapToGlobal(const Point& pos) const override; + + void* GetNativeHandle() override { return &Handle; } + + std::vector GetVulkanInstanceExtensions() override; + VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override; + + static void DispatchEvent(const SDL_Event& event); + static SDL2DisplayWindow* FindEventWindow(const SDL_Event& event); + + void OnWindowEvent(const SDL_WindowEvent& event); + void OnTextInput(const SDL_TextInputEvent& event); + void OnKeyUp(const SDL_KeyboardEvent& event); + void OnKeyDown(const SDL_KeyboardEvent& event); + void OnMouseButtonUp(const SDL_MouseButtonEvent& event); + void OnMouseButtonDown(const SDL_MouseButtonEvent& event); + void OnMouseWheel(const SDL_MouseWheelEvent& event); + void OnMouseMotion(const SDL_MouseMotionEvent& event); + void OnPaintEvent(); + static void OnTimerEvent(const SDL_UserEvent& event); + + InputKey GetMouseButtonKey(const SDL_MouseButtonEvent& event); + + static InputKey ScancodeToInputKey(SDL_Scancode keycode); + static SDL_Scancode InputKeyToScancode(InputKey inputkey); + + template + Point GetMousePos(const T& event) + { + double uiscale = GetDpiScale(); + return Point(event.x / uiscale, event.y / uiscale); + } + + static void ProcessEvents(); + static void RunLoop(); + static void ExitLoop(); + + static void* StartTimer(int timeoutMilliseconds, std::function onTimer); + static void StopTimer(void* timerID); + static Uint32 ExecTimer(Uint32 interval, void* id); + + DisplayWindowHost* WindowHost = nullptr; + SDL2NativeHandle Handle; + SDL_Renderer* RendererHandle = nullptr; + SDL_Texture* BackBufferTexture = nullptr; + int BackBufferWidth = 0; + int BackBufferHeight = 0; + + double UIScale = 1.0; + + bool CursorLocked = false; + bool isFullscreen = false; + + static bool ExitRunLoop; + static Uint32 PaintEventNumber; + static std::unordered_map WindowList; + + static std::unordered_map TimerHandles; + static std::unordered_map> Timers; + static unsigned long TimerIDs; + static Uint32 TimerEventNumber; +}; diff --git a/src/window/stub/stub_open_file_dialog.cpp b/src/window/stub/stub_open_file_dialog.cpp new file mode 100644 index 000000000..b3c61fe0b --- /dev/null +++ b/src/window/stub/stub_open_file_dialog.cpp @@ -0,0 +1,64 @@ + +#include "stub_open_file_dialog.h" + +StubOpenFileDialog::StubOpenFileDialog(DisplayWindow* owner) : owner(owner) +{ +} + +bool StubOpenFileDialog::Show() +{ + return false; +} + +std::string StubOpenFileDialog::Filename() const +{ + return !filenames.empty() ? filenames.front() : std::string(); +} + +std::vector StubOpenFileDialog::Filenames() const +{ + return filenames; +} + +void StubOpenFileDialog::SetMultiSelect(bool new_multi_select) +{ + multi_select = new_multi_select; +} + +void StubOpenFileDialog::SetFilename(const std::string& filename) +{ + initial_filename = filename; +} + +void StubOpenFileDialog::AddFilter(const std::string& filter_description, const std::string& filter_extension) +{ + Filter f; + f.description = filter_description; + f.extension = filter_extension; + filters.push_back(std::move(f)); +} + +void StubOpenFileDialog::ClearFilters() +{ + filters.clear(); +} + +void StubOpenFileDialog::SetFilterIndex(int filter_index) +{ + filterindex = filter_index; +} + +void StubOpenFileDialog::SetInitialDirectory(const std::string& path) +{ + initial_directory = path; +} + +void StubOpenFileDialog::SetTitle(const std::string& newtitle) +{ + title = newtitle; +} + +void StubOpenFileDialog::SetDefaultExtension(const std::string& extension) +{ + defaultext = extension; +} diff --git a/src/window/stub/stub_open_file_dialog.h b/src/window/stub/stub_open_file_dialog.h new file mode 100644 index 000000000..9ffcead2e --- /dev/null +++ b/src/window/stub/stub_open_file_dialog.h @@ -0,0 +1,41 @@ +#pragma once + +#include "systemdialogs/open_file_dialog.h" + +class DisplayWindow; + +class StubOpenFileDialog : public OpenFileDialog +{ +public: + StubOpenFileDialog(DisplayWindow* owner); + + bool Show() override; + std::string Filename() const override; + std::vector Filenames() const override; + void SetMultiSelect(bool new_multi_select) override; + void SetFilename(const std::string& filename) override; + void AddFilter(const std::string& filter_description, const std::string& filter_extension) override; + void ClearFilters() override; + void SetFilterIndex(int filter_index) override; + void SetInitialDirectory(const std::string& path) override; + void SetTitle(const std::string& newtitle) override; + void SetDefaultExtension(const std::string& extension) override; + +private: + DisplayWindow* owner = nullptr; + + std::string initial_directory; + std::string initial_filename; + std::string title; + std::vector filenames; + bool multi_select = false; + + struct Filter + { + std::string description; + std::string extension; + }; + std::vector filters; + int filterindex = 0; + std::string defaultext; +}; diff --git a/src/window/stub/stub_open_folder_dialog.cpp b/src/window/stub/stub_open_folder_dialog.cpp new file mode 100644 index 000000000..83ca24469 --- /dev/null +++ b/src/window/stub/stub_open_folder_dialog.cpp @@ -0,0 +1,26 @@ + +#include "stub_open_folder_dialog.h" + +StubOpenFolderDialog::StubOpenFolderDialog(DisplayWindow* owner) : owner(owner) +{ +} + +bool StubOpenFolderDialog::Show() +{ + return false; +} + +std::string StubOpenFolderDialog::SelectedPath() const +{ + return selected_path; +} + +void StubOpenFolderDialog::SetInitialDirectory(const std::string& path) +{ + initial_directory = path; +} + +void StubOpenFolderDialog::SetTitle(const std::string& newtitle) +{ + title = newtitle; +} diff --git a/src/window/stub/stub_open_folder_dialog.h b/src/window/stub/stub_open_folder_dialog.h new file mode 100644 index 000000000..4d18708be --- /dev/null +++ b/src/window/stub/stub_open_folder_dialog.h @@ -0,0 +1,23 @@ +#pragma once + +#include "systemdialogs/open_folder_dialog.h" + +class DisplayWindow; + +class StubOpenFolderDialog : public OpenFolderDialog +{ +public: + StubOpenFolderDialog(DisplayWindow* owner); + + bool Show() override; + std::string SelectedPath() const override; + void SetInitialDirectory(const std::string& path) override; + void SetTitle(const std::string& newtitle) override; + +private: + DisplayWindow* owner = nullptr; + + std::string selected_path; + std::string initial_directory; + std::string title; +}; diff --git a/src/window/stub/stub_save_file_dialog.cpp b/src/window/stub/stub_save_file_dialog.cpp new file mode 100644 index 000000000..64e96a2ff --- /dev/null +++ b/src/window/stub/stub_save_file_dialog.cpp @@ -0,0 +1,54 @@ + +#include "stub_save_file_dialog.h" + +StubSaveFileDialog::StubSaveFileDialog(DisplayWindow* owner) : owner(owner) +{ +} + +bool StubSaveFileDialog::Show() +{ + return false; +} + +std::string StubSaveFileDialog::Filename() const +{ + return filename; +} + +void StubSaveFileDialog::SetFilename(const std::string& filename) +{ + initial_filename = filename; +} + +void StubSaveFileDialog::AddFilter(const std::string& filter_description, const std::string& filter_extension) +{ + Filter f; + f.description = filter_description; + f.extension = filter_extension; + filters.push_back(std::move(f)); +} + +void StubSaveFileDialog::ClearFilters() +{ + filters.clear(); +} + +void StubSaveFileDialog::SetFilterIndex(int filter_index) +{ + filterindex = filter_index; +} + +void StubSaveFileDialog::SetInitialDirectory(const std::string& path) +{ + initial_directory = path; +} + +void StubSaveFileDialog::SetTitle(const std::string& newtitle) +{ + title = newtitle; +} + +void StubSaveFileDialog::SetDefaultExtension(const std::string& extension) +{ + defaultext = extension; +} diff --git a/src/window/stub/stub_save_file_dialog.h b/src/window/stub/stub_save_file_dialog.h new file mode 100644 index 000000000..c57ce2e5e --- /dev/null +++ b/src/window/stub/stub_save_file_dialog.h @@ -0,0 +1,39 @@ +#pragma once + +#include "systemdialogs/save_file_dialog.h" + +class DisplayWindow; + +class StubSaveFileDialog : public SaveFileDialog +{ +public: + StubSaveFileDialog(DisplayWindow* owner); + + bool Show() override; + std::string Filename() const override; + void SetFilename(const std::string& filename) override; + void AddFilter(const std::string& filter_description, const std::string& filter_extension) override; + void ClearFilters() override; + void SetFilterIndex(int filter_index) override; + void SetInitialDirectory(const std::string& path) override; + void SetTitle(const std::string& newtitle) override; + void SetDefaultExtension(const std::string& extension) override; + +private: + DisplayWindow* owner = nullptr; + + std::string filename; + std::string initial_directory; + std::string initial_filename; + std::string title; + std::vector filenames; + + struct Filter + { + std::string description; + std::string extension; + }; + std::vector filters; + int filterindex = 0; + std::string defaultext; +}; diff --git a/src/window/wayland/wayland_display_backend.cpp b/src/window/wayland/wayland_display_backend.cpp new file mode 100644 index 000000000..ffb394853 --- /dev/null +++ b/src/window/wayland/wayland_display_backend.cpp @@ -0,0 +1,1150 @@ +#include "wayland_display_backend.h" +#include "wayland_display_window.h" + +#ifdef USE_DBUS +#include "window/dbus/dbus_open_file_dialog.h" +#include "window/dbus/dbus_save_file_dialog.h" +#include "window/dbus/dbus_open_folder_dialog.h" +#endif + +WaylandDisplayBackend::WaylandDisplayBackend() +{ + if (!s_waylandDisplay) + throw std::runtime_error("Wayland Display initialization failed!"); + + s_waylandRegistry = s_waylandDisplay.get_registry(); + + s_waylandRegistry.on_global() = [&](uint32_t name, std::string interface, uint32_t version) { + if (interface == wayland::compositor_t::interface_name) + s_waylandRegistry.bind(name, m_waylandCompositor, 3); + if (interface == wayland::shm_t::interface_name) + s_waylandRegistry.bind(name, m_waylandSHM, version); + if (interface == wayland::output_t::interface_name) + s_waylandRegistry.bind(name, m_waylandOutput, version); + if (interface == wayland::seat_t::interface_name) + s_waylandRegistry.bind(name, m_waylandSeat, 8); + if (interface == wayland::data_device_manager_t::interface_name) + s_waylandRegistry.bind(name, m_DataDeviceManager, 3); + if (interface == wayland::xdg_wm_base_t::interface_name) + s_waylandRegistry.bind(name, m_XDGWMBase, 4); + if (interface == wayland::zxdg_output_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_XDGOutputManager, version); + if (interface == wayland::zxdg_exporter_v2_t::interface_name) + s_waylandRegistry.bind(name, m_XDGExporter, 1); + if (interface == wayland::zwp_pointer_constraints_v1_t::interface_name) + s_waylandRegistry.bind(name, m_PointerConstraints, 1); + if (interface == wayland::xdg_activation_v1_t::interface_name) + s_waylandRegistry.bind(name, m_XDGActivation, 1); + if (interface == wayland::zxdg_decoration_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_XDGDecorationManager, 1); + if (interface == wayland::fractional_scale_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_FractionalScaleManager, 1); + if (interface == wayland::zwp_relative_pointer_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_RelativePointerManager, 1); + }; + + s_waylandDisplay.roundtrip(); + + if (!m_XDGWMBase) + throw std::runtime_error("WaylandDisplayBackend: XDG-Shell is required!"); + + if (!m_XDGOutputManager) + throw std::runtime_error("WaylandDisplayBackend: xdg-output-manager-v1 is required!"); + + if (!m_XDGExporter) + throw std::runtime_error("WaylandDisplayBackend: xdg-foreign-unstable-v2 is required!"); + + if (!m_PointerConstraints) + throw std::runtime_error("WaylandDisplayBackend: pointer-constrains-unstable-v1 is required!"); + + if (!m_RelativePointerManager) + throw std::runtime_error("WaylandDisplayBackend: relative-pointer-unstable-v1 is required!"); + + m_waylandOutput.on_mode() = [this] (wayland::output_mode flags, int32_t width, int32_t height, int32_t refresh) { + s_ScreenSize = Size(width, height); + }; + + m_XDGWMBase.on_ping() = [this] (uint32_t serial) { + m_XDGWMBase.pong(serial); + }; + + m_waylandSeat.on_capabilities() = [this] (uint32_t capabilities) { + hasKeyboard = capabilities & wayland::seat_capability::keyboard; + hasPointer = capabilities & wayland::seat_capability::pointer; + }; + + m_XDGOutput = m_XDGOutputManager.get_xdg_output(m_waylandOutput); + + m_XDGOutput.on_logical_position() = [this] (int32_t x, int32_t y) { + //m_WindowGlobalPos = Point(x, y); + }; + + m_XDGOutput.on_logical_size() = [this] (int32_t width, int32_t height) { + s_ScreenSize = Size(width, height); + }; + + if (!m_FractionalScaleManager) + { + m_waylandOutput.on_scale() = [this] (int32_t scale) { + for (WaylandDisplayWindow* w : s_Windows) + { + w->m_ScaleFactor = scale; + w->m_NeedsUpdate = true; + w->windowHost->OnWindowDpiScaleChanged(); + } + }; + } + + s_waylandDisplay.roundtrip(); + + // To do: this shouldn't really be fatal. The user might have forgotten to plug in their keyboard or mouse. + if (!hasKeyboard) + throw std::runtime_error("No keyboard detected!"); + if (!hasPointer) + throw std::runtime_error("No pointer device detected!"); + + m_waylandKeyboard = m_waylandSeat.get_keyboard(); + m_waylandPointer = m_waylandSeat.get_pointer(); + m_RelativePointer = m_RelativePointerManager.get_relative_pointer(m_waylandPointer); + + ConnectDeviceEvents(); + + m_cursorSurface = m_waylandCompositor.create_surface(); + SetCursor(StandardCursor::arrow); + +/* + m_DataDevice = m_DataDeviceManager.get_data_device(m_waylandSeat); + + m_DataSource = m_DataDeviceManager.create_data_source(); + m_DataSource.offer("text/plain"); + + m_DataSource.on_send() = [&] (std::string mime_type, int fd) { + if (mime_type != "text/plain") + return; + + if (!m_ClipboardContents.empty()) + write(fd, m_ClipboardContents.data(), m_ClipboardContents.size()); + close(fd); + }; + + m_DataDevice.on_selection() = [&] (wayland::data_offer_t dataOffer) { + m_ClipboardContents.clear(); + + if (!dataOffer) + // Clipboard is empty + return; + + int fds[2]; + pipe(fds); + + dataOffer.receive("text/plain", fds[1]); + close(fds[1]); + + m_waylandDisplay.roundtrip(); + + while (true) + { + char buf[1024]; + + ssize_t n = read(fds[0], buf, sizeof(buf)); + + if (n <= 0) + break; + + m_ClipboardContents += buf; + } + + close(fds[0]); + + dataOffer.proxy_release(); + }; +*/ +} + +WaylandDisplayBackend::~WaylandDisplayBackend() +{ + if (m_KeymapContext) + xkb_context_unref(m_KeymapContext); + + if (m_KeyboardState) + xkb_state_unref(m_KeyboardState); +} + +void WaylandDisplayBackend::ConnectDeviceEvents() +{ + m_KeymapContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + m_waylandKeyboard.on_keymap() = [this] (wayland::keyboard_keymap_format format, int fd, uint32_t size) { + if (format != wayland::keyboard_keymap_format::xkb_v1) + throw std::runtime_error("WaylandDisplayBackend: Unrecognized keymap format!"); + + char* mapSHM = (char*)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (mapSHM == MAP_FAILED) + throw std::runtime_error("WaylandDisplayBackend: Keymap shared memory allocation failed!"); + + if (m_Keymap) + xkb_keymap_unref(m_Keymap); + + m_Keymap = xkb_keymap_new_from_string(m_KeymapContext, mapSHM, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(mapSHM, size); + close(fd); + + if (m_KeyboardState) + xkb_state_unref(m_KeyboardState); + + m_KeyboardState = xkb_state_new(m_Keymap); + }; + + m_waylandKeyboard.on_modifiers() = [this] (uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { + xkb_state_update_mask(m_KeyboardState, mods_depressed, mods_latched, mods_locked, 0, 0, group); + }; + + m_waylandKeyboard.on_enter() = [this] (uint32_t serial, wayland::surface_t surfaceEntered, wayland::array_t keys) { + std::vector keysVec = keys; + + m_KeyboardSerial = serial; + + // Find the window to focus on by checking the surface window owns. + if (!m_FocusWindow || m_FocusWindow->GetWindowSurface() != surfaceEntered) + { + for (auto win: s_Windows) + { + if (win->GetWindowSurface() == surfaceEntered) + m_FocusWindow = win; + } + } + + + for (auto key: keysVec) + { + // keys parameter represents the keys pressed when entering the surface + // key variable is Linux evdev scancode, to translate it to XKB keycode, we must add 8 + xkb_keysym_t sym = xkb_state_key_get_one_sym(m_KeyboardState, key + 8); + OnKeyboardKeyEvent(sym, wayland::keyboard_key_state::pressed); + + // Also cause a Char event + char buf[128]; + xkb_state_key_get_utf8(m_KeyboardState, key + 8, buf, sizeof(buf)); + + OnKeyboardCharEvent(buf, wayland::keyboard_key_state::pressed); + } + }; + + m_waylandKeyboard.on_key() = [this] (uint32_t serial, uint32_t time, uint32_t key, wayland::keyboard_key_state state) { + // key is Linux evdev scancode, to translate it to XKB keycode, we must add 8 + xkb_keysym_t sym = xkb_state_key_get_one_sym(m_KeyboardState, key + 8); + OnKeyboardKeyEvent(sym, state); + + // Also cause a Char event + char buf[128]; + xkb_state_key_get_utf8(m_KeyboardState, key + 8, buf, sizeof(buf)); + + OnKeyboardCharEvent(buf, state); + + //m_DataDevice.set_selection(m_DataSource, m_KeyboardSerial); + }; + + m_waylandPointer.on_enter() = [this](uint32_t serial, wayland::surface_t surfaceEntered, double surfaceX, double surfaceY) { + // Find the window to focus on by checking the surface window owns. + if (!m_MouseFocusWindow || m_MouseFocusWindow->GetWindowSurface() != surfaceEntered) + { + for (auto win: s_Windows) + { + if (win->GetWindowSurface() == surfaceEntered) + { + m_MouseFocusWindow = win; + } + } + } + + currentPointerEvent.event_mask |= POINTER_EVENT_ENTER; + currentPointerEvent.serial = serial; + currentPointerEvent.surfaceX = surfaceX; + currentPointerEvent.surfaceY = surfaceY; + }; + + m_waylandPointer.on_leave() = [this](uint32_t serial, wayland::surface_t surfaceLeft) { + currentPointerEvent.event_mask |= POINTER_EVENT_LEAVE; + currentPointerEvent.serial = serial; + }; + + m_waylandPointer.on_motion() = [this] (uint32_t serial, double surfaceX, double surfaceY) { + currentPointerEvent.event_mask |= POINTER_EVENT_MOTION; + currentPointerEvent.serial = serial; + currentPointerEvent.surfaceX = surfaceX; + currentPointerEvent.surfaceY = surfaceY; + }; + + m_waylandPointer.on_button() = [this] (uint32_t serial, uint32_t time, uint32_t button, wayland::pointer_button_state state) { + currentPointerEvent.event_mask |= POINTER_EVENT_BUTTON; + currentPointerEvent.serial = serial; + currentPointerEvent.time = time; + currentPointerEvent.button = button; + currentPointerEvent.state = state; + }; + + m_waylandPointer.on_axis() = [this] (uint32_t serial, wayland::pointer_axis axis, double value) { + currentPointerEvent.event_mask |= POINTER_EVENT_AXIS; + currentPointerEvent.serial = serial; + currentPointerEvent.axes[uint32_t(axis)].value = value; + currentPointerEvent.axes[uint32_t(axis)].valid = true; + }; + + // High resolution scroll event + m_waylandPointer.on_axis_value120() = [this] (wayland::pointer_axis axis, int32_t value) { + currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_120; + currentPointerEvent.axes[uint32_t(axis)].valid = true; + currentPointerEvent.axes[uint32_t(axis)].value120 = value; + }; + + m_waylandPointer.on_axis_source() = [this] (wayland::pointer_axis_source axis) { + currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_SOURCE; + currentPointerEvent.axis_source = axis; + }; + + m_waylandPointer.on_axis_stop() = [this] (uint32_t time, wayland::pointer_axis axis) { + currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_STOP; + currentPointerEvent.time = time; + currentPointerEvent.axes[uint32_t(axis)].valid = true; + }; + + m_RelativePointer.on_relative_motion() = [this] (uint32_t utime_hi, uint32_t utime_lo, + double dx, double dy, + double dx_unaccel, double dy_unaccel) { + currentPointerEvent.event_mask |= POINTER_EVENT_RELATIVE_MOTION; + currentPointerEvent.time = utime_lo; + currentPointerEvent.dx = dx; + currentPointerEvent.dy = dy; + }; + + m_waylandPointer.on_frame() = [this] () { + if (currentPointerEvent.event_mask & POINTER_EVENT_ENTER) + OnMouseEnterEvent(currentPointerEvent.serial); + + if (currentPointerEvent.event_mask & POINTER_EVENT_LEAVE) + OnMouseLeaveEvent(); + + if (currentPointerEvent.event_mask & POINTER_EVENT_MOTION) + { + OnMouseMoveEvent(Point(currentPointerEvent.surfaceX, currentPointerEvent.surfaceY)); + } + + if (currentPointerEvent.event_mask & POINTER_EVENT_RELATIVE_MOTION) + { + if (hasMouseLock) + OnMouseMoveRawEvent(int(currentPointerEvent.dx), int(currentPointerEvent.dy)); + } + + if (currentPointerEvent.event_mask & POINTER_EVENT_BUTTON) + { + InputKey ikey = LinuxInputEventCodeToInputKey(currentPointerEvent.button); + if (currentPointerEvent.state == wayland::pointer_button_state::pressed) + OnMousePressEvent(ikey); + else // released + OnMouseReleaseEvent(ikey); + } + + uint32_t axisevents = POINTER_EVENT_AXIS | POINTER_EVENT_AXIS_120 | POINTER_EVENT_AXIS_SOURCE | POINTER_EVENT_AXIS_STOP; + + if (currentPointerEvent.event_mask & axisevents) + { + for (size_t idx = 0 ; idx < 2 ; idx++) + { + if (!currentPointerEvent.axes[idx].valid) + continue; + + if (currentPointerEvent.event_mask & POINTER_EVENT_AXIS_120) + { + if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value > 0) + OnMouseWheelEvent(InputKey::MouseWheelDown); + if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value < 0) + OnMouseWheelEvent(InputKey::MouseWheelUp); + } + else if (currentPointerEvent.event_mask & POINTER_EVENT_AXIS) + { + if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value120 > 0) + OnMouseWheelEvent(InputKey::MouseWheelDown); + if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value120 < 0) + OnMouseWheelEvent(InputKey::MouseWheelUp); + } + } + } + + // Reset everything once all the events are processed + currentPointerEvent = {0}; + }; + + m_keyboardDelayTimer = ZTimer(); + m_keyboardRepeatTimer = ZTimer(); + + m_waylandKeyboard.on_repeat_info() = [this] (int32_t rate, int32_t delay) { + // rate is characters per second, delay is in milliseconds + m_keyboardDelayTimer.SetDuration(ZTimer::Duration(delay)); + m_keyboardRepeatTimer.SetDuration(ZTimer::Duration(1000.0 / rate)); + }; + + m_keyboardDelayTimer.SetCallback([this] () { OnKeyboardDelayEnd(); }); + m_keyboardRepeatTimer.SetCallback([this] () { OnKeyboardRepeat(); }); + + m_keyboardRepeatTimer.SetRepeating(true); + + m_previousTime = ZTimer::Clock::now(); + m_currentTime = ZTimer::Clock::now(); +} + +void WaylandDisplayBackend::OnKeyboardKeyEvent(xkb_keysym_t xkbKeySym, wayland::keyboard_key_state state) +{ + InputKey inputKey = XKBKeySymToInputKey(xkbKeySym); + + if (state == wayland::keyboard_key_state::pressed) + { + inputKeyStates[inputKey] = true; + m_FocusWindow->windowHost->OnWindowKeyDown(inputKey); + if (inputKey != previousKey) + { + previousKey = inputKey; + m_keyboardDelayTimer.Stop(); + m_keyboardRepeatTimer.Stop(); + } + m_keyboardDelayTimer.Start(); + } + if (state == wayland::keyboard_key_state::released) + { + inputKeyStates[inputKey] = false; + if (m_FocusWindow) + m_FocusWindow->windowHost->OnWindowKeyUp(inputKey); + m_keyboardDelayTimer.Stop(); + m_keyboardRepeatTimer.Stop(); + } + +} + +void WaylandDisplayBackend::OnKeyboardCharEvent(const char* ch, wayland::keyboard_key_state state) +{ + if (state == wayland::keyboard_key_state::pressed) + { + previousChars = std::string(ch); + m_FocusWindow->windowHost->OnWindowKeyChar(previousChars); + } +} + +void WaylandDisplayBackend::OnKeyboardDelayEnd() +{ + if (inputKeyStates[previousKey]) + m_keyboardRepeatTimer.Start(); +} + +void WaylandDisplayBackend::OnKeyboardRepeat() +{ + if (inputKeyStates[previousKey]) + { + m_FocusWindow->windowHost->OnWindowKeyDown(previousKey); + m_FocusWindow->windowHost->OnWindowKeyChar(previousChars); + } +} + +void WaylandDisplayBackend::OnMouseEnterEvent(uint32_t serial) +{ + m_cursorSurface.attach(!hasMouseLock ? m_cursorBuffer : nullptr, 0, 0); + if (!hasMouseLock) + m_cursorSurface.damage(0, 0, m_cursorImage.width(), m_cursorImage.height()); + m_cursorSurface.commit(); + m_waylandPointer.set_cursor(serial, m_cursorSurface, 0, 0); +} + +void WaylandDisplayBackend::OnMouseLeaveEvent() +{ + if (m_MouseFocusWindow) + { + m_MouseFocusWindow->windowHost->OnWindowMouseLeave(); + //m_MouseFocusWindow = nullptr; // Borks up the menus + } +} + +void WaylandDisplayBackend::OnMousePressEvent(InputKey button) +{ + if (m_MouseFocusWindow) + m_MouseFocusWindow->windowHost->OnWindowMouseDown(m_MouseFocusWindow->m_SurfaceMousePos, button); +} + +void WaylandDisplayBackend::OnMouseReleaseEvent(InputKey button) +{ + if (m_MouseFocusWindow) + m_MouseFocusWindow->windowHost->OnWindowMouseUp(m_MouseFocusWindow->m_SurfaceMousePos, button); +} + +void WaylandDisplayBackend::OnMouseMoveEvent(Point surfacePos) +{ + if (m_MouseFocusWindow) + { + m_MouseFocusWindow->m_SurfaceMousePos = surfacePos / m_MouseFocusWindow->m_ScaleFactor; + m_MouseFocusWindow->windowHost->OnWindowMouseMove(m_MouseFocusWindow->m_SurfaceMousePos); + } +} + +void WaylandDisplayBackend::OnMouseMoveRawEvent(int dx, int dy) +{ + if (m_MouseFocusWindow) + m_MouseFocusWindow->windowHost->OnWindowRawMouseMove(dx, dy); +} + +void WaylandDisplayBackend::OnMouseWheelEvent(InputKey button) +{ + if (m_MouseFocusWindow) + m_MouseFocusWindow->windowHost->OnWindowMouseWheel(m_MouseFocusWindow->m_SurfaceMousePos, button); +} + +void WaylandDisplayBackend::SetCursor(StandardCursor cursor) +{ + std::string cursorName = GetWaylandCursorName(cursor); + + // Perhaps the cursor size can be inferred from the user prefs as well? + wayland::cursor_theme_t cursorTheme = wayland::cursor_theme_t("default", 16, m_waylandSHM); + wayland::cursor_t obtainedCursor = cursorTheme.get_cursor(cursorName); + m_cursorImage = obtainedCursor.image(0); + m_cursorBuffer = m_cursorImage.get_buffer(); +} + +void WaylandDisplayBackend::ShowCursor(bool enable) +{ + m_cursorSurface.attach(enable ? m_cursorBuffer : nullptr, 0, 0); + m_cursorSurface.commit(); +} + +std::unique_ptr WaylandDisplayBackend::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) +{ + return std::make_unique(this, windowHost, popupWindow, static_cast(owner), renderAPI); +} + +void WaylandDisplayBackend::OnWindowCreated(WaylandDisplayWindow* window) +{ + s_Windows.push_back(window); + if (!window->m_PopupWindow) + { + m_FocusWindow = window; + m_MouseFocusWindow = window; + } + +} + +void WaylandDisplayBackend::OnWindowDestroyed(WaylandDisplayWindow* window) +{ + auto it = std::find(s_Windows.begin(), s_Windows.end(), window); + if (it != s_Windows.end()) + s_Windows.erase(it); + + if (m_FocusWindow == window) + m_FocusWindow = nullptr; + + if (m_MouseFocusWindow == window) + m_MouseFocusWindow = nullptr; +} + +void WaylandDisplayBackend::ProcessEvents() +{ + while (wl_display_prepare_read(s_waylandDisplay)) + s_waylandDisplay.dispatch_pending(); + + while (wl_display_flush(s_waylandDisplay) < 0 && errno == EAGAIN) + poll_single(s_waylandDisplay.get_fd(), POLLOUT, -1); + + if (poll_single(s_waylandDisplay.get_fd(), POLLIN, 0) & POLLIN) + { + wl_display_read_events(s_waylandDisplay); + s_waylandDisplay.dispatch_pending(); + } + else + wl_display_cancel_read(s_waylandDisplay); + + if (s_waylandDisplay.get_error()) + throw std::runtime_error("Wayland Protocol Error"); +} + +void WaylandDisplayBackend::RunLoop() +{ + exitRunLoop = false; + + while (!exitRunLoop) + { + CheckNeedsUpdate(); + UpdateTimers(); + + ProcessEvents(); + } +} + +void WaylandDisplayBackend::UpdateTimers() +{ + m_currentTime = ZTimer::Clock::now(); + + m_keyboardDelayTimer.Update(m_currentTime - m_previousTime); + m_keyboardRepeatTimer.Update(m_currentTime - m_previousTime); + + m_previousTime = m_currentTime; +} + +void WaylandDisplayBackend::CheckNeedsUpdate() +{ + for (auto window: s_Windows) + { + if (window->m_NeedsUpdate) + { + window->m_NeedsUpdate = false; + window->windowHost->OnWindowPaint(); + } + } +} + +void WaylandDisplayBackend::ExitLoop() +{ + exitRunLoop = true; +} + +Size WaylandDisplayBackend::GetScreenSize() +{ + return s_ScreenSize; +} + +void* WaylandDisplayBackend::StartTimer(int timeoutMilliseconds, std::function onTimer) +{ + return nullptr; +} + +void WaylandDisplayBackend::StopTimer(void* timerID) +{ +} + +#ifdef USE_DBUS +std::unique_ptr WaylandDisplayBackend::CreateOpenFileDialog(DisplayWindow* owner) +{ + std::string ownerHandle; + if (owner) + ownerHandle = "wayland:" + static_cast(owner)->GetWaylandWindowID(); + return std::make_unique(ownerHandle); +} + +std::unique_ptr WaylandDisplayBackend::CreateSaveFileDialog(DisplayWindow* owner) +{ + std::string ownerHandle; + if (owner) + ownerHandle = "wayland:" + static_cast(owner)->GetWaylandWindowID(); + return std::make_unique(ownerHandle); +} + +std::unique_ptr WaylandDisplayBackend::CreateOpenFolderDialog(DisplayWindow* owner) +{ + std::string ownerHandle; + if (owner) + ownerHandle = "wayland:" + static_cast(owner)->GetWaylandWindowID(); + return std::make_unique(ownerHandle); +} +#endif + +bool WaylandDisplayBackend::GetKeyState(InputKey key) +{ + auto it = inputKeyStates.find(key); + + // if the key isn't "registered", then it is not pressed. + if (it == inputKeyStates.end()) + return false; + + return it->second; +} + +InputKey WaylandDisplayBackend::XKBKeySymToInputKey(xkb_keysym_t keySym) +{ + switch (keySym) + { + case XKB_KEY_Escape: + return InputKey::Escape; + case XKB_KEY_1: + return InputKey::_1; + case XKB_KEY_2: + return InputKey::_2; + case XKB_KEY_3: + return InputKey::_3; + case XKB_KEY_4: + return InputKey::_4; + case XKB_KEY_5: + return InputKey::_5; + case XKB_KEY_6: + return InputKey::_6; + case XKB_KEY_7: + return InputKey::_7; + case XKB_KEY_8: + return InputKey::_8; + case XKB_KEY_9: + return InputKey::_9; + case XKB_KEY_0: + return InputKey::_0; + case XKB_KEY_KP_1: + return InputKey::NumPad1; + case XKB_KEY_KP_2: + return InputKey::NumPad2; + case XKB_KEY_KP_3: + return InputKey::NumPad3; + case XKB_KEY_KP_4: + return InputKey::NumPad4; + case XKB_KEY_KP_5: + return InputKey::NumPad5; + case XKB_KEY_KP_6: + return InputKey::NumPad6; + case XKB_KEY_KP_7: + return InputKey::NumPad7; + case XKB_KEY_KP_8: + return InputKey::NumPad8; + case XKB_KEY_KP_9: + return InputKey::NumPad9; + case XKB_KEY_KP_0: + return InputKey::NumPad0; + case XKB_KEY_F1: + return InputKey::F1; + case XKB_KEY_F2: + return InputKey::F2; + case XKB_KEY_F3: + return InputKey::F3; + case XKB_KEY_F4: + return InputKey::F4; + case XKB_KEY_F5: + return InputKey::F5; + case XKB_KEY_F6: + return InputKey::F6; + case XKB_KEY_F7: + return InputKey::F7; + case XKB_KEY_F8: + return InputKey::F8; + case XKB_KEY_F9: + return InputKey::F9; + case XKB_KEY_F10: + return InputKey::F10; + case XKB_KEY_F11: + return InputKey::F11; + case XKB_KEY_F12: + return InputKey::F12; + case XKB_KEY_F13: + return InputKey::F13; + case XKB_KEY_F14: + return InputKey::F14; + case XKB_KEY_F15: + return InputKey::F15; + case XKB_KEY_F16: + return InputKey::F16; + case XKB_KEY_F17: + return InputKey::F17; + case XKB_KEY_F18: + return InputKey::F18; + case XKB_KEY_F19: + return InputKey::F19; + case XKB_KEY_F20: + return InputKey::F20; + case XKB_KEY_F21: + return InputKey::F21; + case XKB_KEY_F22: + return InputKey::F22; + case XKB_KEY_F23: + return InputKey::F23; + case XKB_KEY_F24: + return InputKey::F24; + case XKB_KEY_minus: + case XKB_KEY_KP_Subtract: + return InputKey::Minus; + case XKB_KEY_equal: + return InputKey::Equals; + case XKB_KEY_BackSpace: + return InputKey::Backspace; + case XKB_KEY_backslash: + return InputKey::Backslash; + case XKB_KEY_Tab: + return InputKey::Tab; + case XKB_KEY_braceleft: + return InputKey::LeftBracket; + case XKB_KEY_braceright: + return InputKey::RightBracket; + case XKB_KEY_Control_L: + case XKB_KEY_Control_R: + return InputKey::Ctrl; + case XKB_KEY_Alt_L: + case XKB_KEY_Alt_R: + return InputKey::Alt; + case XKB_KEY_Delete: + return InputKey::Delete; + case XKB_KEY_semicolon: + return InputKey::Semicolon; + case XKB_KEY_comma: + return InputKey::Comma; + case XKB_KEY_period: + return InputKey::Period; + case XKB_KEY_Num_Lock: + return InputKey::NumLock; + case XKB_KEY_Caps_Lock: + return InputKey::CapsLock; + case XKB_KEY_Scroll_Lock: + return InputKey::ScrollLock; + case XKB_KEY_Shift_L: + return InputKey::LShift; + case XKB_KEY_Shift_R: + return InputKey::RShift; + case XKB_KEY_grave: + return InputKey::Tilde; + case XKB_KEY_apostrophe: + return InputKey::SingleQuote; + case XKB_KEY_KP_Enter: + case XKB_KEY_Return: + return InputKey::Enter; + case XKB_KEY_space: + return InputKey::Space; + + case XKB_KEY_Up: + return InputKey::Up; + case XKB_KEY_Down: + return InputKey::Down; + case XKB_KEY_Left: + return InputKey::Left; + case XKB_KEY_Right: + return InputKey::Right; + + case XKB_KEY_A: + case XKB_KEY_a: + return InputKey::A; + case XKB_KEY_B: + case XKB_KEY_b: + return InputKey::B; + case XKB_KEY_C: + case XKB_KEY_c: + return InputKey::C; + case XKB_KEY_D: + case XKB_KEY_d: + return InputKey::D; + case XKB_KEY_E: + case XKB_KEY_e: + return InputKey::E; + case XKB_KEY_F: + case XKB_KEY_f: + return InputKey::F; + case XKB_KEY_G: + case XKB_KEY_g: + return InputKey::G; + case XKB_KEY_H: + case XKB_KEY_h: + return InputKey::H; + case XKB_KEY_I: + case XKB_KEY_i: + return InputKey::I; + case XKB_KEY_J: + case XKB_KEY_j: + return InputKey::J; + case XKB_KEY_K: + case XKB_KEY_k: + return InputKey::K; + case XKB_KEY_L: + case XKB_KEY_l: + return InputKey::L; + case XKB_KEY_M: + case XKB_KEY_m: + return InputKey::M; + case XKB_KEY_N: + case XKB_KEY_n: + return InputKey::N; + case XKB_KEY_O: + case XKB_KEY_o: + return InputKey::O; + case XKB_KEY_P: + case XKB_KEY_p: + return InputKey::P; + case XKB_KEY_Q: + case XKB_KEY_q: + return InputKey::Q; + case XKB_KEY_R: + case XKB_KEY_r: + return InputKey::R; + case XKB_KEY_S: + case XKB_KEY_s: + return InputKey::S; + case XKB_KEY_T: + case XKB_KEY_t: + return InputKey::T; + case XKB_KEY_U: + case XKB_KEY_u: + return InputKey::U; + case XKB_KEY_V: + case XKB_KEY_v: + return InputKey::V; + case XKB_KEY_W: + case XKB_KEY_w: + return InputKey::W; + case XKB_KEY_X: + case XKB_KEY_x: + return InputKey::X; + case XKB_KEY_Y: + case XKB_KEY_y: + return InputKey::Y; + case XKB_KEY_Z: + case XKB_KEY_z: + return InputKey::Z; + + case XKB_KEY_NoSymbol: + case XKB_KEY_VoidSymbol: + return InputKey::None; + default: + return InputKey::None; + } +} + +InputKey WaylandDisplayBackend::LinuxInputEventCodeToInputKey(uint32_t inputCode) +{ + switch (inputCode) + { + // Keyboard + // Probably not needed due to the existence of XKBKeySym + case KEY_ESC: + return InputKey::Escape; + case KEY_1: + return InputKey::_1; + case KEY_2: + return InputKey::_2; + case KEY_3: + return InputKey::_3; + case KEY_4: + return InputKey::_4; + case KEY_5: + return InputKey::_5; + case KEY_6: + return InputKey::_6; + case KEY_7: + return InputKey::_7; + case KEY_8: + return InputKey::_8; + case KEY_9: + return InputKey::_9; + case KEY_0: + return InputKey::_0; + case KEY_KP1: + return InputKey::NumPad1; + case KEY_KP2: + return InputKey::NumPad2; + case KEY_KP3: + return InputKey::NumPad3; + case KEY_KP4: + return InputKey::NumPad4; + case KEY_KP5: + return InputKey::NumPad5; + case KEY_KP6: + return InputKey::NumPad6; + case KEY_KP7: + return InputKey::NumPad7; + case KEY_KP8: + return InputKey::NumPad8; + case KEY_KP9: + return InputKey::NumPad9; + case KEY_KP0: + return InputKey::NumPad0; + case KEY_F1: + return InputKey::F1; + case KEY_F2: + return InputKey::F2; + case KEY_F3: + return InputKey::F3; + case KEY_F4: + return InputKey::F4; + case KEY_F5: + return InputKey::F5; + case KEY_F6: + return InputKey::F6; + case KEY_F7: + return InputKey::F7; + case KEY_F8: + return InputKey::F8; + case KEY_F9: + return InputKey::F9; + case KEY_F10: + return InputKey::F10; + case KEY_F11: + return InputKey::F11; + case KEY_F12: + return InputKey::F12; + case KEY_F13: + return InputKey::F13; + case KEY_F14: + return InputKey::F14; + case KEY_F15: + return InputKey::F15; + case KEY_F16: + return InputKey::F16; + case KEY_F17: + return InputKey::F17; + case KEY_F18: + return InputKey::F18; + case KEY_F19: + return InputKey::F19; + case KEY_F20: + return InputKey::F20; + case KEY_F21: + return InputKey::F21; + case KEY_F22: + return InputKey::F22; + case KEY_F23: + return InputKey::F23; + case KEY_F24: + return InputKey::F24; + case KEY_MINUS: + case KEY_KPMINUS: + return InputKey::Minus; + case KEY_EQUAL: + return InputKey::Equals; + case KEY_BACKSPACE: + return InputKey::Backspace; + case KEY_BACKSLASH: + return InputKey::Backslash; + case KEY_TAB: + return InputKey::Tab; + case KEY_LEFTBRACE: + return InputKey::LeftBracket; + case KEY_RIGHTBRACE: + return InputKey::RightBracket; + case KEY_LEFTCTRL: + return InputKey::LControl; + case KEY_RIGHTCTRL: + return InputKey::RControl; + case KEY_LEFTALT: + case KEY_RIGHTALT: + return InputKey::Alt; + case KEY_DELETE: + return InputKey::Delete; + case KEY_SEMICOLON: + return InputKey::Semicolon; + case KEY_COMMA: + return InputKey::Comma; + case KEY_DOT: + return InputKey::Period; + case KEY_NUMLOCK: + return InputKey::NumLock; + case KEY_CAPSLOCK: + return InputKey::CapsLock; + case KEY_SCROLLDOWN: + return InputKey::ScrollLock; + case KEY_LEFTSHIFT: + return InputKey::LShift; + case KEY_RIGHTSHIFT: + return InputKey::RShift; + case KEY_GRAVE: + return InputKey::Tilde; + case KEY_APOSTROPHE: + return InputKey::SingleQuote; + case KEY_SPACE: + return InputKey::Space; + case KEY_ENTER: + case KEY_KPENTER: + return InputKey::Enter; + + case KEY_UP: + return InputKey::Up; + case KEY_DOWN: + return InputKey::Down; + case KEY_LEFT: + return InputKey::Left; + case KEY_RIGHT: + return InputKey::Right; + + case KEY_A: + return InputKey::A; + case KEY_B: + return InputKey::B; + case KEY_C: + return InputKey::C; + case KEY_D: + return InputKey::D; + case KEY_E: + return InputKey::E; + case KEY_F: + return InputKey::F; + case KEY_G: + return InputKey::G; + case KEY_H: + return InputKey::H; + case KEY_I: + return InputKey::I; + case KEY_J: + return InputKey::J; + case KEY_K: + return InputKey::K; + case KEY_L: + return InputKey::L; + case KEY_M: + return InputKey::M; + case KEY_N: + return InputKey::N; + case KEY_O: + return InputKey::O; + case KEY_P: + return InputKey::P; + case KEY_Q: + return InputKey::Q; + case KEY_R: + return InputKey::R; + case KEY_S: + return InputKey::S; + case KEY_T: + return InputKey::T; + case KEY_U: + return InputKey::U; + case KEY_V: + return InputKey::V; + case KEY_W: + return InputKey::W; + case KEY_X: + return InputKey::X; + case KEY_Y: + return InputKey::Y; + case KEY_Z: + return InputKey::Z; + + // Mouse + case BTN_LEFT: + return InputKey::LeftMouse; + case BTN_RIGHT: + return InputKey::RightMouse; + case BTN_MIDDLE: + return InputKey::MiddleMouse; + default: + return InputKey::None; + } +} + +std::string WaylandDisplayBackend::GetWaylandCursorName(StandardCursor cursor) +{ + // Checked out Adwaita and Breeze cursors for the names. + // Other cursor themes should adhere to the names these two have. + switch (cursor) + { + case StandardCursor::arrow: + return "default"; + case StandardCursor::appstarting: + return "progress"; + case StandardCursor::cross: + return "crosshair"; + case StandardCursor::hand: + return "pointer"; + case StandardCursor::ibeam: + return "text"; + case StandardCursor::no: + return "not-allowed"; + case StandardCursor::size_all: + return "fleur"; + case StandardCursor::size_nesw: + return "nesw-resize"; + case StandardCursor::size_ns: + return "ns-resize"; + case StandardCursor::size_nwse: + return "nwse-resize"; + case StandardCursor::size_we: + return "ew-resize"; + case StandardCursor::uparrow: + // Breeze actually has an up-arrow cursor, but Adwaita doesn't, so the default cursor it is + return "default"; + case StandardCursor::wait: + return "wait"; + default: + return "default"; + } +} + diff --git a/src/window/wayland/wayland_display_backend.h b/src/window/wayland/wayland_display_backend.h new file mode 100644 index 000000000..26e3e7f64 --- /dev/null +++ b/src/window/wayland/wayland_display_backend.h @@ -0,0 +1,168 @@ +#pragma once + +#include "window/window.h" +#include "window/ztimer/ztimer.h" + +#include +#include +#include +#include +#include +#include "wl_fractional_scaling_protocol.hpp" +#include +#include +#include +#include + +static short poll_single(int fd, short events, int timeout) { + pollfd pfd { .fd = fd, .events = events, .revents = 0 }; + if (0 > poll(&pfd, 1, timeout)) { + throw std::runtime_error("poll() failed"); + } + + return pfd.revents; +} + +enum pointer_event_mask { + POINTER_EVENT_ENTER = 1 << 0, + POINTER_EVENT_LEAVE = 1 << 1, + POINTER_EVENT_MOTION = 1 << 2, + POINTER_EVENT_BUTTON = 1 << 3, + POINTER_EVENT_AXIS = 1 << 4, + POINTER_EVENT_AXIS_SOURCE = 1 << 5, + POINTER_EVENT_AXIS_STOP = 1 << 6, + POINTER_EVENT_AXIS_DISCRETE = 1 << 7, + POINTER_EVENT_AXIS_120 = 1 << 8, + POINTER_EVENT_RELATIVE_MOTION = 1 << 9, +}; + +struct WaylandPointerEvent +{ + uint32_t event_mask; + double surfaceX, surfaceY; + double dx, dy; + uint32_t button; + wayland::pointer_button_state state; + uint32_t time; + uint32_t serial; + struct { + bool valid; + double value; + int32_t discrete; + int32_t value120; + } axes[2]; + wayland::pointer_axis_source axis_source; +}; + +class WaylandDisplayWindow; + +class WaylandDisplayBackend : public DisplayBackend +{ +public: + WaylandDisplayBackend(); + ~WaylandDisplayBackend(); + + std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) override; + void ProcessEvents() override; + void RunLoop() override; + void ExitLoop() override; + + void* StartTimer(int timeoutMilliseconds, std::function onTimer) override; + void StopTimer(void* timerID) override; + + Size GetScreenSize() override; + + bool IsWayland() override { return true; } + + void OnWindowCreated(WaylandDisplayWindow* window); + void OnWindowDestroyed(WaylandDisplayWindow* window); + + void SetCursor(StandardCursor cursor); + void ShowCursor(bool enable); + bool GetKeyState(InputKey key); + +#ifdef USE_DBUS + std::unique_ptr CreateOpenFileDialog(DisplayWindow* owner) override; + std::unique_ptr CreateSaveFileDialog(DisplayWindow* owner) override; + std::unique_ptr CreateOpenFolderDialog(DisplayWindow* owner) override; +#endif + + bool exitRunLoop = false; + Size s_ScreenSize = Size(0, 0); + wayland::display_t s_waylandDisplay = wayland::display_t(); + wayland::registry_t s_waylandRegistry; + std::vector s_Windows; + WaylandDisplayWindow* m_FocusWindow = nullptr; + WaylandDisplayWindow* m_MouseFocusWindow = nullptr; // Mouse focus should be tracked separately. + + wayland::compositor_t m_waylandCompositor; + wayland::shm_t m_waylandSHM; + wayland::seat_t m_waylandSeat; + wayland::output_t m_waylandOutput; + wayland::data_device_manager_t m_DataDeviceManager; + wayland::xdg_wm_base_t m_XDGWMBase; + wayland::zwp_pointer_constraints_v1_t m_PointerConstraints; + wayland::xdg_activation_v1_t m_XDGActivation; + wayland::zxdg_decoration_manager_v1_t m_XDGDecorationManager; + wayland::fractional_scale_manager_v1_t m_FractionalScaleManager; + wayland::zxdg_output_manager_v1_t m_XDGOutputManager; + wayland::zxdg_output_v1_t m_XDGOutput; + wayland::zxdg_exporter_v2_t m_XDGExporter; + + wayland::keyboard_t m_waylandKeyboard; + wayland::pointer_t m_waylandPointer; + + wayland::zwp_relative_pointer_manager_v1_t m_RelativePointerManager; + wayland::zwp_relative_pointer_v1_t m_RelativePointer; + + wayland::cursor_image_t m_cursorImage; + wayland::surface_t m_cursorSurface; + wayland::buffer_t m_cursorBuffer; + + std::map inputKeyStates; // True when the key is pressed, false when isn't + + bool IsMouseLocked() { return hasMouseLock; } + void SetMouseLocked(bool val) { hasMouseLock = val; } + +private: + void CheckNeedsUpdate(); + void UpdateTimers(); + void ConnectDeviceEvents(); + void OnKeyboardKeyEvent(xkb_keysym_t xkbKeySym, wayland::keyboard_key_state state); + void OnKeyboardCharEvent(const char* ch, wayland::keyboard_key_state state); + void OnKeyboardDelayEnd(); + void OnKeyboardRepeat(); + void OnMouseEnterEvent(uint32_t serial); + void OnMouseLeaveEvent(); + void OnMousePressEvent(InputKey button); + void OnMouseReleaseEvent(InputKey button); + void OnMouseMoveEvent(Point surfacePos); + void OnMouseMoveRawEvent(int surfaceX, int surfaceY); + void OnMouseWheelEvent(InputKey button); + + InputKey XKBKeySymToInputKey(xkb_keysym_t keySym); + InputKey LinuxInputEventCodeToInputKey(uint32_t inputCode); + + std::string GetWaylandCursorName(StandardCursor cursor); + + bool hasKeyboard = false; + bool hasPointer = false; + bool hasMouseLock = false; + + ZTimer::TimePoint m_previousTime; + ZTimer::TimePoint m_currentTime; + + ZTimer m_keyboardDelayTimer; + ZTimer m_keyboardRepeatTimer; + + InputKey previousKey = {}; + std::string previousChars; + + uint32_t m_KeyboardSerial = 0; + + xkb_context* m_KeymapContext = nullptr; + xkb_keymap* m_Keymap = nullptr; + xkb_state* m_KeyboardState = nullptr; + + WaylandPointerEvent currentPointerEvent = {0}; +}; diff --git a/src/window/wayland/wayland_display_window.cpp b/src/window/wayland/wayland_display_window.cpp new file mode 100644 index 000000000..f41b3317e --- /dev/null +++ b/src/window/wayland/wayland_display_window.cpp @@ -0,0 +1,472 @@ +#include "wayland_display_window.h" + +#include +#include + +WaylandDisplayWindow::WaylandDisplayWindow(WaylandDisplayBackend* backend, DisplayWindowHost* windowHost, bool popupWindow, WaylandDisplayWindow* owner, RenderAPI renderAPI) + : backend(backend), m_owner(owner), windowHost(windowHost), m_PopupWindow(popupWindow), m_renderAPI(renderAPI) +{ + m_AppSurface = backend->m_waylandCompositor.create_surface(); + + m_NativeHandle.display = backend->s_waylandDisplay; + m_NativeHandle.surface = m_AppSurface; + + if (backend->m_FractionalScaleManager) + { + m_FractionalScale = backend->m_FractionalScaleManager.get_fractional_scale(m_AppSurface); + + m_FractionalScale.on_preferred_scale() = [&](uint32_t scale_numerator) { + // parameter is the numerator of a fraction with the denominator of 120 + m_ScaleFactor = scale_numerator / 120.0; + + m_NeedsUpdate = true; + windowHost->OnWindowDpiScaleChanged(); + }; + } + + m_XDGSurface = backend->m_XDGWMBase.get_xdg_surface(m_AppSurface); + m_XDGSurface.on_configure() = [&] (uint32_t serial) { + m_XDGSurface.ack_configure(serial); + }; + + if (popupWindow) + { + InitializePopup(); + } + else + { + InitializeToplevel(); + } + + backend->OnWindowCreated(this); + + this->DrawSurface(); +} + +WaylandDisplayWindow::~WaylandDisplayWindow() +{ + backend->OnWindowDestroyed(this); +} + +void WaylandDisplayWindow::InitializeToplevel() +{ + m_XDGToplevel = m_XDGSurface.get_toplevel(); + m_XDGToplevel.set_title("ZWidget Window"); + + if (m_owner) + m_XDGToplevel.set_parent(m_owner->m_XDGToplevel); + + if (backend->m_XDGDecorationManager) + { + // Force server side decorations if possible + m_XDGToplevelDecoration = backend->m_XDGDecorationManager.get_toplevel_decoration(m_XDGToplevel); + m_XDGToplevelDecoration.set_mode(wayland::zxdg_toplevel_decoration_v1_mode::server_side); + } + + m_AppSurface.commit(); + + backend->s_waylandDisplay.roundtrip(); + + // These have to be added after the roundtrip + m_XDGToplevel.on_configure() = [&] (int32_t width, int32_t height, wayland::array_t states) { + OnXDGToplevelConfigureEvent(width, height); + }; + + m_XDGToplevel.on_close() = [&] () { + OnExitEvent(); + }; + + m_XDGToplevel.on_configure_bounds() = [this] (int32_t width, int32_t height) + { + + }; + + m_XDGExported = backend->m_XDGExporter.export_toplevel(m_AppSurface); + + m_XDGExported.on_handle() = [&] (std::string handleStr) { + OnExportHandleEvent(handleStr); + }; +} + +void WaylandDisplayWindow::InitializePopup() +{ + if (!m_owner) + throw std::runtime_error("Popup window must have an owner!"); + + wayland::xdg_positioner_t popupPositioner = backend->m_XDGWMBase.create_positioner(); + + popupPositioner.set_anchor(wayland::xdg_positioner_anchor::bottom); + popupPositioner.set_anchor_rect(0, 0, 1, 30); + popupPositioner.set_size(1, 1); + + m_XDGPopup = m_XDGSurface.get_popup(m_owner->m_XDGSurface, popupPositioner); + + m_XDGPopup.on_configure() = [&] (int32_t x, int32_t y, int32_t width, int32_t height) { + SetClientFrame(Rect::xywh(x, y, width, height)); + }; + + //m_XDGPopup.on_repositioned() + + m_XDGPopup.on_popup_done() = [&] () { + OnExitEvent(); + }; + + m_AppSurface.commit(); + + backend->s_waylandDisplay.roundtrip(); +} + + +void WaylandDisplayWindow::SetWindowTitle(const std::string& text) +{ + if (m_XDGToplevel) + m_XDGToplevel.set_title(text); +} + +void WaylandDisplayWindow::SetWindowFrame(const Rect& box) +{ + // Resizing will be shown on the next commit + CreateBuffers(box.width, box.height); + windowHost->OnWindowGeometryChanged(); + m_NeedsUpdate = true; + m_AppSurface.commit(); +} + +void WaylandDisplayWindow::SetClientFrame(const Rect& box) +{ + SetWindowFrame(box); +} + +void WaylandDisplayWindow::Show() +{ + m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0); + m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height); + m_AppSurface.commit(); +} + +void WaylandDisplayWindow::ShowFullscreen() +{ + if (m_XDGToplevel) + { + m_XDGToplevel.set_fullscreen(backend->m_waylandOutput); + isFullscreen = true; + } +} + +void WaylandDisplayWindow::ShowMaximized() +{ + if (m_XDGToplevel) + m_XDGToplevel.set_maximized(); +} + +void WaylandDisplayWindow::ShowMinimized() +{ + if (m_XDGToplevel) + m_XDGToplevel.set_minimized(); +} + +void WaylandDisplayWindow::ShowNormal() +{ + if (m_XDGToplevel) + m_XDGToplevel.unset_fullscreen(); +} + +bool WaylandDisplayWindow::IsWindowFullscreen() +{ + return isFullscreen; +} + +void WaylandDisplayWindow::Hide() +{ + // Apparently this is how hiding a window works + // By attaching a null buffer to the surface + // See: https://lists.freedesktop.org/archives/wayland-devel/2017-November/035963.html + m_AppSurface.attach(nullptr, 0, 0); + m_AppSurface.commit(); +} + +void WaylandDisplayWindow::Activate() +{ + // To do: this needs to be in the backend instance if all windows share the activation token + wayland::xdg_activation_token_v1_t xdgActivationToken = backend->m_XDGActivation.get_activation_token(); + + std::string tokenString; + + xdgActivationToken.on_done() = [&tokenString] (std::string obtainedString) { + tokenString = obtainedString; + }; + + xdgActivationToken.set_surface(m_AppSurface); + xdgActivationToken.commit(); // This will set our token string + + backend->m_XDGActivation.activate(tokenString, m_AppSurface); + backend->m_FocusWindow = this; + backend->m_MouseFocusWindow = this; + windowHost->OnWindowActivated(); +} + +void WaylandDisplayWindow::ShowCursor(bool enable) +{ + backend->ShowCursor(enable); +} + +void WaylandDisplayWindow::LockCursor() +{ + m_LockedPointer = backend->m_PointerConstraints.lock_pointer(m_AppSurface, backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent); + backend->SetMouseLocked(true); + ShowCursor(false); +} + +void WaylandDisplayWindow::UnlockCursor() +{ + if (m_LockedPointer) + m_LockedPointer.proxy_release(); + backend->SetMouseLocked(false); + ShowCursor(true); +} + +void WaylandDisplayWindow::CaptureMouse() +{ + m_ConfinedPointer = backend->m_PointerConstraints.confine_pointer(GetWindowSurface(), backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent); + ShowCursor(false); +} + +void WaylandDisplayWindow::ReleaseMouseCapture() +{ + if (m_ConfinedPointer) + m_ConfinedPointer.proxy_release(); + ShowCursor(true); +} + +void WaylandDisplayWindow::Update() +{ + m_NeedsUpdate = true; +} + +bool WaylandDisplayWindow::GetKeyState(InputKey key) +{ + return backend->GetKeyState(key); +} + +void WaylandDisplayWindow::SetCursor(StandardCursor cursor) +{ + backend->SetCursor(cursor); +} + +Rect WaylandDisplayWindow::GetWindowFrame() const +{ + return Rect(m_WindowGlobalPos.x, m_WindowGlobalPos.y, m_WindowSize.width, m_WindowSize.height); +} + +Size WaylandDisplayWindow::GetClientSize() const +{ + return m_WindowSize / m_ScaleFactor; +} + +int WaylandDisplayWindow::GetPixelWidth() const +{ + return m_WindowSize.width; +} + +int WaylandDisplayWindow::GetPixelHeight() const +{ + return m_WindowSize.height; +} + +double WaylandDisplayWindow::GetDpiScale() const +{ + return m_ScaleFactor; +} + +void WaylandDisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels) +{ + // Make new buffers if the sizes don't match + if (width != m_WindowSize.width || height != m_WindowSize.height) + CreateBuffers(width, height); + + std::memcpy(shared_mem->get_mem(), (void*)pixels, width * height * 4); +} + +void WaylandDisplayWindow::SetBorderColor(uint32_t bgra8) +{ + +} + +void WaylandDisplayWindow::SetCaptionColor(uint32_t bgra8) +{ + +} + +void WaylandDisplayWindow::SetCaptionTextColor(uint32_t bgra8) +{ + +} + +std::string WaylandDisplayWindow::GetClipboardText() +{ + return m_ClipboardContents; +} + +void WaylandDisplayWindow::SetClipboardText(const std::string& text) +{ + m_ClipboardContents = text; +} + +Point WaylandDisplayWindow::MapFromGlobal(const Point& pos) const +{ + return (pos - m_WindowGlobalPos) / m_ScaleFactor; +} + +Point WaylandDisplayWindow::MapToGlobal(const Point& pos) const +{ + return (m_WindowGlobalPos + pos) / m_ScaleFactor; +} + +void WaylandDisplayWindow::OnXDGToplevelConfigureEvent(int32_t width, int32_t height) +{ + Rect rect = GetWindowFrame(); + rect.width = width / m_ScaleFactor; + rect.height = height / m_ScaleFactor; + SetWindowFrame(rect); + windowHost->OnWindowGeometryChanged(); +} + +void WaylandDisplayWindow::OnExportHandleEvent(std::string exportedHandle) +{ + m_windowID = exportedHandle; +} + +void WaylandDisplayWindow::OnExitEvent() +{ + windowHost->OnWindowClose(); +} + +void WaylandDisplayWindow::DrawSurface(uint32_t serial) +{ + m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0); + m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height); + + if (m_renderAPI == RenderAPI::Unspecified || m_renderAPI == RenderAPI::Bitmap) + { + m_FrameCallback = m_AppSurface.frame(); + + m_FrameCallback.on_done() = bind_mem_fn(&WaylandDisplayWindow::DrawSurface, this); + } + m_AppSurface.commit(); +} + +void WaylandDisplayWindow::CreateBuffers(int32_t width, int32_t height) +{ + if (width == 0 || height == 0) + return; + + if (shared_mem) + shared_mem.reset(); + + int scaled_width = width * m_ScaleFactor; + int scaled_height = height * m_ScaleFactor; + + shared_mem = std::make_shared(scaled_width * scaled_height * 4); + auto pool = backend->m_waylandSHM.create_pool(shared_mem->get_fd(), scaled_width * scaled_height * 4); + + m_AppSurfaceBuffer = pool.create_buffer(0, scaled_width, scaled_height, scaled_width * 4, wayland::shm_format::xrgb8888); + + m_WindowSize = Size(scaled_width, scaled_height); +} + +std::string WaylandDisplayWindow::GetWaylandWindowID() +{ + return m_windowID; +} + +// This is to avoid needing all the Vulkan headers and the volk binding library just for this: +#ifndef VK_VERSION_1_0 + +#define VKAPI_CALL +#define VKAPI_PTR VKAPI_CALL + +typedef uint32_t VkFlags; +typedef enum VkStructureType { VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000, VK_OBJECT_TYPE_MAX_ENUM = 0x7FFFFFFF } VkStructureType; +typedef enum VkResult { VK_SUCCESS = 0, VK_RESULT_MAX_ENUM = 0x7FFFFFFF } VkResult; +typedef struct VkAllocationCallbacks VkAllocationCallbacks; + +typedef void (VKAPI_PTR* PFN_vkVoidFunction)(void); +typedef PFN_vkVoidFunction(VKAPI_PTR* PFN_vkGetInstanceProcAddr)(VkInstance instance, const char* pName); + +#ifndef VK_KHR_wayland_surface + +typedef VkFlags VkWaylandSurfaceCreateFlagsKHR; +typedef struct VkWaylandSurfaceCreateInfoKHR +{ + VkStructureType sType; + const void* pNext; + VkWaylandSurfaceCreateFlagsKHR flags; + struct wl_display* display; + struct wl_surface* surface; +} VkWaylandSurfaceCreateInfoKHR; + +typedef VkResult(VKAPI_PTR* PFN_vkCreateWaylandSurfaceKHR)(VkInstance instance, const VkWaylandSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface); + +#endif +#endif + +class ZWidgetWaylandVulkanLoader +{ +public: + ZWidgetWaylandVulkanLoader() + { +#if defined(__APPLE__) + module = dlopen("libvulkan.dylib", RTLD_NOW | RTLD_LOCAL); + if (!module) + module = dlopen("libvulkan.1.dylib", RTLD_NOW | RTLD_LOCAL); + if (!module) + module = dlopen("libMoltenVK.dylib", RTLD_NOW | RTLD_LOCAL); +#else + module = dlopen("libvulkan.so.1", RTLD_NOW | RTLD_LOCAL); + if (!module) + module = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL); +#endif + + if (!module) + throw std::runtime_error("Could not load vulkan"); + + vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym(module, "vkGetInstanceProcAddr"); + if (!vkGetInstanceProcAddr) + { + dlclose(module); + throw std::runtime_error("vkGetInstanceProcAddr not found"); + } + } + + ~ZWidgetWaylandVulkanLoader() + { + dlclose(module); + } + + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr; + void* module = nullptr; +}; + +VkSurfaceKHR WaylandDisplayWindow::CreateVulkanSurface(VkInstance instance) +{ + static ZWidgetWaylandVulkanLoader loader; + + auto vkCreateWaylandSurfaceKHR = (PFN_vkCreateWaylandSurfaceKHR)loader.vkGetInstanceProcAddr(instance, "vkCreateWaylandSurfaceKHR"); + if (!vkCreateWaylandSurfaceKHR) + throw std::runtime_error("Could not create vulkan surface"); + + VkWaylandSurfaceCreateInfoKHR createInfo = { VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR }; + createInfo.display = m_NativeHandle.display; + createInfo.surface = m_NativeHandle.surface; + + VkSurfaceKHR surface = {}; + VkResult result = vkCreateWaylandSurfaceKHR(instance, &createInfo, nullptr, &surface); + if (result != VK_SUCCESS) + throw std::runtime_error("Could not create vulkan surface"); + return surface; +} + +std::vector WaylandDisplayWindow::GetVulkanInstanceExtensions() +{ + return { "VK_KHR_surface", "VK_KHR_wayland_surface" }; +} diff --git a/src/window/wayland/wayland_display_window.h b/src/window/wayland/wayland_display_window.h new file mode 100644 index 000000000..5553ece84 --- /dev/null +++ b/src/window/wayland/wayland_display_window.h @@ -0,0 +1,198 @@ +#pragma once + +#include "wayland_display_backend.h" +#include "window/ztimer/ztimer.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "zwidget/window/window.h" +#include "zwidget/window/waylandnativehandle.h" + +#include +#include +#include +#include + +template +std::function bind_mem_fn(R(T::* func)(Args...), T *t) +{ + return [func, t] (Args... args) + { + return (t->*func)(args...); + }; +} + +class SharedMemHelper +{ +public: + SharedMemHelper(size_t size) + : len(size) + { + std::stringstream ss; + std::random_device device; + std::default_random_engine engine(device()); + std::uniform_int_distribution distribution(0, std::numeric_limits::max()); + ss << distribution(engine); + name = ss.str(); + + fd = memfd_create(name.c_str(), 0); + if(fd < 0) + throw std::runtime_error("shm_open failed."); + + if(ftruncate(fd, size) < 0) + throw std::runtime_error("ftruncate failed."); + + mem = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if(mem == MAP_FAILED) // NOLINT + throw std::runtime_error("mmap failed with len " + std::to_string(len) + "."); + } + + ~SharedMemHelper() noexcept + { + if(fd) + { + munmap(mem, len); + close(fd); + shm_unlink(name.c_str()); + } + } + + int get_fd() const + { + return fd; + } + + void *get_mem() + { + return mem; + } + +private: + std::string name; + int fd = 0; + size_t len = 0; + void *mem = nullptr; +}; + +class WaylandDisplayWindow : public DisplayWindow +{ +public: + WaylandDisplayWindow(WaylandDisplayBackend* backend, DisplayWindowHost* windowHost, bool popupWindow, WaylandDisplayWindow* owner, RenderAPI renderAPI); + ~WaylandDisplayWindow(); + + void SetWindowTitle(const std::string& text) override; + void SetWindowFrame(const Rect& box) override; + void SetClientFrame(const Rect& box) override; + void Show() override; + void ShowFullscreen() override; + void ShowMaximized() override; + void ShowMinimized() override; + void ShowNormal() override; + void Hide() override; + void Activate() override; + void ShowCursor(bool enable) override; + void LockCursor() override; + void UnlockCursor() override; + void CaptureMouse() override; + void ReleaseMouseCapture() override; + void Update() override; + bool GetKeyState(InputKey key) override; + void SetCursor(StandardCursor cursor) override; + + Rect GetWindowFrame() const override; + Size GetClientSize() const override; + int GetPixelWidth() const override; + int GetPixelHeight() const override; + double GetDpiScale() const override; + + void PresentBitmap(int width, int height, const uint32_t* pixels) override; + + void SetBorderColor(uint32_t bgra8) override; + void SetCaptionColor(uint32_t bgra8) override; + void SetCaptionTextColor(uint32_t bgra8) override; + + std::string GetClipboardText() override; + void SetClipboardText(const std::string& text) override; + + Point MapFromGlobal(const Point& pos) const override; + Point MapToGlobal(const Point& pos) const override; + + void* GetNativeHandle() override { return (void*)&m_NativeHandle; } + + std::vector GetVulkanInstanceExtensions() override; + VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override; + + wayland::surface_t GetWindowSurface() { return m_AppSurface; } + + bool IsWindowFullscreen() override; + +private: + // Event handlers as otherwise linking DisplayWindowHost On...() functions with Wayland events directly crashes the app + // Alternatively to avoid crashes one can capture by value ([=]) instead of reference ([&]) + void OnXDGToplevelConfigureEvent(int32_t width, int32_t height); + void OnExportHandleEvent(std::string exportedHandle); + void OnExitEvent(); + + void DrawSurface(uint32_t serial = 0); + + void InitializeToplevel(); + void InitializePopup(); + + WaylandDisplayBackend* backend = nullptr; + WaylandDisplayWindow* m_owner = nullptr; + DisplayWindowHost* windowHost = nullptr; + bool m_PopupWindow = false; + + bool m_NeedsUpdate = true; + + Point m_WindowGlobalPos = Point(0, 0); + Size m_WindowSize = Size(0, 0); + double m_ScaleFactor = 1.0; + + Point m_SurfaceMousePos = Point(0, 0); + + WaylandNativeHandle m_NativeHandle; + RenderAPI m_renderAPI; + + wayland::data_device_t m_DataDevice; + wayland::data_source_t m_DataSource; + + wayland::zxdg_toplevel_decoration_v1_t m_XDGToplevelDecoration; + + wayland::fractional_scale_v1_t m_FractionalScale; + + wayland::surface_t m_AppSurface; + wayland::buffer_t m_AppSurfaceBuffer; + + wayland::xdg_surface_t m_XDGSurface; + wayland::xdg_toplevel_t m_XDGToplevel; + wayland::xdg_popup_t m_XDGPopup; + + wayland::zxdg_exported_v2_t m_XDGExported; + + wayland::zwp_locked_pointer_v1_t m_LockedPointer; + wayland::zwp_confined_pointer_v1_t m_ConfinedPointer; + + wayland::callback_t m_FrameCallback; + + std::string m_windowID; + std::string m_ClipboardContents; + + std::shared_ptr shared_mem; + + bool isFullscreen = false; + + // Helper functions + void CreateBuffers(int32_t width, int32_t height); + std::string GetWaylandWindowID(); + + friend WaylandDisplayBackend; +}; diff --git a/src/window/wayland/wl_fractional_scaling_protocol.cpp b/src/window/wayland/wl_fractional_scaling_protocol.cpp new file mode 100644 index 000000000..536ae765d --- /dev/null +++ b/src/window/wayland/wl_fractional_scaling_protocol.cpp @@ -0,0 +1,205 @@ +#include "wl_fractional_scaling_protocol.hpp" + +using namespace wayland; +using namespace wayland::detail; + +const wl_interface* fractional_scale_manager_v1_interface_destroy_request[0] = { +}; + +const wl_interface* fractional_scale_manager_v1_interface_get_fractional_scale_request[2] = { + &fractional_scale_v1_interface, + &surface_interface, +}; + +const wl_message fractional_scale_manager_v1_interface_requests[2] = { + { + "destroy", + "", + fractional_scale_manager_v1_interface_destroy_request, + }, + { + "get_fractional_scale", + "no", + fractional_scale_manager_v1_interface_get_fractional_scale_request, + }, +}; + +const wl_message fractional_scale_manager_v1_interface_events[0] = { +}; + +const wl_interface wayland::detail::fractional_scale_manager_v1_interface = + { + "wp_fractional_scale_manager_v1", + 1, + 2, + fractional_scale_manager_v1_interface_requests, + 0, + fractional_scale_manager_v1_interface_events, + }; + +const wl_interface* fractional_scale_v1_interface_destroy_request[0] = { +}; + +const wl_interface* fractional_scale_v1_interface_preferred_scale_event[1] = { + nullptr, +}; + +const wl_message fractional_scale_v1_interface_requests[1] = { + { + "destroy", + "", + fractional_scale_v1_interface_destroy_request, + }, +}; + +const wl_message fractional_scale_v1_interface_events[1] = { + { + "preferred_scale", + "u", + fractional_scale_v1_interface_preferred_scale_event, + }, +}; + +const wl_interface wayland::detail::fractional_scale_v1_interface = + { + "wp_fractional_scale_v1", + 1, + 1, + fractional_scale_v1_interface_requests, + 1, + fractional_scale_v1_interface_events, + }; + +fractional_scale_manager_v1_t::fractional_scale_manager_v1_t(const proxy_t &p) + : proxy_t(p) +{ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&fractional_scale_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return fractional_scale_manager_v1_t(p); }); +} + +fractional_scale_manager_v1_t::fractional_scale_manager_v1_t() +{ + set_interface(&fractional_scale_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return fractional_scale_manager_v1_t(p); }); +} + +fractional_scale_manager_v1_t::fractional_scale_manager_v1_t(wp_fractional_scale_manager_v1 *p, wrapper_type t) + : proxy_t(reinterpret_cast (p), t){ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&fractional_scale_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return fractional_scale_manager_v1_t(p); }); +} + +fractional_scale_manager_v1_t::fractional_scale_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/) + : proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){ + set_interface(&fractional_scale_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return fractional_scale_manager_v1_t(p); }); +} + +fractional_scale_manager_v1_t fractional_scale_manager_v1_t::proxy_create_wrapper() +{ + return {*this, construct_proxy_wrapper_tag()}; +} + +const std::string fractional_scale_manager_v1_t::interface_name = "wp_fractional_scale_manager_v1"; + +fractional_scale_manager_v1_t::operator wp_fractional_scale_manager_v1*() const +{ + return reinterpret_cast (c_ptr()); +} + +fractional_scale_v1_t fractional_scale_manager_v1_t::get_fractional_scale(surface_t const& surface) +{ + proxy_t p = marshal_constructor(1U, &fractional_scale_v1_interface, nullptr, surface.proxy_has_object() ? reinterpret_cast(surface.c_ptr()) : nullptr); + return fractional_scale_v1_t(p); +} + + +int fractional_scale_manager_v1_t::dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e) +{ + return 0; +} + + +fractional_scale_v1_t::fractional_scale_v1_t(const proxy_t &p) + : proxy_t(p) +{ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&fractional_scale_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return fractional_scale_v1_t(p); }); +} + +fractional_scale_v1_t::fractional_scale_v1_t() +{ + set_interface(&fractional_scale_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return fractional_scale_v1_t(p); }); +} + +fractional_scale_v1_t::fractional_scale_v1_t(wp_fractional_scale_v1 *p, wrapper_type t) + : proxy_t(reinterpret_cast (p), t){ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&fractional_scale_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return fractional_scale_v1_t(p); }); +} + +fractional_scale_v1_t::fractional_scale_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/) + : proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){ + set_interface(&fractional_scale_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return fractional_scale_v1_t(p); }); +} + +fractional_scale_v1_t fractional_scale_v1_t::proxy_create_wrapper() +{ + return {*this, construct_proxy_wrapper_tag()}; +} + +const std::string fractional_scale_v1_t::interface_name = "wp_fractional_scale_v1"; + +fractional_scale_v1_t::operator wp_fractional_scale_v1*() const +{ + return reinterpret_cast (c_ptr()); +} + +std::function &fractional_scale_v1_t::on_preferred_scale() +{ + return std::static_pointer_cast(get_events())->preferred_scale; +} + +int fractional_scale_v1_t::dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e) +{ + std::shared_ptr events = std::static_pointer_cast(e); + switch(opcode) + { + case 0: + if(events->preferred_scale) events->preferred_scale(args[0].get()); + break; + } + return 0; +} + + diff --git a/src/window/wayland/wl_fractional_scaling_protocol.hpp b/src/window/wayland/wl_fractional_scaling_protocol.hpp new file mode 100644 index 000000000..859530671 --- /dev/null +++ b/src/window/wayland/wl_fractional_scaling_protocol.hpp @@ -0,0 +1,126 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +struct wp_fractional_scale_manager_v1; +struct wp_fractional_scale_v1; + +namespace wayland +{ +class fractional_scale_manager_v1_t; +enum class fractional_scale_manager_v1_error : uint32_t; +class fractional_scale_v1_t; + +namespace detail +{ + extern const wl_interface fractional_scale_manager_v1_interface; + extern const wl_interface fractional_scale_v1_interface; +} + +/** \brief fractional surface scale information + + A global interface for requesting surfaces to use fractional scales. + +*/ +class fractional_scale_manager_v1_t : public proxy_t +{ +private: + struct events_t : public detail::events_base_t + { + }; + + static int dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e); + + fractional_scale_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/); + +public: + fractional_scale_manager_v1_t(); + explicit fractional_scale_manager_v1_t(const proxy_t &proxy); + fractional_scale_manager_v1_t(wp_fractional_scale_manager_v1 *p, wrapper_type t = wrapper_type::standard); + + fractional_scale_manager_v1_t proxy_create_wrapper(); + + static const std::string interface_name; + + operator wp_fractional_scale_manager_v1*() const; + + /** \brief extend surface interface for scale information + \return the new surface scale info interface id + \param surface the surface + + Create an add-on object for the the wl_surface to let the compositor + request fractional scales. If the given wl_surface already has a + wp_fractional_scale_v1 object associated, the fractional_scale_exists + protocol error is raised. + + */ + fractional_scale_v1_t get_fractional_scale(surface_t const& surface); + + /** \brief Minimum protocol version required for the \ref get_fractional_scale function + */ + static constexpr std::uint32_t get_fractional_scale_since_version = 1; + +}; + +/** \brief + + */ +enum class fractional_scale_manager_v1_error : uint32_t + { + /** \brief the surface already has a fractional_scale object associated */ + fractional_scale_exists = 0 +}; + + +/** \brief fractional scale interface to a wl_surface + + An additional interface to a wl_surface object which allows the compositor + to inform the client of the preferred scale. + +*/ +class fractional_scale_v1_t : public proxy_t +{ +private: + struct events_t : public detail::events_base_t + { + std::function preferred_scale; + }; + + static int dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e); + + fractional_scale_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/); + +public: + fractional_scale_v1_t(); + explicit fractional_scale_v1_t(const proxy_t &proxy); + fractional_scale_v1_t(wp_fractional_scale_v1 *p, wrapper_type t = wrapper_type::standard); + + fractional_scale_v1_t proxy_create_wrapper(); + + static const std::string interface_name; + + operator wp_fractional_scale_v1*() const; + + /** \brief notify of new preferred scale + \param scale the new preferred scale + + Notification of a new preferred scale for this surface that the + compositor suggests that the client should use. + + The sent scale is the numerator of a fraction with a denominator of 120. + + */ + std::function &on_preferred_scale(); + +}; + + + +} diff --git a/src/window/win32/win32_display_backend.cpp b/src/window/win32/win32_display_backend.cpp new file mode 100644 index 000000000..0934da042 --- /dev/null +++ b/src/window/win32/win32_display_backend.cpp @@ -0,0 +1,56 @@ + +#include "win32_display_backend.h" +#include "win32_display_window.h" +#include "win32_open_file_dialog.h" +#include "win32_save_file_dialog.h" +#include "win32_open_folder_dialog.h" + +std::unique_ptr Win32DisplayBackend::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) +{ + return std::make_unique(windowHost, popupWindow, static_cast(owner), renderAPI); +} + +void Win32DisplayBackend::ProcessEvents() +{ + Win32DisplayWindow::ProcessEvents(); +} + +void Win32DisplayBackend::RunLoop() +{ + Win32DisplayWindow::RunLoop(); +} + +void Win32DisplayBackend::ExitLoop() +{ + Win32DisplayWindow::ExitLoop(); +} + +Size Win32DisplayBackend::GetScreenSize() +{ + return Win32DisplayWindow::GetScreenSize(); +} + +void* Win32DisplayBackend::StartTimer(int timeoutMilliseconds, std::function onTimer) +{ + return Win32DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer)); +} + +void Win32DisplayBackend::StopTimer(void* timerID) +{ + Win32DisplayWindow::StopTimer(timerID); +} + +std::unique_ptr Win32DisplayBackend::CreateOpenFileDialog(DisplayWindow* owner) +{ + return std::make_unique(static_cast(owner)); +} + +std::unique_ptr Win32DisplayBackend::CreateSaveFileDialog(DisplayWindow* owner) +{ + return std::make_unique(static_cast(owner)); +} + +std::unique_ptr Win32DisplayBackend::CreateOpenFolderDialog(DisplayWindow* owner) +{ + return std::make_unique(static_cast(owner)); +} diff --git a/src/window/win32/win32_display_backend.h b/src/window/win32/win32_display_backend.h new file mode 100644 index 000000000..f641249c2 --- /dev/null +++ b/src/window/win32/win32_display_backend.h @@ -0,0 +1,23 @@ +#pragma once + +#include "window/window.h" + +class Win32DisplayBackend : public DisplayBackend +{ +public: + std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) override; + void ProcessEvents() override; + void RunLoop() override; + void ExitLoop() override; + + void* StartTimer(int timeoutMilliseconds, std::function onTimer) override; + void StopTimer(void* timerID) override; + + Size GetScreenSize() override; + + std::unique_ptr CreateOpenFileDialog(DisplayWindow* owner) override; + std::unique_ptr CreateSaveFileDialog(DisplayWindow* owner) override; + std::unique_ptr CreateOpenFolderDialog(DisplayWindow* owner) override; + + bool IsWin32() override { return true; } +}; diff --git a/src/window/win32/win32_display_window.cpp b/src/window/win32/win32_display_window.cpp new file mode 100644 index 000000000..f45da1a14 --- /dev/null +++ b/src/window/win32/win32_display_window.cpp @@ -0,0 +1,788 @@ + +#include "win32_display_window.h" +#include +#include +#include +#include +#include + +#pragma comment(lib, "dwmapi.lib") + +#ifndef HID_USAGE_PAGE_GENERIC +#define HID_USAGE_PAGE_GENERIC ((USHORT) 0x01) +#endif + +#ifndef HID_USAGE_GENERIC_MOUSE +#define HID_USAGE_GENERIC_MOUSE ((USHORT) 0x02) +#endif + +#ifndef HID_USAGE_GENERIC_JOYSTICK +#define HID_USAGE_GENERIC_JOYSTICK ((USHORT) 0x04) +#endif + +#ifndef HID_USAGE_GENERIC_GAMEPAD +#define HID_USAGE_GENERIC_GAMEPAD ((USHORT) 0x05) +#endif + +#ifndef RIDEV_INPUTSINK +#define RIDEV_INPUTSINK (0x100) +#endif + +Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner, RenderAPI renderAPI) : WindowHost(windowHost), PopupWindow(popupWindow) +{ + Windows.push_front(this); + WindowsIterator = Windows.begin(); + + WNDCLASSEX classdesc = {}; + classdesc.cbSize = sizeof(WNDCLASSEX); + classdesc.hInstance = GetModuleHandle(0); + classdesc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS; + classdesc.lpszClassName = L"ZWidgetWindow"; + classdesc.lpfnWndProc = &Win32DisplayWindow::WndProc; + RegisterClassEx(&classdesc); + + // Microsoft logic at its finest: + // WS_EX_DLGMODALFRAME hides the sysmenu icon + // WS_CAPTION shows the caption (yay! actually a flag that does what it says it does!) + // WS_SYSMENU shows the min/max/close buttons + // WS_THICKFRAME makes the window resizable + + DWORD style = 0, exstyle = 0; + if (popupWindow) + { + exstyle = WS_EX_NOACTIVATE; + style = WS_POPUP; + } + else + { + exstyle = WS_EX_APPWINDOW | WS_EX_DLGMODALFRAME; + style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX; + } + CreateWindowEx(exstyle, L"ZWidgetWindow", L"", style, 0, 0, 100, 100, owner ? owner->WindowHandle.hwnd : 0, 0, GetModuleHandle(0), this); +} + +Win32DisplayWindow::~Win32DisplayWindow() +{ + if (WindowHandle.hwnd) + { + DestroyWindow(WindowHandle.hwnd); + WindowHandle.hwnd = 0; + } + + Windows.erase(WindowsIterator); +} + +void Win32DisplayWindow::SetWindowTitle(const std::string& text) +{ + SetWindowText(WindowHandle.hwnd, to_utf16(text).c_str()); +} + +void Win32DisplayWindow::SetBorderColor(uint32_t bgra8) +{ + bgra8 = bgra8 & 0x00ffffff; + DwmSetWindowAttribute(WindowHandle.hwnd, 34/*DWMWA_BORDER_COLOR*/, &bgra8, sizeof(uint32_t)); +} + +void Win32DisplayWindow::SetCaptionColor(uint32_t bgra8) +{ + bgra8 = bgra8 & 0x00ffffff; + DwmSetWindowAttribute(WindowHandle.hwnd, 35/*DWMWA_CAPTION_COLOR*/, &bgra8, sizeof(uint32_t)); +} + +void Win32DisplayWindow::SetCaptionTextColor(uint32_t bgra8) +{ + bgra8 = bgra8 & 0x00ffffff; + DwmSetWindowAttribute(WindowHandle.hwnd, 36/*DWMWA_TEXT_COLOR*/, &bgra8, sizeof(uint32_t)); +} + +void Win32DisplayWindow::SetWindowFrame(const Rect& box) +{ + double dpiscale = GetDpiScale(); + SetWindowPos(WindowHandle.hwnd, nullptr, (int)std::round(box.x * dpiscale), (int)std::round(box.y * dpiscale), (int)std::round(box.width * dpiscale), (int)std::round(box.height * dpiscale), SWP_NOACTIVATE | SWP_NOZORDER); +} + +void Win32DisplayWindow::SetClientFrame(const Rect& box) +{ + double dpiscale = GetDpiScale(); + + RECT rect = {}; + rect.left = (int)std::round(box.x * dpiscale); + rect.top = (int)std::round(box.y * dpiscale); + rect.right = rect.left + (int)std::round(box.width * dpiscale); + rect.bottom = rect.top + (int)std::round(box.height * dpiscale); + + DWORD style = (DWORD)GetWindowLongPtr(WindowHandle.hwnd, GWL_STYLE); + DWORD exstyle = (DWORD)GetWindowLongPtr(WindowHandle.hwnd, GWL_EXSTYLE); + AdjustWindowRectExForDpi(&rect, style, FALSE, exstyle, GetDpiForWindow(WindowHandle.hwnd)); + + SetWindowPos(WindowHandle.hwnd, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER); +} + +void Win32DisplayWindow::Show() +{ + ShowWindow(WindowHandle.hwnd, PopupWindow ? SW_SHOWNA : SW_SHOW); +} + +void Win32DisplayWindow::ShowFullscreen() +{ + HDC screenDC = GetDC(0); + int width = GetDeviceCaps(screenDC, HORZRES); + int height = GetDeviceCaps(screenDC, VERTRES); + ReleaseDC(0, screenDC); + DWORD dwStyle = GetWindowLong(WindowHandle.hwnd, GWL_STYLE); + SetWindowLongPtr(WindowHandle.hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW); + SetWindowLongPtr(WindowHandle.hwnd, GWL_STYLE, dwStyle & ~WS_OVERLAPPEDWINDOW); + SetWindowPos(WindowHandle.hwnd, HWND_TOP, 0, 0, width, height, SWP_FRAMECHANGED | SWP_SHOWWINDOW); + Fullscreen = true; +} + +void Win32DisplayWindow::ShowMaximized() +{ + ShowWindow(WindowHandle.hwnd, SW_SHOWMAXIMIZED); +} + +void Win32DisplayWindow::ShowMinimized() +{ + ShowWindow(WindowHandle.hwnd, SW_SHOWMINIMIZED); +} + +void Win32DisplayWindow::ShowNormal() +{ + if (Fullscreen) + { + SetWindowLongPtr(WindowHandle.hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW); + Fullscreen = false; + } + ShowWindow(WindowHandle.hwnd, SW_NORMAL); +} + +bool Win32DisplayWindow::IsWindowFullscreen() +{ + return Fullscreen; +} + +void Win32DisplayWindow::Hide() +{ + ShowWindow(WindowHandle.hwnd, SW_HIDE); +} + +void Win32DisplayWindow::Activate() +{ + if (!PopupWindow) + SetFocus(WindowHandle.hwnd); +} + +void Win32DisplayWindow::ShowCursor(bool enable) +{ +} + +void Win32DisplayWindow::LockCursor() +{ + if (!MouseLocked) + { + MouseLocked = true; + GetCursorPos(&MouseLockPos); + ::ShowCursor(FALSE); + + RAWINPUTDEVICE rid = {}; + rid.usUsagePage = HID_USAGE_PAGE_GENERIC; + rid.usUsage = HID_USAGE_GENERIC_MOUSE; + rid.dwFlags = RIDEV_INPUTSINK; + rid.hwndTarget = WindowHandle.hwnd; + RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)); + } +} + +void Win32DisplayWindow::UnlockCursor() +{ + if (MouseLocked) + { + RAWINPUTDEVICE rid = {}; + rid.usUsagePage = HID_USAGE_PAGE_GENERIC; + rid.usUsage = HID_USAGE_GENERIC_MOUSE; + rid.dwFlags = RIDEV_REMOVE; + rid.hwndTarget = 0; + RegisterRawInputDevices(&rid, 1, sizeof(rid)); + + MouseLocked = false; + SetCursorPos(MouseLockPos.x, MouseLockPos.y); + ::ShowCursor(TRUE); + } +} + +void Win32DisplayWindow::CaptureMouse() +{ + SetCapture(WindowHandle.hwnd); +} + +void Win32DisplayWindow::ReleaseMouseCapture() +{ + ReleaseCapture(); +} + +void Win32DisplayWindow::Update() +{ + InvalidateRect(WindowHandle.hwnd, nullptr, FALSE); +} + +bool Win32DisplayWindow::GetKeyState(InputKey key) +{ + return ::GetKeyState((int)key) & 0x8000; // High bit (0x8000) means key is down, Low bit (0x0001) means key is sticky on (like Caps Lock, Num Lock, etc.) +} + +void Win32DisplayWindow::SetCursor(StandardCursor cursor) +{ + if (cursor != CurrentCursor) + { + CurrentCursor = cursor; + UpdateCursor(); + } +} + +Rect Win32DisplayWindow::GetWindowFrame() const +{ + RECT box = {}; + GetWindowRect(WindowHandle.hwnd, &box); + double dpiscale = GetDpiScale(); + return Rect(box.left / dpiscale, box.top / dpiscale, (box.right - box.left) / dpiscale, (box.bottom - box.top) / dpiscale); +} + +Point Win32DisplayWindow::MapFromGlobal(const Point& pos) const +{ + double dpiscale = GetDpiScale(); + POINT point = {}; + point.x = (LONG)std::round(pos.x / dpiscale); + point.y = (LONG)std::round(pos.y / dpiscale); + ScreenToClient(WindowHandle.hwnd, &point); + return Point(point.x * dpiscale, point.y * dpiscale); +} + +Point Win32DisplayWindow::MapToGlobal(const Point& pos) const +{ + double dpiscale = GetDpiScale(); + POINT point = {}; + point.x = (LONG)std::round(pos.x * dpiscale); + point.y = (LONG)std::round(pos.y * dpiscale); + ClientToScreen(WindowHandle.hwnd, &point); + return Point(point.x / dpiscale, point.y / dpiscale); +} + +Size Win32DisplayWindow::GetClientSize() const +{ + RECT box = {}; + GetClientRect(WindowHandle.hwnd, &box); + double dpiscale = GetDpiScale(); + return Size(box.right / dpiscale, box.bottom / dpiscale); +} + +int Win32DisplayWindow::GetPixelWidth() const +{ + RECT box = {}; + GetClientRect(WindowHandle.hwnd, &box); + return box.right; +} + +int Win32DisplayWindow::GetPixelHeight() const +{ + RECT box = {}; + GetClientRect(WindowHandle.hwnd, &box); + return box.bottom; +} + +double Win32DisplayWindow::GetDpiScale() const +{ + return GetDpiForWindow(WindowHandle.hwnd) / 96.0; +} + +std::string Win32DisplayWindow::GetClipboardText() +{ + BOOL result = OpenClipboard(WindowHandle.hwnd); + if (result == FALSE) + throw std::runtime_error("Unable to open clipboard"); + + HANDLE handle = GetClipboardData(CF_UNICODETEXT); + if (handle == 0) + { + CloseClipboard(); + return std::string(); + } + + std::wstring::value_type* data = (std::wstring::value_type*)GlobalLock(handle); + if (data == 0) + { + CloseClipboard(); + return std::string(); + } + std::string str = from_utf16(data); + GlobalUnlock(handle); + + CloseClipboard(); + return str; +} + +void Win32DisplayWindow::SetClipboardText(const std::string& text) +{ + std::wstring text16 = to_utf16(text); + + BOOL result = OpenClipboard(WindowHandle.hwnd); + if (result == FALSE) + throw std::runtime_error("Unable to open clipboard"); + + result = EmptyClipboard(); + if (result == FALSE) + { + CloseClipboard(); + throw std::runtime_error("Unable to empty clipboard"); + } + + unsigned int length = (unsigned int)((text16.length() + 1) * sizeof(std::wstring::value_type)); + HANDLE handle = GlobalAlloc(GMEM_MOVEABLE, length); + if (handle == 0) + { + CloseClipboard(); + throw std::runtime_error("Unable to allocate clipboard memory"); + } + + void* data = GlobalLock(handle); + if (data == 0) + { + GlobalFree(handle); + CloseClipboard(); + throw std::runtime_error("Unable to lock clipboard memory"); + } + memcpy(data, text16.c_str(), length); + GlobalUnlock(handle); + + HANDLE data_result = SetClipboardData(CF_UNICODETEXT, handle); + + if (data_result == 0) + { + GlobalFree(handle); + CloseClipboard(); + throw std::runtime_error("Unable to set clipboard data"); + } + + CloseClipboard(); +} + +void Win32DisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels) +{ + BITMAPV5HEADER header = {}; + header.bV5Size = sizeof(BITMAPV5HEADER); + header.bV5Width = width; + header.bV5Height = -height; + header.bV5Planes = 1; + header.bV5BitCount = 32; + header.bV5Compression = BI_BITFIELDS; + header.bV5AlphaMask = 0xff000000; + header.bV5RedMask = 0x00ff0000; + header.bV5GreenMask = 0x0000ff00; + header.bV5BlueMask = 0x000000ff; + header.bV5SizeImage = width * height * sizeof(uint32_t); + header.bV5CSType = LCS_sRGB; + + HDC dc = PaintDC; + if (dc != 0) + { + SetDIBitsToDevice(dc, 0, 0, width, height, 0, 0, 0, height, pixels, (const BITMAPINFO*)&header, BI_RGB); + } +} + +LRESULT Win32DisplayWindow::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lparam) +{ + LPARAM result = 0; + if (DwmDefWindowProc(WindowHandle.hwnd, msg, wparam, lparam, &result)) + return result; + + if (msg == WM_INPUT) + { + bool hasFocus = GetFocus() != 0; + + HRAWINPUT handle = (HRAWINPUT)lparam; + UINT size = 0; + UINT result = GetRawInputData(handle, RID_INPUT, 0, &size, sizeof(RAWINPUTHEADER)); + if (result == 0 && size > 0) + { + size *= 2; + std::vector buffer(size); + result = GetRawInputData(handle, RID_INPUT, buffer.data(), &size, sizeof(RAWINPUTHEADER)); + if (result >= 0) + { + RAWINPUT* rawinput = (RAWINPUT*)buffer.data(); + if (rawinput->header.dwType == RIM_TYPEMOUSE) + { + if (hasFocus) + WindowHost->OnWindowRawMouseMove(rawinput->data.mouse.lLastX, rawinput->data.mouse.lLastY); + } + } + } + return DefWindowProc(WindowHandle.hwnd, msg, wparam, lparam); + } + else if (msg == WM_PAINT) + { + PAINTSTRUCT paintStruct = {}; + PaintDC = BeginPaint(WindowHandle.hwnd, &paintStruct); + if (PaintDC) + { + WindowHost->OnWindowPaint(); + EndPaint(WindowHandle.hwnd, &paintStruct); + PaintDC = 0; + } + return 0; + } + else if (msg == WM_ACTIVATE) + { + WindowHost->OnWindowActivated(); + } + else if (msg == WM_MOUSEACTIVATE) + { + // We don't want to activate the window on mouse clicks as that changes the focus from the popup owner to the popup itself + if (PopupWindow) + return MA_NOACTIVATE; + } + else if (msg == WM_MOUSEMOVE) + { + if (MouseLocked && GetFocus() != 0) + { + RECT box = {}; + GetClientRect(WindowHandle.hwnd, &box); + + POINT center = {}; + center.x = box.right / 2; + center.y = box.bottom / 2; + ClientToScreen(WindowHandle.hwnd, ¢er); + + SetCursorPos(center.x, center.y); + } + else + { + UpdateCursor(); + } + + if (!TrackMouseActive) + { + TRACKMOUSEEVENT eventTrack = {}; + eventTrack.cbSize = sizeof(TRACKMOUSEEVENT); + eventTrack.hwndTrack = WindowHandle.hwnd; + eventTrack.dwFlags = TME_LEAVE; + if (TrackMouseEvent(&eventTrack)) + TrackMouseActive = true; + } + + WindowHost->OnWindowMouseMove(GetLParamPos(lparam)); + } + else if (msg == WM_MOUSELEAVE) + { + TrackMouseActive = false; + WindowHost->OnWindowMouseLeave(); + } + else if (msg == WM_LBUTTONDOWN) + { + WindowHost->OnWindowMouseDown(GetLParamPos(lparam), InputKey::LeftMouse); + } + else if (msg == WM_LBUTTONDBLCLK) + { + WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), InputKey::LeftMouse); + } + else if (msg == WM_LBUTTONUP) + { + WindowHost->OnWindowMouseUp(GetLParamPos(lparam), InputKey::LeftMouse); + } + else if (msg == WM_MBUTTONDOWN) + { + WindowHost->OnWindowMouseDown(GetLParamPos(lparam), InputKey::MiddleMouse); + } + else if (msg == WM_MBUTTONDBLCLK) + { + WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), InputKey::MiddleMouse); + } + else if (msg == WM_MBUTTONUP) + { + WindowHost->OnWindowMouseUp(GetLParamPos(lparam), InputKey::MiddleMouse); + } + else if (msg == WM_RBUTTONDOWN) + { + WindowHost->OnWindowMouseDown(GetLParamPos(lparam), InputKey::RightMouse); + } + else if (msg == WM_RBUTTONDBLCLK) + { + WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), InputKey::RightMouse); + } + else if (msg == WM_RBUTTONUP) + { + WindowHost->OnWindowMouseUp(GetLParamPos(lparam), InputKey::RightMouse); + } + else if (msg == WM_MOUSEWHEEL) + { + double delta = GET_WHEEL_DELTA_WPARAM(wparam) / (double)WHEEL_DELTA; + + // Note: WM_MOUSEWHEEL uses screen coordinates. GetLParamPos assumes client coordinates. + double dpiscale = GetDpiScale(); + POINT pos; + pos.x = GET_X_LPARAM(lparam); + pos.y = GET_Y_LPARAM(lparam); + ScreenToClient(WindowHandle.hwnd, &pos); + + WindowHost->OnWindowMouseWheel(Point(pos.x / dpiscale, pos.y / dpiscale), delta < 0.0 ? InputKey::MouseWheelDown : InputKey::MouseWheelUp); + } + else if (msg == WM_CHAR) + { + wchar_t buf[2] = { (wchar_t)wparam, 0 }; + WindowHost->OnWindowKeyChar(from_utf16(buf)); + } + else if (msg == WM_KEYDOWN) + { + WindowHost->OnWindowKeyDown((InputKey)wparam); + } + else if (msg == WM_KEYUP) + { + WindowHost->OnWindowKeyUp((InputKey)wparam); + } + else if (msg == WM_SETFOCUS) + { + if (MouseLocked) + { + ::ShowCursor(FALSE); + } + } + else if (msg == WM_KILLFOCUS) + { + if (MouseLocked) + { + ::ShowCursor(TRUE); + } + } + else if (msg == WM_CLOSE) + { + WindowHost->OnWindowClose(); + return 0; + } + else if (msg == WM_SIZE) + { + WindowHost->OnWindowGeometryChanged(); + return 0; + } + /*else if (msg == WM_NCCALCSIZE && wparam == TRUE) // calculate client area for the window + { + NCCALCSIZE_PARAMS* calcsize = (NCCALCSIZE_PARAMS*)lparam; + return WVR_REDRAW; + }*/ + + return DefWindowProc(WindowHandle.hwnd, msg, wparam, lparam); +} + +void Win32DisplayWindow::UpdateCursor() +{ + LPCWSTR cursor = IDC_ARROW; + switch (CurrentCursor) + { + case StandardCursor::arrow: cursor = IDC_ARROW; break; + case StandardCursor::appstarting: cursor = IDC_APPSTARTING; break; + case StandardCursor::cross: cursor = IDC_CROSS; break; + case StandardCursor::hand: cursor = IDC_HAND; break; + case StandardCursor::ibeam: cursor = IDC_IBEAM; break; + case StandardCursor::no: cursor = IDC_NO; break; + case StandardCursor::size_all: cursor = IDC_SIZEALL; break; + case StandardCursor::size_nesw: cursor = IDC_SIZENESW; break; + case StandardCursor::size_ns: cursor = IDC_SIZENS; break; + case StandardCursor::size_nwse: cursor = IDC_SIZENWSE; break; + case StandardCursor::size_we: cursor = IDC_SIZEWE; break; + case StandardCursor::uparrow: cursor = IDC_UPARROW; break; + case StandardCursor::wait: cursor = IDC_WAIT; break; + default: break; + } + + ::SetCursor((HCURSOR)LoadImage(0, cursor, IMAGE_CURSOR, LR_DEFAULTSIZE, LR_DEFAULTSIZE, LR_SHARED)); +} + +Point Win32DisplayWindow::GetLParamPos(LPARAM lparam) const +{ + double dpiscale = GetDpiScale(); + return Point(GET_X_LPARAM(lparam) / dpiscale, GET_Y_LPARAM(lparam) / dpiscale); +} + +LRESULT Win32DisplayWindow::WndProc(HWND windowhandle, UINT msg, WPARAM wparam, LPARAM lparam) +{ + if (msg == WM_CREATE) + { + CREATESTRUCT* createstruct = (CREATESTRUCT*)lparam; + Win32DisplayWindow* viewport = (Win32DisplayWindow*)createstruct->lpCreateParams; + viewport->WindowHandle.hwnd = windowhandle; + SetWindowLongPtr(windowhandle, GWLP_USERDATA, (LONG_PTR)viewport); + return viewport->OnWindowMessage(msg, wparam, lparam); + } + else + { + Win32DisplayWindow* viewport = (Win32DisplayWindow*)GetWindowLongPtr(windowhandle, GWLP_USERDATA); + if (viewport) + { + LRESULT result = viewport->OnWindowMessage(msg, wparam, lparam); + if (msg == WM_DESTROY) + { + SetWindowLongPtr(windowhandle, GWLP_USERDATA, 0); + viewport->WindowHandle.hwnd = 0; + } + return result; + } + else + { + return DefWindowProc(windowhandle, msg, wparam, lparam); + } + } +} + +void Win32DisplayWindow::ProcessEvents() +{ + while (true) + { + MSG msg = {}; + if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE) <= 0) + break; + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + +void Win32DisplayWindow::RunLoop() +{ + while (!ExitRunLoop && !Windows.empty()) + { + MSG msg = {}; + if (GetMessage(&msg, 0, 0, 0) <= 0) + break; + TranslateMessage(&msg); + DispatchMessage(&msg); + } + ExitRunLoop = false; +} + +void Win32DisplayWindow::ExitLoop() +{ + ExitRunLoop = true; +} + +Size Win32DisplayWindow::GetScreenSize() +{ + HDC screenDC = GetDC(0); + int screenWidth = GetDeviceCaps(screenDC, HORZRES); + int screenHeight = GetDeviceCaps(screenDC, VERTRES); + double dpiScale = GetDeviceCaps(screenDC, LOGPIXELSX) / 96.0; + ReleaseDC(0, screenDC); + + return Size(screenWidth / dpiScale, screenHeight / dpiScale); +} + +// This is to avoid needing all the Vulkan headers and the volk binding library just for this: +#ifndef VK_VERSION_1_0 + +#define VKAPI_CALL __stdcall +#define VKAPI_PTR VKAPI_CALL + +typedef uint32_t VkFlags; +typedef enum VkStructureType { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000, VK_OBJECT_TYPE_MAX_ENUM = 0x7FFFFFFF } VkStructureType; +typedef enum VkResult { VK_SUCCESS = 0, VK_RESULT_MAX_ENUM = 0x7FFFFFFF } VkResult; +typedef struct VkAllocationCallbacks VkAllocationCallbacks; + +typedef void (VKAPI_PTR* PFN_vkVoidFunction)(void); +typedef PFN_vkVoidFunction(VKAPI_PTR* PFN_vkGetInstanceProcAddr)(VkInstance instance, const char* pName); + +#ifndef VK_KHR_win32_surface + +typedef VkFlags VkWin32SurfaceCreateFlagsKHR; +typedef struct VkWin32SurfaceCreateInfoKHR +{ + VkStructureType sType; + const void* pNext; + VkWin32SurfaceCreateFlagsKHR flags; + HINSTANCE hinstance; + HWND hwnd; +} VkWin32SurfaceCreateInfoKHR; + +typedef VkResult(VKAPI_PTR* PFN_vkCreateWin32SurfaceKHR)(VkInstance instance, const VkWin32SurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface); + +#endif +#endif + +class ZWidgetVulkanLoader +{ +public: + ZWidgetVulkanLoader() + { + module = LoadLibraryA("vulkan-1.dll"); + if (!module) + throw std::runtime_error("Could not load vulkan-1.dll"); + + vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)(void(*)(void))GetProcAddress(module, "vkGetInstanceProcAddr"); + if (!vkGetInstanceProcAddr) + { + FreeLibrary(module); + throw std::runtime_error("vkGetInstanceProcAddr not found in vulkan-1.dll"); + } + } + + ~ZWidgetVulkanLoader() + { + FreeLibrary(module); + } + + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr; + HMODULE module = {}; +}; + +VkSurfaceKHR Win32DisplayWindow::CreateVulkanSurface(VkInstance instance) +{ + static ZWidgetVulkanLoader loader; + + auto vkCreateWin32SurfaceKHR = (PFN_vkCreateWin32SurfaceKHR)loader.vkGetInstanceProcAddr(instance, "vkCreateWin32SurfaceKHR"); + if (!vkCreateWin32SurfaceKHR) + throw std::runtime_error("Could not create vulkan surface"); + + VkWin32SurfaceCreateInfoKHR createInfo = { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR }; + createInfo.hwnd = WindowHandle.hwnd; + createInfo.hinstance = GetModuleHandle(nullptr); + + VkSurfaceKHR surface = {}; + VkResult result = vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface); + if (result != VK_SUCCESS) + throw std::runtime_error("Could not create vulkan surface"); + return surface; +} + +std::vector Win32DisplayWindow::GetVulkanInstanceExtensions() +{ + return { "VK_KHR_surface", "VK_KHR_win32_surface" }; +} + +static void CALLBACK Win32TimerCallback(HWND handle, UINT message, UINT_PTR timerID, DWORD timestamp) +{ + auto it = Win32DisplayWindow::Timers.find(timerID); + if (it != Win32DisplayWindow::Timers.end()) + { + auto callback = it->second; + callback(); + } +} + +void* Win32DisplayWindow::StartTimer(int timeoutMilliseconds, std::function onTimer) +{ + UINT_PTR result = SetTimer(0, 0, timeoutMilliseconds, Win32TimerCallback); + if (result == 0) + throw std::runtime_error("Could not create timer"); + Timers[result] = std::move(onTimer); + return (void*)result; +} + +void Win32DisplayWindow::StopTimer(void* timerID) +{ + auto it = Timers.find((UINT_PTR)timerID); + if (it != Timers.end()) + { + Timers.erase(it); + KillTimer(0, (UINT_PTR)timerID); + } +} + +std::list Win32DisplayWindow::Windows; +bool Win32DisplayWindow::ExitRunLoop; + +std::unordered_map> Win32DisplayWindow::Timers; diff --git a/src/window/win32/win32_display_window.h b/src/window/win32/win32_display_window.h new file mode 100644 index 000000000..7666274bd --- /dev/null +++ b/src/window/win32/win32_display_window.h @@ -0,0 +1,94 @@ +#pragma once + +#include "win32_util.h" + +#include +#include +#include +#include + +class Win32DisplayWindow : public DisplayWindow +{ +public: + Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner, RenderAPI renderAPI); + ~Win32DisplayWindow(); + + void SetWindowTitle(const std::string& text) override; + void SetWindowFrame(const Rect& box) override; + void SetClientFrame(const Rect& box) override; + void Show() override; + void ShowFullscreen() override; + void ShowMaximized() override; + void ShowMinimized() override; + void ShowNormal() override; + bool IsWindowFullscreen() override; + void Hide() override; + void Activate() override; + void ShowCursor(bool enable) override; + void LockCursor() override; + void UnlockCursor() override; + void CaptureMouse() override; + void ReleaseMouseCapture() override; + void Update() override; + bool GetKeyState(InputKey key) override; + + void SetCursor(StandardCursor cursor) override; + void UpdateCursor(); + + Rect GetWindowFrame() const override; + Size GetClientSize() const override; + int GetPixelWidth() const override; + int GetPixelHeight() const override; + double GetDpiScale() const override; + + void PresentBitmap(int width, int height, const uint32_t* pixels) override; + + void SetBorderColor(uint32_t bgra8) override; + void SetCaptionColor(uint32_t bgra8) override; + void SetCaptionTextColor(uint32_t bgra8) override; + + std::string GetClipboardText() override; + void SetClipboardText(const std::string& text) override; + + Point MapFromGlobal(const Point& pos) const override; + Point MapToGlobal(const Point& pos) const override; + + Point GetLParamPos(LPARAM lparam) const; + + void* GetNativeHandle() override { return &WindowHandle; } + + std::vector GetVulkanInstanceExtensions() override; + VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override; + + static void ProcessEvents(); + static void RunLoop(); + static void ExitLoop(); + static Size GetScreenSize(); + + static void* StartTimer(int timeoutMilliseconds, std::function onTimer); + static void StopTimer(void* timerID); + + static bool ExitRunLoop; + static std::list Windows; + std::list::iterator WindowsIterator; + + static std::unordered_map> Timers; + + LRESULT OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lparam); + static LRESULT CALLBACK WndProc(HWND windowhandle, UINT msg, WPARAM wparam, LPARAM lparam); + + DisplayWindowHost* WindowHost = nullptr; + bool PopupWindow = false; + + Win32NativeHandle WindowHandle; + bool Fullscreen = false; + + bool MouseLocked = false; + POINT MouseLockPos = {}; + + bool TrackMouseActive = false; + + HDC PaintDC = 0; + + StandardCursor CurrentCursor = StandardCursor::arrow; +}; diff --git a/src/window/win32/win32_open_file_dialog.cpp b/src/window/win32/win32_open_file_dialog.cpp new file mode 100644 index 000000000..092a78da1 --- /dev/null +++ b/src/window/win32/win32_open_file_dialog.cpp @@ -0,0 +1,227 @@ + +#include "win32_open_file_dialog.h" +#include "win32_display_window.h" +#include "core/widget.h" +#include +#include +#include +#include +#include + +Win32OpenFileDialog::Win32OpenFileDialog(Win32DisplayWindow* owner) : owner(owner) +{ +} + +bool Win32OpenFileDialog::Show() +{ + std::wstring title16 = to_utf16(title); + std::wstring initial_directory16 = to_utf16(initial_directory); + + HRESULT result; + ComPtr open_dialog; + + result = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, open_dialog.GetIID(), open_dialog.InitPtr()); + throw_if_failed(result, "CoCreateInstance(FileOpenDialog) failed"); + + result = open_dialog->SetTitle(title16.c_str()); + throw_if_failed(result, "IFileOpenDialog.SetTitle failed"); + + if (!initial_filename.empty()) + { + result = open_dialog->SetFileName(to_utf16(initial_filename).c_str()); + throw_if_failed(result, "IFileOpenDialog.SetFileName failed"); + } + + FILEOPENDIALOGOPTIONS options = FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST; + if (multi_select) + options |= FOS_ALLOWMULTISELECT; + result = open_dialog->SetOptions(options); + throw_if_failed(result, "IFileOpenDialog.SetOptions() failed"); + + if (!filters.empty()) + { + std::vector filterspecs(filters.size()); + std::vector descriptions(filters.size()); + std::vector extensions(filters.size()); + for (size_t i = 0; i < filters.size(); i++) + { + descriptions[i] = to_utf16(filters[i].description); + extensions[i] = to_utf16(filters[i].extension); + COMDLG_FILTERSPEC& spec = filterspecs[i]; + spec.pszName = descriptions[i].c_str(); + spec.pszSpec = extensions[i].c_str(); + } + result = open_dialog->SetFileTypes((UINT)filterspecs.size(), filterspecs.data()); + throw_if_failed(result, "IFileOpenDialog.SetFileTypes() failed"); + + if ((size_t)filterindex < filters.size()) + { + result = open_dialog->SetFileTypeIndex(filterindex); + throw_if_failed(result, "IFileOpenDialog.SetFileTypeIndex() failed"); + } + } + + if (!defaultext.empty()) + { + result = open_dialog->SetDefaultExtension(to_utf16(defaultext).c_str()); + throw_if_failed(result, "IFileOpenDialog.SetDefaultExtension() failed"); + } + + if (initial_directory16.length() > 0) + { + LPITEMIDLIST item_id_list = nullptr; + SFGAOF flags = 0; + result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags); + throw_if_failed(result, "SHParseDisplayName failed"); + + ComPtr folder_item; + result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr()); + ILFree(item_id_list); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + + /* This code requires Windows Vista or newer: + ComPtr folder_item; + result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr()); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + */ + + if (folder_item) + { + result = open_dialog->SetFolder(folder_item); + throw_if_failed(result, "IFileOpenDialog.SetFolder failed"); + } + } + + // For some reason this can hang deep inside Win32 if we do it on the calling thread! + { + bool done = false; + std::mutex mutex; + std::condition_variable condvar; + + std::thread thread([&]() { + + if (owner) + result = open_dialog->Show(owner->WindowHandle.hwnd); + else + result = open_dialog->Show(0); + + std::unique_lock lock(mutex); + done = true; + condvar.notify_all(); + + }); + + std::unique_lock lock(mutex); + while (!done) + { + DisplayBackend::Get()->ProcessEvents(); + using namespace std::chrono_literals; + condvar.wait_for(lock, 50ms, [&]() { return done; }); + } + lock.unlock(); + thread.join(); + } + + if (SUCCEEDED(result)) + { + ComPtr items; + result = open_dialog->GetResults(items.TypedInitPtr()); + throw_if_failed(result, "IFileOpenDialog.GetSelectedItems failed"); + + DWORD num_items = 0; + result = items->GetCount(&num_items); + throw_if_failed(result, "IShellItemArray.GetCount failed"); + + for (DWORD i = 0; i < num_items; i++) + { + ComPtr item; + result = items->GetItemAt(i, item.TypedInitPtr()); + throw_if_failed(result, "IShellItemArray.GetItemAt failed"); + + WCHAR* buffer = nullptr; + result = item->GetDisplayName(SIGDN_FILESYSPATH, &buffer); + throw_if_failed(result, "IShellItem.GetDisplayName failed"); + + std::wstring output16; + if (buffer) + { + try + { + output16 = buffer; + } + catch (...) + { + CoTaskMemFree(buffer); + throw; + } + } + + CoTaskMemFree(buffer); + filenames.push_back(from_utf16(output16)); + } + return true; + } + else + { + return false; + } +} + +std::string Win32OpenFileDialog::Filename() const +{ + return !filenames.empty() ? filenames.front() : std::string(); +} + +std::vector Win32OpenFileDialog::Filenames() const +{ + return filenames; +} + +void Win32OpenFileDialog::SetMultiSelect(bool new_multi_select) +{ + multi_select = new_multi_select; +} + +void Win32OpenFileDialog::SetFilename(const std::string& filename) +{ + initial_filename = filename; +} + +void Win32OpenFileDialog::AddFilter(const std::string& filter_description, const std::string& filter_extension) +{ + Filter f; + f.description = filter_description; + f.extension = filter_extension; + filters.push_back(std::move(f)); +} + +void Win32OpenFileDialog::ClearFilters() +{ + filters.clear(); +} + +void Win32OpenFileDialog::SetFilterIndex(int filter_index) +{ + filterindex = filter_index; +} + +void Win32OpenFileDialog::SetInitialDirectory(const std::string& path) +{ + initial_directory = path; +} + +void Win32OpenFileDialog::SetTitle(const std::string& newtitle) +{ + title = newtitle; +} + +void Win32OpenFileDialog::SetDefaultExtension(const std::string& extension) +{ + defaultext = extension; +} + +void Win32OpenFileDialog::throw_if_failed(HRESULT result, const std::string& error) +{ + if (FAILED(result)) + throw std::runtime_error(error); +} diff --git a/src/window/win32/win32_open_file_dialog.h b/src/window/win32/win32_open_file_dialog.h new file mode 100644 index 000000000..52ac858f0 --- /dev/null +++ b/src/window/win32/win32_open_file_dialog.h @@ -0,0 +1,44 @@ +#pragma once + +#include "systemdialogs/open_file_dialog.h" +#include "win32_util.h" + +class Win32DisplayWindow; + +class Win32OpenFileDialog : public OpenFileDialog +{ +public: + Win32OpenFileDialog(Win32DisplayWindow* owner); + + bool Show() override; + std::string Filename() const override; + std::vector Filenames() const override; + void SetMultiSelect(bool new_multi_select) override; + void SetFilename(const std::string& filename) override; + void AddFilter(const std::string& filter_description, const std::string& filter_extension) override; + void ClearFilters() override; + void SetFilterIndex(int filter_index) override; + void SetInitialDirectory(const std::string& path) override; + void SetTitle(const std::string& newtitle) override; + void SetDefaultExtension(const std::string& extension) override; + +private: + void throw_if_failed(HRESULT result, const std::string& error); + + Win32DisplayWindow* owner = nullptr; + + std::string initial_directory; + std::string initial_filename; + std::string title; + std::vector filenames; + bool multi_select = false; + + struct Filter + { + std::string description; + std::string extension; + }; + std::vector filters; + int filterindex = 0; + std::string defaultext; +}; diff --git a/src/window/win32/win32_open_folder_dialog.cpp b/src/window/win32/win32_open_folder_dialog.cpp new file mode 100644 index 000000000..f579f0ce7 --- /dev/null +++ b/src/window/win32/win32_open_folder_dialog.cpp @@ -0,0 +1,111 @@ + +#include "win32_open_folder_dialog.h" +#include "win32_display_window.h" +#include "core/widget.h" +#include + +Win32OpenFolderDialog::Win32OpenFolderDialog(Win32DisplayWindow* owner) : owner(owner) +{ +} + +bool Win32OpenFolderDialog::Show() +{ + std::wstring title16 = to_utf16(title); + std::wstring initial_directory16 = to_utf16(initial_directory); + + HRESULT result; + ComPtr open_dialog; + + result = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, open_dialog.GetIID(), open_dialog.InitPtr()); + throw_if_failed(result, "CoCreateInstance(FileOpenDialog) failed"); + + result = open_dialog->SetTitle(title16.c_str()); + throw_if_failed(result, "IFileOpenDialog.SetTitle failed"); + + result = open_dialog->SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST); + throw_if_failed(result, "IFileOpenDialog.SetOptions((FOS_PICKFOLDERS) failed"); + + if (initial_directory16.length() > 0) + { + LPITEMIDLIST item_id_list = nullptr; + SFGAOF flags = 0; + result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags); + throw_if_failed(result, "SHParseDisplayName failed"); + + ComPtr folder_item; + result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr()); + ILFree(item_id_list); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + + /* This code requires Windows Vista or newer: + ComPtr folder_item; + result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr()); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + */ + + if (folder_item) + { + result = open_dialog->SetFolder(folder_item); + throw_if_failed(result, "IFileOpenDialog.SetFolder failed"); + } + } + + if (owner) + result = open_dialog->Show(owner->WindowHandle.hwnd); + else + result = open_dialog->Show(0); + + if (SUCCEEDED(result)) + { + ComPtr chosen_folder; + result = open_dialog->GetResult(chosen_folder.TypedInitPtr()); + throw_if_failed(result, "IFileOpenDialog.GetResult failed"); + + WCHAR* buffer = nullptr; + result = chosen_folder->GetDisplayName(SIGDN_FILESYSPATH, &buffer); + throw_if_failed(result, "IShellItem.GetDisplayName failed"); + + std::wstring output_directory16; + if (buffer) + { + try + { + output_directory16 = buffer; + } + catch (...) + { + CoTaskMemFree(buffer); + throw; + } + } + + CoTaskMemFree(buffer); + selected_path = from_utf16(output_directory16); + return true; + } + else + { + return false; + } +} + +std::string Win32OpenFolderDialog::SelectedPath() const +{ + return selected_path; +} + +void Win32OpenFolderDialog::SetInitialDirectory(const std::string& path) +{ + initial_directory = path; +} + +void Win32OpenFolderDialog::SetTitle(const std::string& newtitle) +{ + title = newtitle; +} + +void Win32OpenFolderDialog::throw_if_failed(HRESULT result, const std::string& error) +{ + if (FAILED(result)) + throw std::runtime_error(error); +} diff --git a/src/window/win32/win32_open_folder_dialog.h b/src/window/win32/win32_open_folder_dialog.h new file mode 100644 index 000000000..45f05a26b --- /dev/null +++ b/src/window/win32/win32_open_folder_dialog.h @@ -0,0 +1,26 @@ +#pragma once + +#include "systemdialogs/open_folder_dialog.h" +#include "win32_util.h" + +class Win32DisplayWindow; + +class Win32OpenFolderDialog : public OpenFolderDialog +{ +public: + Win32OpenFolderDialog(Win32DisplayWindow* owner); + + bool Show() override; + std::string SelectedPath() const override; + void SetInitialDirectory(const std::string& path) override; + void SetTitle(const std::string& newtitle) override; + +private: + void throw_if_failed(HRESULT result, const std::string& error); + + Win32DisplayWindow* owner = nullptr; + + std::string selected_path; + std::string initial_directory; + std::string title; +}; diff --git a/src/window/win32/win32_save_file_dialog.cpp b/src/window/win32/win32_save_file_dialog.cpp new file mode 100644 index 000000000..02568f1ff --- /dev/null +++ b/src/window/win32/win32_save_file_dialog.cpp @@ -0,0 +1,174 @@ + +#include "win32_save_file_dialog.h" +#include "win32_display_window.h" +#include "core/widget.h" + +Win32SaveFileDialog::Win32SaveFileDialog(Win32DisplayWindow* owner) : owner(owner) +{ +} + +bool Win32SaveFileDialog::Show() +{ + std::wstring title16 = to_utf16(title); + std::wstring initial_directory16 = to_utf16(initial_directory); + + HRESULT result; + ComPtr save_dialog; + + result = CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_ALL, save_dialog.GetIID(), save_dialog.InitPtr()); + throw_if_failed(result, "CoCreateInstance(FileSaveDialog) failed"); + + result = save_dialog->SetTitle(title16.c_str()); + throw_if_failed(result, "IFileSaveDialog.SetTitle failed"); + + if (!initial_filename.empty()) + { + result = save_dialog->SetFileName(to_utf16(initial_filename).c_str()); + throw_if_failed(result, "IFileSaveDialog.SetFileName failed"); + } + + FILEOPENDIALOGOPTIONS options = FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST; + result = save_dialog->SetOptions(options); + throw_if_failed(result, "IFileSaveDialog.SetOptions() failed"); + + if (!filters.empty()) + { + std::vector filterspecs(filters.size()); + std::vector descriptions(filters.size()); + std::vector extensions(filters.size()); + for (size_t i = 0; i < filters.size(); i++) + { + descriptions[i] = to_utf16(filters[i].description); + extensions[i] = to_utf16(filters[i].extension); + COMDLG_FILTERSPEC& spec = filterspecs[i]; + spec.pszName = descriptions[i].c_str(); + spec.pszSpec = extensions[i].c_str(); + } + result = save_dialog->SetFileTypes((UINT)filterspecs.size(), filterspecs.data()); + throw_if_failed(result, "IFileOpenDialog.SetFileTypes() failed"); + + if ((size_t)filterindex < filters.size()) + { + result = save_dialog->SetFileTypeIndex(filterindex); + throw_if_failed(result, "IFileOpenDialog.SetFileTypeIndex() failed"); + } + } + + if (!defaultext.empty()) + { + result = save_dialog->SetDefaultExtension(to_utf16(defaultext).c_str()); + throw_if_failed(result, "IFileOpenDialog.SetDefaultExtension() failed"); + } + + if (initial_directory16.length() > 0) + { + LPITEMIDLIST item_id_list = nullptr; + SFGAOF flags = 0; + result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags); + throw_if_failed(result, "SHParseDisplayName failed"); + + ComPtr folder_item; + result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr()); + ILFree(item_id_list); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + + /* This code requires Windows Vista or newer: + ComPtr folder_item; + result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr()); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + */ + + if (folder_item) + { + result = save_dialog->SetFolder(folder_item); + throw_if_failed(result, "IFileSaveDialog.SetFolder failed"); + } + } + + if (owner) + result = save_dialog->Show(owner->WindowHandle.hwnd); + else + result = save_dialog->Show(0); + + if (SUCCEEDED(result)) + { + ComPtr chosen_folder; + result = save_dialog->GetResult(chosen_folder.TypedInitPtr()); + throw_if_failed(result, "IFileSaveDialog.GetResult failed"); + + WCHAR* buffer = nullptr; + result = chosen_folder->GetDisplayName(SIGDN_FILESYSPATH, &buffer); + throw_if_failed(result, "IShellItem.GetDisplayName failed"); + + std::wstring output16; + if (buffer) + { + try + { + output16 = buffer; + } + catch (...) + { + CoTaskMemFree(buffer); + throw; + } + } + + CoTaskMemFree(buffer); + filename = from_utf16(output16); + return true; + } + else + { + return false; + } +} + +std::string Win32SaveFileDialog::Filename() const +{ + return filename; +} + +void Win32SaveFileDialog::SetFilename(const std::string& filename) +{ + initial_filename = filename; +} + +void Win32SaveFileDialog::AddFilter(const std::string& filter_description, const std::string& filter_extension) +{ + Filter f; + f.description = filter_description; + f.extension = filter_extension; + filters.push_back(std::move(f)); +} + +void Win32SaveFileDialog::ClearFilters() +{ + filters.clear(); +} + +void Win32SaveFileDialog::SetFilterIndex(int filter_index) +{ + filterindex = filter_index; +} + +void Win32SaveFileDialog::SetInitialDirectory(const std::string& path) +{ + initial_directory = path; +} + +void Win32SaveFileDialog::SetTitle(const std::string& newtitle) +{ + title = newtitle; +} + +void Win32SaveFileDialog::SetDefaultExtension(const std::string& extension) +{ + defaultext = extension; +} + +void Win32SaveFileDialog::throw_if_failed(HRESULT result, const std::string& error) +{ + if (FAILED(result)) + throw std::runtime_error(error); +} diff --git a/src/window/win32/win32_save_file_dialog.h b/src/window/win32/win32_save_file_dialog.h new file mode 100644 index 000000000..5a5071423 --- /dev/null +++ b/src/window/win32/win32_save_file_dialog.h @@ -0,0 +1,42 @@ +#pragma once + +#include "systemdialogs/save_file_dialog.h" +#include "win32_util.h" + +class Win32DisplayWindow; + +class Win32SaveFileDialog : public SaveFileDialog +{ +public: + Win32SaveFileDialog(Win32DisplayWindow* owner); + + bool Show() override; + std::string Filename() const override; + void SetFilename(const std::string& filename) override; + void AddFilter(const std::string& filter_description, const std::string& filter_extension) override; + void ClearFilters() override; + void SetFilterIndex(int filter_index) override; + void SetInitialDirectory(const std::string& path) override; + void SetTitle(const std::string& newtitle) override; + void SetDefaultExtension(const std::string& extension) override; + +private: + void throw_if_failed(HRESULT result, const std::string& error); + + Win32DisplayWindow* owner = nullptr; + + std::string filename; + std::string initial_directory; + std::string initial_filename; + std::string title; + std::vector filenames; + + struct Filter + { + std::string description; + std::string extension; + }; + std::vector filters; + int filterindex = 0; + std::string defaultext; +}; diff --git a/src/window/win32/win32_util.h b/src/window/win32/win32_util.h new file mode 100644 index 000000000..06f89540e --- /dev/null +++ b/src/window/win32/win32_util.h @@ -0,0 +1,60 @@ +#pragma once + +#define NOMINMAX +#define WIN32_MEAN_AND_LEAN +#ifndef WINVER +#define WINVER 0x0605 +#endif +#include +#include +#include + +namespace +{ + static std::string from_utf16(const std::wstring& str) + { + if (str.empty()) return {}; + int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr); + if (needed == 0) + throw std::runtime_error("WideCharToMultiByte failed"); + std::string result; + result.resize(needed); + needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr); + if (needed == 0) + throw std::runtime_error("WideCharToMultiByte failed"); + return result; + } + + static std::wstring to_utf16(const std::string& str) + { + if (str.empty()) return {}; + int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + std::wstring result; + result.resize(needed); + needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size()); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + return result; + } + + template + class ComPtr + { + public: + ComPtr() { Ptr = nullptr; } + ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } + ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; } + ~ComPtr() { reset(); } + ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; } + void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; } + T* get() { return Ptr; } + static IID GetIID() { return __uuidof(T); } + void** InitPtr() { return (void**)TypedInitPtr(); } + T** TypedInitPtr() { reset(); return &Ptr; } + operator T* () const { return Ptr; } + T* operator ->() const { return Ptr; } + T* Ptr; + }; +} diff --git a/src/window/window.cpp b/src/window/window.cpp new file mode 100644 index 000000000..5fc35b230 --- /dev/null +++ b/src/window/window.cpp @@ -0,0 +1,201 @@ + +#include "window/window.h" +#include "window/stub/stub_open_folder_dialog.h" +#include "window/stub/stub_open_file_dialog.h" +#include "window/stub/stub_save_file_dialog.h" +#include "window/sdl2nativehandle.h" +#include "core/widget.h" +#include + +std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) +{ + return DisplayBackend::Get()->Create(windowHost, popupWindow, owner, renderAPI); +} + +void DisplayWindow::ProcessEvents() +{ + DisplayBackend::Get()->ProcessEvents(); +} + +void DisplayWindow::RunLoop() +{ + DisplayBackend::Get()->RunLoop(); +} + +void DisplayWindow::ExitLoop() +{ + DisplayBackend::Get()->ExitLoop(); +} + +void* DisplayWindow::StartTimer(int timeoutMilliseconds, std::function onTimer) +{ + return DisplayBackend::Get()->StartTimer(timeoutMilliseconds, onTimer); +} + +void DisplayWindow::StopTimer(void* timerID) +{ + DisplayBackend::Get()->StopTimer(timerID); +} + +Size DisplayWindow::GetScreenSize() +{ + return DisplayBackend::Get()->GetScreenSize(); +} + +///////////////////////////////////////////////////////////////////////////// + +static std::unique_ptr& GetBackendVar() +{ + // In C++, static variables in functions are constructed on first encounter and is destructed in the reverse order when main() ends. + static std::unique_ptr p; + return p; +} + +DisplayBackend* DisplayBackend::Get() +{ + return GetBackendVar().get(); +} + +void DisplayBackend::Set(std::unique_ptr instance) +{ + GetBackendVar() = std::move(instance); +} + +std::unique_ptr DisplayBackend::CreateOpenFileDialog(DisplayWindow* owner) +{ + return std::make_unique(owner); +} + +std::unique_ptr DisplayBackend::CreateSaveFileDialog(DisplayWindow* owner) +{ + return std::make_unique(owner); +} + +std::unique_ptr DisplayBackend::CreateOpenFolderDialog(DisplayWindow* owner) +{ + return std::make_unique(owner); +} + +#ifdef _MSC_VER +#pragma warning(disable: 4996) // warning C4996 : 'getenv' : This function or variable may be unsafe.Consider using _dupenv_s instead.To disable deprecation, use _CRT_SECURE_NO_WARNINGS.See online help for details. +#endif + +std::unique_ptr DisplayBackend::TryCreateBackend() +{ + std::unique_ptr backend; + + // Check if there is an environment variable specified for the desired backend + const char* backendSelectionEnv = std::getenv("ZWIDGET_DISPLAY_BACKEND"); + if (backendSelectionEnv) + { + std::string backendSelectionStr(backendSelectionEnv); + if (backendSelectionStr == "Win32") + { + backend = TryCreateWin32(); + } + else if (backendSelectionStr == "X11") + { + backend = TryCreateX11(); + } + else if (backendSelectionStr == "SDL2") + { + backend = TryCreateSDL2(); + } + } + + if (!backend) + { + backend = TryCreateWin32(); + if (!backend) backend = TryCreateWayland(); + if (!backend) backend = TryCreateX11(); + if (!backend) backend = TryCreateSDL2(); + } + + return backend; +} + +#ifdef WIN32 + +#include "win32/win32_display_backend.h" + +std::unique_ptr DisplayBackend::TryCreateWin32() +{ + return std::make_unique(); +} + +#else + +std::unique_ptr DisplayBackend::TryCreateWin32() +{ + return nullptr; +} + +#endif + +#ifdef USE_SDL2 + +#include "sdl2/sdl2_display_backend.h" + +std::unique_ptr DisplayBackend::TryCreateSDL2() +{ + return std::make_unique(); +} + +#else + +std::unique_ptr DisplayBackend::TryCreateSDL2() +{ + return nullptr; +} + +#endif + +#ifdef USE_X11 + +#include "x11/x11_display_backend.h" + +std::unique_ptr DisplayBackend::TryCreateX11() +{ + try + { + return std::make_unique(); + } + catch (...) + { + return nullptr; + } +} + +#else + +std::unique_ptr DisplayBackend::TryCreateX11() +{ + return nullptr; +} + +#endif + +#ifdef USE_WAYLAND + +#include "wayland/wayland_display_backend.h" + +std::unique_ptr DisplayBackend::TryCreateWayland() +{ + try + { + return std::make_unique(); + } + catch (...) + { + return nullptr; + } +} + +#else + +std::unique_ptr DisplayBackend::TryCreateWayland() +{ + return nullptr; +} + +#endif diff --git a/src/window/x11/x11_display_backend.cpp b/src/window/x11/x11_display_backend.cpp new file mode 100644 index 000000000..b97760b3a --- /dev/null +++ b/src/window/x11/x11_display_backend.cpp @@ -0,0 +1,70 @@ + +#include "x11_display_backend.h" +#include "x11_display_window.h" + +#ifdef USE_DBUS +#include "window/dbus/dbus_open_file_dialog.h" +#include "window/dbus/dbus_save_file_dialog.h" +#include "window/dbus/dbus_open_folder_dialog.h" +#endif + +std::unique_ptr X11DisplayBackend::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) +{ + return std::make_unique(windowHost, popupWindow, static_cast(owner), renderAPI); +} + +void X11DisplayBackend::ProcessEvents() +{ + X11DisplayWindow::ProcessEvents(); +} + +void X11DisplayBackend::RunLoop() +{ + X11DisplayWindow::RunLoop(); +} + +void X11DisplayBackend::ExitLoop() +{ + X11DisplayWindow::ExitLoop(); +} + +Size X11DisplayBackend::GetScreenSize() +{ + return X11DisplayWindow::GetScreenSize(); +} + +void* X11DisplayBackend::StartTimer(int timeoutMilliseconds, std::function onTimer) +{ + return X11DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer)); +} + +void X11DisplayBackend::StopTimer(void* timerID) +{ + X11DisplayWindow::StopTimer(timerID); +} + +#ifdef USE_DBUS +std::unique_ptr X11DisplayBackend::CreateOpenFileDialog(DisplayWindow* owner) +{ + std::string ownerHandle; + if (owner) + ownerHandle = "x11:" + std::to_string(static_cast(owner)->window); + return std::make_unique(ownerHandle); +} + +std::unique_ptr X11DisplayBackend::CreateSaveFileDialog(DisplayWindow* owner) +{ + std::string ownerHandle; + if (owner) + ownerHandle = "x11:" + std::to_string(static_cast(owner)->window); + return std::make_unique(ownerHandle); +} + +std::unique_ptr X11DisplayBackend::CreateOpenFolderDialog(DisplayWindow* owner) +{ + std::string ownerHandle; + if (owner) + ownerHandle = "x11:" + std::to_string(static_cast(owner)->window); + return std::make_unique(ownerHandle); +} +#endif diff --git a/src/window/x11/x11_display_backend.h b/src/window/x11/x11_display_backend.h new file mode 100644 index 000000000..9d8046353 --- /dev/null +++ b/src/window/x11/x11_display_backend.h @@ -0,0 +1,25 @@ +#pragma once + +#include "window/window.h" + +class X11DisplayBackend : public DisplayBackend +{ +public: + std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) override; + void ProcessEvents() override; + void RunLoop() override; + void ExitLoop() override; + + void* StartTimer(int timeoutMilliseconds, std::function onTimer) override; + void StopTimer(void* timerID) override; + + Size GetScreenSize() override; + + bool IsX11() override { return true; } + +#ifdef USE_DBUS + std::unique_ptr CreateOpenFileDialog(DisplayWindow* owner) override; + std::unique_ptr CreateSaveFileDialog(DisplayWindow* owner) override; + std::unique_ptr CreateOpenFolderDialog(DisplayWindow* owner) override; +#endif +}; diff --git a/src/window/x11/x11_display_window.cpp b/src/window/x11/x11_display_window.cpp new file mode 100644 index 000000000..5797b0b4f --- /dev/null +++ b/src/window/x11/x11_display_window.cpp @@ -0,0 +1,1184 @@ + +#include "x11_display_window.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class X11Connection +{ +public: + X11Connection() + { + // If we ever want to support windows on multiple threads: + // XInitThreads(); + + display = XOpenDisplay(nullptr); + if (!display) + throw std::runtime_error("Could not open X11 display"); + + // Make auto-repeat keys detectable + Bool supports_detectable_autorepeat = {}; + XkbSetDetectableAutoRepeat(display, True, &supports_detectable_autorepeat); + + // Loads the XMODIFIERS environment variable to see what IME to use + XSetLocaleModifiers(""); + xim = XOpenIM(display, 0, 0, 0); + if (!xim) + { + // fallback to internal input method + XSetLocaleModifiers("@im=none"); + xim = XOpenIM(display, 0, 0, 0); + } + } + + ~X11Connection() + { + for (auto& it : standardCursors) + XFreeCursor(display, it.second); + if (xim) + XCloseIM(xim); + XCloseDisplay(display); + } + + Display* display = nullptr; + std::map atoms; + std::map windows; + std::map standardCursors; + bool ExitRunLoop = false; + + XIM xim = nullptr; +}; + +static X11Connection* GetX11Connection() +{ + static X11Connection connection; + return &connection; +} + +static Atom GetAtom(const std::string& name) +{ + auto connection = GetX11Connection(); + auto it = connection->atoms.find(name); + if (it != connection->atoms.end()) + return it->second; + + Atom atom = XInternAtom(connection->display, name.c_str(), True); + connection->atoms[name] = atom; + return atom; +} + +X11DisplayWindow::X11DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, X11DisplayWindow* owner, RenderAPI renderAPI) : windowHost(windowHost), owner(owner) +{ + display = GetX11Connection()->display; + + screen = XDefaultScreen(display); + depth = XDefaultDepth(display, screen); + visual = XDefaultVisual(display, screen); + colormap = XDefaultColormap(display, screen); + + int disp_width_px = XDisplayWidth(display, screen); + int disp_height_px = XDisplayHeight(display, screen); + int disp_width_mm = XDisplayWidthMM(display, screen); + double ppi = (disp_width_mm < 24) ? 96.0 : (25.4 * static_cast(disp_width_px) / static_cast(disp_width_mm)); + dpiScale = std::round(ppi / 96.0 * 4.0) / 4.0; // 100%, 125%, 150%, 175%, 200%, etc. + + XSetWindowAttributes attributes = {}; + attributes.backing_store = Always; + attributes.override_redirect = popupWindow ? True : False; + attributes.save_under = popupWindow ? True : False; + attributes.colormap = colormap; + attributes.event_mask = + KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | + EnterWindowMask | LeaveWindowMask | PointerMotionMask | KeymapStateMask | + ExposureMask | StructureNotifyMask | FocusChangeMask | PropertyChangeMask; + + unsigned long mask = CWBackingStore | CWSaveUnder | CWEventMask | CWOverrideRedirect; + + window = XCreateWindow(display, XRootWindow(display, screen), 0, 0, 100, 100, 0, depth, InputOutput, visual, mask, &attributes); + GetX11Connection()->windows[window] = this; + + if (owner) + { + XSetTransientForHint(display, window, owner->window); + } + + // Tell window manager which process this window came from + if (GetAtom("_NET_WM_PID") != None) + { + int32_t pid = getpid(); + if (pid != 0) + { + XChangeProperty(display, window, GetAtom("_NET_WM_PID"), XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1); + } + } + + // Tell window manager which machine this window came from + if (GetAtom("WM_CLIENT_MACHINE") != None) + { + std::vector hostname(256); + if (gethostname(hostname.data(), hostname.size()) >= 0) + { + hostname.push_back(0); + XChangeProperty(display, window, GetAtom("WM_CLIENT_MACHINE"), XA_STRING, 8, PropModeReplace, (unsigned char *)hostname.data(), strlen(hostname.data())); + } + } + + // Tell window manager we want to listen to close events + if (GetAtom("WM_DELETE_WINDOW") != None) + { + Atom protocol = GetAtom("WM_DELETE_WINDOW"); + XSetWMProtocols(display, window, &protocol, 1); + } + + // Tell window manager what type of window we are + if (GetAtom("_NET_WM_WINDOW_TYPE") != None) + { + Atom type = None; + if (popupWindow) + { + type = GetAtom("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"); + if (type == None) + type = GetAtom("_NET_WM_WINDOW_TYPE_POPUP_MENU"); + if (type == None) + type = GetAtom("_NET_WM_WINDOW_TYPE_COMBO"); + } + if (type == None) + type = GetAtom("_NET_WM_WINDOW_TYPE_NORMAL"); + + if (type != None) + { + XChangeProperty(display, window, GetAtom("_NET_WM_WINDOW_TYPE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&type, 1); + } + } + + // Create input context + if (GetX11Connection()->xim) + { + xic = XCreateIC( + GetX11Connection()->xim, + XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, window, + XNFocusWindow, window, + nullptr); + } +} + +X11DisplayWindow::~X11DisplayWindow() +{ + if (hidden_cursor != None) + { + XFreeCursor(display, hidden_cursor); + XFreePixmap(display, cursor_bitmap); + } + + DestroyBackbuffer(); + XDestroyWindow(display, window); + GetX11Connection()->windows.erase(GetX11Connection()->windows.find(window)); +} + +void X11DisplayWindow::SetWindowTitle(const std::string& text) +{ + XSetStandardProperties(display, window, text.c_str(), text.c_str(), None, nullptr, 0, nullptr); +} + +void X11DisplayWindow::SetWindowFrame(const Rect& box) +{ + // To do: this requires cooperation with the window manager + + SetClientFrame(box); +} + +void X11DisplayWindow::SetClientFrame(const Rect& box) +{ + double dpiscale = GetDpiScale(); + int x = (int)std::round(box.x * dpiscale); + int y = (int)std::round(box.y * dpiscale); + int width = (int)std::round(box.width * dpiscale); + int height = (int)std::round(box.height * dpiscale); + + XWindowChanges changes = {}; + changes.x = x; + changes.y = y; + changes.width = width; + changes.height = height; + unsigned int mask = CWX | CWY | CWWidth | CWHeight; + + XConfigureWindow(display, window, mask, &changes); +} + +void X11DisplayWindow::Show() +{ + if (!isMapped) + { + XMapRaised(display, window); + isMapped = true; + } +} + +void X11DisplayWindow::ShowFullscreen() +{ + Show(); + + if (GetAtom("_NET_WM_STATE") != None && GetAtom("_NET_WM_STATE_FULLSCREEN") != None) + { + Atom state = GetAtom("_NET_WM_STATE_FULLSCREEN"); + XChangeProperty(display, window, GetAtom("_NET_WM_STATE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&state, 1); + isFullscreen = true; + } +} + +bool X11DisplayWindow::IsWindowFullscreen() +{ + return isFullscreen; +} + +void X11DisplayWindow::ShowMaximized() +{ + Show(); +} + +void X11DisplayWindow::ShowMinimized() +{ + if (!isMinimized) + { + Show(); // To do: can this be avoided? WMHints has an initial state that can make it show minimized + XIconifyWindow(display, window, screen); + isMinimized = true; + } +} + +void X11DisplayWindow::ShowNormal() +{ + Show(); + isFullscreen = false; +} + +void X11DisplayWindow::Hide() +{ + if (isMapped) + { + XUnmapWindow(display, window); + isMapped = false; + } +} + +void X11DisplayWindow::Activate() +{ + XRaiseWindow(display, window); +} + +void X11DisplayWindow::ShowCursor(bool enable) +{ + if (isCursorEnabled != enable) + { + isCursorEnabled = enable; + UpdateCursor(); + } +} + +void X11DisplayWindow::LockCursor() +{ + ShowCursor(false); +} + +void X11DisplayWindow::UnlockCursor() +{ + ShowCursor(true); +} + +void X11DisplayWindow::CaptureMouse() +{ + ShowCursor(false); +} + +void X11DisplayWindow::ReleaseMouseCapture() +{ + ShowCursor(true); +} + +void X11DisplayWindow::Update() +{ + needsUpdate = true; +} + +bool X11DisplayWindow::GetKeyState(InputKey key) +{ + auto it = keyState.find(key); + return it != keyState.end() ? it->second : false; +} + +void X11DisplayWindow::SetCursor(StandardCursor newcursor) +{ + if (cursor != newcursor) + { + cursor = newcursor; + UpdateCursor(); + } +} + +void X11DisplayWindow::UpdateCursor() +{ + if (isCursorEnabled) + { + Cursor& x11cursor = GetX11Connection()->standardCursors[cursor]; + if (x11cursor == None) + { + unsigned int index = XC_left_ptr; + switch (cursor) + { + default: + case StandardCursor::arrow: index = XC_left_ptr; break; + case StandardCursor::appstarting: index = XC_watch; break; + case StandardCursor::cross: index = XC_cross; break; + case StandardCursor::hand: index = XC_hand2; break; + case StandardCursor::ibeam: index = XC_xterm; break; + case StandardCursor::size_all: index = XC_fleur; break; + case StandardCursor::size_ns: index = XC_double_arrow; break; + case StandardCursor::size_we: index = XC_sb_h_double_arrow; break; + case StandardCursor::uparrow: index = XC_sb_up_arrow; break; + case StandardCursor::wait: index = XC_watch; break; + case StandardCursor::no: index = XC_X_cursor; break; + case StandardCursor::size_nesw: break; // To do: need to map this + case StandardCursor::size_nwse: break; + } + x11cursor = XCreateFontCursor(display, index); + } + XDefineCursor(display, window, x11cursor); + } + else + { + if (hidden_cursor == None) + { + char data[64] = {}; + XColor black_color = {}; + cursor_bitmap = XCreateBitmapFromData(display, window, data, 8, 8); + hidden_cursor = XCreatePixmapCursor(display, cursor_bitmap, cursor_bitmap, &black_color, &black_color, 0, 0); + } + XDefineCursor(display, window, hidden_cursor); + } +} + +Rect X11DisplayWindow::GetWindowFrame() const +{ + // To do: this needs to include the window manager frame + + double dpiscale = GetDpiScale(); + + Window root = {}; + int x = 0; + int y = 0; + unsigned int width = 0; + unsigned int height = 0; + unsigned int borderwidth = 0; + unsigned int depth = 0; + Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth); + + return Rect::xywh(x / dpiscale, y / dpiscale, width / dpiscale, height / dpiscale); +} + +Size X11DisplayWindow::GetClientSize() const +{ + double dpiscale = GetDpiScale(); + + Window root = {}; + int x = 0; + int y = 0; + unsigned int width = 0; + unsigned int height = 0; + unsigned int borderwidth = 0; + unsigned int depth = 0; + Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth); + + return Size(width / dpiscale, height / dpiscale); +} + +int X11DisplayWindow::GetPixelWidth() const +{ + Window root = {}; + int x = 0; + int y = 0; + unsigned int width = 0; + unsigned int height = 0; + unsigned int borderwidth = 0; + unsigned int depth = 0; + Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth); + return width; +} + +int X11DisplayWindow::GetPixelHeight() const +{ + Window root = {}; + int x = 0; + int y = 0; + unsigned int width = 0; + unsigned int height = 0; + unsigned int borderwidth = 0; + unsigned int depth = 0; + Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth); + return height; +} + +double X11DisplayWindow::GetDpiScale() const +{ + return dpiScale; +} + +void X11DisplayWindow::CreateBackbuffer(int width, int height) +{ + backbuffer.pixels = malloc(width * height * sizeof(uint32_t)); + backbuffer.image = XCreateImage(display, DefaultVisual(display, screen), depth, ZPixmap, 0, (char*)backbuffer.pixels, width, height, 32, 0); + backbuffer.pixmap = XCreatePixmap(display, window, width, height, depth); + backbuffer.width = width; + backbuffer.height = height; +} + +void X11DisplayWindow::DestroyBackbuffer() +{ + if (backbuffer.width > 0 && backbuffer.height > 0) + { + XDestroyImage(backbuffer.image); + XFreePixmap(display, backbuffer.pixmap); + backbuffer.width = 0; + backbuffer.height = 0; + backbuffer.pixmap = None; + backbuffer.image = nullptr; + backbuffer.pixels = nullptr; + } +} + +void X11DisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels) +{ + if (backbuffer.width != width || backbuffer.height != height) + { + DestroyBackbuffer(); + if (width > 0 && height > 0) + CreateBackbuffer(width, height); + } + + if (backbuffer.width == width && backbuffer.height == height) + { + memcpy(backbuffer.pixels, pixels, width * height * sizeof(uint32_t)); + GC gc = XDefaultGC(display, screen); + XPutImage(display, backbuffer.pixmap, gc, backbuffer.image, 0, 0, 0, 0, width, height); + XCopyArea(display, backbuffer.pixmap, window, gc, 0, 0, width, height, BlackPixel(display, screen), WhitePixel(display, screen)); + } +} + +void X11DisplayWindow::SetBorderColor(uint32_t bgra8) +{ +} + +void X11DisplayWindow::SetCaptionColor(uint32_t bgra8) +{ +} + +void X11DisplayWindow::SetCaptionTextColor(uint32_t bgra8) +{ +} + +std::vector X11DisplayWindow::GetWindowProperty(Atom property, Atom &actual_type, int &actual_format, unsigned long &item_count) +{ + long read_bytes = 0; + Atom _actual_type = actual_type; + int _actual_format = actual_format; + unsigned long _item_count = item_count; + unsigned long bytes_remaining = 0; + unsigned char *read_data = nullptr; + do + { + int result = XGetWindowProperty( + display, window, property, 0ul, read_bytes, + False, AnyPropertyType, &actual_type, &actual_format, + &_item_count, &bytes_remaining, &read_data); + if (result != Success) + { + actual_type = None; + actual_format = 0; + item_count = 0; + return {}; + } + } while (bytes_remaining > 0); + + item_count = _item_count; + if (!read_data) + return {}; + std::vector buffer(read_data, read_data + read_bytes); + XFree(read_data); + return buffer; +} + +std::string X11DisplayWindow::GetClipboardText() +{ + Atom clipboard = GetAtom("CLIPBOARD"); + if (clipboard == None) + return {}; + + XConvertSelection(display, clipboard, XA_STRING, clipboard, window, CurrentTime); + XFlush(display); + + // Wait 500 ms for a response + XEvent event = {}; + while (true) + { + if (XCheckTypedWindowEvent(display, window, SelectionNotify, &event)) + break; + if (!WaitForEvents(500)) + return {}; + } + + Atom type = None; + int format = 0; + unsigned long count = 0; + std::vector data = GetWindowProperty(clipboard, type, format, count); + if (type != XA_STRING || format != 8 || count <= 0 || data.empty()) + return {}; + + data.push_back(0); + return (char*)data.data(); +} + +void X11DisplayWindow::SetClipboardText(const std::string& text) +{ + clipboardText = text; + + Atom clipboard = GetAtom("CLIPBOARD"); + if (clipboard == None) + return; + + XSetSelectionOwner(display, XA_PRIMARY, window, CurrentTime); + XSetSelectionOwner(display, clipboard, window, CurrentTime); +} + +Point X11DisplayWindow::MapFromGlobal(const Point& pos) const +{ + double dpiscale = GetDpiScale(); + Window root = XRootWindow(display, screen); + Window child = {}; + int srcx = (int)std::round(pos.x * dpiscale); + int srcy = (int)std::round(pos.y * dpiscale); + int destx = 0; + int desty = 0; + Bool result = XTranslateCoordinates(display, root, window, srcx, srcy, &destx, &desty, &child); + return Point(destx / dpiscale, desty / dpiscale); +} + +Point X11DisplayWindow::MapToGlobal(const Point& pos) const +{ + double dpiscale = GetDpiScale(); + Window root = XRootWindow(display, screen); + Window child = {}; + int srcx = (int)std::round(pos.x * dpiscale); + int srcy = (int)std::round(pos.y * dpiscale); + int destx = 0; + int desty = 0; + Bool result = XTranslateCoordinates(display, window, root, srcx, srcy, &destx, &desty, &child); + return Point(destx / dpiscale, desty / dpiscale); +} + +void* X11DisplayWindow::GetNativeHandle() +{ + return reinterpret_cast(window); +} + +bool X11DisplayWindow::WaitForEvents(int timeout) +{ + Display* display = GetX11Connection()->display; + int fd = XConnectionNumber(display); + + struct timeval tv; + if (timeout > 0) + { + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) / 1000; + } + + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + int result = select(fd + 1, &rfds, nullptr, nullptr, timeout >= 0 ? &tv : nullptr); + return result > 0 && FD_ISSET(fd, &rfds); +} + +void X11DisplayWindow::CheckNeedsUpdate() +{ + for (auto& it : GetX11Connection()->windows) + { + if (it.second->needsUpdate) + { + it.second->needsUpdate = false; + it.second->windowHost->OnWindowPaint(); + } + } +} + +void X11DisplayWindow::ProcessEvents() +{ + CheckNeedsUpdate(); + Display* display = GetX11Connection()->display; + while (XPending(display) > 0) + { + XEvent event = {}; + XNextEvent(display, &event); + DispatchEvent(&event); + } +} + +void X11DisplayWindow::RunLoop() +{ + X11Connection* connection = GetX11Connection(); + connection->ExitRunLoop = false; + while (!connection->ExitRunLoop && !connection->windows.empty()) + { + CheckNeedsUpdate(); + XEvent event = {}; + XNextEvent(connection->display, &event); + DispatchEvent(&event); + } +} + +void X11DisplayWindow::ExitLoop() +{ + X11Connection* connection = GetX11Connection(); + connection->ExitRunLoop = true; +} + +void X11DisplayWindow::DispatchEvent(XEvent* event) +{ + X11Connection* connection = GetX11Connection(); + auto it = connection->windows.find(event->xany.window); + if (it != connection->windows.end()) + { + X11DisplayWindow* window = it->second; + window->OnEvent(event); + } +} + +void X11DisplayWindow::OnEvent(XEvent* event) +{ + if (event->type == ConfigureNotify) + OnConfigureNotify(event); + else if (event->type == ClientMessage) + OnClientMessage(event); + else if (event->type == Expose) + OnExpose(event); + else if (event->type == FocusIn) + OnFocusIn(event); + else if (event->type == FocusOut) + OnFocusOut(event); + else if (event->type == PropertyNotify) + OnPropertyNotify(event); + else if (event->type == KeyPress) + OnKeyPress(event); + else if (event->type == KeyRelease) + OnKeyRelease(event); + else if (event->type == ButtonPress) + OnButtonPress(event); + else if (event->type == ButtonRelease) + OnButtonRelease(event); + else if (event->type == MotionNotify) + OnMotionNotify(event); + else if (event->type == LeaveNotify) + OnLeaveNotify(event); + else if (event->type == SelectionClear) + OnSelectionClear(event); + else if (event->type == SelectionNotify) + OnSelectionNotify(event); + else if (event->type == SelectionRequest) + OnSelectionRequest(event); +} + +void X11DisplayWindow::OnConfigureNotify(XEvent* event) +{ + ClientSizeX = event->xconfigure.width; + ClientSizeY = event->xconfigure.height; + windowHost->OnWindowGeometryChanged(); +} + +void X11DisplayWindow::OnClientMessage(XEvent* event) +{ + Atom protocolsAtom = GetAtom("WM_PROTOCOLS"); + if (protocolsAtom != None && event->xclient.message_type == protocolsAtom) + { + Atom deleteAtom = GetAtom("WM_DELETE_WINDOW"); + Atom pingAtom = GetAtom("_NET_WM_PING"); + + Atom protocol = event->xclient.data.l[0]; + if (deleteAtom != None && protocol == deleteAtom) + { + windowHost->OnWindowClose(); + } + else if (pingAtom != None && protocol == pingAtom) + { + XSendEvent(display, RootWindow(display, screen), False, SubstructureNotifyMask | SubstructureRedirectMask, event); + } + } +} + +void X11DisplayWindow::OnExpose(XEvent* event) +{ + windowHost->OnWindowPaint(); +} + +void X11DisplayWindow::OnFocusIn(XEvent* event) +{ + if (xic) + XSetICFocus(xic); + + windowHost->OnWindowActivated(); +} + +void X11DisplayWindow::OnFocusOut(XEvent* event) +{ + windowHost->OnWindowDeactivated(); +} + +void X11DisplayWindow::OnPropertyNotify(XEvent* event) +{ + // Sent when window is minimized, maximized, etc. +} + +InputKey X11DisplayWindow::GetInputKey(XEvent* event) +{ + if (event->type == KeyPress || event->type == KeyRelease) + { + KeySym keysymbol = XkbKeycodeToKeysym(display, event->xkey.keycode, 0, 0); + switch (keysymbol) + { + case XK_BackSpace: return InputKey::Backspace; + case XK_Tab: return InputKey::Tab; + case XK_Return: return InputKey::Enter; + // To do: should we merge them or not? Windows merges them + case XK_Shift_L: return InputKey::Shift; // InputKey::LShift + case XK_Shift_R: return InputKey::Shift; // InputKey::RShift + case XK_Control_L: return InputKey::Ctrl; // InputKey::LControl + case XK_Control_R: return InputKey::Ctrl; // InputKey::RControl + case XK_Meta_L: return InputKey::Alt; + case XK_Meta_R: return InputKey::Alt; + case XK_Pause: return InputKey::Pause; + case XK_Caps_Lock: return InputKey::CapsLock; + case XK_Escape: return InputKey::Escape; + case XK_space: return InputKey::Space; + case XK_Page_Up: return InputKey::PageUp; + case XK_Page_Down: return InputKey::PageDown; + case XK_End: return InputKey::End; + case XK_Home: return InputKey::Home; + case XK_Left: return InputKey::Left; + case XK_Up: return InputKey::Up; + case XK_Right: return InputKey::Right; + case XK_Down: return InputKey::Down; + case XK_Print: return InputKey::Print; + case XK_Execute: return InputKey::Execute; + // case XK_Print_Screen: return InputKey::PrintScrn; + case XK_Insert: return InputKey::Insert; + case XK_Delete: return InputKey::Delete; + case XK_Help: return InputKey::Help; + case XK_0: return InputKey::_0; + case XK_1: return InputKey::_1; + case XK_2: return InputKey::_2; + case XK_3: return InputKey::_3; + case XK_4: return InputKey::_4; + case XK_5: return InputKey::_5; + case XK_6: return InputKey::_6; + case XK_7: return InputKey::_7; + case XK_8: return InputKey::_8; + case XK_9: return InputKey::_9; + case XK_A: case XK_a: return InputKey::A; + case XK_B: case XK_b: return InputKey::B; + case XK_C: case XK_c: return InputKey::C; + case XK_D: case XK_d: return InputKey::D; + case XK_E: case XK_e: return InputKey::E; + case XK_F: case XK_f: return InputKey::F; + case XK_G: case XK_g: return InputKey::G; + case XK_H: case XK_h: return InputKey::H; + case XK_I: case XK_i: return InputKey::I; + case XK_J: case XK_j: return InputKey::J; + case XK_K: case XK_k: return InputKey::K; + case XK_L: case XK_l: return InputKey::L; + case XK_M: case XK_m: return InputKey::M; + case XK_N: case XK_n: return InputKey::N; + case XK_O: case XK_o: return InputKey::O; + case XK_P: case XK_p: return InputKey::P; + case XK_Q: case XK_q: return InputKey::Q; + case XK_R: case XK_r: return InputKey::R; + case XK_S: case XK_s: return InputKey::S; + case XK_T: case XK_t: return InputKey::T; + case XK_U: case XK_u: return InputKey::U; + case XK_V: case XK_v: return InputKey::V; + case XK_W: case XK_w: return InputKey::W; + case XK_X: case XK_x: return InputKey::X; + case XK_Y: case XK_y: return InputKey::Y; + case XK_Z: case XK_z: return InputKey::Z; + case XK_KP_0: return InputKey::NumPad0; + case XK_KP_1: return InputKey::NumPad1; + case XK_KP_2: return InputKey::NumPad2; + case XK_KP_3: return InputKey::NumPad3; + case XK_KP_4: return InputKey::NumPad4; + case XK_KP_5: return InputKey::NumPad5; + case XK_KP_6: return InputKey::NumPad6; + case XK_KP_7: return InputKey::NumPad7; + case XK_KP_8: return InputKey::NumPad8; + case XK_KP_9: return InputKey::NumPad9; + case XK_KP_Multiply: return InputKey::GreyStar; + case XK_KP_Add: return InputKey::GreyPlus; + case XK_KP_Separator: return InputKey::Separator; + case XK_KP_Subtract: return InputKey::GreyMinus; + case XK_KP_Decimal: return InputKey::NumPadPeriod; + case XK_KP_Divide: return InputKey::GreySlash; + case XK_F1: return InputKey::F1; + case XK_F2: return InputKey::F2; + case XK_F3: return InputKey::F3; + case XK_F4: return InputKey::F4; + case XK_F5: return InputKey::F5; + case XK_F6: return InputKey::F6; + case XK_F7: return InputKey::F7; + case XK_F8: return InputKey::F8; + case XK_F9: return InputKey::F9; + case XK_F10: return InputKey::F10; + case XK_F11: return InputKey::F11; + case XK_F12: return InputKey::F12; + case XK_F13: return InputKey::F13; + case XK_F14: return InputKey::F14; + case XK_F15: return InputKey::F15; + case XK_F16: return InputKey::F16; + case XK_F17: return InputKey::F17; + case XK_F18: return InputKey::F18; + case XK_F19: return InputKey::F19; + case XK_F20: return InputKey::F20; + case XK_F21: return InputKey::F21; + case XK_F22: return InputKey::F22; + case XK_F23: return InputKey::F23; + case XK_F24: return InputKey::F24; + case XK_Num_Lock: return InputKey::NumLock; + case XK_Scroll_Lock: return InputKey::ScrollLock; + case XK_semicolon: return InputKey::Semicolon; + case XK_equal: return InputKey::Equals; + case XK_comma: return InputKey::Comma; + case XK_minus: return InputKey::Minus; + case XK_period: return InputKey::Period; + case XK_slash: return InputKey::Slash; + case XK_dead_tilde: return InputKey::Tilde; + case XK_bracketleft: return InputKey::LeftBracket; + case XK_backslash: return InputKey::Backslash; + case XK_bracketright: return InputKey::RightBracket; + case XK_apostrophe: return InputKey::SingleQuote; + default: return (InputKey)(((uint32_t)keysymbol) << 8); + } + } + else if (event->type == ButtonPress || event->type == ButtonRelease) + { + switch (event->xbutton.button) + { + case 1: return InputKey::LeftMouse; + case 2: return InputKey::MiddleMouse; + case 3: return InputKey::RightMouse; + case 4: return InputKey::MouseWheelUp; + case 5: return InputKey::MouseWheelDown; + // case 6: return InputKey::XButton1; + // case 7: return InputKey::XButton2; + default: break; + } + } + return {}; +} + +Point X11DisplayWindow::GetMousePos(XEvent* event) +{ + double dpiScale = GetDpiScale(); + int x = event->xbutton.x; + int y = event->xbutton.y; + return Point(x / dpiScale, y / dpiScale); +} + +void X11DisplayWindow::OnKeyPress(XEvent* event) +{ + // If we ever want to track keypress repeat: + // char keyboard_state[32]; + // XQueryKeymap(display, keyboard_state); + // unsigned int keycode = event->xkey.keycode; + // bool isrepeat = event->type == KeyPress && keyboard_state[keycode / 8] & (1 << keycode % 8); + + InputKey key = GetInputKey(event); + keyState[key] = true; + windowHost->OnWindowKeyDown(key); + + std::string text; + if (xic) // utf-8 text input + { + Status status = {}; + KeySym keysym = NoSymbol; + char buffer[32] = {}; + Xutf8LookupString(xic, &event->xkey, buffer, sizeof(buffer) - 1, &keysym, &status); + if (status == XLookupChars || status == XLookupBoth) + text = buffer; + } + else // latin-1 input fallback + { + const int buff_size = 16; + char buff[buff_size]; + int result = XLookupString(&event->xkey, buff, buff_size - 1, nullptr, nullptr); + if (result < 0) result = 0; + if (result > (buff_size - 1)) result = buff_size - 1; + buff[result] = 0; + text = std::string(buff, result); + + // Lazy way to convert to utf-8 + for (char& c : text) + { + if (c < 0) + c = '?'; + } + } + + if (!text.empty()) + windowHost->OnWindowKeyChar(std::move(text)); +} + +void X11DisplayWindow::OnKeyRelease(XEvent* event) +{ + InputKey key = GetInputKey(event); + keyState[key] = false; + windowHost->OnWindowKeyUp(key); +} + +void X11DisplayWindow::OnButtonPress(XEvent* event) +{ + InputKey key = GetInputKey(event); + keyState[key] = true; + windowHost->OnWindowMouseDown(GetMousePos(event), key); + // if (lastClickWithin400ms) + // windowHost->OnWindowMouseDoubleclick(GetMousePos(event), InputKey::LeftMouse); +} + +void X11DisplayWindow::OnButtonRelease(XEvent* event) +{ + InputKey key = GetInputKey(event); + keyState[key] = false; + windowHost->OnWindowMouseUp(GetMousePos(event), key); +} + +void X11DisplayWindow::OnMotionNotify(XEvent* event) +{ + double dpiScale = GetDpiScale(); + int x = event->xmotion.x; + int y = event->xmotion.y; + if (isCursorEnabled) + { + windowHost->OnWindowMouseMove(Point(x / dpiScale, y / dpiScale)); + } + else + { + MouseX = ClientSizeX / 2; + MouseY = ClientSizeY / 2; + + if (MouseX != -1 && MouseY != -1) + { + windowHost->OnWindowRawMouseMove(x - MouseX, y - MouseY); + } + + // Warp pointer to the center of the window + XWarpPointer(display, window, window, 0, 0, ClientSizeX, ClientSizeY, ClientSizeX / 2, ClientSizeY / 2); + } + +} + +void X11DisplayWindow::OnLeaveNotify(XEvent* event) +{ + windowHost->OnWindowMouseLeave(); +} + +void X11DisplayWindow::OnSelectionClear(XEvent* event) +{ + clipboardText.clear(); +} + +void X11DisplayWindow::OnSelectionNotify(XEvent* event) +{ + // This is handled in GetClipboardText +} + +void X11DisplayWindow::OnSelectionRequest(XEvent* event) +{ + Atom requestor = event->xselectionrequest.requestor; + if (requestor == window) + return; + + Atom targetsAtom = GetAtom("TARGETS"); + Atom multipleAtom = GetAtom("MULTIPLE"); + + struct Request { Window target; Atom property; }; + std::vector requests; + + if (event->xselectionrequest.target == multipleAtom) + { + Atom actualType = None; + int actualFormat = 0; + unsigned long itemCount = 0; + std::vector data = GetWindowProperty(requestor, actualType, actualFormat, itemCount); + if (data.size() < itemCount * sizeof(Atom)) + return; + + Atom* atoms = (Atom*)data.data(); + for (unsigned long i = 0; i + 1 < itemCount; i += 2) + { + requests.push_back({ atoms[i], atoms[i + 1]}); + } + } + else + { + requests.push_back({ event->xselectionrequest.target, event->xselectionrequest.property }); + } + + for (const Request& request : requests) + { + Window xtarget = request.target; + Atom xproperty = request.property; + + XEvent response = {}; + response.xselection.type = SelectionNotify; + response.xselection.display = event->xselectionrequest.display; + response.xselection.requestor = event->xselectionrequest.requestor; + response.xselection.selection = event->xselectionrequest.selection; + response.xselection.target = event->xselectionrequest.target; + response.xselection.property = xproperty; + response.xselection.time = event->xselectionrequest.time; + + if (xtarget == targetsAtom) + { + Atom newTargets = XA_STRING; + XChangeProperty(display, requestor, xproperty, targetsAtom, 32, PropModeReplace, (unsigned char *)&newTargets, 1); + } + else if (xtarget == XA_STRING) + { + XChangeProperty(display, requestor, xproperty, xtarget, 8, PropModeReplace, (const unsigned char*)clipboardText.c_str(), clipboardText.size()); + } + else + { + response.xselection.property = None; // Is this correct? + } + + XSendEvent(display, requestor, False, 0, &response); + } +} + +Size X11DisplayWindow::GetScreenSize() +{ + X11Connection* connection = GetX11Connection(); + Display* display = connection->display; + int screen = XDefaultScreen(display); + + int disp_width_px = XDisplayWidth(display, screen); + int disp_height_px = XDisplayHeight(display, screen); + int disp_width_mm = XDisplayWidthMM(display, screen); + double ppi = (disp_width_mm < 24) ? 96.0 : (25.4 * static_cast(disp_width_px) / static_cast(disp_width_mm)); + double dpiScale = ppi / 96.0; + + return Size(disp_width_px / dpiScale, disp_height_px / dpiScale); +} + +void* X11DisplayWindow::StartTimer(int timeoutMilliseconds, std::function onTimer) +{ + return nullptr; +} + +void X11DisplayWindow::StopTimer(void* timerID) +{ +} + +// This is to avoid needing all the Vulkan headers and the volk binding library just for this: +#ifndef VK_VERSION_1_0 + +#define VKAPI_CALL +#define VKAPI_PTR VKAPI_CALL + +typedef uint32_t VkFlags; +typedef enum VkStructureType { VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR = 1000004000, VK_OBJECT_TYPE_MAX_ENUM = 0x7FFFFFFF } VkStructureType; +typedef enum VkResult { VK_SUCCESS = 0, VK_RESULT_MAX_ENUM = 0x7FFFFFFF } VkResult; +typedef struct VkAllocationCallbacks VkAllocationCallbacks; + +typedef void (VKAPI_PTR* PFN_vkVoidFunction)(void); +typedef PFN_vkVoidFunction(VKAPI_PTR* PFN_vkGetInstanceProcAddr)(VkInstance instance, const char* pName); + +#ifndef VK_KHR_xlib_surface + +typedef VkFlags VkXlibSurfaceCreateFlagsKHR; +typedef struct VkXlibSurfaceCreateInfoKHR +{ + VkStructureType sType; + const void* pNext; + VkXlibSurfaceCreateFlagsKHR flags; + Display* dpy; + Window window; +} VkXlibSurfaceCreateInfoKHR; + +typedef VkResult(VKAPI_PTR* PFN_vkCreateXlibSurfaceKHR)(VkInstance instance, const VkXlibSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface); + +#endif +#endif + +class ZWidgetX11VulkanLoader +{ +public: + ZWidgetX11VulkanLoader() + { +#if defined(__APPLE__) + module = dlopen("libvulkan.dylib", RTLD_NOW | RTLD_LOCAL); + if (!module) + module = dlopen("libvulkan.1.dylib", RTLD_NOW | RTLD_LOCAL); + if (!module) + module = dlopen("libMoltenVK.dylib", RTLD_NOW | RTLD_LOCAL); +#else + module = dlopen("libvulkan.so.1", RTLD_NOW | RTLD_LOCAL); + if (!module) + module = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL); +#endif + + if (!module) + throw std::runtime_error("Could not load vulkan"); + + vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym(module, "vkGetInstanceProcAddr"); + if (!vkGetInstanceProcAddr) + { + dlclose(module); + throw std::runtime_error("vkGetInstanceProcAddr not found"); + } + } + + ~ZWidgetX11VulkanLoader() + { + dlclose(module); + } + + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr; + void* module = nullptr; +}; + +VkSurfaceKHR X11DisplayWindow::CreateVulkanSurface(VkInstance instance) +{ + static ZWidgetX11VulkanLoader loader; + + auto vkCreateXlibSurfaceKHR = (PFN_vkCreateXlibSurfaceKHR)loader.vkGetInstanceProcAddr(instance, "vkCreateXlibSurfaceKHR"); + if (!vkCreateXlibSurfaceKHR) + throw std::runtime_error("Could not create vulkan surface"); + + VkXlibSurfaceCreateInfoKHR createInfo = { VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR }; + createInfo.dpy = display; + createInfo.window = window; + + VkSurfaceKHR surface = {}; + VkResult result = vkCreateXlibSurfaceKHR(instance, &createInfo, nullptr, &surface); + if (result != VK_SUCCESS) + throw std::runtime_error("Could not create vulkan surface"); + return surface; +} + +std::vector X11DisplayWindow::GetVulkanInstanceExtensions() +{ + return { "VK_KHR_surface", "VK_KHR_xlib_surface" }; +} diff --git a/src/window/x11/x11_display_window.h b/src/window/x11/x11_display_window.h new file mode 100644 index 000000000..62a63f151 --- /dev/null +++ b/src/window/x11/x11_display_window.h @@ -0,0 +1,142 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +class X11DisplayWindow : public DisplayWindow +{ +public: + X11DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, X11DisplayWindow* owner, RenderAPI renderAPI); + ~X11DisplayWindow(); + + void SetWindowTitle(const std::string& text) override; + void SetWindowFrame(const Rect& box) override; + void SetClientFrame(const Rect& box) override; + void Show() override; + void ShowFullscreen() override; + void ShowMaximized() override; + void ShowMinimized() override; + void ShowNormal() override; + bool IsWindowFullscreen() override; + void Hide() override; + void Activate() override; + void ShowCursor(bool enable) override; + void LockCursor() override; + void UnlockCursor() override; + void CaptureMouse() override; + void ReleaseMouseCapture() override; + void Update() override; + bool GetKeyState(InputKey key) override; + + void SetCursor(StandardCursor cursor) override; + + Rect GetWindowFrame() const override; + Size GetClientSize() const override; + int GetPixelWidth() const override; + int GetPixelHeight() const override; + double GetDpiScale() const override; + + void PresentBitmap(int width, int height, const uint32_t* pixels) override; + + void SetBorderColor(uint32_t bgra8) override; + void SetCaptionColor(uint32_t bgra8) override; + void SetCaptionTextColor(uint32_t bgra8) override; + + std::string GetClipboardText() override; + void SetClipboardText(const std::string& text) override; + + Point MapFromGlobal(const Point& pos) const override; + Point MapToGlobal(const Point& pos) const override; + + void* GetNativeHandle() override; + + std::vector GetVulkanInstanceExtensions() override; + VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override; + + static void ProcessEvents(); + static void RunLoop(); + static void ExitLoop(); + static Size GetScreenSize(); + static void* StartTimer(int timeoutMilliseconds, std::function onTimer); + static void StopTimer(void* timerID); + +private: + void UpdateCursor(); + + void OnEvent(XEvent* event); + void OnConfigureNotify(XEvent* event); + void OnClientMessage(XEvent* event); + void OnExpose(XEvent* event); + void OnFocusIn(XEvent* event); + void OnFocusOut(XEvent* event); + void OnPropertyNotify(XEvent* event); + void OnKeyPress(XEvent* event); + void OnKeyRelease(XEvent* event); + void OnButtonPress(XEvent* event); + void OnButtonRelease(XEvent* event); + void OnMotionNotify(XEvent* event); + void OnLeaveNotify(XEvent* event); + void OnSelectionClear(XEvent* event); + void OnSelectionNotify(XEvent* event); + void OnSelectionRequest(XEvent* event); + + void CreateBackbuffer(int width, int height); + void DestroyBackbuffer(); + + InputKey GetInputKey(XEvent* event); + Point GetMousePos(XEvent* event); + + static bool WaitForEvents(int timeout); + static void DispatchEvent(XEvent* event); + + static void CheckNeedsUpdate(); + + std::vector GetWindowProperty(Atom property, Atom &actual_type, int &actual_format, unsigned long &item_count); + + DisplayWindowHost* windowHost = nullptr; + X11DisplayWindow* owner = nullptr; + Display* display = nullptr; + Window window = {}; + int screen = 0; + int depth = 0; + Visual* visual = nullptr; + Colormap colormap = {}; + XIC xic = nullptr; + StandardCursor cursor = {}; + bool isCursorEnabled = true; + bool isMapped = false; + bool isMinimized = false; + bool isFullscreen = false; + double dpiScale = 1.0; + + int ClientSizeX = 0; + int ClientSizeY = 0; + int MouseX = -1; + int MouseY = -1; + + Pixmap cursor_bitmap = None; + Cursor hidden_cursor = None; + + std::map keyState; + + std::string clipboardText; + + struct + { + Pixmap pixmap = None; + XImage* image = nullptr; + void* pixels = nullptr; + int width = 0; + int height = 0; + } backbuffer; + + bool needsUpdate = false; + + friend class X11DisplayBackend; +}; diff --git a/src/window/ztimer/ztimer.cpp b/src/window/ztimer/ztimer.cpp new file mode 100644 index 000000000..23e82d10a --- /dev/null +++ b/src/window/ztimer/ztimer.cpp @@ -0,0 +1,73 @@ +#include "ztimer.h" + +ZTimer::ZTimer() +{ +} + +ZTimer::ZTimer(Duration duration_ms) : m_timerDuration(duration_ms) +{ +} + +ZTimer::ZTimer(Duration duration_ms, CallbackFunc callback) + : m_timerDuration(duration_ms), m_callback(callback) +{ +} + +ZTimer::~ZTimer() +{ + Stop(); +} + +void ZTimer::Start() +{ + m_startTime = Clock::now(); + m_currentTime = m_startTime; + m_timerStarted = true; + m_timerFinished = false; +} + +void ZTimer::Stop() +{ + m_timerStarted = false; +} + +void ZTimer::SetDuration(Duration duration_ms) +{ + if (m_timerStarted) + return; + m_timerDuration = duration_ms; +} + +void ZTimer::SetCallback(std::function callback) +{ + if (m_timerStarted) + return; + m_callback = callback; +} + +void ZTimer::SetRepeating(bool value) +{ + if (m_timerStarted) + return; + m_repeatingTimer = value; +} + +void ZTimer::Update(Duration deltaTime) +{ + if (!m_timerStarted) + return; + + m_currentTime += deltaTime; + + if (m_currentTime >= m_startTime + m_timerDuration) + { + m_callback(); + if (!m_repeatingTimer) + { + Stop(); + m_timerFinished = true; + } + else + Start(); + } +} diff --git a/src/window/ztimer/ztimer.h b/src/window/ztimer/ztimer.h new file mode 100644 index 000000000..e72489884 --- /dev/null +++ b/src/window/ztimer/ztimer.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +// ZTimer: A small, independent timer +// Useful for implementing timed events on your backends +class ZTimer +{ +public: + using Duration = std::chrono::duration; + using Clock = std::chrono::system_clock; + using TimePoint = std::chrono::time_point; + using CallbackFunc = std::function; + + ZTimer(); + ZTimer(Duration duration_ms); + ZTimer(Duration duration_ms, CallbackFunc callback); + + ~ZTimer(); + + void Start(); + void Stop(); + void SetDuration(Duration duration_ms); + void SetCallback(CallbackFunc callback); + void SetRepeating(bool value); + void Update(Duration deltaTime); + + bool IsStarted() { return m_timerStarted; } + bool IsFinished() { return m_timerFinished; } + +private: + bool m_timerStarted = false; + bool m_repeatingTimer = false; + bool m_timerFinished = false; + + TimePoint m_startTime; + TimePoint m_currentTime; + Duration m_timerDuration; + CallbackFunc m_callback; +};