asdf-overlay-node 1.2.2

Asdf Overlay Node Addon
use core::num::NonZeroU32;

use asdf_overlay_client::{
    common::{request::UpdateSharedHandle, size::PercentLength},
    event::{
        GpuLuid, OverlayEvent, WindowEvent,
        input::{
            CursorAction, CursorEvent, CursorInput, CursorInputState, Ime, ImeCandidateList,
            InputEvent, Key, KeyInputState, KeyboardInput, ScrollAxis,
        },
    },
    ty::{CopyRect, Rect},
};
use neon::{
    handle::Handle,
    object::Object,
    prelude::{Context, Cx},
    result::{JsResult, NeonResult},
    types::{JsBoolean, JsFunction, JsNumber, JsObject, JsString},
};

pub fn emit_event<'a>(
    cx: &mut Cx<'a>,
    event: OverlayEvent,
    emitter: Handle<'a, JsObject>,
    emit: Handle<'a, JsFunction>,
) -> NeonResult<()> {
    let mut call_options = emit.call_with(cx);
    let builder = call_options.this(emitter);

    match event {
        OverlayEvent::Window { id, event } => match event {
            WindowEvent::Added {
                width,
                height,
                gpu_id,
            } => {
                builder
                    .arg(cx.string("added"))
                    .arg(cx.number(id))
                    .arg(cx.number(width))
                    .arg(cx.number(height))
                    .arg(serialize_gpu_luid(cx, gpu_id)?);
            }
            WindowEvent::Resized { width, height } => {
                builder
                    .arg(cx.string("resized"))
                    .arg(cx.number(id))
                    .arg(cx.number(width))
                    .arg(cx.number(height));
            }
            WindowEvent::Input(event) => match event {
                InputEvent::Cursor(input) => {
                    builder
                        .arg(cx.string("cursor_input"))
                        .arg(cx.number(id))
                        .arg(serialize_cursor_input(cx, input)?);
                }
                InputEvent::Keyboard(input) => {
                    builder
                        .arg(cx.string("keyboard_input"))
                        .arg(cx.number(id))
                        .arg(serialize_keyboard_input(cx, input)?);
                }
            },
            WindowEvent::InputBlockingEnded => {
                builder
                    .arg(cx.string("input_blocking_ended"))
                    .arg(cx.number(id));
            }
            WindowEvent::Destroyed => {
                builder.arg(cx.string("destroyed")).arg(cx.number(id));
            }
        },
    }

    builder.exec(cx)
}

fn serialize_keyboard_input<'a>(cx: &mut Cx<'a>, input: KeyboardInput) -> JsResult<'a, JsObject> {
    let obj = cx.empty_object();

    match input {
        KeyboardInput::Key { key, state } => {
            let kind = cx.string("Key");
            obj.prop(cx, "kind").set(kind)?;

            let key = serialize_key(cx, key)?;
            obj.prop(cx, "key").set(key)?;

            let state = serialize_key_input_state(cx, state);
            obj.prop(cx, "state").set(state)?;
        }
        KeyboardInput::Char(ch) => {
            let kind = cx.string("Char");
            obj.prop(cx, "kind").set(kind)?;

            let mut buf = [0_u8; 4];
            let ch = cx.string(ch.encode_utf8(&mut buf));
            obj.prop(cx, "ch").set(ch)?;
        }
        KeyboardInput::Ime(ime) => {
            let kind = cx.string("Ime");
            obj.prop(cx, "kind").set(kind)?;

            let ime = serialize_ime(cx, ime)?;
            obj.prop(cx, "ime").set(ime)?;
        }
    }

    Ok(obj)
}

fn serialize_cursor_input<'a>(cx: &mut Cx<'a>, input: CursorInput) -> JsResult<'a, JsObject> {
    let obj = cx.empty_object();

    let client_x = cx.number(input.client.x);
    obj.prop(cx, "clientX").set(client_x)?;

    let client_y = cx.number(input.client.y);
    obj.prop(cx, "clientY").set(client_y)?;

    let window_x = cx.number(input.client.x);
    obj.prop(cx, "windowX").set(window_x)?;

    let window_y = cx.number(input.client.y);
    obj.prop(cx, "windowY").set(window_y)?;

    match input.event {
        CursorEvent::Enter => {
            let kind = cx.string("Enter");
            obj.prop(cx, "kind").set(kind)?;
        }
        CursorEvent::Leave => {
            let kind = cx.string("Leave");
            obj.prop(cx, "kind").set(kind)?;
        }
        CursorEvent::Action { state, action } => {
            let kind = cx.string("Action");
            obj.prop(cx, "kind").set(kind)?;

            let (state, double_click) = serialize_cursor_input_state(cx, state);
            obj.prop(cx, "state").set(state)?;
            obj.prop(cx, "doubleClick").set(double_click)?;

            let action = match action {
                CursorAction::Left => cx.string("Left"),
                CursorAction::Right => cx.string("Right"),
                CursorAction::Middle => cx.string("Middle"),
                CursorAction::Back => cx.string("Back"),
                CursorAction::Forward => cx.string("Forward"),
            };
            obj.prop(cx, "action").set(action)?;
        }
        CursorEvent::Move => {
            let kind = cx.string("Move");
            obj.prop(cx, "kind").set(kind)?;
        }
        CursorEvent::Scroll { axis, delta } => {
            let kind = cx.string("Scroll");
            obj.prop(cx, "kind").set(kind)?;

            let axis = match axis {
                ScrollAxis::X => cx.string("X"),
                ScrollAxis::Y => cx.string("Y"),
            };
            obj.prop(cx, "axis").set(axis)?;

            let delta = cx.number(delta);
            obj.prop(cx, "delta").set(delta)?;
        }
    }

    Ok(obj)
}

fn serialize_gpu_luid<'a>(cx: &mut Cx<'a>, id: GpuLuid) -> JsResult<'a, JsObject> {
    let obj = cx.empty_object();

    let low = cx.number(id.low);
    obj.prop(cx, "low").set(low)?;

    let high = cx.number(id.high);
    obj.prop(cx, "high").set(high)?;

    Ok(obj)
}

pub fn deserialize_gpu_luid<'a>(cx: &mut Cx<'a>, obj: &JsObject) -> NeonResult<GpuLuid> {
    let low = obj.prop(cx, "low").get::<f64>()? as u32;
    let high = obj.prop(cx, "high").get::<f64>()? as i32;

    Ok(GpuLuid { low, high })
}

pub fn serialize_handle_update<'a>(
    cx: &mut Cx<'a>,
    update: UpdateSharedHandle,
) -> JsResult<'a, JsObject> {
    let obj = cx.empty_object();

    if let Some(handle) = update.handle {
        obj.prop(cx, "handle").set(handle.get())?;
    }

    Ok(obj)
}

pub fn deserialize_handle_update<'a>(
    cx: &mut Cx<'a>,
    obj: &JsObject,
) -> NeonResult<UpdateSharedHandle> {
    let handle = obj
        .prop(cx, "handle")
        .get::<Option<f64>>()?
        .and_then(|v| NonZeroU32::new(v as u32));
    Ok(UpdateSharedHandle { handle })
}

fn serialize_key_input_state<'a>(cx: &mut Cx<'a>, state: KeyInputState) -> Handle<'a, JsString> {
    match state {
        KeyInputState::Pressed => cx.string("Pressed"),
        KeyInputState::Released => cx.string("Released"),
    }
}

fn serialize_cursor_input_state<'a>(
    cx: &mut Cx<'a>,
    state: CursorInputState,
) -> (Handle<'a, JsString>, Handle<'a, JsBoolean>) {
    match state {
        CursorInputState::Pressed { double_click } => {
            (cx.string("Pressed"), cx.boolean(double_click))
        }
        CursorInputState::Released => (cx.string("Released"), cx.boolean(false)),
    }
}

fn serialize_key<'a>(cx: &mut Cx<'a>, key: Key) -> JsResult<'a, JsObject> {
    let obj = cx.empty_object();

    let code = cx.number(key.code.get());
    obj.prop(cx, "code").set(code)?;

    let extended = cx.boolean(key.extended);
    obj.prop(cx, "extended").set(extended)?;

    Ok(obj)
}

fn serialize_ime<'a>(cx: &mut Cx<'a>, ime: Ime) -> JsResult<'a, JsObject> {
    let obj = cx.empty_object();

    let kind = match ime {
        Ime::Enabled { lang, conversion } => {
            let lang = cx.string(lang);
            obj.prop(cx, "lang").set(lang)?;

            let conversion = cx.number(conversion.bits());
            obj.prop(cx, "conversion").set(conversion)?;

            cx.string("Enabled")
        }
        Ime::Changed(lang) => {
            let lang = cx.string(lang);
            obj.prop(cx, "lang").set(lang)?;

            cx.string("Changed")
        }
        Ime::ConversionChanged(conversion) => {
            let conversion = cx.number(conversion.bits());
            obj.prop(cx, "conversion").set(conversion)?;

            cx.string("ConversionChanged")
        }
        Ime::CandidateChanged(candidate_list) => {
            let candidate_list = serialize_candidate_list(cx, candidate_list)?;
            obj.prop(cx, "list").set(candidate_list)?;

            cx.string("CandidateChanged")
        }
        Ime::CandidateClosed => cx.string("CandidateClosed"),
        Ime::Compose { text, caret } => {
            let text = cx.string(text);
            obj.prop(cx, "text").set(text)?;

            let caret = cx.number(caret as f64);
            obj.prop(cx, "caret").set(caret)?;

            cx.string("Compose")
        }
        Ime::Commit(text) => {
            let text = cx.string(text);
            obj.prop(cx, "text").set(text)?;

            cx.string("Commit")
        }
        Ime::Disabled => cx.string("Disabled"),
    };
    obj.prop(cx, "kind").set(kind)?;

    Ok(obj)
}

fn serialize_candidate_list<'a>(
    cx: &mut Cx<'a>,
    candidate_list: ImeCandidateList,
) -> JsResult<'a, JsObject> {
    let obj = cx.empty_object();

    let page_start_index = cx.number(candidate_list.page_start_index);
    obj.prop(cx, "pageStartIndex").set(page_start_index)?;

    let page_size = cx.number(candidate_list.page_size);
    obj.prop(cx, "pageSize").set(page_size)?;

    let selected_index = cx.number(candidate_list.selected_index);
    obj.prop(cx, "selectedIndex").set(selected_index)?;

    let candidates = {
        let list = cx.empty_array();
        for (i, candidate) in candidate_list.candidates.iter().enumerate() {
            let candidate = cx.string(candidate);
            list.prop(cx, i as u32).set(candidate)?;
        }
        list
    };
    obj.prop(cx, "candidates").set(candidates)?;

    Ok(obj)
}

pub fn deserialize_percent_length<'a>(
    cx: &mut Cx<'a>,
    obj: &Handle<'a, JsObject>,
) -> NeonResult<PercentLength> {
    let ty = obj.get::<JsString, _, _>(cx, "ty")?.value(cx);
    let value = obj.get::<JsNumber, _, _>(cx, "value")?.value(cx);

    match ty.as_str() {
        "percent" => Ok(PercentLength::Percent(value as _)),
        "length" => Ok(PercentLength::Length(value as _)),

        _ => cx.throw_error("invalid PercentLength type"),
    }
}

pub fn deserialize_copy_rect<'a>(
    cx: &mut Cx<'a>,
    obj: &Handle<'a, JsObject>,
) -> NeonResult<CopyRect> {
    let dst_x = obj.get::<JsNumber, _, _>(cx, "dstX")?.value(cx) as u32;
    let dst_y = obj.get::<JsNumber, _, _>(cx, "dstY")?.value(cx) as u32;
    let src = obj.get::<JsObject, _, _>(cx, "src")?;

    Ok(CopyRect {
        dst_x,
        dst_y,
        src: deserialize_rect(cx, &src)?,
    })
}

fn deserialize_rect<'a>(cx: &mut Cx<'a>, obj: &Handle<'a, JsObject>) -> NeonResult<Rect> {
    let x = obj.get::<JsNumber, _, _>(cx, "x")?.value(cx) as u32;
    let y = obj.get::<JsNumber, _, _>(cx, "y")?.value(cx) as u32;
    let width = obj.get::<JsNumber, _, _>(cx, "width")?.value(cx) as u32;
    let height = obj.get::<JsNumber, _, _>(cx, "height")?.value(cx) as u32;

    Ok(Rect {
        x,
        y,
        width,
        height,
    })
}