Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 99 additions & 2 deletions src/glue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,10 @@ pub fn wire(
let mut s = shared.lock();
if kind == 0 {
let id = next_id(&s.preset.slider_categories, |c| c.id);
s.preset.slider_categories.push(SliderCategory { id, name: name.to_string(), streams: vec![] });
s.preset.slider_categories.push(SliderCategory { id, name: name.to_string(), streams: vec![], collapsed: false });
} else {
let id = next_id(&s.preset.button_categories, |c| c.id);
s.preset.button_categories.push(ButtonCategory { id, name: name.to_string(), actions: vec![] });
s.preset.button_categories.push(ButtonCategory { id, name: name.to_string(), actions: vec![], collapsed: false });
}
let _ = crate::storage::save_preset(&s.preset);
let Some(ui) = weak.upgrade() else { return };
Expand Down Expand Up @@ -212,6 +212,64 @@ pub fn wire(
});
}

{
let weak = ui.as_weak();
let shared = shared.clone();
ui.on_toggle_category_collapse(move |kind, id| {
let mut s = shared.lock();
let id = id as u32;
if kind == 0 {
if let Some(c) = s.preset.slider_categories.iter_mut().find(|c| c.id == id) {
c.collapsed = !c.collapsed;
}
} else if let Some(c) = s.preset.button_categories.iter_mut().find(|c| c.id == id) {
c.collapsed = !c.collapsed;
}
let _ = crate::storage::save_preset(&s.preset);
let Some(ui) = weak.upgrade() else { return };
push_preset_to_ui(&ui, &s.preset);
});
}
{
let weak = ui.as_weak();
let shared = shared.clone();
// Reorder category cards (cosmetic, but persisted via Vec order).
ui.on_reorder_category(move |kind, from_id, to_index| {
let mut s = shared.lock();
let from_id = from_id as u32;
let to = to_index.max(0) as usize;
if kind == 0 {
reorder_by_id(&mut s.preset.slider_categories, from_id, to, |c| c.id);
} else {
reorder_by_id(&mut s.preset.button_categories, from_id, to, |c| c.id);
}
let _ = crate::storage::save_preset(&s.preset);
let Some(ui) = weak.upgrade() else { return };
push_preset_to_ui(&ui, &s.preset);
refresh_home(&ui, &s);
});
}
{
let weak = ui.as_weak();
let shared = shared.clone();
// Move an action/stream within or between categories (same target-kind).
ui.on_move_line(move |kind, from_cat, from_idx, to_cat, to_idx| {
let mut s = shared.lock();
let (from_cat, to_cat) = (from_cat as u32, to_cat as u32);
let (from_idx, to_idx) = (from_idx.max(0) as usize, to_idx.max(0) as usize);
if kind == 0 {
move_line(&mut s.preset.slider_categories, from_cat, from_idx, to_cat, to_idx,
|c| c.id, |c| &mut c.streams);
} else {
move_line(&mut s.preset.button_categories, from_cat, from_idx, to_cat, to_idx,
|c| c.id, |c| &mut c.actions);
}
let _ = crate::storage::save_preset(&s.preset);
let Some(ui) = weak.upgrade() else { return };
push_preset_to_ui(&ui, &s.preset);
});
}

// ─── wizard ────────────────────────────────────────────────────────────
{
let weak = ui.as_weak();
Expand Down Expand Up @@ -702,6 +760,43 @@ fn preset_curve(_preset: &Preset) -> (crate::curve::CurvePreset, crate::curve::B
(crate::curve::CurvePreset::Linear, crate::curve::BezierPoints::LINEAR)
}

/// Move the category with `from_id` to insertion index `to_index` (0..=len).
fn reorder_by_id<C>(cats: &mut Vec<C>, from_id: u32, to_index: usize, id: impl Fn(&C) -> u32) {
let Some(from) = cats.iter().position(|c| id(c) == from_id) else { return };
let item = cats.remove(from);
// `to_index` was computed against the original list; removing the item
// shifts everything after it down by one.
let to = if from < to_index { to_index - 1 } else { to_index };
cats.insert(to.min(cats.len()), item);
}

/// Move element `from_idx` of category `from_cat` to insertion index `to_idx` of
/// category `to_cat` (may be the same category). `to_idx` is in the destination's
/// original ordering.
fn move_line<C, E>(
cats: &mut [C],
from_cat: u32,
from_idx: usize,
to_cat: u32,
to_idx: usize,
id: impl Fn(&C) -> u32,
lines: impl Fn(&mut C) -> &mut Vec<E>,
) {
let Some(from_pos) = cats.iter().position(|c| id(c) == from_cat) else { return };
let Some(to_pos) = cats.iter().position(|c| id(c) == to_cat) else { return };
let item = {
let v = lines(&mut cats[from_pos]);
if from_idx >= v.len() {
return;
}
v.remove(from_idx)
};
// Within the same category, a removal before the target shifts it down.
let to = if from_cat == to_cat && from_idx < to_idx { to_idx - 1 } else { to_idx };
let v = lines(&mut cats[to_pos]);
v.insert(to.min(v.len()), item);
}

fn push_preset_to_ui(ui: &AppWindow, preset: &Preset) {
let sliders: Vec<CategorySummary> = preset
.slider_categories
Expand All @@ -710,6 +805,7 @@ fn push_preset_to_ui(ui: &AppWindow, preset: &Preset) {
id: c.id as i32,
name: c.name.clone().into(),
count: c.streams.len() as i32,
collapsed: c.collapsed,
lines: ModelRc::new(VecModel::from(
c.streams.iter().enumerate().map(|(i, s)| LineItem {
id: i as i32,
Expand All @@ -727,6 +823,7 @@ fn push_preset_to_ui(ui: &AppWindow, preset: &Preset) {
id: c.id as i32,
name: c.name.clone().into(),
count: c.actions.len() as i32,
collapsed: c.collapsed,
lines: ModelRc::new(VecModel::from(
c.actions.iter().enumerate().map(|(i, a)| LineItem {
id: i as i32,
Expand Down
8 changes: 4 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,23 +145,23 @@ fn main() -> anyhow::Result<()> {

// seed two slider categories
let scats = vec![
CategorySummary { id: 1, name: "Music".into(), count: 1,
CategorySummary { id: 1, name: "Music".into(), count: 1, collapsed: false,
lines: ModelRc::new(VecModel::from(vec![
LineItem { id: 0, primary: "spotify".into(), secondary: "process".into(), icon_kind: 0 },
])) },
CategorySummary { id: 2, name: "Chat".into(), count: 2,
CategorySummary { id: 2, name: "Chat".into(), count: 2, collapsed: false,
lines: ModelRc::new(VecModel::from(vec![
LineItem { id: 0, primary: "discord".into(), secondary: "process".into(), icon_kind: 0 },
LineItem { id: 1, primary: "default mic".into(), secondary: "microphone".into(), icon_kind: 1 },
])) },
];
ui.set_slider_categories(ModelRc::new(VecModel::from(scats)));
let bcats = vec![
CategorySummary { id: 1, name: "Toggle mute".into(), count: 1,
CategorySummary { id: 1, name: "Toggle mute".into(), count: 1, collapsed: false,
lines: ModelRc::new(VecModel::from(vec![
LineItem { id: 0, primary: "Mute microphone".into(), secondary: "default mic".into(), icon_kind: 1 },
])) },
CategorySummary { id: 2, name: "Media keys".into(), count: 2,
CategorySummary { id: 2, name: "Media keys".into(), count: 2, collapsed: false,
lines: ModelRc::new(VecModel::from(vec![
LineItem { id: 0, primary: "Simulate key".into(), secondary: "Play/Pause (179)".into(), icon_kind: 4 },
LineItem { id: 1, primary: "Open website".into(), secondary: "https://example.com".into(), icon_kind: 3 },
Expand Down
10 changes: 8 additions & 2 deletions src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ pub struct ButtonCategory {
pub name: String,
#[serde(default)]
pub actions: Vec<ButtonAction>,
/// UI-only: whether the card is collapsed in the categories view.
#[serde(default)]
pub collapsed: bool,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
Expand All @@ -89,6 +92,9 @@ pub struct SliderCategory {
pub name: String,
#[serde(default)]
pub streams: Vec<AudioStream>,
/// UI-only: whether the card is collapsed in the categories view.
#[serde(default)]
pub collapsed: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
Expand Down Expand Up @@ -229,8 +235,8 @@ mod tests {
#[test]
fn next_id_grows() {
let v: Vec<SliderCategory> = vec![
SliderCategory { id: 1, name: "a".into(), streams: vec![] },
SliderCategory { id: 5, name: "b".into(), streams: vec![] },
SliderCategory { id: 1, name: "a".into(), streams: vec![], collapsed: false },
SliderCategory { id: 5, name: "b".into(), streams: vec![], collapsed: false },
];
assert_eq!(next_id(&v, |c| c.id), 6);
}
Expand Down
Loading