use {
super::*,
crate::{
command::*,
display::{
status_line,
Areas,
Screen,
W,
WIDE_STATUS,
flags_display,
},
errors::ProgramError,
keys,
skin::PanelSkin,
stage::*,
task_sync::Dam,
verb::*,
},
minimad::{Alignment, Composite},
termimad::Event,
};
pub struct Panel {
pub id: PanelId,
states: Vec<Box<dyn PanelState>>, pub areas: Areas,
status: Status,
pub purpose: PanelPurpose,
input: PanelInput,
}
impl Panel {
pub fn new(
id: PanelId,
state: Box<dyn PanelState>,
areas: Areas,
con: &AppContext,
) -> Self {
let mut input = PanelInput::new(areas.input.clone());
input.set_content(&state.get_starting_input());
let status = state.no_verb_status(false, con);
Self {
id,
states: vec![state],
areas,
status,
purpose: PanelPurpose::None,
input,
}
}
pub fn set_error(&mut self, text: String) {
self.status = Status::from_error(text);
}
#[allow(clippy::too_many_arguments)] pub fn apply_command<'c>(
&mut self,
w: &'c mut W,
cmd: &'c Command,
app_state: &mut AppState,
app_cmd_context: &'c AppCmdContext<'c>,
) -> Result<CmdResult, ProgramError> {
let state_idx = self.states.len() - 1;
let cc = CmdContext {
cmd,
app: app_cmd_context,
panel: PanelCmdContext {
areas: &self.areas,
purpose: self.purpose,
},
};
let result = self.states[state_idx].on_command(w, app_state, &cc);
let has_previous_state = self.states.len() > 1;
self.status = self.state().get_status(app_state, &cc, has_previous_state);
result
}
pub fn refresh_input_status<'c>(
&mut self,
app_state: &AppState,
app_cmd_context: &'c AppCmdContext<'c>,
) {
let cmd = Command::from_raw(self.input.get_content(), false);
let cc = CmdContext {
cmd: &cmd,
app: app_cmd_context,
panel: PanelCmdContext {
areas: &self.areas,
purpose: self.purpose,
},
};
let has_previous_state = self.states.len() > 1;
self.status = self.state().get_status(app_state, &cc, has_previous_state);
}
pub fn do_pending_task(
&mut self,
stage: &Stage,
screen: Screen,
con: &AppContext,
dam: &mut Dam,
) {
self.mut_state().do_pending_task(stage, screen, con, dam)
}
pub fn has_pending_task(&self) -> bool {
self.state().get_pending_task().is_some()
}
pub fn add_event(
&mut self,
w: &mut W,
event: Event,
app_state: &AppState,
con: &AppContext,
) -> Result<Command, ProgramError> {
let sel_info = self.states[self.states.len() - 1].sel_info(&app_state);
self.input.on_event(w, event, con, sel_info, app_state, self.state().get_mode())
}
pub fn push_state(&mut self, new_state: Box<dyn PanelState>) {
self.input.set_content(&new_state.get_starting_input());
self.states.push(new_state);
}
pub fn mut_state(&mut self) -> &mut dyn PanelState {
self.states.last_mut().unwrap().as_mut()
}
pub fn state(&self) -> &dyn PanelState {
self.states.last().unwrap().as_ref()
}
pub fn clear_input(&mut self) {
self.input.set_content("");
}
pub fn clear_input_invocation(&mut self, con: &AppContext) {
let mut command_parts = CommandParts::from(self.input.get_content());
if command_parts.verb_invocation.is_some() {
command_parts.verb_invocation = None;
let new_input = format!("{}", command_parts);
self.input.set_content(&new_input);
}
self.mut_state().set_mode(initial_mode(con));
}
pub fn set_input_content(&mut self, content: &str) {
self.input.set_content(content);
}
pub fn get_input_content(&self) -> String {
self.input.get_content()
}
pub fn set_input_arg(&mut self, arg: String) {
let mut command_parts = CommandParts::from(self.input.get_content());
if let Some(invocation) = &mut command_parts.verb_invocation {
invocation.args = Some(arg);
let new_input = format!("{}", command_parts);
self.input.set_content(&new_input);
}
}
pub fn remove_state(&mut self) -> bool {
if self.states.len() > 1 {
self.states.pop();
self.input.set_content(&self.state().get_starting_input());
true
} else {
false
}
}
pub fn display(
&mut self,
w: &mut W,
disc: &DisplayContext,
) -> Result<(), ProgramError> {
self.mut_state().display(w, disc)?;
if disc.active || !WIDE_STATUS {
self.write_status(w, &disc.panel_skin, disc.screen)?;
}
let mut input_area = self.areas.input.clone();
if disc.active {
self.write_purpose(w, &disc.panel_skin, disc.screen, &disc.con)?;
let flags = self.state().get_flags();
let input_content_len = self.input.get_content().len() as u16;
let flags_len = flags_display::visible_width(&flags);
if input_area.width > input_content_len + 1 + flags_len {
input_area.width -= flags_len + 1;
disc.screen.goto(w, input_area.left + input_area.width, input_area.top)?;
flags_display::write(w, &flags, &disc.panel_skin)?;
}
}
self.input.display(w, disc.active, self.state().get_mode(), input_area, &disc.panel_skin)?;
Ok(())
}
fn write_status(
&self,
w: &mut W,
panel_skin: &PanelSkin,
screen: Screen,
) -> Result<(), ProgramError> {
let task = self.state().get_pending_task();
status_line::write(
w,
task,
&self.status,
&self.areas.status,
panel_skin,
screen,
)
}
fn write_purpose(
&self,
w: &mut W,
panel_skin: &PanelSkin,
screen: Screen,
con: &AppContext,
) -> Result<(), ProgramError> {
if !self.purpose.is_arg_edition() {
return Ok(());
}
if let Some(area) = &self.areas.purpose {
let shortcut = con
.verb_store
.verbs
.iter()
.filter(|v| match &v.execution {
VerbExecution::Internal(exec) => exec.internal == Internal::start_end_panel,
_ => false,
})
.filter_map(|v| v.keys.first())
.map(|&k| keys::key_event_desc(k))
.next()
.unwrap_or_else(|| ":start_end_panel".to_string());
let md = format!("hit *{}* to fill arg ", shortcut);
screen.goto(w, area.left, area.top)?;
panel_skin.purpose_skin.write_composite_fill(
w,
Composite::from_inline(&md),
area.width as usize,
Alignment::Right,
)?;
}
Ok(())
}
}