forked from 1b-agent/opencadstudio-upstream
feat(commandline): fade history entries off the overlay after 3 s
The overlay used to keep the last four history lines on screen indefinitely, leaving stale command output cluttering the viewport. Stamp each `HistoryEntry` with its `Instant` of insertion and filter the rows shown on the overlay to entries younger than 3 s. `CommandLine::has_visible_history()` gates a per-frame tick subscription so the view re-renders and the entry disappears on time. The subscription auto-stops once no entry is fresh enough, keeping the idle CPU draw at zero (#18). Full history stays in the backing `Vec` — the upcoming dropdown button will surface it on demand. Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
862e5aed31
commit
5a8c79deb5
2 files changed
+56
-16
No files matched your search
@@ -661,8 +661,19 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
Subscription::none()
|
||||
};
|
||||
// While the command-line overlay is still displaying any
|
||||
// recently-pushed history entry, re-render every frame so the
|
||||
// entry disappears at the moment its visible window expires.
|
||||
// The subscription auto-stops once no entry is fresh enough
|
||||
// (typically within a few seconds of the last command).
|
||||
let history_tick = if self.command_line.has_visible_history() {
|
||||
window::frames().map(Message::Tick)
|
||||
} else {
|
||||
Subscription::none()
|
||||
};
|
||||
iced::Subscription::batch([
|
||||
frames,
|
||||
history_tick,
|
||||
event::listen_with(|ev, status, win_id| {
|
||||
use iced::event::Status;
|
||||
match ev {
|
||||
|
||||
+45
-16
@@ -1,11 +1,17 @@
|
||||
//! OpenCADStudio-style command line — bottom panel with input and history
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::app::Message;
|
||||
use iced::widget::{column, container, row, text, text_input};
|
||||
use iced::{Background, Border, Color, Element, Length, Theme};
|
||||
|
||||
pub const CMD_INPUT_ID: &str = "cmd_input";
|
||||
|
||||
/// How long a history entry stays visible on the overlay before fading
|
||||
/// out. Picking the full archive happens through the dropdown button.
|
||||
const HISTORY_VISIBLE_SECS: f32 = 3.0;
|
||||
|
||||
fn cmd_input_id() -> iced::widget::Id {
|
||||
iced::widget::Id::new(CMD_INPUT_ID)
|
||||
}
|
||||
@@ -28,6 +34,10 @@ pub struct CommandLine {
|
||||
pub struct HistoryEntry {
|
||||
pub kind: EntryKind,
|
||||
pub text: String,
|
||||
/// When this entry was pushed. Used by the overlay to fade entries
|
||||
/// out after `HISTORY_VISIBLE_SECS`. The dropdown popup ignores it
|
||||
/// and always shows the whole list.
|
||||
pub created_at: Instant,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -112,28 +122,47 @@ impl CommandLine {
|
||||
self.push(EntryKind::Info, msg.to_string());
|
||||
}
|
||||
fn push(&mut self, kind: EntryKind, text: String) {
|
||||
self.history.push(HistoryEntry { kind, text });
|
||||
self.history.push(HistoryEntry {
|
||||
kind,
|
||||
text,
|
||||
created_at: Instant::now(),
|
||||
});
|
||||
if self.history.len() > MAX_HISTORY {
|
||||
self.history.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// `true` while at least one history entry is still within the
|
||||
/// visible window — the host app uses this to drive a low-frequency
|
||||
/// tick subscription so the overlay re-renders and fades the entry
|
||||
/// once it expires.
|
||||
pub fn has_visible_history(&self) -> bool {
|
||||
self.history
|
||||
.iter()
|
||||
.any(|e| e.created_at.elapsed().as_secs_f32() < HISTORY_VISIBLE_SECS)
|
||||
}
|
||||
|
||||
pub fn view(&self) -> Element<'_, Message> {
|
||||
let history_rows =
|
||||
self.history
|
||||
.iter()
|
||||
.rev()
|
||||
.take(4)
|
||||
.rev()
|
||||
.fold(column![].spacing(0), |col, entry| {
|
||||
let color = match entry.kind {
|
||||
EntryKind::Command => CMD_COLOR,
|
||||
EntryKind::Output => OUT_COLOR,
|
||||
EntryKind::Error => ERR_COLOR,
|
||||
EntryKind::Info => INFO_COLOR,
|
||||
};
|
||||
col.push(container(text(&entry.text).size(11).color(color)).padding([1, 8]))
|
||||
});
|
||||
// Only the most recent entries pushed within the last few
|
||||
// seconds show on the overlay. The dropdown button keeps the
|
||||
// full backlog reachable when the user actually wants it.
|
||||
let visible: Vec<&HistoryEntry> = self
|
||||
.history
|
||||
.iter()
|
||||
.filter(|e| e.created_at.elapsed().as_secs_f32() < HISTORY_VISIBLE_SECS)
|
||||
.collect();
|
||||
let start = visible.len().saturating_sub(4);
|
||||
let history_rows = visible[start..]
|
||||
.iter()
|
||||
.fold(column![].spacing(0), |col, entry| {
|
||||
let color = match entry.kind {
|
||||
EntryKind::Command => CMD_COLOR,
|
||||
EntryKind::Output => OUT_COLOR,
|
||||
EntryKind::Error => ERR_COLOR,
|
||||
EntryKind::Info => INFO_COLOR,
|
||||
};
|
||||
col.push(container(text(&entry.text).size(11).color(color)).padding([1, 8]))
|
||||
});
|
||||
let prompt = container(text("Command:").size(11).color(PROMPT_COLOR)).padding([5, 8]);
|
||||
let input = text_input("", &self.input)
|
||||
.id(cmd_input_id())
|
||||
|
||||
Reference in new issue
Block a user