mirror of
https://github.com/enso-org/enso.git
synced 2024-09-11 13:15:52 +03:00
Text rendering quality improvements. (#3855)
This commit is contained in:
parent
45276b243d
commit
cee7f27dc1
@ -89,6 +89,9 @@
|
||||
instances are now reusing the shape shaders and the same sprite system under
|
||||
the hood. This drastically reduces the amount of required draw calls for
|
||||
scenes with a lot of text.
|
||||
- [Text rendering quality improvements][3855]. Glyphs are now hinted in a better
|
||||
way. Also, additional fine-tuning is performed per font and per host operating
|
||||
system.
|
||||
|
||||
#### Enso Standard Library
|
||||
|
||||
@ -369,6 +372,7 @@
|
||||
[3804]: https://github.com/enso-org/enso/pull/3804
|
||||
[3818]: https://github.com/enso-org/enso/pull/3818
|
||||
[3776]: https://github.com/enso-org/enso/pull/3776
|
||||
[3855]: https://github.com/enso-org/enso/pull/3855
|
||||
[3836]: https://github.com/enso-org/enso/pull/3836
|
||||
[3782]: https://github.com/enso-org/enso/pull/3782
|
||||
|
||||
|
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -2018,6 +2018,7 @@ dependencies = [
|
||||
"nalgebra 0.26.2",
|
||||
"percent-encoding 2.1.0",
|
||||
"unicode-segmentation",
|
||||
"unidecode",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
@ -7128,6 +7129,12 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
|
||||
|
||||
[[package]]
|
||||
name = "unidecode"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "402bb19d8e03f1d1a7450e2bd613980869438e0666331be3e073089124aa1adc"
|
||||
|
||||
[[package]]
|
||||
name = "unreachable"
|
||||
version = "1.0.0"
|
||||
|
@ -1321,6 +1321,10 @@ impl TextModel {
|
||||
if truncated {
|
||||
break;
|
||||
}
|
||||
// FIXME[WD]: This is a workaround for a bug in the MSDFgen binding. It
|
||||
// should be fixed after updating the MSDFgen library.
|
||||
// See: https://www.pivotaltracker.com/n/projects/2539304/stories/183747513
|
||||
let magic_scale = 2048.0 / shaped_glyph_set.units_per_em as f32;
|
||||
for shaped_glyph in &shaped_glyph_set.glyphs {
|
||||
let glyph_byte_start = shaped_glyph.start_byte();
|
||||
// Drop styles assigned to skipped bytes. One byte will be skipped
|
||||
@ -1361,11 +1365,11 @@ impl TextModel {
|
||||
glyph.set_color(style.color);
|
||||
glyph.skip_color_animation();
|
||||
glyph.set_sdf_weight(style.sdf_weight.value);
|
||||
glyph.set_size(style.size);
|
||||
glyph.set_size(formatting::Size(style.size.value * magic_scale));
|
||||
glyph.set_properties(shaped_glyph_set.non_variable_variations);
|
||||
glyph.set_glyph_id(shaped_glyph.id());
|
||||
glyph.x_advance.set(x_advance);
|
||||
glyph.view.set_position_xy(glyph_render_offset);
|
||||
glyph.view.set_position_xy(glyph_render_offset * magic_scale);
|
||||
glyph.set_position_xy(Vector2(glyph_offset_x, 0.0));
|
||||
|
||||
glyph_offset_x += x_advance;
|
||||
|
@ -67,7 +67,7 @@ pub use owned_ttf_parser::Width;
|
||||
/// eliminate accidental mistakes, the same way as it's done in CSS:
|
||||
/// https://stackoverflow.com/questions/17967371/are-property-values-in-css-case-sensitive
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Debug, Display, Hash, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, Deref, Display, Hash, PartialEq, Eq)]
|
||||
pub struct Name {
|
||||
pub normalized: String,
|
||||
}
|
||||
|
@ -1,51 +1,47 @@
|
||||
const bool DEBUG = false;
|
||||
|
||||
highp float median(highp vec3 v) {
|
||||
return max(min(v.x, v.y), min(max(v.x, v.y), v.z));
|
||||
}
|
||||
|
||||
/// Compute the uv coordinates of the MSDF texture fragment where it should be sampled.
|
||||
///
|
||||
/// Essentially, it's an input_uv which is a bit transformed to "cut off" the half of the MSDF cell from each side. This
|
||||
/// way we have better pixel alignment on low resolutions.
|
||||
highp vec2 msdf_fragment_uv() {
|
||||
highp vec2 msdf_cell_size = 1.0/input_msdf_size;
|
||||
highp vec2 offset = msdf_cell_size/2.0;
|
||||
highp vec2 scale = 1.0 - msdf_cell_size;
|
||||
return offset + input_uv * scale;
|
||||
}
|
||||
|
||||
highp vec2 get_texture_coord() {
|
||||
highp vec2 msdf_fragment_size = input_msdf_size / vec2(textureSize(input_atlas, 0));
|
||||
highp vec2 offset = vec2(0.0, input_atlas_index) * msdf_fragment_size;
|
||||
return offset + msdf_fragment_uv() * msdf_fragment_size;
|
||||
highp vec2 uv_to_texture_coord(vec2 uv) {
|
||||
highp vec2 texture_glyph_offset = input_msdf_size / vec2(textureSize(input_atlas, 0));
|
||||
highp vec2 offset = vec2(0.0, input_atlas_index) * texture_glyph_offset;
|
||||
return offset + uv * texture_glyph_offset;
|
||||
}
|
||||
|
||||
highp float get_fatting() {
|
||||
highp vec2 local_to_px_ratio = 1.0 / fwidth(input_local.xy);
|
||||
highp float font_size_px = input_font_size * (local_to_px_ratio.x + local_to_px_ratio.y) / 2.0;
|
||||
highp float fatting = input_sdf_weight;
|
||||
highp vec2 local_to_px_ratio = 1.0 / fwidth(input_local.xy);
|
||||
highp float font_size_px = input_font_size * (local_to_px_ratio.x + local_to_px_ratio.y) / 2.0;
|
||||
highp float fatting = input_sdf_weight;
|
||||
return font_size_px * fatting;
|
||||
}
|
||||
|
||||
highp float msdf_alpha() {
|
||||
highp vec2 tex_coord = get_texture_coord();
|
||||
highp vec2 msdf_unit_tex = input_msdf_range / vec2(textureSize(input_atlas,0));
|
||||
highp vec2 msdf_unit_px = msdf_unit_tex / fwidth(tex_coord);
|
||||
highp float get_alpha(vec2 uv) {
|
||||
highp vec2 tex_coord = uv_to_texture_coord(uv);
|
||||
highp vec2 msdf_unit_tex = input_msdf_range / vec2(textureSize(input_atlas, 0));
|
||||
highp vec2 msdf_unit_px = msdf_unit_tex / fwidth(tex_coord);
|
||||
highp float avg_msdf_unit_px = (msdf_unit_px.x + msdf_unit_px.y) / 2.0;
|
||||
|
||||
// We use this parameter to fatten somewhat font on low resolutions. The thershold and exact
|
||||
// value of this fattening was picked by trial an error, searching for best rendering effect.
|
||||
highp float dpi_dilate = avg_msdf_unit_px < input_msdf_range*0.49 ? 1.0 : 0.0;
|
||||
highp vec3 msdf_sample = texture(input_atlas,tex_coord).rgb;
|
||||
highp float sig_dist = median(msdf_sample) - 0.5;
|
||||
highp float sig_dist = median(msdf_sample) - 0.5;
|
||||
highp float sig_dist_px = sig_dist * avg_msdf_unit_px + get_fatting();
|
||||
highp float opacity = 0.5 + sig_dist_px + dpi_dilate * 0.08;
|
||||
highp float opacity = 0.5 + sig_dist_px;
|
||||
opacity += input_opacity_increase;
|
||||
opacity = clamp(opacity, 0.0, 1.0);
|
||||
opacity = pow(opacity, input_opacity_exponent);
|
||||
return opacity;
|
||||
}
|
||||
|
||||
highp vec4 color_from_msdf() {
|
||||
highp vec4 color = input_color;
|
||||
color.a *= msdf_alpha();
|
||||
color.a *= get_alpha(input_uv);
|
||||
color.rgb *= color.a; // premultiply
|
||||
|
||||
if(DEBUG) {
|
||||
vec4 bg_box = vec4(input_uv * input_size / 10.0, 0.0, 1.0);
|
||||
color = (color * 0.7 + bg_box * 0.3);
|
||||
}
|
||||
return color;
|
||||
|
||||
}
|
||||
|
@ -1,59 +0,0 @@
|
||||
highp float median(highp vec3 v) {
|
||||
return max(min(v.x, v.y), min(max(v.x, v.y), v.z));
|
||||
}
|
||||
|
||||
/// Compute the uv coordinates of the MSDF texture fragment where it should be sampled.
|
||||
///
|
||||
/// Essentially, it's an input_uv which is a bit transformed to "cut off" the half of the MSDF cell from each side. This
|
||||
/// way we have better pixel alignment on low resolutions.
|
||||
highp vec2 msdf_fragment_uv() {
|
||||
highp vec2 msdf_cell_size = 1.0/input_msdf_size;
|
||||
highp vec2 offset = msdf_cell_size/2.0;
|
||||
highp vec2 scale = 1.0 - msdf_cell_size;
|
||||
return offset + input_uv * scale;
|
||||
}
|
||||
|
||||
|
||||
highp vec2 get_texture_coord() {
|
||||
highp vec2 msdf_fragment_size = input_msdf_size / vec2(textureSize(input_atlas,0));
|
||||
highp vec2 offset = vec2(0.0, input_atlas_index) * msdf_fragment_size;
|
||||
return offset + get_scaled_uv() * msdf_fragment_size;
|
||||
}
|
||||
|
||||
highp float get_fatting() {
|
||||
highp vec2 local_to_px_ratio = 1.0 / fwidth(input_local.xy);
|
||||
highp float font_size_px = input_font_size * (local_to_px_ratio.x + local_to_px_ratio.y) / 2.0;
|
||||
highp float fatting = input_sdf_weight;
|
||||
return font_size_px * fatting;
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// The following function uses non-standard font adjustiments (lines marked with FIXME). They make
|
||||
// the font bolder and more crisp. It was designed to look nice on nodes in the GUI but leaves the
|
||||
// fonts with a non-standard look (not the one defined by the font author). This should be
|
||||
// revisited, generalized, and refactored out in the future.
|
||||
highp float msdf_alpha() {
|
||||
highp vec2 tex_coord = get_texture_coord();
|
||||
highp vec2 msdf_unit_tex = input_msdf_range / vec2(textureSize(input_atlas,0));
|
||||
highp vec2 msdf_unit_px = msdf_unit_tex / fwidth(tex_coord);
|
||||
highp float avg_msdf_unit_px = (msdf_unit_px.x + msdf_unit_px.y) / 2.0;
|
||||
|
||||
// We use this parameter to fatten somewhat font on low resolutions. The thershold and exact
|
||||
// value of this fattening was picked by trial an error, searching for best rendering effect.
|
||||
highp float dpi_dilate = avg_msdf_unit_px < input_msdf_range*0.49 ? 1.0 : 0.0;
|
||||
highp vec3 msdf_sample = texture(input_atlas,tex_coord).rgb;
|
||||
highp float sig_dist = median(msdf_sample) - 0.5;
|
||||
highp float sig_dist_px = sig_dist * avg_msdf_unit_px + get_fatting();
|
||||
highp float opacity = 0.5 + sig_dist_px + dpi_dilate * 0.08;
|
||||
opacity += 0.6; // FIXME: Widen + sharpen
|
||||
opacity = clamp(opacity, 0.0, 1.0);
|
||||
opacity = pow(opacity,3.0); // FIXME: sharpen
|
||||
return opacity;
|
||||
}
|
||||
|
||||
highp vec4 color_from_msdf() {
|
||||
highp vec4 color = input_color;
|
||||
color.a *= msdf_alpha();
|
||||
color.rgb *= color.a; // premultiply
|
||||
return color;
|
||||
}
|
@ -21,15 +21,61 @@ use ensogl_core::display::scene::Scene;
|
||||
use ensogl_core::display::symbol::material::Material;
|
||||
use ensogl_core::display::symbol::shader::builder::CodeTemplate;
|
||||
use ensogl_core::frp;
|
||||
use ensogl_core::frp::io::keyboard::Key;
|
||||
use ensogl_core::system::gpu::texture;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use ensogl_core::system::gpu::Texture;
|
||||
use ensogl_core::system::web::platform;
|
||||
use font::FontWithAtlas;
|
||||
use font::GlyphRenderInfo;
|
||||
use font::Style;
|
||||
use font::Weight;
|
||||
use font::Width;
|
||||
use owned_ttf_parser::GlyphId;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Hinting ===
|
||||
// ===============
|
||||
|
||||
/// System- and font-specific hinting properties. They affect the way the font is rasterized. In
|
||||
/// order to understand how these variables affect the font rendering, see the GLSL file (the
|
||||
/// [`FUNCTIONS`] variable).
|
||||
///
|
||||
/// Also, you can interactively change the values by holding `ctrl + alt + o` or `ctrl + alt + e`
|
||||
/// keys and using the `+` and `-` key to increment or decrement the value.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Hinting {
|
||||
pub opacity_increase: f32,
|
||||
pub opacity_exponent: f32,
|
||||
}
|
||||
|
||||
impl Default for Hinting {
|
||||
fn default() -> Self {
|
||||
Self { opacity_increase: 0.0, opacity_exponent: 1.0 }
|
||||
}
|
||||
}
|
||||
|
||||
static HINTING_MAP: LazyLock<HashMap<(Option<platform::Platform>, &'static str), Hinting>> =
|
||||
LazyLock::new(|| {
|
||||
HashMap::from([
|
||||
((Some(platform::Platform::MacOS), "mplus1p"), Hinting {
|
||||
opacity_increase: 0.4,
|
||||
opacity_exponent: 4.0,
|
||||
}),
|
||||
((Some(platform::Platform::Windows), "mplus1p"), Hinting {
|
||||
opacity_increase: 0.3,
|
||||
opacity_exponent: 3.0,
|
||||
}),
|
||||
((Some(platform::Platform::Linux), "mplus1p"), Hinting {
|
||||
opacity_increase: 0.3,
|
||||
opacity_exponent: 3.0,
|
||||
}),
|
||||
])
|
||||
});
|
||||
|
||||
|
||||
|
||||
@ -59,12 +105,7 @@ ensogl_core::define_endpoints_2! {
|
||||
#[allow(missing_docs)]
|
||||
pub struct SystemData {}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
const FUNCTIONS: &str = include_str!("glsl/glyph_mac.glsl");
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
const FUNCTIONS: &str = include_str!("glsl/glyph.glsl");
|
||||
|
||||
const MAIN: &str = "output_color = color_from_msdf(); output_id=vec4(0.0,0.0,0.0,0.0);";
|
||||
|
||||
impl SystemData {
|
||||
@ -80,6 +121,9 @@ impl SystemData {
|
||||
material.add_input("font_size", 10.0);
|
||||
material.add_input("color", Vector4::new(0.0, 0.0, 0.0, 1.0));
|
||||
material.add_input("sdf_weight", 0.0);
|
||||
// === Adjusting look and feel of different fonts on different operating systems ===
|
||||
material.add_input("opacity_increase", 0.0);
|
||||
material.add_input("opacity_exponent", 1.0);
|
||||
// TODO[WD]: We need to use this output, as we need to declare the same amount of shader
|
||||
// outputs as the number of attachments to framebuffer. We should manage this more
|
||||
// intelligent. For example, we could allow defining output shader fragments,
|
||||
@ -118,7 +162,15 @@ mod glyph_shape {
|
||||
ensogl_core::shape! {
|
||||
type SystemData = SystemData;
|
||||
type ShapeData = ShapeData;
|
||||
(style: Style, font_size: f32, color: Vector4<f32>, sdf_weight: f32, atlas_index: f32) {
|
||||
(
|
||||
style: Style,
|
||||
font_size: f32,
|
||||
color: Vector4<f32>,
|
||||
sdf_weight: f32,
|
||||
atlas_index: f32,
|
||||
opacity_increase: f32,
|
||||
opacity_exponent: f32
|
||||
) {
|
||||
// The shape does not matter. The [`SystemData`] defines custom GLSL code.
|
||||
Plane().into()
|
||||
}
|
||||
@ -132,7 +184,6 @@ impl ensogl_core::display::shape::CustomSystemData<glyph_shape::Shape> for Syste
|
||||
shape_data: &ShapeData,
|
||||
) -> Self {
|
||||
let font = &shape_data.font;
|
||||
|
||||
let size = font::msdf::Texture::size();
|
||||
let sprite_system = &data.model.sprite_system;
|
||||
let symbol = sprite_system.symbol();
|
||||
@ -522,17 +573,72 @@ impl System {
|
||||
let color_animation = color::Animation::new(frp.network());
|
||||
let x_advance = default();
|
||||
let attached_to_cursor = default();
|
||||
let platform = platform::current();
|
||||
let hinting = HINTING_MAP.get(&(platform, font.name())).copied().unwrap_or_default();
|
||||
let view = glyph_shape::View::new_with_data(ShapeData { font });
|
||||
view.color.set(Vector4::new(0.0, 0.0, 0.0, 0.0));
|
||||
view.atlas_index.set(0.0);
|
||||
view.opacity_increase.set(hinting.opacity_increase);
|
||||
view.opacity_exponent.set(hinting.opacity_exponent);
|
||||
display_object.add_child(&view);
|
||||
|
||||
let network = frp.network();
|
||||
frp::extend! {network
|
||||
let scene = scene();
|
||||
let keyboard = &scene.keyboard.frp;
|
||||
frp::extend! { network
|
||||
frp.private.output.target_color <+ frp.set_color;
|
||||
color_animation.target <+ frp.set_color;
|
||||
color_animation.skip <+ frp.skip_color_animation;
|
||||
eval color_animation.value ((c) view.color.set(Rgba::from(c).into()));
|
||||
|
||||
|
||||
// === Debug mode ===
|
||||
// Allows changing hinting parameters on the fly. See [`Hinting`] to learn more.
|
||||
|
||||
debug_mode <- all_with(&keyboard.is_control_down, &keyboard.is_alt_down, |a, b| *a && *b);
|
||||
plus <- keyboard.down.map(|t| t == &Key::Character("=".into()));
|
||||
minus <- keyboard.down.map(|t| t == &Key::Character("-".into()));
|
||||
|
||||
key_e_down <- keyboard.down.map(|t| t == &Key::Character("e".into())).on_true();
|
||||
key_e_up <- keyboard.up.map(|t| t == &Key::Character("e".into())).on_true();
|
||||
key_e <- bool(&key_e_up, &key_e_down);
|
||||
|
||||
key_o_down <- keyboard.down.map(|t| t == &Key::Character("o".into())).on_true();
|
||||
key_o_up <- keyboard.up.map(|t| t == &Key::Character("o".into())).on_true();
|
||||
key_o <- bool(&key_o_up, &key_o_down);
|
||||
|
||||
plus2 <- all_with(&plus, &debug_mode, |a, b| *a && *b);
|
||||
minus2 <- all_with(&minus, &debug_mode, |a, b| *a && *b);
|
||||
|
||||
plus_e <- keyboard.down.gate(&plus2).gate(&key_e);
|
||||
minus_e <- keyboard.down.gate(&minus2).gate(&key_e);
|
||||
|
||||
plus_o <- keyboard.down.gate(&plus2).gate(&key_o);
|
||||
minus_o <- keyboard.down.gate(&minus2).gate(&key_o);
|
||||
|
||||
eval_ plus_o (view.opacity_increase.modify(|t| {
|
||||
let opacity_increase = t + 0.01;
|
||||
warn!("opacity_increase: {opacity_increase}");
|
||||
opacity_increase
|
||||
}));
|
||||
|
||||
eval_ minus_o (view.opacity_increase.modify(|t| {
|
||||
let opacity_increase = t - 0.01;
|
||||
warn!("opacity_increase: {opacity_increase}");
|
||||
opacity_increase
|
||||
}));
|
||||
|
||||
eval_ plus_e (view.opacity_exponent.modify(|t| {
|
||||
let opacity_exponent = t + 0.1;
|
||||
warn!("opacity_exponent: {opacity_exponent}");
|
||||
opacity_exponent
|
||||
}));
|
||||
|
||||
eval_ minus_e (view.opacity_exponent.modify(|t| {
|
||||
let opacity_exponent = t - 0.1;
|
||||
warn!("opacity_exponent: {opacity_exponent}");
|
||||
opacity_exponent
|
||||
}));
|
||||
}
|
||||
|
||||
Glyph {
|
||||
|
@ -9,7 +9,7 @@ use ide_ci::log::setup_logging;
|
||||
|
||||
pub const PACKAGE: GithubRelease<&str> = GithubRelease {
|
||||
project_url: "https://github.com/enso-org/msdfgen-wasm",
|
||||
version: "v1.4",
|
||||
version: "v1.4.1",
|
||||
filename: "msdfgen_wasm.js",
|
||||
};
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#![feature(let_chains)]
|
||||
#![feature(step_trait)]
|
||||
#![feature(specialization)]
|
||||
#![feature(once_cell)]
|
||||
// === Standard Linter Configuration ===
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
|
@ -17,6 +17,7 @@ pub mod hysteretic;
|
||||
pub mod overshoot;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Animation ===
|
||||
// =================
|
||||
|
@ -29,6 +29,9 @@ use ensogl_core::application::command::FrpNetworkProvider;
|
||||
use ensogl_core::application::Application;
|
||||
use ensogl_core::data::color;
|
||||
use ensogl_core::display::navigation::navigator::Navigator;
|
||||
use ensogl_core::display::Scene;
|
||||
use ensogl_core::frp::io::timer::DocumentOps;
|
||||
use ensogl_core::frp::io::timer::HtmlElementOps;
|
||||
use ensogl_core::system::web;
|
||||
use ensogl_core::system::web::Closure;
|
||||
use ensogl_core::system::web::JsCast;
|
||||
@ -169,8 +172,12 @@ fn init(app: Application) {
|
||||
let zalgo = "Z̮̞̠͙͔ͅḀ̗̞͈̻̗Ḷ͙͎̯̹̞͓G̻O̭̗̮";
|
||||
let _text = quote.to_string() + snowman + zalgo;
|
||||
let _text = "test".to_string();
|
||||
area.set_content("aஓbc🧑🏾de\nfghij\nklmno\npqrst\n01234\n56789");
|
||||
area.set_property_default(color::Rgba::red());
|
||||
let content = "abcdefghijk";
|
||||
// This is a testing string left here for convenience.
|
||||
// area.set_content("aஓbc🧑🏾de\nfghij\nklmno\npqrst\n01234\n56789");
|
||||
area.set_content(content);
|
||||
area.set_font("mplus1p");
|
||||
area.set_property_default(color::Rgba::black());
|
||||
area.focus();
|
||||
area.hover();
|
||||
|
||||
@ -182,15 +189,45 @@ fn init(app: Application) {
|
||||
app.display.default_scene.add_child(&area);
|
||||
|
||||
let area = Rc::new(RefCell::new(Some(area)));
|
||||
init_debug_hotkeys(&area);
|
||||
|
||||
// Initialization of HTML div displaying the same text. It allows for switching between
|
||||
// WebGL and HTML versions to compare them.
|
||||
let style = web::document.create_element_or_panic("style");
|
||||
let css = web::document.create_text_node("@import url('https://fonts.googleapis.com/css2?family=M+PLUS+1p:wght@400;700&display=swap');");
|
||||
style.append_child(&css).unwrap();
|
||||
web::document.head().unwrap().append_child(&style).unwrap();
|
||||
let div = web::document.create_div_or_panic();
|
||||
div.set_style_or_warn("width", "100px");
|
||||
div.set_style_or_warn("height", "100px");
|
||||
div.set_style_or_warn("position", "absolute");
|
||||
div.set_style_or_warn("z-index", "100");
|
||||
div.set_style_or_warn("font-family", "'M PLUS 1p'");
|
||||
div.set_style_or_warn("font-size", "12px");
|
||||
div.set_style_or_warn("display", "none");
|
||||
div.set_inner_text(content);
|
||||
web::document.body().unwrap().append_child(&div).unwrap();
|
||||
|
||||
init_debug_hotkeys(&app.display.default_scene, &area, &div);
|
||||
|
||||
let scene = scene.clone_ref();
|
||||
let handler = app.display.on.before_frame.add(move |_time| {
|
||||
let shape = scene.dom.shape();
|
||||
div.set_style_or_warn("left", &format!("{}px", shape.width / 2.0));
|
||||
div.set_style_or_warn("top", &format!("{}px", shape.height / 2.0 - 0.5));
|
||||
});
|
||||
|
||||
mem::forget(handler);
|
||||
mem::forget(navigator);
|
||||
mem::forget(app);
|
||||
}
|
||||
|
||||
fn init_debug_hotkeys(area: &Rc<RefCell<Option<Text>>>) {
|
||||
fn init_debug_hotkeys(scene: &Scene, area: &Rc<RefCell<Option<Text>>>, div: &web::HtmlDivElement) {
|
||||
let html_version = Rc::new(Cell::new(false));
|
||||
let scene = scene.clone_ref();
|
||||
let area = area.clone_ref();
|
||||
let closure: Closure<dyn Fn(JsValue)> = Closure::new(move |val: JsValue| {
|
||||
let div = div.clone();
|
||||
let mut fonts_cycle = ["dejavusans", "dejavusansmono", "mplus1p"].iter().cycle();
|
||||
let closure: Closure<dyn FnMut(JsValue)> = Closure::new(move |val: JsValue| {
|
||||
let event = val.unchecked_into::<web::KeyboardEvent>();
|
||||
if event.ctrl_key() {
|
||||
let key = event.code();
|
||||
@ -200,10 +237,22 @@ fn init_debug_hotkeys(area: &Rc<RefCell<Option<Text>>>) {
|
||||
}
|
||||
}
|
||||
if let Some(area) = &*area.borrow() {
|
||||
div.set_inner_text(&area.content.value().to_string());
|
||||
if event.ctrl_key() {
|
||||
let key = event.code();
|
||||
warn!("{:?}", key);
|
||||
if key == "Digit1" {
|
||||
if key == "KeyH" {
|
||||
html_version.set(!html_version.get());
|
||||
if html_version.get() {
|
||||
warn!("Showing the HTML version.");
|
||||
area.unset_parent();
|
||||
div.set_style_or_warn("display", "block");
|
||||
} else {
|
||||
warn!("Showing the WebGL version.");
|
||||
scene.add_child(&area);
|
||||
div.set_style_or_warn("display", "none");
|
||||
}
|
||||
} else if key == "Digit1" {
|
||||
if event.shift_key() {
|
||||
area.set_property_default(color::Rgba::black());
|
||||
} else {
|
||||
@ -243,7 +292,7 @@ fn init_debug_hotkeys(area: &Rc<RefCell<Option<Text>>>) {
|
||||
} else {
|
||||
area.set_property(buffer::RangeLike::Selections, formatting::Weight::Bold);
|
||||
}
|
||||
} else if key == "KeyH" {
|
||||
} else if key == "KeyN" {
|
||||
if event.shift_key() {
|
||||
area.set_property_default(formatting::SdfWeight(0.02));
|
||||
} else {
|
||||
@ -258,6 +307,10 @@ fn init_debug_hotkeys(area: &Rc<RefCell<Option<Text>>>) {
|
||||
} else {
|
||||
area.set_property(buffer::RangeLike::Selections, formatting::Style::Italic);
|
||||
}
|
||||
} else if key == "KeyF" {
|
||||
let font = fonts_cycle.next().unwrap();
|
||||
warn!("Switching to font '{}'.", font);
|
||||
area.set_font(font);
|
||||
} else if key == "Equal" {
|
||||
if event.shift_key() {
|
||||
area.set_property_default(formatting::Size(16.0));
|
||||
|
@ -18,6 +18,7 @@ keyboard-types = { version = "0.5.0" }
|
||||
nalgebra = { version = "0.26.1" }
|
||||
percent-encoding = { version = "2.1.0" }
|
||||
unicode-segmentation = { version = "1.6.0" }
|
||||
unidecode = { version = "0.3.0" }
|
||||
# We require exact version of wasm-bindgen because we do patching final js in our build process,
|
||||
# and this is vulnerable to any wasm-bindgen version change.
|
||||
wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
|
||||
|
@ -9,6 +9,7 @@ use crate::io::js::Listener;
|
||||
use enso_web::KeyboardEvent;
|
||||
use inflector::Inflector;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unidecode::unidecode;
|
||||
|
||||
|
||||
|
||||
@ -122,7 +123,7 @@ impl Key {
|
||||
if key == " " {
|
||||
Self::Space
|
||||
} else if key.graphemes(true).count() == 1 {
|
||||
Self::Character(key)
|
||||
Self::Character(unidecode(&key).to_lowercase())
|
||||
} else {
|
||||
let key = KEY_NAME_MAP.get(key_ref).cloned().unwrap_or(Self::Other(key));
|
||||
match (key, code) {
|
||||
|
@ -33,6 +33,7 @@ features = [
|
||||
'Element',
|
||||
'HtmlElement',
|
||||
'HtmlDivElement',
|
||||
'HtmlHeadElement',
|
||||
'HtmlCollection',
|
||||
'CssStyleDeclaration',
|
||||
'HtmlCanvasElement',
|
||||
@ -49,6 +50,7 @@ features = [
|
||||
'Event',
|
||||
'MouseEvent',
|
||||
'EventTarget',
|
||||
'Text',
|
||||
'DomRect',
|
||||
'DomRectReadOnly',
|
||||
'Location',
|
||||
|
@ -464,6 +464,8 @@ mock_data! { Document => EventTarget
|
||||
fn body(&self) -> Option<HtmlElement>;
|
||||
fn create_element(&self, local_name: &str) -> Result<Element, JsValue>;
|
||||
fn get_element_by_id(&self, element_id: &str) -> Option<Element>;
|
||||
fn create_text_node(&self, data: &str) -> Text;
|
||||
fn head(&self) -> Option<HtmlHeadElement>;
|
||||
}
|
||||
|
||||
|
||||
@ -484,6 +486,11 @@ mock_data! { Window => EventTarget
|
||||
}
|
||||
|
||||
|
||||
// === HtmlHeadElement ===
|
||||
mock_data! { HtmlHeadElement => HtmlElement
|
||||
}
|
||||
|
||||
|
||||
// === Function ===
|
||||
mock_data! { Function
|
||||
fn call1(&self, context: &JsValue, arg1: &JsValue) -> Result<JsValue, JsValue>;
|
||||
@ -599,6 +606,16 @@ impl From<HtmlDivElement> for EventTarget {
|
||||
}
|
||||
|
||||
|
||||
// === HtmlDivElement ===
|
||||
mock_data! { Text => CharacterData }
|
||||
|
||||
|
||||
|
||||
// === CharacterData ===
|
||||
mock_data! { CharacterData => Node }
|
||||
|
||||
|
||||
|
||||
// === HtmlCanvasElement ===
|
||||
mock_data! { HtmlCanvasElement => HtmlElement
|
||||
fn width(&self) -> u32;
|
||||
|
@ -9,7 +9,7 @@ use std::convert::TryFrom;
|
||||
// ================
|
||||
|
||||
/// This enumeration lists all the supported platforms.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Platform {
|
||||
Android,
|
||||
|
Loading…
Reference in New Issue
Block a user