aethermap-gui 1.5.0

GUI client for aethermap input remapper
Documentation
use crate::gui::{Message, State};
use crate::theme;
use iced::{
    widget::{button, column, container, row, text, Space},
    Alignment, Element, Length,
};

#[derive(Debug, Clone)]
pub struct KeypadButton {
    pub id: String,
    pub label: String,
    pub row: usize,
    #[allow(dead_code)]
    pub col: usize,
    pub current_remap: Option<String>,
}

pub fn azeron_keypad_layout() -> Vec<KeypadButton> {
    vec![
        KeypadButton {
            id: "JOY_BTN_0".to_string(),
            label: "1".to_string(),
            row: 0,
            col: 0,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_1".to_string(),
            label: "2".to_string(),
            row: 0,
            col: 1,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_2".to_string(),
            label: "3".to_string(),
            row: 0,
            col: 2,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_3".to_string(),
            label: "4".to_string(),
            row: 0,
            col: 3,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_4".to_string(),
            label: "5".to_string(),
            row: 0,
            col: 4,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_5".to_string(),
            label: "Q".to_string(),
            row: 2,
            col: 0,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_6".to_string(),
            label: "W".to_string(),
            row: 2,
            col: 1,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_7".to_string(),
            label: "E".to_string(),
            row: 2,
            col: 2,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_8".to_string(),
            label: "R".to_string(),
            row: 2,
            col: 3,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_9".to_string(),
            label: "A".to_string(),
            row: 3,
            col: 0,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_10".to_string(),
            label: "S".to_string(),
            row: 3,
            col: 1,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_11".to_string(),
            label: "D".to_string(),
            row: 3,
            col: 2,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_12".to_string(),
            label: "F".to_string(),
            row: 3,
            col: 3,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_13".to_string(),
            label: "Z".to_string(),
            row: 4,
            col: 0,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_14".to_string(),
            label: "X".to_string(),
            row: 4,
            col: 1,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_15".to_string(),
            label: "C".to_string(),
            row: 4,
            col: 2,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_16".to_string(),
            label: "V".to_string(),
            row: 4,
            col: 3,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_17".to_string(),
            label: "6".to_string(),
            row: 0,
            col: 5,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_18".to_string(),
            label: "7".to_string(),
            row: 1,
            col: 5,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_19".to_string(),
            label: "8".to_string(),
            row: 2,
            col: 5,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_20".to_string(),
            label: "9".to_string(),
            row: 3,
            col: 5,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_21".to_string(),
            label: "0".to_string(),
            row: 4,
            col: 5,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_22".to_string(),
            label: "TL".to_string(),
            row: 6,
            col: 0,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_23".to_string(),
            label: "TM".to_string(),
            row: 6,
            col: 1,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_24".to_string(),
            label: "TR".to_string(),
            row: 6,
            col: 2,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_25".to_string(),
            label: "BL".to_string(),
            row: 7,
            col: 0,
            current_remap: None,
        },
        KeypadButton {
            id: "JOY_BTN_26".to_string(),
            label: "BR".to_string(),
            row: 7,
            col: 1,
            current_remap: None,
        },
    ]
}

pub fn format_remap_target(target: &str) -> String {
    if let Some(rest) = target.strip_prefix("KEY_") {
        match rest {
            "LEFTCTRL" => "LCtrl".to_string(),
            "RIGHTCTRL" => "RCtrl".to_string(),
            "LEFTSHIFT" => "LShft".to_string(),
            "RIGHTSHIFT" => "RShft".to_string(),
            "LEFTALT" => "LAlt".to_string(),
            "RIGHTALT" => "RAlt".to_string(),
            "LEFTMETA" => "LMeta".to_string(),
            "RIGHTMETA" => "RMeta".to_string(),
            "SPACE" => "Space".to_string(),
            "TAB" => "Tab".to_string(),
            "ENTER" => "Enter".to_string(),
            "ESC" => "Esc".to_string(),
            "BACKSPACE" => "Bksp".to_string(),
            "DELETE" => "Del".to_string(),
            "INSERT" => "Ins".to_string(),
            "HOME" => "Home".to_string(),
            "END" => "End".to_string(),
            "PAGEUP" => "PgUp".to_string(),
            "PAGEDOWN" => "PgDn".to_string(),
            "UP" => "\u{2191}".to_string(),
            "DOWN" => "\u{2193}".to_string(),
            "LEFT" => "\u{2190}".to_string(),
            "RIGHT" => "\u{2192}".to_string(),
            s if s.len() == 1 => s.to_uppercase(),
            s if s.starts_with('F') => format!("F{}", &s[1..]),
            _ => rest.to_string(),
        }
    } else if let Some(rest) = target.strip_prefix("BTN_") {
        match rest {
            "LEFT" => "LMB".to_string(),
            "RIGHT" => "RMB".to_string(),
            "MIDDLE" => "Mid".to_string(),
            "SIDE" => "Side".to_string(),
            "EXTRA" => "Extra".to_string(),
            "FORWARD" => "Fwd".to_string(),
            "BACK" => "Back".to_string(),
            _ => rest.to_string(),
        }
    } else if let Some(rest) = target.strip_prefix("REL_") {
        match rest {
            "WHEEL" => "Wheel".to_string(),
            "HWHEEL" => "HWheel".to_string(),
            _ => rest.to_string(),
        }
    } else {
        if target.len() > 6 {
            format!("{}...", &target[..6])
        } else {
            target.to_string()
        }
    }
}

pub fn view(state: &State) -> Element<'_, Message> {
    let layout = azeron_keypad_layout();

    let mut rows: Vec<Vec<Element<'_, Message>>> = Vec::with_capacity(10);
    for _ in 0..10 {
        rows.push(Vec::new());
    }

    for keypad_button in &layout {
        let button_id = keypad_button.id.clone();
        let label = keypad_button.label.clone();
        let remap = keypad_button.current_remap.clone();
        let is_selected = state.selected_button
            == Some(
                layout
                    .iter()
                    .position(|b| b.id == keypad_button.id)
                    .unwrap_or(usize::MAX),
            );

        let button_style = if is_selected {
            iced::theme::Button::Primary
        } else if remap.is_some() {
            iced::theme::Button::Secondary
        } else {
            iced::theme::Button::Text
        };

        let button_content: Element<'_, Message> = if let Some(ref target) = remap {
            let display_name = format_remap_target(target);
            container(
                column![
                    text(label)
                        .size(8)
                        .style(iced::theme::Text::Color(iced::Color::from_rgb(
                            0.5, 0.5, 0.5
                        ))),
                    text(display_name).size(11).width(Length::Fixed(45.0)),
                ]
                .spacing(2)
                .align_items(Alignment::Center),
            )
            .center_x()
            .center_y()
            .into()
        } else {
            container(text(label).size(12)).center_x().center_y().into()
        };

        let btn = button(button_content)
            .on_press(Message::SelectKeypadButton(button_id.clone()))
            .style(button_style)
            .padding([6, 8])
            .width(iced::Length::Fixed(54.0))
            .height(iced::Length::Fixed(54.0))
            .into();

        if rows.get_mut(keypad_button.row).is_some() {
            rows[keypad_button.row].push(btn);
        }
    }

    let hat_switch = container(text("Hat\n\u{2195}").size(10))
        .width(iced::Length::Fixed(54.0))
        .height(iced::Length::Fixed(54.0))
        .center_x()
        .center_y()
        .style(theme::styles::card)
        .into();

    if rows.get_mut(5).is_some() {
        rows[5].push(hat_switch);
    }

    let keypad_rows: Vec<Element<'_, Message>> = rows
        .into_iter()
        .filter(|r| !r.is_empty())
        .map(|row_elements| {
            row(row_elements)
                .spacing(4)
                .align_items(Alignment::Center)
                .into()
        })
        .collect();

    let keypad_content = column![
        text("Azeron Keypad Layout").size(20),
        Space::with_height(10),
        text("Click a button to configure remapping").size(12),
        Space::with_height(20),
    ]
    .spacing(10)
    .align_items(Alignment::Center)
    .push(
        column(keypad_rows)
            .spacing(4)
            .align_items(Alignment::Center),
    );

    container(keypad_content)
        .padding(24)
        .width(Length::Fill)
        .center_x()
        .into()
}