Skip to content

Commit cf3ed7e

Browse files
chore: working, text_input (#139)
* chore: working, text_input * chore: add event ime * chore: little process * chore: typo fix * chore: ime base * chore: almost finished * chore: change unwrap to expect
1 parent ed4c60c commit cf3ed7e

File tree

12 files changed

+1101
-12
lines changed

12 files changed

+1101
-12
lines changed

iced_examples/counter/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ impl Application for Counter {
7979

8080
fn update(&mut self, message: Message) -> Command<Message> {
8181
match message {
82-
Message::IcedEvent(event) => {
83-
println!("hello {event:?}");
82+
Message::IcedEvent(_event) => {
83+
//println!("hello {event:?}");
8484
Command::none()
8585
}
8686
Message::IncrementPressed => {

iced_layershell/src/actions.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::reexport::{Anchor, Layer, WlRegion};
22
use iced::window::Id as IcedId;
3+
use iced_core::input_method;
34
use iced_core::mouse::Interaction;
45
use layershellev::NewLayerShellSettings;
56
use layershellev::id::Id as LayerId;
@@ -15,7 +16,9 @@ pub(crate) enum LayerShellAction {
1516
CustomActionsWithId(LayershellCustomActionsWithIdInner),
1617
RedrawAll,
1718
RedrawWindow(LayerId), // maybe one day it is useful, but now useless
18-
NewMenu((IcedNewPopupSettings, iced_core::window::Id)),
19+
NewMenu(IcedNewPopupSettings, iced_core::window::Id),
20+
Ime(input_method::InputMethod),
21+
ImeWithId(LayerId, input_method::InputMethod)
1922
}
2023

2124
#[derive(Debug, PartialEq, Eq, Clone, Copy)]

iced_layershell/src/application.rs

Lines changed: 134 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@ use crate::{
77
clipboard::LayerShellClipboard,
88
conversion,
99
error::Error,
10+
ime_preedit::Preedit,
1011
settings::VirtualKeyboardSettings,
1112
};
1213

1314
use super::{Appearance, DefaultStyle};
1415
use iced_graphics::{Compositor, compositor};
1516
use state::State;
1617

17-
use iced_core::{Event as IcedCoreEvent, Size, time::Instant, window as IcedCoreWindow};
18+
use iced_core::{
19+
Event as IcedCoreEvent, InputMethod, Size, input_method, time::Instant,
20+
window as IcedCoreWindow,
21+
};
1822

1923
use iced_runtime::{Action, Debug, Program, UserInterface, task::Task, user_interface};
2024

@@ -333,6 +337,25 @@ where
333337
LayerShellAction::RedrawWindow(index) => {
334338
ev.append_return_data(ReturnData::RedrawIndexRequest(index));
335339
}
340+
LayerShellAction::Ime(ime) => match ime {
341+
iced_core::InputMethod::Disabled => {
342+
ev.set_ime_allowed(false);
343+
}
344+
iced_core::InputMethod::Enabled {
345+
position, purpose, ..
346+
} => {
347+
ev.set_ime_allowed(true);
348+
ev.set_ime_purpose(conversion::ime_purpose(purpose));
349+
ev.set_ime_cursor_area(
350+
layershellev::dpi::LogicalPosition::new(position.x, position.y),
351+
layershellev::dpi::LogicalSize {
352+
width: 10,
353+
height: 10,
354+
},
355+
ev.main_window().id(),
356+
);
357+
}
358+
},
336359
_ => {}
337360
}
338361
}
@@ -341,6 +364,94 @@ where
341364
Ok(())
342365
}
343366

367+
struct IMDrawer<A>
368+
where
369+
A: Application,
370+
A::Theme: iced_core::theme::Base,
371+
{
372+
preedit: Option<Preedit<A::Renderer>>,
373+
ime_state: Option<(iced_core::Point, input_method::Purpose)>,
374+
}
375+
376+
impl<A> IMDrawer<A>
377+
where
378+
A: Application,
379+
A::Theme: iced_core::theme::Base,
380+
{
381+
fn new() -> Self {
382+
Self {
383+
preedit: None,
384+
ime_state: None,
385+
}
386+
}
387+
pub fn request_input_method(
388+
&mut self,
389+
background_color: iced_core::Color,
390+
input_method: InputMethod,
391+
renderer: &A::Renderer,
392+
) {
393+
match input_method {
394+
InputMethod::Disabled => {
395+
self.disable_ime();
396+
}
397+
InputMethod::Enabled {
398+
position,
399+
purpose,
400+
preedit,
401+
} => {
402+
self.enable_ime(position, purpose);
403+
404+
if let Some(preedit) = preedit {
405+
if preedit.content.is_empty() {
406+
self.preedit = None;
407+
} else {
408+
let mut overlay = self.preedit.take().unwrap_or_else(Preedit::new);
409+
410+
overlay.update(position, &preedit, background_color, renderer);
411+
412+
self.preedit = Some(overlay);
413+
}
414+
} else {
415+
self.preedit = None;
416+
}
417+
}
418+
}
419+
}
420+
421+
pub fn draw_preedit(
422+
&mut self,
423+
renderer: &mut A::Renderer,
424+
text_color: iced_core::Color,
425+
background_color: iced_core::Color,
426+
logical_size: iced_core::Size,
427+
) {
428+
use iced_core::Point;
429+
use iced_core::Rectangle;
430+
if let Some(preedit) = &self.preedit {
431+
preedit.draw(
432+
renderer,
433+
text_color,
434+
background_color,
435+
&Rectangle::new(Point::ORIGIN, logical_size),
436+
);
437+
}
438+
}
439+
440+
fn enable_ime(&mut self, position: iced_core::Point, purpose: input_method::Purpose) {
441+
if self.ime_state != Some((position, purpose)) {
442+
self.ime_state = Some((position, purpose));
443+
}
444+
}
445+
446+
fn disable_ime(&mut self) {
447+
if self.ime_state.is_some() {
448+
self.ime_state = None;
449+
}
450+
451+
self.preedit = None;
452+
}
453+
}
454+
344455
#[allow(clippy::too_many_arguments)]
345456
async fn run_instance<A, E, C>(
346457
mut application: A,
@@ -370,6 +481,7 @@ async fn run_instance<A, E, C>(
370481
}
371482

372483
let mut renderer = compositor.create_renderer();
484+
let mut im_drawer: IMDrawer<A> = IMDrawer::new();
373485

374486
let cache = user_interface::Cache::default();
375487

@@ -435,14 +547,33 @@ async fn run_instance<A, E, C>(
435547
let redraw_event =
436548
IcedCoreEvent::Window(IcedCoreWindow::Event::RedrawRequested(Instant::now()));
437549

438-
user_interface.update(
550+
let (ui_state, _) = user_interface.update(
439551
&[redraw_event.clone()],
440552
state.cursor(),
441553
&mut renderer,
442554
&mut clipboard,
443555
&mut messages,
444556
);
445-
events.push(redraw_event.clone());
557+
if let user_interface::State::Updated {
558+
redraw_request: _, // NOTE: I do not know how to use it now
559+
input_method,
560+
} = ui_state
561+
{
562+
events.push(redraw_event.clone());
563+
custom_actions.push(LayerShellAction::Ime(input_method.clone()));
564+
im_drawer.request_input_method(
565+
state.background_color(),
566+
input_method,
567+
&renderer,
568+
);
569+
}
570+
im_drawer.draw_preedit(
571+
&mut renderer,
572+
state.text_color(),
573+
state.background_color(),
574+
state.viewport().logical_size(),
575+
);
576+
446577
runtime.broadcast(iced_futures::subscription::Event::Interaction {
447578
window: main_id,
448579
event: redraw_event,

iced_layershell/src/conversion.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::event::IcedButtonState;
44
use crate::event::WindowEvent as LayerShellEvent;
55
use iced::touch;
66
use iced_core::SmolStr;
7+
use iced_core::input_method;
78
use iced_core::{Event as IcedEvent, keyboard, mouse};
89
use keymap::{key, physical_key};
910
use layershellev::keyboard::KeyLocation;
@@ -121,10 +122,26 @@ pub fn window_event(layerevent: &LayerShellEvent, modifiers: ModifiersState) ->
121122
)),
122123
LayerShellEvent::Unfocus => Some(IcedEvent::Window(iced::window::Event::Unfocused)),
123124
LayerShellEvent::Focused => Some(IcedEvent::Window(iced::window::Event::Focused)),
125+
LayerShellEvent::Ime(event) => Some(IcedEvent::InputMethod(match event {
126+
layershellev::Ime::Enabled => input_method::Event::Opened,
127+
layershellev::Ime::Preedit(content, size) => {
128+
input_method::Event::Preedit(content.clone(), size.map(|(start, end)| (start..end)))
129+
}
130+
layershellev::Ime::Commit(content) => input_method::Event::Commit(content.clone()),
131+
layershellev::Ime::Disabled => input_method::Event::Closed,
132+
})),
124133
_ => None,
125134
}
126135
}
127136

137+
pub fn ime_purpose(purpose: input_method::Purpose) -> layershellev::ImePurpose {
138+
match purpose {
139+
input_method::Purpose::Normal => layershellev::ImePurpose::Normal,
140+
input_method::Purpose::Secure => layershellev::ImePurpose::Password,
141+
input_method::Purpose::Terminal => layershellev::ImePurpose::Terminal,
142+
}
143+
}
144+
128145
pub(crate) fn mouse_interaction(interaction: mouse::Interaction) -> String {
129146
use layershellev::reexport::wp_cursor_shape_device_v1::{Shape, ShapeName};
130147
use mouse::Interaction;

iced_layershell/src/event.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ pub enum WindowEvent {
9393
x: f64,
9494
y: f64,
9595
},
96+
Ime(layershellev::Ime),
9697
}
9798

9899
#[derive(Debug)]
@@ -212,6 +213,7 @@ impl<Message: 'static> From<&DispatchMessage> for IcedLayerEvent<Message> {
212213
DispatchMessage::ModifiersChanged(modifiers) => {
213214
IcedLayerEvent::Window(WindowEvent::ModifiersChanged(*modifiers))
214215
}
216+
DispatchMessage::Ime(ime) => IcedLayerEvent::Window(WindowEvent::Ime(ime.clone())),
215217
DispatchMessage::Axis {
216218
horizontal,
217219
vertical,

iced_layershell/src/ime_preedit.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
use iced_core::{
2+
Color, Padding, Point, Rectangle, Size, Text, Vector, alignment, input_method, renderer, text,
3+
};
4+
5+
pub struct Preedit<Renderer>
6+
where
7+
Renderer: text::Renderer,
8+
{
9+
position: Point,
10+
content: Renderer::Paragraph,
11+
spans: Vec<text::Span<'static, (), Renderer::Font>>,
12+
}
13+
14+
impl<Renderer> Preedit<Renderer>
15+
where
16+
Renderer: text::Renderer,
17+
{
18+
pub fn new() -> Self {
19+
Self {
20+
position: Point::ORIGIN,
21+
spans: Vec::new(),
22+
content: Renderer::Paragraph::default(),
23+
}
24+
}
25+
26+
pub fn update(
27+
&mut self,
28+
position: Point,
29+
preedit: &input_method::Preedit,
30+
background: Color,
31+
renderer: &Renderer,
32+
) {
33+
self.position = position;
34+
35+
let spans = match &preedit.selection {
36+
Some(selection) => {
37+
vec![
38+
text::Span::new(&preedit.content[..selection.start]),
39+
text::Span::new(if selection.start == selection.end {
40+
"\u{200A}"
41+
} else {
42+
&preedit.content[selection.start..selection.end]
43+
})
44+
.color(background),
45+
text::Span::new(&preedit.content[selection.end..]),
46+
]
47+
}
48+
_ => vec![text::Span::new(&preedit.content)],
49+
};
50+
51+
if spans != self.spans.as_slice() {
52+
use text::Paragraph as _;
53+
54+
self.content = Renderer::Paragraph::with_spans(Text {
55+
content: &spans,
56+
bounds: Size::INFINITY,
57+
size: renderer.default_size(),
58+
line_height: text::LineHeight::default(),
59+
font: renderer.default_font(),
60+
horizontal_alignment: alignment::Horizontal::Left,
61+
vertical_alignment: alignment::Vertical::Top,
62+
shaping: text::Shaping::Advanced,
63+
wrapping: text::Wrapping::None,
64+
});
65+
}
66+
}
67+
68+
pub fn draw(
69+
&self,
70+
renderer: &mut Renderer,
71+
color: Color,
72+
background: Color,
73+
viewport: &Rectangle,
74+
) {
75+
use text::Paragraph as _;
76+
77+
if self.content.min_width() < 1.0 {
78+
return;
79+
}
80+
81+
let mut bounds = Rectangle::new(
82+
self.position - Vector::new(0.0, self.content.min_height()),
83+
self.content.min_bounds(),
84+
);
85+
86+
bounds.x = bounds
87+
.x
88+
.max(viewport.x)
89+
.min(viewport.x + viewport.width - bounds.width);
90+
91+
bounds.y = bounds
92+
.y
93+
.max(viewport.y)
94+
.min(viewport.y + viewport.height - bounds.height);
95+
96+
renderer.with_layer(bounds, |renderer| {
97+
renderer.fill_quad(
98+
renderer::Quad {
99+
bounds,
100+
..Default::default()
101+
},
102+
background,
103+
);
104+
105+
renderer.fill_paragraph(&self.content, bounds.position(), color, bounds);
106+
107+
const UNDERLINE: f32 = 2.0;
108+
109+
renderer.fill_quad(
110+
renderer::Quad {
111+
bounds: bounds.shrink(Padding {
112+
top: bounds.height - UNDERLINE,
113+
..Default::default()
114+
}),
115+
..Default::default()
116+
},
117+
color,
118+
);
119+
120+
for span_bounds in self.content.span_bounds(1) {
121+
renderer.fill_quad(
122+
renderer::Quad {
123+
bounds: span_bounds + (bounds.position() - Point::ORIGIN),
124+
..Default::default()
125+
},
126+
color,
127+
);
128+
}
129+
});
130+
}
131+
}

0 commit comments

Comments
 (0)