first commit
This commit is contained in:
commit
2a1252cc7a
69 changed files with 7559 additions and 0 deletions
319
src/app/button_panel.rs
Normal file
319
src/app/button_panel.rs
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
#[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>
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue