2

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.");
    });
}
Katherine1
  • 195
  • 1
  • 2
  • 14
  • You will need to provide a proper [mre] because it [works for me](https://paste.chapril.org/?71a30bbcd4464750#5ScLkCY5uUm15Ed43zMMCMSsQUQcW1TgVqeMUyyBmCHe). – Jmb Jul 23 '21 at 07:27
  • I attached a main() that demonstrates the problem. Basically, I'm expecting to update the screen while the application is running after the UI is set up, as you do with a UI. The problem is that if I try to do so after I've actually started up the UI, it won't update the displayed text. – Katherine1 Jul 23 '21 at 12:04
  • It strikes me that if you are able to update the screen with your callback after the UI started, that what I might need is a custom event that I register as a global callback. during initialization of the UI. I'll have to look into that. – Katherine1 Jul 23 '21 at 12:17
  • 1
    Note that `siv.run()` only returns when your application exits, so your `siv.call_on_name` is executed too late for it to have any visible effect. – Jmb Jul 23 '21 at 13:58
  • And the UI doesn't display until ```siv.run()``` starts, so I'm going to have to look into cursive's events or threading (or a mix of both) to solve this issue. – Katherine1 Jul 23 '21 at 15:34

0 Answers0