From 3fd242e15600125a95a07d795bc7166239c8aae4 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Fri, 20 Sep 2019 16:49:15 +1000 Subject: [PATCH 1/2] Support surrogates pairs in the WM_CHAR handler This makes it possible to support astral plane characters like emoji. Explained in the comments added. --- druid-shell/src/windows/mod.rs | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/druid-shell/src/windows/mod.rs b/druid-shell/src/windows/mod.rs index 420095c44d..1d2efb8c33 100644 --- a/druid-shell/src/windows/mod.rs +++ b/druid-shell/src/windows/mod.rs @@ -178,7 +178,10 @@ struct WndState { /// The `char` of the last `WM_CHAR` event, if there has not already been /// a `WM_KEYUP` event. stashed_char: Option, - //TODO: track surrogate orphan + /// Windows sends astral plane characters through as two WM_CHAR events, + /// using surrogate pairs (two UTF-16 code units). We stash the high so we + /// can reassemble it with the low when it comes. + stashed_high_surrogate: Option, } /// A structure that owns resources for the `WinCtx` (so it lasts long enough). @@ -509,10 +512,33 @@ impl WndProc for MyWndProc { WM_CHAR => { if let Ok(mut s) = self.state.try_borrow_mut() { let s = s.as_mut().unwrap(); - //FIXME: this can receive lone surrogate pairs? let key_code = s.stashed_key_code; + // WM_CHAR wparam is a UTF-16 code unit. Astral plane + // characters are sent as surrogate pairs in two events; + // so high surrogates we stash until the next WM_CHAR + // message comes through with the low surrogate, then we + // decode it at that time. Because Rust's char type + // represents Unicode scalar values, we can't represent + // lone surrogates; mismatched surrogates would indicate + // a buggy user IME, and there's really nothing we can do + // about it, so we discard such things, silently for now. + let code_unit = wparam as u32; + let scalar = if 0xD800 <= code_unit && code_unit <= 0xDC00 { + s.stashed_high_surrogate = Some(code_unit); + return None; + } else if 0xDC00 <= code_unit && code_unit <= 0xDFFF { + if let Some(high_surrogate) = s.stashed_high_surrogate.take() { + // std::char::decode_utf16 could do this too, but this is lighter. + 0x10000 + ((high_surrogate - 0xD800) << 10) + (code_unit - 0xDC00) + } else { + return None; + } + } else { + s.stashed_high_surrogate.take(); + code_unit + }; - s.stashed_char = std::char::from_u32(wparam as u32); + s.stashed_char = std::char::from_u32(scalar); let text = match s.stashed_char { Some(c) => c, None => { @@ -860,6 +886,7 @@ impl WindowBuilder { dpi, stashed_key_code: KeyCode::Unknown(0.into()), stashed_char: None, + stashed_high_surrogate: None, }; win.wndproc.connect(&handle, state); mem::drop(win); From 0b767e5078f9f801ef0d6855b0b2556f801855db Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Fri, 20 Sep 2019 17:07:19 +1000 Subject: [PATCH 2/2] =?UTF-8?q?Fix=20TextBox=E2=80=99s=20handling=20of=20a?= =?UTF-8?q?dvanced=20characters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit key_event.key_code.is_printable() was very, very wrong. If you want such a check, do it on the text, not on the key code, because the key code is very limited. I want to be able to enter characters like λ and 🙂 with my Compose key, for example, but the key code will be garbage for such keys. (I see KeyCode::Unknown(RawKeyCode::Windows(231)) for these.) The empty text case improvement is because you can end up with text being empty in some situations and you shouldn’t reset the flashing. For now, I’m just removing the is_printable() check. If anyone feels strongly about preventing non-printable characters from getting into the strings, they should do something cleverer involving Unicode tables. --- src/widget/textbox.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/widget/textbox.rs b/src/widget/textbox.rs index baaece9b07..5e690bd3d3 100644 --- a/src/widget/textbox.rs +++ b/src/widget/textbox.rs @@ -431,12 +431,13 @@ impl Widget for TextBoxRaw { self.reset_cursor_blink(ctx); } // Actual typing - k_e if k_e.key_code.is_printable() => { + k_e => { let incoming_text = k_e.text().unwrap_or(""); - self.insert(data, incoming_text); - self.reset_cursor_blink(ctx); + if !incoming_text.is_empty() { + self.insert(data, incoming_text); + self.reset_cursor_blink(ctx); + } } - _ => {} } self.update_hscroll(ctx.text(), env, data); ctx.invalidate();