use crate::{
core::{
crossterm::{
self,
event::Event,
style::{Attribute, Attributes, ContentStyle},
},
render::{Renderer, SharedRenderer},
Widget,
},
preset::Evaluator,
widgets::{cursor::Cursor, text_editor},
Signal,
};
mod evaluate;
pub struct Style {
pub prefix_style: ContentStyle,
pub active_char_style: ContentStyle,
pub inactive_char_style: ContentStyle,
}
pub struct Form {
pub renderer: Option<SharedRenderer<usize>>,
pub evaluator: Evaluator<Self>,
pub readlines: Cursor<Vec<text_editor::State>>,
pub focus_styles: Vec<Style>,
pub unfocus_styles: Vec<Style>,
}
#[async_trait::async_trait]
impl crate::Prompt for Form {
async fn initialize(&mut self) -> anyhow::Result<()> {
self.overwrite_styles();
let size = crossterm::terminal::size()?;
self.renderer = Some(SharedRenderer::new(
Renderer::try_new_with_graphemes(
self.readlines
.contents()
.iter()
.enumerate()
.map(|(i, state)| (i, state.create_graphemes(size.0, size.1))),
true,
)
.await?,
));
Ok(())
}
async fn evaluate(&mut self, event: &Event) -> anyhow::Result<Signal> {
let ret = (self.evaluator)(event, self).await;
self.overwrite_styles();
let size = crossterm::terminal::size()?;
self.render(size.0, size.1).await?;
ret
}
type Return = Vec<String>;
fn finalize(&mut self) -> anyhow::Result<Self::Return> {
Ok(self
.readlines
.contents()
.iter()
.map(|state| state.texteditor.text_without_cursor().to_string())
.collect())
}
}
impl Form {
pub fn new<I: IntoIterator<Item = text_editor::State>>(states: I) -> Self {
let (readlines, focus_styles, unfocus_styles): (Vec<_>, Vec<_>, Vec<_>) =
states.into_iter().fold(
(Vec::new(), Vec::new(), Vec::new()),
|(mut readlines, mut focus_styles, mut unfocus_styles), state| {
let focus_style = Style {
prefix_style: state.config.prefix_style,
active_char_style: state.config.active_char_style,
inactive_char_style: state.config.inactive_char_style,
};
let unfocus_style = Style {
prefix_style: ContentStyle {
attributes: Attributes::from(Attribute::Dim),
..state.config.prefix_style
},
active_char_style: ContentStyle {
attributes: Attributes::from(Attribute::Dim),
..Default::default()
},
inactive_char_style: ContentStyle {
attributes: Attributes::from(Attribute::Dim),
..state.config.inactive_char_style
},
};
readlines.push(state);
focus_styles.push(focus_style);
unfocus_styles.push(unfocus_style);
(readlines, focus_styles, unfocus_styles)
},
);
Self {
renderer: None,
evaluator: |event, ctx| Box::pin(evaluate::default(event, ctx)),
readlines: Cursor::new(readlines, 0, false),
focus_styles,
unfocus_styles,
}
}
async fn render(&mut self, width: u16, height: u16) -> anyhow::Result<()> {
match self.renderer.as_ref() {
Some(renderer) => {
renderer
.update(
self.readlines
.contents()
.iter()
.enumerate()
.map(|(i, state)| (i, state.create_graphemes(width, height))),
)
.render()
.await
}
None => Err(anyhow::anyhow!("Renderer not initialized")),
}
}
fn overwrite_styles(&mut self) {
let current_position = self.readlines.position();
self.readlines
.contents_mut()
.iter_mut()
.enumerate()
.for_each(|(i, state)| {
if i == current_position {
state.config.prefix_style = self.focus_styles[i].prefix_style;
state.config.inactive_char_style = self.focus_styles[i].inactive_char_style;
state.config.active_char_style = self.focus_styles[i].active_char_style;
} else {
state.config.prefix_style = self.unfocus_styles[i].prefix_style;
state.config.inactive_char_style = self.unfocus_styles[i].inactive_char_style;
state.config.active_char_style = self.unfocus_styles[i].active_char_style;
}
});
}
}