// Copyright 2026 Ling71671
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// Translate a DOM `KeyboardEvent.key` value into a library `Key`.
///
/// Only named keys are mapped: printable characters travel through the text
/// channel (`Event::TextInput`) instead, which keeps this table small and lets
/// the browser handle layout, dead keys and IME composition.
fn host_key(name : String) -> @core.Key? {
match name {
"Tab" => Some(@core.Key::Tab)
"Enter" => Some(@core.Key::Enter)
"Escape" => Some(@core.Key::Escape)
" " => Some(@core.Key::Space)
"ArrowUp" => Some(@core.Key::ArrowUp)
"ArrowDown" => Some(@core.Key::ArrowDown)
"ArrowLeft" => Some(@core.Key::ArrowLeft)
"ArrowRight" => Some(@core.Key::ArrowRight)
"PageUp" => Some(@core.Key::PageUp)
"PageDown" => Some(@core.Key::PageDown)
"Home" => Some(@core.Key::Home)
"End" => Some(@core.Key::End)
"Backspace" => Some(@core.Key::Backspace)
"Delete" => Some(@core.Key::Delete)
_ => None
}
}
///|
/// Build the input payload for one frame from the primitives the host driver
/// collects. Keeping the boundary to strings, booleans and numbers avoids
/// depending on the mangled names of library constructors from JavaScript.
fn host_raw_input(
mouse_x : Double,
mouse_y : Double,
mouse_down : Bool,
text_input : String,
keys_pressed : Array[String],
keys_released : Array[String],
mod_flags : Int,
scroll_dy? : Double = 0.0,
mouse_secondary_down? : Bool = false,
) -> @core.RawInput {
// mod_flags: bit 0 shift, bit 1 ctrl, bit 2 alt, bit 3 meta
let mods = @core.Modifiers::new(
(mod_flags & 2) != 0,
(mod_flags & 1) != 0,
(mod_flags & 4) != 0,
(mod_flags & 8) != 0,
)
let events : Array[@core.Event] = []
for name in keys_pressed {
match host_key(name) {
Some(key) => events.push(@core.Event::Key(key, true, mods))
None => ()
}
}
for name in keys_released {
match host_key(name) {
Some(key) => events.push(@core.Event::Key(key, false, mods))
None => ()
}
}
// Text goes through the event channel, not the `text_input` field: the field
// is overwritten every frame while events append to whatever is pending, so
// typing several characters inside one frame is preserved.
if !text_input.is_empty() {
events.push(@core.Event::TextInput(text_input))
}
@core.RawInput::with_events(
@math.Vec2::new(mouse_x, mouse_y),
mouse_down,
@math.Vec2::new(0.0, scroll_dy),
events,
"",
mods,
mouse_secondary_down~,
)
}
///|
/// Parse a non-negative decimal index. Returns None for anything else, so a
/// half-typed field never jumps somewhere surprising.
fn parse_index(text : String) -> Int? {
if text.is_empty() {
return None
}
let mut value = 0
for i = 0; i < text.length(); i = i + 1 {
let ch = text[i]
if ch < '0' || ch > '9' {
return None
}
value = value * 10 + (ch.to_int() - 48)
}
Some(value)
}