Implement basic IME selection in Preedit overlay
This commit is contained in:
parent
3a35fd6249
commit
c83809adb9
5 changed files with 180 additions and 42 deletions
|
|
@ -23,10 +23,50 @@ pub enum InputMethod<T = String> {
|
|||
/// Ideally, your widget will show pre-edits on-the-spot; but, since that can
|
||||
/// be tricky, you can instead provide the current pre-edit here and the
|
||||
/// runtime will display it as an overlay (i.e. "Over-the-spot IME").
|
||||
preedit: Option<T>,
|
||||
preedit: Option<Preedit<T>>,
|
||||
},
|
||||
}
|
||||
|
||||
/// The pre-edit of an [`InputMethod`].
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct Preedit<T = String> {
|
||||
/// The current content.
|
||||
pub content: T,
|
||||
/// The selected range of the content.
|
||||
pub selection: Option<Range<usize>>,
|
||||
}
|
||||
|
||||
impl<T> Preedit<T> {
|
||||
/// Creates a new empty [`Preedit`].
|
||||
pub fn new() -> Self
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Turns a [`Preedit`] into its owned version.
|
||||
pub fn to_owned(&self) -> Preedit
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
Preedit {
|
||||
content: self.content.as_ref().to_owned(),
|
||||
selection: self.selection.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Preedit {
|
||||
/// Borrows the contents of a [`Preedit`].
|
||||
pub fn as_ref(&self) -> Preedit<&str> {
|
||||
Preedit {
|
||||
content: &self.content,
|
||||
selection: self.selection.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The purpose of an [`InputMethod`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum Purpose {
|
||||
|
|
@ -84,10 +124,7 @@ impl InputMethod {
|
|||
*self = Self::Open {
|
||||
position: *position,
|
||||
purpose: *purpose,
|
||||
preedit: preedit
|
||||
.as_ref()
|
||||
.map(AsRef::as_ref)
|
||||
.map(str::to_owned),
|
||||
preedit: preedit.as_ref().map(Preedit::to_owned),
|
||||
};
|
||||
}
|
||||
InputMethod::Allowed
|
||||
|
|
|
|||
|
|
@ -270,6 +270,23 @@ pub struct Span<'a, Link = (), Font = crate::Font> {
|
|||
pub strikethrough: bool,
|
||||
}
|
||||
|
||||
impl<Link, Font> Default for Span<'_, Link, Font> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
text: Cow::default(),
|
||||
size: None,
|
||||
line_height: None,
|
||||
font: None,
|
||||
color: None,
|
||||
link: None,
|
||||
highlight: None,
|
||||
padding: Padding::default(),
|
||||
underline: false,
|
||||
strikethrough: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A text highlight.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Highlight {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ use std::borrow::Cow;
|
|||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::ops::DerefMut;
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use text::editor::{Action, Edit, Line, LineEnding, Motion};
|
||||
|
|
@ -365,7 +366,7 @@ where
|
|||
InputMethod::Open {
|
||||
position,
|
||||
purpose: input_method::Purpose::Normal,
|
||||
preedit: Some(preedit),
|
||||
preedit: Some(preedit.as_ref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -496,7 +497,7 @@ where
|
|||
#[derive(Debug)]
|
||||
pub struct State<Highlighter: text::Highlighter> {
|
||||
focus: Option<Focus>,
|
||||
preedit: Option<String>,
|
||||
preedit: Option<input_method::Preedit>,
|
||||
last_click: Option<mouse::Click>,
|
||||
drag_click: Option<mouse::click::Kind>,
|
||||
partial_scroll: f32,
|
||||
|
|
@ -751,11 +752,15 @@ where
|
|||
}
|
||||
Update::InputMethod(update) => match update {
|
||||
Ime::Toggle(is_open) => {
|
||||
state.preedit = is_open.then(String::new);
|
||||
state.preedit =
|
||||
is_open.then(input_method::Preedit::new);
|
||||
}
|
||||
Ime::Preedit(text) => {
|
||||
Ime::Preedit { content, selection } => {
|
||||
if state.focus.is_some() {
|
||||
state.preedit = Some(text);
|
||||
state.preedit = Some(input_method::Preedit {
|
||||
content,
|
||||
selection,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ime::Commit(text) => {
|
||||
|
|
@ -1202,7 +1207,10 @@ enum Update<Message> {
|
|||
|
||||
enum Ime {
|
||||
Toggle(bool),
|
||||
Preedit(String),
|
||||
Preedit {
|
||||
content: String,
|
||||
selection: Option<Range<usize>>,
|
||||
},
|
||||
Commit(String),
|
||||
}
|
||||
|
||||
|
|
@ -1272,8 +1280,11 @@ impl<Message> Update<Message> {
|
|||
input_method::Event::Opened
|
||||
))))
|
||||
}
|
||||
input_method::Event::Preedit(content, _range) => {
|
||||
Some(Update::InputMethod(Ime::Preedit(content)))
|
||||
input_method::Event::Preedit(content, selection) => {
|
||||
Some(Update::InputMethod(Ime::Preedit {
|
||||
content,
|
||||
selection,
|
||||
}))
|
||||
}
|
||||
input_method::Event::Commit(content) => {
|
||||
Some(Update::InputMethod(Ime::Commit(content)))
|
||||
|
|
|
|||
|
|
@ -440,7 +440,7 @@ where
|
|||
} else {
|
||||
input_method::Purpose::Normal
|
||||
},
|
||||
preedit: Some(preedit),
|
||||
preedit: Some(preedit.as_ref()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1256,13 +1256,16 @@ where
|
|||
|
||||
state.is_ime_open =
|
||||
matches!(event, input_method::Event::Opened)
|
||||
.then(String::new);
|
||||
.then(input_method::Preedit::new);
|
||||
}
|
||||
input_method::Event::Preedit(content, _range) => {
|
||||
input_method::Event::Preedit(content, selection) => {
|
||||
let state = state::<Renderer>(tree);
|
||||
|
||||
if state.is_focused.is_some() {
|
||||
state.is_ime_open = Some(content.to_owned());
|
||||
state.is_ime_open = Some(input_method::Preedit {
|
||||
content: content.to_owned(),
|
||||
selection: selection.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
input_method::Event::Commit(text) => {
|
||||
|
|
@ -1514,7 +1517,7 @@ pub struct State<P: text::Paragraph> {
|
|||
placeholder: paragraph::Plain<P>,
|
||||
icon: paragraph::Plain<P>,
|
||||
is_focused: Option<Focus>,
|
||||
is_ime_open: Option<String>,
|
||||
is_ime_open: Option<input_method::Preedit>,
|
||||
is_dragging: bool,
|
||||
is_pasting: Option<Value>,
|
||||
last_click: Option<mouse::Click>,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::conversion;
|
||||
use crate::core::alignment;
|
||||
use crate::core::input_method;
|
||||
use crate::core::mouse;
|
||||
use crate::core::renderer;
|
||||
use crate::core::text;
|
||||
|
|
@ -12,11 +13,13 @@ use crate::core::{
|
|||
use crate::graphics::Compositor;
|
||||
use crate::program::{Program, State};
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
use winit::dpi::{LogicalPosition, LogicalSize};
|
||||
use winit::monitor::MonitorHandle;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct WindowManager<P, C>
|
||||
where
|
||||
|
|
@ -226,16 +229,26 @@ where
|
|||
|
||||
self.raw.set_ime_purpose(conversion::ime_purpose(purpose));
|
||||
|
||||
if let Some(content) = preedit {
|
||||
if content.is_empty() {
|
||||
if let Some(preedit) = preedit {
|
||||
if preedit.content.is_empty() {
|
||||
self.preedit = None;
|
||||
} else if let Some(preedit) = &mut self.preedit {
|
||||
preedit.update(position, &content, &self.renderer);
|
||||
} else if let Some(overlay) = &mut self.preedit {
|
||||
overlay.update(
|
||||
position,
|
||||
&preedit,
|
||||
self.state.background_color(),
|
||||
&self.renderer,
|
||||
);
|
||||
} else {
|
||||
let mut preedit = Preedit::new();
|
||||
preedit.update(position, &content, &self.renderer);
|
||||
let mut overlay = Preedit::new();
|
||||
overlay.update(
|
||||
position,
|
||||
&preedit,
|
||||
self.state.background_color(),
|
||||
&self.renderer,
|
||||
);
|
||||
|
||||
self.preedit = Some(preedit);
|
||||
self.preedit = Some(overlay);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -263,7 +276,8 @@ where
|
|||
Renderer: text::Renderer,
|
||||
{
|
||||
position: Point,
|
||||
content: text::paragraph::Plain<Renderer::Paragraph>,
|
||||
content: Renderer::Paragraph,
|
||||
spans: Vec<text::Span<'static, (), Renderer::Font>>,
|
||||
}
|
||||
|
||||
impl<Renderer> Preedit<Renderer>
|
||||
|
|
@ -273,15 +287,57 @@ where
|
|||
fn new() -> Self {
|
||||
Self {
|
||||
position: Point::ORIGIN,
|
||||
content: text::paragraph::Plain::default(),
|
||||
spans: Vec::new(),
|
||||
content: Renderer::Paragraph::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, position: Point, text: &str, renderer: &Renderer) {
|
||||
fn update(
|
||||
&mut self,
|
||||
position: Point,
|
||||
preedit: &input_method::Preedit,
|
||||
background: Color,
|
||||
renderer: &Renderer,
|
||||
) {
|
||||
self.position = position;
|
||||
|
||||
self.content.update(Text {
|
||||
content: text,
|
||||
let spans = match &preedit.selection {
|
||||
Some(selection) => {
|
||||
vec![
|
||||
text::Span {
|
||||
text: Cow::Borrowed(
|
||||
&preedit.content[..selection.start],
|
||||
),
|
||||
..text::Span::default()
|
||||
},
|
||||
text::Span {
|
||||
text: Cow::Borrowed(
|
||||
if selection.start == selection.end {
|
||||
"\u{200A}"
|
||||
} else {
|
||||
&preedit.content[selection.start..selection.end]
|
||||
},
|
||||
),
|
||||
color: Some(background),
|
||||
..text::Span::default()
|
||||
},
|
||||
text::Span {
|
||||
text: Cow::Borrowed(&preedit.content[selection.end..]),
|
||||
..text::Span::default()
|
||||
},
|
||||
]
|
||||
}
|
||||
_ => vec![text::Span {
|
||||
text: Cow::Borrowed(&preedit.content),
|
||||
..text::Span::default()
|
||||
}],
|
||||
};
|
||||
|
||||
if spans != self.spans.as_slice() {
|
||||
use text::Paragraph as _;
|
||||
|
||||
self.content = Renderer::Paragraph::with_spans(Text {
|
||||
content: &spans,
|
||||
bounds: Size::INFINITY,
|
||||
size: renderer.default_size(),
|
||||
line_height: text::LineHeight::default(),
|
||||
|
|
@ -292,6 +348,7 @@ where
|
|||
wrapping: text::Wrapping::None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
|
|
@ -300,6 +357,8 @@ where
|
|||
background: Color,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
use text::Paragraph as _;
|
||||
|
||||
if self.content.min_width() < 1.0 {
|
||||
return;
|
||||
}
|
||||
|
|
@ -329,7 +388,7 @@ where
|
|||
);
|
||||
|
||||
renderer.fill_paragraph(
|
||||
self.content.raw(),
|
||||
&self.content,
|
||||
bounds.position(),
|
||||
color,
|
||||
bounds,
|
||||
|
|
@ -347,6 +406,17 @@ where
|
|||
},
|
||||
color,
|
||||
);
|
||||
|
||||
for span_bounds in self.content.span_bounds(1) {
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: span_bounds
|
||||
+ (bounds.position() - Point::ORIGIN),
|
||||
..Default::default()
|
||||
},
|
||||
color,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue