mirror of https://github.com/sgoudham/neovide.git
Opengl (#655)
* OpenGL Backend (#486) * opengl renderer sorta working * add x11 build argument * remove x11 * format * remove alternate windowing systems * remove non opengl windowing systems * fix thread spin * add graphics interface debugging expects * update interface build and add conditional compilation * install nightly in actions * add format component to nightly install * remove vulkan from action * working without shaping * Add rustybuzz * applied suggestions from calvinkosmatka * remove husky * update skia-safe * better subpixel font rendering * commit lock file * fix merge error * Bump skia-safe to 0.39.1 (#584) Compiling for apple silicon/m1 only works on 0.39.1 * remove sdl2 file and upgrade skia/swap to egl * Update README.md to reflect changes in the main branch. Fix sequence break in Mac installation guide. (#615) Co-authored-by: Lord Valen <lord_valen@protonmail.com> * OpenGL Backend (#486) * opengl renderer sorta working * add x11 build argument * remove x11 * format * remove alternate windowing systems * remove non opengl windowing systems * fix thread spin * add graphics interface debugging expects * update interface build and add conditional compilation * install nightly in actions * add format component to nightly install * remove vulkan from action * working without shaping * Add rustybuzz * applied suggestions from calvinkosmatka * remove husky * update skia-safe * better subpixel font rendering * commit lock file * fix merge error * Bump skia-safe to 0.39.1 (#584) Compiling for apple silicon/m1 only works on 0.39.1 * remove sdl2 file and upgrade skia/swap to egl * Update README.md to reflect changes in the main branch. Fix sequence break in Mac installation guide. (#615) Co-authored-by: Lord Valen <lord_valen@protonmail.com> * Version check (#631) * add more robust version check * clippy * fix some copy pasta * revert font changes * add lock file back * pull in animation length change from main and adjust default cursor settings * Snap for Ubuntu (#576) * add more robust version check * clippy * fix some copy pasta * revert font changes * snap builds correctly * add snapcraft workflow * clean up workflow * clean up workflow * clean up workflow * check workflow works * build snap * use lxd * add snap push * add step id * use official snapcraft actions * add snap badge * move snap badge * swap from rustybuzz to swash. WAY faster performance * format files * fix command line jump filtering * attempt fix of github action * add comma * add caching to the build dependencies * fix foating window position * Fix 577 (#668) * fix formatting and clippy errors * upstream formatting fix * Added trackpad support to scrolling/smoothscrolling in OpenGL branch (#681) * Update mod.rs * Update mod.rs * Update mod.rs * Update mod.rs * Update mod.rs * Update mod.rs * Fixed regular scroll * Update mod.rs * Update mod.rs * Reverted previous change, dividing first instead * Update mod.rs * Update mod.rs * I completely forgot about how unsigned integers in rust work * Remove the casting to integer * Forgot function signature requires integer * divide before cast * remove semicolon * Changed function signature to use float instead of integer, using float for PixelDelta * Forgot parenthesis * Change 0 to math float * Do the same for the function itself * Scrolling still too fast * Still way to fast * Still too fast * Still too fast * somehow still too fast * Still too fast * Update mod.rs * testing * still fast * doesn't fit in f32 anymore * Update mod.rs * Update mod.rs * Update mod.rs * Update mod.rs * Update mod.rs * Testing * Testing something new * Update mod.rs * Update mod.rs * Deleted unnecessary code * Implement CLI parsing with clap (#680) * implemented command line parsing with clap * removed println! * fixed file opening * added files parameter to avoid -- [FILES] * use new cmdlinesettings in various places * lets stick to the old cli-api * moved to builder syntax to allow for hypens in args * Fixed merge conflict that was overlooked before * Basic Environment variables * Added alias for MultiGrid environment var * Fix for #566 (#687) * add logging to async channels * use better logging in channels * made float change backwards compatible * fix clippy work * ignore new clippy lint * fix mac clippy lint * adjust actions * use nightly format * install utilities in mac and linux builds * Fix some shift+key mappings (#695) * fix publish test results step * Basic Frameless window support (#694) * Frameless window (not resizeable) * Update * Delete neovide.ico idk how did this get here * add readme tweak to make sure people know what neovim is * add scoop instructions for windows * implement manual font fallback with swash * M1 Runner (#711) * add self-hosted m1 * remove vulkan * cahnge toolchain * fmt * clippy * remove llvm * upload m1 * checkout upstream build.yaml * remove llvm step * remove git caching for self hosted * Add environment variables for window options (#708) * Add Env for window options * Remove PascalCase * Font Fallback (#701) * better font fallback * actually fix font fallback * swap to u64s for most font size solutions * increase line height slightly and attempt stencil buffer fix * fix divider issue * clippy and formatting fixes Co-authored-by: Keith Simmons <keithsim@microsoft.com> * clippy fixes * Fix default font (#719) * size using Z * default font ordering * clippy fixes * interactive wsl path * fix formatting Co-authored-by: Keith Simmons <keithsim@microsoft.com> Co-authored-by: shaunsingh <71196912+shaunsingh@users.noreply.github.com> Co-authored-by: Lord-Valen <46138807+Lord-Valen@users.noreply.github.com> Co-authored-by: Lord Valen <lord_valen@protonmail.com> Co-authored-by: j4qfrost <j4qfrost@gmail.com> Co-authored-by: PyGamer0 <64531844+PyGamer0@users.noreply.github.com> Co-authored-by: Obyoxar <43534802+ErikMayrhofer@users.noreply.github.com> Co-authored-by: Benson Li <bensonbinbinli@gmail.com> Co-authored-by: meluskyc <meluskyc@gmail.com>macos-click-through
parent
c6a68915a2
commit
c7694569bd
@ -0,0 +1,26 @@
|
|||||||
|
name: Snap
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
snap:
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out Git repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- uses: snapcore/action-build@v1
|
||||||
|
env:
|
||||||
|
SNAPCRAFT_BUILD_ENVIRONMENT_MEMORY: 6G
|
||||||
|
id: snapcraft
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: snap
|
||||||
|
path: ${{ steps.snapcraft.outputs.snap }}
|
||||||
|
|
||||||
|
- uses: snapcore/action-publish@v1
|
||||||
|
with:
|
||||||
|
store_login: ${{ secrets.SNAPCRAFT_TOKEN }}
|
||||||
|
snap: ${{ steps.snapcraft.outputs.snap }}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -0,0 +1,86 @@
|
|||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
Binary file not shown.
@ -0,0 +1,114 @@
|
|||||||
|
name: neovide # you probably want to 'snapcraft register <name>'
|
||||||
|
base: core18 # the base snap is the execution environment for this snap
|
||||||
|
version: "0.7.1+git"
|
||||||
|
summary: The snappiest vim editor you are likely to find.
|
||||||
|
description: |
|
||||||
|
This is a simple graphical user interface for Neovim. Where possible there are some graphical improvements,
|
||||||
|
but it should act functionally like the terminal UI.
|
||||||
|
|
||||||
|
grade: devel # must be 'stable' to release into candidate/stable channels
|
||||||
|
confinement: strict # use 'strict' once you have the right plugs and slots
|
||||||
|
build-packages:
|
||||||
|
- cmake
|
||||||
|
- freeglut3-dev
|
||||||
|
- libbz2-dev
|
||||||
|
- libexpat1-dev
|
||||||
|
- libgl-dev
|
||||||
|
- libssl-dev
|
||||||
|
- libxmu-dev
|
||||||
|
- pkg-config
|
||||||
|
|
||||||
|
parts:
|
||||||
|
nvim:
|
||||||
|
source: https://github.com/neovim/neovim.git
|
||||||
|
override-pull: |
|
||||||
|
snapcraftctl pull
|
||||||
|
latest_tag="$(git tag -l --sort=refname|head -1)"
|
||||||
|
git checkout "${latest_tag}"
|
||||||
|
major="$(awk '/NVIM_VERSION_MAJOR/{gsub(")","",$2); print $2}' CMakeLists.txt)"
|
||||||
|
minor="$(awk '/NVIM_VERSION_MINOR/{gsub(")","",$2); print $2}' CMakeLists.txt)"
|
||||||
|
patch="$(awk '/NVIM_VERSION_PATCH/{gsub(")","",$2); print $2}' CMakeLists.txt)"
|
||||||
|
version_prefix="v$major.$minor.$patch"
|
||||||
|
git_described="$(git describe --first-parent --dirty 2> /dev/null | perl -lpe 's/v\d.\d.\d-//g')"
|
||||||
|
git_described="${git_described:-$(git describe --first-parent --tags --always --dirty)}"
|
||||||
|
if [ "${version_prefix}" != "${git_described}" ]; then
|
||||||
|
VERSION="${version_prefix}-${git_described}-${latest_tag}"
|
||||||
|
else
|
||||||
|
VERSION="${version_prefix}-${latest_tag}"
|
||||||
|
fi
|
||||||
|
snapcraftctl set-version "${VERSION}"
|
||||||
|
plugin: make
|
||||||
|
make-parameters:
|
||||||
|
- CMAKE_BUILD_TYPE=RelWithDebInfo
|
||||||
|
- CMAKE_INSTALL_PREFIX=/usr
|
||||||
|
- CMAKE_FLAGS=-DPREFER_LUA=ON
|
||||||
|
- DEPS_CMAKE_FLAGS="-DUSE_BUNDLED_LUA=ON -DUSE_BUNDLED_LUAJIT=OFF"
|
||||||
|
override-build: |
|
||||||
|
echo "Building on $SNAP_ARCH"
|
||||||
|
set -x
|
||||||
|
case "$SNAP_ARCH" in
|
||||||
|
"arm64" | "ppc64el" | "s390x")
|
||||||
|
make -j"${SNAPCRAFT_PARALLEL_BUILD_COUNT}" \
|
||||||
|
CMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||||
|
CMAKE_INSTALL_PREFIX=/usr \
|
||||||
|
CMAKE_FLAGS=-DPREFER_LUA=ON \
|
||||||
|
DEPS_CMAKE_FLAGS="-DUSE_BUNDLED_LUA=ON -DUSE_BUNDLED_LUAJIT=OFF"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
make -j"${SNAPCRAFT_PARALLEL_BUILD_COUNT}" \
|
||||||
|
CMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||||
|
CMAKE_INSTALL_PREFIX=/usr
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
make DESTDIR="$SNAPCRAFT_PART_INSTALL" install
|
||||||
|
# Fix Desktop file
|
||||||
|
sed -i 's|^Exec=nvim|Exec=/snap/bin/nvim.nvim|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop
|
||||||
|
sed -i 's|^TryExec=nvim|TryExec=/snap/bin/nvim.nvim|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop
|
||||||
|
sed -i 's|^Icon=.*|Icon=${SNAP}/usr/share/pixmaps/nvim.png|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop
|
||||||
|
build-packages:
|
||||||
|
- ninja-build
|
||||||
|
- libtool
|
||||||
|
- libtool-bin
|
||||||
|
- autoconf
|
||||||
|
- automake
|
||||||
|
- gawk
|
||||||
|
- g++
|
||||||
|
- git
|
||||||
|
- gettext
|
||||||
|
- unzip
|
||||||
|
- wget
|
||||||
|
prime:
|
||||||
|
- -usr/share/man
|
||||||
|
desktop-glib-only:
|
||||||
|
plugin: make
|
||||||
|
source: https://github.com/ubuntu/snapcraft-desktop-helpers.git
|
||||||
|
source-subdir: glib-only
|
||||||
|
neovide:
|
||||||
|
plugin: rust
|
||||||
|
source: .
|
||||||
|
build-packages:
|
||||||
|
- fontconfig
|
||||||
|
- libfontconfig1-dev
|
||||||
|
- libfreetype6-dev
|
||||||
|
stage-packages:
|
||||||
|
- fontconfig
|
||||||
|
- fonts-noto
|
||||||
|
- libfontconfig1
|
||||||
|
- libibus-1.0-5
|
||||||
|
- libpng16-16
|
||||||
|
- libx11-dev
|
||||||
|
- libx11-xcb1
|
||||||
|
- libxcursor1
|
||||||
|
- libxi-dev
|
||||||
|
- libxrandr-dev
|
||||||
|
- locales-all
|
||||||
|
- xdg-user-dirs
|
||||||
|
|
||||||
|
apps:
|
||||||
|
neovide:
|
||||||
|
command: bin/neovide
|
||||||
|
plugs:
|
||||||
|
- desktop
|
||||||
|
- desktop-legacy
|
||||||
|
command-chain:
|
||||||
|
- bin/desktop-launch
|
@ -0,0 +1,54 @@
|
|||||||
|
use std::fmt::Debug;
|
||||||
|
use std::sync::mpsc::{SendError, Sender};
|
||||||
|
|
||||||
|
use crossfire::mpsc::{SendError as TxError, TxUnbounded};
|
||||||
|
use log::trace;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LoggingSender<T>
|
||||||
|
where
|
||||||
|
T: Debug,
|
||||||
|
{
|
||||||
|
sender: Sender<T>,
|
||||||
|
channel_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> LoggingSender<T>
|
||||||
|
where
|
||||||
|
T: Debug,
|
||||||
|
{
|
||||||
|
pub fn attach(sender: Sender<T>, channel_name: String) -> Self {
|
||||||
|
Self {
|
||||||
|
sender,
|
||||||
|
channel_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send(&self, message: T) -> Result<(), SendError<T>> {
|
||||||
|
trace!("{} {:?}", self.channel_name, &message);
|
||||||
|
self.sender.send(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LoggingTx<T>
|
||||||
|
where
|
||||||
|
T: Debug,
|
||||||
|
{
|
||||||
|
tx: TxUnbounded<T>,
|
||||||
|
channel_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> LoggingTx<T>
|
||||||
|
where
|
||||||
|
T: Debug,
|
||||||
|
{
|
||||||
|
pub fn attach(tx: TxUnbounded<T>, channel_name: String) -> Self {
|
||||||
|
Self { tx, channel_name }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send(&self, message: T) -> Result<(), TxError<T>> {
|
||||||
|
trace!("{} {:?}", self.channel_name, &message);
|
||||||
|
self.tx.send(message)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
use crate::settings::*;
|
||||||
|
|
||||||
|
use clap::{App, Arg};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct CmdLineSettings {
|
||||||
|
pub verbosity: u64,
|
||||||
|
pub log_to_file: bool,
|
||||||
|
pub neovim_args: Vec<String>,
|
||||||
|
pub neovim_bin: Option<String>,
|
||||||
|
pub files_to_open: Vec<String>,
|
||||||
|
|
||||||
|
pub disowned: bool,
|
||||||
|
pub geometry: Option<String>,
|
||||||
|
pub wsl: bool,
|
||||||
|
pub remote_tcp: Option<String>,
|
||||||
|
pub multi_grid: bool,
|
||||||
|
pub maximized: bool,
|
||||||
|
pub frameless: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CmdLineSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
neovim_bin: None,
|
||||||
|
verbosity: 0,
|
||||||
|
log_to_file: false,
|
||||||
|
neovim_args: vec![],
|
||||||
|
files_to_open: vec![],
|
||||||
|
disowned: false,
|
||||||
|
geometry: None,
|
||||||
|
wsl: false,
|
||||||
|
remote_tcp: None,
|
||||||
|
multi_grid: false,
|
||||||
|
maximized: false,
|
||||||
|
frameless: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_command_line_arguments() {
|
||||||
|
let clapp = App::new("Neovide")
|
||||||
|
.version(crate_version!())
|
||||||
|
.author(crate_authors!())
|
||||||
|
.about(crate_description!())
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("verbosity")
|
||||||
|
.short("v")
|
||||||
|
.multiple(true)
|
||||||
|
.help("Set the level of verbosity"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("log_to_file")
|
||||||
|
.long("log")
|
||||||
|
.help("Log to a file"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("disowned")
|
||||||
|
.long("disowned")
|
||||||
|
.help("Disown the process. (only on macos)"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("maximized")
|
||||||
|
.long("maximized")
|
||||||
|
.help("Maximize the window"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("multi_grid")
|
||||||
|
//.long("multi-grid") TODO: multiGrid is the current way to call this, but I
|
||||||
|
//personally would prefer sticking to a unix-y way of naming things...
|
||||||
|
.long("multiGrid")
|
||||||
|
.help("Enable Multigrid"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("frameless")
|
||||||
|
.long("frameless")
|
||||||
|
.help("Removes the window frame. NOTE: Window might not be resizable after this setting is enabled.")
|
||||||
|
)
|
||||||
|
.arg(Arg::with_name("wsl").long("wsl").help("Run in WSL"))
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("remote_tcp")
|
||||||
|
.long("remote-tcp")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Connect to Remote TCP"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("geometry")
|
||||||
|
.long("geometry")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Specify the Geometry of the window"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("files")
|
||||||
|
.multiple(true)
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Specify the Geometry of the window"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("neovim_args")
|
||||||
|
.multiple(true)
|
||||||
|
.takes_value(true)
|
||||||
|
.last(true)
|
||||||
|
.help("Specify Arguments to pass down to neovim"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let matches = clapp.get_matches();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Integrate Environment Variables as Defaults to the command-line ones.
|
||||||
|
*
|
||||||
|
* NEOVIM_BIN
|
||||||
|
* NeovideMultiGrid || --multiGrid
|
||||||
|
*/
|
||||||
|
SETTINGS.set::<CmdLineSettings>(&CmdLineSettings {
|
||||||
|
neovim_bin: std::env::var("NEOVIM_BIN").ok(),
|
||||||
|
neovim_args: matches
|
||||||
|
.values_of("neovim_args")
|
||||||
|
.map(|opt| opt.map(|v| v.to_owned()).collect())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
verbosity: matches.occurrences_of("verbosity"),
|
||||||
|
log_to_file: matches.is_present("log_to_file"),
|
||||||
|
files_to_open: matches
|
||||||
|
.values_of("files")
|
||||||
|
.map(|opt| opt.map(|v| v.to_owned()).collect())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
maximized: matches.is_present("maximized") || std::env::var("NEOVIDE_MAXIMIZED").is_ok(),
|
||||||
|
multi_grid: std::env::var("NEOVIDE_MULTIGRID").is_ok()
|
||||||
|
|| std::env::var("NeovideMultiGrid").is_ok()
|
||||||
|
|| matches.is_present("multi_grid"),
|
||||||
|
remote_tcp: matches.value_of("remote_tcp").map(|i| i.to_owned()),
|
||||||
|
disowned: matches.is_present("disowned"),
|
||||||
|
wsl: matches.is_present("wsl"),
|
||||||
|
geometry: matches.value_of("geometry").map(|i| i.to_owned()),
|
||||||
|
frameless: matches.is_present("frameless") || std::env::var("NEOVIDE_FRAMELESS").is_ok(),
|
||||||
|
});
|
||||||
|
}
|
@ -1,190 +1,266 @@
|
|||||||
use std::collections::HashMap;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use font_kit::metrics::Metrics;
|
|
||||||
use log::{trace, warn};
|
use log::{trace, warn};
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
use skribo::{FontCollection, FontRef as SkriboFont, LayoutSession, TextStyle};
|
use skia_safe::{TextBlob, TextBlobBuilder};
|
||||||
use skulpin::skia_safe::{Font as SkiaFont, TextBlob, TextBlobBuilder};
|
use swash::shape::ShapeContext;
|
||||||
|
use swash::text::cluster::{CharCluster, Parser, Status, Token};
|
||||||
|
use swash::text::Script;
|
||||||
|
use swash::Metrics;
|
||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use super::font_loader::*;
|
use super::font_loader::*;
|
||||||
use super::font_options::*;
|
use super::font_options::*;
|
||||||
use super::utils::*;
|
|
||||||
|
|
||||||
const STANDARD_CHARACTER_STRING: &str =
|
|
||||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
|
||||||
|
|
||||||
#[cfg(any(feature = "embed-fonts", test))]
|
|
||||||
#[derive(RustEmbed)]
|
|
||||||
#[folder = "assets/fonts/"]
|
|
||||||
pub struct Asset;
|
|
||||||
|
|
||||||
const DEFAULT_FONT_SIZE: f32 = 14.0;
|
const DEFAULT_FONT_SIZE: f32 = 14.0;
|
||||||
|
|
||||||
#[derive(new, Clone, Hash, PartialEq, Eq, Debug)]
|
#[derive(new, Clone, Hash, PartialEq, Eq, Debug)]
|
||||||
struct ShapeKey {
|
struct ShapeKey {
|
||||||
pub text: String,
|
pub cells: Vec<String>,
|
||||||
pub bold: bool,
|
pub bold: bool,
|
||||||
pub italic: bool,
|
pub italic: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FontSet {
|
pub struct CachingShaper {
|
||||||
normal: FontCollection,
|
pub options: Option<FontOptions>,
|
||||||
bold: FontCollection,
|
font_loader: FontLoader,
|
||||||
italic: FontCollection,
|
blob_cache: LruCache<ShapeKey, Vec<TextBlob>>,
|
||||||
|
shape_context: ShapeContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FontSet {
|
impl CachingShaper {
|
||||||
fn new(fallback_list: &[String], loader: &mut FontLoader) -> FontSet {
|
pub fn new() -> CachingShaper {
|
||||||
FontSet {
|
CachingShaper {
|
||||||
normal: loader
|
options: None,
|
||||||
.build_collection_by_font_name(fallback_list, build_properties(false, false)),
|
font_loader: FontLoader::new(DEFAULT_FONT_SIZE),
|
||||||
bold: loader
|
blob_cache: LruCache::new(10000),
|
||||||
.build_collection_by_font_name(fallback_list, build_properties(true, false)),
|
shape_context: ShapeContext::new(),
|
||||||
italic: loader
|
|
||||||
.build_collection_by_font_name(fallback_list, build_properties(false, true)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, bold: bool, italic: bool) -> &FontCollection {
|
fn current_font_pair(&mut self) -> Arc<FontPair> {
|
||||||
match (bold, italic) {
|
let font_key = self
|
||||||
(true, _) => &self.bold,
|
.options
|
||||||
(false, false) => &self.normal,
|
.as_ref()
|
||||||
(false, true) => &self.italic,
|
.map(|options| options.fallback_list.first().unwrap().clone().into())
|
||||||
|
.unwrap_or(FontKey::Default);
|
||||||
|
|
||||||
|
self.font_loader
|
||||||
|
.get_or_load(font_key)
|
||||||
|
.expect("Could not load font")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn current_size(&self) -> f32 {
|
||||||
|
self.options
|
||||||
|
.as_ref()
|
||||||
|
.map(|options| options.size)
|
||||||
|
.unwrap_or(DEFAULT_FONT_SIZE)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CachingShaper {
|
pub fn update_font(&mut self, guifont_setting: &str) -> bool {
|
||||||
pub options: FontOptions,
|
let new_options = FontOptions::parse(guifont_setting, DEFAULT_FONT_SIZE);
|
||||||
font_set: FontSet,
|
|
||||||
font_loader: FontLoader,
|
|
||||||
font_cache: LruCache<String, SkiaFont>,
|
|
||||||
blob_cache: LruCache<ShapeKey, Vec<TextBlob>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CachingShaper {
|
if new_options != self.options && new_options.is_some() {
|
||||||
pub fn new() -> CachingShaper {
|
self.font_loader = FontLoader::new(new_options.as_ref().unwrap().size);
|
||||||
let options = FontOptions::new(String::from(SYSTEM_DEFAULT_FONT), DEFAULT_FONT_SIZE);
|
self.blob_cache.clear();
|
||||||
let mut loader = FontLoader::new();
|
self.options = new_options;
|
||||||
let font_set = FontSet::new(&options.fallback_list, &mut loader);
|
|
||||||
|
|
||||||
CachingShaper {
|
true
|
||||||
options,
|
} else {
|
||||||
font_set,
|
false
|
||||||
font_loader: loader,
|
|
||||||
font_cache: LruCache::new(10),
|
|
||||||
blob_cache: LruCache::new(10000),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_skia_font(&mut self, skribo_font: &SkriboFont) -> Option<&SkiaFont> {
|
fn metrics(&mut self) -> Metrics {
|
||||||
let font_name = skribo_font.font.postscript_name()?;
|
let font_pair = self.current_font_pair();
|
||||||
if !self.font_cache.contains(&font_name) {
|
let size = self.current_size();
|
||||||
let font = build_skia_font_from_skribo_font(skribo_font, self.options.size)?;
|
let shaper = self
|
||||||
self.font_cache.put(font_name.clone(), font);
|
.shape_context
|
||||||
|
.builder(font_pair.swash_font.as_ref())
|
||||||
|
.size(size)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
shaper.metrics()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.font_cache.get(&font_name)
|
pub fn font_base_dimensions(&mut self) -> (u64, u64) {
|
||||||
|
let metrics = self.metrics();
|
||||||
|
let font_height = (metrics.ascent + metrics.descent + metrics.leading).ceil() as u64;
|
||||||
|
let font_width = metrics.average_width as u64;
|
||||||
|
|
||||||
|
(font_width, font_height)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn metrics(&self) -> Metrics {
|
pub fn underline_position(&mut self) -> u64 {
|
||||||
self.font_set
|
self.metrics().underline_offset as u64
|
||||||
.normal
|
|
||||||
.itemize("a")
|
|
||||||
.next()
|
|
||||||
.expect("Cannot get font metrics")
|
|
||||||
.1
|
|
||||||
.font
|
|
||||||
.metrics()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shape(&mut self, text: &str, bold: bool, italic: bool) -> Vec<TextBlob> {
|
pub fn y_adjustment(&mut self) -> u64 {
|
||||||
let style = TextStyle {
|
|
||||||
size: self.options.size,
|
|
||||||
};
|
|
||||||
let session = LayoutSession::create(text, &style, &self.font_set.get(bold, italic));
|
|
||||||
let metrics = self.metrics();
|
let metrics = self.metrics();
|
||||||
let ascent = metrics.ascent * self.options.size / metrics.units_per_em as f32;
|
(metrics.ascent + metrics.leading) as u64
|
||||||
let mut blobs = Vec::new();
|
}
|
||||||
|
|
||||||
for layout_run in session.iter_all() {
|
fn build_clusters(&mut self, text: &str) -> Vec<(Vec<CharCluster>, Arc<FontPair>)> {
|
||||||
let skribo_font = layout_run.font();
|
let mut cluster = CharCluster::new();
|
||||||
|
|
||||||
if let Some(skia_font) = self.get_skia_font(&skribo_font) {
|
// Enumerate the characters storing the glyph index in the user data so that we can position
|
||||||
let mut blob_builder = TextBlobBuilder::new();
|
// glyphs according to Neovim's grid rules
|
||||||
let count = layout_run.glyphs().count();
|
let mut character_index = 0;
|
||||||
let (glyphs, positions) =
|
let mut parser = Parser::new(
|
||||||
blob_builder.alloc_run_pos_h(&skia_font, count, ascent, None);
|
Script::Latin,
|
||||||
|
text.graphemes(true)
|
||||||
|
.enumerate()
|
||||||
|
.map(|(glyph_index, unicode_segment)| {
|
||||||
|
unicode_segment.chars().map(move |character| {
|
||||||
|
let token = Token {
|
||||||
|
ch: character,
|
||||||
|
offset: character_index as u32,
|
||||||
|
len: character.len_utf8() as u8,
|
||||||
|
info: character.into(),
|
||||||
|
data: glyph_index as u32,
|
||||||
|
};
|
||||||
|
character_index += 1;
|
||||||
|
token
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten(),
|
||||||
|
);
|
||||||
|
|
||||||
for (i, glyph) in layout_run.glyphs().enumerate() {
|
let mut results = Vec::new();
|
||||||
glyphs[i] = glyph.glyph_id as u16;
|
'cluster: while parser.next(&mut cluster) {
|
||||||
positions[i] = glyph.offset.x();
|
let mut font_fallback_keys = Vec::new();
|
||||||
|
|
||||||
|
// Add guifont fallback list
|
||||||
|
if let Some(options) = &self.options {
|
||||||
|
font_fallback_keys.extend(
|
||||||
|
options
|
||||||
|
.fallback_list
|
||||||
|
.iter()
|
||||||
|
.map(|font_name| font_name.into()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
// Add default font
|
||||||
|
font_fallback_keys.push(FontKey::Default);
|
||||||
|
|
||||||
|
// Add skia fallback
|
||||||
|
font_fallback_keys.push(cluster.chars()[0].ch.into());
|
||||||
|
|
||||||
blobs.push(blob_builder.make().unwrap());
|
let mut best = None;
|
||||||
|
// Use the cluster.map function to select a viable font from the fallback list
|
||||||
|
for fallback_key in font_fallback_keys.into_iter() {
|
||||||
|
if let Some(font_pair) = self.font_loader.get_or_load(fallback_key) {
|
||||||
|
let charmap = font_pair.swash_font.as_ref().charmap();
|
||||||
|
match cluster.map(|ch| charmap.map(ch)) {
|
||||||
|
Status::Complete => {
|
||||||
|
results.push((cluster.to_owned(), font_pair.clone()));
|
||||||
|
continue 'cluster;
|
||||||
|
}
|
||||||
|
Status::Keep => best = Some(font_pair),
|
||||||
|
Status::Discard => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we find a font with partial coverage of the cluster, select it
|
||||||
|
if let Some(best) = best {
|
||||||
|
results.push((cluster.to_owned(), best.clone()));
|
||||||
|
continue 'cluster;
|
||||||
} else {
|
} else {
|
||||||
warn!("Could not load skribo font");
|
warn!("No valid font for {:?}", cluster.chars());
|
||||||
|
|
||||||
|
// No good match. Just render with the default font.
|
||||||
|
let default_font = self
|
||||||
|
.font_loader
|
||||||
|
.get_or_load(FontKey::Default)
|
||||||
|
.expect("Could not load default font");
|
||||||
|
results.push((cluster.to_owned(), default_font));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blobs
|
// Now we have to group clusters by the font used so that the shaper can actually form
|
||||||
|
// ligatures across clusters
|
||||||
|
let mut grouped_results = Vec::new();
|
||||||
|
let mut current_group = Vec::new();
|
||||||
|
let mut current_font_option = None;
|
||||||
|
for (cluster, font) in results {
|
||||||
|
if let Some(current_font) = current_font_option.clone() {
|
||||||
|
if current_font == font {
|
||||||
|
current_group.push(cluster);
|
||||||
|
} else {
|
||||||
|
grouped_results.push((current_group, current_font));
|
||||||
|
current_group = Vec::new();
|
||||||
|
current_font_option = Some(font);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current_group = vec![cluster];
|
||||||
|
current_font_option = Some(font);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shape_cached(&mut self, text: &str, bold: bool, italic: bool) -> &Vec<TextBlob> {
|
if !current_group.is_empty() {
|
||||||
let key = ShapeKey::new(text.to_string(), bold, italic);
|
grouped_results.push((current_group, current_font_option.unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
if !self.blob_cache.contains(&key) {
|
grouped_results
|
||||||
let blobs = self.shape(text, bold, italic);
|
|
||||||
self.blob_cache.put(key.clone(), blobs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.blob_cache.get(&key).unwrap()
|
pub fn shape(&mut self, cells: &[String]) -> Vec<TextBlob> {
|
||||||
|
let current_size = self.current_size();
|
||||||
|
let (glyph_width, _) = self.font_base_dimensions();
|
||||||
|
|
||||||
|
let mut resulting_blobs = Vec::new();
|
||||||
|
|
||||||
|
let text = cells.concat();
|
||||||
|
trace!("Shaping text: {}", text);
|
||||||
|
|
||||||
|
for (cluster_group, font_pair) in self.build_clusters(&text) {
|
||||||
|
let mut shaper = self
|
||||||
|
.shape_context
|
||||||
|
.builder(font_pair.swash_font.as_ref())
|
||||||
|
.size(current_size)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let charmap = font_pair.swash_font.as_ref().charmap();
|
||||||
|
for mut cluster in cluster_group {
|
||||||
|
cluster.map(|ch| charmap.map(ch));
|
||||||
|
shaper.add_cluster(&cluster);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_font(&mut self, guifont_setting: &str) -> bool {
|
let mut glyph_data = Vec::new();
|
||||||
let updated = self.options.update(guifont_setting);
|
|
||||||
if updated {
|
shaper.shape_with(|glyph_cluster| {
|
||||||
trace!("Font changed: {:?}", self.options);
|
for glyph in glyph_cluster.glyphs {
|
||||||
self.font_set = FontSet::new(&self.options.fallback_list, &mut self.font_loader);
|
glyph_data.push((glyph.id, glyph.data as u64 * glyph_width));
|
||||||
self.font_cache.clear();
|
|
||||||
self.blob_cache.clear();
|
|
||||||
}
|
}
|
||||||
updated
|
});
|
||||||
|
|
||||||
|
if glyph_data.is_empty() {
|
||||||
|
return Vec::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn font_base_dimensions(&mut self) -> (f32, f32) {
|
let mut blob_builder = TextBlobBuilder::new();
|
||||||
let metrics = self.metrics();
|
let (glyphs, positions) =
|
||||||
let font_height =
|
blob_builder.alloc_run_pos_h(&font_pair.skia_font, glyph_data.len(), 0.0, None);
|
||||||
(metrics.ascent - metrics.descent) * self.options.size / metrics.units_per_em as f32;
|
for (i, (glyph_id, glyph_x_position)) in glyph_data.iter().enumerate() {
|
||||||
let style = TextStyle {
|
glyphs[i] = *glyph_id;
|
||||||
size: self.options.size,
|
positions[i] = *glyph_x_position as f32;
|
||||||
};
|
}
|
||||||
let session =
|
|
||||||
LayoutSession::create(STANDARD_CHARACTER_STRING, &style, &self.font_set.normal);
|
|
||||||
let layout_run = session.iter_all().next().unwrap();
|
|
||||||
let glyph_offsets: Vec<f32> = layout_run.glyphs().map(|glyph| glyph.offset.x()).collect();
|
|
||||||
let glyph_advances: Vec<f32> = glyph_offsets
|
|
||||||
.windows(2)
|
|
||||||
.map(|pair| pair[1] - pair[0])
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut amounts = HashMap::new();
|
let blob = blob_builder.make();
|
||||||
|
resulting_blobs.push(blob.expect("Could not create textblob"));
|
||||||
|
}
|
||||||
|
|
||||||
for advance in glyph_advances.iter() {
|
resulting_blobs
|
||||||
amounts
|
|
||||||
.entry(advance.to_string())
|
|
||||||
.and_modify(|e| *e += 1)
|
|
||||||
.or_insert(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (font_width, _) = amounts.into_iter().max_by_key(|(_, count)| *count).unwrap();
|
pub fn shape_cached(&mut self, cells: &[String], bold: bool, italic: bool) -> &Vec<TextBlob> {
|
||||||
let font_width = font_width.parse::<f32>().unwrap();
|
let key = ShapeKey::new(cells.to_vec(), bold, italic);
|
||||||
|
|
||||||
(font_width, font_height)
|
if !self.blob_cache.contains(&key) {
|
||||||
|
let blobs = self.shape(cells);
|
||||||
|
self.blob_cache.put(key.clone(), blobs);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn underline_position(&mut self) -> f32 {
|
self.blob_cache.get(&key).unwrap()
|
||||||
let metrics = self.metrics();
|
|
||||||
-metrics.underline_position * self.options.size / metrics.units_per_em as f32
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,116 +0,0 @@
|
|||||||
use font_kit::{family_handle::FamilyHandle, font::Font, properties::Properties};
|
|
||||||
use skribo::{FontFamily, FontRef as SkriboFont};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ExtendedFontFamily {
|
|
||||||
pub fonts: Vec<SkriboFont>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ExtendedFontFamily {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExtendedFontFamily {
|
|
||||||
pub fn new() -> ExtendedFontFamily {
|
|
||||||
ExtendedFontFamily { fonts: Vec::new() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_font(&mut self, font: SkriboFont) {
|
|
||||||
self.fonts.push(font);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, props: Properties) -> Option<&Font> {
|
|
||||||
if let Some(first_handle) = &self.fonts.first() {
|
|
||||||
for handle in &self.fonts {
|
|
||||||
let font = &handle.font;
|
|
||||||
let properties = font.properties();
|
|
||||||
|
|
||||||
if properties.weight == props.weight
|
|
||||||
&& properties.style == props.style
|
|
||||||
&& properties.stretch == props.stretch
|
|
||||||
{
|
|
||||||
return Some(&font);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some(&first_handle.font);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<FamilyHandle> for ExtendedFontFamily {
|
|
||||||
fn from(handle: FamilyHandle) -> Self {
|
|
||||||
handle
|
|
||||||
.fonts()
|
|
||||||
.iter()
|
|
||||||
.fold(ExtendedFontFamily::new(), |mut family, font| {
|
|
||||||
if let Ok(font) = font.load() {
|
|
||||||
family.add_font(SkriboFont::new(font));
|
|
||||||
}
|
|
||||||
family
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ExtendedFontFamily> for FontFamily {
|
|
||||||
fn from(extended_font_family: ExtendedFontFamily) -> Self {
|
|
||||||
extended_font_family
|
|
||||||
.fonts
|
|
||||||
.iter()
|
|
||||||
.fold(FontFamily::new(), |mut new_family, font| {
|
|
||||||
new_family.add_font(font.clone());
|
|
||||||
new_family
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use font_kit::properties::{Properties, Stretch, Style, Weight};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::renderer::fonts::caching_shaper::Asset;
|
|
||||||
|
|
||||||
const PROPERTIES: Properties = Properties {
|
|
||||||
weight: Weight::NORMAL,
|
|
||||||
style: Style::Normal,
|
|
||||||
stretch: Stretch::NORMAL,
|
|
||||||
};
|
|
||||||
const EXTRA_SYMBOL_FONT: &str = "Extra Symbols.otf";
|
|
||||||
|
|
||||||
fn dummy_font() -> SkriboFont {
|
|
||||||
SkriboFont::new(
|
|
||||||
Asset::get(EXTRA_SYMBOL_FONT)
|
|
||||||
.and_then(|font_data| Font::from_bytes(font_data.to_vec().into(), 0).ok())
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_add_font() {
|
|
||||||
let mut eft = ExtendedFontFamily::new();
|
|
||||||
let font = dummy_font();
|
|
||||||
eft.add_font(font.clone());
|
|
||||||
assert_eq!(
|
|
||||||
eft.fonts.first().unwrap().font.full_name(),
|
|
||||||
font.font.full_name()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get() {
|
|
||||||
let mut eft = ExtendedFontFamily::new();
|
|
||||||
assert!(eft.get(PROPERTIES).is_none());
|
|
||||||
|
|
||||||
let font = dummy_font();
|
|
||||||
eft.fonts.push(font.clone());
|
|
||||||
assert_eq!(
|
|
||||||
eft.get(font.font.properties()).unwrap().full_name(),
|
|
||||||
font.font.full_name()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,278 +1,128 @@
|
|||||||
use std::iter;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use cfg_if::cfg_if;
|
|
||||||
use font_kit::{properties::Properties, source::SystemSource};
|
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
use rand::Rng;
|
use skia_safe::{font::Edging, Data, Font, FontHinting, FontMgr, FontStyle, Typeface};
|
||||||
use skribo::{FontCollection, FontFamily};
|
|
||||||
|
|
||||||
#[cfg(any(feature = "embed-fonts", test))]
|
use super::swash_font::SwashFont;
|
||||||
use super::caching_shaper::Asset;
|
|
||||||
use super::extended_font_family::*;
|
|
||||||
|
|
||||||
cfg_if! {
|
#[derive(RustEmbed)]
|
||||||
if #[cfg(target_os = "windows")] {
|
#[folder = "assets/fonts/"]
|
||||||
pub const SYSTEM_DEFAULT_FONT: &str = "Consolas";
|
pub struct Asset;
|
||||||
pub const SYSTEM_SYMBOL_FONT: &str = "Segoe UI Symbol";
|
|
||||||
pub const SYSTEM_EMOJI_FONT: &str = "Segoe UI Emoji";
|
|
||||||
} else if #[cfg(target_os = "linux")] {
|
|
||||||
pub const SYSTEM_DEFAULT_FONT: &str = "Noto Sans Mono";
|
|
||||||
pub const SYSTEM_SYMBOL_FONT: &str = "Noto Sans Mono";
|
|
||||||
pub const SYSTEM_EMOJI_FONT: &str = "Noto Color Emoji";
|
|
||||||
} else if #[cfg(target_os = "macos")] {
|
|
||||||
pub const SYSTEM_DEFAULT_FONT: &str = "Menlo";
|
|
||||||
pub const SYSTEM_SYMBOL_FONT: &str = "Apple Symbols";
|
|
||||||
pub const SYSTEM_EMOJI_FONT: &str = "Apple Color Emoji";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const EXTRA_SYMBOL_FONT: &str = "Extra Symbols.otf";
|
const DEFAULT_FONT: &str = "FiraCode-Regular.ttf";
|
||||||
pub const MISSING_GLYPH_FONT: &str = "Missing Glyphs.otf";
|
|
||||||
|
|
||||||
pub struct FontLoader {
|
pub struct FontPair {
|
||||||
cache: LruCache<String, ExtendedFontFamily>,
|
pub skia_font: Font,
|
||||||
source: SystemSource,
|
pub swash_font: SwashFont,
|
||||||
random_font_name: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FontLoader {
|
impl FontPair {
|
||||||
pub fn new() -> FontLoader {
|
fn new(mut skia_font: Font) -> Option<FontPair> {
|
||||||
FontLoader {
|
skia_font.set_subpixel(true);
|
||||||
cache: LruCache::new(10),
|
skia_font.set_hinting(FontHinting::Full);
|
||||||
source: SystemSource::new(),
|
skia_font.set_edging(Edging::SubpixelAntiAlias);
|
||||||
random_font_name: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get(&mut self, font_name: &str) -> Option<ExtendedFontFamily> {
|
|
||||||
self.cache.get(&String::from(font_name)).cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(feature = "embed-fonts", test))]
|
let (font_data, index) = skia_font.typeface().unwrap().to_font_data().unwrap();
|
||||||
fn load_from_asset(&mut self, font_name: &str) -> Option<ExtendedFontFamily> {
|
let swash_font = SwashFont::from_data(font_data, index)?;
|
||||||
use font_kit::font::Font;
|
|
||||||
use skribo::FontRef as SkriboFont;
|
|
||||||
let mut family = ExtendedFontFamily::new();
|
|
||||||
|
|
||||||
if let Some(font) = Asset::get(font_name)
|
Some(Self {
|
||||||
.and_then(|font_data| Font::from_bytes(font_data.to_vec().into(), 0).ok())
|
skia_font,
|
||||||
{
|
swash_font,
|
||||||
family.add_font(SkriboFont::new(font));
|
})
|
||||||
self.cache.put(String::from(font_name), family);
|
|
||||||
self.get(font_name)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(any(feature = "embed-fonts", test)))]
|
impl PartialEq for FontPair {
|
||||||
fn load_from_asset(&self, font_name: &str) -> Option<ExtendedFontFamily> {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
log::warn!(
|
self.swash_font.key == other.swash_font.key
|
||||||
"Tried to load {} from assets but build didn't include embed-fonts feature",
|
|
||||||
font_name
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn load(&mut self, font_name: &str) -> Option<ExtendedFontFamily> {
|
pub struct FontLoader {
|
||||||
let handle = match self.source.select_family_by_name(font_name) {
|
font_mgr: FontMgr,
|
||||||
Ok(it) => it,
|
cache: LruCache<FontKey, Arc<FontPair>>,
|
||||||
_ => return None,
|
font_size: f32,
|
||||||
};
|
}
|
||||||
|
|
||||||
if !handle.is_empty() {
|
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
|
||||||
let family = ExtendedFontFamily::from(handle);
|
pub enum FontKey {
|
||||||
self.cache.put(String::from(font_name), family);
|
Default,
|
||||||
self.get(font_name)
|
Name(String),
|
||||||
} else {
|
Character(char),
|
||||||
None
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_random_system_font_family(&mut self) -> Option<ExtendedFontFamily> {
|
impl From<&str> for FontKey {
|
||||||
if let Some(font) = self.random_font_name.clone() {
|
fn from(string: &str) -> FontKey {
|
||||||
self.get(&font)
|
let string = string.to_string();
|
||||||
} else {
|
FontKey::Name(string)
|
||||||
let font_names = self.source.all_families().expect("fonts exist");
|
|
||||||
let n = rand::thread_rng().gen::<usize>() % font_names.len();
|
|
||||||
let font_name = &font_names[n];
|
|
||||||
self.random_font_name = Some(font_name.clone());
|
|
||||||
self.load(&font_name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_or_load(&mut self, font_name: &str) -> Option<ExtendedFontFamily> {
|
impl From<&String> for FontKey {
|
||||||
if let Some(cached) = self.get(font_name) {
|
fn from(string: &String) -> FontKey {
|
||||||
Some(cached)
|
let string = string.to_owned();
|
||||||
} else if let Some(loaded) = self.load(font_name) {
|
FontKey::Name(string)
|
||||||
Some(loaded)
|
|
||||||
} else {
|
|
||||||
self.load_from_asset(font_name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build_collection_by_font_name(
|
impl From<String> for FontKey {
|
||||||
&mut self,
|
fn from(string: String) -> FontKey {
|
||||||
fallback_list: &[String],
|
FontKey::Name(string)
|
||||||
properties: Properties,
|
|
||||||
) -> FontCollection {
|
|
||||||
let mut collection = FontCollection::new();
|
|
||||||
|
|
||||||
let gui_fonts = fallback_list
|
|
||||||
.iter()
|
|
||||||
.map(|fallback_item| fallback_item.as_ref())
|
|
||||||
.chain(iter::once(SYSTEM_DEFAULT_FONT));
|
|
||||||
|
|
||||||
for font_name in gui_fonts {
|
|
||||||
if let Some(family) = self.get_or_load(font_name) {
|
|
||||||
if let Some(font) = family.get(properties) {
|
|
||||||
collection.add_family(FontFamily::new_from_font(font.clone()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<char> for FontKey {
|
||||||
|
fn from(character: char) -> FontKey {
|
||||||
|
FontKey::Character(character)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for font in &[
|
impl FontLoader {
|
||||||
SYSTEM_SYMBOL_FONT,
|
pub fn new(font_size: f32) -> FontLoader {
|
||||||
SYSTEM_EMOJI_FONT,
|
FontLoader {
|
||||||
EXTRA_SYMBOL_FONT,
|
font_mgr: FontMgr::new(),
|
||||||
MISSING_GLYPH_FONT,
|
cache: LruCache::new(10),
|
||||||
] {
|
font_size,
|
||||||
if let Some(family) = self.get_or_load(font) {
|
|
||||||
collection.add_family(FontFamily::from(family));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.cache.is_empty() {
|
fn load(&mut self, font_key: FontKey) -> Option<FontPair> {
|
||||||
let font_family = self.get_random_system_font_family();
|
match font_key {
|
||||||
collection.add_family(FontFamily::from(font_family.expect("font family loaded")));
|
FontKey::Default => {
|
||||||
|
let default_font_data = Asset::get(DEFAULT_FONT).unwrap();
|
||||||
|
let data = Data::new_copy(&default_font_data);
|
||||||
|
let typeface = Typeface::from_data(data, 0).unwrap();
|
||||||
|
FontPair::new(Font::from_typeface(typeface, self.font_size))
|
||||||
}
|
}
|
||||||
collection
|
FontKey::Name(name) => {
|
||||||
|
let font_style = FontStyle::normal();
|
||||||
|
let typeface = self.font_mgr.match_family_style(name, font_style)?;
|
||||||
|
FontPair::new(Font::from_typeface(typeface, self.font_size))
|
||||||
}
|
}
|
||||||
}
|
FontKey::Character(character) => {
|
||||||
|
let font_style = FontStyle::normal();
|
||||||
#[cfg(test)]
|
let typeface = self.font_mgr.match_family_style_character(
|
||||||
mod test {
|
"",
|
||||||
use font_kit::{
|
font_style,
|
||||||
font::Font,
|
&[],
|
||||||
properties::{Properties, Stretch, Style, Weight},
|
character as i32,
|
||||||
};
|
)?;
|
||||||
use skribo::FontRef as SkriboFont;
|
FontPair::new(Font::from_typeface(typeface, self.font_size))
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::renderer::fonts::utils::*;
|
|
||||||
|
|
||||||
const PROPERTIES1: Properties = Properties {
|
|
||||||
weight: Weight::NORMAL,
|
|
||||||
style: Style::Normal,
|
|
||||||
stretch: Stretch::NORMAL,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PROPERTIES2: Properties = Properties {
|
|
||||||
weight: Weight::BOLD,
|
|
||||||
style: Style::Normal,
|
|
||||||
stretch: Stretch::NORMAL,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PROPERTIES3: Properties = Properties {
|
|
||||||
weight: Weight::NORMAL,
|
|
||||||
style: Style::Italic,
|
|
||||||
stretch: Stretch::NORMAL,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PROPERTIES4: Properties = Properties {
|
|
||||||
weight: Weight::BOLD,
|
|
||||||
style: Style::Italic,
|
|
||||||
stretch: Stretch::NORMAL,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn dummy_font() -> SkriboFont {
|
|
||||||
SkriboFont::new(
|
|
||||||
Asset::get(EXTRA_SYMBOL_FONT)
|
|
||||||
.and_then(|font_data| Font::from_bytes(font_data.to_vec().into(), 0).ok())
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_build_properties() {
|
|
||||||
assert_eq!(build_properties(false, false), PROPERTIES1);
|
|
||||||
assert_eq!(build_properties(true, false), PROPERTIES2);
|
|
||||||
assert_eq!(build_properties(false, true), PROPERTIES3);
|
|
||||||
assert_eq!(build_properties(true, true), PROPERTIES4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_load_from_asset() {
|
|
||||||
let mut loader = FontLoader::new();
|
|
||||||
|
|
||||||
let font_family = loader.load_from_asset("");
|
|
||||||
assert!(font_family.is_none());
|
|
||||||
|
|
||||||
let font = dummy_font();
|
|
||||||
let mut eft = ExtendedFontFamily::new();
|
|
||||||
eft.add_font(font.clone());
|
|
||||||
let font_family = loader.load_from_asset(EXTRA_SYMBOL_FONT);
|
|
||||||
let result = font_family.unwrap().fonts.first().unwrap().font.full_name();
|
|
||||||
assert_eq!(&result, &eft.fonts.first().unwrap().font.full_name());
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
&result,
|
|
||||||
&loader
|
|
||||||
.cache
|
|
||||||
.get(&EXTRA_SYMBOL_FONT.to_string())
|
|
||||||
.unwrap()
|
|
||||||
.fonts
|
|
||||||
.first()
|
|
||||||
.unwrap()
|
|
||||||
.font
|
|
||||||
.full_name()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
pub fn get_or_load(&mut self, font_key: FontKey) -> Option<Arc<FontPair>> {
|
||||||
fn test_load() {
|
if let Some(cached) = self.cache.get(&font_key) {
|
||||||
let mut loader = FontLoader::new();
|
return Some(cached.clone());
|
||||||
let junk_text = "uhasiudhaiudshiaushd";
|
}
|
||||||
let font_family = loader.load(junk_text);
|
|
||||||
assert!(font_family.is_none());
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
let loaded_font = self.load(font_key.clone())?;
|
||||||
const SYSTEM_DEFAULT_FONT: &str = "monospace";
|
|
||||||
|
|
||||||
let font_family = loader.load(SYSTEM_DEFAULT_FONT);
|
let font_arc = Arc::new(loaded_font);
|
||||||
let result = font_family.unwrap().fonts.first().unwrap().font.full_name();
|
|
||||||
assert_eq!(
|
|
||||||
&result,
|
|
||||||
&loader
|
|
||||||
.cache
|
|
||||||
.get(&SYSTEM_DEFAULT_FONT.to_string())
|
|
||||||
.unwrap()
|
|
||||||
.fonts
|
|
||||||
.first()
|
|
||||||
.unwrap()
|
|
||||||
.font
|
|
||||||
.full_name()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
self.cache.put(font_key, font_arc.clone());
|
||||||
fn test_get_random_system_font() {
|
|
||||||
let mut loader = FontLoader::new();
|
|
||||||
|
|
||||||
let font_family = loader.get_random_system_font_family();
|
Some(font_arc)
|
||||||
let font_name = loader.random_font_name.unwrap();
|
|
||||||
let result = font_family.unwrap().fonts.first().unwrap().font.full_name();
|
|
||||||
assert_eq!(
|
|
||||||
&result,
|
|
||||||
&loader
|
|
||||||
.cache
|
|
||||||
.get(&font_name)
|
|
||||||
.unwrap()
|
|
||||||
.fonts
|
|
||||||
.first()
|
|
||||||
.unwrap()
|
|
||||||
.font
|
|
||||||
.full_name()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
pub mod caching_shaper;
|
pub mod caching_shaper;
|
||||||
mod extended_font_family;
|
|
||||||
mod font_loader;
|
mod font_loader;
|
||||||
mod font_options;
|
mod font_options;
|
||||||
mod utils;
|
mod swash_font;
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
use swash::{CacheKey, FontRef};
|
||||||
|
|
||||||
|
pub struct SwashFont {
|
||||||
|
data: Vec<u8>,
|
||||||
|
offset: u32,
|
||||||
|
pub key: CacheKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SwashFont {
|
||||||
|
pub fn from_data(data: Vec<u8>, index: usize) -> Option<Self> {
|
||||||
|
let font = FontRef::from_index(&data, index)?;
|
||||||
|
let (offset, key) = (font.offset, font.key);
|
||||||
|
Some(Self { data, offset, key })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_ref(&self) -> FontRef {
|
||||||
|
FontRef {
|
||||||
|
data: &self.data,
|
||||||
|
offset: self.offset,
|
||||||
|
key: self.key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,24 +0,0 @@
|
|||||||
use font_kit::properties::{Properties, Stretch, Style, Weight};
|
|
||||||
use skribo::FontRef as SkriboFont;
|
|
||||||
use skulpin::skia_safe::{Data, Font as SkiaFont, Typeface};
|
|
||||||
|
|
||||||
pub fn build_skia_font_from_skribo_font(
|
|
||||||
skribo_font: &SkriboFont,
|
|
||||||
base_size: f32,
|
|
||||||
) -> Option<SkiaFont> {
|
|
||||||
let font_data = skribo_font.font.copy_font_data()?;
|
|
||||||
let skia_data = Data::new_copy(&font_data[..]);
|
|
||||||
let typeface = Typeface::from_data(skia_data, None)?;
|
|
||||||
|
|
||||||
Some(SkiaFont::from_typeface(typeface, base_size))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_properties(bold: bool, italic: bool) -> Properties {
|
|
||||||
let weight = if bold { Weight::BOLD } else { Weight::NORMAL };
|
|
||||||
let style = if italic { Style::Italic } else { Style::Normal };
|
|
||||||
Properties {
|
|
||||||
weight,
|
|
||||||
style,
|
|
||||||
stretch: Stretch::NORMAL,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
mod qwerty;
|
|
||||||
|
|
||||||
use crate::{settings::SETTINGS, window::keyboard::Modifiers, WindowSettings};
|
|
||||||
use skulpin::sdl2::keyboard::Mod;
|
|
||||||
|
|
||||||
pub use qwerty::handle_qwerty_layout;
|
|
||||||
|
|
||||||
impl From<Mod> for Modifiers {
|
|
||||||
fn from(mods: Mod) -> Modifiers {
|
|
||||||
let iso_layout = SETTINGS.get::<WindowSettings>().iso_layout;
|
|
||||||
Modifiers {
|
|
||||||
shift: mods.contains(Mod::LSHIFTMOD) || mods.contains(Mod::RSHIFTMOD),
|
|
||||||
control: mods.contains(Mod::LCTRLMOD) || mods.contains(Mod::RCTRLMOD),
|
|
||||||
meta: mods.contains(Mod::LALTMOD) || (!iso_layout && mods.contains(Mod::RALTMOD)),
|
|
||||||
logo: mods.contains(Mod::LGUIMOD) || mods.contains(Mod::RGUIMOD),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,184 +0,0 @@
|
|||||||
use crate::window::keyboard::{unsupported_key, Token};
|
|
||||||
use skulpin::sdl2::keyboard::Keycode::{self, *};
|
|
||||||
|
|
||||||
/// Maps winit keyboard events to Vim tokens
|
|
||||||
pub fn handle_qwerty_layout(keycode: Keycode, shift: bool) -> Option<Token<'static>> {
|
|
||||||
let special = |text| Some(Token::new(text, true, true));
|
|
||||||
let normal = |text| Some(Token::new(text, false, true));
|
|
||||||
let partial = |text| Some(Token::new(text, false, false));
|
|
||||||
match (keycode, shift) {
|
|
||||||
(Backspace, _) => special("BS"),
|
|
||||||
(Tab, _) => special("Tab"),
|
|
||||||
(Return, _) => special("Enter"),
|
|
||||||
(Escape, _) => special("Esc"),
|
|
||||||
(Space, _) => normal(" "),
|
|
||||||
(Exclaim, _) => normal("!"),
|
|
||||||
(Quotedbl, _) => normal("\""),
|
|
||||||
(Hash, _) => normal("#"),
|
|
||||||
(Dollar, _) => normal("$"),
|
|
||||||
(Percent, _) => normal("%"),
|
|
||||||
(Ampersand, _) => normal("&"),
|
|
||||||
(Quote, false) => normal("'"),
|
|
||||||
(Quote, true) => normal("\""),
|
|
||||||
(LeftParen, _) => normal("("),
|
|
||||||
(RightParen, _) => normal(")"),
|
|
||||||
(Asterisk, _) => normal("*"),
|
|
||||||
(Plus, _) => normal("+"),
|
|
||||||
(Comma, false) => normal(","),
|
|
||||||
(Comma, true) => special("lt"),
|
|
||||||
(Minus, false) => partial("-"),
|
|
||||||
(Minus, true) => partial("_"),
|
|
||||||
(Period, false) => partial("."),
|
|
||||||
(Period, true) => partial(">"),
|
|
||||||
(Slash, false) => partial("/"),
|
|
||||||
(Slash, true) => partial("?"),
|
|
||||||
(Num0, false) => partial("0"),
|
|
||||||
(Num0, true) => special(")"),
|
|
||||||
(Num1, false) => partial("1"),
|
|
||||||
(Num1, true) => special("!"),
|
|
||||||
(Num2, false) => partial("2"),
|
|
||||||
(Num2, true) => partial("@"),
|
|
||||||
(Num3, false) => partial("3"),
|
|
||||||
(Num3, true) => partial("#"),
|
|
||||||
(Num4, false) => partial("4"),
|
|
||||||
(Num4, true) => partial("$"),
|
|
||||||
(Num5, false) => partial("5"),
|
|
||||||
(Num5, true) => partial("%"),
|
|
||||||
(Num6, false) => partial("6"),
|
|
||||||
(Num6, true) => partial("^"),
|
|
||||||
(Num7, false) => partial("7"),
|
|
||||||
(Num7, true) => partial("&"),
|
|
||||||
(Num8, false) => partial("8"),
|
|
||||||
(Num8, true) => partial("*"),
|
|
||||||
(Num9, false) => partial("9"),
|
|
||||||
(Num9, true) => partial("("),
|
|
||||||
(Colon, _) => normal(":"),
|
|
||||||
(Semicolon, false) => partial(";"),
|
|
||||||
(Semicolon, true) => partial(":"),
|
|
||||||
(Less, _) => special("lt"),
|
|
||||||
(Equals, false) => partial("="),
|
|
||||||
(Equals, true) => partial("+"),
|
|
||||||
(Greater, _) => normal("gt"),
|
|
||||||
(Question, _) => normal("?"),
|
|
||||||
(At, _) => normal("@"),
|
|
||||||
(LeftBracket, false) => partial("["),
|
|
||||||
(LeftBracket, true) => partial("{"),
|
|
||||||
(Backslash, false) => partial("\\"),
|
|
||||||
(Backslash, true) => partial("|"),
|
|
||||||
(RightBracket, false) => partial("]"),
|
|
||||||
(RightBracket, true) => partial("}"),
|
|
||||||
(Caret, _) => normal("^"),
|
|
||||||
(Underscore, _) => normal("_"),
|
|
||||||
(Backquote, false) => partial("`"),
|
|
||||||
(Backquote, true) => partial("~"),
|
|
||||||
(A, _) => normal("a"),
|
|
||||||
(B, _) => normal("b"),
|
|
||||||
(C, _) => normal("c"),
|
|
||||||
(D, _) => normal("d"),
|
|
||||||
(E, _) => normal("e"),
|
|
||||||
(F, _) => normal("f"),
|
|
||||||
(G, _) => normal("g"),
|
|
||||||
(H, _) => normal("h"),
|
|
||||||
(I, _) => normal("i"),
|
|
||||||
(J, _) => normal("j"),
|
|
||||||
(K, _) => normal("k"),
|
|
||||||
(L, _) => normal("l"),
|
|
||||||
(M, _) => normal("m"),
|
|
||||||
(N, _) => normal("n"),
|
|
||||||
(O, _) => normal("o"),
|
|
||||||
(P, _) => normal("p"),
|
|
||||||
(Q, _) => normal("q"),
|
|
||||||
(R, _) => normal("r"),
|
|
||||||
(S, _) => normal("s"),
|
|
||||||
(T, _) => normal("t"),
|
|
||||||
(U, _) => normal("u"),
|
|
||||||
(V, _) => normal("v"),
|
|
||||||
(W, _) => normal("w"),
|
|
||||||
(X, _) => normal("x"),
|
|
||||||
(Y, _) => normal("y"),
|
|
||||||
(Z, _) => normal("z"),
|
|
||||||
(Delete, _) => special("Delete"),
|
|
||||||
(F1, _) => special("F1"),
|
|
||||||
(F2, _) => special("F2"),
|
|
||||||
(F3, _) => special("F3"),
|
|
||||||
(F4, _) => special("F4"),
|
|
||||||
(F5, _) => special("F5"),
|
|
||||||
(F6, _) => special("F6"),
|
|
||||||
(F7, _) => special("F7"),
|
|
||||||
(F8, _) => special("F8"),
|
|
||||||
(F9, _) => special("F9"),
|
|
||||||
(F10, _) => special("F10"),
|
|
||||||
(F11, _) => special("F11"),
|
|
||||||
(F12, _) => special("F12"),
|
|
||||||
(Insert, _) => special("Insert"),
|
|
||||||
(Home, _) => special("Home"),
|
|
||||||
(PageUp, _) => special("PageUp"),
|
|
||||||
(End, _) => special("End"),
|
|
||||||
(PageDown, _) => special("PageDown"),
|
|
||||||
(Right, _) => special("Right"),
|
|
||||||
(Left, _) => special("Left"),
|
|
||||||
(Down, _) => special("Down"),
|
|
||||||
(Up, _) => special("Up"),
|
|
||||||
(KpDivide, _) => special("/"),
|
|
||||||
(KpMultiply, _) => special("*"),
|
|
||||||
(KpMinus, _) => special("-"),
|
|
||||||
(KpPlus, _) => special("+"),
|
|
||||||
(KpEnter, _) => special("Enter"),
|
|
||||||
(Kp0, _) => normal("0"),
|
|
||||||
(Kp1, _) => normal("1"),
|
|
||||||
(Kp2, _) => normal("2"),
|
|
||||||
(Kp3, _) => normal("3"),
|
|
||||||
(Kp4, _) => normal("4"),
|
|
||||||
(Kp5, _) => normal("5"),
|
|
||||||
(Kp6, _) => normal("6"),
|
|
||||||
(Kp7, _) => normal("7"),
|
|
||||||
(Kp8, _) => normal("8"),
|
|
||||||
(Kp9, _) => normal("9"),
|
|
||||||
(KpPeriod, _) => normal("."),
|
|
||||||
(KpEquals, _) => normal("="),
|
|
||||||
(F13, _) => special("F13"),
|
|
||||||
(F14, _) => special("F14"),
|
|
||||||
(F15, _) => special("F15"),
|
|
||||||
(F16, _) => special("F16"),
|
|
||||||
(F17, _) => special("F17"),
|
|
||||||
(F18, _) => special("F18"),
|
|
||||||
(F19, _) => special("F19"),
|
|
||||||
(F20, _) => special("F20"),
|
|
||||||
(F21, _) => special("F21"),
|
|
||||||
(F22, _) => special("F22"),
|
|
||||||
(F23, _) => special("F23"),
|
|
||||||
(F24, _) => special("F24"),
|
|
||||||
(KpLeftParen, _) => normal("("),
|
|
||||||
(KpRightParen, _) => normal("("),
|
|
||||||
(KpLeftBrace, _) => normal("["),
|
|
||||||
(KpRightBrace, _) => normal("]"),
|
|
||||||
(KpTab, _) => special("TAB"),
|
|
||||||
(KpBackspace, _) => special("BS"),
|
|
||||||
(KpA, _) => normal("A"),
|
|
||||||
(KpB, _) => normal("B"),
|
|
||||||
(KpC, _) => normal("C"),
|
|
||||||
(KpD, _) => normal("D"),
|
|
||||||
(KpE, _) => normal("E"),
|
|
||||||
(KpF, _) => normal("F"),
|
|
||||||
(KpPower, _) => normal("^"),
|
|
||||||
(KpPercent, _) => normal("%"),
|
|
||||||
(KpLess, _) => special("lt"),
|
|
||||||
(KpGreater, _) => special("gt"),
|
|
||||||
(KpAmpersand, _) => normal("&"),
|
|
||||||
(KpVerticalBar, _) => normal("|"),
|
|
||||||
(KpColon, _) => normal(":"),
|
|
||||||
(KpHash, _) => normal("#"),
|
|
||||||
(KpSpace, _) => normal(" "),
|
|
||||||
(KpAt, _) => normal("@"),
|
|
||||||
(KpExclam, _) => normal("!"),
|
|
||||||
(LCtrl, _) => None,
|
|
||||||
(LShift, _) => None,
|
|
||||||
(LAlt, _) => None,
|
|
||||||
(LGui, _) => None,
|
|
||||||
(RCtrl, _) => None,
|
|
||||||
(RShift, _) => None,
|
|
||||||
(RAlt, _) => None,
|
|
||||||
(RGui, _) => None,
|
|
||||||
(keycode, _) => unsupported_key(keycode),
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,525 +0,0 @@
|
|||||||
#[macro_use]
|
|
||||||
mod layouts;
|
|
||||||
|
|
||||||
use super::{handle_new_grid_size, keyboard::neovim_keybinding_string, WindowSettings};
|
|
||||||
use crate::{
|
|
||||||
bridge::UiCommand, editor::WindowCommand, error_handling::ResultPanicExplanation,
|
|
||||||
redraw_scheduler::REDRAW_SCHEDULER, renderer::Renderer, settings::SETTINGS,
|
|
||||||
};
|
|
||||||
use crossfire::mpsc::TxUnbounded;
|
|
||||||
use image::load_from_memory_with_format;
|
|
||||||
use layouts::handle_qwerty_layout;
|
|
||||||
use skulpin::{
|
|
||||||
ash::prelude::VkResult,
|
|
||||||
sdl2::{
|
|
||||||
self,
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
keyboard::Keycode,
|
|
||||||
video::FullscreenType,
|
|
||||||
EventPump, Sdl,
|
|
||||||
},
|
|
||||||
CoordinateSystem, LogicalSize, PhysicalSize, PresentMode, Renderer as SkulpinRenderer,
|
|
||||||
RendererBuilder, Sdl2Window, Window,
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
sync::{
|
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
mpsc::Receiver,
|
|
||||||
Arc,
|
|
||||||
},
|
|
||||||
thread::sleep,
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(RustEmbed)]
|
|
||||||
#[folder = "assets/"]
|
|
||||||
struct Asset;
|
|
||||||
|
|
||||||
pub struct Sdl2WindowWrapper {
|
|
||||||
context: Sdl,
|
|
||||||
window: sdl2::video::Window,
|
|
||||||
skulpin_renderer: SkulpinRenderer,
|
|
||||||
event_pump: EventPump,
|
|
||||||
renderer: Renderer,
|
|
||||||
mouse_down: bool,
|
|
||||||
mouse_position: LogicalSize,
|
|
||||||
mouse_enabled: bool,
|
|
||||||
grid_id_under_mouse: u64,
|
|
||||||
title: String,
|
|
||||||
previous_size: LogicalSize,
|
|
||||||
fullscreen: bool,
|
|
||||||
cached_size: (u32, u32),
|
|
||||||
cached_position: (i32, i32),
|
|
||||||
ui_command_sender: TxUnbounded<UiCommand>,
|
|
||||||
window_command_receiver: Receiver<WindowCommand>,
|
|
||||||
running: Arc<AtomicBool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sdl2WindowWrapper {
|
|
||||||
pub fn toggle_fullscreen(&mut self) {
|
|
||||||
if self.fullscreen {
|
|
||||||
if cfg!(target_os = "windows") {
|
|
||||||
unsafe {
|
|
||||||
let raw_handle = self.window.raw();
|
|
||||||
sdl2::sys::SDL_SetWindowResizable(raw_handle, sdl2::sys::SDL_bool::SDL_TRUE);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.window.set_fullscreen(FullscreenType::Off).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use cached size and position
|
|
||||||
self.window
|
|
||||||
.set_size(self.cached_size.0, self.cached_size.1)
|
|
||||||
.unwrap();
|
|
||||||
self.window.set_position(
|
|
||||||
sdl2::video::WindowPos::Positioned(self.cached_position.0),
|
|
||||||
sdl2::video::WindowPos::Positioned(self.cached_position.1),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
self.cached_size = self.window.size();
|
|
||||||
self.cached_position = self.window.position();
|
|
||||||
|
|
||||||
if cfg!(target_os = "windows") {
|
|
||||||
let video_subsystem = self.window.subsystem();
|
|
||||||
if let Ok(rect) = self
|
|
||||||
.window
|
|
||||||
.display_index()
|
|
||||||
.and_then(|index| video_subsystem.display_bounds(index))
|
|
||||||
{
|
|
||||||
// Set window to fullscreen
|
|
||||||
unsafe {
|
|
||||||
let raw_handle = self.window.raw();
|
|
||||||
sdl2::sys::SDL_SetWindowResizable(
|
|
||||||
raw_handle,
|
|
||||||
sdl2::sys::SDL_bool::SDL_FALSE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self.window.set_size(rect.width(), rect.height()).unwrap();
|
|
||||||
self.window.set_position(
|
|
||||||
sdl2::video::WindowPos::Positioned(rect.x()),
|
|
||||||
sdl2::video::WindowPos::Positioned(rect.y()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.window.set_fullscreen(FullscreenType::Desktop).ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.fullscreen = !self.fullscreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn synchronize_settings(&mut self) {
|
|
||||||
let transparency = { SETTINGS.get::<WindowSettings>().transparency };
|
|
||||||
|
|
||||||
if let Ok(opacity) = self.window.opacity() {
|
|
||||||
if (opacity - transparency).abs() > std::f32::EPSILON {
|
|
||||||
self.window.set_opacity(transparency).ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let fullscreen = { SETTINGS.get::<WindowSettings>().fullscreen };
|
|
||||||
|
|
||||||
if self.fullscreen != fullscreen {
|
|
||||||
self.toggle_fullscreen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_title_changed(&mut self, new_title: String) {
|
|
||||||
self.title = new_title;
|
|
||||||
self.window
|
|
||||||
.set_title(&self.title)
|
|
||||||
.expect("Could not set title");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_quit(&mut self) {
|
|
||||||
self.running.store(false, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_keyboard_input(&mut self, keycode: Option<Keycode>, text: Option<String>) {
|
|
||||||
let modifiers = self.context.keyboard().mod_state();
|
|
||||||
|
|
||||||
if keycode.is_some() || text.is_some() {
|
|
||||||
log::trace!(
|
|
||||||
"Keyboard Input Received: keycode-{:?} modifiers-{:?} text-{:?}",
|
|
||||||
keycode,
|
|
||||||
modifiers,
|
|
||||||
text
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(keybinding_string) =
|
|
||||||
neovim_keybinding_string(keycode, text, modifiers, handle_qwerty_layout)
|
|
||||||
{
|
|
||||||
self.ui_command_sender
|
|
||||||
.send(UiCommand::Keyboard(keybinding_string))
|
|
||||||
.unwrap_or_explained_panic(
|
|
||||||
"Could not send UI command from the window system to the neovim process.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_pointer_motion(&mut self, x: i32, y: i32) {
|
|
||||||
let previous_position = self.mouse_position;
|
|
||||||
let sdl_window_wrapper = Sdl2Window::new(&self.window);
|
|
||||||
let logical_position =
|
|
||||||
PhysicalSize::new(x as u32, y as u32).to_logical(sdl_window_wrapper.scale_factor());
|
|
||||||
|
|
||||||
let mut top_window_position = (0.0, 0.0);
|
|
||||||
let mut top_grid_position = None;
|
|
||||||
|
|
||||||
for details in self.renderer.window_regions.iter() {
|
|
||||||
if logical_position.width >= details.region.left as u32
|
|
||||||
&& logical_position.width < details.region.right as u32
|
|
||||||
&& logical_position.height >= details.region.top as u32
|
|
||||||
&& logical_position.height < details.region.bottom as u32
|
|
||||||
{
|
|
||||||
top_window_position = (details.region.left, details.region.top);
|
|
||||||
top_grid_position = Some((
|
|
||||||
details.id,
|
|
||||||
LogicalSize::new(
|
|
||||||
logical_position.width - details.region.left as u32,
|
|
||||||
logical_position.height - details.region.top as u32,
|
|
||||||
),
|
|
||||||
details.floating,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((grid_id, grid_position, grid_floating)) = top_grid_position {
|
|
||||||
self.grid_id_under_mouse = grid_id;
|
|
||||||
self.mouse_position = LogicalSize::new(
|
|
||||||
(grid_position.width as f32 / self.renderer.font_width) as u32,
|
|
||||||
(grid_position.height as f32 / self.renderer.font_height) as u32,
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.mouse_enabled && self.mouse_down && previous_position != self.mouse_position {
|
|
||||||
let (window_left, window_top) = top_window_position;
|
|
||||||
|
|
||||||
// Until https://github.com/neovim/neovim/pull/12667 is merged, we have to special
|
|
||||||
// case non floating windows. Floating windows correctly transform mouse positions
|
|
||||||
// into grid coordinates, but non floating windows do not.
|
|
||||||
let position = if grid_floating {
|
|
||||||
(self.mouse_position.width, self.mouse_position.height)
|
|
||||||
} else {
|
|
||||||
let adjusted_drag_left =
|
|
||||||
self.mouse_position.width + (window_left / self.renderer.font_width) as u32;
|
|
||||||
let adjusted_drag_top = self.mouse_position.height
|
|
||||||
+ (window_top / self.renderer.font_height) as u32;
|
|
||||||
(adjusted_drag_left, adjusted_drag_top)
|
|
||||||
};
|
|
||||||
|
|
||||||
self.ui_command_sender
|
|
||||||
.send(UiCommand::Drag {
|
|
||||||
grid_id: self.grid_id_under_mouse,
|
|
||||||
position,
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_pointer_down(&mut self) {
|
|
||||||
if self.mouse_enabled {
|
|
||||||
self.ui_command_sender
|
|
||||||
.send(UiCommand::MouseButton {
|
|
||||||
action: String::from("press"),
|
|
||||||
grid_id: self.grid_id_under_mouse,
|
|
||||||
position: (self.mouse_position.width, self.mouse_position.height),
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
self.mouse_down = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_pointer_up(&mut self) {
|
|
||||||
if self.mouse_enabled {
|
|
||||||
self.ui_command_sender
|
|
||||||
.send(UiCommand::MouseButton {
|
|
||||||
action: String::from("release"),
|
|
||||||
grid_id: self.grid_id_under_mouse,
|
|
||||||
position: (self.mouse_position.width, self.mouse_position.height),
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
self.mouse_down = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_mouse_wheel(&mut self, x: i32, y: i32) {
|
|
||||||
if !self.mouse_enabled {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let vertical_input_type = match y {
|
|
||||||
_ if y > 0 => Some("up"),
|
|
||||||
_ if y < 0 => Some("down"),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(input_type) = vertical_input_type {
|
|
||||||
self.ui_command_sender
|
|
||||||
.send(UiCommand::Scroll {
|
|
||||||
direction: input_type.to_string(),
|
|
||||||
grid_id: self.grid_id_under_mouse,
|
|
||||||
position: (self.mouse_position.width, self.mouse_position.height),
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
let horizontal_input_type = match y {
|
|
||||||
_ if x > 0 => Some("right"),
|
|
||||||
_ if x < 0 => Some("left"),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(input_type) = horizontal_input_type {
|
|
||||||
self.ui_command_sender
|
|
||||||
.send(UiCommand::Scroll {
|
|
||||||
direction: input_type.to_string(),
|
|
||||||
grid_id: self.grid_id_under_mouse,
|
|
||||||
position: (self.mouse_position.width, self.mouse_position.height),
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_focus_lost(&mut self) {
|
|
||||||
self.ui_command_sender.send(UiCommand::FocusLost).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_focus_gained(&mut self) {
|
|
||||||
self.ui_command_sender.send(UiCommand::FocusGained).ok();
|
|
||||||
REDRAW_SCHEDULER.queue_next_frame();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_events(&mut self) {
|
|
||||||
self.synchronize_settings();
|
|
||||||
|
|
||||||
let mut keycode = None;
|
|
||||||
let mut keytext = None;
|
|
||||||
let mut ignore_text_this_frame = false;
|
|
||||||
|
|
||||||
let window_events: Vec<Event> = self.event_pump.poll_iter().collect();
|
|
||||||
for event in window_events.into_iter() {
|
|
||||||
match event {
|
|
||||||
Event::Quit { .. } => self.handle_quit(),
|
|
||||||
Event::DropFile { filename, .. } => {
|
|
||||||
self.ui_command_sender
|
|
||||||
.send(UiCommand::FileDrop(filename))
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
Event::KeyDown {
|
|
||||||
keycode: received_keycode,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
keycode = received_keycode;
|
|
||||||
}
|
|
||||||
Event::TextInput { text, .. } => keytext = Some(text),
|
|
||||||
Event::MouseMotion { x, y, .. } => self.handle_pointer_motion(x, y),
|
|
||||||
Event::MouseButtonDown { .. } => self.handle_pointer_down(),
|
|
||||||
Event::MouseButtonUp { .. } => self.handle_pointer_up(),
|
|
||||||
Event::MouseWheel { x, y, .. } => self.handle_mouse_wheel(x, y),
|
|
||||||
Event::Window {
|
|
||||||
win_event: WindowEvent::FocusLost,
|
|
||||||
..
|
|
||||||
} => self.handle_focus_lost(),
|
|
||||||
Event::Window {
|
|
||||||
win_event: WindowEvent::FocusGained,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
ignore_text_this_frame = true; // Ignore any text events on the first frame when focus is regained. https://github.com/Kethku/neovide/issues/193
|
|
||||||
self.handle_focus_gained();
|
|
||||||
}
|
|
||||||
Event::Window { .. } => REDRAW_SCHEDULER.queue_next_frame(),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ignore_text_this_frame {
|
|
||||||
self.handle_keyboard_input(keycode, keytext);
|
|
||||||
}
|
|
||||||
|
|
||||||
let window_commands: Vec<WindowCommand> = self.window_command_receiver.try_iter().collect();
|
|
||||||
for window_command in window_commands.into_iter() {
|
|
||||||
match window_command {
|
|
||||||
WindowCommand::TitleChanged(new_title) => self.handle_title_changed(new_title),
|
|
||||||
WindowCommand::SetMouseEnabled(mouse_enabled) => self.mouse_enabled = mouse_enabled,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_frame(&mut self, dt: f32) -> VkResult<bool> {
|
|
||||||
let sdl_window_wrapper = Sdl2Window::new(&self.window);
|
|
||||||
let new_size = sdl_window_wrapper.logical_size();
|
|
||||||
if self.previous_size != new_size {
|
|
||||||
handle_new_grid_size(new_size, &self.renderer, &self.ui_command_sender);
|
|
||||||
self.previous_size = new_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
let current_size = self.previous_size;
|
|
||||||
let ui_command_sender = self.ui_command_sender.clone();
|
|
||||||
|
|
||||||
if REDRAW_SCHEDULER.should_draw() || SETTINGS.get::<WindowSettings>().no_idle {
|
|
||||||
log::debug!("Render Triggered");
|
|
||||||
|
|
||||||
let scaling = sdl_window_wrapper.scale_factor();
|
|
||||||
let renderer = &mut self.renderer;
|
|
||||||
self.skulpin_renderer.draw(
|
|
||||||
&sdl_window_wrapper,
|
|
||||||
|canvas, coordinate_system_helper| {
|
|
||||||
if renderer.draw_frame(canvas, &coordinate_system_helper, dt, scaling as f32) {
|
|
||||||
handle_new_grid_size(current_size, &renderer, &ui_command_sender);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(true)
|
|
||||||
} else {
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn allow_compositing() {
|
|
||||||
// This fixes any vestiges of https://github.com/Kethku/neovide/issues/370
|
|
||||||
// which is an issue where KDE and perhaps others misbehave when compositing is forced off for
|
|
||||||
// a window, as is done by default with SDL2. Since this just sets a hint that will be ignored
|
|
||||||
// if unsupported, it is OK to leave in for all platforms.
|
|
||||||
//
|
|
||||||
// This is `sdl2_sys::SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR`
|
|
||||||
let name = "SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR";
|
|
||||||
sdl2::hint::set(name, "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_icon(win: &mut sdl2::video::Window) {
|
|
||||||
let icon_data = Asset::get("nvim.ico").expect("Failed to read icon data");
|
|
||||||
let icon = load_from_memory_with_format(&icon_data, image::ImageFormat::ICO)
|
|
||||||
.expect("Failed to parse icon data");
|
|
||||||
|
|
||||||
let icon = icon.into_rgba();
|
|
||||||
let width = icon.width();
|
|
||||||
let height = icon.height();
|
|
||||||
let mut icon = icon.into_raw();
|
|
||||||
|
|
||||||
let surf = sdl2::surface::Surface::from_data(
|
|
||||||
&mut icon,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
4 * width,
|
|
||||||
sdl2::pixels::PixelFormatEnum::RGBA32,
|
|
||||||
)
|
|
||||||
.expect("Failed to create icon surface");
|
|
||||||
|
|
||||||
win.set_icon(surf);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_loop(
|
|
||||||
window_command_receiver: Receiver<WindowCommand>,
|
|
||||||
ui_command_sender: TxUnbounded<UiCommand>,
|
|
||||||
running: Arc<AtomicBool>,
|
|
||||||
logical_size: LogicalSize,
|
|
||||||
renderer: Renderer,
|
|
||||||
) {
|
|
||||||
sdl2::hint::set("SDL_MOUSE_FOCUS_CLICKTHROUGH", "1");
|
|
||||||
|
|
||||||
let context = sdl2::init().expect("Failed to initialize sdl2");
|
|
||||||
|
|
||||||
allow_compositing();
|
|
||||||
|
|
||||||
let video_subsystem = context
|
|
||||||
.video()
|
|
||||||
.expect("Failed to create sdl video subsystem");
|
|
||||||
video_subsystem.text_input().start();
|
|
||||||
|
|
||||||
let mut sdl_window = video_subsystem
|
|
||||||
.window("Neovide", logical_size.width, logical_size.height)
|
|
||||||
.position_centered()
|
|
||||||
.allow_highdpi()
|
|
||||||
.resizable()
|
|
||||||
.build()
|
|
||||||
.expect("Failed to create window");
|
|
||||||
log::info!("window created");
|
|
||||||
|
|
||||||
set_icon(&mut sdl_window);
|
|
||||||
|
|
||||||
if std::env::args().any(|arg| arg == "--maximized") {
|
|
||||||
sdl_window.maximize();
|
|
||||||
}
|
|
||||||
|
|
||||||
let skulpin_renderer = {
|
|
||||||
let sdl_window_wrapper = Sdl2Window::new(&sdl_window);
|
|
||||||
RendererBuilder::new()
|
|
||||||
.prefer_discrete_gpu()
|
|
||||||
.use_vulkan_debug_layer(false)
|
|
||||||
.present_mode_priority(vec![PresentMode::Immediate])
|
|
||||||
.coordinate_system(CoordinateSystem::Logical)
|
|
||||||
.build(&sdl_window_wrapper)
|
|
||||||
.expect("Failed to create renderer")
|
|
||||||
};
|
|
||||||
|
|
||||||
let event_pump = context
|
|
||||||
.event_pump()
|
|
||||||
.expect("Could not create sdl event pump");
|
|
||||||
|
|
||||||
let mut window_wrapper = Sdl2WindowWrapper {
|
|
||||||
context,
|
|
||||||
window: sdl_window,
|
|
||||||
skulpin_renderer,
|
|
||||||
renderer,
|
|
||||||
event_pump,
|
|
||||||
mouse_down: false,
|
|
||||||
mouse_position: LogicalSize {
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
},
|
|
||||||
mouse_enabled: true,
|
|
||||||
grid_id_under_mouse: 0,
|
|
||||||
title: String::from("Neovide"),
|
|
||||||
previous_size: logical_size,
|
|
||||||
fullscreen: false,
|
|
||||||
cached_size: (0, 0),
|
|
||||||
cached_position: (0, 0),
|
|
||||||
ui_command_sender,
|
|
||||||
window_command_receiver,
|
|
||||||
running: running.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut was_animating = false;
|
|
||||||
let mut previous_frame_start = Instant::now();
|
|
||||||
loop {
|
|
||||||
if !running.load(Ordering::Relaxed) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let frame_start = Instant::now();
|
|
||||||
|
|
||||||
let refresh_rate = { SETTINGS.get::<WindowSettings>().refresh_rate as f32 };
|
|
||||||
let dt = if was_animating {
|
|
||||||
previous_frame_start.elapsed().as_secs_f32()
|
|
||||||
} else {
|
|
||||||
1.0 / refresh_rate
|
|
||||||
};
|
|
||||||
|
|
||||||
window_wrapper.handle_events();
|
|
||||||
|
|
||||||
match window_wrapper.draw_frame(dt) {
|
|
||||||
Ok(animating) => {
|
|
||||||
was_animating = animating;
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
log::error!("Render failed: {}", error);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let elapsed = frame_start.elapsed();
|
|
||||||
let expected_frame_length = Duration::from_secs_f32(1.0 / refresh_rate);
|
|
||||||
|
|
||||||
previous_frame_start = frame_start;
|
|
||||||
|
|
||||||
if elapsed < expected_frame_length {
|
|
||||||
sleep(expected_frame_length - elapsed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
mod qwerty;
|
mod qwerty;
|
||||||
|
|
||||||
use crate::window::keyboard::Modifiers;
|
use crate::window::keyboard::Modifiers;
|
||||||
use skulpin::winit::event::ModifiersState;
|
use glutin::event::ModifiersState;
|
||||||
|
|
||||||
pub use qwerty::handle_qwerty_layout;
|
pub use qwerty::handle_qwerty_layout;
|
||||||
|
|
@ -0,0 +1,88 @@
|
|||||||
|
use skia_safe::gpu::gl::FramebufferInfo;
|
||||||
|
use skia_safe::gpu::{BackendRenderTarget, DirectContext, SurfaceOrigin};
|
||||||
|
use skia_safe::{Canvas, ColorType, Surface};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
use gl::types::*;
|
||||||
|
type WindowedContext = glutin::ContextWrapper<glutin::PossiblyCurrent, glutin::window::Window>;
|
||||||
|
|
||||||
|
fn create_surface(
|
||||||
|
windowed_context: &WindowedContext,
|
||||||
|
gr_context: &mut DirectContext,
|
||||||
|
fb_info: FramebufferInfo,
|
||||||
|
) -> Surface {
|
||||||
|
let pixel_format = windowed_context.get_pixel_format();
|
||||||
|
let size = windowed_context.window().inner_size();
|
||||||
|
let size = (
|
||||||
|
size.width.try_into().expect("Could not convert width"),
|
||||||
|
size.height.try_into().expect("Could not convert height"),
|
||||||
|
);
|
||||||
|
let backend_render_target = BackendRenderTarget::new_gl(
|
||||||
|
size,
|
||||||
|
pixel_format
|
||||||
|
.multisampling
|
||||||
|
.map(|s| s.try_into().expect("Could not convert multisampling")),
|
||||||
|
pixel_format
|
||||||
|
.stencil_bits
|
||||||
|
.try_into()
|
||||||
|
.expect("Could not convert stencil"),
|
||||||
|
fb_info,
|
||||||
|
);
|
||||||
|
windowed_context.resize(size.into());
|
||||||
|
Surface::from_backend_render_target(
|
||||||
|
gr_context,
|
||||||
|
&backend_render_target,
|
||||||
|
SurfaceOrigin::BottomLeft,
|
||||||
|
ColorType::RGBA8888,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.expect("Could not create skia surface")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SkiaRenderer {
|
||||||
|
pub gr_context: DirectContext,
|
||||||
|
fb_info: FramebufferInfo,
|
||||||
|
surface: Surface,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SkiaRenderer {
|
||||||
|
pub fn new(windowed_context: &WindowedContext) -> SkiaRenderer {
|
||||||
|
gl::load_with(|s| windowed_context.get_proc_address(s));
|
||||||
|
|
||||||
|
let interface = skia_safe::gpu::gl::Interface::new_load_with(|name| {
|
||||||
|
if name == "eglGetCurrentDisplay" {
|
||||||
|
return std::ptr::null();
|
||||||
|
}
|
||||||
|
windowed_context.get_proc_address(name)
|
||||||
|
})
|
||||||
|
.expect("Could not create interface");
|
||||||
|
|
||||||
|
let mut gr_context = skia_safe::gpu::DirectContext::new_gl(Some(interface), None)
|
||||||
|
.expect("Could not create direct context");
|
||||||
|
let fb_info = {
|
||||||
|
let mut fboid: GLint = 0;
|
||||||
|
unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) };
|
||||||
|
|
||||||
|
FramebufferInfo {
|
||||||
|
fboid: fboid.try_into().expect("Could not create frame buffer id"),
|
||||||
|
format: skia_safe::gpu::gl::Format::RGBA8.into(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let surface = create_surface(windowed_context, &mut gr_context, fb_info);
|
||||||
|
|
||||||
|
SkiaRenderer {
|
||||||
|
gr_context,
|
||||||
|
fb_info,
|
||||||
|
surface,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn canvas(&mut self) -> &mut Canvas {
|
||||||
|
self.surface.canvas()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(&mut self, windowed_context: &WindowedContext) {
|
||||||
|
self.surface = create_surface(windowed_context, &mut self.gr_context, self.fb_info);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue