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 <noreply@anthropic.com>
This commit is contained in:
parent
862e5aed31
commit
5a8c79deb5
2 changed files with 56 additions and 16 deletions
|
|
@ -661,8 +661,19 @@ impl OpenCADStudio {
|
||||||
} else {
|
} else {
|
||||||
Subscription::none()
|
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([
|
iced::Subscription::batch([
|
||||||
frames,
|
frames,
|
||||||
|
history_tick,
|
||||||
event::listen_with(|ev, status, win_id| {
|
event::listen_with(|ev, status, win_id| {
|
||||||
use iced::event::Status;
|
use iced::event::Status;
|
||||||
match ev {
|
match ev {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
//! OpenCADStudio-style command line — bottom panel with input and history
|
//! OpenCADStudio-style command line — bottom panel with input and history
|
||||||
|
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use iced::widget::{column, container, row, text, text_input};
|
use iced::widget::{column, container, row, text, text_input};
|
||||||
use iced::{Background, Border, Color, Element, Length, Theme};
|
use iced::{Background, Border, Color, Element, Length, Theme};
|
||||||
|
|
||||||
pub const CMD_INPUT_ID: &str = "cmd_input";
|
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 {
|
fn cmd_input_id() -> iced::widget::Id {
|
||||||
iced::widget::Id::new(CMD_INPUT_ID)
|
iced::widget::Id::new(CMD_INPUT_ID)
|
||||||
}
|
}
|
||||||
|
|
@ -28,6 +34,10 @@ pub struct CommandLine {
|
||||||
pub struct HistoryEntry {
|
pub struct HistoryEntry {
|
||||||
pub kind: EntryKind,
|
pub kind: EntryKind,
|
||||||
pub text: String,
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -112,19 +122,38 @@ impl CommandLine {
|
||||||
self.push(EntryKind::Info, msg.to_string());
|
self.push(EntryKind::Info, msg.to_string());
|
||||||
}
|
}
|
||||||
fn push(&mut self, kind: EntryKind, text: 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 {
|
if self.history.len() > MAX_HISTORY {
|
||||||
self.history.remove(0);
|
self.history.remove(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(&self) -> Element<'_, Message> {
|
/// `true` while at least one history entry is still within the
|
||||||
let history_rows =
|
/// 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
|
self.history
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.any(|e| e.created_at.elapsed().as_secs_f32() < HISTORY_VISIBLE_SECS)
|
||||||
.take(4)
|
}
|
||||||
.rev()
|
|
||||||
|
pub fn view(&self) -> Element<'_, Message> {
|
||||||
|
// 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| {
|
.fold(column![].spacing(0), |col, entry| {
|
||||||
let color = match entry.kind {
|
let color = match entry.kind {
|
||||||
EntryKind::Command => CMD_COLOR,
|
EntryKind::Command => CMD_COLOR,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue