1 use { 2 super::*, 3 crate::{ 4 command::*, 5 display::{ 6 status_line, 7 Areas, 8 Screen, 9 W, 10 WIDE_STATUS, 11 flags_display, 12 }, 13 errors::ProgramError, 14 keys, 15 skin::PanelSkin, 16 stage::*, 17 task_sync::Dam, 18 verb::*, 19 }, 20 termimad::{ 21 minimad::{Alignment, Composite}, 22 TimedEvent, 23 }, 24 }; 25 26 /// A colon on screen containing a stack of states, the top 27 /// one being visible 28 pub struct Panel { 29 pub id: PanelId, 30 states: Vec<Box<dyn PanelState>>, // stack: the last one is current 31 pub areas: Areas, 32 status: Status, 33 pub purpose: PanelPurpose, 34 input: PanelInput, 35 } 36 37 impl Panel { 38 new( id: PanelId, state: Box<dyn PanelState>, areas: Areas, con: &AppContext, ) -> Self39 pub fn new( 40 id: PanelId, 41 state: Box<dyn PanelState>, 42 areas: Areas, 43 con: &AppContext, 44 ) -> Self { 45 let mut input = PanelInput::new(areas.input.clone()); 46 input.set_content(&state.get_starting_input()); 47 let status = state.no_verb_status(false, con); 48 Self { 49 id, 50 states: vec![state], 51 areas, 52 status, 53 purpose: PanelPurpose::None, 54 input, 55 } 56 } 57 set_error(&mut self, text: String)58 pub fn set_error(&mut self, text: String) { 59 self.status = Status::from_error(text); 60 } 61 62 /// apply a command on the current state, with no 63 /// effect on screen 64 #[allow(clippy::too_many_arguments)] // a refactory could still be useful 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>65 pub fn apply_command<'c>( 66 &mut self, 67 w: &'c mut W, 68 cmd: &'c Command, 69 app_state: &mut AppState, 70 app_cmd_context: &'c AppCmdContext<'c>, 71 ) -> Result<CmdResult, ProgramError> { 72 let state_idx = self.states.len() - 1; 73 let cc = CmdContext { 74 cmd, 75 app: app_cmd_context, 76 panel: PanelCmdContext { 77 areas: &self.areas, 78 purpose: self.purpose, 79 }, 80 }; 81 let result = self.states[state_idx].on_command(w, app_state, &cc); 82 let has_previous_state = self.states.len() > 1; 83 self.status = self.state().get_status(app_state, &cc, has_previous_state); 84 result 85 } 86 87 /// called on focusing the panel and before the display, 88 /// this updates the status from the command read in the input refresh_input_status<'c>( &mut self, app_state: &AppState, app_cmd_context: &'c AppCmdContext<'c>, )89 pub fn refresh_input_status<'c>( 90 &mut self, 91 app_state: &AppState, 92 app_cmd_context: &'c AppCmdContext<'c>, 93 ) { 94 let cmd = Command::from_raw(self.input.get_content(), false); 95 let cc = CmdContext { 96 cmd: &cmd, 97 app: app_cmd_context, 98 panel: PanelCmdContext { 99 areas: &self.areas, 100 purpose: self.purpose, 101 }, 102 }; 103 let has_previous_state = self.states.len() > 1; 104 self.status = self.state().get_status(app_state, &cc, has_previous_state); 105 } 106 107 108 /// do the next pending task stopping as soon as there's an event 109 /// in the dam do_pending_task( &mut self, stage: &Stage, screen: Screen, con: &AppContext, dam: &mut Dam, )110 pub fn do_pending_task( 111 &mut self, 112 stage: &Stage, 113 screen: Screen, 114 con: &AppContext, 115 dam: &mut Dam, 116 ) { 117 self.mut_state().do_pending_task(stage, screen, con, dam) 118 } 119 has_pending_task(&self) -> bool120 pub fn has_pending_task(&self) -> bool { 121 self.state().get_pending_task().is_some() 122 } 123 124 /// return a new command 125 /// Update the input field add_event( &mut self, w: &mut W, event: TimedEvent, app_state: &AppState, con: &AppContext, ) -> Result<Command, ProgramError>126 pub fn add_event( 127 &mut self, 128 w: &mut W, 129 event: TimedEvent, 130 app_state: &AppState, 131 con: &AppContext, 132 ) -> Result<Command, ProgramError> { 133 let sel_info = self.states[self.states.len() - 1].sel_info(app_state); 134 self.input.on_event(w, event, con, sel_info, app_state, self.state().get_mode()) 135 } 136 push_state(&mut self, new_state: Box<dyn PanelState>)137 pub fn push_state(&mut self, new_state: Box<dyn PanelState>) { 138 self.input.set_content(&new_state.get_starting_input()); 139 self.states.push(new_state); 140 } mut_state(&mut self) -> &mut dyn PanelState141 pub fn mut_state(&mut self) -> &mut dyn PanelState { 142 self.states.last_mut().unwrap().as_mut() 143 } state(&self) -> &dyn PanelState144 pub fn state(&self) -> &dyn PanelState { 145 self.states.last().unwrap().as_ref() 146 } 147 clear_input(&mut self)148 pub fn clear_input(&mut self) { 149 self.input.set_content(""); 150 } 151 /// remove the verb invocation from the input but keep 152 /// the filter if there's one clear_input_invocation(&mut self, con: &AppContext)153 pub fn clear_input_invocation(&mut self, con: &AppContext) { 154 let mut command_parts = CommandParts::from(self.input.get_content()); 155 if command_parts.verb_invocation.is_some() { 156 command_parts.verb_invocation = None; 157 let new_input = format!("{}", command_parts); 158 self.input.set_content(&new_input); 159 } 160 self.mut_state().set_mode(initial_mode(con)); 161 } 162 set_input_content(&mut self, content: &str)163 pub fn set_input_content(&mut self, content: &str) { 164 self.input.set_content(content); 165 } 166 get_input_content(&self) -> String167 pub fn get_input_content(&self) -> String { 168 self.input.get_content() 169 } 170 171 /// change the argument of the verb in the input, if there's one set_input_arg(&mut self, arg: String)172 pub fn set_input_arg(&mut self, arg: String) { 173 let mut command_parts = CommandParts::from(self.input.get_content()); 174 if let Some(invocation) = &mut command_parts.verb_invocation { 175 invocation.args = Some(arg); 176 let new_input = format!("{}", command_parts); 177 self.input.set_content(&new_input); 178 } 179 } 180 181 /// return true when the element has been removed remove_state(&mut self) -> bool182 pub fn remove_state(&mut self) -> bool { 183 if self.states.len() > 1 { 184 self.states.pop(); 185 self.input.set_content(&self.state().get_starting_input()); 186 true 187 } else { 188 false 189 } 190 } 191 192 /// render the whole panel (state, status, purpose, input, flags) display( &mut self, w: &mut W, disc: &DisplayContext, ) -> Result<(), ProgramError>193 pub fn display( 194 &mut self, 195 w: &mut W, 196 disc: &DisplayContext, 197 ) -> Result<(), ProgramError> { 198 self.mut_state().display(w, disc)?; 199 if disc.active || !WIDE_STATUS { 200 self.write_status(w, disc.panel_skin, disc.screen)?; 201 } 202 let mut input_area = self.areas.input.clone(); 203 if disc.active { 204 self.write_purpose(w, disc.panel_skin, disc.screen, disc.con)?; 205 let flags = self.state().get_flags(); 206 let input_content_len = self.input.get_content().len() as u16; 207 let flags_len = flags_display::visible_width(&flags); 208 if input_area.width > input_content_len + 1 + flags_len { 209 input_area.width -= flags_len + 1; 210 disc.screen.goto(w, input_area.left + input_area.width, input_area.top)?; 211 flags_display::write(w, &flags, disc.panel_skin)?; 212 } 213 } 214 self.input.display(w, disc.active, self.state().get_mode(), input_area, disc.panel_skin)?; 215 Ok(()) 216 } 217 write_status( &self, w: &mut W, panel_skin: &PanelSkin, screen: Screen, ) -> Result<(), ProgramError>218 fn write_status( 219 &self, 220 w: &mut W, 221 panel_skin: &PanelSkin, 222 screen: Screen, 223 ) -> Result<(), ProgramError> { 224 let task = self.state().get_pending_task(); 225 status_line::write( 226 w, 227 task, 228 &self.status, 229 &self.areas.status, 230 panel_skin, 231 screen, 232 ) 233 } 234 235 /// if a panel has a specific purpose (i.e. is here for 236 /// editing of the verb argument on another panel), render 237 /// a hint of that purpose on screen write_purpose( &self, w: &mut W, panel_skin: &PanelSkin, screen: Screen, con: &AppContext, ) -> Result<(), ProgramError>238 fn write_purpose( 239 &self, 240 w: &mut W, 241 panel_skin: &PanelSkin, 242 screen: Screen, 243 con: &AppContext, 244 ) -> Result<(), ProgramError> { 245 if !self.purpose.is_arg_edition() { 246 return Ok(()); 247 } 248 if let Some(area) = &self.areas.purpose { 249 let shortcut = con 250 .verb_store 251 .verbs 252 .iter() 253 .filter(|v| match &v.execution { 254 VerbExecution::Internal(exec) => exec.internal == Internal::start_end_panel, 255 _ => false, 256 }) 257 .filter_map(|v| v.keys.first()) 258 .map(|&k| keys::key_event_desc(k)) 259 .next() 260 .unwrap_or_else(|| ":start_end_panel".to_string()); 261 262 let md = format!("hit *{}* to fill arg ", shortcut); 263 // Add verbindex in purpose ? 264 screen.goto(w, area.left, area.top)?; 265 panel_skin.purpose_skin.write_composite_fill( 266 w, 267 Composite::from_inline(&md), 268 area.width as usize, 269 Alignment::Right, 270 )?; 271 } 272 Ok(()) 273 } 274 275 } 276