I have UI components, each component is a struct that implement trait UIComponent
. Also I have UI
renderer which render all this components.
I want to store all created components inside BTree
. So this way I can keep component instances (in case they have some state). I want to avoid using stateless components and avoid keeping all data within UI.
(To be honest I do not know how to do this better).
This is my code:
pub struct UI<'a> {
_terminal: Terminal<CrosstermBackend<Stdout>>,
_components: BTreeMap<&'a str, Box<dyn UIComponent<CrosstermBackend<Stdout>>>>,
}
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: Self::init_components(),
}
}
fn init_components() -> BTreeMap<&'a str, Box<dyn UIComponent<CrosstermBackend<Stdout>>>> {
let mut components = BTreeMap::new();
components.insert("DebugPanel", Box::new(DebugPanel::new())).unwrap();
components.insert("Title", Box::new(Title::new())).unwrap();
components
}
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._components.get("Title").unwrap().render(frame, chunks[0]);
self._components.get("DebugPanel").unwrap().render(frame, output_panels[0]);
}).unwrap();
}
}
the problem here is that I have compile error:
error[E0308]: mismatched types
--> src\ui\mod.rs:64:9
|
59 | fn init_components() -> BTreeMap<&'a str, Box<dyn UIComponent<CrosstermBackend<Stdout>>>> {
| ----------------------------------------------------------------- expected `BTreeMap<&'a str, Box<(dyn UIComponent<CrosstermBackend<Stdout>> + 'static)>>` because of return type
...
64 | components
| ^^^^^^^^^^ expected trait object `dyn UIComponent`, found struct `DebugPanel`
|
= note: expected struct `BTreeMap<&'a str, Box<(dyn UIComponent<CrosstermBackend<Stdout>> + 'static)>>`
found struct `BTreeMap<&str, Box<DebugPanel<'_>>>`
Could somebody explain how to fix this issue ? Also I would appreciate if somebody can advice how better keep components for render in my case.
UPDATED. I think the main question I want to ask is how to refactor my code to allow instantiated components be available from outside of UI::render
to let me keep its state.
Just in case, this is where I use UI:
async fn handle_ui(&mut self) -> JoinHandle<()> {
let message_pipe = Arc::clone(&self._message_pipe);
tokio::spawn(async move {
let mut ui = UI::new();
loop {
ui.render(UIOptions {
message: message_pipe.lock().await.recv(),
});
sleep(Duration::from_millis(WRITE_TIMEOUT)).await;
}
})
}
and this is how I want render
function to be implemented:
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]);
match options.message {
MessageType::Message(output) => {
debug_panel.add_message(output);
},
MessageType::ChooseCharacter(_characters) => {
// ...
},
MessageType::ChooseRealm(_realms) => {
// ...
},
}
title.render(frame, chunks[0]);
debug_panel.render(frame, output_panels[0]);
}).unwrap();
}