use std::time::{Duration, Instant};
use super::App;
impl App {
pub(super) fn accelerated_scroll(&mut self, up: bool) -> u32 {
let now = Instant::now();
let same_direction = self.state.scroll_last_direction == Some(up);
let within_window = self
.state
.scroll_last_time
.is_some_and(|t| now.duration_since(t) < Duration::from_millis(200));
if same_direction && within_window {
self.state.scroll_accel_level = (self.state.scroll_accel_level + 1).min(2);
} else {
self.state.scroll_accel_level = 0;
}
self.state.scroll_last_direction = Some(up);
self.state.scroll_last_time = Some(now);
match self.state.scroll_accel_level {
0 => 1,
1 => 2,
_ => 3,
}
}
pub(super) fn update_autocomplete(&mut self) {
if self.state.agent_active {
self.state.autocomplete.dismiss();
return;
}
let text_before_cursor = self.state.input_buffer[..self.state.input_cursor].to_string();
self.state.autocomplete.update(&text_before_cursor);
}
pub(super) fn handle_tick(&mut self) {
if !self.state.welcome_panel.fade_complete {
let w = self.state.terminal_width;
let h = self.state.terminal_height;
let rain_w = ((w as f32 * 0.7) as usize).clamp(20, 90);
let rain_h = (h.saturating_sub(11) as usize).clamp(4, 20);
self.state.welcome_panel.ensure_rain_field(rain_w, rain_h);
self.state.welcome_panel.tick(w, h);
}
if self.state.agent_active
|| !self.state.active_tools.is_empty()
|| self.state.background_task_count > 0
{
self.state.spinner.tick();
}
if self
.state
.messages
.iter()
.rev()
.any(|m| m.role == super::DisplayRole::Reasoning && m.thinking_duration_secs.is_none())
{
self.state.message_generation += 1;
}
if !self.state.todo_items.is_empty() {
let all_done = self
.state
.todo_items
.iter()
.all(|i| i.status == crate::widgets::TodoDisplayStatus::Completed);
if !all_done {
self.state.todo_spinner_tick = self.state.todo_spinner_tick.wrapping_add(1);
}
}
for tool in &mut self.state.active_tools {
if !tool.is_finished() {
tool.elapsed_secs = tool.started_at.elapsed().as_secs();
tool.tick_count += 1;
}
}
for subagent in &mut self.state.active_subagents {
if !subagent.finished {
subagent.advance_tick();
}
}
let active_tools = &self.state.active_tools;
let task_watcher_open = self.state.task_watcher_open;
self.state.active_subagents.retain(|s| {
if !s.finished {
return true;
}
if s.backgrounded {
let grace = if task_watcher_open { 60 } else { 5 };
return s.finished_at.is_some_and(|t| t.elapsed().as_secs() < grace);
}
let has_active_tool = if let Some(ref ptid) = s.parent_tool_id {
active_tools.iter().any(|t| t.id == *ptid)
} else {
active_tools.iter().any(|t| {
t.name == "spawn_subagent"
&& t.args.get("task").and_then(|v| v.as_str()) == Some(&s.task)
})
};
if has_active_tool {
return true;
}
let grace = if task_watcher_open { 60 } else { 1 };
s.finished_at.is_some_and(|t| t.elapsed().as_secs() < grace)
});
if let Some(ref mut progress) = self.state.task_progress {
progress.elapsed_secs = progress.started_at.elapsed().as_secs();
}
let bg_agent_running = self.state.bg_agent_manager.running_count();
let bg_process_running = if let Ok(mgr) = self.task_manager.try_lock() {
mgr.running_count()
} else {
0
};
let bg_subagent_running = self
.state
.active_subagents
.iter()
.filter(|s| s.backgrounded && !s.finished)
.count();
let covered_bg_count: usize = {
let covered_ids: std::collections::HashSet<&String> = self
.state
.active_subagents
.iter()
.filter(|s| s.backgrounded && !s.finished)
.filter_map(|s| self.state.bg_subagent_map.get(&s.subagent_id))
.collect();
covered_ids
.iter()
.filter(|id| {
self.state
.bg_agent_manager
.get_task(id)
.is_some_and(|t| t.is_running())
})
.count()
};
self.state.background_task_count =
bg_agent_running + bg_process_running + bg_subagent_running - covered_bg_count;
if self.state.task_watcher_open {
let has_running = self.state.background_task_count > 0;
if has_running {
self.state.task_watcher_all_done_at = None;
} else if self.state.task_watcher_all_done_at.is_none() {
self.state.task_watcher_all_done_at = Some(Instant::now());
} else if self
.state
.task_watcher_all_done_at
.is_some_and(|t| t.elapsed() > Duration::from_secs(3))
{
self.state.task_watcher_open = false;
self.state.task_watcher_all_done_at = None;
self.state.force_clear = true;
}
}
if let Some((_, when)) = &self.state.last_task_completion
&& when.elapsed() > Duration::from_secs(3)
{
self.state.last_task_completion = None;
}
if let Some((_, when)) = &self.state.backgrounded_task_info
&& when.elapsed() > Duration::from_secs(3)
{
self.state.backgrounded_task_info = None;
}
self.state.toasts.retain(|t| !t.is_expired());
if !self.state.toasts.is_empty() {
self.state.dirty = true;
}
if self.state.leader_pending {
if let Some(ts) = self.state.leader_timestamp
&& ts.elapsed() > Duration::from_secs(2)
{
self.state.leader_pending = false;
self.state.leader_timestamp = None;
}
self.state.dirty = true;
}
if self.state.selection.active
&& let Some(direction) = self.state.selection.auto_scroll_direction
{
if direction < 0 {
self.state.scroll_offset = self.state.scroll_offset.saturating_add(1);
self.state.user_scrolled = true;
} else {
if self.state.scroll_offset > 0 {
self.state.scroll_offset = self.state.scroll_offset.saturating_sub(1);
}
}
self.state.dirty = true;
}
if !self.state.user_scrolled {
self.state.scroll_offset = 0;
}
}
}