You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1682 lines
56KB

  1. /*
  2. * meli - ui crate.
  3. *
  4. * Copyright 2017-2018 Manos Pitsidianakis
  5. *
  6. * This file is part of meli.
  7. *
  8. * meli is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * meli is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with meli. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. /*! Various useful components that can be used in a generic fashion.
  22. */
  23. use super::*;
  24. mod widgets;
  25. pub use self::widgets::*;
  26. use fnv::FnvHashSet;
  27. /// A horizontally split in half container.
  28. #[derive(Debug)]
  29. pub struct HSplit {
  30. top: Box<dyn Component>,
  31. bottom: Box<dyn Component>,
  32. show_divider: bool,
  33. ratio: usize, // bottom/whole height * 100
  34. id: ComponentId,
  35. }
  36. impl fmt::Display for HSplit {
  37. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  38. // TODO display subject/info
  39. Display::fmt(&self.top, f)
  40. }
  41. }
  42. impl HSplit {
  43. pub fn new(
  44. top: Box<dyn Component>,
  45. bottom: Box<dyn Component>,
  46. ratio: usize,
  47. show_divider: bool,
  48. ) -> Self {
  49. HSplit {
  50. top,
  51. bottom,
  52. show_divider,
  53. ratio,
  54. id: ComponentId::new_v4(),
  55. }
  56. }
  57. }
  58. impl Component for HSplit {
  59. fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  60. if !is_valid_area!(area) {
  61. return;
  62. }
  63. let upper_left = upper_left!(area);
  64. let bottom_right = bottom_right!(area);
  65. let total_rows = get_y(bottom_right) - get_y(upper_left);
  66. let bottom_component_height = (self.ratio * total_rows) / 100;
  67. let mid = get_y(upper_left) + total_rows - bottom_component_height;
  68. if self.show_divider {
  69. for i in get_x(upper_left)..=get_x(bottom_right) {
  70. grid[(i, mid)].set_ch('─');
  71. }
  72. context
  73. .dirty_areas
  74. .push_back(((get_x(upper_left), mid), (get_x(bottom_right), mid)));
  75. }
  76. self.top.draw(
  77. grid,
  78. (
  79. upper_left,
  80. (get_x(bottom_right), get_y(upper_left) + mid - 1),
  81. ),
  82. context,
  83. );
  84. self.bottom.draw(
  85. grid,
  86. ((get_x(upper_left), get_y(upper_left) + mid), bottom_right),
  87. context,
  88. );
  89. }
  90. fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
  91. self.top.process_event(event, context) || self.bottom.process_event(event, context)
  92. }
  93. fn is_dirty(&self) -> bool {
  94. self.top.is_dirty() || self.bottom.is_dirty()
  95. }
  96. fn set_dirty(&mut self) {
  97. self.top.set_dirty();
  98. self.bottom.set_dirty();
  99. }
  100. fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
  101. let mut top_map = self.top.get_shortcuts(context);
  102. top_map.extend(self.bottom.get_shortcuts(context).into_iter());
  103. top_map
  104. }
  105. fn id(&self) -> ComponentId {
  106. self.id
  107. }
  108. fn set_id(&mut self, id: ComponentId) {
  109. self.id = id;
  110. }
  111. }
  112. /// A vertically split in half container.
  113. #[derive(Debug)]
  114. pub struct VSplit {
  115. left: Box<dyn Component>,
  116. right: Box<dyn Component>,
  117. show_divider: bool,
  118. prev_visibility: (bool, bool),
  119. /// This is the width of the right container to the entire width.
  120. ratio: usize, // right/(container width) * 100
  121. id: ComponentId,
  122. }
  123. impl fmt::Display for VSplit {
  124. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  125. // TODO display focused component
  126. Display::fmt(&self.right, f)
  127. }
  128. }
  129. impl VSplit {
  130. pub fn new(
  131. left: Box<dyn Component>,
  132. right: Box<dyn Component>,
  133. ratio: usize,
  134. show_divider: bool,
  135. ) -> Self {
  136. VSplit {
  137. left,
  138. right,
  139. show_divider,
  140. prev_visibility: (true, true),
  141. ratio,
  142. id: ComponentId::new_v4(),
  143. }
  144. }
  145. }
  146. impl Component for VSplit {
  147. fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  148. if !is_valid_area!(area) {
  149. return;
  150. }
  151. let upper_left = upper_left!(area);
  152. let bottom_right = bottom_right!(area);
  153. let total_cols = get_x(bottom_right) - get_x(upper_left);
  154. let visibility = (self.left.is_visible(), self.right.is_visible());
  155. if visibility != self.prev_visibility {
  156. self.set_dirty();
  157. self.prev_visibility = visibility;
  158. }
  159. let right_component_width = match visibility {
  160. (true, true) => (self.ratio * total_cols) / 100,
  161. (false, true) => total_cols,
  162. (true, false) => 0,
  163. (false, false) => {
  164. clear_area(grid, area);
  165. return;
  166. }
  167. };
  168. let mid = get_x(bottom_right) - right_component_width;
  169. if get_y(upper_left) > 1 {
  170. let c = grid
  171. .get(mid, get_y(upper_left) - 1)
  172. .map(Cell::ch)
  173. .unwrap_or_else(|| ' ');
  174. if let HORZ_BOUNDARY = c {
  175. grid[(mid, get_y(upper_left) - 1)].set_ch(LIGHT_DOWN_AND_HORIZONTAL);
  176. }
  177. }
  178. if self.show_divider && mid != get_x(upper_left) {
  179. for i in get_y(upper_left)..=get_y(bottom_right) {
  180. grid[(mid, i)].set_ch(VERT_BOUNDARY);
  181. grid[(mid, i)].set_fg(Color::Default);
  182. grid[(mid, i)].set_bg(Color::Default);
  183. }
  184. if get_y(bottom_right) > 1 {
  185. let c = grid
  186. .get(mid, get_y(bottom_right) - 1)
  187. .map(Cell::ch)
  188. .unwrap_or_else(|| ' ');
  189. if let HORZ_BOUNDARY = c {
  190. grid[(mid, get_y(bottom_right) + 1)].set_ch(LIGHT_UP_AND_HORIZONTAL);
  191. }
  192. }
  193. context
  194. .dirty_areas
  195. .push_back(((mid, get_y(upper_left)), (mid, get_y(bottom_right))));
  196. }
  197. if right_component_width == total_cols {
  198. self.right.draw(grid, area, context);
  199. } else if right_component_width == 0 {
  200. self.left.draw(grid, area, context);
  201. } else {
  202. self.left.draw(
  203. grid,
  204. (
  205. upper_left,
  206. (
  207. if self.show_divider { mid - 1 } else { mid },
  208. get_y(bottom_right),
  209. ),
  210. ),
  211. context,
  212. );
  213. self.right
  214. .draw(grid, (set_x(upper_left, mid + 1), bottom_right), context);
  215. }
  216. }
  217. fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
  218. (self.left.process_event(event, context) || self.right.process_event(event, context))
  219. }
  220. fn is_dirty(&self) -> bool {
  221. self.left.is_dirty() || self.right.is_dirty()
  222. }
  223. fn set_dirty(&mut self) {
  224. self.left.set_dirty();
  225. self.right.set_dirty();
  226. }
  227. fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
  228. let mut right_map = self.right.get_shortcuts(context);
  229. right_map.extend(self.left.get_shortcuts(context).into_iter());
  230. right_map
  231. }
  232. fn id(&self) -> ComponentId {
  233. self.id
  234. }
  235. fn set_id(&mut self, id: ComponentId) {
  236. self.id = id;
  237. }
  238. }
  239. #[derive(Debug, Clone, Copy)]
  240. pub enum PageMovement {
  241. Home,
  242. PageUp,
  243. PageDown,
  244. End,
  245. }
  246. /// A pager for text.
  247. /// `Pager` holds its own content in its own `CellBuffer` and when `draw` is called, it draws the
  248. /// current view of the text. It is responsible for scrolling etc.
  249. #[derive(Default, Debug, Clone)]
  250. pub struct Pager {
  251. text: String,
  252. cursor_pos: usize,
  253. max_cursor_pos: Option<usize>,
  254. height: usize,
  255. width: usize,
  256. dirty: bool,
  257. content: CellBuffer,
  258. movement: Option<PageMovement>,
  259. id: ComponentId,
  260. }
  261. impl fmt::Display for Pager {
  262. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  263. // TODO display info
  264. write!(f, "{}", Pager::DESCRIPTION)
  265. }
  266. }
  267. impl Pager {
  268. const DESCRIPTION: &'static str = "pager";
  269. pub fn update_from_str(&mut self, text: &str, width: Option<usize>) {
  270. let lines: Vec<&str> = if let Some(width) = width {
  271. word_break_string(text, width)
  272. } else {
  273. text.trim().split('\n').collect()
  274. };
  275. let height = lines.len() + 1;
  276. let width = width.unwrap_or_else(|| lines.iter().map(|l| l.len()).max().unwrap_or(0));
  277. let mut content = CellBuffer::new(width, height, Cell::with_char(' '));
  278. Pager::print_string(&mut content, lines);
  279. self.text = text.to_string();
  280. self.content = content;
  281. self.height = height;
  282. self.width = width;
  283. self.dirty = true;
  284. self.cursor_pos = 0;
  285. self.max_cursor_pos = None;
  286. }
  287. pub fn from_string(
  288. mut text: String,
  289. context: Option<&Context>,
  290. cursor_pos: Option<usize>,
  291. width: Option<usize>,
  292. ) -> Self {
  293. let pager_filter: Option<&String> = if let Some(context) = context {
  294. context.settings.pager.filter.as_ref()
  295. //let format_flowed: bool = context.settings.pager.format_flowed;
  296. } else {
  297. None
  298. };
  299. if let Some(bin) = pager_filter {
  300. use std::io::Write;
  301. use std::process::{Command, Stdio};
  302. let mut filter_child = Command::new(bin)
  303. .stdin(Stdio::piped())
  304. .stdout(Stdio::piped())
  305. .spawn()
  306. .expect("Failed to start pager filter process");
  307. {
  308. let stdin = filter_child.stdin.as_mut().expect("failed to open stdin");
  309. stdin
  310. .write_all(text.as_bytes())
  311. .expect("Failed to write to stdin");
  312. }
  313. text = String::from_utf8_lossy(
  314. &filter_child
  315. .wait_with_output()
  316. .expect("Failed to wait on filter")
  317. .stdout,
  318. )
  319. .to_string();
  320. }
  321. let content = {
  322. let lines: Vec<&str> = if let Some(width) = width {
  323. word_break_string(text.as_str(), width)
  324. } else {
  325. text.trim().split('\n').collect()
  326. };
  327. let height = lines.len() + 1;
  328. let width = width.unwrap_or_else(|| lines.iter().map(|l| l.len()).max().unwrap_or(0));
  329. let mut content = CellBuffer::new(width, height, Cell::with_char(' '));
  330. //interpret_format_flowed(&text);
  331. Pager::print_string(&mut content, lines);
  332. content
  333. };
  334. Pager {
  335. text,
  336. cursor_pos: cursor_pos.unwrap_or(0),
  337. height: content.size().1,
  338. width: content.size().0,
  339. dirty: true,
  340. content,
  341. id: ComponentId::new_v4(),
  342. ..Default::default()
  343. }
  344. }
  345. pub fn from_str(text: &str, cursor_pos: Option<usize>, width: Option<usize>) -> Self {
  346. let lines: Vec<&str> = if let Some(width) = width {
  347. word_break_string(text, width)
  348. } else {
  349. text.trim().split('\n').collect()
  350. };
  351. let height = lines.len() + 1;
  352. let width = width.unwrap_or_else(|| lines.iter().map(|l| l.len()).max().unwrap_or(0));
  353. let mut content = CellBuffer::new(width, height, Cell::with_char(' '));
  354. Pager::print_string(&mut content, lines);
  355. Pager {
  356. text: text.to_string(),
  357. cursor_pos: cursor_pos.unwrap_or(0),
  358. height,
  359. width,
  360. dirty: true,
  361. content,
  362. id: ComponentId::new_v4(),
  363. ..Default::default()
  364. }
  365. }
  366. pub fn from_buf(content: CellBuffer, cursor_pos: Option<usize>) -> Self {
  367. let (width, height) = content.size();
  368. Pager {
  369. text: String::new(),
  370. cursor_pos: cursor_pos.unwrap_or(0),
  371. height,
  372. width,
  373. dirty: true,
  374. content,
  375. id: ComponentId::new_v4(),
  376. ..Default::default()
  377. }
  378. }
  379. pub fn print_string(content: &mut CellBuffer, lines: Vec<&str>) {
  380. let width = content.size().0;
  381. for (i, l) in lines.iter().enumerate() {
  382. write_string_to_grid(
  383. l,
  384. content,
  385. Color::Default,
  386. Color::Default,
  387. Attr::Default,
  388. ((0, i), (width - 1, i)),
  389. true,
  390. );
  391. }
  392. }
  393. pub fn cursor_pos(&self) -> usize {
  394. self.cursor_pos
  395. }
  396. }
  397. impl Component for Pager {
  398. fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  399. if !is_valid_area!(area) {
  400. return;
  401. }
  402. if !self.is_dirty() {
  403. return;
  404. }
  405. self.dirty = false;
  406. let height = height!(area);
  407. if let Some(mvm) = self.movement.take() {
  408. match mvm {
  409. PageMovement::PageUp => {
  410. self.cursor_pos = self.cursor_pos.saturating_sub(height);
  411. }
  412. PageMovement::PageDown => {
  413. if self.cursor_pos + height < self.height {
  414. self.cursor_pos += height;
  415. }
  416. }
  417. PageMovement::Home => {
  418. self.cursor_pos = 0;
  419. }
  420. PageMovement::End => {
  421. self.cursor_pos = (self.height / height) * height;
  422. }
  423. }
  424. }
  425. if self.height == 0 || self.width == 0 {
  426. return;
  427. }
  428. clear_area(grid, area);
  429. //let pager_context: usize = context.settings.pager.pager_context;
  430. //let pager_stop: bool = context.settings.pager.pager_stop;
  431. //let rows = y(bottom_right) - y(upper_left);
  432. //let page_length = rows / self.height;
  433. let width = width!(area);
  434. if width != self.width {
  435. // Reflow text.
  436. let lines: Vec<&str> = word_break_string(self.text.as_str(), width);
  437. let height = lines.len() + 1;
  438. self.width = width;
  439. self.height = height;
  440. self.content = CellBuffer::new(width, height, Cell::with_char(' '));
  441. Pager::print_string(&mut self.content, lines);
  442. }
  443. if self.cursor_pos + height >= self.height {
  444. self.cursor_pos = self.height.saturating_sub(height);
  445. };
  446. copy_area_with_break(
  447. grid,
  448. &self.content,
  449. area,
  450. ((0, self.cursor_pos), (self.width - 1, self.height - 1)),
  451. );
  452. context.dirty_areas.push_back(area);
  453. }
  454. fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
  455. let shortcuts = &self.get_shortcuts(context)[Self::DESCRIPTION];
  456. match event {
  457. UIEvent::Input(ref key) if *key == shortcuts["scroll_up"] => {
  458. if self.cursor_pos > 0 {
  459. self.cursor_pos -= 1;
  460. self.dirty = true;
  461. }
  462. return true;
  463. }
  464. UIEvent::Input(ref key) if *key == shortcuts["scroll_down"] => {
  465. if self.height > 0 && self.cursor_pos + 1 < self.height {
  466. self.cursor_pos += 1;
  467. self.dirty = true;
  468. }
  469. return true;
  470. }
  471. UIEvent::Input(ref key) if *key == shortcuts["page_up"] => {
  472. self.movement = Some(PageMovement::PageUp);
  473. self.dirty = true;
  474. return true;
  475. }
  476. UIEvent::Input(ref key) if *key == shortcuts["page_down"] => {
  477. self.movement = Some(PageMovement::PageDown);
  478. self.dirty = true;
  479. return true;
  480. }
  481. UIEvent::ChangeMode(UIMode::Normal) => {
  482. self.dirty = true;
  483. }
  484. UIEvent::Action(Pager(Pipe(ref bin, ref args))) => {
  485. use std::io::Write;
  486. use std::process::{Command, Stdio};
  487. let mut command_obj = match Command::new(bin)
  488. .args(args.as_slice())
  489. .stdin(Stdio::piped())
  490. .stdout(Stdio::piped())
  491. .spawn()
  492. {
  493. Ok(o) => o,
  494. Err(e) => {
  495. context.replies.push_back(UIEvent::StatusEvent(
  496. StatusEvent::DisplayMessage(format!(
  497. "Could not pipe to {}: {}",
  498. bin, e
  499. )),
  500. ));
  501. return true;
  502. }
  503. };
  504. let stdin = command_obj.stdin.as_mut().expect("failed to open stdin");
  505. stdin
  506. .write_all(self.text.as_bytes())
  507. .expect("Failed to write to stdin");
  508. context
  509. .replies
  510. .push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(format!(
  511. "Pager text piped to '{}{}{}'",
  512. &bin,
  513. if args.is_empty() { "" } else { " " },
  514. args.join(" ")
  515. ))));
  516. return true;
  517. }
  518. UIEvent::Resize => {
  519. self.dirty = true;
  520. self.max_cursor_pos = None;
  521. }
  522. _ => {}
  523. }
  524. false
  525. }
  526. fn is_dirty(&self) -> bool {
  527. self.dirty
  528. }
  529. fn set_dirty(&mut self) {
  530. self.dirty = true;
  531. }
  532. fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
  533. let config_map: FnvHashMap<&'static str, &Key> =
  534. context.settings.shortcuts.pager.key_values();
  535. [(
  536. Pager::DESCRIPTION.to_string(),
  537. [
  538. ("scroll_up", (*config_map["scroll_up"]).clone()),
  539. ("scroll_down", (*config_map["scroll_down"]).clone()),
  540. ("page_up", (*config_map["page_up"]).clone()),
  541. ("page_down", (*config_map["page_down"]).clone()),
  542. ]
  543. .iter()
  544. .cloned()
  545. .collect::<ShortcutMap>(),
  546. )]
  547. .iter()
  548. .cloned()
  549. .collect()
  550. }
  551. fn id(&self) -> ComponentId {
  552. self.id
  553. }
  554. fn set_id(&mut self, id: ComponentId) {
  555. self.id = id;
  556. }
  557. }
  558. /// Status bar.
  559. #[derive(Debug)]
  560. pub struct StatusBar {
  561. container: Box<dyn Component>,
  562. status: String,
  563. notifications: VecDeque<String>,
  564. cur_notification: Option<(std::time::Instant, String)>,
  565. ex_buffer: Field,
  566. display_buffer: String,
  567. mode: UIMode,
  568. height: usize,
  569. dirty: bool,
  570. id: ComponentId,
  571. auto_complete: AutoComplete,
  572. cmd_history: Vec<String>,
  573. }
  574. impl fmt::Display for StatusBar {
  575. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  576. // TODO display info
  577. write!(f, "status bar")
  578. }
  579. }
  580. impl StatusBar {
  581. pub fn new(container: Box<dyn Component>) -> Self {
  582. StatusBar {
  583. container,
  584. status: String::with_capacity(256),
  585. notifications: VecDeque::new(),
  586. cur_notification: None,
  587. ex_buffer: Field::Text(UText::new(String::with_capacity(256)), None),
  588. display_buffer: String::with_capacity(8),
  589. dirty: true,
  590. mode: UIMode::Normal,
  591. height: 1,
  592. id: ComponentId::new_v4(),
  593. auto_complete: AutoComplete::new(Vec::new()),
  594. cmd_history: crate::execute::history::old_cmd_history(),
  595. }
  596. }
  597. fn draw_status_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  598. clear_area(grid, area);
  599. let (x, y) = write_string_to_grid(
  600. &self.status,
  601. grid,
  602. Color::Byte(123),
  603. Color::Byte(26),
  604. Attr::Default,
  605. area,
  606. false,
  607. );
  608. let offset = self.status.find('|').unwrap_or_else(|| self.status.len());
  609. for x in get_x(upper_left!(area))..get_x(upper_left!(area)) + offset {
  610. grid[(x, y)].set_attrs(Attr::Bold);
  611. }
  612. if self.cur_notification.is_some() {
  613. let (t, n) = self.cur_notification.as_ref().unwrap();
  614. if std::time::Instant::now().duration_since(*t) < std::time::Duration::new(5, 0) {
  615. write_string_to_grid(
  616. n,
  617. grid,
  618. Color::Byte(219),
  619. Color::Byte(88),
  620. Attr::Default,
  621. (
  622. (std::cmp::max(x, width!(area).saturating_sub(n.len())), y),
  623. bottom_right!(area),
  624. ),
  625. false,
  626. );
  627. } else {
  628. self.cur_notification = None;
  629. }
  630. }
  631. if self.cur_notification.is_none() {
  632. if let Some(n) = self.notifications.pop_front() {
  633. write_string_to_grid(
  634. &n,
  635. grid,
  636. Color::Byte(219),
  637. Color::Byte(88),
  638. Attr::Default,
  639. (
  640. (std::cmp::max(x, width!(area).saturating_sub(n.len())), y),
  641. bottom_right!(area),
  642. ),
  643. false,
  644. );
  645. self.cur_notification = Some((std::time::Instant::now(), n));
  646. }
  647. }
  648. let (x, y) = bottom_right!(area);
  649. for (idx, c) in self.display_buffer.chars().rev().enumerate() {
  650. if let Some(cell) = grid.get_mut(x.saturating_sub(idx).saturating_sub(1), y) {
  651. cell.set_ch(c);
  652. } else {
  653. break;
  654. }
  655. }
  656. change_colors(grid, area, Color::Byte(123), Color::Byte(26));
  657. context.dirty_areas.push_back(area);
  658. }
  659. fn draw_execute_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  660. clear_area(grid, area);
  661. let (x, y) = write_string_to_grid(
  662. self.ex_buffer.as_str(),
  663. grid,
  664. Color::Byte(219),
  665. Color::Byte(88),
  666. Attr::Default,
  667. area,
  668. false,
  669. );
  670. grid[(x, y)].set_attrs(Attr::Underline);
  671. change_colors(grid, area, Color::Byte(219), Color::Byte(88));
  672. context.dirty_areas.push_back(area);
  673. }
  674. }
  675. impl Component for StatusBar {
  676. fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  677. if !is_valid_area!(area) {
  678. return;
  679. }
  680. let upper_left = upper_left!(area);
  681. let bottom_right = bottom_right!(area);
  682. let total_rows = get_y(bottom_right) - get_y(upper_left);
  683. if total_rows <= self.height {
  684. return;
  685. }
  686. let height = self.height;
  687. self.container.draw(
  688. grid,
  689. (
  690. upper_left,
  691. (get_x(bottom_right), get_y(bottom_right) - height),
  692. ),
  693. context,
  694. );
  695. if !self.is_dirty() {
  696. return;
  697. }
  698. self.dirty = false;
  699. self.draw_status_bar(
  700. grid,
  701. (set_y(upper_left, get_y(bottom_right)), bottom_right),
  702. context,
  703. );
  704. match self.mode {
  705. UIMode::Normal => {}
  706. UIMode::Execute => {
  707. self.draw_execute_bar(
  708. grid,
  709. (
  710. set_y(upper_left, get_y(bottom_right) - height + 1),
  711. set_y(bottom_right, get_y(bottom_right) - height + 1),
  712. ),
  713. context,
  714. );
  715. /* don't autocomplete for less than 3 characters */
  716. if self.ex_buffer.as_str().split_graphemes().len() <= 2 {
  717. return;
  718. }
  719. let mut unique_suggestions: FnvHashSet<&str> = FnvHashSet::default();
  720. let mut suggestions: Vec<AutoCompleteEntry> = self
  721. .cmd_history
  722. .iter()
  723. .filter_map(|h| {
  724. let sug = self.ex_buffer.as_str();
  725. if h.starts_with(sug) && !unique_suggestions.contains(sug) {
  726. unique_suggestions.insert(sug);
  727. Some(h.clone().into())
  728. } else {
  729. None
  730. }
  731. })
  732. .collect();
  733. suggestions.extend(crate::execute::COMMAND_COMPLETION.iter().filter_map(|e| {
  734. if e.0.starts_with(self.ex_buffer.as_str()) {
  735. Some(e.into())
  736. } else {
  737. None
  738. }
  739. }));
  740. if suggestions.is_empty() && !self.auto_complete.suggestions().is_empty() {
  741. self.auto_complete.set_suggestions(suggestions);
  742. /* redraw self.container because we have got ridden of an autocomplete
  743. * box, and it must be drawn over */
  744. self.container.set_dirty();
  745. return;
  746. }
  747. /* redraw self.container because we have less suggestions than before */
  748. if suggestions.len() < self.auto_complete.suggestions().len() {
  749. self.container.set_dirty();
  750. }
  751. if self.auto_complete.set_suggestions(suggestions) {
  752. let len = self.auto_complete.suggestions().len() - 1;
  753. self.auto_complete.set_cursor(len);
  754. }
  755. let hist_height = std::cmp::min(15, self.auto_complete.suggestions().len());
  756. let hist_area = if height < self.auto_complete.suggestions().len() {
  757. let mut scrollbar = ScrollBar::default();
  758. scrollbar.set_show_arrows(false);
  759. scrollbar.set_block_character(Some('▌'));
  760. scrollbar.draw(
  761. grid,
  762. (
  763. (
  764. get_x(upper_left),
  765. get_y(bottom_right) - height - hist_height + 1,
  766. ),
  767. set_y(bottom_right, get_y(bottom_right) - height),
  768. ),
  769. self.auto_complete.cursor(),
  770. hist_height,
  771. self.auto_complete.suggestions().len(),
  772. );
  773. change_colors(
  774. grid,
  775. (
  776. (
  777. get_x(upper_left),
  778. get_y(bottom_right) - height - hist_height + 1,
  779. ),
  780. set_y(bottom_right, get_y(bottom_right) - height),
  781. ),
  782. Color::Byte(197), // DeepPink2,
  783. Color::Byte(174), //LightPink3
  784. );
  785. context.dirty_areas.push_back((
  786. (
  787. get_x(upper_left),
  788. get_y(bottom_right) - height - hist_height + 1,
  789. ),
  790. set_y(bottom_right, get_y(bottom_right) - height),
  791. ));
  792. (
  793. (
  794. get_x(upper_left) + 1,
  795. get_y(bottom_right) - height - hist_height + 1,
  796. ),
  797. set_y(bottom_right, get_y(bottom_right) - height),
  798. )
  799. } else {
  800. (
  801. (
  802. get_x(upper_left),
  803. get_y(bottom_right) - height - hist_height + 1,
  804. ),
  805. set_y(bottom_right, get_y(bottom_right) - height),
  806. )
  807. };
  808. let offset = if hist_height
  809. > (self.auto_complete.suggestions().len() - self.auto_complete.cursor())
  810. {
  811. self.auto_complete.suggestions().len() - hist_height
  812. } else {
  813. self.auto_complete.cursor()
  814. };
  815. clear_area(grid, hist_area);
  816. if hist_height > 0 {
  817. change_colors(
  818. grid,
  819. hist_area,
  820. Color::Byte(88), // DarkRed,
  821. Color::Byte(174), //LightPink3
  822. );
  823. }
  824. for (y_offset, s) in self
  825. .auto_complete
  826. .suggestions()
  827. .iter()
  828. .skip(offset)
  829. .take(hist_height)
  830. .enumerate()
  831. {
  832. let (x, y) = write_string_to_grid(
  833. s.as_str(),
  834. grid,
  835. Color::Byte(88), // DarkRed,
  836. Color::Byte(174), //LightPink3
  837. Attr::Default,
  838. (
  839. set_y(
  840. upper_left!(hist_area),
  841. get_y(bottom_right!(hist_area)) - hist_height + y_offset + 1,
  842. ),
  843. bottom_right!(hist_area),
  844. ),
  845. true,
  846. );
  847. write_string_to_grid(
  848. &s.description,
  849. grid,
  850. Color::White,
  851. Color::Byte(174),
  852. Attr::Default,
  853. ((x + 2, y), bottom_right!(hist_area)),
  854. false,
  855. );
  856. if y_offset + offset == self.auto_complete.cursor() {
  857. change_colors(
  858. grid,
  859. (
  860. set_y(
  861. upper_left!(hist_area),
  862. get_y(bottom_right!(hist_area)) - hist_height + y_offset + 1,
  863. ),
  864. set_y(
  865. bottom_right!(hist_area),
  866. get_y(bottom_right!(hist_area)) - hist_height + y_offset + 1,
  867. ),
  868. ),
  869. Color::Byte(88), // DarkRed,
  870. Color::Byte(173), //LightSalmon3
  871. );
  872. write_string_to_grid(
  873. &s.as_str()[self.ex_buffer.as_str().len()..],
  874. grid,
  875. Color::Byte(97), // MediumPurple3,
  876. Color::Byte(88), //LightPink3
  877. Attr::Default,
  878. (
  879. (
  880. get_y(upper_left)
  881. + self.ex_buffer.as_str().split_graphemes().len(),
  882. get_y(bottom_right) - height + 1,
  883. ),
  884. set_y(bottom_right, get_y(bottom_right) - height + 1),
  885. ),
  886. true,
  887. );
  888. }
  889. }
  890. context.dirty_areas.push_back(hist_area);
  891. }
  892. _ => {}
  893. }
  894. }
  895. fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
  896. if self.container.process_event(event, context) {
  897. return true;
  898. }
  899. match event {
  900. UIEvent::ChangeMode(m) => {
  901. let offset = self.status.find('|').unwrap_or_else(|| self.status.len());
  902. self.status.replace_range(..offset, &format!("{} ", m));
  903. self.dirty = true;
  904. self.mode = *m;
  905. match m {
  906. UIMode::Normal => {
  907. self.height = 1;
  908. if !self.ex_buffer.is_empty() {
  909. context
  910. .replies
  911. .push_back(UIEvent::Command(self.ex_buffer.as_str().to_string()));
  912. }
  913. if parse_command(&self.ex_buffer.as_str().as_bytes())
  914. .to_full_result()
  915. .is_ok()
  916. && self.cmd_history.last().map(String::as_str)
  917. != Some(self.ex_buffer.as_str())
  918. {
  919. crate::execute::history::log_cmd(self.ex_buffer.as_str().to_string());
  920. self.cmd_history.push(self.ex_buffer.as_str().to_string());
  921. }
  922. self.ex_buffer.clear();
  923. }
  924. UIMode::Execute => {
  925. self.height = 2;
  926. }
  927. _ => {}
  928. };
  929. }
  930. UIEvent::ExInput(Key::Char('\t')) => {
  931. if let Some(suggestion) = self.auto_complete.get_suggestion() {
  932. let mut utext = UText::new(suggestion);
  933. let len = utext.as_str().len();
  934. utext.set_cursor(len);
  935. self.container.set_dirty();
  936. self.set_dirty();
  937. self.ex_buffer = Field::Text(utext, None);
  938. }
  939. }
  940. UIEvent::ExInput(Key::Char(c)) => {
  941. self.dirty = true;
  942. self.ex_buffer
  943. .process_event(&mut UIEvent::InsertInput(Key::Char(*c)), context);
  944. return true;
  945. }
  946. UIEvent::ExInput(Key::Paste(s)) => {
  947. self.dirty = true;
  948. self.ex_buffer
  949. .process_event(&mut UIEvent::InsertInput(Key::Paste(s.clone())), context);
  950. return true;
  951. }
  952. UIEvent::ExInput(Key::Ctrl('u')) => {
  953. self.dirty = true;
  954. self.ex_buffer.clear();
  955. return true;
  956. }
  957. UIEvent::ExInput(k @ Key::Backspace) | UIEvent::ExInput(k @ Key::Ctrl('h')) => {
  958. self.dirty = true;
  959. self.ex_buffer
  960. .process_event(&mut UIEvent::InsertInput(k.clone()), context);
  961. return true;
  962. }
  963. UIEvent::ExInput(Key::Up) => {
  964. self.auto_complete.dec_cursor();
  965. self.dirty = true;
  966. }
  967. UIEvent::ExInput(Key::Down) => {
  968. self.auto_complete.inc_cursor();
  969. self.dirty = true;
  970. }
  971. UIEvent::Resize => {
  972. self.dirty = true;
  973. }
  974. UIEvent::StatusEvent(StatusEvent::DisplayMessage(s)) => {
  975. self.notifications.push_back(s.clone());
  976. self.dirty = true;
  977. }
  978. UIEvent::StatusEvent(StatusEvent::BufClear) => {
  979. self.display_buffer.clear();
  980. self.dirty = true;
  981. }
  982. UIEvent::StatusEvent(StatusEvent::BufSet(s)) => {
  983. self.display_buffer = s.clone();
  984. self.dirty = true;
  985. }
  986. UIEvent::StatusEvent(StatusEvent::UpdateStatus(ref mut s)) => {
  987. self.status = format!("{} | {}", self.mode, std::mem::replace(s, String::new()));
  988. self.dirty = true;
  989. }
  990. _ => {}
  991. }
  992. false
  993. }
  994. fn is_dirty(&self) -> bool {
  995. self.dirty || self.container.is_dirty()
  996. }
  997. fn set_dirty(&mut self) {
  998. self.dirty = true;
  999. }
  1000. fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
  1001. self.container.get_shortcuts(context)
  1002. }
  1003. fn id(&self) -> ComponentId {
  1004. self.id
  1005. }
  1006. fn set_id(&mut self, id: ComponentId) {
  1007. self.id = id;
  1008. }
  1009. fn can_quit_cleanly(&mut self) -> bool {
  1010. self.container.can_quit_cleanly()
  1011. }
  1012. }
  1013. #[derive(Debug)]
  1014. pub struct Progress {
  1015. description: String,
  1016. total_work: usize,
  1017. finished: usize,
  1018. id: ComponentId,
  1019. }
  1020. impl Progress {
  1021. pub fn new(s: String, total_work: usize) -> Self {
  1022. Progress {
  1023. description: s,
  1024. total_work,
  1025. finished: 0,
  1026. id: ComponentId::new_v4(),
  1027. }
  1028. }
  1029. pub fn add_work(&mut self, n: usize) {
  1030. if self.finished >= self.total_work {
  1031. return;
  1032. }
  1033. self.finished += n;
  1034. }
  1035. pub fn percentage(&self) -> usize {
  1036. if self.total_work > 0 {
  1037. 100 * self.finished / self.total_work
  1038. } else {
  1039. 0
  1040. }
  1041. }
  1042. pub fn description(&self) -> &str {
  1043. &self.description
  1044. }
  1045. }
  1046. impl fmt::Display for Progress {
  1047. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  1048. // TODO display info
  1049. write!(f, "progress bar")
  1050. }
  1051. }
  1052. impl Component for Progress {
  1053. fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {
  1054. unimplemented!()
  1055. }
  1056. fn process_event(&mut self, _event: &mut UIEvent, _context: &mut Context) -> bool {
  1057. false
  1058. }
  1059. fn set_dirty(&mut self) {}
  1060. fn is_dirty(&self) -> bool {
  1061. false
  1062. }
  1063. fn id(&self) -> ComponentId {
  1064. self.id
  1065. }
  1066. fn set_id(&mut self, id: ComponentId) {
  1067. self.id = id;
  1068. }
  1069. }
  1070. #[derive(Debug)]
  1071. pub struct Tabbed {
  1072. pinned: usize,
  1073. children: Vec<Box<dyn Component>>,
  1074. cursor_pos: usize,
  1075. show_shortcuts: bool,
  1076. help_screen_cursor: (usize, usize),
  1077. help_content: CellBuffer,
  1078. help_curr_views: ShortcutMaps,
  1079. dirty: bool,
  1080. id: ComponentId,
  1081. }
  1082. impl Tabbed {
  1083. pub fn new(children: Vec<Box<dyn Component>>) -> Self {
  1084. let pinned = children.len();
  1085. Tabbed {
  1086. pinned,
  1087. children,
  1088. cursor_pos: 0,
  1089. show_shortcuts: false,
  1090. help_content: CellBuffer::default(),
  1091. help_screen_cursor: (0, 0),
  1092. help_curr_views: ShortcutMaps::default(),
  1093. dirty: true,
  1094. id: ComponentId::new_v4(),
  1095. }
  1096. }
  1097. fn draw_tabs(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  1098. let upper_left = upper_left!(area);
  1099. if self.children.is_empty() {
  1100. clear_area(grid, area);
  1101. return;
  1102. }
  1103. let mut x = get_x(upper_left);
  1104. let y: usize = get_y(upper_left);
  1105. for (idx, c) in self.children.iter().enumerate() {
  1106. let (fg, bg) = if idx == self.cursor_pos {
  1107. (Color::Default, Color::Default)
  1108. } else {
  1109. (Color::Byte(15), Color::Byte(8))
  1110. };
  1111. let (x_, _y_) = write_string_to_grid(
  1112. &format!(" {} ", c),
  1113. grid,
  1114. fg,
  1115. bg,
  1116. Attr::Default,
  1117. (set_x(upper_left, x), bottom_right!(area)),
  1118. false,
  1119. );
  1120. x = x_ + 1;
  1121. if idx == self.pinned.saturating_sub(1) {
  1122. x += 2;
  1123. }
  1124. if y != _y_ {
  1125. break;
  1126. }
  1127. }
  1128. let (cols, _) = grid.size();
  1129. let cslice: &mut [Cell] = grid;
  1130. //TODO: bounds check
  1131. let cslice_len = cslice.len();
  1132. for c in cslice[(y * cols) + x.saturating_sub(1)
  1133. ..std::cmp::min((y * cols) + x.saturating_sub(1), cslice_len)]
  1134. .iter_mut()
  1135. {
  1136. c.set_bg(Color::Byte(7));
  1137. c.set_ch(' ');
  1138. }
  1139. if self.cursor_pos == self.children.len() - 1 {
  1140. cslice[(y * cols) + x].set_ch('▍');
  1141. cslice[(y * cols) + x].set_fg(Color::Byte(8));
  1142. cslice[(y * cols) + x].set_bg(Color::Default);
  1143. }
  1144. context.dirty_areas.push_back(area);
  1145. }
  1146. pub fn add_component(&mut self, new: Box<dyn Component>) {
  1147. self.children.push(new);
  1148. }
  1149. }
  1150. impl fmt::Display for Tabbed {
  1151. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  1152. // TODO display info
  1153. write!(f, "tabs")
  1154. }
  1155. }
  1156. impl Component for Tabbed {
  1157. fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  1158. if self.dirty {
  1159. clear_area(
  1160. grid,
  1161. (
  1162. upper_left!(area),
  1163. set_x(upper_left!(area), get_x(bottom_right!(area))),
  1164. ),
  1165. );
  1166. context.dirty_areas.push_back((
  1167. upper_left!(area),
  1168. set_x(upper_left!(area), get_x(bottom_right!(area))),
  1169. ));
  1170. }
  1171. /* If children are dirty but self isn't and the shortcuts panel is visible, it will get
  1172. * overwritten. */
  1173. let must_redraw_shortcuts: bool = self.show_shortcuts && !self.dirty && self.is_dirty();
  1174. /* children should be drawn after the shortcuts/help panel lest they overwrite the panel on
  1175. * the grid. the drawing order is determined by the dirty_areas queue which is LIFO */
  1176. if self.children.len() > 1 {
  1177. self.draw_tabs(
  1178. grid,
  1179. (
  1180. upper_left!(area),
  1181. set_x(upper_left!(area), get_x(bottom_right!(area))),
  1182. ),
  1183. context,
  1184. );
  1185. let y = get_y(upper_left!(area));
  1186. self.children[self.cursor_pos].draw(
  1187. grid,
  1188. (set_y(upper_left!(area), y + 1), bottom_right!(area)),
  1189. context,
  1190. );
  1191. } else {
  1192. self.children[self.cursor_pos].draw(grid, area, context);
  1193. }
  1194. if (self.show_shortcuts && self.dirty) || must_redraw_shortcuts {
  1195. let area = (
  1196. pos_inc(upper_left!(area), (2, 1)),
  1197. set_x(
  1198. bottom_right!(area),
  1199. get_x(bottom_right!(area)).saturating_sub(2),
  1200. ),
  1201. );
  1202. context.dirty_areas.push_back(area);
  1203. clear_area(grid, area);
  1204. create_box(grid, area);
  1205. let area = (
  1206. pos_inc(upper_left!(area), (3, 2)),
  1207. set_x(
  1208. bottom_right!(area),
  1209. get_x(bottom_right!(area)).saturating_sub(3),
  1210. ),
  1211. );
  1212. let children_maps = self.children[self.cursor_pos].get_shortcuts(context);
  1213. if children_maps.is_empty() {
  1214. return;
  1215. }
  1216. if (children_maps == self.help_curr_views) && must_redraw_shortcuts {
  1217. let (width, height) = self.help_content.size();
  1218. let (cols, rows) = (width!(area), height!(area));
  1219. copy_area(
  1220. grid,
  1221. &self.help_content,
  1222. area,
  1223. (
  1224. (
  1225. std::cmp::min(
  1226. (width - 1).saturating_sub(cols),
  1227. self.help_screen_cursor.0,
  1228. ),
  1229. std::cmp::min(
  1230. (height - 1).saturating_sub(rows),
  1231. self.help_screen_cursor.1,
  1232. ),
  1233. ),
  1234. (
  1235. std::cmp::min(self.help_screen_cursor.0 + cols, width - 1),
  1236. std::cmp::min(self.help_screen_cursor.1 + rows, height - 1),
  1237. ),
  1238. ),
  1239. );
  1240. self.dirty = false;
  1241. return;
  1242. }
  1243. self.help_curr_views = children_maps;
  1244. let mut max_length = 5;
  1245. let mut max_width = "Use Up, Down, Left, Right to scroll.".len() + 3;
  1246. for (desc, shortcuts) in self.help_curr_views.iter() {
  1247. max_length += shortcuts.len() + 3;
  1248. max_width = std::cmp::max(
  1249. max_width,
  1250. std::cmp::max(
  1251. desc.len(),
  1252. shortcuts.keys().map(|k| k.len() + 5).max().unwrap_or(0),
  1253. ),
  1254. );
  1255. }
  1256. self.help_content = CellBuffer::new(max_width, max_length + 2, Cell::default());
  1257. let (width, height) = self.help_content.size();
  1258. let (cols, rows) = (width!(area), height!(area));
  1259. if cols == 0 || rows == 0 {
  1260. return;
  1261. }
  1262. /* trim cursor if it's bigger than the help screen */
  1263. self.help_screen_cursor = (
  1264. std::cmp::min(width.saturating_sub(cols), self.help_screen_cursor.0),
  1265. std::cmp::min(height.saturating_sub(rows), self.help_screen_cursor.1),
  1266. );
  1267. /* In this case we will be scrolling, so show the user how to do it */
  1268. if height.wrapping_div(rows) > 0 || width.wrapping_div(cols) > 0 {
  1269. write_string_to_grid(
  1270. "Use Up, Down, Left, Right to scroll.",
  1271. &mut self.help_content,
  1272. Color::Default,
  1273. Color::Default,
  1274. Attr::Default,
  1275. ((2, 0), (max_width.saturating_sub(2), max_length - 1)),
  1276. false,
  1277. );
  1278. }
  1279. let mut idx = 0;
  1280. for (desc, shortcuts) in self.help_curr_views.iter() {
  1281. write_string_to_grid(
  1282. desc,
  1283. &mut self.help_content,
  1284. Color::Default,
  1285. Color::Default,
  1286. Attr::Default,
  1287. ((2, 2 + idx), (max_width.saturating_sub(2), max_length - 1)),
  1288. false,
  1289. );
  1290. idx += 2;
  1291. let mut shortcuts = shortcuts.iter().collect::<Vec<_>>();
  1292. shortcuts.sort_unstable_by_key(|(ref k, _)| *k);
  1293. for (k, v) in shortcuts {
  1294. debug!(&(k, v));
  1295. let (x, y) = write_string_to_grid(
  1296. &k,
  1297. &mut self.help_content,
  1298. Color::Byte(29),
  1299. Color::Default,
  1300. Attr::Default,
  1301. ((2, 2 + idx), (max_width.saturating_sub(2), max_length - 1)),
  1302. false,
  1303. );
  1304. write_string_to_grid(
  1305. &format!("{}", v),
  1306. &mut self.help_content,
  1307. Color::Default,
  1308. Color::Default,
  1309. Attr::Default,
  1310. ((x + 2, y), (max_width.saturating_sub(2), max_length - 1)),
  1311. false,
  1312. );
  1313. idx += 1;
  1314. }
  1315. idx += 1;
  1316. }
  1317. copy_area(
  1318. grid,
  1319. &self.help_content,
  1320. area,
  1321. (
  1322. (
  1323. std::cmp::min((width - 1).saturating_sub(cols), self.help_screen_cursor.0),
  1324. std::cmp::min((height - 1).saturating_sub(rows), self.help_screen_cursor.1),
  1325. ),
  1326. (
  1327. std::cmp::min(self.help_screen_cursor.0 + cols, width - 1),
  1328. std::cmp::min(self.help_screen_cursor.1 + rows, height - 1),
  1329. ),
  1330. ),
  1331. );
  1332. }
  1333. self.dirty = false;
  1334. }
  1335. fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
  1336. match *event {
  1337. UIEvent::Input(Key::Char('T')) => {
  1338. self.cursor_pos = (self.cursor_pos + 1) % self.children.len();
  1339. context
  1340. .replies
  1341. .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
  1342. self.children[self.cursor_pos]
  1343. .get_status(context)
  1344. .unwrap_or_default(),
  1345. )));
  1346. self.set_dirty();
  1347. return true;
  1348. }
  1349. UIEvent::Input(Key::Char('?')) => {
  1350. if self.show_shortcuts {
  1351. /* children below the shortcut overlay must be redrawn */
  1352. self.set_dirty();
  1353. }
  1354. self.show_shortcuts = !self.show_shortcuts;
  1355. self.dirty = true;
  1356. return true;
  1357. }
  1358. UIEvent::Action(Tab(NewDraft(account_idx, ref draft))) => {
  1359. let mut composer = Composer::new(account_idx);
  1360. if let Some(draft) = draft {
  1361. composer.set_draft(draft.clone());
  1362. }
  1363. self.add_component(Box::new(composer));
  1364. self.cursor_pos = self.children.len() - 1;
  1365. self.children[self.cursor_pos].set_dirty();
  1366. return true;
  1367. }
  1368. UIEvent::Action(Tab(Reply(coordinates, msg))) => {
  1369. self.add_component(Box::new(Composer::with_context(coordinates, msg, context)));
  1370. self.cursor_pos = self.children.len() - 1;
  1371. self.children[self.cursor_pos].set_dirty();
  1372. return true;
  1373. }
  1374. UIEvent::Action(Tab(Edit(account_pos, msg))) => {
  1375. self.add_component(Box::new(Composer::edit(account_pos, msg, context)));
  1376. self.cursor_pos = self.children.len() - 1;
  1377. self.children[self.cursor_pos].set_dirty();
  1378. return true;
  1379. }
  1380. UIEvent::Action(Tab(New(ref mut e))) if e.is_some() => {
  1381. self.add_component(e.take().unwrap());
  1382. self.cursor_pos = self.children.len() - 1;
  1383. self.children[self.cursor_pos].set_dirty();
  1384. return true;
  1385. }
  1386. UIEvent::Action(Tab(Close)) => {
  1387. if self.pinned > self.cursor_pos {
  1388. return true;
  1389. }
  1390. let id = self.children[self.cursor_pos].id();
  1391. self.children[self.cursor_pos].kill(id, context);
  1392. self.set_dirty();
  1393. return true;
  1394. }
  1395. UIEvent::Action(Tab(Kill(id))) => {
  1396. if self.pinned > self.cursor_pos {
  1397. return true;
  1398. }
  1399. if let Some(c_idx) = self.children.iter().position(|x| x.id() == id) {
  1400. self.children.remove(c_idx);
  1401. self.cursor_pos = 0;
  1402. self.set_dirty();
  1403. return true;
  1404. } else {
  1405. debug!(
  1406. "DEBUG: Child component with id {:?} not found.\nList: {:?}",
  1407. id, self.children
  1408. );
  1409. }
  1410. }
  1411. UIEvent::Resize => {
  1412. self.dirty = true;
  1413. }
  1414. UIEvent::Input(ref k) if self.show_shortcuts => {
  1415. match k {
  1416. Key::Up => {
  1417. self.help_screen_cursor.1 = self.help_screen_cursor.1.saturating_sub(1);
  1418. }
  1419. Key::Down => {
  1420. self.help_screen_cursor.1 = self.help_screen_cursor.1 + 1;
  1421. }
  1422. Key::Left => {
  1423. self.help_screen_cursor.0 = self.help_screen_cursor.0.saturating_sub(1);
  1424. }
  1425. Key::Right => {
  1426. self.help_screen_cursor.0 = self.help_screen_cursor.0 + 1;
  1427. }
  1428. _ => {
  1429. /* ignore, don't pass to components below the shortcut panel */
  1430. return false;
  1431. }
  1432. }
  1433. self.dirty = true;
  1434. return true;
  1435. }
  1436. _ => {}
  1437. }
  1438. self.children[self.cursor_pos].process_event(event, context)
  1439. }
  1440. fn is_dirty(&self) -> bool {
  1441. self.dirty || self.children[self.cursor_pos].is_dirty()
  1442. }
  1443. fn set_dirty(&mut self) {
  1444. self.dirty = true;
  1445. self.children[self.cursor_pos].set_dirty();
  1446. }
  1447. fn id(&self) -> ComponentId {
  1448. self.id
  1449. }
  1450. fn set_id(&mut self, id: ComponentId) {
  1451. self.id = id;
  1452. }
  1453. fn can_quit_cleanly(&mut self) -> bool {
  1454. for (i, c) in self.children.iter_mut().enumerate() {
  1455. if !c.can_quit_cleanly() {
  1456. self.cursor_pos = i;
  1457. self.set_dirty();
  1458. return false;
  1459. }
  1460. }
  1461. true
  1462. }
  1463. }
  1464. type EntryIdentifier = Vec<u8>;
  1465. /// Shows selection to user
  1466. #[derive(Debug, PartialEq, Clone)]
  1467. pub struct Selector {
  1468. single_only: bool,
  1469. /// allow only one selection
  1470. entries: Vec<(EntryIdentifier, bool)>,
  1471. selected_entry_count: u32,
  1472. content: CellBuffer,
  1473. cursor: usize,
  1474. dirty: bool,
  1475. id: ComponentId,
  1476. }
  1477. impl fmt::Display for Selector {
  1478. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  1479. Display::fmt("Selector", f)
  1480. }
  1481. }
  1482. impl Component for Selector {
  1483. fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  1484. let (width, height) = self.content.size();
  1485. copy_area_with_break(grid, &self.content, area, ((0, 0), (width, height)));
  1486. context.dirty_areas.push_back(area);
  1487. }
  1488. fn process_event(&mut self, event: &mut UIEvent, _context: &mut Context) -> bool {
  1489. let (width, height) = self.content.size();
  1490. match *event {
  1491. UIEvent::Input(Key::Char('\t')) => {
  1492. self.entries[self.cursor].1 = !self.entries[self.cursor].1;
  1493. if self.entries[self.cursor].1 {
  1494. write_string_to_grid(
  1495. "x",
  1496. &mut self.content,
  1497. Color::Default,
  1498. Color::Default,
  1499. Attr::Default,
  1500. ((1, self.cursor), (width, self.cursor)),
  1501. false,
  1502. );
  1503. } else {
  1504. write_string_to_grid(
  1505. " ",
  1506. &mut self.content,
  1507. Color::Default,
  1508. Color::Default,
  1509. Attr::Default,
  1510. ((1, self.cursor), (width, self.cursor)),
  1511. false,
  1512. );
  1513. }
  1514. self.dirty = true;
  1515. return true;
  1516. }
  1517. UIEvent::Input(Key::Up) if self.cursor > 0 => {
  1518. self.cursor -= 1;
  1519. self.dirty = true;
  1520. return true;
  1521. }
  1522. UIEvent::Input(Key::Down) if self.cursor < height.saturating_sub(1) => {
  1523. self.cursor += 1;
  1524. self.dirty = true;
  1525. return true;
  1526. }
  1527. _ => {}
  1528. }
  1529. false
  1530. }
  1531. fn is_dirty(&self) -> bool {
  1532. self.dirty
  1533. }
  1534. fn set_dirty(&mut self) {
  1535. self.dirty = true;
  1536. }
  1537. fn id(&self) -> ComponentId {
  1538. self.id
  1539. }
  1540. fn set_id(&mut self, id: ComponentId) {
  1541. self.id = id;
  1542. }
  1543. }
  1544. impl Selector {
  1545. pub fn new(mut entries: Vec<(EntryIdentifier, String)>, single_only: bool) -> Selector {
  1546. let width = entries
  1547. .iter()
  1548. .max_by_key(|e| e.1.len())
  1549. .map(|v| v.1.len())
  1550. .unwrap_or(0)
  1551. + 4;
  1552. let height = entries.len();
  1553. let mut content = CellBuffer::new(width, height, Cell::with_char(' '));
  1554. let identifiers = entries
  1555. .iter_mut()
  1556. .map(|(id, _)| (std::mem::replace(&mut *id, Vec::new()), false))
  1557. .collect();
  1558. for (i, e) in entries.into_iter().enumerate() {
  1559. write_string_to_grid(
  1560. &format!("[ ] {}", e.1),
  1561. &mut content,
  1562. Color::Default,
  1563. Color::Default,
  1564. Attr::Default,
  1565. ((0, i), (width - 1, i)),
  1566. false,
  1567. );
  1568. }
  1569. Selector {
  1570. single_only,
  1571. entries: identifiers,
  1572. selected_entry_count: 0,
  1573. content,
  1574. cursor: 0,
  1575. dirty: true,
  1576. id: ComponentId::new_v4(),
  1577. }
  1578. }
  1579. pub fn collect(self) -> Vec<EntryIdentifier> {
  1580. self.entries
  1581. .into_iter()
  1582. .filter(|v| v.1)
  1583. .map(|(id, _)| id)
  1584. .collect()
  1585. }
  1586. }