use euclid::Point2D;
use iced::advanced::input_method;
use iced::keyboard::{self, key as ice_key};
use iced::mouse;
use iced::touch;
use iced::window;
use iced::{Event, Point, Rectangle};
use servo::{
CSSPixel, Code as ServoCode, CompositionEvent, CompositionState, ImeEvent, InputEvent,
Key as ServoKey, KeyState, KeyboardEvent, Location as ServoLocation,
Modifiers as ServoModifiers, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent,
NamedKey as ServoNamedKey, TouchEvent, TouchEventType, TouchId, WebView, WebViewPoint,
WheelDelta, WheelEvent, WheelMode,
};
use crate::controller::ServoWebViewController;
pub(crate) fn translate_event(
event: &Event,
bounds: Rectangle,
cursor: mouse::Cursor,
focused: bool,
controller: &ServoWebViewController,
) -> bool {
let webview = controller.webview();
let scale = controller.scale_factor();
match event {
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
let Some(local) = cursor.position_in(bounds) else {
return false;
};
webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent::new(web_point(
local, scale,
))));
true
}
Event::Mouse(mouse::Event::CursorLeft) => {
webview.notify_input_event(InputEvent::MouseLeftViewport(
servo::MouseLeftViewportEvent::default(),
));
true
}
Event::Mouse(mouse::Event::ButtonPressed(btn)) => {
if matches!(btn, mouse::Button::Back) {
controller.go_back();
return true;
}
if matches!(btn, mouse::Button::Forward) {
controller.go_forward();
return true;
}
let Some(local) = cursor.position_in(bounds) else {
return false;
};
webview.notify_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
MouseButtonAction::Down,
iced_button_to_servo(*btn),
web_point(local, scale),
)));
true
}
Event::Mouse(mouse::Event::ButtonReleased(btn)) => {
if matches!(btn, mouse::Button::Back | mouse::Button::Forward) {
return true;
}
let local = cursor
.position_in(bounds)
.or_else(|| {
cursor
.position()
.map(|p| Point::new(p.x - bounds.x, p.y - bounds.y))
})
.unwrap_or(Point::ORIGIN);
webview.notify_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
MouseButtonAction::Up,
iced_button_to_servo(*btn),
web_point(local, scale),
)));
true
}
Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
let Some(local) = cursor.position_in(bounds) else {
return false;
};
let (dx, dy, mode) = iced_wheel_delta(*delta);
webview.notify_input_event(InputEvent::Wheel(WheelEvent::new(
WheelDelta {
x: dx,
y: dy,
z: 0.0,
mode,
},
web_point(local, scale),
)));
true
}
Event::Keyboard(kb) if focused => translate_keyboard(kb, webview),
Event::Touch(t) => translate_touch(t, bounds, webview),
Event::InputMethod(ime) if focused => translate_input_method(ime, webview),
Event::Window(window::Event::Focused) => {
webview.focus();
true
}
Event::Window(window::Event::Unfocused) => {
webview.blur();
true
}
_ => false,
}
}
fn translate_touch(event: &touch::Event, bounds: Rectangle, webview: &WebView) -> bool {
let (event_type, id, position) = match event {
touch::Event::FingerPressed { id, position } => (TouchEventType::Down, *id, *position),
touch::Event::FingerMoved { id, position } => (TouchEventType::Move, *id, *position),
touch::Event::FingerLifted { id, position } => (TouchEventType::Up, *id, *position),
touch::Event::FingerLost { id, position } => (TouchEventType::Cancel, *id, *position),
};
let inside = position.x >= bounds.x
&& position.y >= bounds.y
&& position.x <= bounds.x + bounds.width
&& position.y <= bounds.y + bounds.height;
if matches!(event_type, TouchEventType::Down) && !inside {
return false;
}
let local = Point::new(position.x - bounds.x, position.y - bounds.y);
let touch_id = TouchId(id.0 as i32);
webview.notify_input_event(InputEvent::Touch(TouchEvent::new(
event_type,
touch_id,
web_point(local, 1.0),
)));
true
}
fn translate_input_method(event: &input_method::Event, webview: &WebView) -> bool {
let servo_event = match event {
input_method::Event::Opened => ImeEvent::Composition(CompositionEvent {
state: CompositionState::Start,
data: String::new(),
}),
input_method::Event::Preedit(text, _range) => ImeEvent::Composition(CompositionEvent {
state: CompositionState::Update,
data: text.clone(),
}),
input_method::Event::Commit(text) => ImeEvent::Composition(CompositionEvent {
state: CompositionState::End,
data: text.clone(),
}),
input_method::Event::Closed => ImeEvent::Dismissed,
};
webview.notify_input_event(InputEvent::Ime(servo_event));
true
}
fn translate_keyboard(event: &keyboard::Event, webview: &WebView) -> bool {
let (state, key, modified_key, physical_key, location, modifiers, repeat) = match event {
keyboard::Event::KeyPressed {
key,
modified_key,
physical_key,
location,
modifiers,
..
} => (
KeyState::Down,
key,
modified_key,
physical_key,
location,
modifiers,
false, ),
keyboard::Event::KeyReleased {
key,
modified_key,
physical_key,
location,
modifiers,
..
} => (
KeyState::Up,
key,
modified_key,
physical_key,
location,
modifiers,
false,
),
keyboard::Event::ModifiersChanged(_) => return false,
};
let servo_key = iced_key_to_servo_key(modified_key, key);
let servo_code = iced_physical_to_servo_code(physical_key);
let servo_location = iced_location_to_servo(*location);
let servo_modifiers = iced_modifiers_to_servo(*modifiers);
webview.notify_input_event(InputEvent::Keyboard(KeyboardEvent::new_without_event(
state,
servo_key,
servo_code,
servo_location,
servo_modifiers,
repeat,
false,
)));
true
}
fn iced_key_to_servo_key(modified: &ice_key::Key, base: &ice_key::Key) -> ServoKey {
match modified {
ice_key::Key::Character(s) => ServoKey::Character(s.to_string()),
ice_key::Key::Named(named) => named_to_servo(*named),
ice_key::Key::Unidentified => match base {
ice_key::Key::Character(s) => ServoKey::Character(s.to_string()),
ice_key::Key::Named(named) => named_to_servo(*named),
ice_key::Key::Unidentified => ServoKey::Named(ServoNamedKey::Unidentified),
},
}
}
fn named_to_servo(named: ice_key::Named) -> ServoKey {
if named == ice_key::Named::Space {
return ServoKey::Character(" ".into());
}
let debug_name = format!("{named:?}");
debug_name
.parse::<ServoNamedKey>()
.map(ServoKey::Named)
.unwrap_or(ServoKey::Named(ServoNamedKey::Unidentified))
}
fn iced_physical_to_servo_code(physical: &ice_key::Physical) -> ServoCode {
match physical {
ice_key::Physical::Code(code) => {
let debug_name = format!("{code:?}");
debug_name
.parse::<ServoCode>()
.unwrap_or(ServoCode::Unidentified)
}
ice_key::Physical::Unidentified(_) => ServoCode::Unidentified,
}
}
fn iced_location_to_servo(loc: keyboard::Location) -> ServoLocation {
match loc {
keyboard::Location::Standard => ServoLocation::Standard,
keyboard::Location::Left => ServoLocation::Left,
keyboard::Location::Right => ServoLocation::Right,
keyboard::Location::Numpad => ServoLocation::Numpad,
}
}
fn iced_modifiers_to_servo(mods: keyboard::Modifiers) -> ServoModifiers {
let mut out = ServoModifiers::empty();
if mods.shift() {
out |= ServoModifiers::SHIFT;
}
if mods.control() {
out |= ServoModifiers::CONTROL;
}
if mods.alt() {
out |= ServoModifiers::ALT;
}
if mods.logo() {
out |= ServoModifiers::META;
}
out
}
fn web_point(local: Point, _scale: f32) -> WebViewPoint {
WebViewPoint::from(Point2D::<f32, CSSPixel>::new(local.x, local.y))
}
fn iced_button_to_servo(button: mouse::Button) -> MouseButton {
match button {
mouse::Button::Left => MouseButton::Left,
mouse::Button::Right => MouseButton::Right,
mouse::Button::Middle => MouseButton::Middle,
mouse::Button::Back => MouseButton::Back,
mouse::Button::Forward => MouseButton::Forward,
mouse::Button::Other(n) => MouseButton::Other(n),
}
}
fn iced_wheel_delta(delta: mouse::ScrollDelta) -> (f64, f64, WheelMode) {
match delta {
mouse::ScrollDelta::Lines { x, y } => {
(x as f64 * 76.0, y as f64 * 76.0, WheelMode::DeltaPixel)
}
mouse::ScrollDelta::Pixels { x, y } => (x as f64, y as f64, WheelMode::DeltaPixel),
}
}
#[allow(dead_code)]
pub(crate) fn forward_raw(webview: &WebView, event: InputEvent) {
let _ = webview.notify_input_event(event);
}
pub(crate) fn cursor_to_interaction(cursor: servo::Cursor) -> mouse::Interaction {
use mouse::Interaction::*;
use servo::Cursor;
match cursor {
Cursor::None => Hidden,
Cursor::Default => None,
Cursor::Pointer => Pointer,
Cursor::Text | Cursor::VerticalText => Text,
Cursor::Wait => Wait,
Cursor::Progress => Progress,
Cursor::Crosshair => Crosshair,
Cursor::Move | Cursor::AllScroll => Move,
Cursor::Grab => Grab,
Cursor::Grabbing => Grabbing,
Cursor::NotAllowed => NotAllowed,
Cursor::NoDrop => NoDrop,
Cursor::Help => Help,
Cursor::ZoomIn => ZoomIn,
Cursor::ZoomOut => ZoomOut,
Cursor::Cell => Cell,
Cursor::ContextMenu => ContextMenu,
Cursor::Alias => Alias,
Cursor::Copy => Copy,
Cursor::ColResize => ResizingColumn,
Cursor::RowResize => ResizingRow,
Cursor::EResize | Cursor::WResize | Cursor::EwResize => ResizingHorizontally,
Cursor::NResize | Cursor::SResize | Cursor::NsResize => ResizingVertically,
Cursor::NeResize | Cursor::SwResize | Cursor::NeswResize => ResizingDiagonallyUp,
Cursor::NwResize | Cursor::SeResize | Cursor::NwseResize => ResizingDiagonallyDown,
}
}