Skip to main content

native/guppy_nif/src/bridge_view/events.rs

use crate::{
    ir::ShortcutBinding,
    native_events::{
        self, ActionEventPayload, ContextMenuEventPayload, DragMoveEventPayload, EventModifiers,
        KeyEventPayload, MouseDownEventPayload, MouseMoveEventPayload, MouseUpEventPayload,
        ScrollWheelEventPayload,
    },
};
use gpui::{
    KeyDownEvent, KeyUpEvent, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
    Pixels, ScrollDelta, ScrollWheelEvent,
};

#[derive(Clone, Debug)]
pub(crate) struct BridgeDragState {
    pub source_id: String,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct RowControlEventContext {
    pub node_id: String,
    pub list_id: String,
    pub row_id: String,
    pub control_id: String,
}

pub(crate) fn emit_click(view_id: u64, node_id: &str, callback_id: &str) {
    let _ = native_events::send_click_event(view_id, node_id, callback_id);
}

pub(crate) fn emit_row_control_click(
    view_id: u64,
    context: &RowControlEventContext,
    callback_id: &str,
) {
    let _ = native_events::send_row_control_click_event(
        view_id,
        &context.node_id,
        callback_id,
        &context.list_id,
        &context.row_id,
        &context.control_id,
    );
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn emit_data_table_event(
    view_id: u64,
    event_code: i32,
    node_id: &str,
    callback_id: &str,
    table_id: &str,
    row_id: Option<&str>,
    column_id: Option<&str>,
) {
    let _ = native_events::send_data_table_event(
        view_id,
        event_code,
        node_id,
        callback_id,
        table_id,
        row_id,
        column_id,
    );
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn emit_data_table_column_reorder(
    view_id: u64,
    node_id: &str,
    callback_id: &str,
    table_id: &str,
    column_id: &str,
    target_column_id: &str,
    direction: &str,
) {
    let _ = native_events::send_data_table_column_reorder_event(
        view_id,
        node_id,
        callback_id,
        table_id,
        column_id,
        target_column_id,
        direction,
    );
}

pub(crate) fn emit_data_table_column_resize(
    view_id: u64,
    node_id: &str,
    callback_id: &str,
    table_id: &str,
    column_id: &str,
    width_delta: i32,
) {
    let _ = native_events::send_data_table_column_resize_event(
        view_id,
        node_id,
        callback_id,
        table_id,
        column_id,
        width_delta,
    );
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn emit_data_table_context_menu(
    view_id: u64,
    node_id: &str,
    callback_id: &str,
    table_id: &str,
    row_id: &str,
    column_id: Option<&str>,
    event: &MouseDownEvent,
) {
    let _ = native_events::send_data_table_context_menu_event(
        view_id,
        node_id,
        callback_id,
        table_id,
        row_id,
        column_id,
        ContextMenuEventPayload {
            x: pixel_to_f64(event.position.x),
            y: pixel_to_f64(event.position.y),
            modifiers: modifier_flags(&event.modifiers),
        },
    );
}

pub(crate) fn emit_data_table_keyboard_context_menu(
    view_id: u64,
    node_id: &str,
    callback_id: &str,
    table_id: &str,
    row_id: &str,
    column_id: Option<&str>,
    event: &KeyDownEvent,
) {
    let _ = native_events::send_data_table_context_menu_event(
        view_id,
        node_id,
        callback_id,
        table_id,
        row_id,
        column_id,
        ContextMenuEventPayload {
            x: 0.0,
            y: 0.0,
            modifiers: modifier_flags(&event.keystroke.modifiers),
        },
    );
}

pub(crate) fn emit_tree_event(
    view_id: u64,
    event_code: i32,
    node_id: &str,
    callback_id: &str,
    tree_id: &str,
    item_id: &str,
) {
    let _ =
        native_events::send_tree_event(view_id, event_code, node_id, callback_id, tree_id, item_id);
}

pub(crate) fn emit_tree_context_menu(
    view_id: u64,
    node_id: &str,
    callback_id: &str,
    tree_id: &str,
    item_id: &str,
    event: &MouseDownEvent,
) {
    let _ = native_events::send_tree_context_menu_event(
        view_id,
        node_id,
        callback_id,
        tree_id,
        item_id,
        ContextMenuEventPayload {
            x: pixel_to_f64(event.position.x),
            y: pixel_to_f64(event.position.y),
            modifiers: modifier_flags(&event.modifiers),
        },
    );
}

pub(crate) fn emit_tree_keyboard_context_menu(
    view_id: u64,
    node_id: &str,
    callback_id: &str,
    tree_id: &str,
    item_id: &str,
    event: &KeyDownEvent,
) {
    let _ = native_events::send_tree_context_menu_event(
        view_id,
        node_id,
        callback_id,
        tree_id,
        item_id,
        ContextMenuEventPayload {
            x: 0.0,
            y: 0.0,
            modifiers: modifier_flags(&event.keystroke.modifiers),
        },
    );
}

pub(crate) fn emit_row_control_change(
    view_id: u64,
    context: &RowControlEventContext,
    callback_id: &str,
    value: &str,
) {
    let _ = native_events::send_row_control_change_event(
        view_id,
        &context.node_id,
        callback_id,
        &context.list_id,
        &context.row_id,
        &context.control_id,
        value,
    );
}

pub(crate) fn emit_row_control_checkbox_change(
    view_id: u64,
    context: &RowControlEventContext,
    callback_id: &str,
    checked: bool,
) {
    let _ = native_events::send_row_control_checkbox_change_event(
        view_id,
        &context.node_id,
        callback_id,
        &context.list_id,
        &context.row_id,
        &context.control_id,
        checked,
    );
}

pub(crate) fn emit_close(view_id: u64, node_id: &str, callback_id: &str) {
    let _ = native_events::send_close_event(view_id, node_id, callback_id);
}

pub(crate) fn emit_hover(view_id: u64, node_id: &str, callback_id: &str, hovered: bool) {
    let _ = native_events::send_hover_event(view_id, node_id, callback_id, hovered);
}

pub(crate) fn emit_focus(view_id: u64, node_id: &str, callback_id: &str) {
    let _ = native_events::send_focus_event(view_id, node_id, callback_id);
}

pub(crate) fn emit_blur(view_id: u64, node_id: &str, callback_id: &str) {
    let _ = native_events::send_blur_event(view_id, node_id, callback_id);
}

pub(crate) fn emit_change(view_id: u64, node_id: &str, callback_id: &str, value: &str) {
    let _ = native_events::send_change_event(view_id, node_id, callback_id, value);
}

pub(crate) fn emit_key_down(view_id: u64, node_id: &str, callback_id: &str, event: &KeyDownEvent) {
    let _ = native_events::send_key_down_event(
        view_id,
        node_id,
        callback_id,
        KeyEventPayload {
            key: event.keystroke.key.as_str(),
            key_char: key_char(event.keystroke.key_char.as_ref()),
            is_held: event.is_held,
            modifiers: modifier_flags(&event.keystroke.modifiers),
        },
    );
}

pub(crate) fn emit_key_up(view_id: u64, node_id: &str, callback_id: &str, event: &KeyUpEvent) {
    let _ = native_events::send_key_up_event(
        view_id,
        node_id,
        callback_id,
        KeyEventPayload {
            key: event.keystroke.key.as_str(),
            key_char: key_char(event.keystroke.key_char.as_ref()),
            is_held: false,
            modifiers: modifier_flags(&event.keystroke.modifiers),
        },
    );
}

pub(crate) fn emit_action(
    view_id: u64,
    node_id: &str,
    shortcut: &ShortcutBinding,
    event: &KeyDownEvent,
) {
    let _ = native_events::send_action_event(
        view_id,
        node_id,
        &shortcut.callback,
        ActionEventPayload {
            action: &shortcut.action,
            shortcut: &shortcut.shortcut,
            key: event.keystroke.key.as_str(),
            key_char: key_char(event.keystroke.key_char.as_ref()),
            modifiers: modifier_flags(&event.keystroke.modifiers),
        },
    );
}

pub(crate) fn emit_context_menu(
    view_id: u64,
    node_id: &str,
    callback_id: &str,
    event: &MouseDownEvent,
) {
    let _ = native_events::send_context_menu_event(
        view_id,
        node_id,
        callback_id,
        ContextMenuEventPayload {
            x: pixel_to_f64(event.position.x),
            y: pixel_to_f64(event.position.y),
            modifiers: modifier_flags(&event.modifiers),
        },
    );
}

pub(crate) fn emit_keyboard_context_menu(
    view_id: u64,
    node_id: &str,
    callback_id: &str,
    event: &KeyDownEvent,
) {
    let _ = native_events::send_context_menu_event(
        view_id,
        node_id,
        callback_id,
        ContextMenuEventPayload {
            x: 0.0,
            y: 0.0,
            modifiers: modifier_flags(&event.keystroke.modifiers),
        },
    );
}

pub(crate) fn emit_drag_start(view_id: u64, node_id: &str, callback_id: &str, source_id: &str) {
    let _ = native_events::send_drag_start_event(view_id, node_id, callback_id, source_id);
}

pub(crate) fn emit_drag_move(
    view_id: u64,
    node_id: &str,
    callback_id: &str,
    source_id: &str,
    pressed_button: Option<MouseButton>,
    position: gpui::Point<Pixels>,
    modifiers: &Modifiers,
) {
    let _ = native_events::send_drag_move_event(
        view_id,
        node_id,
        callback_id,
        DragMoveEventPayload {
            source_id,
            pressed_button_code: optional_mouse_button_code(pressed_button),
            x: pixel_to_f64(position.x),
            y: pixel_to_f64(position.y),
            modifiers: modifier_flags(modifiers),
        },
    );
}

pub(crate) fn emit_drop(view_id: u64, node_id: &str, callback_id: &str, source_id: &str) {
    let _ = native_events::send_drop_event(view_id, node_id, callback_id, source_id);
}

pub(crate) fn emit_mouse_down(
    view_id: u64,
    node_id: &str,
    callback_id: &str,
    event: &MouseDownEvent,
) {
    let _ = native_events::send_mouse_down_event(
        view_id,
        node_id,
        callback_id,
        MouseDownEventPayload {
            button_code: mouse_button_code(event.button),
            x: pixel_to_f64(event.position.x),
            y: pixel_to_f64(event.position.y),
            click_count: event.click_count as u64,
            modifiers: modifier_flags(&event.modifiers),
            first_mouse: event.first_mouse,
        },
    );
}

pub(crate) fn emit_mouse_up(view_id: u64, node_id: &str, callback_id: &str, event: &MouseUpEvent) {
    let _ = native_events::send_mouse_up_event(
        view_id,
        node_id,
        callback_id,
        MouseUpEventPayload {
            button_code: mouse_button_code(event.button),
            x: pixel_to_f64(event.position.x),
            y: pixel_to_f64(event.position.y),
            click_count: event.click_count as u64,
            modifiers: modifier_flags(&event.modifiers),
        },
    );
}

pub(crate) fn emit_mouse_move(
    view_id: u64,
    node_id: &str,
    callback_id: &str,
    event: &MouseMoveEvent,
) {
    let _ = native_events::send_mouse_move_event(
        view_id,
        node_id,
        callback_id,
        MouseMoveEventPayload {
            pressed_button_code: optional_mouse_button_code(event.pressed_button),
            x: pixel_to_f64(event.position.x),
            y: pixel_to_f64(event.position.y),
            modifiers: modifier_flags(&event.modifiers),
        },
    );
}

pub(crate) fn emit_scroll_wheel(
    view_id: u64,
    node_id: &str,
    callback_id: &str,
    event: &ScrollWheelEvent,
) {
    let (delta_kind_code, delta_x, delta_y) = scroll_delta_parts(event.delta);

    let _ = native_events::send_scroll_wheel_event(
        view_id,
        node_id,
        callback_id,
        ScrollWheelEventPayload {
            x: pixel_to_f64(event.position.x),
            y: pixel_to_f64(event.position.y),
            delta_kind_code,
            delta_x,
            delta_y,
            modifiers: modifier_flags(&event.modifiers),
        },
    );
}

pub(crate) fn matching_shortcut<'a>(
    event: &KeyDownEvent,
    shortcuts: &'a [ShortcutBinding],
) -> Option<&'a ShortcutBinding> {
    if event.is_held {
        return None;
    }

    shortcuts
        .iter()
        .find(|shortcut| event.keystroke.should_match(&shortcut.parsed))
}

pub(crate) fn is_context_menu_key(event: &KeyDownEvent) -> bool {
    if event.is_held {
        return false;
    }

    let keystroke = &event.keystroke;

    keystroke.key == "context_menu"
        || (keystroke.key == "f10"
            && keystroke.modifiers.shift
            && !keystroke.modifiers.control
            && !keystroke.modifiers.alt
            && !keystroke.modifiers.platform
            && !keystroke.modifiers.function)
}

fn modifier_flags(modifiers: &Modifiers) -> EventModifiers {
    EventModifiers {
        control: modifiers.control,
        alt: modifiers.alt,
        shift: modifiers.shift,
        platform: modifiers.platform,
        function: modifiers.function,
    }
}

fn key_char(key_char: Option<&String>) -> Option<&str> {
    key_char.map(String::as_str)
}

fn pixel_to_f64(value: Pixels) -> f64 {
    f64::from(value)
}

fn mouse_button_code(button: MouseButton) -> i32 {
    match button {
        MouseButton::Left => 1,
        MouseButton::Right => 2,
        MouseButton::Middle => 3,
        MouseButton::Navigate(gpui::NavigationDirection::Back) => 4,
        MouseButton::Navigate(gpui::NavigationDirection::Forward) => 5,
    }
}

fn optional_mouse_button_code(button: Option<MouseButton>) -> i32 {
    button.map(mouse_button_code).unwrap_or(0)
}

fn scroll_delta_parts(delta: ScrollDelta) -> (i32, f64, f64) {
    match delta {
        ScrollDelta::Pixels(delta) => (1, pixel_to_f64(delta.x), pixel_to_f64(delta.y)),
        ScrollDelta::Lines(delta) => (2, f64::from(delta.x), f64::from(delta.y)),
    }
}

#[cfg(test)]
#[path = "events_tests.rs"]
mod tests;