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.

1448 lines
46KB

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