From a78edc88c0447f6f4ad5de3fd2cae9fe4b0f6407 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Tue, 28 Dec 2021 03:52:34 -0500 Subject: [PATCH] Basic temp --- src/app.rs | 33 +++- src/app/data_farmer.rs | 1 + src/app/data_harvester/temperature.rs | 2 +- src/data_conversion.rs | 41 +++++ src/tuine/application.rs | 4 +- src/tuine/component/base/block.rs | 8 +- src/tuine/component/base/text_table/mod.rs | 168 +++++++++++-------- src/tuine/component/base/text_table/props.rs | 6 + src/tuine/component/stateful.rs | 2 +- src/tuine/component/widget/battery_table.rs | 0 src/tuine/component/widget/cpu_graph.rs | 0 src/tuine/component/widget/cpu_simple.rs | 0 src/tuine/component/widget/disk_table.rs | 0 src/tuine/component/widget/mem_graph.rs | 0 src/tuine/component/widget/mem_simple.rs | 0 src/tuine/component/widget/mod.rs | 30 ++++ src/tuine/component/widget/net_graph.rs | 0 src/tuine/component/widget/net_simple.rs | 0 src/tuine/component/widget/process_table.rs | 0 src/tuine/component/widget/simple_table.rs | 72 ++++++++ src/tuine/component/widget/temp_table.rs | 55 +++--- src/tuine/element.rs | 3 +- src/tuine/runtime.rs | 31 ++-- 23 files changed, 338 insertions(+), 118 deletions(-) create mode 100644 src/tuine/component/widget/battery_table.rs create mode 100644 src/tuine/component/widget/cpu_graph.rs create mode 100644 src/tuine/component/widget/cpu_simple.rs create mode 100644 src/tuine/component/widget/disk_table.rs create mode 100644 src/tuine/component/widget/mem_graph.rs create mode 100644 src/tuine/component/widget/mem_simple.rs create mode 100644 src/tuine/component/widget/net_graph.rs create mode 100644 src/tuine/component/widget/net_simple.rs create mode 100644 src/tuine/component/widget/process_table.rs create mode 100644 src/tuine/component/widget/simple_table.rs diff --git a/src/app.rs b/src/app.rs index ed3cca8f..7da05590 100644 --- a/src/app.rs +++ b/src/app.rs @@ -29,6 +29,7 @@ use frozen_state::FrozenState; use crate::{ canvas::Painter, constants, + data_conversion::ConvertedData, tuine::{Application, Element, Flex, Status, ViewContext}, units::data_units::DataUnit, Pid, @@ -129,7 +130,13 @@ impl Default for CurrentScreen { pub enum AppMessages { Update(Box), OpenHelp, - KillProcess { to_kill: Vec }, + ConfirmKillProcess { + to_kill: Vec, + }, + KillProcess { + to_kill: Vec, + signal: Option, + }, ToggleFreeze, Reset, Clean, @@ -209,27 +216,34 @@ impl AppState { impl Application for AppState { type Message = AppMessages; - fn update(&mut self, message: Self::Message) { + fn update(&mut self, message: Self::Message) -> bool { match message { AppMessages::Update(new_data) => { self.data_collection.eat_data(new_data); + true } AppMessages::OpenHelp => { self.set_current_screen(CurrentScreen::Help); + true } - AppMessages::KillProcess { to_kill } => {} + AppMessages::ConfirmKillProcess { to_kill } => true, + AppMessages::KillProcess { to_kill, signal } => true, AppMessages::ToggleFreeze => { self.frozen_state.toggle(&self.data_collection); + true } AppMessages::Clean => { self.data_collection .clean_data(constants::STALE_MAX_MILLISECONDS); + false } AppMessages::Quit => { self.terminator.store(true, SeqCst); + false } AppMessages::Reset => { // FIXME: Reset + true } } } @@ -243,10 +257,21 @@ impl Application for AppState { use crate::tuine::StatefulComponent; use crate::tuine::{TempTable, TextTable, TextTableProps}; + let data = match &self.frozen_state { + FrozenState::NotFrozen => &self.data_collection, + FrozenState::Frozen(frozen_data_collection) => &frozen_data_collection, + }; + + let mut converted_data = ConvertedData::default(); + Flex::column() .with_flex_child( Flex::row_with_children(vec![ - FlexElement::new(TempTable::build(ctx)), + FlexElement::new(TempTable::build( + ctx, + &self.painter, + converted_data.temp_table(data, self.app_config_fields.temperature_type), + )), FlexElement::new(TextTable::build( ctx, TextTableProps::new(vec!["D", "E", "F"]), diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs index 75edc3ea..7d2ab41c 100644 --- a/src/app/data_farmer.rs +++ b/src/app/data_farmer.rs @@ -175,6 +175,7 @@ impl Default for DataCollection { } } +// TODO: Just rip this out, store only stringified data...? impl DataCollection { pub fn reset(&mut self) { self.timed_data_vec = Default::default(); diff --git a/src/app/data_harvester/temperature.rs b/src/app/data_harvester/temperature.rs index 3426a053..592140aa 100644 --- a/src/app/data_harvester/temperature.rs +++ b/src/app/data_harvester/temperature.rs @@ -23,7 +23,7 @@ pub struct TempHarvest { pub temperature: f32, } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub enum TemperatureType { Celsius, Kelvin, diff --git a/src/data_conversion.rs b/src/data_conversion.rs index 40704517..74fe5892 100644 --- a/src/data_conversion.rs +++ b/src/data_conversion.rs @@ -7,6 +7,47 @@ use crate::{app::AxisScaling, units::data_units::DataUnit}; use std::borrow::Cow; +/// Stores converted data, and caches results. +#[derive(Default)] +pub struct ConvertedData { + temp_table: Option>>>, +} + +impl ConvertedData { + pub fn temp_table( + &mut self, data: &DataCollection, temp_type: TemperatureType, + ) -> Vec>> { + match &self.temp_table { + Some(temp_table) => temp_table.clone(), + None => { + let temp_table = if data.temp_harvest.is_empty() { + vec![vec!["No Sensors Found".into(), "".into()]] + } else { + let unit = match temp_type { + data_harvester::temperature::TemperatureType::Celsius => "°C", + data_harvester::temperature::TemperatureType::Kelvin => "K", + data_harvester::temperature::TemperatureType::Fahrenheit => "°F", + }; + + data.temp_harvest + .iter() + .map(|temp_harvest| { + let val = temp_harvest.temperature.ceil().to_string(); + vec![ + temp_harvest.name.clone().into(), + format!("{}{}", val, unit).into(), + ] + }) + .collect() + }; + + self.temp_table = Some(temp_table.clone()); + temp_table + } + } + } +} + /// Point is of time, data type Point = (f64, f64); diff --git a/src/tuine/application.rs b/src/tuine/application.rs index 97d5c78a..79af5408 100644 --- a/src/tuine/application.rs +++ b/src/tuine/application.rs @@ -14,8 +14,8 @@ pub type CrosstermBackend = tui::backend::CrosstermBackend; pub trait Application: Sized { type Message: Debug; - /// Determines how to handle a given message. - fn update(&mut self, message: Self::Message); + /// Determines how to handle a given message, and returns `true` if this update should trigger a redraw. + fn update(&mut self, message: Self::Message) -> bool; /// Returns whether to stop the application. Defaults to /// always returning false. diff --git a/src/tuine/component/base/block.rs b/src/tuine/component/base/block.rs index ff3f25a3..9ea7ea9b 100644 --- a/src/tuine/component/base/block.rs +++ b/src/tuine/component/base/block.rs @@ -9,8 +9,7 @@ use crate::tuine::{ /// A set of styles for a [`Block`]. #[derive(Clone, Debug, Default)] pub struct StyleSheet { - text: Style, - border: Style, + pub border: Style, } /// A [`Block`] is a widget that draws a border around a child [`Component`], as well as optional @@ -47,6 +46,11 @@ where self } + pub fn style(mut self, style: StyleSheet) -> Self { + self.style_sheet = style; + self + } + fn inner_rect(&self, original: Rect) -> Rect { let mut inner = original; diff --git a/src/tuine/component/base/text_table/mod.rs b/src/tuine/component/base/text_table/mod.rs index 8b470864..4e6123b4 100644 --- a/src/tuine/component/base/text_table/mod.rs +++ b/src/tuine/component/base/text_table/mod.rs @@ -5,12 +5,14 @@ mod table_scroll_state; use self::table_scroll_state::ScrollState; pub mod data_row; +use crossterm::event::KeyCode; pub use data_row::DataRow; pub mod data_cell; pub use data_cell::DataCell; pub mod sort_type; + pub use sort_type::SortType; pub mod props; @@ -35,9 +37,9 @@ use crate::{ /// A set of styles for a [`TextTable`]. #[derive(Clone, Debug, Default)] pub struct StyleSheet { - text: Style, - selected_text: Style, - table_header: Style, + pub text: Style, + pub selected_text: Style, + pub table_header: Style, } #[derive(PartialEq, Default)] @@ -108,6 +110,71 @@ impl TextTable { self.column_widths = column_widths; } + + fn update_sort_column(&mut self, state: &mut TextTableState, x: u16) -> Status { + match state.sort { + SortType::Unsortable => Status::Ignored, + SortType::Ascending(column) | SortType::Descending(column) => { + let mut cursor = 0; + for (selected_column, width) in self.column_widths.iter().enumerate() { + if x >= cursor && x <= cursor + width { + match state.sort { + SortType::Ascending(_) => { + if selected_column == column { + // FIXME: This should handle default sorting orders... + state.sort = SortType::Descending(selected_column); + } else { + state.sort = SortType::Ascending(selected_column); + } + } + SortType::Descending(_) => { + if selected_column == column { + // FIXME: This should handle default sorting orders... + state.sort = SortType::Ascending(selected_column); + } else { + state.sort = SortType::Descending(selected_column); + } + } + SortType::Unsortable => unreachable!(), // Should be impossible by above check. + } + + return Status::Captured; + } else { + cursor += width; + } + } + Status::Ignored + } + } + } + + pub fn on_page_up(&mut self, state: &mut TextTableState, rect: Rect) -> Status { + let height = rect.height.saturating_sub(self.table_gap + 1); + state.scroll.move_up(height.into()) + } + + pub fn on_page_down(&mut self, state: &mut TextTableState, rect: Rect) -> Status { + let height = rect.height.saturating_sub(self.table_gap + 1); + state.scroll.move_down(height.into()) + } + + pub fn scroll_down( + &mut self, state: &mut TextTableState, messages: &mut Vec, + ) -> Status { + let status = state.scroll.move_down(1); + if let Some(on_select) = &self.on_select { + messages.push(on_select(state.scroll.current_index())); + } + status + } + + pub fn scroll_up(&mut self, state: &mut TextTableState, messages: &mut Vec) -> Status { + let status = state.scroll.move_up(1); + if let Some(on_select) = &self.on_select { + messages.push(on_select(state.scroll.current_index())); + } + status + } } impl StatefulComponent for TextTable { @@ -190,9 +257,31 @@ impl TmpComponent for TextTable { }; // Now build up our headers... - let header = Row::new(self.columns.iter().map(|column| column.name.clone())) - .style(self.style_sheet.table_header) - .bottom_margin(self.table_gap); + let header = match state.sort { + SortType::Unsortable => Row::new(self.columns.iter().map(|column| column.name.clone())), + SortType::Ascending(sort_column) => { + Row::new(self.columns.iter().enumerate().map(|(index, column)| { + const UP_ARROW: &str = "▲"; + if index == sort_column { + format!("{}{}", column.name, UP_ARROW).into() + } else { + column.name.clone() + } + })) + } + SortType::Descending(sort_column) => { + Row::new(self.columns.iter().enumerate().map(|(index, column)| { + const DOWN_ARROW: &str = "▼"; + if index == sort_column { + format!("{}{}", column.name, DOWN_ARROW).into() + } else { + column.name.clone() + } + })) + } + } + .style(self.style_sheet.table_header) + .bottom_margin(self.table_gap); let mut table = Table::new(data_slice) .header(header) @@ -219,6 +308,10 @@ impl TmpComponent for TextTable { Event::Keyboard(key_event) => { if key_event.modifiers.is_empty() { match key_event.code { + KeyCode::PageUp => self.on_page_up(state, rect), + KeyCode::PageDown => self.on_page_down(state, rect), + KeyCode::Up => self.scroll_up(state, messages), + KeyCode::Down => self.scroll_down(state, messages), _ => Status::Ignored, } } else { @@ -232,52 +325,7 @@ impl TmpComponent for TextTable { let y = mouse_event.row - rect.top(); if y == 0 { let x = mouse_event.column - rect.left(); - match state.sort { - SortType::Unsortable => Status::Ignored, - SortType::Ascending(column) | SortType::Descending(column) => { - let mut cursor = 0; - for (selected_column, width) in - self.column_widths.iter().enumerate() - { - let end = cursor + width; - - if x >= cursor && x <= end { - match state.sort { - SortType::Ascending(_) => { - if selected_column == column { - // FIXME: This should handle default sorting orders... - state.sort = SortType::Descending( - selected_column, - ); - } else { - state.sort = SortType::Ascending( - selected_column, - ); - } - } - SortType::Descending(_) => { - if selected_column == column { - // FIXME: This should handle default sorting orders... - state.sort = SortType::Ascending( - selected_column, - ); - } else { - state.sort = SortType::Descending( - selected_column, - ); - } - } - SortType::Unsortable => unreachable!(), // Should be impossible by above check. - } - - return Status::Captured; - } else { - cursor += width; - } - } - Status::Ignored - } - } + self.update_sort_column(state, x) } else if y > self.table_gap { let visual_index = usize::from(y - self.table_gap); match state.scroll.set_visual_index(visual_index) { @@ -297,20 +345,8 @@ impl TmpComponent for TextTable { Status::Ignored } } - MouseEventKind::ScrollDown => { - let status = state.scroll.move_down(1); - if let Some(on_select) = &self.on_select { - messages.push(on_select(state.scroll.current_index())); - } - status - } - MouseEventKind::ScrollUp => { - let status = state.scroll.move_up(1); - if let Some(on_select) = &self.on_select { - messages.push(on_select(state.scroll.current_index())); - } - status - } + MouseEventKind::ScrollDown => self.scroll_down(state, messages), + MouseEventKind::ScrollUp => self.scroll_up(state, messages), _ => Status::Ignored, } } else { diff --git a/src/tuine/component/base/text_table/props.rs b/src/tuine/component/base/text_table/props.rs index 360aaa1c..02995264 100644 --- a/src/tuine/component/base/text_table/props.rs +++ b/src/tuine/component/base/text_table/props.rs @@ -94,6 +94,12 @@ impl TextTableProps { self } + /// Sets the style for the entry. + pub fn style(mut self, style: StyleSheet) -> Self { + self.style_sheet = style; + self + } + pub(crate) fn try_sort_data(&mut self, sort_type: SortType) { use std::cmp::Ordering; diff --git a/src/tuine/component/stateful.rs b/src/tuine/component/stateful.rs index 650753f7..62b89557 100644 --- a/src/tuine/component/stateful.rs +++ b/src/tuine/component/stateful.rs @@ -1,4 +1,4 @@ -use crate::tuine::{State, ViewContext}; +use crate::tuine::{State, StateContext, ViewContext}; use super::TmpComponent; diff --git a/src/tuine/component/widget/battery_table.rs b/src/tuine/component/widget/battery_table.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/tuine/component/widget/cpu_graph.rs b/src/tuine/component/widget/cpu_graph.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/tuine/component/widget/cpu_simple.rs b/src/tuine/component/widget/cpu_simple.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/tuine/component/widget/disk_table.rs b/src/tuine/component/widget/disk_table.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/tuine/component/widget/mem_graph.rs b/src/tuine/component/widget/mem_graph.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/tuine/component/widget/mem_simple.rs b/src/tuine/component/widget/mem_simple.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/tuine/component/widget/mod.rs b/src/tuine/component/widget/mod.rs index 392c7701..57ee7577 100644 --- a/src/tuine/component/widget/mod.rs +++ b/src/tuine/component/widget/mod.rs @@ -1,2 +1,32 @@ +pub mod simple_table; +pub use simple_table::*; + +pub mod cpu_graph; +pub use cpu_graph::*; + +pub mod disk_table; +pub use disk_table::*; + +pub mod mem_graph; +pub use mem_graph::*; + +pub mod net_graph; +pub use net_graph::*; + +pub mod process_table; +pub use process_table::*; + pub mod temp_table; pub use temp_table::*; + +pub mod battery_table; +pub use battery_table::*; + +pub mod cpu_simple; +pub use cpu_simple::*; + +pub mod mem_simple; +pub use mem_simple::*; + +pub mod net_simple; +pub use net_simple::*; diff --git a/src/tuine/component/widget/net_graph.rs b/src/tuine/component/widget/net_graph.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/tuine/component/widget/net_simple.rs b/src/tuine/component/widget/net_simple.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/tuine/component/widget/process_table.rs b/src/tuine/component/widget/process_table.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/tuine/component/widget/simple_table.rs b/src/tuine/component/widget/simple_table.rs new file mode 100644 index 00000000..ebf10702 --- /dev/null +++ b/src/tuine/component/widget/simple_table.rs @@ -0,0 +1,72 @@ +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use tui::style::Style; + +use crate::tuine::{ + self, block, + text_table::{self, DataRow, SortType, TextTableProps}, + Block, Event, Shortcut, StatefulComponent, Status, TextTable, TmpComponent, ViewContext, +}; + +/// A set of styles for a [`SimpleTable`]. +#[derive(Default)] +pub struct StyleSheet { + pub text: Style, + pub selected_text: Style, + pub table_header: Style, + pub border: Style, +} + +/// A [`SimpleTable`] is a wrapper around a [`TextTable`] with basic shortcut support already added for: +/// - Skipping to the start/end of the table +/// - Scrolling up/down by a page +/// - Configurable sorting options +pub struct SimpleTable { + inner: Block>>, +} + +impl SimpleTable { + #[track_caller] + pub fn build>, R: Into>( + ctx: &mut ViewContext<'_>, style: StyleSheet, columns: Vec, data: Vec, + ) -> Self { + let shortcut = Shortcut::with_child(TextTable::build( + ctx, + TextTableProps::new(columns) + .rows(data) + .default_sort(SortType::Ascending(1)) + .style(text_table::StyleSheet { + text: style.text, + selected_text: style.selected_text, + table_header: style.table_header, + }), + )); + + Self { + inner: Block::with_child(shortcut).style(block::StyleSheet { + border: style.border, + }), + } + } +} + +impl TmpComponent for SimpleTable { + fn draw( + &mut self, state_ctx: &mut tuine::StateContext<'_>, draw_ctx: &tuine::DrawContext<'_>, + frame: &mut tui::Frame<'_, Backend>, + ) where + Backend: tui::backend::Backend, + { + self.inner.draw(state_ctx, draw_ctx, frame); + } + + fn on_event( + &mut self, state_ctx: &mut tuine::StateContext<'_>, draw_ctx: &tuine::DrawContext<'_>, + event: tuine::Event, messages: &mut Vec, + ) -> tuine::Status { + self.inner.on_event(state_ctx, draw_ctx, event, messages) + } + + fn layout(&self, bounds: tuine::Bounds, node: &mut tuine::LayoutNode) -> tuine::Size { + self.inner.layout(bounds, node) + } +} diff --git a/src/tuine/component/widget/temp_table.rs b/src/tuine/component/widget/temp_table.rs index fb702a66..5985b6ed 100644 --- a/src/tuine/component/widget/temp_table.rs +++ b/src/tuine/component/widget/temp_table.rs @@ -1,35 +1,41 @@ -use crate::tuine::{ - text_table::{DataRow, SortType, TextTableProps}, - Block, Shortcut, StatefulComponent, TextTable, TmpComponent, ViewContext, +use crate::{ + canvas::Painter, + tuine::{ + Bounds, DataRow, DrawContext, LayoutNode, SimpleTable, Size, StateContext, Status, + TmpComponent, ViewContext, + }, }; -/// A [`TempTable`] is a text table that is meant to display temperature data. +use super::simple_table; + +/// A [`TempTable`] is a table displaying temperature data. +/// +/// It wraps a [`SimpleTable`], with set columns and manages extracting data and styling. pub struct TempTable { - inner: Block>>, + inner: SimpleTable, } impl TempTable { - #[track_caller] - pub fn build(ctx: &mut ViewContext<'_>) -> Self { + pub fn build>( + ctx: &mut ViewContext<'_>, painter: &Painter, data: Vec, + ) -> Self { + let style = simple_table::StyleSheet { + text: painter.colours.text_style, + selected_text: painter.colours.currently_selected_text_style, + table_header: painter.colours.table_header_style, + border: painter.colours.border_style, + }; + Self { - inner: Block::with_child(Shortcut::with_child(TextTable::build( - ctx, - TextTableProps::new(vec!["Sensor", "Temp"]) - .rows(vec![ - DataRow::default().cell("A").cell(2), - DataRow::default().cell("B").cell(3), - DataRow::default().cell("C").cell(1), - ]) - .default_sort(SortType::Ascending(1)), - ))), + inner: SimpleTable::build(ctx, style, vec!["Sensor", "Temp"], data), } } } impl TmpComponent for TempTable { fn draw( - &mut self, state_ctx: &mut crate::tuine::StateContext<'_>, - draw_ctx: &crate::tuine::DrawContext<'_>, frame: &mut tui::Frame<'_, Backend>, + &mut self, state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>, + frame: &mut tui::Frame<'_, Backend>, ) where Backend: tui::backend::Backend, { @@ -37,16 +43,13 @@ impl TmpComponent for TempTable { } fn on_event( - &mut self, state_ctx: &mut crate::tuine::StateContext<'_>, - draw_ctx: &crate::tuine::DrawContext<'_>, event: crate::tuine::Event, - messages: &mut Vec, - ) -> crate::tuine::Status { + &mut self, state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>, + event: crate::tuine::Event, messages: &mut Vec, + ) -> Status { self.inner.on_event(state_ctx, draw_ctx, event, messages) } - fn layout( - &self, bounds: crate::tuine::Bounds, node: &mut crate::tuine::LayoutNode, - ) -> crate::tuine::Size { + fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> Size { self.inner.layout(bounds, node) } } diff --git a/src/tuine/element.rs b/src/tuine/element.rs index 2e2b5a9f..50031aab 100644 --- a/src/tuine/element.rs +++ b/src/tuine/element.rs @@ -3,7 +3,7 @@ use tui::Frame; use super::{ Block, Bounds, Carousel, Container, DrawContext, Empty, Event, Flex, LayoutNode, Shortcut, - Size, StateContext, Status, TempTable, TextTable, TmpComponent, + SimpleTable, Size, StateContext, Status, TempTable, TextTable, TmpComponent, }; /// An [`Element`] is an instantiated [`Component`]. @@ -19,5 +19,6 @@ where Shortcut(Shortcut), TextTable(TextTable), Empty, + SimpleTable(SimpleTable), TempTable(TempTable), } diff --git a/src/tuine/runtime.rs b/src/tuine/runtime.rs index 59f27297..e5ebc88b 100644 --- a/src/tuine/runtime.rs +++ b/src/tuine/runtime.rs @@ -41,27 +41,22 @@ where if let Ok(event) = receiver.recv() { match event { RuntimeEvent::UserInterface(event) => { - match on_event( + if on_event( &mut application, &mut user_interface, &mut app_data, &mut layout, event, ) { - Status::Captured => { - // Hmm... is this really needed? Or is it fine to redraw once then do the termination check? - if application.is_terminated() { - break; - } - - user_interface = new_user_interface(&mut application, &mut app_data); - draw(&mut user_interface, terminal, &mut app_data, &mut layout)?; - } - Status::Ignored => {} + user_interface = new_user_interface(&mut application, &mut app_data); + draw(&mut user_interface, terminal, &mut app_data, &mut layout)?; } } RuntimeEvent::Custom(message) => { - application.update(message); + if application.update(message) { + user_interface = new_user_interface(&mut application, &mut app_data); + draw(&mut user_interface, terminal, &mut app_data, &mut layout)?; + } } RuntimeEvent::Resize { width: _, @@ -86,7 +81,7 @@ where fn on_event( application: &mut A, user_interface: &mut Element, app_data: &mut AppData, layout: &mut LayoutNode, event: Event, -) -> Status +) -> bool where A: Application + 'static, { @@ -103,12 +98,18 @@ where Status::Ignored => application.global_event_handler(event, &mut messages), }; + let mut should_redraw = match event_handled { + Status::Captured => true, + Status::Ignored => false, + }; + for msg in messages { debug!("Message: {:?}", msg); // FIXME: Remove this debug line! - application.update(msg); + let msg_result = application.update(msg); + should_redraw = should_redraw || msg_result; } - event_handled + should_redraw } /// Creates a new [`Element`] representing the root of the user interface.