#[component] fn ButtonPanel( snapshot: DeviceState, selected_profile: RwSignal, set_status: WriteSignal, set_busy: WriteSignal, ) -> impl IntoView { const BUTTONS: [&str; 13] = [ "aim", "left", "middle", "right", "forward", "wheel_up", "middle_forward", "wheel_left", "backward", "wheel_down", "middle_backward", "wheel_right", "bottom", ]; const CATEGORIES: [&str; 11] = [ "disabled", "mouse", "keyboard", "macro", "dpi_switch", "profile_switch", "system", "consumer", "hypershift_toggle", "scroll_mode_toggle", "custom", ]; let demo_mode = snapshot.device.path.starts_with("demo://"); let selected_button = RwSignal::new("left".to_string()); let selected_hypershift = RwSignal::new(false); let mapping_state = RwSignal::new(None::); let demo_mappings = RwSignal::new(mock_button_mappings()); let button_categories = RwSignal::new(std::collections::BTreeMap::::new()); Effect::new(move |_| { let profile = selected_profile.get(); let button = selected_button.get(); let hypershift = selected_hypershift.get(); if demo_mode { let mapping = demo_mapping_for(&demo_mappings.get_untracked(), &profile, &button, hypershift) .unwrap_or_else(|| default_button_mapping(profile.clone(), button.clone(), hypershift)); mapping_state.set(Some(mapping)); return; } set_busy.set(true); set_status.set(format!( "Loading {button} mapping for profile {profile}{}...", if hypershift { " with hypershift" } else { "" } )); spawn_local(async move { match invoke::( "get_button_mapping", &ButtonMappingQueryArgs { profile, button, hypershift, }, ) .await { Ok(mapping) => { mapping_state.set(Some(mapping)); set_status.set("Button mapping loaded.".to_string()); } Err(error) => set_status.set(error), } set_busy.set(false); }); }); Effect::new(move |_| { let profile = selected_profile.get(); let hypershift = selected_hypershift.get(); if demo_mode { let categories = demo_mappings .get_untracked() .into_iter() .filter(|mapping| mapping.profile == profile && mapping.hypershift == hypershift) .map(|mapping| (mapping.button, mapping.category)) .collect(); button_categories.set(categories); return; } spawn_local(async move { let mut categories = std::collections::BTreeMap::new(); for button in BUTTONS { if let Ok(mapping) = invoke::( "get_button_mapping", &ButtonMappingQueryArgs { profile: profile.clone(), button: button.to_string(), hypershift, }, ) .await { categories.insert(button.to_string(), mapping.category); } } button_categories.set(categories); }); }); let save_mapping = move |_| { let Some(mapping) = mapping_state.get_untracked() else { return; }; if demo_mode { demo_mappings.update(|items| upsert_button_mapping(items, mapping.clone())); button_categories.update(|items| { items.insert(mapping.button.clone(), mapping.category.clone()); }); set_status.set("Updated demo button mapping.".to_string()); return; } set_busy.set(true); set_status.set("Writing button mapping...".to_string()); spawn_local(async move { match invoke::( "set_button_mapping", &json!({ "mapping": mapping }), ) .await { Ok(updated) => { button_categories.update(|items| { items.insert(updated.button.clone(), updated.category.clone()); }); mapping_state.set(Some(updated)); set_status.set("Button mapping applied.".to_string()); } Err(error) => set_status.set(error), } set_busy.set(false); }); }; let reset_category = move |category: &'static str| { mapping_state.update(|state| { if let Some(mapping) = state.as_mut() { mapping.category = category.to_string(); mapping.payload = default_payload_for_category(category); } }); }; view! {

"Button Mapping"

{BUTTONS.into_iter().map(|button| { let button_name = button.to_string(); let button_active = button_name.clone(); let button_click = button_name.clone(); view! { } }).collect_view()}

"Each button can be assigned a function when Hypershift is off, and another function when Hypershift is on."

"Assign a button to hypershift_toggle to let it switch Hypershift status."

{move || { let Some(mapping) = mapping_state.get() else { return view! {

"Loading button mapping..."

}.into_any(); }; let current_category = mapping.category.clone(); let payload = mapping.payload.clone(); let mouse_fn = payload_string(&payload, "fn").unwrap_or_else(|| "left".to_string()); let mouse_double_click = payload_bool(&payload, "double_click"); let mouse_turbo = payload_u64(&payload, "turbo").unwrap_or(200) as u16; let keyboard_key = payload_u64(&payload, "key").unwrap_or(0x04) as u8; let keyboard_turbo = payload_u64(&payload, "turbo").map(|value| value as u16); let keyboard_modifiers = payload_string_vec(&payload, "modifier"); let macro_id = payload_u64(&payload, "macro_id").unwrap_or(0) as u16; let macro_mode = payload_string(&payload, "mode").unwrap_or_else(|| "macro_fixed".to_string()); let macro_times = payload_u64(&payload, "times").unwrap_or(1) as u8; let dpi_fn = payload_string(&payload, "fn").unwrap_or_else(|| "next_loop".to_string()); let dpi_stage = payload_u64(&payload, "stage").unwrap_or(1) as u8; let dpi_pair = payload_u16_pair(&payload, "dpi").unwrap_or([800, 800]); let profile_fn = payload_string(&payload, "fn").unwrap_or_else(|| "next_loop".to_string()); let fixed_profile = payload_string(&payload, "profile").unwrap_or_else(|| "white".to_string()); let system_flags = payload_string_vec(&payload, "fn"); let consumer_code = payload_u64(&payload, "fn").unwrap_or(0x00b0) as u16; let toggle_value = payload_u64(&payload, "fn").unwrap_or(1) as u8; let custom_class = payload_u64(&payload, "fn_class").unwrap_or(0) as u8; let custom_bytes = payload_byte_vec(&payload, "fn_value"); view! {

{format!( "Editing {} on profile {}{}.", mapping.button, mapping.profile, if mapping.hypershift { " with hypershift enabled" } else { "" } )}

{CATEGORIES.into_iter().map(|category| { let category_name = category.to_string(); view! { } }).collect_view()}
{render_button_mapping_editor( mapping_state, current_category, payload, mouse_fn, mouse_double_click, mouse_turbo, keyboard_key, keyboard_turbo, keyboard_modifiers, macro_id, macro_mode, macro_times, dpi_fn, dpi_stage, dpi_pair, profile_fn, fixed_profile, system_flags, consumer_code, toggle_value, custom_class, custom_bytes, )}
}.into_any() }}
} }