Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions crates/bevy_ui_widgets/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ bevy_input_focus = { path = "../bevy_input_focus", version = "0.20.0-dev", featu
bevy_log = { path = "../bevy_log", version = "0.20.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.20.0-dev" }
bevy_picking = { path = "../bevy_picking", version = "0.20.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.20.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.20.0-dev" }
bevy_ui = { path = "../bevy_ui", version = "0.20.0-dev" }
bevy_text = { path = "../bevy_text", version = "0.20.0-dev" }
Expand All @@ -32,6 +33,11 @@ accesskit = "0.24"
parley = { version = "0.11.0", default-features = false }
smol_str = "0.2"

# The shortcut layout (Cmd vs Ctrl) depends on the HOST os, which on wasm is
# only knowable at runtime -- see `mac_host` in text_input.
[target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { version = "0.3", features = ["Window", "Navigator"] }

[features]
default = []

Expand Down
118 changes: 75 additions & 43 deletions crates/bevy_ui_widgets/src/text_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,35 @@ const SUPER: u8 = 1;
const CTRL: u8 = 2;
const ALT: u8 = 4;
const SHIFT: u8 = 8;
const COMMAND: u8 = if cfg!(target_os = "macos") {
SUPER
} else {
CTRL
};
// Modifier key for word-level navigation and selection. Alt on macOS, Control otherwise.
const WORD: u8 = if cfg!(target_os = "macos") { ALT } else { CTRL };
const SHIFT_WORD: u8 = SHIFT | WORD;
#[cfg(target_os = "macos")]
const SHIFT_SUPER: u8 = SHIFT | SUPER;
const SHIFT_COMMAND: u8 = SHIFT | COMMAND;
#[cfg(not(target_os = "macos"))]
const SHIFT_ALT: u8 = SHIFT | ALT;

/// Whether shortcuts follow the macOS layout (Cmd as the command key, Option
/// for word navigation). Compile-time everywhere except wasm32, where one
/// binary serves every host OS and the browser has to be asked at runtime.
fn mac_host() -> bool {
#[cfg(not(target_arch = "wasm32"))]
{
cfg!(target_os = "macos")
}
#[cfg(target_arch = "wasm32")]
{
use bevy_platform::sync::OnceLock;
static MAC: OnceLock<bool> = OnceLock::new();
*MAC.get_or_init(|| {
web_sys::window().is_some_and(|w| {
let nav = w.navigator();
// platform() is deprecated but universally shipped; fall back
// to the UA string. "iP" covers iPhone/iPad with keyboards.
let platform = nav.platform().unwrap_or_default();
platform.starts_with("Mac")
|| platform.starts_with("iP")
|| (platform.is_empty() && nav.user_agent().unwrap_or_default().contains("Mac"))
})
})
}
}

/// Editable text widget.
#[derive(Component, Clone, Default, Reflect)]
#[require(EditableText, AccessibilityNode(accesskit::Node::new(Role::TextInput)))]
Expand Down Expand Up @@ -134,6 +149,16 @@ fn on_focused_keyboard_input(

let shift_pressed = (mod_flags & SHIFT) != 0;

// Runtime values rather than consts (and guards rather than patterns
// below): on wasm the layout depends on the HOST os, not the compile
// target. See `mac_host`.
let mac = mac_host();
let command = if mac { SUPER } else { CTRL };
// Modifier key for word-level navigation and selection. Alt on macOS, Control otherwise.
let word = if mac { ALT } else { CTRL };
let shift_command = SHIFT | command;
let shift_word = SHIFT | word;

let mut should_propagate = true;

let mut queue_edit = |edit: TextEdit| {
Expand All @@ -150,54 +175,61 @@ fn on_focused_keyboard_input(
(NONE, Key::Copy) => queue_edit(TextEdit::Copy),
(NONE, Key::Cut) => queue_edit(TextEdit::Cut),
(NONE, Key::Paste) => queue_edit(TextEdit::Paste),
(COMMAND, Key::Character(_))
if matches_edit_shortcut(&keyboard_input.input, "a", KeyCode::KeyA) =>
(m, Key::Character(_))
if m == command && matches_edit_shortcut(&keyboard_input.input, "a", KeyCode::KeyA) =>
{
queue_edit(TextEdit::SelectAll);
}
(COMMAND, Key::Character(_))
if matches_edit_shortcut(&keyboard_input.input, "c", KeyCode::KeyC) =>
(m, Key::Character(_))
if m == command && matches_edit_shortcut(&keyboard_input.input, "c", KeyCode::KeyC) =>
{
queue_edit(TextEdit::Copy);
}
(COMMAND, Key::Character(_))
if matches_edit_shortcut(&keyboard_input.input, "x", KeyCode::KeyX) =>
(m, Key::Character(_))
if m == command && matches_edit_shortcut(&keyboard_input.input, "x", KeyCode::KeyX) =>
{
queue_edit(TextEdit::Cut);
}
(COMMAND, Key::Character(_))
if matches_edit_shortcut(&keyboard_input.input, "v", KeyCode::KeyV) =>
(m, Key::Character(_))
if m == command && matches_edit_shortcut(&keyboard_input.input, "v", KeyCode::KeyV) =>
{
queue_edit(TextEdit::Paste);
}
#[cfg(not(target_os = "macos"))]
(SHIFT, Key::Delete) => queue_edit(TextEdit::Cut),
(WORD, Key::Backspace) => queue_edit(TextEdit::BackspaceWord),
(WORD, Key::Delete) => queue_edit(TextEdit::DeleteWord),
#[cfg(target_os = "macos")]
(SUPER | SHIFT_SUPER, Key::ArrowLeft) => queue_edit(TextEdit::HardLineStart(shift_pressed)),
#[cfg(target_os = "macos")]
(SUPER | SHIFT_SUPER, Key::ArrowRight) => queue_edit(TextEdit::HardLineEnd(shift_pressed)),
#[cfg(not(target_os = "macos"))]
(ALT | SHIFT_ALT, Key::Home) => queue_edit(TextEdit::HardLineStart(shift_pressed)),
#[cfg(not(target_os = "macos"))]
(ALT | SHIFT_ALT, Key::End) => queue_edit(TextEdit::HardLineEnd(shift_pressed)),
(WORD | SHIFT_WORD, Key::ArrowLeft) => queue_edit(TextEdit::WordLeft(shift_pressed)),
(WORD | SHIFT_WORD, Key::ArrowRight) => queue_edit(TextEdit::WordRight(shift_pressed)),
(SHIFT, Key::Delete) if !mac => queue_edit(TextEdit::Cut),
(m, Key::Backspace) if m == word => queue_edit(TextEdit::BackspaceWord),
(m, Key::Delete) if m == word => queue_edit(TextEdit::DeleteWord),
(SUPER | SHIFT_SUPER, Key::ArrowLeft) if mac => {
queue_edit(TextEdit::HardLineStart(shift_pressed));
}
(SUPER | SHIFT_SUPER, Key::ArrowRight) if mac => {
queue_edit(TextEdit::HardLineEnd(shift_pressed));
}
(ALT | SHIFT_ALT, Key::Home) if !mac => queue_edit(TextEdit::HardLineStart(shift_pressed)),
(ALT | SHIFT_ALT, Key::End) if !mac => queue_edit(TextEdit::HardLineEnd(shift_pressed)),
(m, Key::ArrowLeft) if m == word || m == shift_word => {
queue_edit(TextEdit::WordLeft(shift_pressed));
}
(m, Key::ArrowRight) if m == word || m == shift_word => {
queue_edit(TextEdit::WordRight(shift_pressed));
}
(NONE | SHIFT, Key::ArrowLeft) => queue_edit(TextEdit::Left(shift_pressed)),
(NONE | SHIFT, Key::ArrowRight) => queue_edit(TextEdit::Right(shift_pressed)),
#[cfg(target_os = "macos")]
(COMMAND | SHIFT_COMMAND, Key::ArrowUp) => queue_edit(TextEdit::TextStart(shift_pressed)),
#[cfg(target_os = "macos")]
(COMMAND | SHIFT_COMMAND, Key::ArrowDown) => queue_edit(TextEdit::TextEnd(shift_pressed)),
(m, Key::ArrowUp) if mac && (m == command || m == shift_command) => {
queue_edit(TextEdit::TextStart(shift_pressed));
}
(m, Key::ArrowDown) if mac && (m == command || m == shift_command) => {
queue_edit(TextEdit::TextEnd(shift_pressed));
}
(NONE | SHIFT, Key::ArrowUp) => queue_edit(TextEdit::Up(shift_pressed)),
(NONE | SHIFT, Key::ArrowDown) => queue_edit(TextEdit::Down(shift_pressed)),
#[cfg(not(target_os = "macos"))]
(CTRL, Key::ArrowUp) => queue_edit(TextEdit::ScrollByLines(-1.0)),
#[cfg(not(target_os = "macos"))]
(CTRL, Key::ArrowDown) => queue_edit(TextEdit::ScrollByLines(1.0)),
(COMMAND | SHIFT_COMMAND, Key::Home) => queue_edit(TextEdit::TextStart(shift_pressed)),
(COMMAND | SHIFT_COMMAND, Key::End) => queue_edit(TextEdit::TextEnd(shift_pressed)),
(CTRL, Key::ArrowUp) if !mac => queue_edit(TextEdit::ScrollByLines(-1.0)),
(CTRL, Key::ArrowDown) if !mac => queue_edit(TextEdit::ScrollByLines(1.0)),
(m, Key::Home) if m == command || m == shift_command => {
queue_edit(TextEdit::TextStart(shift_pressed));
}
(m, Key::End) if m == command || m == shift_command => {
queue_edit(TextEdit::TextEnd(shift_pressed));
}
(NONE | SHIFT, Key::Home) => queue_edit(TextEdit::LineStart(shift_pressed)),
(NONE | SHIFT, Key::End) => queue_edit(TextEdit::LineEnd(shift_pressed)),
(NONE, Key::Backspace) => queue_edit(TextEdit::Backspace),
Expand Down