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

IME enabled on web #247

Closed
wants to merge 53 commits into from
Closed
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
759e17b
Text wrap correctly on Chinese chrarcter
n2 Jan 10, 2021
efd8027
Text wrap correctly on Chinese chraracter, checked by a stand-alone f…
n2 Jan 12, 2021
40ffef8
Merge remote-tracking branch 'upstream/master' into master
n2 Jan 12, 2021
d5bb79d
Merge remote-tracking branch 'upstream/master' into master
n2 Jan 16, 2021
d4e5fa5
Merge remote-tracking branch 'upstream/master' into master
n2 Mar 14, 2021
e8d9273
epaint: default_fonts replaced by custom_fonts
n2 Mar 18, 2021
5e93450
Merge remote-tracking branch 'upstream/master' into master
n2 Mar 18, 2021
9a88ecf
Fix " env" error on wasm-bindgen 0.2.72
n2 Mar 19, 2021
be93274
Create TexeElement, send text if needed.
n2 Mar 21, 2021
34c6a5f
Toggle keyboard if necessary
n2 Mar 22, 2021
2a33c49
Make agent following cursor.
n2 Mar 22, 2021
8bcf144
Remove text_cursor from Output & canvas panning
n2 Mar 23, 2021
db3be33
Handle Text event when agent is hidden & code cleaning.
n2 Mar 23, 2021
b82b1bd
Some minor changes.
n2 Mar 23, 2021
ce05842
Merge remote-tracking branch 'upstream/master' into master
n2 Mar 23, 2021
86d331d
Revert "epaint: default_fonts replaced by custom_fonts"
n2 Mar 23, 2021
41a3bb6
Fix the panning of the canvas on mobile
emilk Mar 24, 2021
db04d03
Some changes based on feedback.
n2 Mar 25, 2021
9dd0057
Merge remote-tracking branch 'upstream/master'
n2 Mar 26, 2021
ba41b22
Minor changes
n2 Mar 26, 2021
1bed5ca
bug fix: false id clash error for wrapping text
emilk Mar 20, 2021
9540c3e
refactor: simplify CollapsingHeader enable/disable code
emilk Mar 20, 2021
27eba1b
Add helper functions to Rect
emilk Mar 14, 2021
5d3501f
emath: add any_nan to Vec2, Pos2 and Rect
emilk Mar 15, 2021
c912df2
Refactor layout (#241)
emilk Mar 20, 2021
2347d64
Fix bug that would allocate the full width of non-centered layouts
emilk Mar 20, 2021
4237811
Fix: centered horizontal layouts should never overflow upwards
emilk Mar 21, 2021
9c2438e
Add Visuals::debug_widgets to debug layouting by hovering widgets
emilk Mar 21, 2021
deb5921
Bug-fixes related to recent layout rewrite
emilk Mar 21, 2021
e815d21
Add ui.set_row_height
emilk Mar 21, 2021
5c908ff
Depcrecate ui.horizontal_for_text and ui.horizontal_wrapped_for_text
emilk Mar 21, 2021
8aa497f
[EasyMark] Add support for small and raised text
emilk Feb 7, 2021
0f1f317
Middle-click links to open in new tab
emilk Mar 21, 2021
ce4a68d
Add demo app instructions for Defora Rawhide
emilk Mar 21, 2021
93e2385
Document the need for latest rustc
emilk Mar 21, 2021
c5c4a09
Add link to https://github.com/hakolao/egui_winit_vulkano
emilk Mar 21, 2021
6b43c99
ColorPicker: always show hue slider at full saturation and lightness
emilk Mar 21, 2021
9ede193
Improve misc documentation
emilk Mar 21, 2021
e9cc09b
Add instruction to not add .js/.wasm files in pull requests
emilk Mar 21, 2021
01d858a
Add years and email in LICENSE-MIT
emilk Mar 21, 2021
c43ea67
Replace emath::clamp with f32::clamp (new in rustc 1.50)
emilk Mar 21, 2021
ffb66f8
layout: don't return negative availability rectangles
emilk Mar 21, 2021
64ea7d2
Return InnerResponse from Frame, Grid and ui.group()
emilk Mar 21, 2021
bee737d
Revert "epaint: default_fonts replaced by custom_fonts"
n2 Mar 23, 2021
d006d10
Fix the panning of the canvas on mobile
emilk Mar 24, 2021
fc7221b
Some changes based on feedback.
n2 Mar 25, 2021
71e45e0
Add some clippy lints
emilk Mar 23, 2021
897ddad
Render tab character (\t) as four spaces
emilk Mar 23, 2021
cc96ad9
Add checkbox in demo app to turn screen reader on/off
emilk Mar 24, 2021
3a0f1d2
Upgrade wasm-bindgen 0.2.71 -> 0.2.72
emilk Mar 25, 2021
7797c80
ci: install libspeechd-dev and fix new clippy lints (#252)
emilk Mar 25, 2021
fe44235
Minor changes
n2 Mar 26, 2021
f32d369
Merge branch 'master' of https://github.com/n2/egui into master
n2 Mar 26, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions egui_web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,20 @@ features = [
"Clipboard",
"ClipboardEvent",
"console",
"CompositionEvent",
"CssStyleDeclaration",
"DataTransfer",
"Document",
"DomRect",
"Element",
"Event",
"EventListener",
"EventTarget",
"FocusEvent",
"HtmlCanvasElement",
"HtmlElement",
"HtmlInputElement",
"InputEvent",
"KeyboardEvent",
"Location",
"MouseEvent",
Expand Down
3 changes: 2 additions & 1 deletion egui_web/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ impl AppRunner {
self.app.update(egui_ctx, &mut frame);
let (egui_output, clipped_meshes) = self.web_backend.end_frame()?;
self.screen_reader.speak(&egui_output.events_description());
handle_output(&egui_output);
handle_output(&egui_output, self.canvas_id());

{
let epi::backend::AppOutput {
Expand Down Expand Up @@ -253,6 +253,7 @@ fn start_runner(app_runner: AppRunner) -> Result<AppRunnerRef, JsValue> {
let runner_ref = AppRunnerRef(Arc::new(Mutex::new(app_runner)));
install_canvas_events(&runner_ref)?;
install_document_events(&runner_ref)?;
install_text_agent(&runner_ref)?;
repaint_every_ms(&runner_ref, 1000)?; // just in case. TODO: make it a parameter
paint_and_schedule(runner_ref.clone())?;
Ok(runner_ref)
Expand Down
171 changes: 164 additions & 7 deletions egui_web/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ pub use wasm_bindgen;
pub use web_sys;

pub use painter::Painter;
use std::cell::Cell;
use std::rc::Rc;
use std::sync::Arc;
use wasm_bindgen::prelude::*;

static AGENT_ID: &str = "text_agent";

// ----------------------------------------------------------------------------
// Helpers to hide some of the verbosity of web_sys

Expand Down Expand Up @@ -107,11 +111,13 @@ pub fn button_from_mouse_event(event: &web_sys::MouseEvent) -> Option<egui::Poin
}
}

pub fn pos_from_touch_event(event: &web_sys::TouchEvent) -> egui::Pos2 {
pub fn pos_from_touch_event(canvas_id: &str, event: &web_sys::TouchEvent) -> egui::Pos2 {
let canvas = canvas_element(canvas_id).unwrap();
let rect = canvas.get_bounding_client_rect();
let t = event.touches().get(0).unwrap();
egui::Pos2 {
x: t.page_x() as f32,
y: t.page_y() as f32,
x: t.page_x() as f32 - rect.left() as f32,
y: t.page_y() as f32 - rect.top() as f32,
}
}

Expand Down Expand Up @@ -228,7 +234,7 @@ impl epi::Storage for LocalStorage {

// ----------------------------------------------------------------------------

pub fn handle_output(output: &egui::Output) {
pub fn handle_output(output: &egui::Output, _canvas_id: &str) {
Copy link
Owner

Choose a reason for hiding this comment

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

remove unused argument

let egui::Output {
cursor_icon,
open_url,
Expand Down Expand Up @@ -458,6 +464,19 @@ fn paint_and_schedule(runner_ref: AppRunnerRef) -> Result<(), JsValue> {
request_animation_frame(runner_ref)
}

fn text_agent_hiddeh() -> bool {
Copy link
Owner

Choose a reason for hiding this comment

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

nit typo: text_agent_hidden

use wasm_bindgen::JsCast;
web_sys::window()
.unwrap()
.document()
.unwrap()
.get_element_by_id(AGENT_ID)
.unwrap()
.dyn_into::<web_sys::HtmlInputElement>()
.unwrap()
.hidden()
}

fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
use wasm_bindgen::JsCast;
let window = web_sys::window().unwrap();
Expand Down Expand Up @@ -485,7 +504,12 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
modifiers,
});
}
if !modifiers.ctrl && !modifiers.command && !should_ignore_key(&key) {
if !modifiers.ctrl
&& !modifiers.command
&& !should_ignore_key(&key)
// When text agent is shown, it sends text event instead.
&& text_agent_hiddeh()
{
runner_lock.input.raw.events.push(egui::Event::Text(key));
}
runner_lock.needs_repaint.set_true();
Expand Down Expand Up @@ -633,6 +657,97 @@ fn modifiers_from_event(event: &web_sys::KeyboardEvent) -> egui::Modifiers {
}
}

///
/// Text event handler,
fn install_text_agent(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
use wasm_bindgen::JsCast;
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().expect("document should have a body");
let input = document
.create_element("input")?
.dyn_into::<web_sys::HtmlInputElement>()?;
let input = std::rc::Rc::new(input);
input.set_id(AGENT_ID);
let is_composing = Rc::new(Cell::new(false));
{
let style = input.style();
// Transparent
style.set_property("opacity", "0").unwrap();
// Hide under canvas
style.set_property("z-index", "-1").unwrap();
}
// Set size as small as possible, in case user may click on it.
input.set_size(1);
input.set_autofocus(true);
input.set_hidden(true);
{
// When IME is off
let input_clone = input.clone();
let runner_ref = runner_ref.clone();
let is_composing = is_composing.clone();
let on_input = Closure::wrap(Box::new(move |_event: web_sys::InputEvent| {
let text = input_clone.value();
if !text.is_empty() && !is_composing.get() {
input_clone.set_value("");
let mut runner_lock = runner_ref.0.lock();
runner_lock.input.raw.events.push(egui::Event::Text(text));
runner_lock.needs_repaint.set_true();
}
}) as Box<dyn FnMut(_)>);
input.add_event_listener_with_callback("input", on_input.as_ref().unchecked_ref())?;
on_input.forget();
}
{
// When IME is on, handle composition event
let input_clone = input.clone();
let runner_ref = runner_ref.clone();
let on_compositionend = Closure::wrap(Box::new(move |event: web_sys::CompositionEvent| {
// let event_type = event.type_();
match event.type_().as_ref() {
"compositionstart" => {
is_composing.set(true);
input_clone.set_value("");
}
"compositionend" => {
is_composing.set(false);
input_clone.set_value("");
if let Some(text) = event.data() {
let mut runner_lock = runner_ref.0.lock();
runner_lock.input.raw.events.push(egui::Event::Text(text));
runner_lock.needs_repaint.set_true();
}
}
"compositionupdate" => {}
_s => panic!("Unknown type"),
}
}) as Box<dyn FnMut(_)>);
let f = on_compositionend.as_ref().unchecked_ref();
input.add_event_listener_with_callback("compositionstart", f)?;
input.add_event_listener_with_callback("compositionupdate", f)?;
input.add_event_listener_with_callback("compositionend", f)?;
on_compositionend.forget();
}
{
// When input lost focus, focus on it again.
// It is useful when user click somewhere outside canvas.
let on_focusout = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| {
// Delay 10 ms, and focus again.
let func = js_sys::Function::new_no_args(&format!(
"document.getElementById('{}').focus()",
AGENT_ID
));
window
.set_timeout_with_callback_and_timeout_and_arguments_0(&func, 10)
.unwrap();
}) as Box<dyn FnMut(_)>);
input.add_event_listener_with_callback("focusout", on_focusout.as_ref().unchecked_ref())?;
on_focusout.forget();
}
body.append_child(&input)?;
Ok(())
}

fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
use wasm_bindgen::JsCast;
let canvas = canvas_element(runner_ref.0.lock().canvas_id()).unwrap();
Expand Down Expand Up @@ -721,6 +836,7 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
event.stop_propagation();
event.prevent_default();
}
manipulate_agent(runner_lock.canvas_id(), runner_lock.input.latest_touch_pos);
}
}) as Box<dyn FnMut(_)>);
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
Expand All @@ -747,8 +863,8 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let event_name = "touchstart";
let runner_ref = runner_ref.clone();
let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| {
let pos = pos_from_touch_event(&event);
let mut runner_lock = runner_ref.0.lock();
let pos = pos_from_touch_event(runner_lock.canvas_id(), &event);
runner_lock.input.latest_touch_pos = Some(pos);
runner_lock.input.is_touch = true;
let modifiers = runner_lock.input.raw.modifiers;
Expand All @@ -774,8 +890,8 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let event_name = "touchmove";
let runner_ref = runner_ref.clone();
let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| {
let pos = pos_from_touch_event(&event);
let mut runner_lock = runner_ref.0.lock();
let pos = pos_from_touch_event(runner_lock.canvas_id(), &event);
runner_lock.input.latest_touch_pos = Some(pos);
runner_lock.input.is_touch = true;
runner_lock
Expand Down Expand Up @@ -815,6 +931,9 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
runner_lock.needs_repaint.set_true();
event.stop_propagation();
event.prevent_default();

// Finally, focus or blur on agent to toggle keyboard
manipulate_agent(runner_lock.canvas_id(), runner_lock.input.latest_touch_pos);
}
}) as Box<dyn FnMut(_)>);
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
Expand All @@ -838,3 +957,41 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {

Ok(())
}

fn manipulate_agent(canvas_id: &str, latest_cursor: Option<egui::Pos2>) -> Option<()> {
use wasm_bindgen::JsCast;
use web_sys::HtmlInputElement;
let window = web_sys::window()?;
let document = window.document()?;
let input: HtmlInputElement = document.get_element_by_id(AGENT_ID)?.dyn_into().unwrap();
let cutsor_txt = document.body()?.style().get_property_value("cursor").ok()?;
let style = canvas_element(canvas_id)?.style();
if cutsor_txt == cursor_web_name(egui::CursorIcon::Text) {
input.set_hidden(false);
input.focus().ok()?;
// Panning canvas so that text edit is shown at 30%
// Only on touch screens, when keyboard popups
if let Some(p) = latest_cursor {
let inner_height = window.inner_height().ok()?.as_f64()? as f32;
let current_rel = p.y / inner_height;

if current_rel > 0.5 {
// probably below the keyboard

let target_rel = 0.3;

let delta = target_rel - current_rel;
let new_pos_percent = (delta * 100.0).round().to_string() + "%";

style.set_property("position", "absolute").ok()?;
style.set_property("top", &new_pos_percent).ok()?;
}
}
} else {
input.blur().ok()?;
input.set_hidden(true);
style.set_property("position", "absolute").ok()?;
style.set_property("top", "0%").ok()?; // move back to normal position
}
Some(())
}