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, scrollable, text, text_input, Space},
    Alignment, Element, Length,
};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AutoSwitchRule {
    pub app_id: String,
    pub profile_name: String,
    pub device_id: Option<String>,
    pub layer_id: Option<usize>,
}

#[derive(Debug, Clone, Default)]
pub struct AutoSwitchRulesView {
    pub device_id: String,
    pub rules: Vec<AutoSwitchRule>,
    pub editing_rule: Option<usize>,
    pub new_app_id: String,
    pub new_profile_name: String,
    pub new_layer_id: String,
}

pub fn view(state: &State) -> Element<'_, Message> {
    let view = state.auto_switch_view.as_ref().unwrap();

    let focus_display = row![
        text("Current Focus:").size(14),
        Space::with_width(8),
        if let Some(ref focus) = state.current_focus {
            container(text(focus).size(14))
                .padding([4, 12])
                .style(theme::styles::card)
        } else {
            container(text("Unknown").size(14).style(iced::theme::Text::Color(
                iced::Color::from_rgb(0.6, 0.6, 0.6),
            )))
            .padding([4, 12])
        },
    ]
    .spacing(4)
    .align_items(Alignment::Center);

    let rules_header = row![
        text("Auto-Switch Rules").size(18),
        Space::with_width(Length::Fill),
        if view.editing_rule.is_some() {
            button("Cancel")
                .on_press(Message::EditAutoSwitchRule(usize::MAX))
                .style(iced::theme::Button::Text)
        } else {
            button("Add Rule")
                .on_press(Message::EditAutoSwitchRule(usize::MAX))
                .style(iced::theme::Button::Primary)
        },
    ]
    .align_items(Alignment::Center);

    let rules_list = if view.rules.is_empty() {
        column![
            Space::with_height(20),
            text("No rules configured")
                .size(14)
                .style(iced::theme::Text::Color(iced::Color::from_rgb(
                    0.6, 0.6, 0.6
                ))),
            Space::with_height(8),
            text("Add a rule to automatically switch profiles when windows gain focus")
                .size(12)
                .style(iced::theme::Text::Color(iced::Color::from_rgb(
                    0.5, 0.5, 0.5
                ))),
        ]
        .align_items(Alignment::Center)
    } else {
        let mut list = column![].spacing(8);
        for (idx, rule) in view.rules.iter().enumerate() {
            let is_editing = view.editing_rule == Some(idx);
            let indicator: Element<'_, Message> = if is_editing {
                container(text("\u{25b6}")).padding([0, 8]).into()
            } else {
                Space::with_width(20).into()
            };
            let rule_row = row![
                indicator,
                column![
                    text(format!("App: {}", rule.app_id)).size(14),
                    text(format!(
                        "Profile: {}{}",
                        rule.profile_name,
                        rule.layer_id
                            .map(|l| format!(" + Layer {}", l))
                            .unwrap_or_default()
                    ))
                    .size(12),
                ]
                .spacing(2),
                Space::with_width(Length::Fill),
                button("Edit")
                    .on_press(Message::EditAutoSwitchRule(idx))
                    .style(iced::theme::Button::Text),
                button("Delete")
                    .on_press(Message::DeleteAutoSwitchRule(idx))
                    .style(iced::theme::Button::Destructive),
            ]
            .spacing(8)
            .align_items(Alignment::Center);
            list = list.push(rule_row);
        }
        list
    };

    let edit_form = if view.editing_rule.is_some() {
        Some(
            column![
                Space::with_height(20),
                text(if view.editing_rule.unwrap_or(0) < view.rules.len() {
                    "Edit Rule"
                } else {
                    "Add New Rule"
                })
                .size(16),
                Space::with_height(12),
                row![
                    text("App ID:").size(14),
                    Space::with_width(8),
                    text_input("org.alacritty", &view.new_app_id)
                        .on_input(Message::AutoSwitchAppIdChanged)
                        .padding(8)
                        .size(14),
                    Space::with_width(8),
                    button("Use Current")
                        .on_press(Message::AutoSwitchUseCurrentApp)
                        .style(iced::theme::Button::Secondary),
                ]
                .spacing(4)
                .align_items(Alignment::Center),
                Space::with_height(8),
                row![
                    text("Profile:").size(14),
                    Space::with_width(8),
                    text_input("default", &view.new_profile_name)
                        .on_input(Message::AutoSwitchProfileNameChanged)
                        .padding(8)
                        .size(14),
                ]
                .spacing(4)
                .align_items(Alignment::Center),
                Space::with_height(8),
                row![
                    text("Layer (optional):").size(14),
                    Space::with_width(8),
                    text_input("0", &view.new_layer_id)
                        .on_input(Message::AutoSwitchLayerIdChanged)
                        .padding(8)
                        .size(14),
                ]
                .spacing(4)
                .align_items(Alignment::Center),
                Space::with_height(12),
                row![
                    Space::with_width(Length::Fill),
                    button("Save Rule")
                        .on_press(Message::SaveAutoSwitchRule)
                        .style(iced::theme::Button::Primary),
                ]
                .align_items(Alignment::Center),
            ]
            .spacing(4),
        )
    } else {
        None
    };

    let mut content = column![
        focus_display,
        Space::with_height(20),
        rules_header,
        Space::with_height(12),
        scrollable(rules_list).height(Length::Fixed(200.0)),
    ]
    .spacing(4);

    if let Some(form) = edit_form {
        content = content.push(form);
    }

    container(content)
        .padding(20)
        .width(Length::Fill)
        .style(theme::styles::card)
        .into()
}