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.

790 lines
23KB

  1. use super::*;
  2. use fnv::FnvHashMap;
  3. type AutoCompleteFn = Box<Fn(&Context, &str) -> Vec<String> + Send>;
  4. #[derive(Debug, PartialEq)]
  5. enum FormFocus {
  6. Fields,
  7. Buttons,
  8. TextInput,
  9. }
  10. type Cursor = usize;
  11. impl Default for FormFocus {
  12. fn default() -> FormFocus {
  13. FormFocus::Fields
  14. }
  15. }
  16. pub enum Field {
  17. Text(UText, Option<(AutoCompleteFn, AutoComplete)>),
  18. Choice(Vec<String>, Cursor),
  19. }
  20. impl Debug for Field {
  21. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  22. match self {
  23. Text(s, _) => fmt::Debug::fmt(s, f),
  24. k => fmt::Debug::fmt(k, f),
  25. }
  26. }
  27. }
  28. use crate::Field::*;
  29. impl Default for Field {
  30. fn default() -> Field {
  31. Field::Text(UText::new(String::with_capacity(256)), None)
  32. }
  33. }
  34. impl Field {
  35. pub fn as_str(&self) -> &str {
  36. match self {
  37. Text(ref s, _) => s.as_str(),
  38. Choice(ref v, cursor) => {
  39. if v.is_empty() {
  40. ""
  41. } else {
  42. v[*cursor].as_str()
  43. }
  44. }
  45. }
  46. }
  47. pub fn is_empty(&self) -> bool {
  48. self.as_str().is_empty()
  49. }
  50. pub fn into_string(self) -> String {
  51. match self {
  52. Text(s, _) => s.into_string(),
  53. Choice(mut v, cursor) => v.remove(cursor),
  54. }
  55. }
  56. pub fn clear(&mut self) {
  57. match self {
  58. Text(s, _) => s.clear(),
  59. Choice(_, _) => {}
  60. }
  61. }
  62. pub fn draw_cursor(
  63. &mut self,
  64. grid: &mut CellBuffer,
  65. area: Area,
  66. secondary_area: Area,
  67. context: &mut Context,
  68. ) {
  69. let upper_left = upper_left!(area);
  70. match self {
  71. Text(ref term, auto_complete_fn) => {
  72. change_colors(
  73. grid,
  74. (
  75. pos_inc(upper_left, (term.grapheme_pos(), 0)),
  76. (pos_inc(upper_left, (term.grapheme_pos(), 0))),
  77. ),
  78. Color::Default,
  79. Color::Byte(248),
  80. );
  81. if term.grapheme_len() <= 2 {
  82. return;
  83. }
  84. if let Some((auto_complete_fn, auto_complete)) = auto_complete_fn {
  85. let entries = auto_complete_fn(context, term.as_str());
  86. auto_complete.set_suggestions(entries);
  87. auto_complete.draw(grid, secondary_area, context);
  88. }
  89. }
  90. Choice(_, _cursor) => {}
  91. }
  92. }
  93. }
  94. impl Component for Field {
  95. fn draw(&mut self, grid: &mut CellBuffer, area: Area, _context: &mut Context) {
  96. write_string_to_grid(
  97. self.as_str(),
  98. grid,
  99. Color::Default,
  100. Color::Default,
  101. area,
  102. true,
  103. );
  104. }
  105. fn process_event(&mut self, event: &mut UIEvent, _context: &mut Context) -> bool {
  106. if let Text(ref mut s, Some((_, auto_complete))) = self {
  107. if let UIEvent::InsertInput(Key::Char('\t')) = event {
  108. if let Some(suggestion) = auto_complete.get_suggestion() {
  109. *s = UText::new(suggestion);
  110. let len = s.as_str().len().saturating_sub(1);
  111. s.set_cursor(len);
  112. return true;
  113. }
  114. }
  115. }
  116. match *event {
  117. UIEvent::InsertInput(Key::Up) => {
  118. if let Text(_, Some((_, auto_complete))) = self {
  119. auto_complete.dec_cursor();
  120. } else {
  121. return false;
  122. }
  123. }
  124. UIEvent::InsertInput(Key::Down) => {
  125. if let Text(_, Some((_, auto_complete))) = self {
  126. auto_complete.inc_cursor();
  127. } else {
  128. return false;
  129. }
  130. }
  131. UIEvent::InsertInput(Key::Right) => match self {
  132. Text(ref mut s, _) => {
  133. s.cursor_inc();
  134. }
  135. Choice(ref vec, ref mut cursor) => {
  136. *cursor = if *cursor == vec.len().saturating_sub(1) {
  137. 0
  138. } else {
  139. *cursor + 1
  140. };
  141. }
  142. },
  143. UIEvent::InsertInput(Key::Left) => match self {
  144. Text(ref mut s, _) => {
  145. s.cursor_dec();
  146. }
  147. Choice(_, ref mut cursor) => {
  148. if *cursor == 0 {
  149. return false;
  150. } else {
  151. *cursor -= 1;
  152. }
  153. }
  154. },
  155. UIEvent::InsertInput(Key::Char(k)) => {
  156. if let Text(ref mut s, _) = self {
  157. s.insert_char(k);
  158. }
  159. }
  160. UIEvent::InsertInput(Key::Paste(ref p)) => {
  161. if let Text(ref mut s, _) = self {
  162. for c in p.chars() {
  163. s.insert_char(c);
  164. }
  165. }
  166. }
  167. UIEvent::InsertInput(Key::Backspace) | UIEvent::InsertInput(Key::Ctrl('h')) => {
  168. if let Text(ref mut s, auto_complete) = self {
  169. s.backspace();
  170. if let Some(ac) = auto_complete.as_mut() {
  171. ac.1.set_suggestions(Vec::new());
  172. }
  173. }
  174. }
  175. _ => {
  176. return false;
  177. }
  178. }
  179. self.set_dirty();
  180. true
  181. }
  182. fn is_dirty(&self) -> bool {
  183. true
  184. }
  185. fn set_dirty(&mut self) {}
  186. fn id(&self) -> ComponentId {
  187. ComponentId::nil()
  188. }
  189. fn set_id(&mut self, _id: ComponentId) {}
  190. }
  191. impl fmt::Display for Field {
  192. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  193. Display::fmt("field", f)
  194. }
  195. }
  196. #[derive(Debug, Default)]
  197. pub struct FormWidget {
  198. fields: FnvHashMap<String, Field>,
  199. layout: Vec<String>,
  200. buttons: ButtonWidget<bool>,
  201. field_name_max_length: usize,
  202. cursor: usize,
  203. focus: FormFocus,
  204. hide_buttons: bool,
  205. dirty: bool,
  206. id: ComponentId,
  207. }
  208. impl fmt::Display for FormWidget {
  209. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  210. Display::fmt("", f)
  211. }
  212. }
  213. impl FormWidget {
  214. pub fn new(action: String) -> FormWidget {
  215. FormWidget {
  216. buttons: ButtonWidget::new((action, true)),
  217. focus: FormFocus::Fields,
  218. hide_buttons: false,
  219. id: ComponentId::new_v4(),
  220. ..Default::default()
  221. }
  222. }
  223. pub fn cursor(&self) -> usize {
  224. self.cursor
  225. }
  226. pub fn set_cursor(&mut self, new_cursor: usize) {
  227. self.cursor = new_cursor;
  228. }
  229. pub fn hide_buttons(&mut self) {
  230. self.hide_buttons = true;
  231. }
  232. pub fn len(&self) -> usize {
  233. self.layout.len()
  234. }
  235. pub fn add_button(&mut self, val: (String, bool)) {
  236. self.buttons.push(val);
  237. }
  238. pub fn push_choices(&mut self, value: (String, Vec<String>)) {
  239. self.field_name_max_length = std::cmp::max(self.field_name_max_length, value.0.len());
  240. self.layout.push(value.0.clone());
  241. self.fields.insert(value.0, Choice(value.1, 0));
  242. }
  243. pub fn push_cl(&mut self, value: (String, String, AutoCompleteFn)) {
  244. self.field_name_max_length = std::cmp::max(self.field_name_max_length, value.0.len());
  245. self.layout.push(value.0.clone());
  246. self.fields.insert(
  247. value.0,
  248. Text(
  249. UText::new(value.1),
  250. Some((value.2, AutoComplete::new(Vec::new()))),
  251. ),
  252. );
  253. }
  254. pub fn push(&mut self, value: (String, String)) {
  255. self.field_name_max_length = std::cmp::max(self.field_name_max_length, value.0.len());
  256. self.layout.push(value.0.clone());
  257. self.fields.insert(value.0, Text(UText::new(value.1), None));
  258. }
  259. pub fn insert(&mut self, index: usize, value: (String, Field)) {
  260. self.layout.insert(index, value.0.clone());
  261. self.fields.insert(value.0, value.1);
  262. }
  263. pub fn values_mut(&mut self) -> &mut FnvHashMap<String, Field> {
  264. &mut self.fields
  265. }
  266. pub fn collect(self) -> Option<FnvHashMap<String, Field>> {
  267. if let Some(true) = self.buttons_result() {
  268. Some(self.fields)
  269. } else {
  270. None
  271. }
  272. }
  273. pub fn buttons_result(&self) -> Option<bool> {
  274. self.buttons.result
  275. }
  276. }
  277. impl Component for FormWidget {
  278. fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  279. let upper_left = upper_left!(area);
  280. let bottom_right = bottom_right!(area);
  281. for (i, k) in self.layout.iter().enumerate() {
  282. let v = self.fields.get_mut(k).unwrap();
  283. /* Write field label */
  284. write_string_to_grid(
  285. k.as_str(),
  286. grid,
  287. Color::Default,
  288. Color::Default,
  289. (
  290. pos_inc(upper_left, (1, i)),
  291. set_y(bottom_right, i + get_y(upper_left)),
  292. ),
  293. false,
  294. );
  295. /* draw field */
  296. v.draw(
  297. grid,
  298. (
  299. pos_inc(upper_left, (self.field_name_max_length + 3, i)),
  300. set_y(bottom_right, i + get_y(upper_left)),
  301. ),
  302. context,
  303. );
  304. /* Highlight if necessary */
  305. if i == self.cursor {
  306. if self.focus == FormFocus::Fields {
  307. change_colors(
  308. grid,
  309. (
  310. pos_inc(upper_left, (0, i)),
  311. set_y(bottom_right, i + get_y(upper_left)),
  312. ),
  313. Color::Default,
  314. Color::Byte(246),
  315. );
  316. }
  317. if self.focus == FormFocus::TextInput {
  318. v.draw_cursor(
  319. grid,
  320. (
  321. pos_inc(upper_left, (self.field_name_max_length + 3, i)),
  322. (
  323. get_x(upper_left) + self.field_name_max_length + 3,
  324. i + get_y(upper_left),
  325. ),
  326. ),
  327. (
  328. pos_inc(upper_left, (self.field_name_max_length + 3, i + 1)),
  329. bottom_right,
  330. ),
  331. context,
  332. );
  333. }
  334. }
  335. }
  336. if !self.hide_buttons {
  337. let length = self.layout.len();
  338. self.buttons.draw(
  339. grid,
  340. (
  341. pos_inc(upper_left, (1, length * 2 + 3)),
  342. set_y(bottom_right, length * 2 + 3 + get_y(upper_left)),
  343. ),
  344. context,
  345. );
  346. }
  347. self.dirty = false;
  348. context.dirty_areas.push_back(area);
  349. }
  350. fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
  351. if self.focus == FormFocus::Buttons && self.buttons.process_event(event, context) {
  352. return true;
  353. }
  354. match *event {
  355. UIEvent::Input(Key::Up) if self.focus == FormFocus::Buttons => {
  356. self.focus = FormFocus::Fields;
  357. }
  358. UIEvent::InsertInput(Key::Up) if self.focus == FormFocus::TextInput => {
  359. let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
  360. field.process_event(event, context);
  361. }
  362. UIEvent::Input(Key::Up) => {
  363. self.cursor = self.cursor.saturating_sub(1);
  364. }
  365. UIEvent::InsertInput(Key::Down) if self.focus == FormFocus::TextInput => {
  366. let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
  367. field.process_event(event, context);
  368. }
  369. UIEvent::Input(Key::Down) if self.cursor < self.layout.len().saturating_sub(1) => {
  370. self.cursor += 1;
  371. }
  372. UIEvent::Input(Key::Down) if self.focus == FormFocus::Fields => {
  373. self.focus = FormFocus::Buttons;
  374. if self.hide_buttons {
  375. self.set_dirty();
  376. return false;
  377. }
  378. }
  379. UIEvent::InsertInput(Key::Char('\t')) if self.focus == FormFocus::TextInput => {
  380. let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
  381. field.process_event(event, context);
  382. }
  383. UIEvent::Input(Key::Char('\n')) if self.focus == FormFocus::Fields => {
  384. self.focus = FormFocus::TextInput;
  385. context
  386. .replies
  387. .push_back(UIEvent::ChangeMode(UIMode::Insert));
  388. }
  389. UIEvent::InsertInput(Key::Right) if self.focus == FormFocus::TextInput => {
  390. let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
  391. field.process_event(event, context);
  392. }
  393. UIEvent::InsertInput(Key::Left) if self.focus == FormFocus::TextInput => {
  394. let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
  395. if !field.process_event(event, context) {
  396. self.focus = FormFocus::Fields;
  397. context
  398. .replies
  399. .push_back(UIEvent::ChangeMode(UIMode::Normal));
  400. }
  401. }
  402. UIEvent::ChangeMode(UIMode::Normal) if self.focus == FormFocus::TextInput => {
  403. self.focus = FormFocus::Fields;
  404. }
  405. UIEvent::InsertInput(Key::Backspace) if self.focus == FormFocus::TextInput => {
  406. let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
  407. field.process_event(event, context);
  408. }
  409. UIEvent::InsertInput(_) if self.focus == FormFocus::TextInput => {
  410. let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
  411. field.process_event(event, context);
  412. }
  413. _ => {
  414. return false;
  415. }
  416. }
  417. self.set_dirty();
  418. true
  419. }
  420. fn is_dirty(&self) -> bool {
  421. self.dirty
  422. }
  423. fn set_dirty(&mut self) {
  424. self.dirty = true;
  425. }
  426. fn id(&self) -> ComponentId {
  427. self.id
  428. }
  429. fn set_id(&mut self, id: ComponentId) {
  430. self.id = id;
  431. }
  432. }
  433. #[derive(Debug, Default)]
  434. pub struct ButtonWidget<T>
  435. where
  436. T: std::fmt::Debug + Default + Send,
  437. {
  438. buttons: FnvHashMap<String, T>,
  439. layout: Vec<String>,
  440. result: Option<T>,
  441. cursor: usize,
  442. id: ComponentId,
  443. }
  444. impl<T> fmt::Display for ButtonWidget<T>
  445. where
  446. T: std::fmt::Debug + Default + Send,
  447. {
  448. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  449. Display::fmt("", f)
  450. }
  451. }
  452. impl<T> ButtonWidget<T>
  453. where
  454. T: std::fmt::Debug + Default + Send,
  455. {
  456. pub fn new(init_val: (String, T)) -> ButtonWidget<T> {
  457. ButtonWidget {
  458. layout: vec![init_val.0.clone()],
  459. buttons: vec![init_val].into_iter().collect(),
  460. result: None,
  461. cursor: 0,
  462. id: ComponentId::new_v4(),
  463. }
  464. }
  465. pub fn push(&mut self, value: (String, T)) {
  466. self.layout.push(value.0.clone());
  467. self.buttons.insert(value.0, value.1);
  468. }
  469. pub fn is_resolved(&self) -> bool {
  470. self.result.is_some()
  471. }
  472. }
  473. impl<T> Component for ButtonWidget<T>
  474. where
  475. T: std::fmt::Debug + Default + Send,
  476. {
  477. fn draw(&mut self, grid: &mut CellBuffer, area: Area, _context: &mut Context) {
  478. let upper_left = upper_left!(area);
  479. let mut len = 0;
  480. for (i, k) in self.layout.iter().enumerate() {
  481. let cur_len = k.len();
  482. write_string_to_grid(
  483. k.as_str(),
  484. grid,
  485. Color::Default,
  486. if i == self.cursor {
  487. Color::Byte(246)
  488. } else {
  489. Color::Default
  490. },
  491. (
  492. pos_inc(upper_left, (len, 0)),
  493. pos_inc(upper_left, (cur_len + len, 0)),
  494. ),
  495. false,
  496. );
  497. len += cur_len + 3;
  498. }
  499. }
  500. fn process_event(&mut self, event: &mut UIEvent, _context: &mut Context) -> bool {
  501. match *event {
  502. UIEvent::Input(Key::Char('\n')) => {
  503. self.result = Some(
  504. self.buttons
  505. .remove(&self.layout[self.cursor])
  506. .unwrap_or_default(),
  507. );
  508. return true;
  509. }
  510. UIEvent::Input(Key::Left) => {
  511. self.cursor = self.cursor.saturating_sub(1);
  512. return true;
  513. }
  514. UIEvent::Input(Key::Right) if self.cursor < self.layout.len().saturating_sub(1) => {
  515. self.cursor += 1;
  516. return true;
  517. }
  518. _ => {}
  519. }
  520. false
  521. }
  522. fn is_dirty(&self) -> bool {
  523. true
  524. }
  525. fn set_dirty(&mut self) {}
  526. fn id(&self) -> ComponentId {
  527. self.id
  528. }
  529. fn set_id(&mut self, id: ComponentId) {
  530. self.id = id;
  531. }
  532. }
  533. #[derive(Debug, PartialEq, Clone)]
  534. pub struct AutoComplete {
  535. entries: Vec<String>,
  536. content: CellBuffer,
  537. cursor: usize,
  538. dirty: bool,
  539. id: ComponentId,
  540. }
  541. impl fmt::Display for AutoComplete {
  542. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  543. Display::fmt("AutoComplete", f)
  544. }
  545. }
  546. impl Component for AutoComplete {
  547. fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  548. if self.entries.is_empty() {
  549. return;
  550. };
  551. let upper_left = upper_left!(area);
  552. self.dirty = false;
  553. let (width, height) = self.content.size();
  554. copy_area(
  555. grid,
  556. &self.content,
  557. area,
  558. ((0, 0), (width.saturating_sub(1), height.saturating_sub(1))),
  559. );
  560. /* Highlight cursor */
  561. change_colors(
  562. grid,
  563. (
  564. pos_inc(upper_left, (0, self.cursor)),
  565. pos_inc(upper_left, (width.saturating_sub(1), self.cursor)),
  566. ),
  567. Color::Default,
  568. Color::Byte(246),
  569. );
  570. context.dirty_areas.push_back(area);
  571. }
  572. fn process_event(&mut self, _event: &mut UIEvent, _context: &mut Context) -> bool {
  573. false
  574. }
  575. fn is_dirty(&self) -> bool {
  576. self.dirty
  577. }
  578. fn set_dirty(&mut self) {
  579. self.dirty = true;
  580. }
  581. fn id(&self) -> ComponentId {
  582. self.id
  583. }
  584. fn set_id(&mut self, id: ComponentId) {
  585. self.id = id;
  586. }
  587. }
  588. impl AutoComplete {
  589. pub fn new(entries: Vec<String>) -> Self {
  590. let mut ret = AutoComplete {
  591. entries: Vec::new(),
  592. content: CellBuffer::default(),
  593. cursor: 0,
  594. dirty: true,
  595. id: ComponentId::new_v4(),
  596. };
  597. ret.set_suggestions(entries);
  598. ret
  599. }
  600. pub fn set_suggestions(&mut self, entries: Vec<String>) -> bool {
  601. if entries.len() == self.entries.len() && entries == self.entries {
  602. return false;;
  603. }
  604. let mut content = CellBuffer::new(
  605. entries.iter().map(String::len).max().unwrap_or(0) + 1,
  606. entries.len(),
  607. Cell::with_style(Color::Byte(23), Color::Byte(7), Attr::Default),
  608. );
  609. let width = content.cols();
  610. for (i, e) in entries.iter().enumerate() {
  611. write_string_to_grid(
  612. e,
  613. &mut content,
  614. Color::Byte(23),
  615. Color::Byte(7),
  616. ((0, i), (width - 1, i)),
  617. false,
  618. );
  619. write_string_to_grid(
  620. "▒",
  621. &mut content,
  622. Color::Byte(23),
  623. Color::Byte(7),
  624. ((width - 1, i), (width - 1, i)),
  625. false,
  626. );
  627. }
  628. self.content = content;
  629. self.entries = entries;
  630. self.cursor = 0;
  631. true
  632. }
  633. pub fn inc_cursor(&mut self) {
  634. if self.cursor < self.entries.len().saturating_sub(1) {
  635. self.cursor += 1;
  636. self.set_dirty();
  637. }
  638. }
  639. pub fn dec_cursor(&mut self) {
  640. self.cursor = self.cursor.saturating_sub(1);
  641. self.set_dirty();
  642. }
  643. pub fn cursor(&self) -> usize {
  644. self.cursor
  645. }
  646. pub fn set_cursor(&mut self, val: usize) {
  647. debug_assert!(val < self.entries.len());
  648. self.cursor = val;
  649. }
  650. pub fn get_suggestion(&mut self) -> Option<String> {
  651. if self.entries.is_empty() {
  652. return None;
  653. }
  654. let ret = self.entries.remove(self.cursor);
  655. self.entries.clear();
  656. self.cursor = 0;
  657. self.content.empty();
  658. Some(ret)
  659. }
  660. pub fn suggestions(&self) -> &Vec<String> {
  661. &self.entries
  662. }
  663. }
  664. #[derive(Default)]
  665. pub struct ScrollBar {
  666. show_arrows: bool,
  667. block_character: Option<char>,
  668. }
  669. impl ScrollBar {
  670. pub fn set_show_arrows(&mut self, flag: bool) {
  671. self.show_arrows = flag;
  672. }
  673. pub fn set_block_character(&mut self, val: Option<char>) {
  674. self.block_character = val;
  675. }
  676. pub fn draw(
  677. self,
  678. grid: &mut CellBuffer,
  679. area: Area,
  680. pos: usize,
  681. visible_rows: usize,
  682. length: usize,
  683. ) {
  684. if length == 0 {
  685. return;
  686. }
  687. let mut height = height!(area);
  688. if height < 3 {
  689. return;
  690. }
  691. if self.show_arrows {
  692. height -= height;
  693. }
  694. clear_area(grid, area);
  695. let visible_ratio: f32 = (std::cmp::min(visible_rows, length) as f32) / (length as f32);
  696. let scrollbar_height = std::cmp::max((visible_ratio * (height as f32)) as usize, 1);
  697. let scrollbar_offset = {
  698. let temp = (((pos as f32) / (length as f32)) * (height as f32)) as usize;
  699. if temp + scrollbar_height >= height {
  700. height - scrollbar_height
  701. } else {
  702. temp
  703. }
  704. };
  705. let (mut upper_left, bottom_right) = area;
  706. if self.show_arrows {
  707. grid[upper_left].set_ch('▴');
  708. upper_left = (upper_left.0, upper_left.1 + 1);
  709. }
  710. for y in get_y(upper_left)..(get_y(upper_left) + scrollbar_offset) {
  711. grid[set_y(upper_left, y)].set_ch(' ');
  712. }
  713. for y in (get_y(upper_left) + scrollbar_offset)
  714. ..=(get_y(upper_left) + scrollbar_offset + scrollbar_height)
  715. {
  716. grid[set_y(upper_left, y)].set_ch(self.block_character.unwrap_or('█'));
  717. }
  718. for y in (get_y(upper_left) + scrollbar_offset + scrollbar_height + 1)..get_y(bottom_right)
  719. {
  720. grid[set_y(upper_left, y)].set_ch(' ');
  721. }
  722. if self.show_arrows {
  723. grid[set_x(bottom_right, get_x(upper_left))].set_ch('▾');
  724. }
  725. }
  726. }