use serde_json::{Value, json};
use super::keymap::Mods;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PointerKind {
Down,
Move,
Up,
Cancel,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Viewport {
pub left: f64,
pub top: f64,
pub width: f64,
pub height: f64,
pub zoom: f64,
pub dpr: f64,
}
impl Default for Viewport {
fn default() -> Viewport {
Viewport { left: 0.0, top: 0.0, width: 816.0, height: 1056.0, zoom: 1.0, dpr: 1.0 }
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Input {
Key {
code: String,
mods: Mods,
},
Text(String),
Pointer {
kind: PointerKind,
x: f64,
y: f64,
clicks: u8,
mods: Mods,
},
Hover {
x: f64,
y: f64,
},
Wheel {
dx: f64,
dy: f64,
},
Viewport(Viewport),
Command {
id: String,
arg: Option<Value>,
},
Copy,
Cut,
Paste {
text: Option<String>,
html: Option<String>,
token: Option<String>,
},
Focus(bool),
Undo,
Redo,
Platform {
mac: bool,
},
Query {
q: String,
args: Value,
},
Batch {
label: String,
inputs: Vec<Value>,
},
}
#[derive(Clone, Debug, PartialEq)]
pub struct Message {
pub seq: u64,
pub input: Input,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BadInput(pub String);
fn num(v: &Value, k: &str) -> Result<f64, BadInput> {
v.get(k).and_then(Value::as_f64).ok_or_else(|| BadInput(format!("missing number `{k}`")))
}
fn mods(v: &Value) -> Mods {
let m = v.get("mods");
let flag = |k| m.and_then(|m| m.get(k)).and_then(Value::as_bool).unwrap_or(false);
Mods { ctrl: flag("ctrl"), shift: flag("shift"), alt: flag("alt"), meta: flag("meta") }
}
impl Message {
pub fn parse(v: &Value) -> Result<Message, BadInput> {
let seq = v.get("seq").and_then(Value::as_u64).ok_or_else(|| BadInput("missing `seq`".into()))?;
let ty = v.get("type").and_then(Value::as_str).ok_or_else(|| BadInput("missing `type`".into()))?;
let s = |k: &str| v.get(k).and_then(Value::as_str).map(str::to_string);
let input = match ty {
"key" => Input::Key { code: s("code").ok_or_else(|| BadInput("missing `code`".into()))?, mods: mods(v) },
"text" => Input::Text(s("text").ok_or_else(|| BadInput("missing `text`".into()))?),
"pointer" => Input::Pointer {
kind: match s("kind").as_deref() {
Some("down") => PointerKind::Down,
Some("move") => PointerKind::Move,
Some("up") => PointerKind::Up,
Some("cancel") => PointerKind::Cancel,
k => return Err(BadInput(format!("pointer kind {k:?}"))),
},
x: num(v, "x")?,
y: num(v, "y")?,
clicks: v.get("clicks").and_then(Value::as_u64).unwrap_or(1).clamp(1, 3) as u8,
mods: mods(v),
},
"hover" => Input::Hover { x: num(v, "x")?, y: num(v, "y")? },
"wheel" => Input::Wheel { dx: num(v, "dx")?, dy: num(v, "dy")? },
"viewport" => Input::Viewport(Viewport {
left: num(v, "left")?,
top: num(v, "top")?,
width: num(v, "width")?,
height: num(v, "height")?,
zoom: v.get("zoom").and_then(Value::as_f64).unwrap_or(1.0),
dpr: v.get("dpr").and_then(Value::as_f64).unwrap_or(1.0),
}),
"command" => Input::Command { id: s("id").ok_or_else(|| BadInput("missing `id`".into()))?, arg: v.get("arg").cloned() },
"copy" => Input::Copy,
"cut" => Input::Cut,
"paste" => Input::Paste { text: s("text"), html: s("html"), token: s("token") },
"focus" => Input::Focus(v.get("has_focus").and_then(Value::as_bool).unwrap_or(true)),
"platform" => Input::Platform { mac: v.get("mac").and_then(Value::as_bool).unwrap_or(false) },
"undo" => Input::Undo,
"redo" => Input::Redo,
"query" => Input::Query { q: s("q").ok_or_else(|| BadInput("missing `q`".into()))?, args: v.clone() },
"batch" => Input::Batch {
label: s("label").unwrap_or_else(|| "Batch".into()),
inputs: v.get("inputs").and_then(Value::as_array).cloned().ok_or_else(|| BadInput("missing `inputs`".into()))?,
},
"composition" => return Err(BadInput("composition input is not supported; send `text` on compositionend".into())),
t => return Err(BadInput(format!("unknown input type `{t}`"))),
};
Ok(Message { seq, input })
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Request {
ScrollIntoView {
page: usize,
rect: [f64; 4],
},
Autoscroll {
vx: f64,
vy: f64,
},
CapturePointer,
ReleasePointer,
Cursor(&'static str),
Clipboard {
text: String,
html: Option<String>,
},
Placeholder(super::placeholders::PlaceholderInfo),
}
impl Request {
pub fn to_json(&self) -> Value {
match self {
Request::ScrollIntoView { page, rect } => json!({ "type": "scroll_into_view", "page": page, "rect": rect }),
Request::Autoscroll { vx, vy } => json!({ "type": "autoscroll", "vx": vx, "vy": vy }),
Request::CapturePointer => json!({ "type": "capture_pointer" }),
Request::ReleasePointer => json!({ "type": "release_pointer" }),
Request::Cursor(c) => json!({ "type": "cursor", "value": c }),
Request::Clipboard { text, html } => json!({ "type": "clipboard", "text": text, "html": html }),
Request::Placeholder(p) => json!({ "type": "placeholder", "placeholder": p.to_json() }),
}
}
}