-
Notifications
You must be signed in to change notification settings - Fork 515
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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}, | ||
}; | ||
|
@@ -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>(); | ||
|
@@ -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; | ||
|
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
start = start - (end + factor - w); | ||
Check warning on line 364 in src/renderer/cursor_renderer/mod.rs GitHub Actions / clippy
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
|
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 GitHub Actions / clippy
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 GitHub Actions / clippy
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This clippy warning should be fixed |
||
Some(offset) | ||
} else { | ||
None | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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}; | ||
|
@@ -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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you give the the |
||
tracy_zone!("renderer_draw_frame"); | ||
let default_background = self.grid_renderer.get_default_background(); | ||
let font_dimensions = self.grid_renderer.font_dimensions; | ||
|
@@ -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); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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}"); | ||
// } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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), | ||
.. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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}, | ||
|
@@ -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, | ||
|
@@ -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, | ||
|
@@ -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); | ||
} | ||
|
||
|
@@ -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); | ||
} | ||
} | ||
|
@@ -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 GitHub Actions / clippy
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
|
@@ -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(); | ||
|
@@ -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()); | ||
}; | ||
} | ||
|
||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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), | ||
|
There was a problem hiding this comment.
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?