Add quick multi-item entry to categories
Two new ways to add multiple items without repeating yourself: 1. :add-items <category> item1 item2 item3 ... Adds all space-separated items in one command. 2. Category panel quick-add mode (press 'a' or 'o' on a category): - Opens an inline prompt at the bottom of the panel - Enter adds the item and clears the buffer — stays open for next entry - Tab does the same as Enter - Esc closes and returns to the category list - The panel border turns green and the title updates to signal add mode - Item count in the category list updates live as items are added Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -23,17 +23,21 @@ impl<'a> CategoryPanel<'a> {
|
||||
|
||||
impl<'a> Widget for CategoryPanel<'a> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let is_active = matches!(self.mode, AppMode::CategoryPanel);
|
||||
let border_style = if is_active {
|
||||
Style::default().fg(Color::Cyan)
|
||||
let is_item_add = matches!(self.mode, AppMode::ItemAdd { .. });
|
||||
let is_active = matches!(self.mode, AppMode::CategoryPanel) || is_item_add;
|
||||
|
||||
let (border_color, title) = if is_item_add {
|
||||
(Color::Green, " Categories — Adding items (Enter:add Esc:done) ")
|
||||
} else if is_active {
|
||||
(Color::Cyan, " Categories a:add-items Space:cycle-axis ")
|
||||
} else {
|
||||
Style::default().fg(Color::DarkGray)
|
||||
(Color::DarkGray, " Categories ")
|
||||
};
|
||||
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(border_style)
|
||||
.title(" Categories [Enter] cycle axis ");
|
||||
.border_style(Style::default().fg(border_color))
|
||||
.title(title);
|
||||
let inner = block.inner(area);
|
||||
block.render(area, buf);
|
||||
|
||||
@ -45,13 +49,18 @@ impl<'a> Widget for CategoryPanel<'a> {
|
||||
let cat_names: Vec<&str> = self.model.category_names();
|
||||
if cat_names.is_empty() {
|
||||
buf.set_string(inner.x, inner.y,
|
||||
"(no categories)",
|
||||
"(no categories — use :add-cat <name>)",
|
||||
Style::default().fg(Color::DarkGray));
|
||||
return;
|
||||
}
|
||||
|
||||
// How many rows for the list vs the prompt at bottom
|
||||
let prompt_rows = if is_item_add { 2u16 } else { 0 };
|
||||
let list_height = inner.height.saturating_sub(prompt_rows);
|
||||
|
||||
for (i, cat_name) in cat_names.iter().enumerate() {
|
||||
if inner.y + i as u16 >= inner.y + inner.height { break; }
|
||||
if i as u16 >= list_height { break; }
|
||||
let y = inner.y + i as u16;
|
||||
|
||||
let axis = view.axis_of(cat_name);
|
||||
let axis_str = match axis {
|
||||
@ -67,31 +76,50 @@ impl<'a> Widget for CategoryPanel<'a> {
|
||||
Axis::Unassigned => Color::DarkGray,
|
||||
};
|
||||
|
||||
let cat = self.model.category(cat_name);
|
||||
let item_count = cat.map(|c| c.items.len()).unwrap_or(0);
|
||||
let item_count = self.model.category(cat_name).map(|c| c.items.len()).unwrap_or(0);
|
||||
|
||||
let is_selected = i == self.cursor && is_active;
|
||||
let base_style = if is_selected {
|
||||
// Highlight the selected category both in CategoryPanel and ItemAdd modes
|
||||
let is_selected_cat = if is_item_add {
|
||||
if let AppMode::ItemAdd { category, .. } = self.mode {
|
||||
*cat_name == category.as_str()
|
||||
} else { false }
|
||||
} else {
|
||||
i == self.cursor && is_active
|
||||
};
|
||||
|
||||
let base_style = if is_selected_cat {
|
||||
Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
|
||||
// Background fill for selected row
|
||||
if is_selected {
|
||||
if is_selected_cat {
|
||||
let fill = " ".repeat(inner.width as usize);
|
||||
buf.set_string(inner.x, inner.y + i as u16, &fill, base_style);
|
||||
buf.set_string(inner.x, y, &fill, base_style);
|
||||
}
|
||||
|
||||
let name_part = format!(" {cat_name} ({item_count})");
|
||||
let axis_part = format!(" [{axis_str}]");
|
||||
let available = inner.width as usize;
|
||||
|
||||
buf.set_string(inner.x, inner.y + i as u16, &name_part, base_style);
|
||||
if name_part.len() + axis_part.len() < available {
|
||||
let axis_x = inner.x + name_part.len() as u16;
|
||||
buf.set_string(axis_x, inner.y + i as u16, &axis_part,
|
||||
if is_selected { base_style } else { Style::default().fg(axis_color) });
|
||||
buf.set_string(inner.x, y, &name_part, base_style);
|
||||
if name_part.len() + axis_part.len() < inner.width as usize {
|
||||
buf.set_string(inner.x + name_part.len() as u16, y, &axis_part,
|
||||
if is_selected_cat { base_style } else { Style::default().fg(axis_color) });
|
||||
}
|
||||
}
|
||||
|
||||
// Inline prompt at the bottom when in ItemAdd mode
|
||||
if let AppMode::ItemAdd { category, buffer } = self.mode {
|
||||
let sep_y = inner.y + list_height;
|
||||
let prompt_y = sep_y + 1;
|
||||
if sep_y < inner.y + inner.height {
|
||||
let sep = "─".repeat(inner.width as usize);
|
||||
buf.set_string(inner.x, sep_y, &sep, Style::default().fg(Color::Green));
|
||||
}
|
||||
if prompt_y < inner.y + inner.height {
|
||||
let prompt = format!(" + {buffer}▌");
|
||||
buf.set_string(inner.x, prompt_y, &prompt,
|
||||
Style::default().fg(Color::Green).add_modifier(Modifier::BOLD));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user