first commit

This commit is contained in:
Alexander Daichendt 2026-05-17 21:33:41 +02:00
commit 2a1252cc7a
69 changed files with 7559 additions and 0 deletions

319
src/app/button_panel.rs Normal file
View 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>
}
}