319 lines
14 KiB
Rust
319 lines
14 KiB
Rust
#[component]
|
|
fn ButtonPanel(
|
|
snapshot: DeviceState,
|
|
selected_profile: RwSignal<String>,
|
|
set_status: WriteSignal<String>,
|
|
set_busy: WriteSignal<bool>,
|
|
) -> 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::<ButtonMappingState>);
|
|
let demo_mappings = RwSignal::new(mock_button_mappings());
|
|
let button_categories = RwSignal::new(std::collections::BTreeMap::<String, String>::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::<ButtonMappingState, _>(
|
|
"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::<ButtonMappingState, _>(
|
|
"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::<ButtonMappingState, _>(
|
|
"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! {
|
|
<section class="panel-grid">
|
|
<article class="panel wide">
|
|
<h3>"Button Mapping"</h3>
|
|
<div class="button-mapper-grid">
|
|
{BUTTONS.into_iter().map(|button| {
|
|
let button_name = button.to_string();
|
|
let button_active = button_name.clone();
|
|
let button_click = button_name.clone();
|
|
view! {
|
|
<button
|
|
type="button"
|
|
class="button-tile"
|
|
class:active=move || selected_button.get() == button_active
|
|
on:click=move |_| selected_button.set(button_click.clone())
|
|
>
|
|
<strong>{button_label(button)}</strong>
|
|
<span>{move || button_categories.get().get(button).cloned().unwrap_or_else(|| button.to_string())}</span>
|
|
</button>
|
|
}
|
|
}).collect_view()}
|
|
</div>
|
|
|
|
<label class="check-row button-hypershift-toggle">
|
|
<input
|
|
type="checkbox"
|
|
prop:checked=move || selected_hypershift.get()
|
|
on:change=move |ev| selected_hypershift.set(event_target_checked(&ev))
|
|
/>
|
|
<span>"When Hypershift on"</span>
|
|
</label>
|
|
<p class="subtle">
|
|
"Each button can be assigned a function when Hypershift is off, and another function when Hypershift is on."
|
|
</p>
|
|
<p class="subtle">
|
|
"Assign a button to hypershift_toggle to let it switch Hypershift status."
|
|
</p>
|
|
|
|
{move || {
|
|
let Some(mapping) = mapping_state.get() else {
|
|
return view! { <p>"Loading button mapping..."</p> }.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! {
|
|
<p class="subtle">
|
|
{format!(
|
|
"Editing {} on profile {}{}.",
|
|
mapping.button,
|
|
mapping.profile,
|
|
if mapping.hypershift { " with hypershift enabled" } else { "" }
|
|
)}
|
|
</p>
|
|
|
|
<div class="category-grid">
|
|
{CATEGORIES.into_iter().map(|category| {
|
|
let category_name = category.to_string();
|
|
view! {
|
|
<button
|
|
type="button"
|
|
class:active=move || mapping_state.get().map(|item| item.category == category_name).unwrap_or(false)
|
|
on:click=move |_| reset_category(category)
|
|
>
|
|
{category}
|
|
</button>
|
|
}
|
|
}).collect_view()}
|
|
</div>
|
|
|
|
{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,
|
|
)}
|
|
|
|
<div class="button-actions">
|
|
<button type="button" class="primary-action" on:click=save_mapping>"Apply Mapping"</button>
|
|
<button
|
|
type="button"
|
|
class="secondary-action"
|
|
on:click=move |_| {
|
|
let profile = selected_profile.get_untracked();
|
|
let button = selected_button.get_untracked();
|
|
let hypershift = selected_hypershift.get_untracked();
|
|
if demo_mode {
|
|
let mapping = demo_mapping_for(&demo_mappings.get_untracked(), &profile, &button, hypershift)
|
|
.unwrap_or_else(|| default_button_mapping(profile, button, hypershift));
|
|
mapping_state.set(Some(mapping));
|
|
set_status.set("Reloaded demo button mapping.".to_string());
|
|
return;
|
|
}
|
|
set_busy.set(true);
|
|
set_status.set("Reloading button mapping...".to_string());
|
|
spawn_local(async move {
|
|
match invoke::<ButtonMappingState, _>(
|
|
"get_button_mapping",
|
|
&ButtonMappingQueryArgs {
|
|
profile,
|
|
button,
|
|
hypershift,
|
|
},
|
|
)
|
|
.await
|
|
{
|
|
Ok(mapping) => {
|
|
mapping_state.set(Some(mapping));
|
|
set_status.set("Button mapping reloaded.".to_string());
|
|
}
|
|
Err(error) => set_status.set(error),
|
|
}
|
|
set_busy.set(false);
|
|
});
|
|
}
|
|
>
|
|
"Reload"
|
|
</button>
|
|
</div>
|
|
}.into_any()
|
|
}}
|
|
</article>
|
|
</section>
|
|
}
|
|
}
|