I'm currently working on writing up a tui for a server application in a personal project, and I picked the cursive library to do it. I have run into a problem with a component of my application that is supposed to serve as the server's console window. In setting up the ability to output text to the window, I've run into a problem where I can't update the TextView.
According to the last comment in this issue, the way to do this is to use a TextContent and update that, which I set up below. While the initial content will show up in the UI, no updates to the content do. I have tried setting content on the TextView directly by giving it a name, but no luck. I've been able to get_content on the TextView itself and see that the content has been updated, it just isn't reflected in the UI.
Is there any way to do this, or do I need to make a custom view for the output?
Here is the code in question:
use std::ops::Add;
use std::borrow::{BorrowMut, Borrow};
use cursive::{View, Printer, Vec2, Rect, With};
use cursive::event::{Event, EventResult, AnyCb, Key};
use cursive::view::{Selector, ViewNotFound, ViewWrapper};
use cursive::direction::Direction;
use cursive::views::{EditView, LinearLayout, OnEventView, DummyView, TextView, TextContent};
use cursive::traits::{Nameable, Scrollable, Resizable};
use cursive::view::scroll::Scroller;
pub const CONSOLE_INPUT_NAME: &str = "console_input";
pub const CONSOLE_OUTPUT_LENGTH: u16 = 1000;
pub(crate) struct ConsoleView {
inner_view: LinearLayout,
console_output: TextContent,
console_output_history: LinkedList<String>
}
impl Default for ConsoleView {
fn default() -> Self {
let console_output = TextContent::new("This content should get replaced.");
let inner_view = LinearLayout::vertical()
.child(TextView::new_with_content(console_output.clone())
.full_screen()
.scrollable()
.wrap_with(OnEventView::new)
.on_pre_event_inner(Key::PageUp, |v, _| {
let scroller = v.get_scroller_mut();
if scroller.can_scroll_up() {
scroller.scroll_up(
scroller.last_outer_size().y.saturating_sub(1),
);
}
Some(EventResult::Consumed(None))
})
.on_pre_event_inner(Key::PageDown, |v, _| {
let scroller = v.get_scroller_mut();
if scroller.can_scroll_down() {
scroller.scroll_down(
scroller.last_outer_size().y.saturating_sub(1),
);
}
Some(EventResult::Consumed(None))
}))
.child(DummyView)
.child(EditView::new().with_name(CONSOLE_INPUT_NAME));
return Self {
inner_view,
console_output,
console_output_history: LinkedList::new()
}
}
}
impl ViewWrapper for ConsoleView {
type V = LinearLayout;
fn with_view<F, R>(&self, f: F) -> Option<R> where F: FnOnce(&Self::V) -> R {
return Some(f(self.inner_view.borrow()));
}
fn with_view_mut<F, R>(&mut self, f: F) -> Option<R> where F: FnOnce(&mut Self::V) -> R {
return Some(f(self.inner_view.borrow_mut()));
}
fn into_inner(self) -> Result<Self::V, Self> where Self: Sized, Self::V: Sized {
return Ok(self.inner_view);
}
fn wrap_draw(&self, printer: &Printer) {
self.inner_view.draw(printer);
}
fn wrap_required_size(&mut self, req: Vec2) -> Vec2 {
return req;
}
fn wrap_on_event(&mut self, ch: Event) -> EventResult {
return self.inner_view.on_event(ch);
}
fn wrap_layout(&mut self, size: Vec2) {
self.inner_view.layout(size);
}
fn wrap_take_focus(&mut self, source: Direction) -> bool {
return self.inner_view.take_focus(source);
}
fn wrap_call_on_any<'a>(&mut self, selector: &Selector<'_>, callback: AnyCb<'a>) {
self.inner_view.call_on_any(selector, callback);
}
fn wrap_focus_view(&mut self, selector: &Selector<'_>) -> Result<(), ViewNotFound> {
return self.inner_view.focus_view(selector);
}
fn wrap_needs_relayout(&self) -> bool {
return self.inner_view.needs_relayout();
}
fn wrap_important_area(&self, size: Vec2) -> Rect {
return self.inner_view.important_area(size);
}
}
impl ConsoleView {
pub fn print(&mut self, line: &str) {
log::info!("Printing to console: {}", line);
self.console_output_history.push_back(line.to_string());
if self.console_output_history.len() > CONSOLE_OUTPUT_LENGTH as usize {
self.console_output_history.pop_front();
}
let out = self.console_output_history.iter().fold("".to_string(), |acc, s| {
return acc.add(s.as_str()).add("\n");
});
self.console_output.set_content(out);
}
}
Here is a main() to go with to demonstrate the problem. Basically, once the UI is set up and the event loop is started, I can't update the information displayed in the UI. Given that the UI doesn't display until I start the event loop, this is a problem.
let mut siv = cursive::default();
siv.add_layer(ConsoleView::default().with_name("view_name"));
siv.add_global_callback(Key::Esc, |s| s.quit());
siv.run();
siv.call_on_name("view_name", |view: &mut ConsoleView| {
view.print("I should see this text in the app.");
});
}