improve hierachical selection highlighting

tzemanovic
Jan 29, 2026, 12:24 PM
LA45PKNRPTPJ7FZ72EG56YIVT6GIPUNFNQTEKPDJ3DOM3NGT2QBAC

Dependencies

  • [2] 23SFYK4Q big view refactor into a new crate
  • [3] MYGIBRRH wip custom theme
  • [4] PKJCFSBM theme improvements
  • [5] 7SSBM4UQ view: refactor repo view
  • [6] KWTBNTO3 diffs selection and scrolling
  • [7] PTWZYQFR use nav-scrollable for repo status
  • [8] UR4J677R nav for log changes and refactors
  • [9] A6Z4O6RC actions menu
  • [10] JZXYSIYD channel selection!
  • [11] BAUK5BON pimp-up action buttons
  • [12] OJPGHVC3 entire log!
  • [13] 3XRG4BB6 rewritten nav-scrollable!
  • [14] WAOGSCOJ very nice refactor, wip adding channels logs
  • [15] EJPSD5XO shared allowed actions conditions between update and view
  • [16] YK3MOJJL chonky refactor, wip other channels logs & diffs
  • [17] TQEZQJV4 finish other channels logs selection
  • [18] PSKE5G36 view description of change
  • [19] 7WCB5YQJ refactor msgs and modules
  • [20] Z752SDIL advanced shaping for file names
  • [21] LFEMJYYD start of to_record selection
  • [22] N256FH74 improve views
  • [23] 2SLTGWP6 add change files diffs to-record selection
  • [24] FU6P5QLG indicate when a file is a dir with appended '/'
  • [25] 3CM5KELT improve headers when there are no untracked/changed file
  • [26] 5ZRDYL6K fork channel, fix recording esc key
  • [27] WONZMXOW darker action key highlight
  • [28] DST3HRZZ fix emoji rendering
  • [29] LNAL3372 update iced

Change contents

  • replacement in inflorescence_view/src/view.rs at line 150
    [8.3089][8.3089:3137](),[8.3137][24.4903:4930](),[24.4930][14.4720:4906](),[2.16079][14.4720:4906](),[9.683][2.16260:16299](),[10.4327][2.16260:16299](),[14.4906][2.16260:16299](),[2.16260][2.16260:16299](),[2.16299][24.4931:5006](),[24.5006][20.22:84](),[4.2786][20.22:84](),[20.84][4.2786:2879](),[4.2786][4.2786:2879](),[4.2879][19.456:549](),[19.549][24.5007:5093](),[24.5093][19.550:578](),[10.4487][19.550:578](),[19.578][3.7757:7827](),[15.939][3.7757:7827](),[2.16516][3.7757:7827](),[3.7827][2.16586:16604](),[2.16586][2.16586:16604](),[2.16604][25.24:38](),[25.38][7.1555:1565](),[2.16619][7.1555:1565]()
    untracked_files.iter().enumerate().map(
    |(ix, file)| {
    let is_selected = matches!(selection,
    selection::Unified::Status(Some(selection::Status::UntrackedFile{ ix: selected_ix, .. })) if &ix == selected_ix
    );
    el(
    button(
    text(file.to_string())
    .shaping(text::Shaping::Advanced)
    .wrapping(text::Wrapping::WordOrGlyph)
    )
    .on_press(Msg::UnfilteredSelection(selection::UnfilteredMsg::Select(
    selection::Select::UntrackedFile{ix, path: file.clone()},
    )))
    .class(selectable_button_class(is_selected)),
    )
    }
    )
    [8.3089]
    [5.766]
    untracked_files.iter().enumerate().map(|(ix, file)| {
    let selection = match selection {
    selection::Unified::Status(Some(
    selection::Status::UntrackedFile {
    ix: selected_ix,
    diff_selected,
    ..
    },
    )) if &ix == selected_ix => {
    if *diff_selected {
    HierarchicalSelection::ChildSelected
    } else {
    HierarchicalSelection::Selected
    }
    }
    _ => HierarchicalSelection::NotSelected,
    };
    el(button(
    text(file.to_string())
    .shaping(text::Shaping::Advanced)
    .wrapping(text::Wrapping::WordOrGlyph),
    )
    .on_press(Msg::UnfilteredSelection(
    selection::UnfilteredMsg::Select(
    selection::Select::UntrackedFile {
    ix,
    path: file.clone(),
    },
    ),
    ))
    .class(hierarchical_button_class(selection)))
    })
  • replacement in inflorescence_view/src/view.rs at line 185
    [8.3244][8.3244:3290](),[8.3290][24.5094:5131]()
    changed_files.iter().enumerate().map(
    |(ix, (file, _diffs))| {
    [8.3244]
    [24.5131]
    changed_files
    .iter()
    .enumerate()
    .map(|(ix, (file, _diffs))| {
  • replacement in inflorescence_view/src/view.rs at line 190
    [24.5203][23.824:913](),[23.824][23.824:913](),[23.913][24.5204:5308](),[24.5308][23.1022:1045](),[23.1022][23.1022:1045](),[21.804][14.4907:5091](),[23.1045][14.4907:5091](),[2.16783][14.4907:5091](),[9.841][2.16961:16980](),[10.4658][2.16961:16980](),[14.5091][2.16961:16980](),[2.16961][2.16961:16980](),[21.1377][21.1377:1445](),[21.1445][24.5309:5356](),[21.1485][20.85:147](),[24.5356][20.85:147](),[4.2912][20.85:147](),[20.147][4.2912:3005](),[4.2912][4.2912:3005](),[4.3005][19.579:672](),[19.672][24.5357:5441](),[24.5441][19.673:701](),[10.4821][19.673:701](),[19.701][3.7828:7898](),[15.1063][3.7828:7898](),[2.17242][3.7828:7898](),[3.7898][21.1486:1628](),[21.1628][2.17330:17345](),[2.17330][2.17330:17345](),[2.17345][7.1618:1628]()
    let to_record_toggle =
    el(checkbox::three_way(state)
    .on_press_with(||Msg::ToRecord(to_record::Msg::ToggleFile{path :file.clone()}))
    );
    let is_selected = matches!(selection,
    selection::Unified::Status(Some(selection::Status::ChangedFile{ ix: selected_ix, .. })) if &ix == selected_ix
    );
    let file_name_view =
    el(button(
    text(file.to_string())
    .shaping(text::Shaping::Advanced)
    .wrapping(text::Wrapping::WordOrGlyph)
    )
    .on_press(Msg::UnfilteredSelection(selection::UnfilteredMsg::Select(
    selection::Select::ChangedFile{ix, path: file.clone()},
    )))
    .class(selectable_button_class(is_selected)),
    );
    el(row([
    to_record_toggle,
    file_name_view,
    ]))
    },
    )
    [24.5203]
    [5.865]
    let to_record_toggle = el(checkbox::three_way(state)
    .on_press_with(|| {
    Msg::ToRecord(to_record::Msg::ToggleFile {
    path: file.clone(),
    })
    }));
    let selection = match selection {
    selection::Unified::Status(Some(
    selection::Status::ChangedFile {
    ix: selected_ix,
    diff_selected,
    ..
    },
    )) if &ix == selected_ix => {
    if *diff_selected {
    HierarchicalSelection::ChildSelected
    } else {
    HierarchicalSelection::Selected
    }
    }
    _ => HierarchicalSelection::NotSelected,
    };
    let file_name_view = el(button(
    text(file.to_string())
    .shaping(text::Shaping::Advanced)
    .wrapping(text::Wrapping::WordOrGlyph),
    )
    .on_press(Msg::UnfilteredSelection(
    selection::UnfilteredMsg::Select(
    selection::Select::ChangedFile {
    ix,
    path: file.clone(),
    },
    ),
    ))
    .class(hierarchical_button_class(selection)));
    el(row([to_record_toggle, file_name_view]))
    })
  • replacement in inflorescence_view/src/view.rs at line 231
    [12.2268][14.5092:5134](),[14.5134][12.2269:2342](),[8.3422][12.2269:2342](),[12.2342][14.5135:5339](),[9.992][2.17818:17837](),[10.4985][2.17818:17837](),[14.5339][2.17818:17837](),[2.17818][2.17818:17837](),[2.17837][18.181:247](),[18.247][2.18292:18307](),[12.2399][2.18292:18307](),[2.18292][2.18292:18307](),[2.18307][7.1671:1681]()
    short_log.iter().enumerate().map(
    |(
    ix,
    entry,
    )| {
    let is_selected = matches!(selection,
    selection::Unified::Status(Some(selection::Status::LogChange(selection::LogChange{ ix: selected_ix, .. }))) if &ix == selected_ix
    );
    view_log_change_selection(ix, entry, is_selected)
    },
    )
    [12.2268]
    [5.944]
    short_log.iter().enumerate().map(|(ix, entry)| {
    let selection = match selection {
    selection::Unified::Status(Some(
    selection::Status::LogChange(selection::LogChange {
    ix: selected_ix,
    file,
    ..
    }),
    )) if &ix == selected_ix => {
    if file.is_some() {
    HierarchicalSelection::ChildSelected
    } else {
    HierarchicalSelection::Selected
    }
    }
    _ => HierarchicalSelection::NotSelected,
    };
    view_log_change_selection(ix, entry, selection)
    })
  • replacement in inflorescence_view/src/view.rs at line 363
    [14.8405][16.1146:1454](),[16.1454][19.1161:1309](),[19.1309][16.1603:1699](),[16.1603][16.1603:1699]()
    let files = entry.file_paths.iter().enumerate().map(|(ix, path)| {
    let is_selected = matches!(file, Some(selection::LogChangeFileSelection{ path: selected_path, .. }) if selected_path == path);
    el(button(text(path)).on_press_with(move || {
    Msg::UnfilteredSelection(selection::UnfilteredMsg::Select(selection::Select::LogChangeFile { ix, path: path.clone() }))
    }).class(selectable_button_class(is_selected)))
    });
    [14.8405]
    [16.1699]
    let files = entry.file_paths.iter().enumerate().map(
    |(ix, path)| {
    let selection = match file {
    Some(selection::LogChangeFileSelection {
    path: selected_path,
    diff_selected,
    ..
    }) if selected_path == path => {
    if *diff_selected {
    HierarchicalSelection::ChildSelected
    } else {
    HierarchicalSelection::Selected
    }
    }
    _ => HierarchicalSelection::NotSelected,
    };
    el(button(text(path))
    .on_press_with(move || {
    Msg::UnfilteredSelection(
    selection::UnfilteredMsg::Select(
    selection::Select::LogChangeFile {
    ix,
    path: path.clone(),
    },
    ),
    )
    })
    .class(hierarchical_button_class(selection)))
    },
    );
  • replacement in inflorescence_view/src/view.rs at line 664
    [16.5127][16.5127:5307]()
    let is_selected = matches!(selection,
    selection::Unified::Channel(Some(selection::Channel{ ix: selected_ix, .. })) if &ix == selected_ix
    );
    [16.5127]
    [16.5307]
    let selection = match selection {
    selection::Unified::Channel(Some(selection::Channel {
    ix: selected_ix,
    log,
    ..
    })) if &ix == selected_ix => {
    if log.is_some() {
    HierarchicalSelection::ChildSelected
    } else {
    HierarchicalSelection::Selected
    }
    }
    _ => HierarchicalSelection::NotSelected,
    };
  • replacement in inflorescence_view/src/view.rs at line 679
    [16.5344][19.1548:1692](),[17.172][16.5405:5467](),[19.1692][16.5405:5467](),[16.5405][16.5405:5467]()
    .on_press(Msg::UnfilteredSelection(selection::UnfilteredMsg::Select(selection::Select::Channel { ix, name: channel.clone() })))
    .class(selectable_button_class(is_selected)))
    [16.5344]
    [16.5467]
    .on_press(Msg::UnfilteredSelection(
    selection::UnfilteredMsg::Select(
    selection::Select::Channel {
    ix,
    name: channel.clone(),
    },
    ),
    ))
    .class(hierarchical_button_class(selection)))
  • replacement in inflorescence_view/src/view.rs at line 720
    [16.6524][16.6524:6757](),[16.6757][18.515:581]()
    let is_selected = matches!(selection,
    selection::Unified::Channel(Some(selection::Channel { log: Some(selection::LogChange { ix: selected_ix, .. }), .. })) if &ix == selected_ix
    );
    view_log_change_selection(ix, entry, is_selected)
    [16.6524]
    [16.6813]
    let selection = match selection {
    selection::Unified::Channel(Some(selection::Channel {
    log:
    Some(selection::LogChange {
    ix: selected_ix,
    file,
    ..
    }),
    ..
    })) if &ix == selected_ix => {
    if file.is_some() {
    HierarchicalSelection::ChildSelected
    } else {
    HierarchicalSelection::Selected
    }
    }
    _ => HierarchicalSelection::NotSelected,
    };
    view_log_change_selection(ix, entry, selection)
  • replacement in inflorescence_view/src/view.rs at line 799
    [16.9050][16.9050:9366](),[16.9366][19.1768:1920](),[19.1920][16.9519:9623](),[16.9519][16.9519:9623]()
    let files = entry.file_paths.iter().enumerate().map(|(ix, path)| {
    let is_selected = matches!(file, Some(selection::LogChangeFileSelection{ path: selected_path, .. }) if selected_path == path);
    el(button(text(path)).on_press_with(move || {
    Msg::UnfilteredSelection(selection::UnfilteredMsg::Select(selection::Select::LogChangeFile { ix, path: path.clone() }))
    }).class(selectable_button_class(is_selected)))
    });
    [16.9050]
    [18.717]
    let files = entry.file_paths.iter().enumerate().map(
    |(ix, path)| {
    let selection = match file {
    Some(selection::LogChangeFileSelection {
    path: selected_path,
    diff_selected,
    ..
    }) if selected_path == path => {
    if *diff_selected {
    HierarchicalSelection::ChildSelected
    } else {
    HierarchicalSelection::Selected
    }
    }
    _ => HierarchicalSelection::NotSelected,
    };
    el(button(text(path))
    .on_press_with(move || {
    Msg::UnfilteredSelection(
    selection::UnfilteredMsg::Select(
    selection::Select::LogChangeFile {
    ix,
    path: path.clone(),
    },
    ),
    )
    })
    .class(hierarchical_button_class(selection)))
    },
    );
  • replacement in inflorescence_view/src/view.rs at line 951
    [12.6180][14.14509:14678](),[14.14678][12.6337:6352](),[12.6337][12.6337:6352](),[12.6352][18.1246:1308]()
    let is_selected = matches!(selection,
    selection::Unified::EntireLog(Some(selection::LogChange{ ix: selected_ix, .. })) if &ix == selected_ix
    );
    view_log_change_selection(ix, entry, is_selected)
    [12.6180]
    [12.6404]
    let selection = match selection {
    selection::Unified::EntireLog(Some(selection::LogChange {
    ix: selected_ix,
    file,
    ..
    })) if &ix == selected_ix => {
    if file.is_some() {
    HierarchicalSelection::ChildSelected
    } else {
    HierarchicalSelection::Selected
    }
    }
    _ => HierarchicalSelection::NotSelected,
    };
    view_log_change_selection(ix, entry, selection)
  • replacement in inflorescence_view/src/view.rs at line 1009
    [16.13783][16.13783:13870](),[16.13870][14.15315:15470](),[12.7873][14.15315:15470](),[14.15470][12.8017:8091](),[12.8017][12.8017:8091](),[12.8091][19.2241:2393](),[15.2118][12.8223:8327](),[19.2393][12.8223:8327](),[12.8223][12.8223:8327]()
    let files = entry.file_paths.iter().enumerate().map(|(ix, path)| {
    let is_selected = matches!(file, Some(selection::LogChangeFileSelection{ path: selected_path, .. }) if selected_path == path);
    el(button(text(path)).on_press_with(move || {
    Msg::UnfilteredSelection(selection::UnfilteredMsg::Select(selection::Select::LogChangeFile { ix, path: path.clone() }))
    }).class(selectable_button_class(is_selected)))
    });
    [16.13783]
    [18.1418]
    let files = entry.file_paths.iter().enumerate().map(
    |(ix, path)| {
    let selection = match file {
    Some(selection::LogChangeFileSelection {
    path: selected_path,
    diff_selected,
    ..
    }) if selected_path == path => {
    if *diff_selected {
    HierarchicalSelection::ChildSelected
    } else {
    HierarchicalSelection::Selected
    }
    }
    _ => HierarchicalSelection::NotSelected,
    };
    el(button(text(path))
    .on_press_with(move || {
    Msg::UnfilteredSelection(
    selection::UnfilteredMsg::Select(
    selection::Select::LogChangeFile {
    ix,
    path: path.clone(),
    },
    ),
    )
    })
    .class(hierarchical_button_class(selection)))
    },
    );
  • replacement in inflorescence_view/src/view.rs at line 1376
    [11.2125][3.8111:8272](),[9.10086][3.8111:8272](),[2.24509][3.8111:8272]()
    fn selectable_button_class(is_selected: bool) -> theme::Button {
    if is_selected {
    theme::Button::Selected
    } else {
    theme::Button::Normal
    [11.2125]
    [2.25105]
    #[derive(Debug)]
    enum HierarchicalSelection {
    NotSelected,
    Selected,
    ChildSelected,
    }
    fn hierarchical_button_class(
    selection: HierarchicalSelection,
    ) -> theme::Button {
    match selection {
    HierarchicalSelection::NotSelected => theme::Button::Normal,
    HierarchicalSelection::Selected => theme::Button::Selected,
    HierarchicalSelection::ChildSelected => theme::Button::ChildSelected,
  • replacement in inflorescence_view/src/view.rs at line 1407
    [12.15376][12.15376:15399]()
    is_selected: bool,
    [12.15376]
    [12.15399]
    selection: HierarchicalSelection,
  • replacement in inflorescence_view/src/view.rs at line 1422
    [19.3133][12.15795:15854](),[12.15795][12.15795:15854]()
    .class(selectable_button_class(is_selected))),
    [19.3133]
    [22.846]
    .class(hierarchical_button_class(selection))),
  • replacement in inflorescence_view/src/theme.rs at line 28
    [4.590][4.590:640]()
    const MAGENTA_LIGHTEST: Color = color!(0xF38EFA);
    [4.590]
    [4.640]
    const MAGENTA_LIGHTER: Color = color!(0xF38EFA);
    const MAGENTA_LIGHTEST: Color = color!(0xF7BCFC);
  • edit in inflorescence_view/src/theme.rs at line 93
    [3.952]
    [4.1286]
    /// A button is selected, but the selection focus is in its children
    /// sub-selection. The button is less highlighted than `Selected` to
    /// indicate this
    ChildSelected,
  • replacement in inflorescence_view/src/theme.rs at line 210
    [3.2836][4.1879:1950]()
    background: Some(Background::Color(MAGENTA_LIGHTEST)),
    [3.2836]
    [4.1950]
    background: Some(Background::Color(
    if let button::Status::Hovered = status {
    MAGENTA_LIGHTEST
    } else {
    MAGENTA_LIGHTER
    },
    )),
    text_color: BLUE_DARK,
    border: Border {
    color: MAGENTA,
    width: border_width,
    radius: border_radius,
    },
    ..base
    },
    Button::ChildSelected => button::Style {
    background: Some(Background::Color(
    if let button::Status::Hovered = status {
    MAGENTA_LIGHTEST
    } else {
    Color {
    a: 0.8,
    ..MAGENTA_LIGHTER
    }
    },
    )),
  • replacement in inflorescence_view/src/theme.rs at line 345
    [6.5132][6.5132:5194]()
    Scrollable::Selected => MAGENTA_LIGHTEST,
    [6.5132]
    [6.5194]
    Scrollable::Selected => MAGENTA_LIGHTER,
  • replacement in inflorescence_view/src/theme.rs at line 391
    [13.1220][13.1220:1282]()
    Scrollable::Selected => MAGENTA_LIGHTEST,
    [13.1220]
    [13.1282]
    Scrollable::Selected => MAGENTA_LIGHTER,