Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add IME preedit support #2198

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 56 additions & 2 deletions src/renderer/cursor_renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
editor::{Cursor, CursorShape},
profiling::{tracy_plot, tracy_zone},
renderer::animation_utils::*,
renderer::{GridRenderer, RenderedWindow},
renderer::{GridRenderer, Preedit, RenderedWindow},
settings::{ParseFromValue, SETTINGS},
window::{ShouldRender, UserEvent},
};
Expand Down Expand Up @@ -284,7 +284,13 @@
self.blink_status.update_status(&self.cursor)
}

pub fn draw(&mut self, grid_renderer: &mut GridRenderer, canvas: &Canvas) {
pub fn draw(
&mut self,
grid_renderer: &mut GridRenderer,
canvas: &Canvas,
preedit: Option<&Preedit>,
w: f32,
) {
tracy_zone!("cursor_draw");
let render = self.blink_status.should_render();
let settings = SETTINGS.get::<CursorSettings>();
Expand Down Expand Up @@ -341,6 +347,54 @@

canvas.restore();

// draw ime if possible under current cursor
if let Some(preedit) = preedit {
let factor = grid_renderer.font_dimensions.width as f32;
let y_adjustment = y_adjustment as f32 + factor / 5.0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this factor of 5.0 do?


let off = if let Some(off) = preedit.cursor_offset() {
off as f32 * factor
} else {
0f32
};

let mut start = self.destination.x;
let mut end = self.destination.x + off;
if end + factor > w {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ideally we should use the neovim grid window width here, not the global window width, otherwise it will render outside floating window and splits for example.

This als means that the code better belongs in rendered_window, where the width and tranaparency needed is available.

start = start - (end + factor - w);

Check warning on line 364 in src/renderer/cursor_renderer/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] reported by reviewdog 🐶 <pre><code>warning: manual implementation of an assign operation --> src/renderer/cursor_renderer/mod.rs:364:17 | 364 | start = start - (end + factor - w); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `start -= (end + factor - w)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#assign_op_pattern = note: `#[warn(clippy::assign_op_pattern)]` on by default </code></pre> Raw Output: src/renderer/cursor_renderer/mod.rs:364:17:w: <pre><code>warning: manual implementation of an assign operation --> src/renderer/cursor_renderer/mod.rs:364:17 | 364 | start = start - (end + factor - w); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `start -= (end + factor - w)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#assign_op_pattern = note: `#[warn(clippy::assign_op_pattern)]` on by default </code></pre> __END__
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This clippy warning should be fixed

end = w - factor;
}

use skia_safe::Rect;
let r = Rect::new(
start,
self.destination.y,
end,
self.destination.y + y_adjustment + factor / 4.0,
);

paint.set_color(background_color);
canvas.draw_rect(r, &paint);

let blobs =
&grid_renderer
.shaper
.shape_cached(preedit.preedit_text().clone(), bold, italic);
paint.set_color(foreground_color);
for blob in blobs.iter() {
canvas.draw_text_blob(blob, (start, self.destination.y + y_adjustment), &paint);
}

paint.set_color(skia_safe::colors::GREY.to_color());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use the correct highlight group color, otherwise it might be invisible. I think this is the selection right?

Also, shouldn’t it be drawn before the text? So that the text remain invisible, with the text color also set apporopriately through the highlight groups.

I also think that the floating window transparency need to be taken into account.

let r = Rect::new(
start,
self.destination.y + y_adjustment,
end,
self.destination.y + y_adjustment + factor / 4.0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this factor 4.0 do?

);
canvas.draw_rect(r, &paint);
}

if let Some(vfx) = self.cursor_vfx.as_ref() {
vfx.render(&settings, canvas, grid_renderer, &self.cursor);
}
Expand Down
82 changes: 82 additions & 0 deletions src/renderer/ime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use winit::dpi::PhysicalPosition;
#[derive(Debug, Default)]
pub struct Ime {
/// Whether the IME is enabled.
enabled: bool,

/// Current IME preedit.
preedit: Option<Preedit>,

/// IME position
position: PhysicalPosition<i32>,
}

impl Ime {
pub fn new() -> Self {
Default::default()
}

#[inline]
pub fn set_enabled(&mut self, is_enabled: bool) {
if is_enabled {
self.enabled = is_enabled
} else {
// clear all and create new
*self = Default::default();
}
}

#[inline]
pub fn is_enabled(&self) -> bool {
self.enabled
}

#[inline]
pub fn set_preedit(&mut self, preedit: Option<Preedit>) {
self.preedit = preedit;
}

#[inline]
pub fn preedit(&self) -> Option<&Preedit> {
self.preedit.as_ref()
}

#[inline]
pub fn set_position(&mut self, p: PhysicalPosition<i32>) {
self.position = p;
}

#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
return self.position;

Check warning on line 51 in src/renderer/ime.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] reported by reviewdog 🐶 <pre><code>warning: unneeded `return` statement --> src/renderer/ime.rs:51:9 | 51 | return self.position; | ^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return = note: `#[warn(clippy::needless_return)]` on by default help: remove `return` | 51 - return self.position; 51 + self.position | </code></pre> Raw Output: src/renderer/ime.rs:51:9:w: <pre><code>warning: unneeded `return` statement --> src/renderer/ime.rs:51:9 | 51 | return self.position; | ^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return = note: `#[warn(clippy::needless_return)]` on by default help: remove `return` | 51 - return self.position; 51 + self.position | </code></pre> __END__
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This clippy warning should be fixed

}
}

#[derive(Debug, Default, PartialEq, Eq)]
pub struct Preedit {
/// The preedit text.
text: String,

/// Byte offset from cusor start
/// `None` means that the cursor is invisible.
cursor_offset: Option<usize>,
}

impl Preedit {
pub fn new(text: String, cursor_offset: Option<usize>) -> Self {
Self {
text,
cursor_offset,
}
}
pub fn preedit_text(&self) -> &String {
&self.text
}
pub fn cursor_offset(&self) -> Option<usize> {
if let Some(offset) = self.cursor_offset {

Check warning on line 76 in src/renderer/ime.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] reported by reviewdog 🐶 <pre><code>warning: manual implementation of `Option::map` --> src/renderer/ime.rs:76:9 | 76 | / if let Some(offset) = self.cursor_offset { 77 | | Some(offset) 78 | | } else { 79 | | None 80 | | } | |_________^ help: try: `self.cursor_offset.map(|offset| offset)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_map = note: `#[warn(clippy::manual_map)]` on by default </code></pre> Raw Output: src/renderer/ime.rs:76:9:w: <pre><code>warning: manual implementation of `Option::map` --> src/renderer/ime.rs:76:9 | 76 | / if let Some(offset) = self.cursor_offset { 77 | | Some(offset) 78 | | } else { 79 | | None 80 | | } | |_________^ help: try: `self.cursor_offset.map(|offset| offset)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_map = note: `#[warn(clippy::manual_map)]` on by default </code></pre> __END__
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This clippy warning should be fixed

Some(offset)
} else {
None
}
}
}
6 changes: 4 additions & 2 deletions src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod animation_utils;
pub mod cursor_renderer;
pub mod fonts;
pub mod grid_renderer;
pub mod ime;
mod opengl;
pub mod profiler;
mod rendered_window;
Expand Down Expand Up @@ -30,6 +31,7 @@ use crate::{
use cursor_renderer::CursorRenderer;
pub use fonts::caching_shaper::CachingShaper;
pub use grid_renderer::GridRenderer;
pub use ime::{Ime, Preedit};
pub use rendered_window::{LineFragment, RenderedWindow, WindowDrawCommand, WindowDrawDetails};

pub use opengl::{build_context, build_window, Context as WindowedContext, GlWindow};
Expand Down Expand Up @@ -145,7 +147,7 @@ impl Renderer {
self.cursor_renderer.prepare_frame()
}

pub fn draw_frame(&mut self, root_canvas: &Canvas, dt: f32) {
pub fn draw_frame(&mut self, root_canvas: &Canvas, dt: f32, ime: &Ime, w: f32) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give the the w parameter a more descriptive name? We don’t use the shorthand for width anywhere else?

tracy_zone!("renderer_draw_frame");
let default_background = self.grid_renderer.get_default_background();
let font_dimensions = self.grid_renderer.font_dimensions;
Expand Down Expand Up @@ -195,7 +197,7 @@ impl Renderer {
.collect();

self.cursor_renderer
.draw(&mut self.grid_renderer, root_canvas);
.draw(&mut self.grid_renderer, root_canvas, ime.preedit(), w);

self.profiler.draw(root_canvas, dt);

Expand Down
11 changes: 7 additions & 4 deletions src/window/keyboard_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,13 @@ impl KeyboardManager {
log::trace!("Ime commit {text}");
send_ui(SerialCommand::Keyboard(text.to_string()));
}
Event::WindowEvent {
event: WindowEvent::Ime(Ime::Preedit(text, cursor_offset)),
..
} => self.ime_preedit = (text.to_string(), *cursor_offset),
// Event::WindowEvent {
// event: WindowEvent::Ime(Ime::Preedit(text, cursor_offset)),
// ..
// } => {
// self.ime_preedit = (text.to_string(), *cursor_offset);
// log::trace!("Ime preedit {text}");
// }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to keep the ime state in the keyboard manager. And I also think that this breaks something, since we used to block keyboard inputs when the pre-edit was active, and that code is no longer effective.

Event::WindowEvent {
event: WindowEvent::ModifiersChanged(modifiers),
..
Expand Down
60 changes: 49 additions & 11 deletions src/window/window_wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
bridge::{send_ui, ParallelCommand, SerialCommand},
dimensions::Dimensions,
profiling::{tracy_frame, tracy_gpu_collect, tracy_gpu_zone, tracy_plot, tracy_zone},
renderer::{build_context, DrawCommand, GlWindow, Renderer, VSync, WindowedContext},
renderer::{
build_context,
ime::{Ime, Preedit},
DrawCommand, GlWindow, Renderer, VSync, WindowedContext,
},
running_tracker::RUNNING_TRACKER,
settings::{SettingsChanged, DEFAULT_GRID_SIZE, MIN_GRID_SIZE, SETTINGS},
window::{ShouldRender, WindowSize},
Expand Down Expand Up @@ -57,8 +61,8 @@
font_changed_last_frame: bool,
saved_inner_size: PhysicalSize<u32>,
saved_grid_size: Option<Dimensions>,
ime_enabled: bool,
ime_position: PhysicalPosition<i32>,
ime: Ime,
window_width: f32,
requested_columns: Option<u64>,
requested_lines: Option<u64>,
ui_state: UIState,
Expand Down Expand Up @@ -119,8 +123,8 @@
font_changed_last_frame: false,
saved_inner_size,
saved_grid_size: None,
ime_enabled,
ime_position: PhysicalPosition::new(-1, -1),
ime: Ime::new(),
window_width: 0.0,
requested_columns: None,
requested_lines: None,
ui_state: UIState::Initing,
Expand Down Expand Up @@ -158,7 +162,7 @@
}

pub fn set_ime(&mut self, ime_enabled: bool) {
self.ime_enabled = ime_enabled;
self.ime.set_enabled(ime_enabled);
self.windowed_context.window().set_ime_allowed(ime_enabled);
}

Expand Down Expand Up @@ -204,7 +208,7 @@
}
}
WindowSettingsChanged::InputIme(ime_enabled) => {
if self.ime_enabled != ime_enabled {
if self.ime.is_enabled() != ime_enabled {
self.set_ime(ime_enabled);
}
}
Expand Down Expand Up @@ -317,6 +321,32 @@
tracy_zone!("Moved");
self.vsync.update(&self.windowed_context);
}

Event::WindowEvent {
event: WindowEvent::Resized(size),
..
} => {
if size.width != 0 && size.height != 0 {
self.window_width = size.width as f32;
}
}
Event::WindowEvent {
event: WindowEvent::Ime(ime),
..
} => match ime {

Check warning on line 336 in src/window/window_wrapper.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] reported by reviewdog 🐶 <pre><code>warning: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` --> src/window/window_wrapper.rs:336:18 | 336 | } => match ime { | __________________^ 337 | | winit::event::Ime::Preedit(text, cursor_offset) => { 338 | | let preedit = if text.is_empty() { 339 | | None ... | 348 | | _ => {} 349 | | }, | |_____________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#single_match = note: `#[warn(clippy::single_match)]` on by default help: try | 336 ~ } => if let winit::event::Ime::Preedit(text, cursor_offset) = ime { 337 + let preedit = if text.is_empty() { 338 + None 339 + } else { 340 + Some(Preedit::new(text, cursor_offset.map(|offset| offset.0))) 341 + }; 342 + 343 + if self.ime.preedit() != preedit.as_ref() { 344 + self.ime.set_preedit(preedit); 345 + } 346 ~ }, | </code></pre> Raw Output: src/window/window_wrapper.rs:336:18:w: <pre><code>warning: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` --> src/window/window_wrapper.rs:336:18 | 336 | } => match ime { | __________________^ 337 | | winit::event::Ime::Preedit(text, cursor_offset) => { 338 | | let preedit = if text.is_empty() { 339 | | None ... | 348 | | _ => {} 349 | | }, | |_____________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#single_match = note: `#[warn(clippy::single_match)]` on by default help: try | 336 ~ } => if let winit::event::Ime::Preedit(text, cursor_offset) = ime { 337 + let preedit = if text.is_empty() { 338 + None 339 + } else { 340 + Some(Preedit::new(text, cursor_offset.map(|offset| offset.0))) 341 + }; 342 + 343 + if self.ime.preedit() != preedit.as_ref() { 344 + self.ime.set_preedit(preedit); 345 + } 346 ~ }, | </code></pre> __END__
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clippy warnings should be fixed

winit::event::Ime::Preedit(text, cursor_offset) => {
let preedit = if text.is_empty() {
None
} else {
Some(Preedit::new(text, cursor_offset.map(|offset| offset.0)))
};

if self.ime.preedit() != preedit.as_ref() {
self.ime.set_preedit(preedit);
}
}
_ => {}
},
Event::UserEvent(UserEvent::DrawCommandBatch(batch)) => {
self.handle_draw_commands(batch);
}
Expand Down Expand Up @@ -352,7 +382,14 @@

pub fn draw_frame(&mut self, dt: f32) {
tracy_zone!("draw_frame");
self.renderer.draw_frame(self.skia_renderer.canvas(), dt);
self.renderer.prepare_lines();
self.renderer.draw_frame(
self.skia_renderer.canvas(),
dt,
&self.ime,
self.window_width,
);

{
tracy_gpu_zone!("skia flush");
self.skia_renderer.gr_context.flush_and_submit();
Expand Down Expand Up @@ -418,7 +455,7 @@
}

// Ensure that the window has the correct IME state
self.set_ime(self.ime_enabled);
self.set_ime(self.ime.is_enabled());
};
}

Expand Down Expand Up @@ -559,8 +596,9 @@
cursor_position.x.round() as i32,
cursor_position.y.round() as i32 + font_dimensions.height as i32,
);
if position != self.ime_position {
self.ime_position = position;
if position != self.ime.position() {
// self.ime.position = position;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commented code should be removed

self.ime.set_position(position);
self.windowed_context.window().set_ime_cursor_area(
Position::Physical(position),
PhysicalSize::new(100, font_dimensions.height as u32),
Expand Down