rendering
This commit is contained in:
parent
36d698a3e1
commit
49cd6d6ab6
8 changed files with 178 additions and 157 deletions
|
|
@ -72,6 +72,7 @@ impl Agent {
|
|||
started: std::time::Instant::now(),
|
||||
expires_at: std::time::Instant::now() + std::time::Duration::from_secs(3600),
|
||||
});
|
||||
self.changed.notify_one();
|
||||
id
|
||||
}
|
||||
|
||||
|
|
@ -85,6 +86,7 @@ impl Agent {
|
|||
started: std::time::Instant::now(),
|
||||
expires_at: std::time::Instant::now() + ACTIVITY_LINGER,
|
||||
});
|
||||
self.changed.notify_one();
|
||||
}
|
||||
|
||||
/// Remove expired activities.
|
||||
|
|
@ -179,6 +181,8 @@ pub struct Agent {
|
|||
pub agent_cycles: crate::subconscious::subconscious::AgentCycleState,
|
||||
/// Shared active tools — Agent writes, TUI reads.
|
||||
pub active_tools: tools::SharedActiveTools,
|
||||
/// Fires when agent state changes — UI wakes on this instead of polling.
|
||||
pub changed: Arc<tokio::sync::Notify>,
|
||||
}
|
||||
|
||||
fn render_journal(entries: &[context::JournalEntry]) -> String {
|
||||
|
|
@ -237,6 +241,7 @@ impl Agent {
|
|||
generation: 0,
|
||||
agent_cycles,
|
||||
active_tools,
|
||||
changed: Arc::new(tokio::sync::Notify::new()),
|
||||
};
|
||||
|
||||
agent.load_startup_journal();
|
||||
|
|
@ -292,6 +297,7 @@ impl Agent {
|
|||
}
|
||||
}
|
||||
self.context.entries.push(entry);
|
||||
self.changed.notify_one();
|
||||
}
|
||||
|
||||
/// Append streaming text to the last entry (creating a partial
|
||||
|
|
@ -301,6 +307,7 @@ impl Agent {
|
|||
let msg = entry.message_mut();
|
||||
if msg.role == Role::Assistant {
|
||||
msg.append_content(text);
|
||||
self.changed.notify_one();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -308,6 +315,7 @@ impl Agent {
|
|||
self.context.entries.push(ConversationEntry::Message(
|
||||
Message::assistant(text),
|
||||
));
|
||||
self.changed.notify_one();
|
||||
}
|
||||
|
||||
pub fn budget(&self) -> ContextBudget {
|
||||
|
|
|
|||
|
|
@ -247,6 +247,7 @@ impl Mind {
|
|||
// Restore conversation
|
||||
let mut ag = self.agent.lock().await;
|
||||
ag.restore_from_log();
|
||||
ag.changed.notify_one();
|
||||
drop(ag);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -760,11 +760,14 @@ impl ScreenView for InteractScreen {
|
|||
fn label(&self) -> &'static str { "interact" }
|
||||
|
||||
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
||||
key: Option<KeyEvent>, app: &mut App) -> Option<ScreenAction> {
|
||||
// Handle keys
|
||||
if let Some(key) = key {
|
||||
events: &[ratatui::crossterm::event::Event], app: &mut App) {
|
||||
use ratatui::crossterm::event::Event;
|
||||
// Handle events
|
||||
for event in events {
|
||||
match event {
|
||||
Event::Key(key) if key.kind == ratatui::crossterm::event::KeyEventKind::Press => {
|
||||
match key.code {
|
||||
KeyCode::Esc => return Some(ScreenAction::Hotkey(HotkeyAction::Interrupt)),
|
||||
KeyCode::Esc => { let _ = self.mind_tx.send(crate::mind::MindCommand::Interrupt); }
|
||||
KeyCode::Enter if !key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::SHIFT) => {
|
||||
let input: String = self.textarea.lines().join("\n");
|
||||
if !input.is_empty() {
|
||||
|
|
@ -809,7 +812,11 @@ impl ScreenView for InteractScreen {
|
|||
ActivePane::Conversation => ActivePane::Autonomous,
|
||||
};
|
||||
}
|
||||
_ => { self.textarea.input(key); }
|
||||
_ => { self.textarea.input(*key); }
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse) => { self.handle_mouse(*mouse); }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -835,7 +842,6 @@ impl ScreenView for InteractScreen {
|
|||
|
||||
// Draw
|
||||
self.draw_main(frame, area, app);
|
||||
None
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use ratatui::{
|
|||
crossterm::event::{KeyCode, KeyEvent},
|
||||
};
|
||||
|
||||
use super::{App, ScreenAction, ScreenView, screen_legend};
|
||||
use super::{App, ScreenView, screen_legend};
|
||||
use crate::agent::context::ContextSection;
|
||||
|
||||
pub(crate) struct ConsciousScreen {
|
||||
|
|
@ -117,9 +117,10 @@ impl ScreenView for ConsciousScreen {
|
|||
fn label(&self) -> &'static str { "conscious" }
|
||||
|
||||
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
||||
key: Option<KeyEvent>, app: &mut App) -> Option<ScreenAction> {
|
||||
// Handle keys
|
||||
if let Some(key) = key {
|
||||
events: &[ratatui::crossterm::event::Event], app: &mut App) {
|
||||
for event in events {
|
||||
if let ratatui::crossterm::event::Event::Key(key) = event {
|
||||
if key.kind != ratatui::crossterm::event::KeyEventKind::Press { continue; }
|
||||
let context_state = self.read_context_state(app);
|
||||
let item_count = self.item_count(&context_state);
|
||||
|
||||
|
|
@ -145,10 +146,10 @@ impl ScreenView for ConsciousScreen {
|
|||
}
|
||||
KeyCode::PageUp => { self.scroll = self.scroll.saturating_sub(20); }
|
||||
KeyCode::PageDown => { self.scroll += 20; }
|
||||
KeyCode::Esc => return Some(ScreenAction::Switch(0)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw
|
||||
let mut lines: Vec<Line> = Vec::new();
|
||||
|
|
@ -214,6 +215,5 @@ impl ScreenView for ConsciousScreen {
|
|||
.scroll((self.scroll, 0));
|
||||
|
||||
frame.render_widget(para, area);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
219
src/user/mod.rs
219
src/user/mod.rs
|
|
@ -10,8 +10,8 @@ pub mod unconscious;
|
|||
pub mod thalamus;
|
||||
|
||||
use anyhow::Result;
|
||||
use ratatui::crossterm::event::{Event, EventStream, KeyEventKind};
|
||||
use futures::StreamExt;
|
||||
use ratatui::crossterm::event::{Event, KeyEventKind};
|
||||
use std::io::Write;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::mind::MindCommand;
|
||||
|
|
@ -88,7 +88,7 @@ pub enum ScreenAction {
|
|||
/// A screen that can draw itself and handle input.
|
||||
pub(crate) trait ScreenView: Send {
|
||||
fn tick(&mut self, frame: &mut ratatui::Frame, area: ratatui::layout::Rect,
|
||||
key: Option<ratatui::crossterm::event::KeyEvent>, app: &mut App) -> Option<ScreenAction>;
|
||||
events: &[ratatui::crossterm::event::Event], app: &mut App);
|
||||
fn label(&self) -> &'static str;
|
||||
}
|
||||
|
||||
|
|
@ -128,7 +128,6 @@ pub struct App {
|
|||
pub(crate) active_tools: crate::agent::tools::SharedActiveTools,
|
||||
pub should_quit: bool,
|
||||
pub submitted: Vec<String>,
|
||||
pub hotkey_actions: Vec<HotkeyAction>,
|
||||
pub(crate) context_info: Option<ContextInfo>,
|
||||
pub(crate) shared_context: SharedContextState,
|
||||
pub(crate) agent_state: Vec<crate::subconscious::subconscious::AgentSnapshot>,
|
||||
|
|
@ -151,28 +150,13 @@ impl App {
|
|||
top_p: 0.95,
|
||||
top_k: 20,
|
||||
active_tools,
|
||||
should_quit: false, submitted: Vec::new(), hotkey_actions: Vec::new(),
|
||||
should_quit: false, submitted: Vec::new(),
|
||||
context_info: None, shared_context,
|
||||
agent_state: Vec::new(),
|
||||
channel_status: Vec::new(), idle_info: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_global_key_old(&mut self, _key: KeyEvent) -> bool { false } // placeholder
|
||||
|
||||
/// Handle global keys only (Ctrl combos).
|
||||
pub fn handle_global_key(&mut self, key: KeyEvent) -> bool {
|
||||
if key.modifiers.contains(KeyModifiers::CONTROL) {
|
||||
match key.code {
|
||||
KeyCode::Char('c') => { self.should_quit = true; return true; }
|
||||
KeyCode::Char('r') => { self.hotkey_actions.push(HotkeyAction::CycleReasoning); return true; }
|
||||
KeyCode::Char('k') => { self.hotkey_actions.push(HotkeyAction::KillProcess); return true; }
|
||||
KeyCode::Char('p') => { self.hotkey_actions.push(HotkeyAction::CycleAutonomy); return true; }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn set_channel_status(&mut self, channels: Vec<(String, bool, u32)>) {
|
||||
self.channel_status = channels.into_iter()
|
||||
|
|
@ -314,6 +298,20 @@ fn hotkey_adjust_sampling(mind: &crate::mind::Mind, param: usize, delta: f32) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns true if this is an event the main loop handles (F-keys, Ctrl combos, resize).
|
||||
fn is_global_event(event: &ratatui::crossterm::event::Event) -> bool {
|
||||
use ratatui::crossterm::event::{Event, KeyCode, KeyModifiers, KeyEventKind};
|
||||
|
||||
match event {
|
||||
Event::Key(key) => {
|
||||
if key.kind != KeyEventKind::Press { return false; }
|
||||
matches!(key.code, KeyCode::F(_))
|
||||
|| key.modifiers.contains(KeyModifiers::CONTROL)
|
||||
}
|
||||
Event::Resize(_, _) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn diff_mind_state(
|
||||
cur: &crate::mind::MindState,
|
||||
|
|
@ -373,23 +371,38 @@ pub async fn run(
|
|||
tui::set_screen_legend(tui::screen_legend_from(&interact, &screens));
|
||||
|
||||
let mut terminal = tui::init_terminal()?;
|
||||
let mut reader = EventStream::new();
|
||||
|
||||
let mut render_interval = tokio::time::interval(Duration::from_millis(50));
|
||||
render_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
||||
// Terminal event reader — dedicated thread reads sync, pushes to channel
|
||||
let (event_tx, mut event_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
match crossterm::event::read() {
|
||||
Ok(event) => { if event_tx.send(event).is_err() { break; } }
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let agent_changed = agent.lock().await.changed.clone();
|
||||
let mut turn_watch = mind.turn_watch();
|
||||
let mut dirty = true;
|
||||
let mut prev_mind = shared_mind.lock().unwrap().clone();
|
||||
let mut pending_key: Option<ratatui::crossterm::event::KeyEvent> = None;
|
||||
let mut pending: Vec<ratatui::crossterm::event::Event> = Vec::new();
|
||||
|
||||
terminal.hide_cursor()?;
|
||||
|
||||
if let Ok(mut ag) = agent.try_lock() { ag.notify("consciousness v0.3"); }
|
||||
|
||||
// Initial render
|
||||
terminal.draw(|f| {
|
||||
let area = f.area();
|
||||
interact.tick(f, area, None, &mut app);
|
||||
})?;
|
||||
{
|
||||
let mut frame = terminal.get_frame();
|
||||
let area = frame.area();
|
||||
interact.tick(&mut frame, area, &[], &mut app);
|
||||
drop(frame);
|
||||
terminal.flush()?;
|
||||
terminal.swap_buffers();
|
||||
terminal.backend_mut().flush()?;
|
||||
}
|
||||
|
||||
// Replay conversation after Mind init completes (non-blocking check)
|
||||
let mut startup_done = false;
|
||||
|
|
@ -398,56 +411,30 @@ pub async fn run(
|
|||
tokio::select! {
|
||||
biased;
|
||||
|
||||
maybe_event = reader.next() => {
|
||||
match maybe_event {
|
||||
Some(Ok(Event::Key(key))) => {
|
||||
if key.kind != KeyEventKind::Press { continue; }
|
||||
idle_state.user_activity();
|
||||
|
||||
// F-keys switch screens
|
||||
if let ratatui::crossterm::event::KeyCode::F(n) = key.code {
|
||||
active_screen = match n {
|
||||
1 => 0, // interact
|
||||
n @ 2..=5 if (n as usize - 2) < screens.len() => n as usize - 1,
|
||||
_ => active_screen,
|
||||
};
|
||||
if active_screen == 4 { // thalamus — refresh channels
|
||||
let tx = channel_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let result = crate::thalamus::channels::fetch_all_channels().await;
|
||||
let _ = tx.send(result).await;
|
||||
});
|
||||
}
|
||||
dirty = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Global keys (Ctrl combos) — only pass to screen if not consumed
|
||||
if !app.handle_global_key(key) {
|
||||
pending_key = Some(key);
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
Some(Ok(Event::Mouse(_mouse))) => {
|
||||
// TODO: route to active screen
|
||||
dirty = true;
|
||||
}
|
||||
Some(Ok(Event::Resize(_w, _h))) => {
|
||||
terminal.clear()?;
|
||||
dirty = true;
|
||||
}
|
||||
Some(Err(_)) => break,
|
||||
None => break,
|
||||
_ => continue,
|
||||
Some(event) = event_rx.recv() => {
|
||||
pending.push(event);
|
||||
while let Ok(event) = event_rx.try_recv() {
|
||||
pending.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
_ = agent_changed.notified() => {
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
_ = render_interval.tick() => {
|
||||
_ = turn_watch.changed() => {
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
Some(channels) = channel_rx.recv() => {
|
||||
app.set_channel_status(channels);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// State sync on every wake
|
||||
idle_state.decay_ewma();
|
||||
app.update_idle(&idle_state);
|
||||
|
||||
// One-time: mark startup done after Mind init
|
||||
if !startup_done {
|
||||
if let Ok(mut ag) = agent.try_lock() {
|
||||
let model = ag.model().to_string();
|
||||
|
|
@ -456,64 +443,82 @@ pub async fn run(
|
|||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Diff MindState — generate UI messages from changes
|
||||
{
|
||||
let cur = shared_mind.lock().unwrap();
|
||||
diff_mind_state(&cur, &prev_mind, &mut dirty);
|
||||
prev_mind = cur.clone();
|
||||
}
|
||||
|
||||
while let Ok(notif) = notify_rx.try_recv() {
|
||||
while let Ok(_notif) = notify_rx.try_recv() {
|
||||
let tx = channel_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let result = crate::thalamus::channels::fetch_all_channels().await;
|
||||
let _ = tx.send(result).await;
|
||||
});
|
||||
}
|
||||
|
||||
if !pending.is_empty() { idle_state.user_activity(); }
|
||||
|
||||
while !pending.is_empty() {
|
||||
let global_pos = pending.iter().position(|e| is_global_event(e))
|
||||
.unwrap_or(pending.len());
|
||||
|
||||
if global_pos > 0 {
|
||||
let mut frame = terminal.get_frame();
|
||||
let area = frame.area();
|
||||
if active_screen == 0 {
|
||||
interact.tick(&mut frame, area, &pending[..global_pos], &mut app);
|
||||
} else {
|
||||
screens[active_screen - 1].tick(&mut frame, area, &pending[..global_pos], &mut app);
|
||||
}
|
||||
drop(frame);
|
||||
terminal.flush()?;
|
||||
terminal.swap_buffers();
|
||||
terminal.backend_mut().flush()?;
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
Some(channels) = channel_rx.recv() => {
|
||||
app.set_channel_status(channels);
|
||||
dirty = true;
|
||||
}
|
||||
pending = pending.split_off(global_pos);
|
||||
if pending.is_empty() { break; }
|
||||
|
||||
// Global event is first — handle it
|
||||
use ratatui::crossterm::event::{Event, KeyCode, KeyModifiers};
|
||||
let event = pending.remove(0);
|
||||
match event {
|
||||
Event::Key(key) => {
|
||||
if let KeyCode::F(n) = key.code {
|
||||
active_screen = match n {
|
||||
1 => 0,
|
||||
n @ 2..=5 if (n as usize - 2) < screens.len() => n as usize - 1,
|
||||
_ => active_screen,
|
||||
};
|
||||
} else if key.modifiers.contains(KeyModifiers::CONTROL) {
|
||||
match key.code {
|
||||
KeyCode::Char('c') => { app.should_quit = true; }
|
||||
KeyCode::Char('r') => hotkey_cycle_reasoning(mind),
|
||||
KeyCode::Char('k') => hotkey_kill_processes(mind).await,
|
||||
KeyCode::Char('p') => hotkey_cycle_autonomy(mind),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Handle hotkey actions
|
||||
let actions: Vec<HotkeyAction> = app.hotkey_actions.drain(..).collect();
|
||||
for action in actions {
|
||||
match action {
|
||||
HotkeyAction::CycleReasoning => hotkey_cycle_reasoning(mind),
|
||||
HotkeyAction::KillProcess => hotkey_kill_processes(mind).await,
|
||||
HotkeyAction::Interrupt => { let _ = mind_tx.send(MindCommand::Interrupt); }
|
||||
HotkeyAction::CycleAutonomy => hotkey_cycle_autonomy(mind),
|
||||
HotkeyAction::AdjustSampling(param, delta) => hotkey_adjust_sampling(mind, param, delta),
|
||||
}
|
||||
}
|
||||
Event::Resize(_, _) => { let _ = terminal.clear(); }
|
||||
_ => {}
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if dirty {
|
||||
let key = pending_key.take();
|
||||
let mut screen_action = None;
|
||||
let mut frame = terminal.get_frame();
|
||||
let area = frame.area();
|
||||
if active_screen == 0 {
|
||||
terminal.draw(|f| {
|
||||
let area = f.area();
|
||||
screen_action = interact.tick(f, area, key, &mut app);
|
||||
})?;
|
||||
interact.tick(&mut frame, area, &[], &mut app);
|
||||
} else {
|
||||
let screen = &mut screens[active_screen - 1];
|
||||
terminal.draw(|f| {
|
||||
let area = f.area();
|
||||
screen_action = screen.tick(f, area, key, &mut app);
|
||||
})?;
|
||||
}
|
||||
if let Some(action) = screen_action {
|
||||
match action {
|
||||
tui::ScreenAction::Switch(i) => { active_screen = i; dirty = true; continue; }
|
||||
tui::ScreenAction::Hotkey(h) => app.hotkey_actions.push(h),
|
||||
}
|
||||
screens[active_screen - 1].tick(&mut frame, area, &[], &mut app);
|
||||
}
|
||||
drop(frame);
|
||||
terminal.flush()?;
|
||||
terminal.swap_buffers();
|
||||
terminal.backend_mut().flush()?;
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,9 +27,11 @@ impl ScreenView for SubconsciousScreen {
|
|||
fn label(&self) -> &'static str { "subconscious" }
|
||||
|
||||
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
||||
key: Option<KeyEvent>, app: &mut App) -> Option<ScreenAction> {
|
||||
events: &[ratatui::crossterm::event::Event], app: &mut App) {
|
||||
// Handle keys
|
||||
if let Some(key) = key {
|
||||
for event in events {
|
||||
if let ratatui::crossterm::event::Event::Key(key) = event {
|
||||
if key.kind != ratatui::crossterm::event::KeyEventKind::Press { continue; }
|
||||
match key.code {
|
||||
KeyCode::Up if !self.log_view => {
|
||||
self.selected = self.selected.saturating_sub(1);
|
||||
|
|
@ -44,12 +46,12 @@ impl ScreenView for SubconsciousScreen {
|
|||
KeyCode::Esc | KeyCode::Left if self.log_view => {
|
||||
self.log_view = false;
|
||||
}
|
||||
KeyCode::Esc if !self.log_view => return Some(ScreenAction::Switch(0)),
|
||||
KeyCode::PageUp => { self.scroll = self.scroll.saturating_sub(20); }
|
||||
KeyCode::PageDown => { self.scroll += 20; }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw
|
||||
if self.log_view {
|
||||
|
|
@ -57,7 +59,6 @@ impl ScreenView for SubconsciousScreen {
|
|||
} else {
|
||||
self.draw_list(frame, area, app);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,9 +26,11 @@ impl ScreenView for ThalamusScreen {
|
|||
fn label(&self) -> &'static str { "thalamus" }
|
||||
|
||||
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
||||
key: Option<KeyEvent>, app: &mut App) -> Option<ScreenAction> {
|
||||
events: &[ratatui::crossterm::event::Event], app: &mut App) {
|
||||
// Handle keys
|
||||
if let Some(key) = key {
|
||||
for event in events {
|
||||
if let ratatui::crossterm::event::Event::Key(key) = event {
|
||||
if key.kind != ratatui::crossterm::event::KeyEventKind::Press { continue; }
|
||||
match key.code {
|
||||
KeyCode::Up => { self.sampling_selected = self.sampling_selected.saturating_sub(1); }
|
||||
KeyCode::Down => { self.sampling_selected = (self.sampling_selected + 1).min(2); }
|
||||
|
|
@ -36,18 +38,16 @@ impl ScreenView for ThalamusScreen {
|
|||
let delta = match self.sampling_selected {
|
||||
0 => 0.05, 1 => 0.05, 2 => 5.0, _ => 0.0,
|
||||
};
|
||||
return Some(ScreenAction::Hotkey(HotkeyAction::AdjustSampling(self.sampling_selected, delta)));
|
||||
}
|
||||
KeyCode::Left => {
|
||||
let delta = match self.sampling_selected {
|
||||
0 => -0.05, 1 => -0.05, 2 => -5.0, _ => 0.0,
|
||||
};
|
||||
return Some(ScreenAction::Hotkey(HotkeyAction::AdjustSampling(self.sampling_selected, delta)));
|
||||
}
|
||||
KeyCode::Esc => return Some(ScreenAction::Switch(0)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw
|
||||
let section = Style::default().fg(Color::Yellow);
|
||||
|
|
@ -131,6 +131,5 @@ impl ScreenView for ThalamusScreen {
|
|||
.scroll((self.scroll, 0));
|
||||
|
||||
frame.render_widget(para, area);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,15 +38,17 @@ impl ScreenView for UnconsciousScreen {
|
|||
fn label(&self) -> &'static str { "unconscious" }
|
||||
|
||||
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
||||
key: Option<KeyEvent>, _app: &mut App) -> Option<ScreenAction> {
|
||||
if let Some(key) = key {
|
||||
events: &[ratatui::crossterm::event::Event], _app: &mut App) {
|
||||
for event in events {
|
||||
if let ratatui::crossterm::event::Event::Key(key) = event {
|
||||
if key.kind != ratatui::crossterm::event::KeyEventKind::Press { continue; }
|
||||
match key.code {
|
||||
KeyCode::PageUp => { self.scroll = self.scroll.saturating_sub(20); }
|
||||
KeyCode::PageDown => { self.scroll += 20; }
|
||||
KeyCode::Esc => return Some(ScreenAction::Switch(0)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let block = Block::default()
|
||||
.title_top(Line::from(screen_legend()).left_aligned())
|
||||
|
|
@ -79,7 +81,6 @@ impl ScreenView for UnconsciousScreen {
|
|||
render_tasks(frame, &st.tasks, tasks_area);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue