diff --git a/src/app.rs b/src/app.rs index 077903b..6482df2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -20,6 +20,7 @@ pub struct App { pub new_entry_project: String, pub new_entry_cursor: usize, pub new_entry_mode: NewEntryMode, // Task or Project + pub status_message: Option<(String, std::time::Instant)>, } pub enum NewEntryMode { @@ -38,10 +39,14 @@ impl App { new_entry_project: String::new(), new_entry_cursor: 0, new_entry_mode: NewEntryMode::Task, + status_message: None, }) } pub fn handle_event(&mut self, event: Event) -> anyhow::Result { + // Update status message + self.update_status_message(); + match self.current_screen { Screen::Main => self.handle_main_event(event), Screen::Help => self.handle_help_event(event), @@ -69,6 +74,7 @@ impl App { (KeyCode::Char('?'), _) => self.current_screen = Screen::Help, (KeyCode::Char('c'), _) => self.edit_app_config()?, (KeyCode::Char('n'), _) => self.start_new_entry(), + (KeyCode::Char('d'), _) => self.delete_current_item()?, _ => {} }, _ => {} @@ -231,6 +237,73 @@ impl App { Ok(()) } + fn delete_current_item(&mut self) -> anyhow::Result<()> { + // Check if this is the active timer + let should_stop = { + let items = match self.state.current_pane { + 0 => &self.state.permanent_items, + 1 => &self.state.recurring_items, + 2 => &self.state.recent_items, + _ => return Ok(()), + }; + + let index = self.state.selected_indices[self.state.current_pane]; + + if !items.is_empty() && index < items.len() { + if let Some((ref active, _)) = self.state.active_timer { + items[index].name == active.name + } else { + false + } + } else { + return Ok(()); + } + }; + + // Stop timer if needed + if should_stop { + self.state.stop_timer()?; + } + + // Now delete the item + let items = match self.state.current_pane { + 0 => &mut self.state.permanent_items, + 1 => &mut self.state.recurring_items, + 2 => &mut self.state.recent_items, + _ => return Ok(()), + }; + + let index = self.state.selected_indices[self.state.current_pane]; + + if !items.is_empty() && index < items.len() { + // Remove the item + items.remove(index); + + // Adjust index if we're at the end + if !items.is_empty() && index == items.len() { + self.state.selected_indices[self.state.current_pane] = items.len() - 1; + } + + // Save changes + self.state.save()?; + } + + Ok(()) + } + + fn set_status_message>(&mut self, message: S) { + self.status_message = Some((message.into(), std::time::Instant::now())); + } + + fn update_status_message(&mut self) { + // Clear status message after 3 seconds + if let Some((_, instant)) = self.status_message { + if instant.elapsed().as_secs() >= 3 { + self.status_message = None; + } + } + } + fn start_new_entry(&mut self) { self.current_screen = Screen::NewEntry; self.new_entry_buffer.clear(); diff --git a/src/ui.rs b/src/ui.rs index bae37f1..71b733a 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -23,12 +23,19 @@ pub fn render(frame: &mut Frame, app: &App) { fn render_main(frame: &mut Frame, app: &App) { // Calculate layout - accounting for bottom bar if needed let show_bottom_bar = app.config.show_help_hint || app.config.show_command_hints; - let constraints = if show_bottom_bar { + let has_status = app.status_message.is_some(); + let bottom_height = if show_bottom_bar { + if has_status { 2 } else { 1 } + } else { + if has_status { 1 } else { 0 } + }; + + let constraints = if bottom_height > 0 { vec![ Constraint::Min(3), // At least 3 lines for each section Constraint::Min(3), Constraint::Min(3), - Constraint::Length(1), // Command bar + Constraint::Length(bottom_height), // Command bar + optional status ] } else { vec![ @@ -43,7 +50,7 @@ fn render_main(frame: &mut Frame, app: &App) { .constraints(constraints) .split(frame.size()); - let main_height = if show_bottom_bar { + let main_height = if bottom_height > 0 { chunks[0].height + chunks[1].height + chunks[2].height } else { frame.size().height @@ -90,13 +97,8 @@ fn render_main(frame: &mut Frame, app: &App) { ); // Render bottom bar if needed - if show_bottom_bar { - let bottom_area = Rect::new( - 0, - frame.size().height - 1, - frame.size().width, - 1, - ); + if bottom_height > 0 { + let bottom_area = chunks[3]; render_bottom_bar(frame, bottom_area, app); } } @@ -173,10 +175,39 @@ fn render_new_entry(frame: &mut Frame, app: &App) { } fn render_bottom_bar(frame: &mut Frame, area: Rect, app: &App) { - if app.config.show_command_hints { + // Split the area into status and command sections if needed + let chunks = if app.status_message.is_some() { + Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(1), // Status message + Constraint::Length(1), // Command bar + ]) + .split(area) + } else { + Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(1)]) + .split(area) + }; + + let mut command_line_idx = 0; + + // Render status message if present + if let Some((ref message, _)) = app.status_message { + let text = Paragraph::new(message.as_str()) + .style(Style::default().fg(Color::Yellow)) + .alignment(Alignment::Center); + frame.render_widget(text, chunks[0]); + command_line_idx = 1; + } + + // Render command hints + if app.config.show_command_hints && chunks.len() > command_line_idx { let commands = vec![ ("c", "config"), ("n", "new"), + ("d", "delete"), ("q", "quit"), ]; @@ -187,9 +218,14 @@ fn render_bottom_bar(frame: &mut Frame, area: Rect, app: &App) { let command_area = if app.config.show_help_hint { // Leave space for help hint - Rect::new(area.x, area.y, area.width.saturating_sub(12), area.height) + Rect::new( + chunks[command_line_idx].x, + chunks[command_line_idx].y, + chunks[command_line_idx].width.saturating_sub(12), + 1 + ) } else { - area + chunks[command_line_idx] }; let command_bar = Paragraph::new(command_text) @@ -199,14 +235,14 @@ fn render_bottom_bar(frame: &mut Frame, area: Rect, app: &App) { frame.render_widget(command_bar, command_area); } - if app.config.show_help_hint { + if app.config.show_help_hint && chunks.len() > command_line_idx { let help_hint = Paragraph::new("(?) for help") .alignment(Alignment::Right) .style(Style::default().fg(Color::DarkGray)); let help_area = Rect::new( - area.width.saturating_sub(12), - area.y, + chunks[command_line_idx].x + chunks[command_line_idx].width.saturating_sub(12), + chunks[command_line_idx].y, 12, 1, ); @@ -267,6 +303,7 @@ fn render_help(frame: &mut Frame, _app: &App) { "j/k, arrows - Navigate", "h/l, arrows - Switch panes", "Enter - Start/stop timer", + "d - Delete task", "Ctrl+e - Edit tasks config", "c - Edit app config", "n - New task",