diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc6b4d43..cfcd8712 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,8 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-latest + - name: Ubuntu Debug + os: ubuntu-20.04 buildtype: 'Debug' cc: clang-12 cxx: clang++-12 @@ -15,7 +16,8 @@ jobs: upload: false cmakeflags: '-DBUILD_TESTS=ON' - - os: ubuntu-latest + - name: Ubuntu Release + os: ubuntu-20.04 buildtype: 'Release' cc: clang-12 cxx: clang++-12 @@ -24,7 +26,8 @@ jobs: use_vcpkg: true cmakeflags: '' - - os: macos-10.15 + - name: Macos Debug + os: macos-10.15 buildtype: 'Debug' cc: clang cxx: clang++ @@ -32,7 +35,8 @@ jobs: upload: false cmakeflags: '-DBUILD_TESTS=ON' - - os: macos-10.15 + - name: Macos Release + os: macos-10.15 buildtype: 'Release' cc: clang cxx: clang++ @@ -76,24 +80,36 @@ jobs: - name: Ubuntu - Install vcpkg dependencies if: startsWith(matrix.os, 'ubuntu') && matrix.use_vcpkg run: > - sudo apt-get install gperf autoconf build-essential libtool - libgl1-mesa-dev libxi-dev libx11-dev libxext-dev - libxkbcommon-x11-dev libglu1-mesa-dev libx11-xcb-dev - '^libxcb.*-dev' libxrender-dev ninja-build curl - zip unzip tar autopoint python + sudo apt-get install git curl zip unzip tar at libxt-dev gperf libxaw7-dev cifs-utils + build-essential g++ gfortran libx11-dev libxkbcommon-x11-dev libxi-dev + libgl1-mesa-dev libglu1-mesa-dev mesa-common-dev libxinerama-dev libxxf86vm-dev + libxcursor-dev yasm libnuma1 libnuma-dev libtool-bin + flex bison libbison-dev autoconf libudev-dev libncurses5-dev libtool libxrandr-dev + xutils-dev dh-autoreconf autoconf-archive libgles2-mesa-dev ruby-full + libxext-dev libxfixes-dev libxrender-dev + libxcb1-dev libx11-xcb-dev libxcb-glx0-dev meson nasm cmake ninja-build + libxkbcommon-dev libxcb-keysyms1-dev + libxcb-image0-dev libxcb-shm0-dev libxcb-icccm4-dev libxcb-sync-dev + libxcb-xfixes0-dev libxcb-shape0-dev libxcb-randr0-dev + libxcb-render-util0-dev libxcb-xinerama0-dev libxcb-xkb-dev libxcb-xinput-dev + libxcb-cursor-dev libkrb5-dev libxcb-res0-dev libxcb-keysyms1-dev libxcb-xkb-dev libxcb-record0-dev + python3-setuptools python3-mako python3-pip python3-venv nodejs libwayland-dev python2 python-is-python3 + guile-2.2-dev libxdamage-dev libdbus-1-dev libxtst-dev haskell-stack libkrb5-3 zlib1g + libxcb-util0-dev pkg-config libicu66 + # python-yaml - name: Mac - Install vcpkg dependencies if: startsWith(matrix.os, 'macos') && matrix.use_vcpkg run: | - brew install automake + brew install automake autoconf-archive libtool - - name: Restore vcpkg packages - if: matrix.use_vcpkg - uses: actions/cache@v2 - with: - path: | - build/vcpkg_installed - key: ${{ matrix.os }}-${{ hashFiles('vcpkg.json') }}-${{ env.CC }}-${{ env.CXX }} + #- name: Restore vcpkg packages + #if: matrix.use_vcpkg + #uses: actions/cache@v2 + #with: + #path: | + #build/vcpkg_installed + #key: ${{ matrix.os }}-${{ hashFiles('vcpkg.json') }}-${{ env.CC }}-${{ env.CXX }} - name: Ubuntu - Install dependencies from system package manager if: ${{ startsWith(matrix.os, 'ubuntu') && !matrix.use_vcpkg }} @@ -101,13 +117,13 @@ jobs: sudo apt-get install libmsgpack-dev libfmt-dev libboost-all-dev mesa-common-dev libglu1-mesa-dev - - name: Ubuntu - Install Catch2 - if: ${{ startsWith(matrix.os, 'ubuntu') && !matrix.use_vcpkg }} + - name: Ubuntu & MacOS Debug - Build & install Catch2 from source + if: ${{ !matrix.use_vcpkg }} run: | git clone https://github.com/catchorg/Catch2.git cd Catch2 - git checkout v2.x - cmake -B build -H. -DBUILD_TESTING=OFF + git checkout devel + cmake -B build -H. -DBUILD_TESTING=OFF -DCMAKE_CXX_STANDARD=17 cd build sudo make install @@ -130,7 +146,7 @@ jobs: - name: Mac - Install dependencies if: ${{ startsWith(matrix.os, 'macos') && !matrix.use_vcpkg }} run: | - brew install fmt boost qt@5 msgpack-cxx catch2 + brew install fmt boost qt@5 msgpack-cxx sudo ln -s /usr/local/Cellar/qt@5/5.15.2/plugins /usr/local/plugins - name: Mac - Set environment variables @@ -160,13 +176,22 @@ jobs: if: matrix.test run: ./build/nvui_test - - name: Package (Ubuntu & Mac) - if: matrix.upload + - name: Package (Ubuntu) + if: matrix.upload && startsWith(matrix.os, 'ubuntu') run: | ls build chmod +x ./scripts/linux/package.sh ./scripts/linux/package.sh + - name: Package (macOS) + if: matrix.upload && startsWith(matrix.os, 'macos') + run: | + cd build + mkdir packaged + mkdir packaged/bin + cp -r nvui.app packaged/bin + cp -r ../vim packaged + - name: Ubuntu - Upload artifact uses: actions/upload-artifact@v2 if: matrix.upload && startsWith(matrix.os, 'ubuntu') @@ -216,78 +241,128 @@ jobs: tag: ${{ github.ref }} overwrite: false body: "Automated release by Github Actions." - windows-debug: - runs-on: windows-latest + windows: strategy: fail-fast: false + matrix: + include: + - name: Windows Release + os: windows-latest + buildtype: 'Release' + debug: false + upload: true + cmakeflags: '' + c: clang + cpp: clang++ + gen: Ninja + + - name: Windows Debug + os: windows-latest + buildtype: 'Debug' + debug: true + upload: false + cmakeflags: '-DBUILD_TESTS=ON' + c: clang + cpp: clang++ + gen: Ninja + runs-on: ${{ matrix.os }} timeout-minutes: 500 - env: - buildDir: '${{ github.workspace }}/build' steps: - - uses: actions/checkout@v2 - with: - submodules: 'recursive' + - name: Set last commit hash as environment variable (push) + if: ${{ github.event_name == 'push' }} + run: | + "COMMIT_SHA=${{ github.sha }}" >> $env:GITHUB_ENV + - name: Set last commit hash as environment variable (pull request) + if: ${{ github.event_name == 'pull_request' }} + run: | + "COMMIT_SHA=${{ github.event.pull_request.head.sha }}" >> $env:GITHUB_ENV + + - name: Clone repo to C drive + run: | + git clone --recurse-submodules https://github.com/rohit-px2/nvui.git C:\nvui + cd C:\nvui + git checkout $env:COMMIT_SHA + git submodule init + git submodule update + - uses: ilammy/msvc-dev-cmd@v1 with: sdk: 10.0.19041.0 + - name: Install Neovim Stable + if: matrix.debug run: choco install -y neovim + - name: Add Neovim to PATH - run: echo "C:\tools\neovim\Neovim\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - name: Restore deps from cache if they exist - uses: actions/cache@v2 - with: - path: | - build/vcpkg_installed - key: ${{ runner.os }}-${{ hashFiles('vcpkg.json') }} - restore-keys: | - ${{ runner.os }}-build-debug-${{ hashFiles('vcpkg.json') }} - - name: Build nvui and nvui_test - run: .\scripts\windows\build-debug.ps1 + if: matrix.debug + run: | + echo "C:\tools\neovim\nvim-win64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + #- name: Restore deps from cache if they exist + #uses: actions/cache@v2 + #with: + #path: | + #build/vcpkg_installed + #key: ${{ runner.os }}-${{ hashFiles('vcpkg.json') }} + #restore-keys: | + #${{ runner.os }}-build-release-${{ hashFiles('vcpkg.json') }} + + # Step was taken from Neovim-Qt's github actions workflow file + # at https://github.com/equalsraf/neovim-qt/blob/master/.github/workflows/build-test.yml + #- name: Install Qt5 + #env: + #QT_DIR: ${{ github.workspace }}\Qt\5.15.2\msvc2019_64 + #run: | + #python -m pip install aqtinstall + #python -m aqt install-qt -O ./Qt windows desktop 5.15.2 win64_msvc2019_64 + #"Qt5_DIR=$env:QT_DIR\lib\cmake\Qt5" >> $env:GITHUB_ENV + #"${{ env.qt_dir }}" >> $env:GITHUB_PATH + #"${{ env.qt_dir }}" >> $env:GITHUB_PATH + + - name: Bootstrap vcpkg before build + working-directory: C:/nvui + run: .\vcpkg\bootstrap-vcpkg.bat -disableMetrics + + - name: Build nvui (& nvui_test in Debug mode) + working-directory: C:/nvui + run: | + cmake -B build . -DCMAKE_TOOLCHAIN_FILE=.\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }} ${{ matrix.cmakeflags }} -DCMAKE_C_COMPILER=${{ matrix.c }} -DCMAKE_CXX_COMPILER=${{ matrix.cpp }} -G ${{ matrix.gen }} + cmake --build build --config ${{ matrix.buildtype }} + shell: cmd + - name: Run tests + working-directory: C:/nvui + if: matrix.debug run: | cd build .\nvui_test shell: cmd - windows-release: - runs-on: windows-latest - strategy: - fail-fast: false - timeout-minutes: 500 - env: - buildDir: '${{ github.workspace }}/build' - steps: - - uses: actions/checkout@v2 - with: - submodules: 'recursive' - - uses: ilammy/msvc-dev-cmd@v1 - with: - sdk: 10.0.19041.0 - - name: Restore deps from cache if they exist - uses: actions/cache@v2 - with: - path: | - build/vcpkg_installed - key: ${{ runner.os }}-${{ hashFiles('vcpkg.json') }} - restore-keys: | - ${{ runner.os }}-build-release-${{ hashFiles('vcpkg.json') }} - - name: Package into a zip - run: .\scripts\windows\package.ps1 + + - name: Package nvui (Release) + working-directory: C:/nvui + if: ${{ !matrix.debug }} + run: | + .\scripts\windows\package.ps1 + - name: Upload artifact + if: matrix.upload uses: actions/upload-artifact@v2 with: name: nvui-win64 if-no-files-found: ignore path: build/nvui + - name: Rename nvui.zip to nvui-win64.zip for release upload - if: startsWith(github.ref, 'refs/tags/') - run: ren nvui.zip nvui-win64.zip + working-directory: C:/nvui + if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.upload }} + run: | + ren nvui.zip nvui-win64.zip + - name: Upload Release asset - if: startsWith(github.ref, 'refs/tags/') + if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.upload }} uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: nvui-win64.zip + file: C:\nvui\nvui-win64.zip tag: ${{ github.ref }} overwrite: false body: "Automated release by Github Actions." diff --git a/.gitignore b/.gitignore index e087b38a..2db02f9f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ CMakeLists.txt.user .ccls-cache/ CMakeSettings.json .ignore +vcpkg_installed/ diff --git a/CMakeLists.txt b/CMakeLists.txt index ac0d7c7f..58c5b78d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,6 +122,12 @@ endif() target_link_libraries(nvui PRIVATE ${Boost_LIBRARIES} ) +if(APPLE) + set_target_properties(nvui PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/assets/resources/macos/Info.plist + ) +endif() include(CheckIPOSupported) check_ipo_supported(RESULT LTOAvailable) if(LTOAvailable) @@ -160,8 +166,7 @@ if(BUILD_TESTS) target_include_directories(nvui_test PRIVATE ${Boost_INCLUDE_DIR}) endif() target_link_libraries(nvui_test PRIVATE - Boost::thread - Boost::filesystem + ${Boost_LIBRARIES} ) include(CTest) include(Catch) diff --git a/assets/resources/macos/Info.plist b/assets/resources/macos/Info.plist new file mode 100644 index 00000000..cb16414d --- /dev/null +++ b/assets/resources/macos/Info.plist @@ -0,0 +1,10 @@ + + + + + NSPrincipalClass + NSApplication + NSHighResolutionCapable + + + diff --git a/scripts/linux/package.sh b/scripts/linux/package.sh index 189e86b4..d424c941 100644 --- a/scripts/linux/package.sh +++ b/scripts/linux/package.sh @@ -3,6 +3,4 @@ cd build mkdir packaged mkdir packaged/bin cp nvui packaged/bin -cp -r ../assets packaged -rm -rf packaged/assets/display cp -r ../vim packaged diff --git a/scripts/windows/build-debug.ps1 b/scripts/windows/build-debug.ps1 index 70668604..f4a66a57 100644 --- a/scripts/windows/build-debug.ps1 +++ b/scripts/windows/build-debug.ps1 @@ -4,6 +4,6 @@ param( [string]$gen = "Ninja" ) $generator = '"{0}"' -f $gen -$cmd = Write-Output "cmake . -B build -DCMAKE_TOOLCHAIN_FILE=.\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=$cc -DCMAKE_CXX_COMPILER=$cxx -G $generator -DBUILD_TESTS=ON" +$cmd = Write-Output "cmake . -B build -DQt5_DIR=$env:Qt5_DIR -DCMAKE_TOOLCHAIN_FILE=.\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=$cc -DCMAKE_CXX_COMPILER=$cxx -G $generator -DBUILD_TESTS=ON" cmd /C $cmd cmake --build build --config Debug diff --git a/scripts/windows/build-release.ps1 b/scripts/windows/build-release.ps1 index c7686879..4c376081 100644 --- a/scripts/windows/build-release.ps1 +++ b/scripts/windows/build-release.ps1 @@ -4,6 +4,6 @@ param( [string]$gen = "Ninja" ) $generator = '"{0}"' -f $gen -$cmd = Write-Output "cmake . -B build -DCMAKE_TOOLCHAIN_FILE=.\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=$cc -DCMAKE_CXX_COMPILER=$cxx -G $generator" +$cmd = Write-Output "cmake . -B build -DQt5_DIR=$env:Qt5_DIR -DCMAKE_TOOLCHAIN_FILE=.\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=$cc -DCMAKE_CXX_COMPILER=$cxx -G $generator" cmd /C $cmd cmake --build build --config Release diff --git a/scripts/windows/package.ps1 b/scripts/windows/package.ps1 index 46f7b37b..8e6fc7b6 100644 --- a/scripts/windows/package.ps1 +++ b/scripts/windows/package.ps1 @@ -1,4 +1,3 @@ -./scripts/windows/build-release.ps1 cd build mkdir nvui cd nvui @@ -6,9 +5,7 @@ mkdir bin cd .. (gci -Path ./* -Include nvui.exe, *.dll, *.conf).fullname | foreach {Copy-Item -Force -Path $_ -Destination nvui/bin} Copy-Item -Force -Recurse -Path plugins -Destination nvui/bin -Copy-Item -Force -Recurse -Path ../assets -Destination nvui Copy-Item -Force -Recurse -Path ../vim -Destination nvui -Remove-Item -Force -Recurse -Path nvui/assets/display Compress-Archive -Force -Path nvui -DestinationPath nvui.zip Move-Item -Force -Path nvui.zip -Destination ../ cd .. diff --git a/src/cmdline.cpp b/src/cmdline.cpp index ffc0d794..d75f606f 100644 --- a/src/cmdline.cpp +++ b/src/cmdline.cpp @@ -313,9 +313,9 @@ static std::tuple draw_pos( int CmdlineQ::fitting_height() const { + QFontMetricsF fm {cmd_font, this}; int maxwidth = width() - (border_width + padding) * 2; auto contentstring = get_content_string(); - QFontMetricsF fm {cmd_font}; auto maxheight = fm.height() * contentstring.size(); QRectF constraint(0, 0, maxwidth, maxheight); float pad = border_width + padding; @@ -341,7 +341,7 @@ void CmdlineQ::draw_cursor(QPainter& p, const Cursor& cursor) QString contentstring = get_content_string(); int upto = contentstring.size() - cur_content_length + cursor_pos; float pad = border_width + padding; - QFontMetricsF fm {cmd_font}; + const auto fm = p.fontMetrics(); float left = pad; float top = pad; const auto adv_x = [&](float adv) { @@ -352,7 +352,7 @@ void CmdlineQ::draw_cursor(QPainter& p, const Cursor& cursor) if (contentstring[i] == '\n') { left = pad; top += fm.height(); } else { - adv_x(fm.horizontalAdvance(contentstring[i])); + adv_x(p.fontMetrics().horizontalAdvance(contentstring[i])); } } auto [rect, id, drawtext, opacity] = cursor.rect( @@ -366,10 +366,10 @@ void CmdlineQ::draw_cursor(QPainter& p, const Cursor& cursor) void CmdlineQ::paintEvent(QPaintEvent*) { - QFontMetricsF fm {cmd_font}; - int offset = fm.ascent(); QPainter p(this); p.setFont(cmd_font); + const auto fm = p.fontMetrics(); + int offset = fm.ascent(); auto contentstring = get_content_string(); QColor bg = inner_bg.value_or(hl_state.default_bg()).qcolor(); QColor fg = inner_fg.value_or(hl_state.default_fg()).qcolor(); diff --git a/src/main.cpp b/src/main.cpp index 86fe32ad..bdb3defc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,11 @@ #include #include #include +#include + +#ifdef Q_OS_MAC +#include "platform/macos/macos_utils.hpp" +#endif // Q_OS_MAC #include "config.hpp" using std::string; @@ -128,6 +133,12 @@ bool is_executable(std::string_view path) int main(int argc, char** argv) { QCoreApplication::setApplicationName("nvui"); +#ifdef USE_QPAINTER + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#endif +#ifdef Q_OS_MAC + macos_utils::set_env_vars(); +#endif QApplication app {argc, argv}; Config::init(); const auto args = get_args(argc, argv); diff --git a/src/platform/macos/macos_utils.hpp b/src/platform/macos/macos_utils.hpp new file mode 100644 index 00000000..c490ec6c --- /dev/null +++ b/src/platform/macos/macos_utils.hpp @@ -0,0 +1,39 @@ +#ifndef NVUI_PLATFORM_MACOS_UTILS_HPP +#define NVUI_PLATFORM_MACOS_UTILS_HPP + +#include + +namespace macos_utils +{ + +inline void set_env_vars() +{ + QProcess env_printer; + auto shell = qgetenv("SHELL"); + if (shell.isEmpty()) shell = "/bin/bash"; + // Ported from Goneovim's editor.go at + // https://github.com/akiyosi/goneovim/blob/981f41440935542ed35b3ef93cfdcd17744a4e1a/editor/editor.go#L548 + // When nvui is compiled into a .app file and run, there are differences + // between the environment when it is run by clicking vs. running from command line. + // When run by clicking the PATH doesn't contain the Neovim executable path + // even if it was set by the user. + // Thus we have to print out the path in an external process and set it ourselves. + env_printer.start(shell, {"-lc", "env", "-i"}); + if (!env_printer.waitForFinished(5000)) + { + return; + } + auto out = env_printer.readAllStandardOutput().split('\n'); + for(const auto& line : out) + { + // The name of the environment variable, and then its value. + // Separated by an '=' sign. + auto s = line.split('='); + if (s.size() < 2) continue; + qputenv(s[0], s[1]); + } +} + +} // namespace macos_utils + +#endif // NVUI_PLATFORM_MACOS_UTILS_HPP diff --git a/src/platform/windows/d2deditor.cpp b/src/platform/windows/d2deditor.cpp index 22337f47..a253443a 100644 --- a/src/platform/windows/d2deditor.cpp +++ b/src/platform/windows/d2deditor.cpp @@ -39,7 +39,8 @@ D2DEditor::D2DEditor( ) : QWidget(parent), QtEditorUIBase(*this, cols, rows, std::move(capabilities), - std::move(nvim_path), std::move(nvim_args)) + std::move(nvim_path), std::move(nvim_args)), + win_dpi(GetDpiForWindow((HWND) winId())) { setAttribute(Qt::WA_PaintOnScreen); setAttribute(Qt::WA_InputMethodEnabled); @@ -80,6 +81,12 @@ D2DEditor::~D2DEditor() = default; void D2DEditor::resizeEvent(QResizeEvent* event) { Base::handle_nvim_resize(event); + auto maybe_new_dpi = GetDpiForWindow((HWND) winId()); + if (maybe_new_dpi != win_dpi) + { + win_dpi = maybe_new_dpi; + set_fonts(guifonts); + } hwnd_target->Resize(D2D1::SizeU(width(), height())); QWidget::resizeEvent(event); } @@ -139,6 +146,7 @@ D2DEditor::create_render_target(u32 width, u32 height) D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS, &target ); + target->SetDpi(default_dpi, default_dpi); target->CreateBitmap( size, nullptr, 0, D2D1::BitmapProperties1( @@ -153,7 +161,6 @@ D2DEditor::create_render_target(u32 width, u32 height) target->SetTarget(bitmap.Get()); target->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE); target->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); - target->SetDpi(default_dpi, default_dpi); return {target, bitmap}; } @@ -290,7 +297,7 @@ void D2DEditor::set_fonts(std::span fontlist) continue; } dw_formats.emplace_back( - dwrite_factory(), wcname, current_point_size, default_dpi, + dwrite_factory(), wcname, current_point_size * scale_factor(), default_dpi, default_font_weight(), default_font_style() ); dw_fonts.emplace_back(std::move(font)); @@ -301,14 +308,19 @@ void D2DEditor::set_fonts(std::span fontlist) static const auto default_font_name = default_font_family().toStdWString(); dw_fonts.push_back(font_from_name(default_font_name, font_collection.Get())); dw_formats.emplace_back( - dwrite_factory(), default_font_name, current_point_size, default_dpi, - default_font_weight(), default_font_style() + dwrite_factory(), default_font_name, current_point_size * scale_factor(), + default_dpi, default_font_weight(), default_font_style() ); } update_font_metrics(); emit layouts_invalidated(); } +float D2DEditor::scale_factor() const +{ + return win_dpi / default_dpi; +} + void D2DEditor::linespace_changed(float) { update_font_metrics(); @@ -335,7 +347,7 @@ void D2DEditor::update_font_metrics() format->GetFontFamilyName(name.data(), (UINT32) name.size()); QFont f; f.setFamily(QString::fromWCharArray(name.c_str())); - f.setPointSizeF(current_point_size); + f.setPointSizeF(current_point_size * scale_factor()); f.setLetterSpacing(QFont::AbsoluteSpacing, charspace); popup->font_changed(f, font_dimensions()); } diff --git a/src/platform/windows/d2deditor.hpp b/src/platform/windows/d2deditor.hpp index 95b52587..af0d0055 100644 --- a/src/platform/windows/d2deditor.hpp +++ b/src/platform/windows/d2deditor.hpp @@ -74,6 +74,9 @@ class D2DEditor : public QWidget, public QtEditorUIBase void create_grid(u32 x, u32 y, u32 w, u32 h, u64 id) override; void set_fonts(std::span fonts) override; u32 calc_fallback_index(u32 ucs); + // The scale factor for the current monitor, based on dpi divided + // by the default dpi (96). + float scale_factor() const; private: std::unordered_map fallback_indices {}; std::vector> dw_fonts; @@ -85,6 +88,7 @@ class D2DEditor : public QWidget, public QtEditorUIBase ComPtr device = nullptr; float current_point_size = 12.0f; bool vsync = true; + u32 win_dpi; }; #endif // NVUI_D2DEDITOR_HPP diff --git a/src/popupmenu.cpp b/src/popupmenu.cpp index 901b1829..995a05da 100644 --- a/src/popupmenu.cpp +++ b/src/popupmenu.cpp @@ -358,8 +358,12 @@ void PopupMenuQ::paintEvent(QPaintEvent*) QPainter p(this); int pad = border_width; QRect r(pad, pad, width() - 2 * pad, height() - 2 * pad); + auto dpr = devicePixelRatioF(); + QRect pixmap_rect = QRect( + r.x() * dpr, r.y() * dpr, r.width() * dpr, r.height() * dpr + ); p.fillRect(rect(), border_color.qcolor()); - p.drawPixmap(r, pixmap, r); + p.drawPixmap(r, pixmap, pixmap_rect); } void PopupMenuQ::update_dimensions() @@ -403,7 +407,8 @@ void PopupMenuQ::update_dimensions() auto new_pixmap_size = pixmap.size().expandedTo({width, height}); if (new_pixmap_size != pixmap.size()) { - pixmap = QPixmap(width, height); + pixmap = QPixmap(width * devicePixelRatioF(), height * devicePixelRatioF()); + pixmap.setDevicePixelRatio(devicePixelRatioF()); } resize(width, height); if (pmenu) pixmap.fill(hl_state->colors_for(*pmenu).bg.qcolor()); diff --git a/src/qeditor.cpp b/src/qeditor.cpp index 0de15ce9..dff28e71 100644 --- a/src/qeditor.cpp +++ b/src/qeditor.cpp @@ -19,7 +19,7 @@ static void set_relative_font_size( const double tolerance, const std::size_t max_iterations ) -{ +{ constexpr auto width = [](const QFontMetricsF& m) { return m.horizontalAdvance('a'); }; @@ -53,7 +53,8 @@ QEditor::QEditor( ) : QWidget(parent), QtEditorUIBase(*this, cols, rows, std::move(capabilities), - std::move(nvim_path), std::move(nvim_args)) + std::move(nvim_path), std::move(nvim_args)), + device_pixelratio(devicePixelRatioF()) { first_font.setFamily(default_font_family()); first_font.setPointSizeF(11.25); @@ -87,6 +88,11 @@ std::unique_ptr QEditor::cmdline_new() void QEditor::resizeEvent(QResizeEvent* ev) { Base::handle_nvim_resize(ev); + if (device_pixelratio != devicePixelRatioF()) + { + device_pixelratio = devicePixelRatioF(); + update_font_metrics(); + } update(); } @@ -192,7 +198,7 @@ void QEditor::set_fonts(std::span fontdescs) void QEditor::update_font_metrics() { first_font.setLetterSpacing(QFont::AbsoluteSpacing, charspace); - QFontMetricsF metrics {first_font}; + QFontMetricsF metrics {first_font, this}; float combined_height = std::max(metrics.height(), metrics.lineSpacing()); double font_height = combined_height + linespacing(); constexpr QChar any_char = 'W'; @@ -231,8 +237,11 @@ void QEditor::paintEvent(QPaintEvent*) auto* grid = static_cast(grid_base.get()); if (!grid->hidden) { - QSize size = grid->buffer().size(); - auto r = QRectF(grid->pos(), size).intersected(grid_clip_rect); + auto bf_dpr = grid->buffer().devicePixelRatioF(); + QSize sz = grid->buffer().size(); + sz.rwidth() /= bf_dpr; + sz.rheight() /= bf_dpr; + auto r = QRectF(grid->pos(), sz).intersected(grid_clip_rect); p.setClipRect(r); grid->process_events(); grid->render(p); diff --git a/src/qeditor.hpp b/src/qeditor.hpp index 5161e660..54bb7129 100644 --- a/src/qeditor.hpp +++ b/src/qeditor.hpp @@ -55,6 +55,7 @@ class QEditor : public QWidget, public QtEditorUIBase void update_font_metrics(); QFont first_font; std::vector fonts; + double device_pixelratio; }; #endif // NVUI_QEDITOR_HPP diff --git a/src/qpaintgrid.cpp b/src/qpaintgrid.cpp index 47be3477..8a4a0933 100644 --- a/src/qpaintgrid.cpp +++ b/src/qpaintgrid.cpp @@ -73,7 +73,12 @@ static void set_pen_width(QPainter& painter, double w) void QPaintGrid::update_pixmap_size() { auto&& [font_width, font_height] = editor_area->font_dimensions(); - pixmap = QPixmap(cols * font_width, rows * font_height); + pixmap = QPixmap( + cols * font_width * editor_area->devicePixelRatioF(), + rows * font_height * editor_area->devicePixelRatioF() + ); + pixmap.fill(editor_area->default_bg().qcolor()); + pixmap.setDevicePixelRatio(editor_area->devicePixelRatioF()); send_redraw(); } @@ -192,7 +197,7 @@ void QPaintGrid::draw_text_and_bg( QRectF rect = {start, end}; painter.setClipRect(rect); painter.fillRect(rect, bg.qcolor()); - rect.setWidth(rect.width() * 3.); + rect.setWidth(rect.width() + 1.0); draw_text( painter, text, fg, sp, rect, attr.font_opts, font, font_width, font_height ); @@ -307,13 +312,16 @@ void QPaintGrid::process_events() break; case PaintKind::Scroll: { + auto dpr = editor_area->devicePixelRatioF(); auto [font_width, font_height] = editor_area->font_dimensions(); const auto& [rect, dx, dy] = evt.scroll_info(); + // I love how the rectangle coordinates aren't automatically scaled + // by the pixmap's device pixel ratio QRect r( - rect.x() * font_width, - rect.y() * font_height, - rect.width() * font_width, - rect.height() * font_height + rect.x() * font_width * dpr, + rect.y() * font_height * dpr, + rect.width() * font_width * dpr, + rect.height() * font_height * dpr ); // This would probably be faster using QPixmap::scroll // but it doesn't want to work @@ -331,14 +339,20 @@ void QPaintGrid::process_events() void QPaintGrid::render(QPainter& p) { - auto&& [font_width, font_height] = editor_area->font_dimensions(); - QRectF rect(top_left.x(), top_left.y(), pixmap.width(), pixmap.height()); - auto snapshot_height = pixmap.height(); + auto [font_width, font_height] = editor_area->font_dimensions(); + // p (the editor painter) scales ITS OWN coordinates (the QPoint/QPointF) + // that's the first parameter in every call) by the device pixel ratio + // However, the coordinates of the grid pixmap are not scaled by the DPR. + // So we have to take this into account when interacting with the painter. + auto pixmap_height = pixmap.height() / editor_area->devicePixelRatioF(); + auto pixmap_width = pixmap.width() / editor_area->devicePixelRatioF(); + QRectF rect {top_left, QSize(pixmap_width, pixmap_height)}; if (!editor_area->animations_enabled() || !is_scrolling) { p.drawPixmap(pos(), pixmap); return; } + QPointF topleft_text = QPointF(top_left.x() / font_width, top_left.y() / font_height); p.fillRect(rect, editor_area->hlstate().default_bg().qcolor()); float cur_scroll_y = current_scroll_y * font_height; float cur_snapshot_top = viewport.topline * font_height; @@ -347,32 +361,38 @@ void QPaintGrid::render(QPainter& p) for(auto it = snapshots.rbegin(); it != snapshots.rend(); ++it) { const auto& snapshot = *it; - QRectF r; - float snapshot_top = snapshot.vp.topline * font_height; - float offset = snapshot_top - cur_scroll_y; - auto pixmap_top = top_left.y() + offset; - QPointF pt; + float offset = snapshot.vp.topline - current_scroll_y; + QRectF px_rect; + QPointF px_pt; + float top_row = topleft_text.y() + offset; if (snapshot.vp.topline < min_topline) { - auto height = (min_topline - snapshot.vp.topline) * font_height; - height = std::min(height, float(snapshot_height)); + float height = (min_topline - snapshot.vp.topline); + height = std::min(height, float(rows)); min_topline = snapshot.vp.topline; - r = QRect(0, 0, pixmap.width(), height); - pt = {top_left.x(), pixmap_top}; + px_rect = QRectF(0, 0, cols, height); + px_pt = QPointF(topleft_text.x(), top_row); } else if (snapshot.vp.botline > max_botline) { - auto height = (snapshot.vp.botline - max_botline) * font_height; - height = std::min(height, float(snapshot_height)); + float height = (snapshot.vp.botline - max_botline); + height = std::min(height, float(rows)); max_botline = snapshot.vp.botline; - r = QRect(0, snapshot_height - height, pixmap.width(), height); - pt = {top_left.x(), pixmap_top + pixmap.height() - height}; - } - QRectF draw_rect = {top_left, r.size()}; - if (!r.isNull() && rect.contains(draw_rect)) - { - p.drawPixmap(pt, snapshot.image, r); + px_rect = QRectF(0, (rows - height), cols, height); + px_pt = QPointF(topleft_text.x(), top_row + (rows - height)); } + else continue; + double dpr = editor_area->devicePixelRatioF(); + p.drawPixmap( + QPointF {px_pt.x() * font_width, px_pt.y() * font_height}, + snapshot.image, + QRectF { + px_rect.x() * font_width * dpr, + px_rect.y() * font_height * dpr, + px_rect.width() * font_width * dpr, + px_rect.height() * font_height * dpr + } + ); } float offset = cur_snapshot_top - cur_scroll_y; QPointF pt = {top_left.x(), top_left.y() + offset}; @@ -417,7 +437,7 @@ void QPaintGrid::update_position(double new_x, double new_y) top_left = {new_x * font_width, new_y * font_height}; } -void QPaintGrid::initialize_cache() +void QPaintGrid::init_connections() { QObject::connect(editor_area, &QEditor::font_changed, this, [&] { text_cache.clear(); diff --git a/src/qpaintgrid.hpp b/src/qpaintgrid.hpp index 89fe99a7..17e199f1 100644 --- a/src/qpaintgrid.hpp +++ b/src/qpaintgrid.hpp @@ -36,7 +36,7 @@ class QPaintGrid : public GridBase { update_pixmap_size(); update_position(x, y); - initialize_cache(); + init_connections(); initialize_scroll_animation(); initialize_move_animation(); } @@ -85,8 +85,8 @@ class QPaintGrid : public GridBase ); /// Update the pixmap size void update_pixmap_size(); - /// Initialize the cache - void initialize_cache(); + /// Create necessary connections between editor and grid + void init_connections(); /// Initialize scroll animation timer void initialize_scroll_animation(); /// Initialize move animation timer diff --git a/src/utils.hpp b/src/utils.hpp index 31c31c6c..2d561605 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -133,7 +133,7 @@ msgpack::object_handle pack(const T& obj) inline QString default_font_family() { #if defined(Q_OS_MAC) - return "Courier New"; + return "Menlo"; #elif defined(Q_OS_WIN) return "Consolas"; #else diff --git a/test/test_default_colors.cpp b/test/test_default_colors.cpp index ebd8a67b..35b822f2 100644 --- a/test/test_default_colors.cpp +++ b/test/test_default_colors.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include "hlstate.hpp" #include "object.hpp" diff --git a/test/test_hl_attr_from_object.cpp b/test/test_hl_attr_from_object.cpp index 58a90c2d..8bc1af22 100644 --- a/test/test_hl_attr_from_object.cpp +++ b/test/test_hl_attr_from_object.cpp @@ -1,6 +1,6 @@ #include "object.hpp" #include "hlstate.hpp" -#include +#include #include #include #include diff --git a/test/test_main.cpp b/test/test_main.cpp index 4ed06df1..927523ec 100644 --- a/test/test_main.cpp +++ b/test/test_main.cpp @@ -1,2 +1,7 @@ -#define CATCH_CONFIG_MAIN -#include +#include + +int main(int argc, char** argv) +{ + int result = Catch::Session().run(argc, argv); + return result; +} diff --git a/test/test_nvim_eval.cpp b/test/test_nvim_eval.cpp index a6300d40..5a4eee46 100644 --- a/test/test_nvim_eval.cpp +++ b/test/test_nvim_eval.cpp @@ -1,6 +1,6 @@ #include "nvim.hpp" #include "utils.hpp" -#include +#include #include #include #include diff --git a/test/test_nvim_set_var.cpp b/test/test_nvim_set_var.cpp index 47d7a87b..fb5d8e67 100644 --- a/test/test_nvim_set_var.cpp +++ b/test/test_nvim_set_var.cpp @@ -1,6 +1,6 @@ #include "nvim.hpp" #include "utils.hpp" -#include +#include #include #include #include diff --git a/test/test_object.cpp b/test/test_object.cpp index bacf2591..07472486 100644 --- a/test/test_object.cpp +++ b/test/test_object.cpp @@ -1,6 +1,6 @@ #include #include "object.hpp" -#include +#include #include #include diff --git a/test/test_object_deserialization_msgpack.cpp b/test/test_object_deserialization_msgpack.cpp index 1c9f4d6c..d3fe2f71 100644 --- a/test/test_object_deserialization_msgpack.cpp +++ b/test/test_object_deserialization_msgpack.cpp @@ -1,4 +1,4 @@ -#include +#include #include "object.hpp" #include #include diff --git a/vcpkg b/vcpkg index 03ca9b59..89e38172 160000 --- a/vcpkg +++ b/vcpkg @@ -1 +1 @@ -Subproject commit 03ca9b59af1506a86840e3a3a01a092f3333a29b +Subproject commit 89e38172980b10200c2170a1914300643b352b85 diff --git a/vcpkg.json b/vcpkg.json index f22d4471..d58e5125 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -5,8 +5,12 @@ "dependencies": [ "fmt", "msgpack", - "qt5-base", - "qt5-svg", + { + "name": "qt5-base" + }, + { + "name": "qt5-svg" + }, "boost-process", "boost-container" ],