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

Implement IME primitives for PlainEditor. #136

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

xorgy
Copy link
Member

@xorgy xorgy commented Oct 11, 2024

No description provided.

#[derive(Clone, Default)]
struct ComposeState {
selection: Option<(usize, usize)>,
text: Arc<str>,
Copy link
Contributor

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

Copy link
Member Author

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),
Copy link
Contributor

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.

Copy link
Member Author

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.

@xorgy xorgy force-pushed the plain-editor-ime branch 5 times, most recently from 41886fa to 9a6f8f6 Compare October 11, 2024 16:09
Comment on lines +183 to +198
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;
}
Copy link
Member

@tomcur tomcur Oct 21, 2024

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>).

Copy link
Member Author

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.

Comment on lines +119 to +126
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),
);
}
Copy link
Member

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).

Copy link
Member Author

@xorgy xorgy Oct 21, 2024

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.

Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

@tomcur I think #143 should make this pretty easy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants