[tui] Add ComponentOf trait.

I think it will help with fetching a component by type from the
Components vector attached to App?
This commit is contained in:
Shaun Reed 2026-01-20 20:43:01 -05:00
parent edcbea746c
commit fc4ba08600

View File

@ -24,6 +24,59 @@ pub enum AppComponents<'a> {
AppComponent(Box<dyn Component>),
}
/// Usage: get_component_mut::<Editor>() OR get_component::<Editor>()
///
/// Implementing this trait for each AppComponent allows for easy lookup in the vector.
trait ComponentOf<T> {
fn as_ref(&self) -> Option<&T>;
fn as_mut(&mut self) -> Option<&mut T>;
}
impl<'a> ComponentOf<Logger> for AppComponents<'a> {
fn as_ref(&self) -> Option<&Logger> {
if let AppComponents::AppLogger(ref e) = *self {
return Some(e);
}
None
}
fn as_mut(&mut self) -> Option<&mut Logger> {
if let AppComponents::AppLogger(ref mut e) = *self {
return Some(e);
}
None
}
}
impl<'a> ComponentOf<Editor> for AppComponents<'a> {
fn as_ref(&self) -> Option<&Editor> {
if let AppComponents::AppEditor(ref e) = *self {
return Some(e);
}
None
}
fn as_mut(&mut self) -> Option<&mut Editor> {
if let AppComponents::AppEditor(ref mut e) = *self {
return Some(e);
}
None
}
}
impl<'a> ComponentOf<Explorer<'a>> for AppComponents<'a> {
fn as_ref(&self) -> Option<&Explorer<'a>> {
if let AppComponents::AppExplorer(ref e) = *self {
return Some(e);
}
None
}
fn as_mut(&mut self) -> Option<&mut Explorer<'a>> {
if let AppComponents::AppExplorer(ref mut e) = *self {
return Some(e);
}
None
}
}
pub struct App<'a> {
components: Vec<AppComponents<'a>>,
}
@ -37,7 +90,7 @@ impl<'a> App<'a> {
AppComponents::AppLogger(Logger::new()),
],
};
app.get_editor_mut()
app.get_component_mut::<Editor>()
.unwrap()
.set_contents(&root_path.join("src/tui/app.rs"))
.context(format!(
@ -47,44 +100,18 @@ impl<'a> App<'a> {
Ok(app)
}
fn get_explorer(&self) -> Result<&Explorer<'a>> {
for component in &self.components {
if let AppComponents::AppExplorer(explorer) = component {
return Ok(explorer);
}
}
bail!("Failed to find project explorer widget.")
fn get_component<T>(&self) -> Option<&T>
where
AppComponents<'a>: ComponentOf<T>,
{
self.components.iter().find_map(|c| c.as_ref())
}
fn get_explorer_mut(&mut self) -> Result<&mut Explorer<'a>> {
for component in &mut self.components {
if let AppComponents::AppExplorer(explorer) = component {
return Ok(explorer);
}
}
bail!("Failed to find project explorer widget.")
}
fn get_editor(&self) -> Option<&Editor> {
for component in &self.components {
if let AppComponents::AppEditor(editor) = component {
return Some(editor);
}
}
// There is no editor currently opened.
None
}
fn get_editor_mut(&mut self) -> Option<&mut Editor> {
for component in &mut self.components {
if let AppComponents::AppEditor(editor) = component {
return Some(editor);
}
}
// There is no editor currently opened.
None
fn get_component_mut<T>(&mut self) -> Option<&mut T>
where
AppComponents<'a>: ComponentOf<T>,
{
self.components.iter_mut().find_map(|c| c.as_mut())
}
pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
@ -121,7 +148,7 @@ impl<'a> App<'a> {
fn draw_tabs(&self, area: Rect, buf: &mut Buffer) {
// Determine the tab title from the current file (or use a fallback).
let mut title: Option<&str> = None;
if let Some(editor) = self.get_editor() {
if let Some(editor) = self.get_component::<Editor>() {
title = editor
.file_path
.as_ref()
@ -144,12 +171,12 @@ impl<'a> App<'a> {
/// If the selected item is not a file, this does nothing.
fn refresh_editor_contents(&mut self) -> Result<()> {
// Use the currently selected TreeItem or get an absolute path to this source file.
let selected_pathbuf = match self.get_explorer()?.selected() {
let selected_pathbuf = match self.get_component::<Explorer>().unwrap().selected() {
Ok(path) => PathBuf::from(path),
Err(_) => PathBuf::from(std::path::absolute(file!())?.to_string_lossy().to_string()),
};
let editor = self
.get_editor_mut()
.get_component_mut::<Editor>()
.context("Failed to get active editor while refreshing contents.")?;
if let Some(current_file_path) = editor.file_path.clone() {
if selected_pathbuf == current_file_path || !selected_pathbuf.is_file() {
@ -194,15 +221,15 @@ impl<'a> Widget for &mut App<'a> {
self.draw_status(vertical[0], buf);
self.draw_tabs(editor_layout[0], buf);
let id = self.id().to_string();
for component in &mut self.components {
match component {
AppComponents::AppEditor(editor) => editor.render(editor_layout[1], buf),
AppComponents::AppExplorer(explorer) => {
// TODO: What to do about errors during rendering?
// Once there is a debug console, maybe log it and discard? Panic isn't great.
explorer
.render(horizontal[0], buf)
.expect("Failed to render Explorer");
.context("Failed to render Explorer")
.unwrap_or_else(|e| error!(target:id.as_str(), "{}", e));
}
AppComponents::AppLogger(logger) => logger.render(vertical[2], buf),
AppComponents::AppComponent(_) => {}