-
Notifications
You must be signed in to change notification settings - Fork 25
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
Implement IME primitives for PlainEditor. #136
base: main
Are you sure you want to change the base?
Conversation
#[derive(Clone, Default)] | ||
struct ComposeState { | ||
selection: Option<(usize, usize)>, | ||
text: Arc<str>, |
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.
I feel like this should be a String
. Then:
- We could mutate it and re-use the allocation when updating the pre-edit
- Setting it to empty would be allocation free
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.
- Setting it to empty would be allocation free
it may not matter, but it's never empty
// Winit on some platforms delivers an empty Preedit after Commit | ||
// so don't lock into compose when preedit is empty. | ||
Ime::Preedit(text, None) if text.is_empty() => vec![ | ||
PlainEditorOp::SetCompose("".into(), None), |
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.
A dedicated ClearCompose
Op for setting to empty might be nice.
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.
That's fair, and definitely the ergonomic choice when moving to methods instead of ops.
41886fa
to
9a6f8f6
Compare
9a6f8f6
to
d400210
Compare
CommitCompose => { | ||
if let Some(ComposeState { text, .. }) = self.compose.clone() { | ||
let new_insert = self.selection.insertion_index() + text.len(); | ||
self.selection = if new_insert == self.buffer.len() { | ||
Selection::from_index( | ||
&self.layout, | ||
new_insert.saturating_sub(1), | ||
Affinity::Upstream, | ||
) | ||
} else { | ||
Selection::from_index(&self.layout, new_insert, Affinity::Downstream) | ||
}; | ||
} | ||
self.compose = None; | ||
layout_after = true; | ||
} |
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.
It may not matter too much, but this does some additional work on each commit, as Winit delivers a Preedit("", None)
before sending Commit
. On commit, vello_editor in this PR currently clears the compose state, to immediately set it again, to then commit it. If CommitCompose
takes the committed text, some of those buffer operations become unnecessary.
There also appear to be some IMEs that commit without going into compose first, so perhaps it makes sense to have SetCompose(Arc<str>, Option<(usize, usize)>)
, ClearCompose
, and Commit(Arc<str>)
.
Commit(Arc<str>)
would guarantee it also clears the compose state. Otherwise it's effectively the same as InsertOrReplaceSelection(Arc<str>)
.
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.
I agree, that will be the final command set as it has been requested by both you and Nico, and it clearly makes sense.
if let Some(parley::Rect { x0, y0, x1, y1 }) = self.editor.preedit_area() { | ||
println!("{x0} : {y0}"); | ||
println!("{x1} : {y1}"); | ||
render_state.window.set_ime_cursor_area( | ||
LogicalPosition::new(x0, y0), | ||
LogicalSize::new(x1 - x0, y1 - y0), | ||
); | ||
} |
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.
There's discussion in #111 about when to notify the platform about the IME preedit area. @DJMcNab reported an infinite loop if the area is sent as a response to all IME events: #111 (comment). The simplest solution is to cache the previously sent area, and only send the current area if it's not the same. Unconditionally send the area on IME::Enabled
.
To be a bit more conservative in reporting to the platform, the main thing missing is to easily know whether Layout
has changed in response to an event, see also #111 (comment).
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.
Almost all events and ops affect either the cursor position or the Layout, so in the case of the example it doesn't much matter; however it might be good to make the calculation one-shot, maybe give it an observer pattern for ease of use.
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.
That is true, though some solution is required for the infinite loop that currently probably happens on some platforms (report area -> platform resends preedit -> report area -> …). Unfortunately I can't test this on my platform. WindowEvent::{RedrawRequested,CursorMoved}
are possible exeptions.
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.
No description provided.