Squashed 'libraries/ZWidget/' content from commit 96501b6ef1
git-subtree-dir: libraries/ZWidget git-subtree-split: 96501b6ef11e8737cd8ccb2451395115b810dfcc
This commit is contained in:
commit
d9b2c00228
118 changed files with 26617 additions and 0 deletions
93
.github/workflows/build.yaml
vendored
Normal file
93
.github/workflows/build.yaml
vendored
Normal file
|
|
@ -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
|
||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# build artifacts
|
||||
/build
|
||||
|
||||
# misc cache/backup/swap files
|
||||
.cache
|
||||
*~
|
||||
305
CMakeLists.txt
Normal file
305
CMakeLists.txt
Normal file
|
|
@ -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$<$<CONFIG:Debug>: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$<$<CONFIG:Debug>:Debug>")
|
||||
endif()
|
||||
endif()
|
||||
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
|
|
@ -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.
|
||||
2
README.md
Normal file
2
README.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# ZWidget
|
||||
A framework for building user interface applications
|
||||
BIN
example/OpenSans.ttf
Normal file
BIN
example/OpenSans.ttf
Normal file
Binary file not shown.
BIN
example/banner.png
Normal file
BIN
example/banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 352 KiB |
283
example/example.cpp
Normal file
283
example/example.cpp
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
#include <zwidget/core/widget.h>
|
||||
#include <zwidget/core/resourcedata.h>
|
||||
#include <zwidget/core/image.h>
|
||||
#include <zwidget/core/theme.h>
|
||||
#include <zwidget/window/window.h>
|
||||
#include <zwidget/widgets/textedit/textedit.h>
|
||||
#include <zwidget/widgets/mainwindow/mainwindow.h>
|
||||
#include <zwidget/widgets/listview/listview.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 "picopng.h"
|
||||
|
||||
static std::vector<uint8_t> 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<unsigned char> pixels;
|
||||
unsigned long width = 0, height = 0;
|
||||
int result = decodePNG(pixels, width, height, (const unsigned char*)filedata.data(), filedata.size(), true);
|
||||
if (result == 0)
|
||||
{
|
||||
Logo->SetImage(Image::Create(width, height, ImageFormat::R8G8B8A8, pixels.data()));
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void OnGeometryChanged() override
|
||||
{
|
||||
double y = 0.0;
|
||||
|
||||
Logo->SetFrameGeometry(0.0, y, GetWidth(), Logo->GetPreferredHeight());
|
||||
y += Logo->GetPreferredHeight();
|
||||
|
||||
y += 10.0;
|
||||
|
||||
WelcomeLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0, WelcomeLabel->GetPreferredHeight());
|
||||
y += WelcomeLabel->GetPreferredHeight();
|
||||
|
||||
VersionLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0, VersionLabel->GetPreferredHeight());
|
||||
y += VersionLabel->GetPreferredHeight();
|
||||
|
||||
y += 10.0;
|
||||
|
||||
SelectLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0, 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<uint8_t> ReadAllBytes(const std::string& filename);
|
||||
|
||||
std::vector<SingleFontData> LoadWidgetFontData(const std::string& name)
|
||||
{
|
||||
return {
|
||||
{std::move(ReadAllBytes("OpenSans.ttf")), ""}
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> LoadWidgetData(const std::string& name)
|
||||
{
|
||||
return ReadAllBytes(name);
|
||||
}
|
||||
|
||||
int example()
|
||||
{
|
||||
WidgetTheme::SetTheme(std::make_unique<DarkWidgetTheme>());
|
||||
|
||||
// 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 <Windows.h>
|
||||
#include <stdexcept>
|
||||
|
||||
#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<uint8_t> 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<uint8_t> 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 <fstream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
static std::vector<uint8_t> 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<uint8_t> buffer(size);
|
||||
if (!file.read(reinterpret_cast<char*>(buffer.data()), size))
|
||||
throw std::runtime_error("ReadFile failed ");
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
example();
|
||||
}
|
||||
|
||||
#endif
|
||||
593
example/picopng.cpp
Normal file
593
example/picopng.cpp
Normal file
|
|
@ -0,0 +1,593 @@
|
|||
#include <vector>
|
||||
|
||||
/*
|
||||
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<unsigned char>& 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<unsigned long>& bitlen, unsigned long maxbitlen)
|
||||
{ //make tree given the lengths
|
||||
unsigned long numcodes = (unsigned long)(bitlen.size()), treepos = 0, nodefilled = 0;
|
||||
std::vector<unsigned long> 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<unsigned long> 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<unsigned char>& out, const std::vector<unsigned char>& 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<unsigned long> 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<unsigned long> 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<unsigned long> 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<unsigned char>& 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<unsigned char>& 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<unsigned char>& out, const std::vector<unsigned char>& 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<unsigned char> palette;
|
||||
} info;
|
||||
int error;
|
||||
void decode(std::vector<unsigned char>& 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<unsigned char> 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<unsigned char> 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<unsigned char> 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<unsigned char> 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<unsigned char> 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<unsigned char>& 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 <iostream>
|
||||
#include <fstream>
|
||||
|
||||
void loadFile(std::vector<unsigned char>& 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<unsigned char> 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
|
||||
5
example/picopng.h
Normal file
5
example/picopng.h
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
int decodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32 = true);
|
||||
116
include/zwidget/core/canvas.h
Normal file
116
include/zwidget/core/canvas.h
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include "image.h"
|
||||
#include "rect.h"
|
||||
#include <vector>
|
||||
|
||||
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<Canvas> create();
|
||||
|
||||
Canvas();
|
||||
virtual ~Canvas();
|
||||
|
||||
virtual void attach(DisplayWindow* window);
|
||||
virtual void detach();
|
||||
|
||||
virtual void begin(const Colorf& color);
|
||||
virtual void end() { }
|
||||
|
||||
virtual void begin3d() { }
|
||||
virtual void end3d() { }
|
||||
|
||||
Point getOrigin();
|
||||
void setOrigin(const Point& origin);
|
||||
|
||||
void pushClip(const Rect& box);
|
||||
void popClip();
|
||||
|
||||
void fillRect(const Rect& box, const Colorf& color);
|
||||
void line(const Point& p0, const Point& p1, const Colorf& color);
|
||||
|
||||
void drawText(const Point& pos, const Colorf& color, const std::string& text);
|
||||
Rect measureText(const std::string& text);
|
||||
VerticalTextPosition verticalTextAlign();
|
||||
|
||||
void drawText(const std::shared_ptr<Font>& font, const Point& pos, const std::string& text, const Colorf& color);
|
||||
void drawTextEllipsis(const std::shared_ptr<Font>& font, const Point& pos, const Rect& clipBox, const std::string& text, const Colorf& color);
|
||||
Rect measureText(const std::shared_ptr<Font>& font, const std::string& text);
|
||||
FontMetrics getFontMetrics(const std::shared_ptr<Font>& font);
|
||||
int getCharacterIndex(const std::shared_ptr<Font>& font, const std::string& text, const Point& hitPoint);
|
||||
|
||||
void drawImage(const std::shared_ptr<Image>& image, const Point& pos);
|
||||
void drawImage(const std::shared_ptr<Image>& image, const Rect& box);
|
||||
|
||||
protected:
|
||||
virtual std::unique_ptr<CanvasTexture> createTexture(int width, int height, const void* pixels, ImageFormat format = ImageFormat::B8G8R8A8) = 0;
|
||||
virtual void drawLineAntialiased(float x0, float y0, float x1, float y1, Colorf color) = 0;
|
||||
virtual void fillTile(float x, float y, float width, float height, Colorf color) = 0;
|
||||
virtual void drawTile(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) = 0;
|
||||
virtual void drawGlyph(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) = 0;
|
||||
|
||||
int getClipMinX() const;
|
||||
int getClipMinY() const;
|
||||
int getClipMaxX() const;
|
||||
int getClipMaxY() const;
|
||||
|
||||
template<typename T>
|
||||
static T clamp(T val, T minval, T maxval) { return std::max<T>(std::min<T>(val, maxval), minval); }
|
||||
|
||||
DisplayWindow* window = nullptr;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
double uiscale = 1.0f;
|
||||
|
||||
std::unique_ptr<CanvasTexture> whiteTexture;
|
||||
|
||||
private:
|
||||
void setLanguage(const char* lang) { language = lang; }
|
||||
void drawLineUnclipped(const Point& p0, const Point& p1, const Colorf& color);
|
||||
|
||||
std::unique_ptr<CanvasFontGroup> font;
|
||||
|
||||
Point origin;
|
||||
std::vector<Rect> clipStack;
|
||||
|
||||
std::unordered_map<std::shared_ptr<Image>, std::unique_ptr<CanvasTexture>> imageTextures;
|
||||
std::string language;
|
||||
|
||||
friend class CanvasFont;
|
||||
};
|
||||
36
include/zwidget/core/colorf.h
Normal file
36
include/zwidget/core/colorf.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
|
||||
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;
|
||||
};
|
||||
15
include/zwidget/core/font.h
Normal file
15
include/zwidget/core/font.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class Font
|
||||
{
|
||||
public:
|
||||
virtual ~Font() = default;
|
||||
|
||||
virtual const std::string& GetName() const = 0;
|
||||
virtual double GetHeight() const = 0;
|
||||
|
||||
static std::shared_ptr<Font> Create(const std::string& name, double height);
|
||||
};
|
||||
24
include/zwidget/core/image.h
Normal file
24
include/zwidget/core/image.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
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<Image> Create(int width, int height, ImageFormat format, const void* data);
|
||||
static std::shared_ptr<Image> LoadResource(const std::string& resourcename, double dpiscale = 1.0);
|
||||
};
|
||||
96
include/zwidget/core/pathfill.h
Normal file
96
include/zwidget/core/pathfill.h
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#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<Point> points;
|
||||
std::vector<PathFillCommand> commands;
|
||||
bool closed = false;
|
||||
};
|
||||
|
||||
class PathFillDesc
|
||||
{
|
||||
public:
|
||||
PathFillDesc() : subpaths(1) { }
|
||||
|
||||
PathFillMode fill_mode = PathFillMode::alternate;
|
||||
std::vector<PathFillSubpath> 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);
|
||||
};
|
||||
85
include/zwidget/core/rect.h
Normal file
85
include/zwidget/core/rect.h
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
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); }
|
||||
14
include/zwidget/core/resourcedata.h
Normal file
14
include/zwidget/core/resourcedata.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
struct SingleFontData
|
||||
{
|
||||
std::vector<uint8_t> fontdata;
|
||||
std::string language;
|
||||
};
|
||||
|
||||
std::vector<SingleFontData> LoadWidgetFontData(const std::string& name);
|
||||
std::vector<uint8_t> LoadWidgetData(const std::string& name);
|
||||
238
include/zwidget/core/span_layout.h
Normal file
238
include/zwidget/core/span_layout.h
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#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> font, const Colorf& color = Colorf(), int id = -1);
|
||||
void AddImage(const std::shared_ptr<Image> 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<Rect> 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> font;
|
||||
Colorf color;
|
||||
size_t start = 0, end = 0;
|
||||
|
||||
std::shared_ptr<Image> image;
|
||||
Widget* component = nullptr;
|
||||
double baseline_offset = 0;
|
||||
|
||||
int id = -1;
|
||||
};
|
||||
|
||||
struct LineSegment
|
||||
{
|
||||
ObjectType type = object_text;
|
||||
|
||||
std::shared_ptr<Font> 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> 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<LineSegment> 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<LineSegment> segments;
|
||||
};
|
||||
|
||||
struct CurrentLine
|
||||
{
|
||||
std::vector<SpanObject>::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> image;
|
||||
Widget* component = nullptr;
|
||||
int id = -1;
|
||||
};
|
||||
|
||||
TextSizeResult FindTextSize(Canvas* canvas, const TextBlock& block, size_t object_index);
|
||||
std::vector<TextBlock> FindTextBlocks();
|
||||
void LayoutLines(Canvas* canvas, double max_width);
|
||||
void LayoutText(Canvas* canvas, std::vector<TextBlock> blocks, std::vector<TextBlock>::size_type block_index, CurrentLine& current_line, double max_width);
|
||||
void LayoutBlock(CurrentLine& current_line, double max_width, std::vector<TextBlock>& blocks, std::vector<TextBlock>::size_type block_index);
|
||||
void LayoutFloatBlock(CurrentLine& current_line, double max_width);
|
||||
void LayoutInlineBlock(CurrentLine& current_line, double max_width, std::vector<TextBlock>& blocks, std::vector<TextBlock>::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<FloatBox>& 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<SpanObject> objects;
|
||||
std::vector<Line> lines;
|
||||
Point position;
|
||||
|
||||
std::vector<FloatBox> 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<typename T>
|
||||
static T clamp(T val, T minval, T maxval) { return std::max<T>(std::min<T>(val, maxval), minval); }
|
||||
};
|
||||
81
include/zwidget/core/theme.h
Normal file
81
include/zwidget/core/theme.h
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <unordered_map>
|
||||
#include "rect.h"
|
||||
#include "colorf.h"
|
||||
|
||||
class Widget;
|
||||
class Canvas;
|
||||
|
||||
class WidgetStyle
|
||||
{
|
||||
public:
|
||||
WidgetStyle(WidgetStyle* parentStyle = nullptr) : ParentStyle(parentStyle) { }
|
||||
virtual ~WidgetStyle() = default;
|
||||
virtual void Paint(Widget* widget, Canvas* canvas, Size size) = 0;
|
||||
|
||||
void SetBool(const std::string& state, const std::string& propertyName, bool value);
|
||||
void SetInt(const std::string& state, const std::string& propertyName, int value);
|
||||
void SetDouble(const std::string& state, const std::string& propertyName, double value);
|
||||
void SetString(const std::string& state, const std::string& propertyName, const std::string& value);
|
||||
void SetColor(const std::string& state, const std::string& propertyName, const Colorf& value);
|
||||
|
||||
void SetBool(const std::string& propertyName, bool value) { SetBool(std::string(), propertyName, value); }
|
||||
void SetInt(const std::string& propertyName, int value) { SetInt(std::string(), propertyName, value); }
|
||||
void SetDouble(const std::string& propertyName, double value) { SetDouble(std::string(), propertyName, value); }
|
||||
void SetString(const std::string& propertyName, const std::string& value) { SetString(std::string(), propertyName, value); }
|
||||
void SetColor(const std::string& propertyName, const Colorf& value) { SetColor(std::string(), propertyName, value); }
|
||||
|
||||
private:
|
||||
// Note: do not call these directly. Use widget->GetStyleXX instead since a widget may explicitly override a class style
|
||||
bool GetBool(const std::string& state, const std::string& propertyName) const;
|
||||
int GetInt(const std::string& state, const std::string& propertyName) const;
|
||||
double GetDouble(const std::string& state, const std::string& propertyName) const;
|
||||
std::string GetString(const std::string& state, const std::string& propertyName) const;
|
||||
Colorf GetColor(const std::string& state, const std::string& propertyName) const;
|
||||
|
||||
WidgetStyle* ParentStyle = nullptr;
|
||||
typedef std::variant<bool, int, double, std::string, Colorf> PropertyVariant;
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, PropertyVariant>> StyleProperties;
|
||||
|
||||
const PropertyVariant* FindProperty(const std::string& state, const std::string& propertyName) const;
|
||||
|
||||
friend class Widget;
|
||||
};
|
||||
|
||||
class BasicWidgetStyle : public WidgetStyle
|
||||
{
|
||||
public:
|
||||
using WidgetStyle::WidgetStyle;
|
||||
void Paint(Widget* widget, Canvas* canvas, Size size) override;
|
||||
};
|
||||
|
||||
class WidgetTheme
|
||||
{
|
||||
public:
|
||||
virtual ~WidgetTheme() = default;
|
||||
|
||||
WidgetStyle* RegisterStyle(std::unique_ptr<WidgetStyle> widgetStyle, const std::string& widgetClass);
|
||||
WidgetStyle* GetStyle(const std::string& widgetClass);
|
||||
|
||||
static void SetTheme(std::unique_ptr<WidgetTheme> theme);
|
||||
static WidgetTheme* GetTheme();
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, std::unique_ptr<WidgetStyle>> Styles;
|
||||
};
|
||||
|
||||
class DarkWidgetTheme : public WidgetTheme
|
||||
{
|
||||
public:
|
||||
DarkWidgetTheme();
|
||||
};
|
||||
|
||||
class LightWidgetTheme : public WidgetTheme
|
||||
{
|
||||
public:
|
||||
LightWidgetTheme();
|
||||
};
|
||||
26
include/zwidget/core/timer.h
Normal file
26
include/zwidget/core/timer.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
class Widget;
|
||||
|
||||
class Timer
|
||||
{
|
||||
public:
|
||||
Timer(Widget* owner);
|
||||
~Timer();
|
||||
|
||||
void Start(int timeoutMilliseconds, bool repeat = true);
|
||||
void Stop();
|
||||
|
||||
std::function<void()> FuncExpired;
|
||||
|
||||
private:
|
||||
Widget* OwnerObj = nullptr;
|
||||
Timer* PrevTimerObj = nullptr;
|
||||
Timer* NextTimerObj = nullptr;
|
||||
|
||||
void* TimerId = nullptr;
|
||||
|
||||
friend class Widget;
|
||||
};
|
||||
78
include/zwidget/core/utf8reader.h
Normal file
78
include/zwidget/core/utf8reader.h
Normal file
|
|
@ -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 <string>
|
||||
|
||||
/// \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;
|
||||
};
|
||||
241
include/zwidget/core/widget.h
Normal file
241
include/zwidget/core/widget.h
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
#include <unordered_map>
|
||||
#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> canvas);
|
||||
void* GetNativeHandle();
|
||||
int GetNativePixelWidth();
|
||||
int GetNativePixelHeight();
|
||||
|
||||
// Vulkan support:
|
||||
std::vector<std::string> GetVulkanInstanceExtensions() { return Window()->DispWindow->GetVulkanInstanceExtensions(); }
|
||||
VkSurfaceKHR CreateVulkanSurface(VkInstance instance) { return Window()->DispWindow->CreateVulkanSurface(instance); }
|
||||
|
||||
protected:
|
||||
virtual void OnPaintFrame(Canvas* canvas);
|
||||
virtual void 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<DisplayWindow> DispWindow;
|
||||
std::unique_ptr<Canvas> 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<bool, int, double, std::string, Colorf> PropertyVariant;
|
||||
std::unordered_map<std::string, PropertyVariant> StyleProperties;
|
||||
|
||||
Widget(const Widget&) = delete;
|
||||
Widget& operator=(const Widget&) = delete;
|
||||
|
||||
friend class Timer;
|
||||
friend class OpenFileDialog;
|
||||
friend class OpenFolderDialog;
|
||||
friend class SaveFileDialog;
|
||||
};
|
||||
55
include/zwidget/systemdialogs/open_file_dialog.h
Normal file
55
include/zwidget/systemdialogs/open_file_dialog.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class Widget;
|
||||
|
||||
/// \brief Displays the system open file dialog.
|
||||
class OpenFileDialog
|
||||
{
|
||||
public:
|
||||
/// \brief Constructs an open file dialog.
|
||||
static std::unique_ptr<OpenFileDialog> Create(Widget* owner);
|
||||
|
||||
virtual ~OpenFileDialog() = default;
|
||||
|
||||
/// \brief Get the full path of the file selected.
|
||||
///
|
||||
/// If multiple files are selected, this returns the first file.
|
||||
virtual std::string Filename() const = 0;
|
||||
|
||||
/// \brief Gets an array that contains one file name for each selected file.
|
||||
virtual std::vector<std::string> Filenames() const = 0;
|
||||
|
||||
/// \brief Sets if multiple files can be selected or not.
|
||||
/// \param multiselect = When true, multiple items can be selected.
|
||||
virtual void SetMultiSelect(bool multiselect) = 0;
|
||||
|
||||
/// \brief Sets a string containing the full path of the file selected.
|
||||
virtual void SetFilename(const std::string &filename) = 0;
|
||||
|
||||
/// \brief Sets the default extension to use.
|
||||
virtual void SetDefaultExtension(const std::string& extension) = 0;
|
||||
|
||||
/// \brief Add a filter that determines what types of files are displayed.
|
||||
virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0;
|
||||
|
||||
/// \brief Clears all filters.
|
||||
virtual void ClearFilters() = 0;
|
||||
|
||||
/// \brief Sets a default filter, on a 0-based index.
|
||||
virtual void SetFilterIndex(int filter_index) = 0;
|
||||
|
||||
/// \brief Sets the initial directory that is displayed.
|
||||
virtual void SetInitialDirectory(const std::string &path) = 0;
|
||||
|
||||
/// \brief Sets the text that appears in the title bar.
|
||||
virtual void SetTitle(const std::string &title) = 0;
|
||||
|
||||
/// \brief Shows the file dialog.
|
||||
/// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise.
|
||||
virtual bool Show() = 0;
|
||||
};
|
||||
30
include/zwidget/systemdialogs/open_folder_dialog.h
Normal file
30
include/zwidget/systemdialogs/open_folder_dialog.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class Widget;
|
||||
|
||||
/// \brief Displays the system folder browsing dialog
|
||||
class OpenFolderDialog
|
||||
{
|
||||
public:
|
||||
/// \brief Constructs an browse folder dialog.
|
||||
static std::unique_ptr<OpenFolderDialog> Create(Widget*owner);
|
||||
|
||||
virtual ~OpenFolderDialog() = default;
|
||||
|
||||
/// \brief Get the full path of the directory selected.
|
||||
virtual std::string SelectedPath() const = 0;
|
||||
|
||||
/// \brief Sets the initial directory that is displayed.
|
||||
virtual void SetInitialDirectory(const std::string& path) = 0;
|
||||
|
||||
/// \brief Sets the text that appears in the title bar.
|
||||
virtual void SetTitle(const std::string& title) = 0;
|
||||
|
||||
/// \brief Shows the file dialog.
|
||||
/// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise.
|
||||
virtual bool Show() = 0;
|
||||
};
|
||||
46
include/zwidget/systemdialogs/save_file_dialog.h
Normal file
46
include/zwidget/systemdialogs/save_file_dialog.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class Widget;
|
||||
|
||||
/// \brief Displays the system save file dialog.
|
||||
class SaveFileDialog
|
||||
{
|
||||
public:
|
||||
/// \brief Constructs a save file dialog.
|
||||
static std::unique_ptr<SaveFileDialog> Create(Widget *owner);
|
||||
|
||||
virtual ~SaveFileDialog() = default;
|
||||
|
||||
/// \brief Get the full path of the file selected.
|
||||
virtual std::string Filename() const = 0;
|
||||
|
||||
/// \brief Sets a string containing the full path of the file selected.
|
||||
virtual void SetFilename(const std::string &filename) = 0;
|
||||
|
||||
/// \brief Sets the default extension to use.
|
||||
virtual void SetDefaultExtension(const std::string& extension) = 0;
|
||||
|
||||
/// \brief Add a filter that determines what types of files are displayed.
|
||||
virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0;
|
||||
|
||||
/// \brief Clears all filters.
|
||||
virtual void ClearFilters() = 0;
|
||||
|
||||
/// \brief Sets a default filter, on a 0-based index.
|
||||
virtual void SetFilterIndex(int filter_index) = 0;
|
||||
|
||||
/// \brief Sets the initial directory that is displayed.
|
||||
virtual void SetInitialDirectory(const std::string &path) = 0;
|
||||
|
||||
/// \brief Sets the text that appears in the title bar.
|
||||
virtual void SetTitle(const std::string &title) = 0;
|
||||
|
||||
/// \brief Shows the file dialog.
|
||||
/// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise.
|
||||
virtual bool Show() = 0;
|
||||
};
|
||||
34
include/zwidget/widgets/checkboxlabel/checkboxlabel.h
Normal file
34
include/zwidget/widgets/checkboxlabel/checkboxlabel.h
Normal file
|
|
@ -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<void(bool)> 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;
|
||||
};
|
||||
31
include/zwidget/widgets/imagebox/imagebox.h
Normal file
31
include/zwidget/widgets/imagebox/imagebox.h
Normal file
|
|
@ -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<Image> newImage);
|
||||
void SetImageMode(ImageBoxMode mode);
|
||||
|
||||
double GetPreferredWidth() const;
|
||||
double GetPreferredHeight() const;
|
||||
|
||||
protected:
|
||||
void OnPaint(Canvas* canvas) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<Image> image;
|
||||
ImageBoxMode mode = ImageBoxMode::Center;
|
||||
};
|
||||
158
include/zwidget/widgets/lineedit/lineedit.h
Normal file
158
include/zwidget/widgets/lineedit/lineedit.h
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "../../core/widget.h"
|
||||
#include "../../core/timer.h"
|
||||
#include <functional>
|
||||
|
||||
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<bool(InputKey key)> FuncIgnoreKeyDown;
|
||||
std::function<std::string(std::string text)> FuncFilterKeyChar;
|
||||
std::function<void()> FuncBeforeEditChanged;
|
||||
std::function<void()> FuncAfterEditChanged;
|
||||
std::function<void()> FuncSelectionChanged;
|
||||
std::function<void()> FuncFocusGained;
|
||||
std::function<void()> FuncFocusLost;
|
||||
std::function<void()> 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;
|
||||
};
|
||||
46
include/zwidget/widgets/listview/listview.h
Normal file
46
include/zwidget/widgets/listview/listview.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "../../core/widget.h"
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
class Scrollbar;
|
||||
|
||||
class ListView : public Widget
|
||||
{
|
||||
public:
|
||||
ListView(Widget* parent = nullptr);
|
||||
|
||||
void SetColumnWidths(const std::vector<double>& 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<void(int)> OnChanged;
|
||||
std::function<void()> 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<std::vector<std::string>> items;
|
||||
std::vector<double> columnwidths;
|
||||
int selectedItem = 0;
|
||||
};
|
||||
33
include/zwidget/widgets/mainwindow/mainwindow.h
Normal file
33
include/zwidget/widgets/mainwindow/mainwindow.h
Normal file
|
|
@ -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;
|
||||
};
|
||||
121
include/zwidget/widgets/menubar/menubar.h
Normal file
121
include/zwidget/widgets/menubar/menubar.h
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "../../core/widget.h"
|
||||
#include "../textlabel/textlabel.h"
|
||||
#include "../imagebox/imagebox.h"
|
||||
#include <vector>
|
||||
|
||||
class Menu;
|
||||
class MenubarItem;
|
||||
class MenuItem;
|
||||
class MenuItemSeparator;
|
||||
|
||||
class Menubar : public Widget
|
||||
{
|
||||
public:
|
||||
Menubar(Widget* parent);
|
||||
~Menubar();
|
||||
|
||||
MenubarItem* AddItem(std::string text, std::function<void(Menu* menu)> onOpen, bool alignRight = false);
|
||||
|
||||
protected:
|
||||
void OnGeometryChanged() override;
|
||||
bool OnMouseDown(const Point& pos, InputKey key) override;
|
||||
bool OnMouseUp(const Point& pos, InputKey key) override;
|
||||
void OnMouseMove(const Point& pos) override;
|
||||
void OnKeyDown(InputKey key) override;
|
||||
|
||||
private:
|
||||
void ShowMenu(MenubarItem* item);
|
||||
void CloseMenu();
|
||||
|
||||
MenubarItem* GetMenubarItemAt(const Point& pos);
|
||||
int GetItemIndex(MenubarItem* item);
|
||||
|
||||
std::vector<MenubarItem*> menuItems;
|
||||
int currentMenubarItem = -1;
|
||||
Menu* openMenu = nullptr;
|
||||
bool modalMode = false;
|
||||
|
||||
friend class MenubarItem;
|
||||
};
|
||||
|
||||
class MenubarItem : public Widget
|
||||
{
|
||||
public:
|
||||
MenubarItem(Menubar* menubar, std::string text, bool alignRight);
|
||||
|
||||
void SetOpenCallback(std::function<void(Menu* menu)> callback) { onOpen = std::move(callback); }
|
||||
const std::function<void(Menu* menu)>& GetOpenCallback() const { return onOpen; }
|
||||
|
||||
double GetPreferredWidth() const;
|
||||
|
||||
bool AlignRight = false;
|
||||
|
||||
protected:
|
||||
void OnPaint(Canvas* canvas) override;
|
||||
bool OnMouseDown(const Point& pos, InputKey key) override;
|
||||
bool OnMouseUp(const Point& pos, InputKey key) override;
|
||||
void OnMouseMove(const Point& pos) override;
|
||||
void OnMouseLeave() override;
|
||||
|
||||
private:
|
||||
Menubar* menubar = nullptr;
|
||||
std::function<void(Menu* menu)> onOpen;
|
||||
std::string text;
|
||||
};
|
||||
|
||||
class Menu : public Widget
|
||||
{
|
||||
public:
|
||||
Menu(Widget* parent);
|
||||
|
||||
void SetLeftPosition(const Point& globalPos);
|
||||
void SetRightPosition(const Point& globalPos);
|
||||
MenuItem* AddItem(std::shared_ptr<Image> icon, std::string text, std::function<void()> onClick = {});
|
||||
MenuItemSeparator* AddSeparator();
|
||||
|
||||
double GetPreferredWidth() const;
|
||||
double GetPreferredHeight() const;
|
||||
|
||||
void SetSelected(MenuItem* item);
|
||||
|
||||
protected:
|
||||
void OnGeometryChanged() override;
|
||||
|
||||
std::function<void()> onCloseMenu;
|
||||
MenuItem* selectedItem = nullptr;
|
||||
|
||||
friend class Menubar;
|
||||
};
|
||||
|
||||
class MenuItem : public Widget
|
||||
{
|
||||
public:
|
||||
MenuItem(Menu* menu, std::function<void()> onClick);
|
||||
|
||||
void Click();
|
||||
|
||||
Menu* menu = nullptr;
|
||||
ImageBox* icon = nullptr;
|
||||
TextLabel* text = nullptr;
|
||||
|
||||
protected:
|
||||
bool OnMouseUp(const Point& pos, InputKey key) override;
|
||||
void OnMouseMove(const Point& pos) override;
|
||||
void OnMouseLeave() override;
|
||||
void OnGeometryChanged() override;
|
||||
|
||||
private:
|
||||
std::function<void()> onClick;
|
||||
};
|
||||
|
||||
class MenuItemSeparator : public Widget
|
||||
{
|
||||
public:
|
||||
MenuItemSeparator(Widget* parent);
|
||||
|
||||
protected:
|
||||
void OnPaint(Canvas* canvas) override;
|
||||
};
|
||||
32
include/zwidget/widgets/pushbutton/pushbutton.h
Normal file
32
include/zwidget/widgets/pushbutton/pushbutton.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "../../core/widget.h"
|
||||
#include <functional>
|
||||
|
||||
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<void()> 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;
|
||||
};
|
||||
99
include/zwidget/widgets/scrollbar/scrollbar.h
Normal file
99
include/zwidget/widgets/scrollbar/scrollbar.h
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "../../core/widget.h"
|
||||
#include "../../core/timer.h"
|
||||
#include <functional>
|
||||
|
||||
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<void()> FuncScroll;
|
||||
std::function<void()> FuncScrollMin;
|
||||
std::function<void()> FuncScrollMax;
|
||||
std::function<void()> FuncScrollLineDecrement;
|
||||
std::function<void()> FuncScrollLineIncrement;
|
||||
std::function<void()> FuncScrollPageDecrement;
|
||||
std::function<void()> FuncScrollPageIncrement;
|
||||
std::function<void()> FuncScrollThumbRelease;
|
||||
std::function<void()> FuncScrollThumbTrack;
|
||||
std::function<void()> 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<void()>* 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<void()>* FuncScrollOnMouseDown = nullptr;
|
||||
};
|
||||
19
include/zwidget/widgets/statusbar/statusbar.h
Normal file
19
include/zwidget/widgets/statusbar/statusbar.h
Normal file
|
|
@ -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;
|
||||
};
|
||||
129
include/zwidget/widgets/tabwidget/tabwidget.h
Normal file
129
include/zwidget/widgets/tabwidget/tabwidget.h
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "../../core/widget.h"
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<Image>& 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<Image>& icon);
|
||||
void SetTabIcon(Widget* page, const std::shared_ptr<Image>& icon);
|
||||
|
||||
int GetCurrentIndex() const;
|
||||
Widget* GetCurrentWidget() const;
|
||||
|
||||
int GetPageIndex(Widget* pageWidget) const;
|
||||
|
||||
void SetCurrentIndex(int pageIndex);
|
||||
void SetCurrentWidget(Widget* pageWidget);
|
||||
|
||||
std::function<void()> OnCurrentChanged;
|
||||
|
||||
protected:
|
||||
void OnGeometryChanged() override;
|
||||
|
||||
private:
|
||||
void OnBarCurrentChanged();
|
||||
|
||||
TabBar* Bar = nullptr;
|
||||
TabWidgetStack* PageStack = nullptr;
|
||||
std::vector<Widget*> Pages;
|
||||
};
|
||||
|
||||
class TabBar : public Widget
|
||||
{
|
||||
public:
|
||||
TabBar(Widget* parent);
|
||||
|
||||
int AddTab(const std::string& label);
|
||||
int AddTab(const std::shared_ptr<Image>& icon, const std::string& label);
|
||||
|
||||
void SetTabText(int index, const std::string& text);
|
||||
void SetTabIcon(int index, const std::shared_ptr<Image>& icon);
|
||||
|
||||
int GetCurrentIndex() const;
|
||||
void SetCurrentIndex(int pageIndex);
|
||||
|
||||
double GetPreferredHeight() const { return 30.0; }
|
||||
|
||||
std::function<void()> OnCurrentChanged;
|
||||
|
||||
protected:
|
||||
void OnGeometryChanged() override;
|
||||
|
||||
private:
|
||||
void OnTabClicked(TabBarTab* tab);
|
||||
int GetTabIndex(TabBarTab* tab);
|
||||
|
||||
int CurrentIndex = -1;
|
||||
std::vector<TabBarTab*> 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<Image>& icon);
|
||||
void SetCurrent(bool value);
|
||||
|
||||
double GetPreferredWidth() const;
|
||||
|
||||
std::function<void()> 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;
|
||||
};
|
||||
155
include/zwidget/widgets/textedit/textedit.h
Normal file
155
include/zwidget/widgets/textedit/textedit.h
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "../../core/widget.h"
|
||||
#include "../../core/timer.h"
|
||||
#include "../../core/span_layout.h"
|
||||
#include "../../core/font.h"
|
||||
#include <functional>
|
||||
|
||||
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<std::string(std::string text)> FuncFilterKeyChar;
|
||||
std::function<void()> FuncBeforeEditChanged;
|
||||
std::function<void()> FuncAfterEditChanged;
|
||||
std::function<void()> FuncSelectionChanged;
|
||||
std::function<void()> FuncFocusGained;
|
||||
std::function<void()> FuncFocusLost;
|
||||
std::function<void()> 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<Line> 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 = Font::Create("NotoSans", 12.0);
|
||||
|
||||
template<typename T>
|
||||
static T clamp(T val, T minval, T maxval) { return std::max<T>(std::min<T>(val, maxval), minval); }
|
||||
};
|
||||
33
include/zwidget/widgets/textlabel/textlabel.h
Normal file
33
include/zwidget/widgets/textlabel/textlabel.h
Normal file
|
|
@ -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;
|
||||
};
|
||||
31
include/zwidget/widgets/toolbar/toolbar.h
Normal file
31
include/zwidget/widgets/toolbar/toolbar.h
Normal file
|
|
@ -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<void()> onClicked = {});
|
||||
|
||||
protected:
|
||||
void OnGeometryChanged() override;
|
||||
|
||||
private:
|
||||
ToolbarDirection direction = ToolbarDirection::Horizontal;
|
||||
std::vector<ToolbarButton*> buttons;
|
||||
};
|
||||
34
include/zwidget/widgets/toolbar/toolbarbutton.h
Normal file
34
include/zwidget/widgets/toolbar/toolbarbutton.h
Normal file
|
|
@ -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<void()> 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;
|
||||
};
|
||||
12
include/zwidget/window/sdl2nativehandle.h
Normal file
12
include/zwidget/window/sdl2nativehandle.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct SDL_Window;
|
||||
|
||||
class SDL2NativeHandle
|
||||
{
|
||||
public:
|
||||
SDL_Window* window = nullptr;
|
||||
};
|
||||
10
include/zwidget/window/waylandnativehandle.h
Normal file
10
include/zwidget/window/waylandnativehandle.h
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <wayland-client-protocol.h>
|
||||
|
||||
class WaylandNativeHandle
|
||||
{
|
||||
public:
|
||||
wl_display* display = nullptr;
|
||||
wl_surface* surface = nullptr;
|
||||
};
|
||||
9
include/zwidget/window/win32nativehandle.h
Normal file
9
include/zwidget/window/win32nativehandle.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
typedef struct HWND__* HWND;
|
||||
|
||||
class Win32NativeHandle
|
||||
{
|
||||
public:
|
||||
HWND hwnd = {};
|
||||
};
|
||||
243
include/zwidget/window/window.h
Normal file
243
include/zwidget/window/window.h
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#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<DisplayWindow> 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<void()> 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<std::string> GetVulkanInstanceExtensions() = 0;
|
||||
virtual VkSurfaceKHR CreateVulkanSurface(VkInstance instance) = 0;
|
||||
};
|
||||
|
||||
class DisplayBackend
|
||||
{
|
||||
public:
|
||||
static DisplayBackend* Get();
|
||||
static void Set(std::unique_ptr<DisplayBackend> instance);
|
||||
|
||||
static std::unique_ptr<DisplayBackend> TryCreateWin32();
|
||||
static std::unique_ptr<DisplayBackend> TryCreateSDL2();
|
||||
static std::unique_ptr<DisplayBackend> TryCreateX11();
|
||||
static std::unique_ptr<DisplayBackend> TryCreateWayland();
|
||||
|
||||
static std::unique_ptr<DisplayBackend> TryCreateBackend();
|
||||
|
||||
virtual ~DisplayBackend() = default;
|
||||
|
||||
virtual bool IsWin32() { return false; }
|
||||
virtual bool IsSDL2() { return false; }
|
||||
virtual bool IsX11() { return false; }
|
||||
virtual bool IsWayland() { return false; }
|
||||
|
||||
virtual std::unique_ptr<DisplayWindow> Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) = 0;
|
||||
virtual void ProcessEvents() = 0;
|
||||
virtual void RunLoop() = 0;
|
||||
virtual void ExitLoop() = 0;
|
||||
|
||||
virtual void* StartTimer(int timeoutMilliseconds, std::function<void()> onTimer) = 0;
|
||||
virtual void StopTimer(void* timerID) = 0;
|
||||
|
||||
virtual Size GetScreenSize() = 0;
|
||||
|
||||
virtual std::unique_ptr<OpenFileDialog> CreateOpenFileDialog(DisplayWindow* owner);
|
||||
virtual std::unique_ptr<SaveFileDialog> CreateSaveFileDialog(DisplayWindow* owner);
|
||||
virtual std::unique_ptr<OpenFolderDialog> CreateOpenFolderDialog(DisplayWindow* owner);
|
||||
};
|
||||
13
include/zwidget/window/x11nativehandle.h
Normal file
13
include/zwidget/window/x11nativehandle.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
typedef struct _XDisplay Display;
|
||||
typedef unsigned long XID;
|
||||
typedef unsigned long VisualID;
|
||||
typedef XID Window;
|
||||
|
||||
class X11NativeHandle
|
||||
{
|
||||
public:
|
||||
Display* display = nullptr;
|
||||
Window window = 0;
|
||||
};
|
||||
1084
src/core/canvas.cpp
Normal file
1084
src/core/canvas.cpp
Normal file
File diff suppressed because it is too large
Load diff
28
src/core/font.cpp
Normal file
28
src/core/font.cpp
Normal file
|
|
@ -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> Font::Create(const std::string& name, double height)
|
||||
{
|
||||
return std::make_shared<FontImpl>(name, height);
|
||||
}
|
||||
110
src/core/image.cpp
Normal file
110
src/core/image.cpp
Normal file
|
|
@ -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 <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
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<uint32_t[]>(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<uint32_t[]> Data;
|
||||
};
|
||||
|
||||
std::shared_ptr<Image> Image::Create(int width, int height, ImageFormat format, const void* data)
|
||||
{
|
||||
return std::make_shared<ImageImpl>(width, height, format, data);
|
||||
}
|
||||
|
||||
std::shared_ptr<Image> 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<unsigned char> pixels;
|
||||
unsigned long width = 0, height = 0;
|
||||
int result = decodePNG(pixels, width, height, (const unsigned char*)filedata.data(), filedata.size(), true);
|
||||
if (result != 0)
|
||||
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 = 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");
|
||||
}
|
||||
}
|
||||
6
src/core/nanosvg/nanosvg.cpp
Normal file
6
src/core/nanosvg/nanosvg.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
#define NANOSVG_IMPLEMENTATION
|
||||
#include "nanosvg.h"
|
||||
|
||||
#define NANOSVGRAST_IMPLEMENTATION
|
||||
#include "nanosvgrast.h"
|
||||
3107
src/core/nanosvg/nanosvg.h
Normal file
3107
src/core/nanosvg/nanosvg.h
Normal file
File diff suppressed because it is too large
Load diff
1459
src/core/nanosvg/nanosvgrast.h
Normal file
1459
src/core/nanosvg/nanosvgrast.h
Normal file
File diff suppressed because it is too large
Load diff
613
src/core/pathfill.cpp
Normal file
613
src/core/pathfill.cpp
Normal file
|
|
@ -0,0 +1,613 @@
|
|||
|
||||
#include "core/pathfill.h"
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
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<PathScanlineEdge> edges;
|
||||
std::vector<unsigned char> 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<PathScanline> 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<double>(AntialiasLevel);
|
||||
x1 *= static_cast<double>(AntialiasLevel);
|
||||
y0 *= static_cast<double>(AntialiasLevel);
|
||||
y1 *= static_cast<double>(AntialiasLevel);
|
||||
|
||||
bool up_direction = y1 < y0;
|
||||
double dy = y1 - y0;
|
||||
|
||||
constexpr const double epsilon = std::numeric_limits<double>::epsilon();
|
||||
if (dy < -epsilon || dy > epsilon)
|
||||
{
|
||||
int start_y = static_cast<int>(std::floor(std::min(y0, y1) + 0.5f));
|
||||
int end_y = static_cast<int>(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<int>(scanline->edges[i].x + 0.5f);
|
||||
x1 = static_cast<int>(scanline->edges[i + 1].x - 0.5f) + 1;
|
||||
i += 2;
|
||||
found = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
x0 = static_cast<int>(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<int>(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;
|
||||
}
|
||||
18
src/core/picopng/LICENSE.txt
Normal file
18
src/core/picopng/LICENSE.txt
Normal file
|
|
@ -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.
|
||||
595
src/core/picopng/picopng.cpp
Normal file
595
src/core/picopng/picopng.cpp
Normal file
|
|
@ -0,0 +1,595 @@
|
|||
#include <vector>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
/*
|
||||
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<unsigned char>& 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<unsigned long>& bitlen, unsigned long maxbitlen)
|
||||
{ //make tree given the lengths
|
||||
unsigned long numcodes = (unsigned long)(bitlen.size()), treepos = 0, nodefilled = 0;
|
||||
std::vector<unsigned long> 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<unsigned long> 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<unsigned char>& out, const std::vector<unsigned char>& 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<unsigned long> 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<unsigned long> 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<unsigned long> 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<unsigned char>& 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<unsigned char>& 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<unsigned char>& out, const std::vector<unsigned char>& 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<unsigned char> palette;
|
||||
} info;
|
||||
int error;
|
||||
void decode(std::vector<unsigned char>& 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<unsigned char> 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<unsigned char> 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<unsigned char> 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<unsigned char> 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<unsigned char> 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<unsigned char>& 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 <iostream>
|
||||
#include <fstream>
|
||||
|
||||
void loadFile(std::vector<unsigned char>& 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<unsigned char> 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
|
||||
5
src/core/picopng/picopng.h
Normal file
5
src/core/picopng/picopng.h
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
int decodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32 = true);
|
||||
883
src/core/span_layout.cpp
Normal file
883
src/core/span_layout.cpp
Normal file
|
|
@ -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<Rect> SpanLayout::GetRectById(int id) const
|
||||
{
|
||||
std::vector<Rect> segment_rects;
|
||||
|
||||
double x = position.x;
|
||||
double y = position.y;
|
||||
for (std::vector<Line>::size_type line_index = 0; line_index < lines.size(); line_index++)
|
||||
{
|
||||
const Line& line = lines[line_index];
|
||||
for (std::vector<LineSegment>::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<Line>::size_type line_index = 0; line_index < lines.size(); line_index++)
|
||||
{
|
||||
Line& line = lines[line_index];
|
||||
for (std::vector<LineSegment>::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<Line>::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<LineSegment>::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<Line>::size_type line_index = 0; line_index < lines.size(); line_index++)
|
||||
{
|
||||
const Line& line = lines[line_index];
|
||||
for (std::vector<LineSegment>::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> 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> 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> 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::TextBlock> SpanLayout::FindTextBlocks()
|
||||
{
|
||||
std::vector<TextBlock> blocks;
|
||||
std::vector<SpanObject>::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<TextBlock> blocks = FindTextBlocks();
|
||||
for (std::vector<TextBlock>::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<TextBlock>& blocks, std::vector<TextBlock>::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<TextBlock>& blocks, std::vector<TextBlock>::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<FloatBox>& 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<TextBlock> blocks, std::vector<TextBlock>::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<LineSegment>::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<LineSegment>::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<Line>::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<LineSegment>::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<Line>::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<LineSegment>::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<Line>::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<LineSegment>::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;
|
||||
}
|
||||
}
|
||||
412
src/core/theme.cpp
Normal file
412
src/core/theme.cpp
Normal file
|
|
@ -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<bool>(*prop) : false;
|
||||
}
|
||||
|
||||
int WidgetStyle::GetInt(const std::string& state, const std::string& propertyName) const
|
||||
{
|
||||
const PropertyVariant* prop = FindProperty(state, propertyName);
|
||||
return prop ? std::get<int>(*prop) : 0;
|
||||
}
|
||||
|
||||
double WidgetStyle::GetDouble(const std::string& state, const std::string& propertyName) const
|
||||
{
|
||||
const PropertyVariant* prop = FindProperty(state, propertyName);
|
||||
return prop ? std::get<double>(*prop) : 0.0;
|
||||
}
|
||||
|
||||
std::string WidgetStyle::GetString(const std::string& state, const std::string& propertyName) const
|
||||
{
|
||||
const PropertyVariant* prop = FindProperty(state, propertyName);
|
||||
return prop ? std::get<std::string>(*prop) : std::string();
|
||||
}
|
||||
|
||||
Colorf WidgetStyle::GetColor(const std::string& state, const std::string& propertyName) const
|
||||
{
|
||||
const PropertyVariant* prop = FindProperty(state, propertyName);
|
||||
return prop ? std::get<Colorf>(*prop) : Colorf::transparent();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void BasicWidgetStyle::Paint(Widget* widget, Canvas* canvas, Size size)
|
||||
{
|
||||
Colorf bgcolor = widget->GetStyleColor("background-color");
|
||||
if (bgcolor.a > 0.0f)
|
||||
canvas->fillRect(Rect::xywh(0.0, 0.0, size.width, size.height), bgcolor);
|
||||
|
||||
Colorf borderleft = widget->GetStyleColor("border-left-color");
|
||||
Colorf bordertop = widget->GetStyleColor("border-top-color");
|
||||
Colorf borderright = widget->GetStyleColor("border-right-color");
|
||||
Colorf borderbottom = widget->GetStyleColor("border-bottom-color");
|
||||
|
||||
double borderwidth = widget->GridFitSize(1.0);
|
||||
|
||||
if (bordertop.a > 0.0f)
|
||||
canvas->fillRect(Rect::xywh(0.0, 0.0, size.width, borderwidth), bordertop);
|
||||
if (borderbottom.a > 0.0f)
|
||||
canvas->fillRect(Rect::xywh(0.0, size.height - borderwidth, size.width, borderwidth), borderbottom);
|
||||
if (borderleft.a > 0.0f)
|
||||
canvas->fillRect(Rect::xywh(0.0, 0.0, borderwidth, size.height), borderleft);
|
||||
if (borderright.a > 0.0f)
|
||||
canvas->fillRect(Rect::xywh(size.width - borderwidth, 0.0, borderwidth, size.height), borderright);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static std::unique_ptr<WidgetTheme> CurrentTheme;
|
||||
|
||||
WidgetStyle* WidgetTheme::RegisterStyle(std::unique_ptr<WidgetStyle> widgetStyle, const std::string& widgetClass)
|
||||
{
|
||||
auto& style = Styles[widgetClass];
|
||||
style = std::move(widgetStyle);
|
||||
return style.get();
|
||||
}
|
||||
|
||||
WidgetStyle* WidgetTheme::GetStyle(const std::string& widgetClass)
|
||||
{
|
||||
auto it = Styles.find(widgetClass);
|
||||
return it != Styles.end() ? it->second.get() : nullptr;
|
||||
}
|
||||
|
||||
void WidgetTheme::SetTheme(std::unique_ptr<WidgetTheme> theme)
|
||||
{
|
||||
CurrentTheme = std::move(theme);
|
||||
}
|
||||
|
||||
WidgetTheme* WidgetTheme::GetTheme()
|
||||
{
|
||||
return CurrentTheme.get();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
DarkWidgetTheme::DarkWidgetTheme()
|
||||
{
|
||||
auto widget = RegisterStyle(std::make_unique<BasicWidgetStyle>(), "widget");
|
||||
auto textlabel = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textlabel");
|
||||
auto pushbutton = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "pushbutton");
|
||||
auto lineedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "lineedit");
|
||||
auto textedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textedit");
|
||||
auto listview = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "listview");
|
||||
auto scrollbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "scrollbar");
|
||||
auto tabbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar");
|
||||
auto tabbar_tab = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar-tab");
|
||||
auto tabbar_spacer = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar-spacer");
|
||||
auto tabwidget_stack = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabwidget-stack");
|
||||
auto checkbox_label = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "checkbox-label");
|
||||
auto menubar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menubar");
|
||||
auto menubaritem = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menubaritem");
|
||||
auto menu = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menu");
|
||||
auto menuitem = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menuitem");
|
||||
auto toolbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "toolbar");
|
||||
auto toolbarbutton = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "toolbarbutton");
|
||||
auto statusbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "statusbar");
|
||||
|
||||
widget->SetString("font-family", "NotoSans");
|
||||
widget->SetColor("color", Colorf::fromRgba8(226, 223, 219));
|
||||
widget->SetColor("window-background", Colorf::fromRgba8(51, 51, 51));
|
||||
widget->SetColor("window-border", Colorf::fromRgba8(51, 51, 51));
|
||||
widget->SetColor("window-caption-color", Colorf::fromRgba8(33, 33, 33));
|
||||
widget->SetColor("window-caption-text-color", Colorf::fromRgba8(226, 223, 219));
|
||||
|
||||
pushbutton->SetDouble("noncontent-left", 10.0);
|
||||
pushbutton->SetDouble("noncontent-top", 5.0);
|
||||
pushbutton->SetDouble("noncontent-right", 10.0);
|
||||
pushbutton->SetDouble("noncontent-bottom", 5.0);
|
||||
pushbutton->SetColor("background-color", Colorf::fromRgba8(68, 68, 68));
|
||||
pushbutton->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100));
|
||||
pushbutton->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100));
|
||||
pushbutton->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100));
|
||||
pushbutton->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
|
||||
pushbutton->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78));
|
||||
pushbutton->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88));
|
||||
|
||||
lineedit->SetDouble("noncontent-left", 5.0);
|
||||
lineedit->SetDouble("noncontent-top", 3.0);
|
||||
lineedit->SetDouble("noncontent-right", 5.0);
|
||||
lineedit->SetDouble("noncontent-bottom", 3.0);
|
||||
lineedit->SetColor("background-color", Colorf::fromRgba8(38, 38, 38));
|
||||
lineedit->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100));
|
||||
lineedit->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100));
|
||||
lineedit->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100));
|
||||
lineedit->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
|
||||
lineedit->SetColor("selection-color", Colorf::fromRgba8(100, 100, 100));
|
||||
lineedit->SetColor("no-focus-selection-color", Colorf::fromRgba8(68, 68, 68));
|
||||
|
||||
textedit->SetDouble("noncontent-left", 8.0);
|
||||
textedit->SetDouble("noncontent-top", 8.0);
|
||||
textedit->SetDouble("noncontent-right", 8.0);
|
||||
textedit->SetDouble("noncontent-bottom", 8.0);
|
||||
textedit->SetColor("background-color", Colorf::fromRgba8(38, 38, 38));
|
||||
textedit->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100));
|
||||
textedit->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100));
|
||||
textedit->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100));
|
||||
textedit->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
|
||||
|
||||
listview->SetDouble("noncontent-left", 10.0);
|
||||
listview->SetDouble("noncontent-top", 10.0);
|
||||
listview->SetDouble("noncontent-right", 3.0);
|
||||
listview->SetDouble("noncontent-bottom", 10.0);
|
||||
listview->SetColor("background-color", Colorf::fromRgba8(38, 38, 38));
|
||||
listview->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100));
|
||||
listview->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100));
|
||||
listview->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100));
|
||||
listview->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
|
||||
listview->SetColor("selection-color", Colorf::fromRgba8(100, 100, 100));
|
||||
|
||||
scrollbar->SetColor("track-color", Colorf::fromRgba8(33, 33, 33));
|
||||
scrollbar->SetColor("thumb-color", Colorf::fromRgba8(58, 58, 58));
|
||||
|
||||
tabbar->SetDouble("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<BasicWidgetStyle>(), "widget");
|
||||
auto textlabel = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textlabel");
|
||||
auto pushbutton = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "pushbutton");
|
||||
auto lineedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "lineedit");
|
||||
auto textedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textedit");
|
||||
auto listview = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "listview");
|
||||
auto scrollbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "scrollbar");
|
||||
auto tabbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar");
|
||||
auto tabbar_tab = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar-tab");
|
||||
auto tabbar_spacer = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar-spacer");
|
||||
auto tabwidget_stack = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabwidget-stack");
|
||||
auto checkbox_label = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "checkbox-label");
|
||||
auto menubar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menubar");
|
||||
auto menubaritem = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menubaritem");
|
||||
auto menu = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menu");
|
||||
auto menuitem = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menuitem");
|
||||
|
||||
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));
|
||||
}
|
||||
45
src/core/timer.cpp
Normal file
45
src/core/timer.cpp
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
1201
src/core/truetypefont.cpp
Normal file
1201
src/core/truetypefont.cpp
Normal file
File diff suppressed because it is too large
Load diff
527
src/core/truetypefont.h
Normal file
527
src/core/truetypefont.h
Normal file
|
|
@ -0,0 +1,527 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
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<uint8_t, 4> 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<const uint8_t*>(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<TTF_TableRecord> 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<ttf_Offset32> 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<TTF_EncodingRecord> 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<ttf_uint8> 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<ttf_uint16> endCode;
|
||||
ttf_uint16 reservedPad = {};
|
||||
std::vector<ttf_uint16> startCode;
|
||||
std::vector<ttf_int16> idDelta;
|
||||
std::vector<ttf_uint16> idRangeOffsets;
|
||||
std::vector<ttf_uint16> 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<TTF_GlyphRange> 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<longHorMetric> hMetrics; // [hhea.numberOfHMetrics]
|
||||
std::vector<ttf_int16> 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> nameRecord; // [count]
|
||||
|
||||
// v1 only:
|
||||
ttf_uint16 langTagCount = {};
|
||||
std::vector<LangTagRecord> 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<ttf_Offset32> 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<int> endPtsOfContours;
|
||||
std::vector<ttf_uint8> flags;
|
||||
std::vector<TTF_Point> points;
|
||||
};
|
||||
|
||||
class TrueTypeGlyph
|
||||
{
|
||||
public:
|
||||
int advanceWidth = 0;
|
||||
int leftSideBearing = 0;
|
||||
int yOffset = 0;
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
std::unique_ptr<uint8_t[]> grayscale;
|
||||
};
|
||||
|
||||
class TrueTypeTextMetrics
|
||||
{
|
||||
public:
|
||||
double ascender = 0.0;
|
||||
double descender = 0.0;
|
||||
double lineGap = 0.0;
|
||||
};
|
||||
|
||||
class TrueTypeFontFileData
|
||||
{
|
||||
public:
|
||||
TrueTypeFontFileData(std::vector<uint8_t> 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<void*>(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<uint8_t> 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<TrueTypeFontFileData> data, int ttcFontIndex = 0);
|
||||
|
||||
static std::vector<TTCFontName> GetFontNames(const std::shared_ptr<TrueTypeFontFileData>& 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<TrueTypeFontFileData> 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<TTF_GlyphRange> Ranges;
|
||||
std::vector<TTF_GlyphRange> ManyToOneRanges;
|
||||
};
|
||||
153
src/core/utf8reader.cpp
Normal file
153
src/core/utf8reader.cpp
Normal file
|
|
@ -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;
|
||||
}
|
||||
957
src/core/widget.cpp
Normal file
957
src/core/widget.cpp
Normal file
|
|
@ -0,0 +1,957 @@
|
|||
|
||||
#include "core/widget.h"
|
||||
#include "core/timer.h"
|
||||
#include "core/colorf.h"
|
||||
#include "core/theme.h"
|
||||
#include <stdexcept>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
Widget::Widget(Widget* parent, WidgetType type, 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> 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<Widget*>(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<bool>(it->second);
|
||||
WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass);
|
||||
return style ? style->GetBool(StyleState, propertyName) : false;
|
||||
}
|
||||
|
||||
int Widget::GetStyleInt(const std::string& propertyName) const
|
||||
{
|
||||
auto it = StyleProperties.find(propertyName);
|
||||
if (it != StyleProperties.end())
|
||||
return std::get<int>(it->second);
|
||||
WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass);
|
||||
return style ? style->GetInt(StyleState, propertyName) : 0;
|
||||
}
|
||||
|
||||
double Widget::GetStyleDouble(const std::string& propertyName) const
|
||||
{
|
||||
auto it = StyleProperties.find(propertyName);
|
||||
if (it != StyleProperties.end())
|
||||
return std::get<double>(it->second);
|
||||
WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass);
|
||||
return style ? style->GetDouble(StyleState, propertyName) : 0.0;
|
||||
}
|
||||
|
||||
std::string Widget::GetStyleString(const std::string& propertyName) const
|
||||
{
|
||||
auto it = StyleProperties.find(propertyName);
|
||||
if (it != StyleProperties.end())
|
||||
return std::get<std::string>(it->second);
|
||||
WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass);
|
||||
return style ? style->GetString(StyleState, propertyName) : std::string();
|
||||
}
|
||||
|
||||
Colorf Widget::GetStyleColor(const std::string& propertyName) const
|
||||
{
|
||||
auto it = StyleProperties.find(propertyName);
|
||||
if (it != StyleProperties.end())
|
||||
return std::get<Colorf>(it->second);
|
||||
WidgetStyle* style = WidgetTheme::GetTheme()->GetStyle(StyleClass);
|
||||
return style ? style->GetColor(StyleState, propertyName) : Colorf::transparent();
|
||||
}
|
||||
12
src/systemdialogs/open_file_dialog.cpp
Normal file
12
src/systemdialogs/open_file_dialog.cpp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
#include "systemdialogs/open_file_dialog.h"
|
||||
#include "window/window.h"
|
||||
#include "core/widget.h"
|
||||
|
||||
std::unique_ptr<OpenFileDialog> OpenFileDialog::Create(Widget* owner)
|
||||
{
|
||||
DisplayWindow* windowOwner = nullptr;
|
||||
if (owner && owner->Window())
|
||||
windowOwner = owner->Window()->DispWindow.get();
|
||||
return DisplayBackend::Get()->CreateOpenFileDialog(windowOwner);
|
||||
}
|
||||
12
src/systemdialogs/open_folder_dialog.cpp
Normal file
12
src/systemdialogs/open_folder_dialog.cpp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
#include "systemdialogs/open_folder_dialog.h"
|
||||
#include "window/window.h"
|
||||
#include "core/widget.h"
|
||||
|
||||
std::unique_ptr<OpenFolderDialog> OpenFolderDialog::Create(Widget* owner)
|
||||
{
|
||||
DisplayWindow* windowOwner = nullptr;
|
||||
if (owner && owner->Window())
|
||||
windowOwner = owner->Window()->DispWindow.get();
|
||||
return DisplayBackend::Get()->CreateOpenFolderDialog(windowOwner);
|
||||
}
|
||||
12
src/systemdialogs/save_file_dialog.cpp
Normal file
12
src/systemdialogs/save_file_dialog.cpp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
#include "systemdialogs/save_file_dialog.h"
|
||||
#include "window/window.h"
|
||||
#include "core/widget.h"
|
||||
|
||||
std::unique_ptr<SaveFileDialog> SaveFileDialog::Create(Widget* owner)
|
||||
{
|
||||
DisplayWindow* windowOwner = nullptr;
|
||||
if (owner && owner->Window())
|
||||
windowOwner = owner->Window()->DispWindow.get();
|
||||
return DisplayBackend::Get()->CreateSaveFileDialog(windowOwner);
|
||||
}
|
||||
101
src/widgets/checkboxlabel/checkboxlabel.cpp
Normal file
101
src/widgets/checkboxlabel/checkboxlabel.cpp
Normal file
|
|
@ -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);
|
||||
}
|
||||
73
src/widgets/imagebox/imagebox.cpp
Normal file
73
src/widgets/imagebox/imagebox.cpp
Normal file
|
|
@ -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<Image> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
1171
src/widgets/lineedit/lineedit.cpp
Normal file
1171
src/widgets/lineedit/lineedit.cpp
Normal file
File diff suppressed because it is too large
Load diff
286
src/widgets/listview/listview.cpp
Normal file
286
src/widgets/listview/listview.cpp
Normal file
|
|
@ -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<double>& widths)
|
||||
{
|
||||
columnwidths = widths;
|
||||
|
||||
bool updated = false;
|
||||
const size_t newWidth = columnwidths.size();
|
||||
for (std::vector<std::string>& 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<std::string> 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<int>(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<std::string>& 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/widgets/mainwindow/mainwindow.cpp
Normal file
44
src/widgets/mainwindow/mainwindow.cpp
Normal file
|
|
@ -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);
|
||||
}
|
||||
390
src/widgets/menubar/menubar.cpp
Normal file
390
src/widgets/menubar/menubar.cpp
Normal file
|
|
@ -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<void(Menu* menu)> 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<MenuItem*>(openMenu->LastChild()));
|
||||
}
|
||||
else
|
||||
{
|
||||
openMenu->SetSelected(static_cast<MenuItem*>(openMenu->selectedItem->PrevSibling()));
|
||||
}
|
||||
|
||||
if (openMenu->selectedItem && openMenu->selectedItem->GetStyleClass() != "menuitemseparator")
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (key == InputKey::Down)
|
||||
{
|
||||
if (openMenu)
|
||||
{
|
||||
// Keep trying until we don't find a separator
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
if (!openMenu->selectedItem || !openMenu->selectedItem->NextSibling())
|
||||
{
|
||||
if (openMenu->FirstChild())
|
||||
openMenu->SetSelected(static_cast<MenuItem*>(openMenu->FirstChild()));
|
||||
}
|
||||
else
|
||||
{
|
||||
openMenu->SetSelected(static_cast<MenuItem*>(openMenu->selectedItem->NextSibling()));
|
||||
}
|
||||
|
||||
if (openMenu->selectedItem && openMenu->selectedItem->GetStyleClass() != "menuitemseparator")
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (key == InputKey::Enter)
|
||||
{
|
||||
if (openMenu && openMenu->selectedItem)
|
||||
openMenu->selectedItem->Click();
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MenubarItem::MenubarItem(Menubar* menubar, std::string text, bool alignRight) : Widget(menubar), menubar(menubar), text(text), AlignRight(alignRight)
|
||||
{
|
||||
SetStyleClass("menubaritem");
|
||||
}
|
||||
|
||||
bool MenubarItem::OnMouseDown(const Point& pos, InputKey key)
|
||||
{
|
||||
menubar->ShowMenu(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MenubarItem::OnMouseUp(const Point& pos, InputKey key)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void MenubarItem::OnMouseMove(const Point& pos)
|
||||
{
|
||||
if (GetStyleState().empty())
|
||||
{
|
||||
SetStyleState("hover");
|
||||
}
|
||||
}
|
||||
|
||||
void MenubarItem::OnMouseLeave()
|
||||
{
|
||||
SetStyleState("");
|
||||
}
|
||||
|
||||
double MenubarItem::GetPreferredWidth() const
|
||||
{
|
||||
Canvas* canvas = GetCanvas();
|
||||
return canvas->measureText(text).width;
|
||||
}
|
||||
|
||||
void MenubarItem::OnPaint(Canvas* canvas)
|
||||
{
|
||||
double x = (GetWidth() - canvas->measureText(text).width) * 0.5;
|
||||
canvas->drawText(Point(x, 21.0), GetStyleColor("color"), text);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Menu::Menu(Widget* parent) : Widget(parent, WidgetType::Popup)
|
||||
{
|
||||
SetStyleClass("menu");
|
||||
}
|
||||
|
||||
void Menu::SetLeftPosition(const Point& pos)
|
||||
{
|
||||
SetFrameGeometry(Rect::xywh(pos.x, pos.y, GetPreferredWidth() + GetNoncontentLeft() + GetNoncontentRight(), GetPreferredHeight() + GetNoncontentTop() + GetNoncontentBottom()));
|
||||
}
|
||||
|
||||
void Menu::SetRightPosition(const Point& pos)
|
||||
{
|
||||
SetFrameGeometry(Rect::xywh(pos.x - GetWidth() - GetNoncontentLeft() - GetNoncontentRight(), pos.y, GetWidth() + GetNoncontentLeft() + GetNoncontentRight(), GetHeight() + GetNoncontentTop() + GetNoncontentBottom()));
|
||||
}
|
||||
|
||||
MenuItem* Menu::AddItem(std::shared_ptr<Image> icon, std::string text, std::function<void()> onClick)
|
||||
{
|
||||
auto item = new MenuItem(this, [this, onClick]() { if (onCloseMenu) onCloseMenu(); if (onClick) onClick(); });
|
||||
if (icon)
|
||||
item->icon->SetImage(icon);
|
||||
item->text->SetText(text);
|
||||
return item;
|
||||
}
|
||||
|
||||
MenuItemSeparator* Menu::AddSeparator()
|
||||
{
|
||||
auto sep = new MenuItemSeparator(this);
|
||||
return sep;
|
||||
}
|
||||
|
||||
double Menu::GetPreferredWidth() const
|
||||
{
|
||||
return GridFitSize(200.0);
|
||||
}
|
||||
|
||||
double Menu::GetPreferredHeight() const
|
||||
{
|
||||
double h = 0.0;
|
||||
for (Widget* item = FirstChild(); item != nullptr; item = item->NextSibling())
|
||||
{
|
||||
double itemheight = GridFitSize((item->GetStyleClass() == "menuitemseparator") ? 7.0 : 30.0);
|
||||
h += itemheight;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
void Menu::OnGeometryChanged()
|
||||
{
|
||||
double w = GetWidth();
|
||||
double y = 0.0;
|
||||
for (Widget* item = FirstChild(); item != nullptr; item = item->NextSibling())
|
||||
{
|
||||
double itemheight = GridFitSize((item->GetStyleClass() == "menuitemseparator") ? 7.0 : 30.0);
|
||||
item->SetFrameGeometry(Rect::xywh(0.0, y, w, itemheight));
|
||||
y += itemheight;
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::SetSelected(MenuItem* item)
|
||||
{
|
||||
if (selectedItem)
|
||||
{
|
||||
selectedItem->SetStyleState("");
|
||||
}
|
||||
selectedItem = item;
|
||||
if (selectedItem)
|
||||
{
|
||||
selectedItem->SetStyleState("hover");
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MenuItem::MenuItem(Menu* menu, std::function<void()> onClick) : Widget(menu), menu(menu), onClick(onClick)
|
||||
{
|
||||
SetStyleClass("menuitem");
|
||||
icon = new ImageBox(this);
|
||||
text = new TextLabel(this);
|
||||
}
|
||||
|
||||
void MenuItem::OnMouseMove(const Point& pos)
|
||||
{
|
||||
menu->SetSelected(this);
|
||||
}
|
||||
|
||||
bool MenuItem::OnMouseUp(const Point& pos, InputKey key)
|
||||
{
|
||||
Click();
|
||||
return true;
|
||||
}
|
||||
|
||||
void MenuItem::Click()
|
||||
{
|
||||
if (onClick)
|
||||
{
|
||||
// We have to make a copy of the handler as it may delete 'this'
|
||||
auto handler = onClick;
|
||||
handler();
|
||||
}
|
||||
}
|
||||
|
||||
void MenuItem::OnMouseLeave()
|
||||
{
|
||||
menu->SetSelected(nullptr);
|
||||
}
|
||||
|
||||
void MenuItem::OnGeometryChanged()
|
||||
{
|
||||
double iconwidth = GridFitSize(icon->GetPreferredWidth());
|
||||
double iconheight = GridFitSize(icon->GetPreferredHeight());
|
||||
double w = GetWidth();
|
||||
double h = GetHeight();
|
||||
double textheight = 19.0;
|
||||
double x0 = GridFitPoint(5.0);
|
||||
double x1 = GridFitPoint(5.0 + iconwidth);
|
||||
icon->SetFrameGeometry(Rect::xywh(x0, GridFitPoint((h - iconheight) * 0.5), iconwidth, iconheight));
|
||||
text->SetFrameGeometry(Rect::xywh(x1, GridFitPoint((h - textheight) * 0.5), w - x1, textheight));
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MenuItemSeparator::MenuItemSeparator(Widget* parent) : Widget(parent)
|
||||
{
|
||||
SetStyleClass("menuitemseparator");
|
||||
}
|
||||
|
||||
void MenuItemSeparator::OnPaint(Canvas* canvas)
|
||||
{
|
||||
canvas->fillRect(Rect::xywh(0.0, GridFitPoint(GetHeight() * 0.5), GetWidth(), GridFitSize(1.0)), Colorf::fromRgba8(75, 75, 75));
|
||||
}
|
||||
88
src/widgets/pushbutton/pushbutton.cpp
Normal file
88
src/widgets/pushbutton/pushbutton.cpp
Normal file
|
|
@ -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();
|
||||
}
|
||||
409
src/widgets/scrollbar/scrollbar.cpp
Normal file
409
src/widgets/scrollbar/scrollbar.cpp
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
|
||||
#include "widgets/scrollbar/scrollbar.h"
|
||||
#include "core/colorf.h"
|
||||
#include <stdexcept>
|
||||
|
||||
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<void()>* 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)();
|
||||
}
|
||||
21
src/widgets/statusbar/statusbar.cpp
Normal file
21
src/widgets/statusbar/statusbar.cpp
Normal file
|
|
@ -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:");
|
||||
}
|
||||
345
src/widgets/tabwidget/tabwidget.cpp
Normal file
345
src/widgets/tabwidget/tabwidget.cpp
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
|
||||
#include "widgets/tabwidget/tabwidget.h"
|
||||
#include "widgets/textlabel/textlabel.h"
|
||||
#include "widgets/imagebox/imagebox.h"
|
||||
#include <algorithm>
|
||||
|
||||
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<Image>& 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<Image>& icon)
|
||||
{
|
||||
Bar->SetTabIcon(index, icon);
|
||||
}
|
||||
|
||||
void TabWidget::SetTabIcon(Widget* page, const std::shared_ptr<Image>& 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<Image>& 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<Image>& 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>& 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()));
|
||||
}
|
||||
1045
src/widgets/textedit/textedit.cpp
Normal file
1045
src/widgets/textedit/textedit.cpp
Normal file
File diff suppressed because it is too large
Load diff
60
src/widgets/textlabel/textlabel.cpp
Normal file
60
src/widgets/textlabel/textlabel.cpp
Normal file
|
|
@ -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);
|
||||
}
|
||||
63
src/widgets/toolbar/toolbar.cpp
Normal file
63
src/widgets/toolbar/toolbar.cpp
Normal file
|
|
@ -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<void()> onClicked)
|
||||
{
|
||||
ToolbarButton* button = new ToolbarButton(this);
|
||||
if (!icon.empty())
|
||||
button->SetIcon(icon);
|
||||
if (!text.empty())
|
||||
button->SetText(text);
|
||||
button->OnClick = std::move(onClicked);
|
||||
buttons.push_back(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
void Toolbar::OnGeometryChanged()
|
||||
{
|
||||
if (direction == ToolbarDirection::Horizontal)
|
||||
{
|
||||
double x = 7.0;
|
||||
double barHeight = GetHeight();
|
||||
double gap = 7.0;
|
||||
for (ToolbarButton* button : buttons)
|
||||
{
|
||||
double width = button->GetPreferredWidth();
|
||||
double height = button->GetPreferredHeight();
|
||||
button->SetFrameGeometry(Rect::xywh(x, (barHeight - height) * 0.5, width, height));
|
||||
x += width + gap;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double y = 7.0;
|
||||
double barWidth = GetWidth();
|
||||
double gap = 7.0;
|
||||
for (ToolbarButton* button : buttons)
|
||||
{
|
||||
double width = button->GetPreferredWidth();
|
||||
double height = button->GetPreferredHeight();
|
||||
button->SetFrameGeometry(Rect::xywh((barWidth - width) * 0.5, y, width, height));
|
||||
y += height + gap;
|
||||
}
|
||||
}
|
||||
}
|
||||
114
src/widgets/toolbar/toolbarbutton.cpp
Normal file
114
src/widgets/toolbar/toolbarbutton.cpp
Normal file
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
288
src/window/dbus/dbus_open_file_dialog.cpp
Normal file
288
src/window/dbus/dbus_open_file_dialog.cpp
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
|
||||
#include "dbus_open_file_dialog.h"
|
||||
#include <dbus/dbus.h>
|
||||
|
||||
DBusOpenFileDialog::DBusOpenFileDialog(std::string ownerHandle) : ownerHandle(ownerHandle)
|
||||
{
|
||||
}
|
||||
|
||||
std::string DBusOpenFileDialog::Filename() const
|
||||
{
|
||||
return outputFilenames.empty() ? std::string() : outputFilenames.front();
|
||||
}
|
||||
|
||||
std::vector<std::string> DBusOpenFileDialog::Filenames() const
|
||||
{
|
||||
return outputFilenames;
|
||||
}
|
||||
|
||||
void DBusOpenFileDialog::SetMultiSelect(bool multiselect)
|
||||
{
|
||||
this->multiSelect = multiSelect;
|
||||
}
|
||||
|
||||
void DBusOpenFileDialog::SetFilename(const std::string &filename)
|
||||
{
|
||||
inputFilename = filename;
|
||||
}
|
||||
|
||||
void DBusOpenFileDialog::SetDefaultExtension(const std::string& extension)
|
||||
{
|
||||
defaultExt = extension;
|
||||
}
|
||||
|
||||
void DBusOpenFileDialog::AddFilter(const std::string &filter_description, const std::string &filter_extension)
|
||||
{
|
||||
filters.push_back({ filter_description, filter_extension });
|
||||
}
|
||||
|
||||
void DBusOpenFileDialog::ClearFilters()
|
||||
{
|
||||
filters.clear();
|
||||
}
|
||||
|
||||
void DBusOpenFileDialog::SetFilterIndex(int filter_index)
|
||||
{
|
||||
this->filter_index = filter_index;
|
||||
}
|
||||
|
||||
void DBusOpenFileDialog::SetInitialDirectory(const std::string &path)
|
||||
{
|
||||
initialDirectory = path;
|
||||
}
|
||||
|
||||
void DBusOpenFileDialog::SetTitle(const std::string &title)
|
||||
{
|
||||
this->title = title;
|
||||
}
|
||||
|
||||
bool DBusOpenFileDialog::Show()
|
||||
{
|
||||
// https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.FileChooser.html
|
||||
|
||||
dbus_bool_t bresult = {};
|
||||
DBusError error = {};
|
||||
dbus_error_init(&error);
|
||||
|
||||
DBusConnection* connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
|
||||
if (!connection)
|
||||
{
|
||||
dbus_error_free(&error);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string busname = dbus_bus_get_unique_name(connection);
|
||||
|
||||
DBusMessage* request = dbus_message_new_method_call("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.FileChooser", "OpenFile");
|
||||
if (!request)
|
||||
{
|
||||
dbus_connection_unref(connection);
|
||||
dbus_error_free(&error);
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* parentWindow = ownerHandle.c_str();
|
||||
const char* title = this->title.c_str();
|
||||
|
||||
DBusMessageIter requestArgs = {};
|
||||
dbus_message_iter_init_append(request, &requestArgs);
|
||||
|
||||
bresult = dbus_message_iter_append_basic(&requestArgs, DBUS_TYPE_STRING, &parentWindow);
|
||||
bresult = dbus_message_iter_append_basic(&requestArgs, DBUS_TYPE_STRING, &title);
|
||||
|
||||
DBusMessageIter requestOptions = {};
|
||||
bresult = dbus_message_iter_open_container(&requestArgs, DBUS_TYPE_ARRAY, "{sv}", &requestOptions);
|
||||
|
||||
// handle_token - s race condition prevention for signal (/org/freedesktop/portal/desktop/request/SENDER/TOKEN)
|
||||
// accept_label - s text label for the OK button
|
||||
// modal - b makes dialog modal. Defaults to true. Not really sure what it means since nothing stayed modal on my computer
|
||||
// directory - b open folder mode, added in version 3
|
||||
// filters - a(sa(us)) [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])]
|
||||
// current_filter - sa(us) filter from filters that should be current filter
|
||||
// choices - a(ssa(ss)s) list of serialized combo boxes to add to the file chooser
|
||||
// current_name - s suggested name
|
||||
|
||||
// current_folder - ay
|
||||
if (!initialDirectory.empty())
|
||||
{
|
||||
// Note: the docs unfortunately says "The portal implementation is free to ignore this option"
|
||||
|
||||
const char* key = "current_folder";
|
||||
const char* value = initialDirectory.c_str();
|
||||
int valueCount = (int)initialDirectory.size() + 1;
|
||||
|
||||
DBusMessageIter entry = {};
|
||||
bresult = dbus_message_iter_open_container(&requestOptions, DBUS_TYPE_DICT_ENTRY, nullptr, &entry);
|
||||
bresult = dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
|
||||
|
||||
DBusMessageIter variant = {};
|
||||
DBusMessageIter array = {};
|
||||
bresult = dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "ay", &variant);
|
||||
bresult = dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &array);
|
||||
bresult = dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &value, valueCount);
|
||||
bresult = dbus_message_iter_close_container(&variant, &array);
|
||||
bresult = dbus_message_iter_close_container(&entry, &variant);
|
||||
|
||||
bresult = dbus_message_iter_close_container(&requestOptions, &entry);
|
||||
}
|
||||
|
||||
// multiple - b
|
||||
{
|
||||
const char* key = "multiple";
|
||||
dbus_bool_t value = multiSelect;
|
||||
|
||||
DBusMessageIter entry = {};
|
||||
bresult = dbus_message_iter_open_container(&requestOptions, DBUS_TYPE_DICT_ENTRY, nullptr, &entry);
|
||||
bresult = dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
|
||||
|
||||
DBusMessageIter variant = {};
|
||||
bresult = dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &variant);
|
||||
bresult = dbus_message_iter_append_basic(&variant, DBUS_TYPE_BOOLEAN, &value);
|
||||
bresult = dbus_message_iter_close_container(&entry, &variant);
|
||||
|
||||
bresult = dbus_message_iter_close_container(&requestOptions, &entry);
|
||||
}
|
||||
|
||||
bresult = dbus_message_iter_close_container(&requestArgs, &requestOptions);
|
||||
|
||||
DBusMessage* response = dbus_connection_send_with_reply_and_block(connection, request, DBUS_TIMEOUT_USE_DEFAULT, &error);
|
||||
if (!response)
|
||||
{
|
||||
dbus_message_unref(request);
|
||||
dbus_connection_unref(connection);
|
||||
dbus_error_free(&error);
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* handle = nullptr;
|
||||
bresult = dbus_message_get_args(response, &error, DBUS_TYPE_OBJECT_PATH, &handle, DBUS_TYPE_INVALID);
|
||||
if (!bresult)
|
||||
{
|
||||
dbus_message_unref(response);
|
||||
dbus_message_unref(request);
|
||||
dbus_connection_unref(connection);
|
||||
dbus_error_free(&error);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string signalObjectPath = handle;
|
||||
|
||||
dbus_message_unref(response);
|
||||
dbus_message_unref(request);
|
||||
|
||||
std::string rule = "type='signal',interface='org.freedesktop.portal.Request',member='Response',path='" + signalObjectPath + "'";
|
||||
dbus_bus_add_match(connection, rule.c_str(), &error);
|
||||
|
||||
// Wait for the response signal
|
||||
//
|
||||
// To do: process the run loop while we wait
|
||||
//
|
||||
DBusMessage* signalmsg = nullptr;
|
||||
while (!signalmsg && dbus_connection_read_write(connection, -1))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
DBusMessage* message = dbus_connection_pop_message(connection);
|
||||
if (!message)
|
||||
break;
|
||||
|
||||
if (dbus_message_is_signal(message, "org.freedesktop.portal.Request", "Response") && dbus_message_get_path(message) == signalObjectPath)
|
||||
{
|
||||
signalmsg = message;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
dbus_message_unref(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
dbus_bus_remove_match(connection, rule.c_str(), &error);
|
||||
|
||||
// Read the response
|
||||
|
||||
dbus_uint32_t responseCode = 0;
|
||||
std::vector<std::string> uris;
|
||||
|
||||
DBusMessageIter signalArgs;
|
||||
bresult = dbus_message_iter_init(signalmsg, &signalArgs);
|
||||
|
||||
// response code - u
|
||||
if (dbus_message_iter_get_arg_type(&signalArgs) == DBUS_TYPE_UINT32)
|
||||
{
|
||||
dbus_message_iter_get_basic(&signalArgs, &responseCode);
|
||||
}
|
||||
dbus_message_iter_next(&signalArgs);
|
||||
|
||||
// results - a{sv}
|
||||
if (dbus_message_iter_get_arg_type(&signalArgs) == DBUS_TYPE_ARRAY)
|
||||
{
|
||||
DBusMessageIter resultsArray = {};
|
||||
dbus_message_iter_recurse(&signalArgs, &resultsArray);
|
||||
while (true)
|
||||
{
|
||||
int type = dbus_message_iter_get_arg_type(&resultsArray);
|
||||
if (type != DBUS_TYPE_DICT_ENTRY)
|
||||
break;
|
||||
|
||||
DBusMessageIter entry = {};
|
||||
dbus_message_iter_recurse(&resultsArray, &entry);
|
||||
|
||||
const char* key = nullptr;
|
||||
dbus_message_iter_get_basic(&entry, &key);
|
||||
dbus_message_iter_next(&entry);
|
||||
|
||||
DBusMessageIter value = {};
|
||||
dbus_message_iter_recurse(&entry, &value);
|
||||
|
||||
std::string k = key;
|
||||
if (k == "uris" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_ARRAY) // as
|
||||
{
|
||||
DBusMessageIter uriArray = {};
|
||||
dbus_message_iter_recurse(&value, &uriArray);
|
||||
while (true)
|
||||
{
|
||||
int type = dbus_message_iter_get_arg_type(&uriArray);
|
||||
if (type != DBUS_TYPE_STRING)
|
||||
break;
|
||||
|
||||
const char* uri = nullptr;
|
||||
dbus_message_iter_get_basic(&uriArray, &uri);
|
||||
uris.push_back(uri);
|
||||
|
||||
dbus_message_iter_next(&uriArray);
|
||||
}
|
||||
}
|
||||
else if (k == "choices" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_ARRAY) // a(ss)
|
||||
{
|
||||
// An array of pairs of strings,
|
||||
// the first string being the ID of a combobox that was passed into this call,
|
||||
// the second string being the selected option.
|
||||
}
|
||||
else if (k == "current_filter" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_STRUCT) // sa(us)
|
||||
{
|
||||
// The filter that was selected.
|
||||
// This may match a filter in the filter list or another filter that was applied unconditionally.
|
||||
}
|
||||
|
||||
dbus_message_iter_next(&entry);
|
||||
dbus_message_iter_next(&resultsArray);
|
||||
}
|
||||
}
|
||||
dbus_message_iter_next(&signalArgs);
|
||||
|
||||
dbus_message_unref(signalmsg);
|
||||
dbus_connection_unref(connection);
|
||||
dbus_error_free(&error);
|
||||
|
||||
if (responseCode != 0) // User cancelled
|
||||
return false;
|
||||
|
||||
for (const std::string& uri : uris)
|
||||
{
|
||||
if (uri.size() > 7 && uri.substr(0, 7) == "file://")
|
||||
outputFilenames.push_back(uri.substr(7));
|
||||
}
|
||||
|
||||
return !uris.empty();
|
||||
}
|
||||
38
src/window/dbus/dbus_open_file_dialog.h
Normal file
38
src/window/dbus/dbus_open_file_dialog.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include "systemdialogs/open_file_dialog.h"
|
||||
|
||||
class DBusOpenFileDialog : public OpenFileDialog
|
||||
{
|
||||
public:
|
||||
DBusOpenFileDialog(std::string ownerHandle);
|
||||
|
||||
std::string Filename() const override;
|
||||
std::vector<std::string> Filenames() const override;
|
||||
void SetMultiSelect(bool multiselect) override;
|
||||
void SetFilename(const std::string &filename) override;
|
||||
void SetDefaultExtension(const std::string& extension) override;
|
||||
void AddFilter(const std::string &filter_description, const std::string &filter_extension) override;
|
||||
void ClearFilters() override;
|
||||
void SetFilterIndex(int filter_index) override;
|
||||
void SetInitialDirectory(const std::string &path) override;
|
||||
void SetTitle(const std::string& newtitle) override;
|
||||
bool Show() override;
|
||||
|
||||
private:
|
||||
std::string ownerHandle;
|
||||
std::string title;
|
||||
std::string initialDirectory;
|
||||
std::string inputFilename;
|
||||
std::string defaultExt;
|
||||
std::vector<std::string> outputFilenames;
|
||||
bool multiSelect = false;
|
||||
|
||||
struct Filter
|
||||
{
|
||||
std::string description;
|
||||
std::string extension;
|
||||
};
|
||||
std::vector<Filter> filters;
|
||||
int filter_index = 0;
|
||||
};
|
||||
26
src/window/dbus/dbus_open_folder_dialog.cpp
Normal file
26
src/window/dbus/dbus_open_folder_dialog.cpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
#include "dbus_open_folder_dialog.h"
|
||||
|
||||
DBusOpenFolderDialog::DBusOpenFolderDialog(std::string ownerHandle) : ownerHandle(ownerHandle)
|
||||
{
|
||||
}
|
||||
|
||||
bool DBusOpenFolderDialog::Show()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string DBusOpenFolderDialog::SelectedPath() const
|
||||
{
|
||||
return selected_path;
|
||||
}
|
||||
|
||||
void DBusOpenFolderDialog::SetInitialDirectory(const std::string& path)
|
||||
{
|
||||
initial_directory = path;
|
||||
}
|
||||
|
||||
void DBusOpenFolderDialog::SetTitle(const std::string& newtitle)
|
||||
{
|
||||
title = newtitle;
|
||||
}
|
||||
21
src/window/dbus/dbus_open_folder_dialog.h
Normal file
21
src/window/dbus/dbus_open_folder_dialog.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include "systemdialogs/open_folder_dialog.h"
|
||||
|
||||
class DBusOpenFolderDialog : public OpenFolderDialog
|
||||
{
|
||||
public:
|
||||
DBusOpenFolderDialog(std::string ownerHandle);
|
||||
|
||||
bool Show() override;
|
||||
std::string SelectedPath() const override;
|
||||
void SetInitialDirectory(const std::string& path) override;
|
||||
void SetTitle(const std::string& newtitle) override;
|
||||
|
||||
private:
|
||||
std::string ownerHandle;
|
||||
|
||||
std::string selected_path;
|
||||
std::string initial_directory;
|
||||
std::string title;
|
||||
};
|
||||
54
src/window/dbus/dbus_save_file_dialog.cpp
Normal file
54
src/window/dbus/dbus_save_file_dialog.cpp
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
|
||||
#include "dbus_save_file_dialog.h"
|
||||
|
||||
DBusSaveFileDialog::DBusSaveFileDialog(std::string ownerHandle) : ownerHandle(ownerHandle)
|
||||
{
|
||||
}
|
||||
|
||||
bool DBusSaveFileDialog::Show()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string DBusSaveFileDialog::Filename() const
|
||||
{
|
||||
return filename;
|
||||
}
|
||||
|
||||
void DBusSaveFileDialog::SetFilename(const std::string& filename)
|
||||
{
|
||||
initial_filename = filename;
|
||||
}
|
||||
|
||||
void DBusSaveFileDialog::AddFilter(const std::string& filter_description, const std::string& filter_extension)
|
||||
{
|
||||
Filter f;
|
||||
f.description = filter_description;
|
||||
f.extension = filter_extension;
|
||||
filters.push_back(std::move(f));
|
||||
}
|
||||
|
||||
void DBusSaveFileDialog::ClearFilters()
|
||||
{
|
||||
filters.clear();
|
||||
}
|
||||
|
||||
void DBusSaveFileDialog::SetFilterIndex(int filter_index)
|
||||
{
|
||||
filterindex = filter_index;
|
||||
}
|
||||
|
||||
void DBusSaveFileDialog::SetInitialDirectory(const std::string& path)
|
||||
{
|
||||
initial_directory = path;
|
||||
}
|
||||
|
||||
void DBusSaveFileDialog::SetTitle(const std::string& newtitle)
|
||||
{
|
||||
title = newtitle;
|
||||
}
|
||||
|
||||
void DBusSaveFileDialog::SetDefaultExtension(const std::string& extension)
|
||||
{
|
||||
defaultext = extension;
|
||||
}
|
||||
37
src/window/dbus/dbus_save_file_dialog.h
Normal file
37
src/window/dbus/dbus_save_file_dialog.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include "systemdialogs/save_file_dialog.h"
|
||||
|
||||
class DBusSaveFileDialog : public SaveFileDialog
|
||||
{
|
||||
public:
|
||||
DBusSaveFileDialog(std::string ownerHandle);
|
||||
|
||||
bool Show() override;
|
||||
std::string Filename() const override;
|
||||
void SetFilename(const std::string& filename) override;
|
||||
void AddFilter(const std::string& filter_description, const std::string& filter_extension) override;
|
||||
void ClearFilters() override;
|
||||
void SetFilterIndex(int filter_index) override;
|
||||
void SetInitialDirectory(const std::string& path) override;
|
||||
void SetTitle(const std::string& newtitle) override;
|
||||
void SetDefaultExtension(const std::string& extension) override;
|
||||
|
||||
private:
|
||||
std::string ownerHandle;
|
||||
|
||||
std::string filename;
|
||||
std::string initial_directory;
|
||||
std::string initial_filename;
|
||||
std::string title;
|
||||
std::vector<std::string> filenames;
|
||||
|
||||
struct Filter
|
||||
{
|
||||
std::string description;
|
||||
std::string extension;
|
||||
};
|
||||
std::vector<Filter> filters;
|
||||
int filterindex = 0;
|
||||
std::string defaultext;
|
||||
};
|
||||
98
src/window/sdl2/sdl2_display_backend.cpp
Normal file
98
src/window/sdl2/sdl2_display_backend.cpp
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
|
||||
#include "sdl2_display_backend.h"
|
||||
#include "sdl2_display_window.h"
|
||||
#include <stdexcept>
|
||||
#include <SDL2/SDL_video.h>
|
||||
#ifndef WIN32
|
||||
#include <dlfcn.h>
|
||||
#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<DisplayWindow> SDL2DisplayBackend::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI)
|
||||
{
|
||||
return std::make_unique<SDL2DisplayWindow>(windowHost, popupWindow, static_cast<SDL2DisplayWindow*>(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<void()> onTimer)
|
||||
{
|
||||
return SDL2DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer));
|
||||
}
|
||||
|
||||
void SDL2DisplayBackend::StopTimer(void* timerID)
|
||||
{
|
||||
SDL2DisplayWindow::StopTimer(timerID);
|
||||
}
|
||||
24
src/window/sdl2/sdl2_display_backend.h
Normal file
24
src/window/sdl2/sdl2_display_backend.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "window/window.h"
|
||||
|
||||
class SDL2DisplayBackend : public DisplayBackend
|
||||
{
|
||||
public:
|
||||
SDL2DisplayBackend();
|
||||
|
||||
std::unique_ptr<DisplayWindow> Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) override;
|
||||
void ProcessEvents() override;
|
||||
void RunLoop() override;
|
||||
void ExitLoop() override;
|
||||
|
||||
void* StartTimer(int timeoutMilliseconds, std::function<void()> onTimer) override;
|
||||
void StopTimer(void* timerID) override;
|
||||
|
||||
Size GetScreenSize() override;
|
||||
|
||||
bool IsSDL2() override { return true; }
|
||||
|
||||
private:
|
||||
double UIScale = 1.0;
|
||||
};
|
||||
792
src/window/sdl2/sdl2_display_window.cpp
Normal file
792
src/window/sdl2/sdl2_display_window.cpp
Normal file
|
|
@ -0,0 +1,792 @@
|
|||
|
||||
#include "sdl2_display_window.h"
|
||||
#include <stdexcept>
|
||||
#include <SDL2/SDL_vulkan.h>
|
||||
|
||||
Uint32 SDL2DisplayWindow::PaintEventNumber = 0xffffffff;
|
||||
bool SDL2DisplayWindow::ExitRunLoop;
|
||||
std::unordered_map<int, SDL2DisplayWindow*> SDL2DisplayWindow::WindowList;
|
||||
|
||||
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<std::string> SDL2DisplayWindow::GetVulkanInstanceExtensions()
|
||||
{
|
||||
unsigned int extCount = 0;
|
||||
SDL_Vulkan_GetInstanceExtensions(Handle.window, &extCount, nullptr);
|
||||
std::vector<const char*> extNames(extCount);
|
||||
SDL_Vulkan_GetInstanceExtensions(Handle.window, &extCount, extNames.data());
|
||||
|
||||
std::vector<std::string> result;
|
||||
result.reserve(extNames.size());
|
||||
for (const char* ext : extNames)
|
||||
result.emplace_back(ext);
|
||||
return result;
|
||||
}
|
||||
|
||||
VkSurfaceKHR SDL2DisplayWindow::CreateVulkanSurface(VkInstance instance)
|
||||
{
|
||||
VkSurfaceKHR surfaceHandle = {};
|
||||
SDL_Vulkan_CreateSurface(Handle.window, instance, &surfaceHandle);
|
||||
if (surfaceHandle)
|
||||
throw std::runtime_error("Could not create vulkan surface");
|
||||
return surfaceHandle;
|
||||
}
|
||||
|
||||
void SDL2DisplayWindow::SetWindowTitle(const std::string& text)
|
||||
{
|
||||
SDL_SetWindowTitle(Handle.window, text.c_str());
|
||||
}
|
||||
|
||||
void SDL2DisplayWindow::SetWindowFrame(const Rect& box)
|
||||
{
|
||||
// SDL2 doesn't really seem to have an API for this.
|
||||
// The docs aren't clear what you're setting when calling SDL_SetWindowSize.
|
||||
SetClientFrame(box);
|
||||
}
|
||||
|
||||
void SDL2DisplayWindow::SetClientFrame(const Rect& box)
|
||||
{
|
||||
// Is there a way to set both in one call?
|
||||
|
||||
double uiscale = GetDpiScale();
|
||||
int x = (int)std::round(box.x * uiscale);
|
||||
int y = (int)std::round(box.y * uiscale);
|
||||
int w = (int)std::round(box.width * uiscale);
|
||||
int h = (int)std::round(box.height * uiscale);
|
||||
|
||||
SDL_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<void *, std::function<void()>> SDL2DisplayWindow::Timers;
|
||||
std::unordered_map<void *, void *> 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<void()> 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;
|
||||
}
|
||||
}
|
||||
112
src/window/sdl2/sdl2_display_window.h
Normal file
112
src/window/sdl2/sdl2_display_window.h
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
#include <zwidget/window/window.h>
|
||||
#include <zwidget/window/sdl2nativehandle.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
class SDL2DisplayWindow : public DisplayWindow
|
||||
{
|
||||
public:
|
||||
SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, SDL2DisplayWindow* owner, 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<std::string> 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<typename T>
|
||||
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<void()> 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<int, SDL2DisplayWindow*> WindowList;
|
||||
|
||||
static std::unordered_map<void *, void *> TimerHandles;
|
||||
static std::unordered_map<void *, std::function<void()>> Timers;
|
||||
static unsigned long TimerIDs;
|
||||
static Uint32 TimerEventNumber;
|
||||
};
|
||||
64
src/window/stub/stub_open_file_dialog.cpp
Normal file
64
src/window/stub/stub_open_file_dialog.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
|
||||
#include "stub_open_file_dialog.h"
|
||||
|
||||
StubOpenFileDialog::StubOpenFileDialog(DisplayWindow* owner) : owner(owner)
|
||||
{
|
||||
}
|
||||
|
||||
bool StubOpenFileDialog::Show()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string StubOpenFileDialog::Filename() const
|
||||
{
|
||||
return !filenames.empty() ? filenames.front() : std::string();
|
||||
}
|
||||
|
||||
std::vector<std::string> StubOpenFileDialog::Filenames() const
|
||||
{
|
||||
return filenames;
|
||||
}
|
||||
|
||||
void StubOpenFileDialog::SetMultiSelect(bool new_multi_select)
|
||||
{
|
||||
multi_select = new_multi_select;
|
||||
}
|
||||
|
||||
void StubOpenFileDialog::SetFilename(const std::string& filename)
|
||||
{
|
||||
initial_filename = filename;
|
||||
}
|
||||
|
||||
void StubOpenFileDialog::AddFilter(const std::string& filter_description, const std::string& filter_extension)
|
||||
{
|
||||
Filter f;
|
||||
f.description = filter_description;
|
||||
f.extension = filter_extension;
|
||||
filters.push_back(std::move(f));
|
||||
}
|
||||
|
||||
void StubOpenFileDialog::ClearFilters()
|
||||
{
|
||||
filters.clear();
|
||||
}
|
||||
|
||||
void StubOpenFileDialog::SetFilterIndex(int filter_index)
|
||||
{
|
||||
filterindex = filter_index;
|
||||
}
|
||||
|
||||
void StubOpenFileDialog::SetInitialDirectory(const std::string& path)
|
||||
{
|
||||
initial_directory = path;
|
||||
}
|
||||
|
||||
void StubOpenFileDialog::SetTitle(const std::string& newtitle)
|
||||
{
|
||||
title = newtitle;
|
||||
}
|
||||
|
||||
void StubOpenFileDialog::SetDefaultExtension(const std::string& extension)
|
||||
{
|
||||
defaultext = extension;
|
||||
}
|
||||
41
src/window/stub/stub_open_file_dialog.h
Normal file
41
src/window/stub/stub_open_file_dialog.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include "systemdialogs/open_file_dialog.h"
|
||||
|
||||
class DisplayWindow;
|
||||
|
||||
class StubOpenFileDialog : public OpenFileDialog
|
||||
{
|
||||
public:
|
||||
StubOpenFileDialog(DisplayWindow* owner);
|
||||
|
||||
bool Show() override;
|
||||
std::string Filename() const override;
|
||||
std::vector<std::string> Filenames() const override;
|
||||
void SetMultiSelect(bool new_multi_select) override;
|
||||
void SetFilename(const std::string& filename) override;
|
||||
void AddFilter(const std::string& filter_description, const std::string& filter_extension) override;
|
||||
void ClearFilters() override;
|
||||
void SetFilterIndex(int filter_index) override;
|
||||
void SetInitialDirectory(const std::string& path) override;
|
||||
void SetTitle(const std::string& newtitle) override;
|
||||
void SetDefaultExtension(const std::string& extension) override;
|
||||
|
||||
private:
|
||||
DisplayWindow* owner = nullptr;
|
||||
|
||||
std::string initial_directory;
|
||||
std::string initial_filename;
|
||||
std::string title;
|
||||
std::vector<std::string> filenames;
|
||||
bool multi_select = false;
|
||||
|
||||
struct Filter
|
||||
{
|
||||
std::string description;
|
||||
std::string extension;
|
||||
};
|
||||
std::vector<Filter> filters;
|
||||
int filterindex = 0;
|
||||
std::string defaultext;
|
||||
};
|
||||
26
src/window/stub/stub_open_folder_dialog.cpp
Normal file
26
src/window/stub/stub_open_folder_dialog.cpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
#include "stub_open_folder_dialog.h"
|
||||
|
||||
StubOpenFolderDialog::StubOpenFolderDialog(DisplayWindow* owner) : owner(owner)
|
||||
{
|
||||
}
|
||||
|
||||
bool StubOpenFolderDialog::Show()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string StubOpenFolderDialog::SelectedPath() const
|
||||
{
|
||||
return selected_path;
|
||||
}
|
||||
|
||||
void StubOpenFolderDialog::SetInitialDirectory(const std::string& path)
|
||||
{
|
||||
initial_directory = path;
|
||||
}
|
||||
|
||||
void StubOpenFolderDialog::SetTitle(const std::string& newtitle)
|
||||
{
|
||||
title = newtitle;
|
||||
}
|
||||
23
src/window/stub/stub_open_folder_dialog.h
Normal file
23
src/window/stub/stub_open_folder_dialog.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include "systemdialogs/open_folder_dialog.h"
|
||||
|
||||
class DisplayWindow;
|
||||
|
||||
class StubOpenFolderDialog : public OpenFolderDialog
|
||||
{
|
||||
public:
|
||||
StubOpenFolderDialog(DisplayWindow* owner);
|
||||
|
||||
bool Show() override;
|
||||
std::string SelectedPath() const override;
|
||||
void SetInitialDirectory(const std::string& path) override;
|
||||
void SetTitle(const std::string& newtitle) override;
|
||||
|
||||
private:
|
||||
DisplayWindow* owner = nullptr;
|
||||
|
||||
std::string selected_path;
|
||||
std::string initial_directory;
|
||||
std::string title;
|
||||
};
|
||||
54
src/window/stub/stub_save_file_dialog.cpp
Normal file
54
src/window/stub/stub_save_file_dialog.cpp
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
|
||||
#include "stub_save_file_dialog.h"
|
||||
|
||||
StubSaveFileDialog::StubSaveFileDialog(DisplayWindow* owner) : owner(owner)
|
||||
{
|
||||
}
|
||||
|
||||
bool StubSaveFileDialog::Show()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string StubSaveFileDialog::Filename() const
|
||||
{
|
||||
return filename;
|
||||
}
|
||||
|
||||
void StubSaveFileDialog::SetFilename(const std::string& filename)
|
||||
{
|
||||
initial_filename = filename;
|
||||
}
|
||||
|
||||
void StubSaveFileDialog::AddFilter(const std::string& filter_description, const std::string& filter_extension)
|
||||
{
|
||||
Filter f;
|
||||
f.description = filter_description;
|
||||
f.extension = filter_extension;
|
||||
filters.push_back(std::move(f));
|
||||
}
|
||||
|
||||
void StubSaveFileDialog::ClearFilters()
|
||||
{
|
||||
filters.clear();
|
||||
}
|
||||
|
||||
void StubSaveFileDialog::SetFilterIndex(int filter_index)
|
||||
{
|
||||
filterindex = filter_index;
|
||||
}
|
||||
|
||||
void StubSaveFileDialog::SetInitialDirectory(const std::string& path)
|
||||
{
|
||||
initial_directory = path;
|
||||
}
|
||||
|
||||
void StubSaveFileDialog::SetTitle(const std::string& newtitle)
|
||||
{
|
||||
title = newtitle;
|
||||
}
|
||||
|
||||
void StubSaveFileDialog::SetDefaultExtension(const std::string& extension)
|
||||
{
|
||||
defaultext = extension;
|
||||
}
|
||||
39
src/window/stub/stub_save_file_dialog.h
Normal file
39
src/window/stub/stub_save_file_dialog.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#include "systemdialogs/save_file_dialog.h"
|
||||
|
||||
class DisplayWindow;
|
||||
|
||||
class StubSaveFileDialog : public SaveFileDialog
|
||||
{
|
||||
public:
|
||||
StubSaveFileDialog(DisplayWindow* owner);
|
||||
|
||||
bool Show() override;
|
||||
std::string Filename() const override;
|
||||
void SetFilename(const std::string& filename) override;
|
||||
void AddFilter(const std::string& filter_description, const std::string& filter_extension) override;
|
||||
void ClearFilters() override;
|
||||
void SetFilterIndex(int filter_index) override;
|
||||
void SetInitialDirectory(const std::string& path) override;
|
||||
void SetTitle(const std::string& newtitle) override;
|
||||
void SetDefaultExtension(const std::string& extension) override;
|
||||
|
||||
private:
|
||||
DisplayWindow* owner = nullptr;
|
||||
|
||||
std::string filename;
|
||||
std::string initial_directory;
|
||||
std::string initial_filename;
|
||||
std::string title;
|
||||
std::vector<std::string> filenames;
|
||||
|
||||
struct Filter
|
||||
{
|
||||
std::string description;
|
||||
std::string extension;
|
||||
};
|
||||
std::vector<Filter> filters;
|
||||
int filterindex = 0;
|
||||
std::string defaultext;
|
||||
};
|
||||
1150
src/window/wayland/wayland_display_backend.cpp
Normal file
1150
src/window/wayland/wayland_display_backend.cpp
Normal file
File diff suppressed because it is too large
Load diff
168
src/window/wayland/wayland_display_backend.h
Normal file
168
src/window/wayland/wayland_display_backend.h
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
#pragma once
|
||||
|
||||
#include "window/window.h"
|
||||
#include "window/ztimer/ztimer.h"
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include <wayland-client.hpp>
|
||||
#include <wayland-client-protocol-extra.hpp>
|
||||
#include <wayland-client-protocol-unstable.hpp>
|
||||
#include <wayland-cursor.hpp>
|
||||
#include "wl_fractional_scaling_protocol.hpp"
|
||||
#include <linux/input.h>
|
||||
#include <poll.h>
|
||||
#include <map>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
|
||||
static short poll_single(int fd, short events, int timeout) {
|
||||
pollfd pfd { .fd = fd, .events = events, .revents = 0 };
|
||||
if (0 > poll(&pfd, 1, timeout)) {
|
||||
throw std::runtime_error("poll() failed");
|
||||
}
|
||||
|
||||
return pfd.revents;
|
||||
}
|
||||
|
||||
enum pointer_event_mask {
|
||||
POINTER_EVENT_ENTER = 1 << 0,
|
||||
POINTER_EVENT_LEAVE = 1 << 1,
|
||||
POINTER_EVENT_MOTION = 1 << 2,
|
||||
POINTER_EVENT_BUTTON = 1 << 3,
|
||||
POINTER_EVENT_AXIS = 1 << 4,
|
||||
POINTER_EVENT_AXIS_SOURCE = 1 << 5,
|
||||
POINTER_EVENT_AXIS_STOP = 1 << 6,
|
||||
POINTER_EVENT_AXIS_DISCRETE = 1 << 7,
|
||||
POINTER_EVENT_AXIS_120 = 1 << 8,
|
||||
POINTER_EVENT_RELATIVE_MOTION = 1 << 9,
|
||||
};
|
||||
|
||||
struct WaylandPointerEvent
|
||||
{
|
||||
uint32_t event_mask;
|
||||
double surfaceX, surfaceY;
|
||||
double dx, dy;
|
||||
uint32_t button;
|
||||
wayland::pointer_button_state state;
|
||||
uint32_t time;
|
||||
uint32_t serial;
|
||||
struct {
|
||||
bool valid;
|
||||
double value;
|
||||
int32_t discrete;
|
||||
int32_t value120;
|
||||
} axes[2];
|
||||
wayland::pointer_axis_source axis_source;
|
||||
};
|
||||
|
||||
class WaylandDisplayWindow;
|
||||
|
||||
class WaylandDisplayBackend : public DisplayBackend
|
||||
{
|
||||
public:
|
||||
WaylandDisplayBackend();
|
||||
~WaylandDisplayBackend();
|
||||
|
||||
std::unique_ptr<DisplayWindow> Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) override;
|
||||
void ProcessEvents() override;
|
||||
void RunLoop() override;
|
||||
void ExitLoop() override;
|
||||
|
||||
void* StartTimer(int timeoutMilliseconds, std::function<void()> onTimer) override;
|
||||
void StopTimer(void* timerID) override;
|
||||
|
||||
Size GetScreenSize() override;
|
||||
|
||||
bool IsWayland() override { return true; }
|
||||
|
||||
void OnWindowCreated(WaylandDisplayWindow* window);
|
||||
void OnWindowDestroyed(WaylandDisplayWindow* window);
|
||||
|
||||
void SetCursor(StandardCursor cursor);
|
||||
void ShowCursor(bool enable);
|
||||
bool GetKeyState(InputKey key);
|
||||
|
||||
#ifdef USE_DBUS
|
||||
std::unique_ptr<OpenFileDialog> CreateOpenFileDialog(DisplayWindow* owner) override;
|
||||
std::unique_ptr<SaveFileDialog> CreateSaveFileDialog(DisplayWindow* owner) override;
|
||||
std::unique_ptr<OpenFolderDialog> CreateOpenFolderDialog(DisplayWindow* owner) override;
|
||||
#endif
|
||||
|
||||
bool exitRunLoop = false;
|
||||
Size s_ScreenSize = Size(0, 0);
|
||||
wayland::display_t s_waylandDisplay = wayland::display_t();
|
||||
wayland::registry_t s_waylandRegistry;
|
||||
std::vector<WaylandDisplayWindow*> s_Windows;
|
||||
WaylandDisplayWindow* m_FocusWindow = nullptr;
|
||||
WaylandDisplayWindow* m_MouseFocusWindow = nullptr; // Mouse focus should be tracked separately.
|
||||
|
||||
wayland::compositor_t m_waylandCompositor;
|
||||
wayland::shm_t m_waylandSHM;
|
||||
wayland::seat_t m_waylandSeat;
|
||||
wayland::output_t m_waylandOutput;
|
||||
wayland::data_device_manager_t m_DataDeviceManager;
|
||||
wayland::xdg_wm_base_t m_XDGWMBase;
|
||||
wayland::zwp_pointer_constraints_v1_t m_PointerConstraints;
|
||||
wayland::xdg_activation_v1_t m_XDGActivation;
|
||||
wayland::zxdg_decoration_manager_v1_t m_XDGDecorationManager;
|
||||
wayland::fractional_scale_manager_v1_t m_FractionalScaleManager;
|
||||
wayland::zxdg_output_manager_v1_t m_XDGOutputManager;
|
||||
wayland::zxdg_output_v1_t m_XDGOutput;
|
||||
wayland::zxdg_exporter_v2_t m_XDGExporter;
|
||||
|
||||
wayland::keyboard_t m_waylandKeyboard;
|
||||
wayland::pointer_t m_waylandPointer;
|
||||
|
||||
wayland::zwp_relative_pointer_manager_v1_t m_RelativePointerManager;
|
||||
wayland::zwp_relative_pointer_v1_t m_RelativePointer;
|
||||
|
||||
wayland::cursor_image_t m_cursorImage;
|
||||
wayland::surface_t m_cursorSurface;
|
||||
wayland::buffer_t m_cursorBuffer;
|
||||
|
||||
std::map<InputKey, bool> inputKeyStates; // True when the key is pressed, false when isn't
|
||||
|
||||
bool IsMouseLocked() { return hasMouseLock; }
|
||||
void SetMouseLocked(bool val) { hasMouseLock = val; }
|
||||
|
||||
private:
|
||||
void CheckNeedsUpdate();
|
||||
void UpdateTimers();
|
||||
void ConnectDeviceEvents();
|
||||
void OnKeyboardKeyEvent(xkb_keysym_t xkbKeySym, wayland::keyboard_key_state state);
|
||||
void OnKeyboardCharEvent(const char* ch, wayland::keyboard_key_state state);
|
||||
void OnKeyboardDelayEnd();
|
||||
void OnKeyboardRepeat();
|
||||
void OnMouseEnterEvent(uint32_t serial);
|
||||
void OnMouseLeaveEvent();
|
||||
void OnMousePressEvent(InputKey button);
|
||||
void OnMouseReleaseEvent(InputKey button);
|
||||
void OnMouseMoveEvent(Point surfacePos);
|
||||
void OnMouseMoveRawEvent(int surfaceX, int surfaceY);
|
||||
void OnMouseWheelEvent(InputKey button);
|
||||
|
||||
InputKey XKBKeySymToInputKey(xkb_keysym_t keySym);
|
||||
InputKey LinuxInputEventCodeToInputKey(uint32_t inputCode);
|
||||
|
||||
std::string GetWaylandCursorName(StandardCursor cursor);
|
||||
|
||||
bool hasKeyboard = false;
|
||||
bool hasPointer = false;
|
||||
bool hasMouseLock = false;
|
||||
|
||||
ZTimer::TimePoint m_previousTime;
|
||||
ZTimer::TimePoint m_currentTime;
|
||||
|
||||
ZTimer m_keyboardDelayTimer;
|
||||
ZTimer m_keyboardRepeatTimer;
|
||||
|
||||
InputKey previousKey = {};
|
||||
std::string previousChars;
|
||||
|
||||
uint32_t m_KeyboardSerial = 0;
|
||||
|
||||
xkb_context* m_KeymapContext = nullptr;
|
||||
xkb_keymap* m_Keymap = nullptr;
|
||||
xkb_state* m_KeyboardState = nullptr;
|
||||
|
||||
WaylandPointerEvent currentPointerEvent = {0};
|
||||
};
|
||||
472
src/window/wayland/wayland_display_window.cpp
Normal file
472
src/window/wayland/wayland_display_window.cpp
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
#include "wayland_display_window.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <dlfcn.h>
|
||||
|
||||
WaylandDisplayWindow::WaylandDisplayWindow(WaylandDisplayBackend* backend, DisplayWindowHost* windowHost, bool popupWindow, WaylandDisplayWindow* owner, RenderAPI renderAPI)
|
||||
: backend(backend), m_owner(owner), windowHost(windowHost), m_PopupWindow(popupWindow), m_renderAPI(renderAPI)
|
||||
{
|
||||
m_AppSurface = backend->m_waylandCompositor.create_surface();
|
||||
|
||||
m_NativeHandle.display = backend->s_waylandDisplay;
|
||||
m_NativeHandle.surface = m_AppSurface;
|
||||
|
||||
if (backend->m_FractionalScaleManager)
|
||||
{
|
||||
m_FractionalScale = backend->m_FractionalScaleManager.get_fractional_scale(m_AppSurface);
|
||||
|
||||
m_FractionalScale.on_preferred_scale() = [&](uint32_t scale_numerator) {
|
||||
// parameter is the numerator of a fraction with the denominator of 120
|
||||
m_ScaleFactor = scale_numerator / 120.0;
|
||||
|
||||
m_NeedsUpdate = true;
|
||||
windowHost->OnWindowDpiScaleChanged();
|
||||
};
|
||||
}
|
||||
|
||||
m_XDGSurface = backend->m_XDGWMBase.get_xdg_surface(m_AppSurface);
|
||||
m_XDGSurface.on_configure() = [&] (uint32_t serial) {
|
||||
m_XDGSurface.ack_configure(serial);
|
||||
};
|
||||
|
||||
if (popupWindow)
|
||||
{
|
||||
InitializePopup();
|
||||
}
|
||||
else
|
||||
{
|
||||
InitializeToplevel();
|
||||
}
|
||||
|
||||
backend->OnWindowCreated(this);
|
||||
|
||||
this->DrawSurface();
|
||||
}
|
||||
|
||||
WaylandDisplayWindow::~WaylandDisplayWindow()
|
||||
{
|
||||
backend->OnWindowDestroyed(this);
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::InitializeToplevel()
|
||||
{
|
||||
m_XDGToplevel = m_XDGSurface.get_toplevel();
|
||||
m_XDGToplevel.set_title("ZWidget Window");
|
||||
|
||||
if (m_owner)
|
||||
m_XDGToplevel.set_parent(m_owner->m_XDGToplevel);
|
||||
|
||||
if (backend->m_XDGDecorationManager)
|
||||
{
|
||||
// Force server side decorations if possible
|
||||
m_XDGToplevelDecoration = backend->m_XDGDecorationManager.get_toplevel_decoration(m_XDGToplevel);
|
||||
m_XDGToplevelDecoration.set_mode(wayland::zxdg_toplevel_decoration_v1_mode::server_side);
|
||||
}
|
||||
|
||||
m_AppSurface.commit();
|
||||
|
||||
backend->s_waylandDisplay.roundtrip();
|
||||
|
||||
// These have to be added after the roundtrip
|
||||
m_XDGToplevel.on_configure() = [&] (int32_t width, int32_t height, wayland::array_t states) {
|
||||
OnXDGToplevelConfigureEvent(width, height);
|
||||
};
|
||||
|
||||
m_XDGToplevel.on_close() = [&] () {
|
||||
OnExitEvent();
|
||||
};
|
||||
|
||||
m_XDGToplevel.on_configure_bounds() = [this] (int32_t width, int32_t height)
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
m_XDGExported = backend->m_XDGExporter.export_toplevel(m_AppSurface);
|
||||
|
||||
m_XDGExported.on_handle() = [&] (std::string handleStr) {
|
||||
OnExportHandleEvent(handleStr);
|
||||
};
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::InitializePopup()
|
||||
{
|
||||
if (!m_owner)
|
||||
throw std::runtime_error("Popup window must have an owner!");
|
||||
|
||||
wayland::xdg_positioner_t popupPositioner = backend->m_XDGWMBase.create_positioner();
|
||||
|
||||
popupPositioner.set_anchor(wayland::xdg_positioner_anchor::bottom);
|
||||
popupPositioner.set_anchor_rect(0, 0, 1, 30);
|
||||
popupPositioner.set_size(1, 1);
|
||||
|
||||
m_XDGPopup = m_XDGSurface.get_popup(m_owner->m_XDGSurface, popupPositioner);
|
||||
|
||||
m_XDGPopup.on_configure() = [&] (int32_t x, int32_t y, int32_t width, int32_t height) {
|
||||
SetClientFrame(Rect::xywh(x, y, width, height));
|
||||
};
|
||||
|
||||
//m_XDGPopup.on_repositioned()
|
||||
|
||||
m_XDGPopup.on_popup_done() = [&] () {
|
||||
OnExitEvent();
|
||||
};
|
||||
|
||||
m_AppSurface.commit();
|
||||
|
||||
backend->s_waylandDisplay.roundtrip();
|
||||
}
|
||||
|
||||
|
||||
void WaylandDisplayWindow::SetWindowTitle(const std::string& text)
|
||||
{
|
||||
if (m_XDGToplevel)
|
||||
m_XDGToplevel.set_title(text);
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::SetWindowFrame(const Rect& box)
|
||||
{
|
||||
// Resizing will be shown on the next commit
|
||||
CreateBuffers(box.width, box.height);
|
||||
windowHost->OnWindowGeometryChanged();
|
||||
m_NeedsUpdate = true;
|
||||
m_AppSurface.commit();
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::SetClientFrame(const Rect& box)
|
||||
{
|
||||
SetWindowFrame(box);
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::Show()
|
||||
{
|
||||
m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0);
|
||||
m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height);
|
||||
m_AppSurface.commit();
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::ShowFullscreen()
|
||||
{
|
||||
if (m_XDGToplevel)
|
||||
{
|
||||
m_XDGToplevel.set_fullscreen(backend->m_waylandOutput);
|
||||
isFullscreen = true;
|
||||
}
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::ShowMaximized()
|
||||
{
|
||||
if (m_XDGToplevel)
|
||||
m_XDGToplevel.set_maximized();
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::ShowMinimized()
|
||||
{
|
||||
if (m_XDGToplevel)
|
||||
m_XDGToplevel.set_minimized();
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::ShowNormal()
|
||||
{
|
||||
if (m_XDGToplevel)
|
||||
m_XDGToplevel.unset_fullscreen();
|
||||
}
|
||||
|
||||
bool WaylandDisplayWindow::IsWindowFullscreen()
|
||||
{
|
||||
return isFullscreen;
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::Hide()
|
||||
{
|
||||
// Apparently this is how hiding a window works
|
||||
// By attaching a null buffer to the surface
|
||||
// See: https://lists.freedesktop.org/archives/wayland-devel/2017-November/035963.html
|
||||
m_AppSurface.attach(nullptr, 0, 0);
|
||||
m_AppSurface.commit();
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::Activate()
|
||||
{
|
||||
// To do: this needs to be in the backend instance if all windows share the activation token
|
||||
wayland::xdg_activation_token_v1_t xdgActivationToken = backend->m_XDGActivation.get_activation_token();
|
||||
|
||||
std::string tokenString;
|
||||
|
||||
xdgActivationToken.on_done() = [&tokenString] (std::string obtainedString) {
|
||||
tokenString = obtainedString;
|
||||
};
|
||||
|
||||
xdgActivationToken.set_surface(m_AppSurface);
|
||||
xdgActivationToken.commit(); // This will set our token string
|
||||
|
||||
backend->m_XDGActivation.activate(tokenString, m_AppSurface);
|
||||
backend->m_FocusWindow = this;
|
||||
backend->m_MouseFocusWindow = this;
|
||||
windowHost->OnWindowActivated();
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::ShowCursor(bool enable)
|
||||
{
|
||||
backend->ShowCursor(enable);
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::LockCursor()
|
||||
{
|
||||
m_LockedPointer = backend->m_PointerConstraints.lock_pointer(m_AppSurface, backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent);
|
||||
backend->SetMouseLocked(true);
|
||||
ShowCursor(false);
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::UnlockCursor()
|
||||
{
|
||||
if (m_LockedPointer)
|
||||
m_LockedPointer.proxy_release();
|
||||
backend->SetMouseLocked(false);
|
||||
ShowCursor(true);
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::CaptureMouse()
|
||||
{
|
||||
m_ConfinedPointer = backend->m_PointerConstraints.confine_pointer(GetWindowSurface(), backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent);
|
||||
ShowCursor(false);
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::ReleaseMouseCapture()
|
||||
{
|
||||
if (m_ConfinedPointer)
|
||||
m_ConfinedPointer.proxy_release();
|
||||
ShowCursor(true);
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::Update()
|
||||
{
|
||||
m_NeedsUpdate = true;
|
||||
}
|
||||
|
||||
bool WaylandDisplayWindow::GetKeyState(InputKey key)
|
||||
{
|
||||
return backend->GetKeyState(key);
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::SetCursor(StandardCursor cursor)
|
||||
{
|
||||
backend->SetCursor(cursor);
|
||||
}
|
||||
|
||||
Rect WaylandDisplayWindow::GetWindowFrame() const
|
||||
{
|
||||
return Rect(m_WindowGlobalPos.x, m_WindowGlobalPos.y, m_WindowSize.width, m_WindowSize.height);
|
||||
}
|
||||
|
||||
Size WaylandDisplayWindow::GetClientSize() const
|
||||
{
|
||||
return m_WindowSize / m_ScaleFactor;
|
||||
}
|
||||
|
||||
int WaylandDisplayWindow::GetPixelWidth() const
|
||||
{
|
||||
return m_WindowSize.width;
|
||||
}
|
||||
|
||||
int WaylandDisplayWindow::GetPixelHeight() const
|
||||
{
|
||||
return m_WindowSize.height;
|
||||
}
|
||||
|
||||
double WaylandDisplayWindow::GetDpiScale() const
|
||||
{
|
||||
return m_ScaleFactor;
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels)
|
||||
{
|
||||
// Make new buffers if the sizes don't match
|
||||
if (width != m_WindowSize.width || height != m_WindowSize.height)
|
||||
CreateBuffers(width, height);
|
||||
|
||||
std::memcpy(shared_mem->get_mem(), (void*)pixels, width * height * 4);
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::SetBorderColor(uint32_t bgra8)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::SetCaptionColor(uint32_t bgra8)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::SetCaptionTextColor(uint32_t bgra8)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
std::string WaylandDisplayWindow::GetClipboardText()
|
||||
{
|
||||
return m_ClipboardContents;
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::SetClipboardText(const std::string& text)
|
||||
{
|
||||
m_ClipboardContents = text;
|
||||
}
|
||||
|
||||
Point WaylandDisplayWindow::MapFromGlobal(const Point& pos) const
|
||||
{
|
||||
return (pos - m_WindowGlobalPos) / m_ScaleFactor;
|
||||
}
|
||||
|
||||
Point WaylandDisplayWindow::MapToGlobal(const Point& pos) const
|
||||
{
|
||||
return (m_WindowGlobalPos + pos) / m_ScaleFactor;
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::OnXDGToplevelConfigureEvent(int32_t width, int32_t height)
|
||||
{
|
||||
Rect rect = GetWindowFrame();
|
||||
rect.width = width / m_ScaleFactor;
|
||||
rect.height = height / m_ScaleFactor;
|
||||
SetWindowFrame(rect);
|
||||
windowHost->OnWindowGeometryChanged();
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::OnExportHandleEvent(std::string exportedHandle)
|
||||
{
|
||||
m_windowID = exportedHandle;
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::OnExitEvent()
|
||||
{
|
||||
windowHost->OnWindowClose();
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::DrawSurface(uint32_t serial)
|
||||
{
|
||||
m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0);
|
||||
m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height);
|
||||
|
||||
if (m_renderAPI == RenderAPI::Unspecified || m_renderAPI == RenderAPI::Bitmap)
|
||||
{
|
||||
m_FrameCallback = m_AppSurface.frame();
|
||||
|
||||
m_FrameCallback.on_done() = bind_mem_fn(&WaylandDisplayWindow::DrawSurface, this);
|
||||
}
|
||||
m_AppSurface.commit();
|
||||
}
|
||||
|
||||
void WaylandDisplayWindow::CreateBuffers(int32_t width, int32_t height)
|
||||
{
|
||||
if (width == 0 || height == 0)
|
||||
return;
|
||||
|
||||
if (shared_mem)
|
||||
shared_mem.reset();
|
||||
|
||||
int scaled_width = width * m_ScaleFactor;
|
||||
int scaled_height = height * m_ScaleFactor;
|
||||
|
||||
shared_mem = std::make_shared<SharedMemHelper>(scaled_width * scaled_height * 4);
|
||||
auto pool = backend->m_waylandSHM.create_pool(shared_mem->get_fd(), scaled_width * scaled_height * 4);
|
||||
|
||||
m_AppSurfaceBuffer = pool.create_buffer(0, scaled_width, scaled_height, scaled_width * 4, wayland::shm_format::xrgb8888);
|
||||
|
||||
m_WindowSize = Size(scaled_width, scaled_height);
|
||||
}
|
||||
|
||||
std::string WaylandDisplayWindow::GetWaylandWindowID()
|
||||
{
|
||||
return m_windowID;
|
||||
}
|
||||
|
||||
// This is to avoid needing all the Vulkan headers and the volk binding library just for this:
|
||||
#ifndef VK_VERSION_1_0
|
||||
|
||||
#define VKAPI_CALL
|
||||
#define VKAPI_PTR VKAPI_CALL
|
||||
|
||||
typedef uint32_t VkFlags;
|
||||
typedef enum VkStructureType { VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000, VK_OBJECT_TYPE_MAX_ENUM = 0x7FFFFFFF } VkStructureType;
|
||||
typedef enum VkResult { VK_SUCCESS = 0, VK_RESULT_MAX_ENUM = 0x7FFFFFFF } VkResult;
|
||||
typedef struct VkAllocationCallbacks VkAllocationCallbacks;
|
||||
|
||||
typedef void (VKAPI_PTR* PFN_vkVoidFunction)(void);
|
||||
typedef PFN_vkVoidFunction(VKAPI_PTR* PFN_vkGetInstanceProcAddr)(VkInstance instance, const char* pName);
|
||||
|
||||
#ifndef VK_KHR_wayland_surface
|
||||
|
||||
typedef VkFlags VkWaylandSurfaceCreateFlagsKHR;
|
||||
typedef struct VkWaylandSurfaceCreateInfoKHR
|
||||
{
|
||||
VkStructureType sType;
|
||||
const void* pNext;
|
||||
VkWaylandSurfaceCreateFlagsKHR flags;
|
||||
struct wl_display* display;
|
||||
struct wl_surface* surface;
|
||||
} VkWaylandSurfaceCreateInfoKHR;
|
||||
|
||||
typedef VkResult(VKAPI_PTR* PFN_vkCreateWaylandSurfaceKHR)(VkInstance instance, const VkWaylandSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class ZWidgetWaylandVulkanLoader
|
||||
{
|
||||
public:
|
||||
ZWidgetWaylandVulkanLoader()
|
||||
{
|
||||
#if defined(__APPLE__)
|
||||
module = dlopen("libvulkan.dylib", RTLD_NOW | RTLD_LOCAL);
|
||||
if (!module)
|
||||
module = dlopen("libvulkan.1.dylib", RTLD_NOW | RTLD_LOCAL);
|
||||
if (!module)
|
||||
module = dlopen("libMoltenVK.dylib", RTLD_NOW | RTLD_LOCAL);
|
||||
#else
|
||||
module = dlopen("libvulkan.so.1", RTLD_NOW | RTLD_LOCAL);
|
||||
if (!module)
|
||||
module = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
|
||||
#endif
|
||||
|
||||
if (!module)
|
||||
throw std::runtime_error("Could not load vulkan");
|
||||
|
||||
vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym(module, "vkGetInstanceProcAddr");
|
||||
if (!vkGetInstanceProcAddr)
|
||||
{
|
||||
dlclose(module);
|
||||
throw std::runtime_error("vkGetInstanceProcAddr not found");
|
||||
}
|
||||
}
|
||||
|
||||
~ZWidgetWaylandVulkanLoader()
|
||||
{
|
||||
dlclose(module);
|
||||
}
|
||||
|
||||
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr;
|
||||
void* module = nullptr;
|
||||
};
|
||||
|
||||
VkSurfaceKHR WaylandDisplayWindow::CreateVulkanSurface(VkInstance instance)
|
||||
{
|
||||
static ZWidgetWaylandVulkanLoader loader;
|
||||
|
||||
auto vkCreateWaylandSurfaceKHR = (PFN_vkCreateWaylandSurfaceKHR)loader.vkGetInstanceProcAddr(instance, "vkCreateWaylandSurfaceKHR");
|
||||
if (!vkCreateWaylandSurfaceKHR)
|
||||
throw std::runtime_error("Could not create vulkan surface");
|
||||
|
||||
VkWaylandSurfaceCreateInfoKHR createInfo = { VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR };
|
||||
createInfo.display = m_NativeHandle.display;
|
||||
createInfo.surface = m_NativeHandle.surface;
|
||||
|
||||
VkSurfaceKHR surface = {};
|
||||
VkResult result = vkCreateWaylandSurfaceKHR(instance, &createInfo, nullptr, &surface);
|
||||
if (result != VK_SUCCESS)
|
||||
throw std::runtime_error("Could not create vulkan surface");
|
||||
return surface;
|
||||
}
|
||||
|
||||
std::vector<std::string> WaylandDisplayWindow::GetVulkanInstanceExtensions()
|
||||
{
|
||||
return { "VK_KHR_surface", "VK_KHR_wayland_surface" };
|
||||
}
|
||||
198
src/window/wayland/wayland_display_window.h
Normal file
198
src/window/wayland/wayland_display_window.h
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
#pragma once
|
||||
|
||||
#include "wayland_display_backend.h"
|
||||
#include "window/ztimer/ztimer.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "zwidget/window/window.h"
|
||||
#include "zwidget/window/waylandnativehandle.h"
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
template <typename R, typename T, typename... Args>
|
||||
std::function<R(Args...)> bind_mem_fn(R(T::* func)(Args...), T *t)
|
||||
{
|
||||
return [func, t] (Args... args)
|
||||
{
|
||||
return (t->*func)(args...);
|
||||
};
|
||||
}
|
||||
|
||||
class SharedMemHelper
|
||||
{
|
||||
public:
|
||||
SharedMemHelper(size_t size)
|
||||
: len(size)
|
||||
{
|
||||
std::stringstream ss;
|
||||
std::random_device device;
|
||||
std::default_random_engine engine(device());
|
||||
std::uniform_int_distribution<unsigned int> distribution(0, std::numeric_limits<unsigned int>::max());
|
||||
ss << distribution(engine);
|
||||
name = ss.str();
|
||||
|
||||
fd = memfd_create(name.c_str(), 0);
|
||||
if(fd < 0)
|
||||
throw std::runtime_error("shm_open failed.");
|
||||
|
||||
if(ftruncate(fd, size) < 0)
|
||||
throw std::runtime_error("ftruncate failed.");
|
||||
|
||||
mem = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if(mem == MAP_FAILED) // NOLINT
|
||||
throw std::runtime_error("mmap failed with len " + std::to_string(len) + ".");
|
||||
}
|
||||
|
||||
~SharedMemHelper() noexcept
|
||||
{
|
||||
if(fd)
|
||||
{
|
||||
munmap(mem, len);
|
||||
close(fd);
|
||||
shm_unlink(name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
int get_fd() const
|
||||
{
|
||||
return fd;
|
||||
}
|
||||
|
||||
void *get_mem()
|
||||
{
|
||||
return mem;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
int fd = 0;
|
||||
size_t len = 0;
|
||||
void *mem = nullptr;
|
||||
};
|
||||
|
||||
class WaylandDisplayWindow : public DisplayWindow
|
||||
{
|
||||
public:
|
||||
WaylandDisplayWindow(WaylandDisplayBackend* backend, DisplayWindowHost* windowHost, bool popupWindow, WaylandDisplayWindow* owner, RenderAPI renderAPI);
|
||||
~WaylandDisplayWindow();
|
||||
|
||||
void SetWindowTitle(const std::string& text) override;
|
||||
void SetWindowFrame(const Rect& box) override;
|
||||
void SetClientFrame(const Rect& box) override;
|
||||
void Show() override;
|
||||
void ShowFullscreen() override;
|
||||
void ShowMaximized() override;
|
||||
void ShowMinimized() override;
|
||||
void ShowNormal() override;
|
||||
void Hide() override;
|
||||
void Activate() override;
|
||||
void ShowCursor(bool enable) override;
|
||||
void LockCursor() override;
|
||||
void UnlockCursor() override;
|
||||
void CaptureMouse() override;
|
||||
void ReleaseMouseCapture() override;
|
||||
void Update() override;
|
||||
bool GetKeyState(InputKey key) override;
|
||||
void SetCursor(StandardCursor cursor) override;
|
||||
|
||||
Rect GetWindowFrame() const override;
|
||||
Size GetClientSize() const override;
|
||||
int GetPixelWidth() const override;
|
||||
int GetPixelHeight() const override;
|
||||
double GetDpiScale() const override;
|
||||
|
||||
void PresentBitmap(int width, int height, const uint32_t* pixels) override;
|
||||
|
||||
void SetBorderColor(uint32_t bgra8) override;
|
||||
void SetCaptionColor(uint32_t bgra8) override;
|
||||
void SetCaptionTextColor(uint32_t bgra8) override;
|
||||
|
||||
std::string GetClipboardText() override;
|
||||
void SetClipboardText(const std::string& text) override;
|
||||
|
||||
Point MapFromGlobal(const Point& pos) const override;
|
||||
Point MapToGlobal(const Point& pos) const override;
|
||||
|
||||
void* GetNativeHandle() override { return (void*)&m_NativeHandle; }
|
||||
|
||||
std::vector<std::string> GetVulkanInstanceExtensions() override;
|
||||
VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override;
|
||||
|
||||
wayland::surface_t GetWindowSurface() { return m_AppSurface; }
|
||||
|
||||
bool IsWindowFullscreen() override;
|
||||
|
||||
private:
|
||||
// Event handlers as otherwise linking DisplayWindowHost On...() functions with Wayland events directly crashes the app
|
||||
// Alternatively to avoid crashes one can capture by value ([=]) instead of reference ([&])
|
||||
void OnXDGToplevelConfigureEvent(int32_t width, int32_t height);
|
||||
void OnExportHandleEvent(std::string exportedHandle);
|
||||
void OnExitEvent();
|
||||
|
||||
void DrawSurface(uint32_t serial = 0);
|
||||
|
||||
void InitializeToplevel();
|
||||
void InitializePopup();
|
||||
|
||||
WaylandDisplayBackend* backend = nullptr;
|
||||
WaylandDisplayWindow* m_owner = nullptr;
|
||||
DisplayWindowHost* windowHost = nullptr;
|
||||
bool m_PopupWindow = false;
|
||||
|
||||
bool m_NeedsUpdate = true;
|
||||
|
||||
Point m_WindowGlobalPos = Point(0, 0);
|
||||
Size m_WindowSize = Size(0, 0);
|
||||
double m_ScaleFactor = 1.0;
|
||||
|
||||
Point m_SurfaceMousePos = Point(0, 0);
|
||||
|
||||
WaylandNativeHandle m_NativeHandle;
|
||||
RenderAPI m_renderAPI;
|
||||
|
||||
wayland::data_device_t m_DataDevice;
|
||||
wayland::data_source_t m_DataSource;
|
||||
|
||||
wayland::zxdg_toplevel_decoration_v1_t m_XDGToplevelDecoration;
|
||||
|
||||
wayland::fractional_scale_v1_t m_FractionalScale;
|
||||
|
||||
wayland::surface_t m_AppSurface;
|
||||
wayland::buffer_t m_AppSurfaceBuffer;
|
||||
|
||||
wayland::xdg_surface_t m_XDGSurface;
|
||||
wayland::xdg_toplevel_t m_XDGToplevel;
|
||||
wayland::xdg_popup_t m_XDGPopup;
|
||||
|
||||
wayland::zxdg_exported_v2_t m_XDGExported;
|
||||
|
||||
wayland::zwp_locked_pointer_v1_t m_LockedPointer;
|
||||
wayland::zwp_confined_pointer_v1_t m_ConfinedPointer;
|
||||
|
||||
wayland::callback_t m_FrameCallback;
|
||||
|
||||
std::string m_windowID;
|
||||
std::string m_ClipboardContents;
|
||||
|
||||
std::shared_ptr<SharedMemHelper> shared_mem;
|
||||
|
||||
bool isFullscreen = false;
|
||||
|
||||
// Helper functions
|
||||
void CreateBuffers(int32_t width, int32_t height);
|
||||
std::string GetWaylandWindowID();
|
||||
|
||||
friend WaylandDisplayBackend;
|
||||
};
|
||||
205
src/window/wayland/wl_fractional_scaling_protocol.cpp
Normal file
205
src/window/wayland/wl_fractional_scaling_protocol.cpp
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
#include "wl_fractional_scaling_protocol.hpp"
|
||||
|
||||
using namespace wayland;
|
||||
using namespace wayland::detail;
|
||||
|
||||
const wl_interface* fractional_scale_manager_v1_interface_destroy_request[0] = {
|
||||
};
|
||||
|
||||
const wl_interface* fractional_scale_manager_v1_interface_get_fractional_scale_request[2] = {
|
||||
&fractional_scale_v1_interface,
|
||||
&surface_interface,
|
||||
};
|
||||
|
||||
const wl_message fractional_scale_manager_v1_interface_requests[2] = {
|
||||
{
|
||||
"destroy",
|
||||
"",
|
||||
fractional_scale_manager_v1_interface_destroy_request,
|
||||
},
|
||||
{
|
||||
"get_fractional_scale",
|
||||
"no",
|
||||
fractional_scale_manager_v1_interface_get_fractional_scale_request,
|
||||
},
|
||||
};
|
||||
|
||||
const wl_message fractional_scale_manager_v1_interface_events[0] = {
|
||||
};
|
||||
|
||||
const wl_interface wayland::detail::fractional_scale_manager_v1_interface =
|
||||
{
|
||||
"wp_fractional_scale_manager_v1",
|
||||
1,
|
||||
2,
|
||||
fractional_scale_manager_v1_interface_requests,
|
||||
0,
|
||||
fractional_scale_manager_v1_interface_events,
|
||||
};
|
||||
|
||||
const wl_interface* fractional_scale_v1_interface_destroy_request[0] = {
|
||||
};
|
||||
|
||||
const wl_interface* fractional_scale_v1_interface_preferred_scale_event[1] = {
|
||||
nullptr,
|
||||
};
|
||||
|
||||
const wl_message fractional_scale_v1_interface_requests[1] = {
|
||||
{
|
||||
"destroy",
|
||||
"",
|
||||
fractional_scale_v1_interface_destroy_request,
|
||||
},
|
||||
};
|
||||
|
||||
const wl_message fractional_scale_v1_interface_events[1] = {
|
||||
{
|
||||
"preferred_scale",
|
||||
"u",
|
||||
fractional_scale_v1_interface_preferred_scale_event,
|
||||
},
|
||||
};
|
||||
|
||||
const wl_interface wayland::detail::fractional_scale_v1_interface =
|
||||
{
|
||||
"wp_fractional_scale_v1",
|
||||
1,
|
||||
1,
|
||||
fractional_scale_v1_interface_requests,
|
||||
1,
|
||||
fractional_scale_v1_interface_events,
|
||||
};
|
||||
|
||||
fractional_scale_manager_v1_t::fractional_scale_manager_v1_t(const proxy_t &p)
|
||||
: proxy_t(p)
|
||||
{
|
||||
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
|
||||
{
|
||||
set_events(std::shared_ptr<detail::events_base_t>(new events_t), dispatcher);
|
||||
set_destroy_opcode(0U);
|
||||
}
|
||||
set_interface(&fractional_scale_manager_v1_interface);
|
||||
set_copy_constructor([] (const proxy_t &p) -> proxy_t
|
||||
{ return fractional_scale_manager_v1_t(p); });
|
||||
}
|
||||
|
||||
fractional_scale_manager_v1_t::fractional_scale_manager_v1_t()
|
||||
{
|
||||
set_interface(&fractional_scale_manager_v1_interface);
|
||||
set_copy_constructor([] (const proxy_t &p) -> proxy_t
|
||||
{ return fractional_scale_manager_v1_t(p); });
|
||||
}
|
||||
|
||||
fractional_scale_manager_v1_t::fractional_scale_manager_v1_t(wp_fractional_scale_manager_v1 *p, wrapper_type t)
|
||||
: proxy_t(reinterpret_cast<wl_proxy*> (p), t){
|
||||
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
|
||||
{
|
||||
set_events(std::shared_ptr<detail::events_base_t>(new events_t), dispatcher);
|
||||
set_destroy_opcode(0U);
|
||||
}
|
||||
set_interface(&fractional_scale_manager_v1_interface);
|
||||
set_copy_constructor([] (const proxy_t &p) -> proxy_t
|
||||
{ return fractional_scale_manager_v1_t(p); });
|
||||
}
|
||||
|
||||
fractional_scale_manager_v1_t::fractional_scale_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/)
|
||||
: proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){
|
||||
set_interface(&fractional_scale_manager_v1_interface);
|
||||
set_copy_constructor([] (const proxy_t &p) -> proxy_t
|
||||
{ return fractional_scale_manager_v1_t(p); });
|
||||
}
|
||||
|
||||
fractional_scale_manager_v1_t fractional_scale_manager_v1_t::proxy_create_wrapper()
|
||||
{
|
||||
return {*this, construct_proxy_wrapper_tag()};
|
||||
}
|
||||
|
||||
const std::string fractional_scale_manager_v1_t::interface_name = "wp_fractional_scale_manager_v1";
|
||||
|
||||
fractional_scale_manager_v1_t::operator wp_fractional_scale_manager_v1*() const
|
||||
{
|
||||
return reinterpret_cast<wp_fractional_scale_manager_v1*> (c_ptr());
|
||||
}
|
||||
|
||||
fractional_scale_v1_t fractional_scale_manager_v1_t::get_fractional_scale(surface_t const& surface)
|
||||
{
|
||||
proxy_t p = marshal_constructor(1U, &fractional_scale_v1_interface, nullptr, surface.proxy_has_object() ? reinterpret_cast<wl_object*>(surface.c_ptr()) : nullptr);
|
||||
return fractional_scale_v1_t(p);
|
||||
}
|
||||
|
||||
|
||||
int fractional_scale_manager_v1_t::dispatcher(uint32_t opcode, const std::vector<any>& args, const std::shared_ptr<detail::events_base_t>& e)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
fractional_scale_v1_t::fractional_scale_v1_t(const proxy_t &p)
|
||||
: proxy_t(p)
|
||||
{
|
||||
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
|
||||
{
|
||||
set_events(std::shared_ptr<detail::events_base_t>(new events_t), dispatcher);
|
||||
set_destroy_opcode(0U);
|
||||
}
|
||||
set_interface(&fractional_scale_v1_interface);
|
||||
set_copy_constructor([] (const proxy_t &p) -> proxy_t
|
||||
{ return fractional_scale_v1_t(p); });
|
||||
}
|
||||
|
||||
fractional_scale_v1_t::fractional_scale_v1_t()
|
||||
{
|
||||
set_interface(&fractional_scale_v1_interface);
|
||||
set_copy_constructor([] (const proxy_t &p) -> proxy_t
|
||||
{ return fractional_scale_v1_t(p); });
|
||||
}
|
||||
|
||||
fractional_scale_v1_t::fractional_scale_v1_t(wp_fractional_scale_v1 *p, wrapper_type t)
|
||||
: proxy_t(reinterpret_cast<wl_proxy*> (p), t){
|
||||
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
|
||||
{
|
||||
set_events(std::shared_ptr<detail::events_base_t>(new events_t), dispatcher);
|
||||
set_destroy_opcode(0U);
|
||||
}
|
||||
set_interface(&fractional_scale_v1_interface);
|
||||
set_copy_constructor([] (const proxy_t &p) -> proxy_t
|
||||
{ return fractional_scale_v1_t(p); });
|
||||
}
|
||||
|
||||
fractional_scale_v1_t::fractional_scale_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/)
|
||||
: proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){
|
||||
set_interface(&fractional_scale_v1_interface);
|
||||
set_copy_constructor([] (const proxy_t &p) -> proxy_t
|
||||
{ return fractional_scale_v1_t(p); });
|
||||
}
|
||||
|
||||
fractional_scale_v1_t fractional_scale_v1_t::proxy_create_wrapper()
|
||||
{
|
||||
return {*this, construct_proxy_wrapper_tag()};
|
||||
}
|
||||
|
||||
const std::string fractional_scale_v1_t::interface_name = "wp_fractional_scale_v1";
|
||||
|
||||
fractional_scale_v1_t::operator wp_fractional_scale_v1*() const
|
||||
{
|
||||
return reinterpret_cast<wp_fractional_scale_v1*> (c_ptr());
|
||||
}
|
||||
|
||||
std::function<void(uint32_t)> &fractional_scale_v1_t::on_preferred_scale()
|
||||
{
|
||||
return std::static_pointer_cast<events_t>(get_events())->preferred_scale;
|
||||
}
|
||||
|
||||
int fractional_scale_v1_t::dispatcher(uint32_t opcode, const std::vector<any>& args, const std::shared_ptr<detail::events_base_t>& e)
|
||||
{
|
||||
std::shared_ptr<events_t> events = std::static_pointer_cast<events_t>(e);
|
||||
switch(opcode)
|
||||
{
|
||||
case 0:
|
||||
if(events->preferred_scale) events->preferred_scale(args[0].get<uint32_t>());
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
126
src/window/wayland/wl_fractional_scaling_protocol.hpp
Normal file
126
src/window/wayland/wl_fractional_scaling_protocol.hpp
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <wayland-client.hpp>
|
||||
|
||||
struct wp_fractional_scale_manager_v1;
|
||||
struct wp_fractional_scale_v1;
|
||||
|
||||
namespace wayland
|
||||
{
|
||||
class fractional_scale_manager_v1_t;
|
||||
enum class fractional_scale_manager_v1_error : uint32_t;
|
||||
class fractional_scale_v1_t;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
extern const wl_interface fractional_scale_manager_v1_interface;
|
||||
extern const wl_interface fractional_scale_v1_interface;
|
||||
}
|
||||
|
||||
/** \brief fractional surface scale information
|
||||
|
||||
A global interface for requesting surfaces to use fractional scales.
|
||||
|
||||
*/
|
||||
class fractional_scale_manager_v1_t : public proxy_t
|
||||
{
|
||||
private:
|
||||
struct events_t : public detail::events_base_t
|
||||
{
|
||||
};
|
||||
|
||||
static int dispatcher(uint32_t opcode, const std::vector<detail::any>& args, const std::shared_ptr<detail::events_base_t>& e);
|
||||
|
||||
fractional_scale_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/);
|
||||
|
||||
public:
|
||||
fractional_scale_manager_v1_t();
|
||||
explicit fractional_scale_manager_v1_t(const proxy_t &proxy);
|
||||
fractional_scale_manager_v1_t(wp_fractional_scale_manager_v1 *p, wrapper_type t = wrapper_type::standard);
|
||||
|
||||
fractional_scale_manager_v1_t proxy_create_wrapper();
|
||||
|
||||
static const std::string interface_name;
|
||||
|
||||
operator wp_fractional_scale_manager_v1*() const;
|
||||
|
||||
/** \brief extend surface interface for scale information
|
||||
\return the new surface scale info interface id
|
||||
\param surface the surface
|
||||
|
||||
Create an add-on object for the the wl_surface to let the compositor
|
||||
request fractional scales. If the given wl_surface already has a
|
||||
wp_fractional_scale_v1 object associated, the fractional_scale_exists
|
||||
protocol error is raised.
|
||||
|
||||
*/
|
||||
fractional_scale_v1_t get_fractional_scale(surface_t const& surface);
|
||||
|
||||
/** \brief Minimum protocol version required for the \ref get_fractional_scale function
|
||||
*/
|
||||
static constexpr std::uint32_t get_fractional_scale_since_version = 1;
|
||||
|
||||
};
|
||||
|
||||
/** \brief
|
||||
|
||||
*/
|
||||
enum class fractional_scale_manager_v1_error : uint32_t
|
||||
{
|
||||
/** \brief the surface already has a fractional_scale object associated */
|
||||
fractional_scale_exists = 0
|
||||
};
|
||||
|
||||
|
||||
/** \brief fractional scale interface to a wl_surface
|
||||
|
||||
An additional interface to a wl_surface object which allows the compositor
|
||||
to inform the client of the preferred scale.
|
||||
|
||||
*/
|
||||
class fractional_scale_v1_t : public proxy_t
|
||||
{
|
||||
private:
|
||||
struct events_t : public detail::events_base_t
|
||||
{
|
||||
std::function<void(uint32_t)> preferred_scale;
|
||||
};
|
||||
|
||||
static int dispatcher(uint32_t opcode, const std::vector<detail::any>& args, const std::shared_ptr<detail::events_base_t>& e);
|
||||
|
||||
fractional_scale_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/);
|
||||
|
||||
public:
|
||||
fractional_scale_v1_t();
|
||||
explicit fractional_scale_v1_t(const proxy_t &proxy);
|
||||
fractional_scale_v1_t(wp_fractional_scale_v1 *p, wrapper_type t = wrapper_type::standard);
|
||||
|
||||
fractional_scale_v1_t proxy_create_wrapper();
|
||||
|
||||
static const std::string interface_name;
|
||||
|
||||
operator wp_fractional_scale_v1*() const;
|
||||
|
||||
/** \brief notify of new preferred scale
|
||||
\param scale the new preferred scale
|
||||
|
||||
Notification of a new preferred scale for this surface that the
|
||||
compositor suggests that the client should use.
|
||||
|
||||
The sent scale is the numerator of a fraction with a denominator of 120.
|
||||
|
||||
*/
|
||||
std::function<void(uint32_t)> &on_preferred_scale();
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue