I have some components implemented with tui-rs
, all components implements trait UIComponent
:
pub trait UIComponent<B: Backend> {
fn new() -> Self where Self: Sized;
fn render(&mut self, frame: &mut Frame<B>, rect: Rect);
}
this is my component, where I have an issue:
pub struct DebugPanel<'a> {
items: Vec<Spans<'a>>,
}
impl<'a> DebugPanel<'a> {
pub fn add_item(&mut self, output: LoggerOutput) -> &self {
let message = Self::generate_message(output);
if !message.content.is_empty() {
self.items.push(Spans::from(message));
}
&self
}
fn generate_message(output: LoggerOutput) -> Span<'a> {
match output {
LoggerOutput::Info(data) if !data.is_empty() => Span::styled(
format!("[INFO]: {}\n", data), Style::default().fg(Color::Gray)
),
LoggerOutput::Debug(data) if !data.is_empty() => Span::styled(
format!("[DEBUG]: {}\n", data), Style::default().fg(Color::DarkGray)
),
LoggerOutput::Error(data) if !data.is_empty() => Span::styled(
format!("[ERROR]: {}\n", data), Style::default().fg(Color::Red)
),
LoggerOutput::Success(data) if !data.is_empty() => Span::styled(
format!("[SUCCESS]: {}\n", data), Style::default().fg(Color::LightGreen)
),
LoggerOutput::Server(data) if !data.is_empty() => Span::styled(
format!("[SERVER]: {}\n", data), Style::default().fg(Color::LightMagenta)
),
LoggerOutput::Client(data) if !data.is_empty() => Span::styled(
format!("[CLIENT]: {}\n", data), Style::default().fg(Color::LightBlue)
),
_ => Span::raw(""),
}
}
}
impl<'a, B: Backend> UIComponent<B> for DebugPanel<'a> {
fn new() -> Self {
Self {
items: vec![],
}
}
fn render(&mut self, frame: &mut Frame<B>, rect: Rect) {
let block = Block::default()
.title(PANEL_TITLE)
.borders(Borders::ALL)
.border_type(BorderType::Plain);
let mut offset: usize = 0;
if self.items.len() > rect.height as usize {
offset = self.items.len() - (rect.height - MARGIN * 2) as usize;
}
let paragraph = Paragraph::new(self.items[offset..].to_vec())
.alignment(Alignment::Left)
.wrap(Wrap { trim: true })
.style(Style::default().fg(Color::White).bg(Color::Black))
.block(block);
frame.render_widget(paragraph, rect);
}
}
and this is UI renderer where I initialize my component:
pub struct UI<'a> {
_terminal: Terminal<CrosstermBackend<Stdout>>,
_debug_panel: DebugPanel<'a>,
_title: Title,
}
impl<'a> UI<'a> {
pub fn new() -> Self {
enable_raw_mode().unwrap();
let mut stdout = std::io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap();
let backend = CrosstermBackend::new(stdout);
let mut _terminal = Terminal::new(backend).unwrap();
_terminal.clear().unwrap();
_terminal.hide_cursor().unwrap();
Self {
_terminal,
// components
_debug_panel: DebugPanel::new(),
_title: Title::new(),
}
}
pub fn render(&mut self, options: UIOptions) {
self._terminal.draw(|frame| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(MARGIN)
.constraints([
Constraint::Percentage(12),
Constraint::Percentage(76),
Constraint::Percentage(12),
])
.split(frame.size());
let output_panels = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage(100),
])
.split(chunks[1]);
// ...
self._title.render(frame, chunks[0]);
self._debug_panel.render(frame, output_panels[0]);
}).unwrap();
}
}
the issue is that I cannot use this trait in case I want to implement some extra methods on my component. I got an error inside UI
renderer:
error[E0282]: type annotations needed
--> src\ui\mod.rs:57:27
|
57 | _debug_panel: DebugPanel::new(),
| ^^^^^^^^^^^^^^^ cannot infer type for type parameter `B` declared on the trait `UIComponent`
it's not clear where I should add <B: Backend> to apply it for DebugPanel.
Below is what I want to achieve.
I would like to refactor the code to use custom generic Backend, so I can initialize UI like this:
let ui = UI::<Crossterm<Stdout>>::new();
so I tried to do this:
pub struct UI<'a, B: Backend> {
_terminal: Terminal<B>,
_debug_panel: DebugPanel<'a, B>,
_title: Title,
}
impl<'a, B: Backend> UI<'a, B> {
pub fn new() -> Self {
enable_raw_mode().unwrap();
let mut stdout = std::io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap();
let mut _terminal = Terminal::new(B).unwrap();
_terminal.clear().unwrap();
_terminal.hide_cursor().unwrap();
}
// ...
}
pub struct DebugPanel<'a, B: Backend> {
items: Vec<Spans<'a>>,
}
impl<'a, B: Backend> DebugPanel<'a, B> {
pub fn add_item(&mut self, output: LoggerOutput) -> &self {
let message = Self::generate_message(output);
if !message.content.is_empty() {
self.items.push(Spans::from(message));
}
&self
}
// ...
}
impl<'a, B: Backend> UIComponent<B> for DebugPanel<'a, B> {
fn new() -> Self {
Self {
items: vec![],
}
}
fn render(&mut self, frame: &mut Frame<B>, rect: Rect) {
let block = Block::default()
.title(PANEL_TITLE)
.borders(Borders::ALL)
.border_type(BorderType::Plain);
let mut offset: usize = 0;
if self.items.len() > rect.height as usize {
offset = self.items.len() - (rect.height - MARGIN * 2) as usize;
}
let paragraph = Paragraph::new(self.items[offset..].to_vec())
.alignment(Alignment::Left)
.wrap(Wrap { trim: true })
.style(Style::default().fg(Color::White).bg(Color::Black))
.block(block);
frame.render_widget(paragraph, rect);
}
}
but then I got another error:
error[E0392]: parameter `B` is never used
--> src\ui\debug_panel.rs:13:27
|
13 | pub struct DebugPanel<'a, B: Backend> {
| ^ unused parameter
|
= help: consider removing `B`, referring to it in a field, or using a marker such as `PhantomData`
= help: if you intended `B` to be a const parameter, use `const B: usize` instead
and
error[E0423]: expected value, found type parameter `B`
--> src\ui\mod.rs:49:43
|
49 | let mut _terminal = Terminal::new(B).unwrap();
| ^ not a value
unfortunately, I cannot add example on play-rust since there no tui-rs
supported.
Could somebody help me to fix the typings issue ?