gooey/presentation/
curses.rs

1//! Curses (terminal) backend
2
3use std;
4use std::sync::{LazyLock, Mutex};
5use {log, easycurses, pancurses, nsys};
6
7use crate::{Application, Interface, Tree};
8use crate::math::Vector2;
9use crate::geometry::integer::Aabb2;
10use crate::geometry::intersect::integer as intersect;
11use crate::interface::{view, View};
12use crate::widget;
13use crate::tree::NodeId;
14use super::{Graphics, Presentation};
15
16pub static INIT_COLORS : LazyLock <Mutex <Option <[[u8; 3]; 8]>>> =
17  LazyLock::new (|| Mutex::new (None));
18
19/// A pancurses-based presentation implementation.
20#[derive(Debug)]
21pub struct Curses {
22  inner           : nsys::curses::Curses,
23  /// Pancuses doesn't generate release button events, so we simulate them by generating
24  /// a release event on the next frame for each press. Note that this includes key
25  /// repeat events.
26  release_buttons : Vec <view::input::Button>
27}
28
29pub fn border_acs_lines() -> view::Border {
30  #[expect(trivial_numeric_casts)]
31  view::Border {
32    top:              pancurses::ACS_HLINE()    as u32,
33    bottom:           pancurses::ACS_HLINE()    as u32,
34    left:             pancurses::ACS_VLINE()    as u32,
35    right:            pancurses::ACS_VLINE()    as u32,
36    top_left:         pancurses::ACS_ULCORNER() as u32,
37    top_right:        pancurses::ACS_URCORNER() as u32,
38    bottom_left:      pancurses::ACS_LLCORNER() as u32,
39    bottom_right:     pancurses::ACS_LRCORNER() as u32,
40    thickness_top:    1,
41    thickness_bottom: 1,
42    thickness_left:   1,
43    thickness_right:  1
44  }
45}
46
47impl Curses {
48  #[inline]
49  pub const fn inner (&self) -> &nsys::curses::Curses {
50    &self.inner
51  }
52  #[inline]
53  pub const fn inner_mut (&mut self) -> &mut nsys::curses::Curses {
54    &mut self.inner
55  }
56  #[inline]
57  pub fn dimensions (&self) -> view::dimensions::Tile {
58    let (rows, columns) = self.inner.dimensions_rc();
59    debug_assert!(rows > 0);
60    debug_assert!(columns > 0);
61    Vector2::new (rows as u32, columns as u32).into()
62  }
63}
64
65impl Default for Curses {
66  fn default() -> Self {
67    let mut inner =  {
68      let init_colors = INIT_COLORS.lock().unwrap();
69      nsys::curses::Curses::new (init_colors.as_ref())
70    };
71    // push a resize event so the interface screen element can be initialized
72    inner.un_get_input (pancurses::Input::KeyResize);
73    Curses { inner, release_buttons: Vec::new() }
74  }
75}
76
77impl Graphics     for Curses { }
78impl Presentation for Curses {
79  fn with_root (_root : View, _id : NodeId) -> Self {
80    Self::default()
81  }
82
83  /// Create a new interface with a root screen frame element
84  fn make_interface <A : Application> () -> Interface <A, Self> {
85    use widget::BuildElement;
86    // create a root screen frame and use the resize control function to update the
87    // dimensions
88    let screen = widget::frame::screen::TileBuilder::<A>::new().build_element();
89    let mut interface = Interface::<A, Self>::with_root (screen);
90    let dimensions = interface.presentation.dimensions();
91    let mut actions = vec![];
92    widget::frame::screen::resize (
93      &view::input::System::Resized {
94        width:  dimensions.columns(),
95        height: dimensions.rows()
96      },
97      interface.elements(),
98      interface.root_id(),
99      &mut actions
100    );
101    let _ = interface.actions (actions);
102    interface
103  }
104
105  fn get_input (&mut self, input_buffer : &mut Vec <view::Input>) {
106    use view::{input, Input};
107    log::trace!("get input...");
108    input_buffer.extend (self.release_buttons.drain (..)
109      .map (|button|{
110        let input = Input::Button (button, input::button::State::Released);
111        log::debug!("input: {input:?}");
112        input
113      }));
114    if let Some (pancurses_input) = self.inner.getch_nowait() {
115      log::trace!("pancurses input: {pancurses_input:?}");
116      let input = match pancurses_input.into() {
117        // pancurses::Input::KeyResize is converted to input::System::Resize{0,0}
118        Input::System (input::System::Resized { width: 0, height: 0 }) => {
119          // get the current dimensions
120          let (width, height) = {
121            let (rows, columns) = self.inner.dimensions_rc();
122            debug_assert!(rows    >= 0);
123            debug_assert!(columns >= 0);
124            // NOTE: the curses display represents dimensions as (rows, columns) pairs,
125            // but the interface represents dimensions in (width, height), so these
126            // values are transposed when generating the resize event
127            (columns as u32, rows as u32)
128          };
129          input::System::Resized { width, height }.into()
130        }
131        Input::Button (button, pressed) => {
132          debug_assert_eq!(pressed, input::button::State::Pressed);
133          // release next frame
134          self.release_buttons.push (button);
135          Input::Button (button, pressed)
136        }
137        Input::Text (input::Text::Char (ch)) => {
138          // generate a button event for char input
139          let button = {
140            let (keycode, modifiers) = char_to_keycode (ch);
141            input::Button { variant: keycode.into(), modifiers }
142          };
143          let input = Input::Text (input::Text::Char (ch));
144          log::debug!("input: {input:?}");
145          input_buffer.push (input);
146          // release next frame
147          self.release_buttons.push (button);
148          (button, input::button::State::Pressed).into()
149        }
150        input => input
151      };
152      log::debug!("input: {input:?}");
153      input_buffer.push (input);
154    }
155    log::trace!("...get input");
156  }
157
158  fn display_view <V : AsRef <View>> (&mut self,
159    view_tree      : &Tree <V>,
160    _display_values : std::vec::Drain <(NodeId, view::Display)>
161  ) {
162    use std::collections::VecDeque;
163    use pancurses::chtype;
164    use nsys::curses::{color_pair_attr, pancurses_ok, pancurses_warn_ok};
165    use view::component::{canvas, Body, Canvas, Image, Kind};
166    log::trace!("display view...");
167    // trace info
168    let (attrs, colors) = self.inner.win().attrget();
169    let bkgd = self.inner.win().getbkgd();
170    log::trace!("  window: attributes: {attrs:064b}");
171    log::trace!("  window: color pair: {colors}");
172    log::trace!("  window: background: {bkgd:064b}");
173    // screen is the root canvas element
174    let screen_id     = view_tree.root_node_id().unwrap();
175    let screen        = view_tree.get (screen_id).unwrap().data().as_ref();
176    let screen_canvas = Canvas::try_ref (&screen.component).unwrap();
177    // set background and clear display if clear color is set
178    // TODO: this logic may need to rethinking
179    let (clear, bg_color) = {
180      let color = match screen_canvas.clear_color {
181        canvas::ClearColor::Appearance =>
182          screen.appearance.style.as_ref().map(|style| style.bg),
183        canvas::ClearColor::Fixed (clear_color) => clear_color
184      };
185      ( color.is_some(),
186        // 0x00 == no color bits: color pair id 0 should be white/black
187        color.map_or (0x00, |color| {
188          let bg = convert_color (color).unwrap();
189          color_pair_attr (easycurses::Color::White, bg)
190        })
191      )
192    };
193    self.inner.win().bkgdset (' ' as chtype | bg_color);
194    if clear {
195      pancurses_ok!(self.inner.win().erase());
196    }
197    // initialize style
198    let style = screen.appearance.style.clone().unwrap_or_default();
199    // draw screen border if present
200    #[expect(trivial_numeric_casts)]
201    if let Some (border) = screen_canvas.border.as_ref() {
202      let rc_min = (0,0);
203      let rc_max = (
204        screen_canvas.coordinates.dimensions().vec().numcast().unwrap()
205          - Vector2::new (1,1)
206      ).into_tuple();
207      let color = color_pair_attr (
208        convert_color (style.fg).unwrap(),
209        convert_color (style.bg).unwrap());
210      pancurses_warn_ok!(self.inner.draw_border (
211        border.left         as chtype | color,
212        border.right        as chtype | color,
213        border.top          as chtype | color,
214        border.bottom       as chtype | color,
215        border.top_left     as chtype | color,
216        border.top_right    as chtype | color,
217        border.bottom_left  as chtype | color,
218        border.bottom_right as chtype | color,
219        rc_min, rc_max,
220        border.thickness_top    as u32,
221        border.thickness_bottom as u32,
222        border.thickness_left   as u32,
223        border.thickness_right  as u32
224      ));
225    }
226    // clip first-level child canvases and draw
227    let screen_aabb  = screen_canvas.coordinates.into();
228    let mut canvases = VecDeque::new();
229    for child_id in view_tree.children_ids (screen_id).unwrap() {
230      let child = view_tree.get (child_id).unwrap().data().as_ref();
231      let style = child.appearance.style.as_ref().unwrap_or (&style);
232      if let Some (child_canvas) = Canvas::try_ref (&child.component) &&
233        let Some ((canvas, body_aabb)) =
234          canvas_and_body_aabb (child_canvas, &screen_aabb)
235      {
236        canvases.push_back ((child_id, canvas, style, body_aabb));
237      }
238    }
239    while canvases.len() > 0 {
240      let (node_id, canvas, style, body_aabb) = canvases.pop_front().unwrap();
241      let node = view_tree.get (node_id).unwrap();
242      let mut text  = None;
243      let mut image = None;
244      for child_id in node.children().iter().rev() {
245        let child = view_tree.get (child_id).unwrap().data().as_ref();
246        let style = child.appearance.style.as_ref().unwrap_or (style);
247        if let Some (child_canvas) = Canvas::try_ref (&child.component) &&
248          let Some ((canvas, body_aabb)) =
249            canvas_and_body_aabb (child_canvas, &screen_aabb)
250        {
251          canvases.push_front ((child_id, canvas, style, body_aabb));
252        } else if let Some (child_text) = Body::try_ref (&child.component) {
253          debug_assert!(text.is_none());
254          // NOTE: this slicing is unsafe if the string contains multi-byte
255          // characters
256          let skip  = body_aabb.min().0.x as usize;
257          let take  = body_aabb.max().0.x as usize - skip;
258          let start = body_aabb.min().0.y as usize;
259          let end   = body_aabb.max().0.y as usize;
260          text = Some ((
261            child_text.0.lines().skip (skip).take (take).map (
262              move |line| &line[start.min (line.len())..end.min (line.len())]),
263            child.appearance.style.clone()
264          ));
265        } else if let Some (child_image) = Image::try_ref (&child.component) {
266          debug_assert!(image.is_none());
267          match child_image {
268            Image::Raw64 (pixmap) => {
269              let skip  = body_aabb.min().0.x as usize;
270              let take  = body_aabb.max().0.x as usize - skip;
271              let start = body_aabb.min().0.y as usize;
272              let end   = body_aabb.max().0.y as usize;
273              image = Some (pixmap.rows().skip (skip).take (take).map (
274                move |row| &row[start.min (row.len())..end.min (row.len())]
275              ));
276            }
277            Image::Raw8(_) | Image::Raw16(_) | Image::Raw32(_) |
278            Image::Texture(_) => {
279              // TODO: image resource
280              image = None;
281            }
282          }
283        }
284      }
285      self.draw_canvas (&canvas, style, text, image, true);
286    }
287    // TODO: win().refresh() ?
288    //self.inner.draw_border_default();  // TODO: debug
289    log::trace!("...display view");
290
291    fn canvas_and_body_aabb (child_canvas : &Canvas, screen_aabb : &Aabb2 <i32>)
292      -> Option <(Canvas, Aabb2 <i32>)>
293    {
294      let child_aabb = child_canvas.coordinates.into();
295      intersect::continuous_aabb2_aabb2 (screen_aabb, &child_aabb).map (
296        |intersection| {
297          let coordinates = view::Coordinates::tile_from_aabb (intersection);
298          let mut canvas  = Canvas {
299            coordinates, .. child_canvas.clone()
300          };
301          let (body_width, body_height) = child_canvas.body_wh();
302          let (mut body_min_row, mut body_min_col) = (0, 0);
303          let (mut body_max_row, mut body_max_col) =
304            (body_height as i32, body_width as i32);
305          if let Some (border) = canvas.border.as_mut() {
306            let outside_top = screen_aabb.min().0.x - child_aabb.min().0.x;
307            if outside_top > 0 {
308              body_min_row = (outside_top as u32)
309                .saturating_sub (border.thickness_top as u32) as i32;
310              border.thickness_top = border.thickness_top
311                .saturating_sub (outside_top    as u16);
312            }
313            let outside_bottom = child_aabb.max().0.x - screen_aabb.max().0.x;
314            if outside_bottom > 0 {
315              body_max_row -= (outside_bottom as u32)
316                .saturating_sub (border.thickness_bottom as u32) as i32;
317              border.thickness_bottom = border.thickness_bottom
318                .saturating_sub (outside_bottom as u16);
319            }
320            let outside_left = screen_aabb.min().0.y - child_aabb.min().0.y;
321            if outside_left > 0 {
322              body_min_col = (outside_left as u32)
323                .saturating_sub (border.thickness_left as u32) as i32;
324              border.thickness_left   = border.thickness_left
325                .saturating_sub (outside_left   as u16);
326            }
327            let outside_right = child_aabb.max().0.y - screen_aabb.max().0.y;
328            if outside_right > 0 {
329              body_max_col -= (outside_right as u32)
330                .saturating_sub (border.thickness_right as u32) as i32;
331              border.thickness_right  = border.thickness_right
332                .saturating_sub (outside_right  as u16);
333            }
334          }
335          ( canvas,
336            Aabb2::with_minmax (
337              [body_min_row, body_min_col].into(),
338              [body_max_row, body_max_col].into()
339            )
340          )
341        }
342      )
343    }
344  }
345}
346
347impl Curses {
348  // TODO: debug flag
349  fn draw_canvas <'a> (&mut self,
350    canvas : &view::component::Canvas,
351    style  : &view::Style,
352    text   : Option <(impl Iterator <Item=&'a str>, Option <view::Style>)>,
353    image  : Option <impl Iterator <Item=&'a [u64]>>,
354    _debug : bool
355  ) {
356    use std::convert::TryInto;
357    use nsys::curses::{chtype_color_pair, color_pair_attr, pancurses_ok,
358      pancurses_warn_ok};
359    use pancurses::chtype;
360    use crate::interface::view::component::canvas;
361    log::trace!("draw_canvas...");
362    let curses  = &mut self.inner;
363    let (position, dimensions)
364      : (view::position::Tile, view::dimensions::Tile)
365      = canvas.coordinates.try_into().unwrap();
366    let (rc_min, rc_max) = {
367      let (row,  col)  = (position.row(), position.column());
368      let (rows, cols) = (dimensions.rows() as i32, dimensions.columns() as i32);
369      ( (row, col),
370        (row + rows-1, col + cols-1) )
371    };
372    // clear
373    let clear_color = match canvas.clear_color {
374      canvas::ClearColor::Appearance    => Some (style.bg),
375      canvas::ClearColor::Fixed (color) => color
376    };
377    clear_color.map (|color|{
378      let border       = ' ' as chtype;
379      let bgcolor      = convert_color (color).unwrap();
380      let fill_color   = color_pair_attr (easycurses::Color::White, bgcolor);
381      let fill         = ' ' as chtype | fill_color;
382      curses.draw_rect (border, fill, rc_min, rc_max, 0,0);  // 0,0: no border
383    });
384    let color = color_pair_attr (
385      convert_color (style.fg).unwrap(),
386      convert_color (style.bg).unwrap());
387    let color_pair = chtype_color_pair (color);
388    pancurses_ok!(curses.win().color_set (color_pair));
389    curses.win().bkgdset (b' ' as chtype | color);
390    // border
391    #[expect(trivial_numeric_casts)]
392    let (border_thickness_top, border_thickness_left) =
393      canvas.border.as_ref().map (|border|{
394        pancurses_warn_ok!(curses.draw_border (
395          border.left         as chtype | color,
396          border.right        as chtype | color,
397          border.top          as chtype | color,
398          border.bottom       as chtype | color,
399          border.top_left     as chtype | color,
400          border.top_right    as chtype | color,
401          border.bottom_left  as chtype | color,
402          border.bottom_right as chtype | color,
403          rc_min, rc_max,
404          border.thickness_top    as u32,
405          border.thickness_bottom as u32,
406          border.thickness_left   as u32,
407          border.thickness_right  as u32
408        ));
409        ( border.thickness_top  as i32,
410          border.thickness_left as i32 )
411      }).unwrap_or_default();
412    // text
413    text.map (|(text, style)|{
414      style.map (|style|{
415        let color = color_pair_attr (
416          convert_color (style.fg).unwrap(),
417          convert_color (style.bg).unwrap());
418        let color_pair = chtype_color_pair (color);
419        pancurses_ok!(curses.win().color_set (color_pair));
420        curses.win().bkgdset (b' ' as chtype | color);
421      });
422      for (i, line) in text.enumerate() {
423        let printable = line.trim_start_matches ('\0');
424        let skip = line.len() - printable.len();
425        let row = rc_min.0 + border_thickness_top + i as i32;
426        let col = rc_min.1 + border_thickness_left + skip as i32;
427        if line.len() > 0 {
428          let _ = curses.win().mvaddstr (row, col, printable);
429        }
430      }
431    });
432    // image
433    image.map (|image|{
434      for (i, line) in image.enumerate() {
435        let row = rc_min.0 + border_thickness_top + i as i32;
436        let col = rc_min.1 + border_thickness_left;
437        for (j, ch) in line.iter().enumerate() {
438          if *ch != 0x0 {
439            let _ = curses.win().mvaddch (row, col+j as i32, *ch as chtype);
440          }
441        }
442      }
443    });
444    log::trace!("...draw_canvas");
445  }
446}
447
448impl From <pancurses::Input> for view::Input {
449
450  /// Convert from pancurses input to an input button (keycode). Note that
451  /// easycurses exports an alias of `pancurses::Input` as `easycurses::Input`.
452  ///
453  /// Note that some key combinations may not be captured or may be missing
454  /// modifiers.
455  fn from (input : pancurses::Input) -> Self {
456    use pancurses::Input::*;
457    use view::input::{self, button::Keycode, Modifiers};
458    const ALT   : Modifiers = Modifiers::ALT;
459    const CTRL  : Modifiers = Modifiers::CTRL;
460    const SHIFT : Modifiers = Modifiers::SHIFT;
461    const EMPTY : Modifiers = Modifiers::empty();
462    let unhandled = |input| {
463      log::warn!("curses unhandled input: {input:?}");
464      (Keycode::NonConvert, EMPTY)
465    };
466    #[expect(clippy::match_same_arms)]
467    let (keycode, modifiers) = match input {
468      Character (ch) => match ch {
469        'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' |
470        'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' |
471        'y' | 'z' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0' |
472        '-' | '=' | '`' | '[' | ']' | '\\'| ';' | '\''| ',' | '.' | '/' | 'A' |
473        'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' |
474        'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' |
475        'Z' | '!' | '@' | '#' | '$' | '%' | '^' | '&' | '*' | '(' | ')' | '_' |
476        '+' | '~' | '{' | '}' | '|' | ':' | '"' | '<' | '>' | '?' | ' ' | '\n'|
477        '\t'    => return input::Text::Char (ch).into(),
478        special => {
479          let unhandled = |ch| {
480            log::warn!("curses unhandled special character: {ch:?}");
481            (Keycode::NonConvert, EMPTY)
482          };
483          let keyname = pancurses::keyname (special as i32).unwrap_or_else (||{
484            log::error!("pancurses keyname failed for special char {special:?}");
485            unreachable!()
486          });
487          log::trace!("keyname: {keyname:?}");
488          match keyname.as_str() {
489            "^A" => (Keycode::A, CTRL),
490            "^B" => (Keycode::B, CTRL),
491            "^C" => (Keycode::C, CTRL),
492            "^D" => (Keycode::D, CTRL),
493            "^E" => (Keycode::E, CTRL),
494            "^F" => (Keycode::F, CTRL),
495            "^G" => (Keycode::G, CTRL),
496            // NOTE: on windows (pdcurses) backspace is emitted as CTRL+H
497            "^H" => (Keycode::Backspace, EMPTY),
498            "^I" => (Keycode::I, CTRL),
499            "^J" => (Keycode::J, CTRL),
500            "^K" => (Keycode::K, CTRL),
501            "^L" => (Keycode::L, CTRL),
502            "^M" => (Keycode::M, CTRL),
503            "^N" => (Keycode::N, CTRL),
504            "^O" => (Keycode::O, CTRL),
505            "^P" => (Keycode::P, CTRL),
506            "^Q" => (Keycode::Q, CTRL),
507            "^R" => (Keycode::R, CTRL),
508            "^S" => (Keycode::S, CTRL),
509            "^T" => (Keycode::T, CTRL),
510            "^U" => (Keycode::U, CTRL),
511            "^V" => (Keycode::V, CTRL),
512            "^W" => (Keycode::W, CTRL),
513            "^X" => (Keycode::X, CTRL),
514            "^Y" => (Keycode::Y, CTRL),
515            "^Z" => (Keycode::Z, CTRL),
516            "^_" => (Keycode::Minus, SHIFT | CTRL),
517            "^?" => (Keycode::Backspace,   EMPTY),
518            "^[" => (Keycode::Escape, EMPTY), // note does not capture modifiers
519            // note this is generated by other key combinations, e.g. Ctrl+Space
520            "^@" => (Keycode::Key2, CTRL),
521            // PDCurses
522            "KEY_PAUSE"      => (Keycode::Pause,  EMPTY),
523            "KEY_SCROLLLOCK" => (Keycode::ScrollLock, EMPTY),
524            "KEY_SDOWN"      => (Keycode::Down,   SHIFT),
525            "KEY_SUP"        => (Keycode::Up,     SHIFT),
526            "KEY_B2"         => (Keycode::Numpad5, SHIFT),
527            "SHF_PADENTER"   => (Keycode::NumpadEnter, SHIFT),
528            "SHF_PADMINUS"   => (Keycode::NumpadSubtract, SHIFT),
529            "SHF_PADPLUS"    => (Keycode::NumpadAdd,    SHIFT),
530            "SHF_PADSLASH"   => (Keycode::NumpadDivide, SHIFT),
531            "SHF_PADSTAR"    => (Keycode::NumpadMultiply, SHIFT),
532            "KEY_F(25)"      => (Keycode::F1,     CTRL),
533            "KEY_F(26)"      => (Keycode::F2,     CTRL),
534            "KEY_F(27)"      => (Keycode::F3,     CTRL),
535            "KEY_F(28)"      => (Keycode::F4,     CTRL),
536            "KEY_F(29)"      => (Keycode::F5,     CTRL),
537            "KEY_F(30)"      => (Keycode::F6,     CTRL),
538            "KEY_F(31)"      => (Keycode::F7,     CTRL),
539            "KEY_F(32)"      => (Keycode::F8,     CTRL),
540            "KEY_F(33)"      => (Keycode::F9,     CTRL),
541            "KEY_F(34)"      => (Keycode::F10,    CTRL),
542            "KEY_F(35)"      => (Keycode::F11,    CTRL),
543            "KEY_F(36)"      => (Keycode::F12,    CTRL),
544            "KEY_F(37)"      => (Keycode::F1,     ALT),
545            "KEY_F(38)"      => (Keycode::F2,     ALT),
546            "KEY_F(39)"      => (Keycode::F3,     ALT),
547            "KEY_F(40)"      => (Keycode::F4,     ALT),
548            "KEY_F(41)"      => (Keycode::F5,     ALT),
549            "KEY_F(42)"      => (Keycode::F6,     ALT),
550            "KEY_F(43)"      => (Keycode::F7,     ALT),
551            "KEY_F(44)"      => (Keycode::F8,     ALT),
552            "KEY_F(45)"      => (Keycode::F9,     ALT),
553            "KEY_F(46)"      => (Keycode::F10,    ALT),
554            "KEY_F(47)"      => (Keycode::F11,    ALT),
555            "KEY_F(48)"      => (Keycode::F12,    ALT),
556            "CTL_0"          => (Keycode::Key0,   CTRL),
557            "CTL_1"          => (Keycode::Key1,   CTRL),
558            "CTL_2"          => (Keycode::Key2,   CTRL),
559            "CTL_3"          => (Keycode::Key3,   CTRL),
560            "CTL_4"          => (Keycode::Key4,   CTRL),
561            "CTL_5"          => (Keycode::Key5,   CTRL),
562            "CTL_6"          => (Keycode::Key6,   CTRL),
563            "CTL_7"          => (Keycode::Key7,   CTRL),
564            "CTL_8"          => (Keycode::Key8,   CTRL),
565            "CTL_9"          => (Keycode::Key9,   CTRL),
566            "CTL_DOWN"       => (Keycode::Down,   CTRL),
567            "CTL_LEFT"       => (Keycode::Left,   CTRL),
568            "CTL_RIGHT"      => (Keycode::Right,  CTRL),
569            "CTL_UP"         => (Keycode::Up,     CTRL),
570            "CTL_PAD0"       => (Keycode::Numpad0, CTRL),
571            "CTL_PAD1"       => (Keycode::Numpad1, CTRL),
572            "CTL_PAD2"       => (Keycode::Numpad2, CTRL),
573            "CTL_PAD3"       => (Keycode::Numpad3, CTRL),
574            "CTL_PAD4"       => (Keycode::Numpad4, CTRL),
575            "CTL_PAD5"       => (Keycode::Numpad5, CTRL),
576            "CTL_PAD6"       => (Keycode::Numpad6, CTRL),
577            "CTL_PAD7"       => (Keycode::Numpad7, CTRL),
578            "CTL_PAD8"       => (Keycode::Numpad8, CTRL),
579            "CTL_PAD9"       => (Keycode::Numpad9, CTRL),
580            "CTL_PADENTER"   => (Keycode::NumpadEnter, CTRL),
581            "CTL_PADMINUS"   => (Keycode::NumpadSubtract, CTRL),
582            "CTL_PADPLUS"    => (Keycode::NumpadAdd,  CTRL),
583            "CTL_PADSLASH"   => (Keycode::NumpadDivide, CTRL),
584            "CTL_PADSTOP"    => (Keycode::NumpadDecimal, CTRL),
585            "CTL_PADSTAR"    => (Keycode::NumpadMultiply, CTRL),
586            "CTL_BQUOTE"     => (Keycode::Grave,  CTRL),
587            "CTL_DEL"        => (Keycode::Delete, CTRL),
588            "CTL_END"        => (Keycode::End,    CTRL),
589            "CTL_FLASH"      => (Keycode::Slash,  CTRL),
590            "CTL_HOME"       => (Keycode::Home,   CTRL),
591            "CTL_INS"        => (Keycode::Insert, CTRL),
592            "CTL_PAUSE"      => (Keycode::Pause,  CTRL),
593            "CTL_PGDN"       => (Keycode::PageDown, CTRL),
594            "CTL_PGUP"       => (Keycode::PageUp, CTRL),
595            "CTL_SEMICOLON"  => (Keycode::Semicolon, CTRL),
596            "CTL_STOP"       => (Keycode::Period, CTRL),
597            "CTL_TAB"        => (Keycode::Tab,    CTRL),
598            "ALT_0"          => (Keycode::Key0, ALT),
599            "ALT_1"          => (Keycode::Key1, ALT),
600            "ALT_2"          => (Keycode::Key2, ALT),
601            "ALT_3"          => (Keycode::Key3, ALT),
602            "ALT_4"          => (Keycode::Key4, ALT),
603            "ALT_5"          => (Keycode::Key5, ALT),
604            "ALT_6"          => (Keycode::Key6, ALT),
605            "ALT_7"          => (Keycode::Key7, ALT),
606            "ALT_8"          => (Keycode::Key8, ALT),
607            "ALT_9"          => (Keycode::Key9, ALT),
608            "ALT_A"          => (Keycode::A, ALT),
609            "ALT_B"          => (Keycode::B, ALT),
610            "ALT_C"          => (Keycode::C, ALT),
611            "ALT_D"          => (Keycode::D, ALT),
612            "ALT_E"          => (Keycode::E, ALT),
613            "ALT_F"          => (Keycode::F, ALT),
614            "ALT_G"          => (Keycode::G, ALT),
615            "ALT_H"          => (Keycode::H, ALT),
616            "ALT_I"          => (Keycode::I, ALT),
617            "ALT_J"          => (Keycode::J, ALT),
618            "ALT_K"          => (Keycode::K, ALT),
619            "ALT_L"          => (Keycode::L, ALT),
620            "ALT_M"          => (Keycode::M, ALT),
621            "ALT_N"          => (Keycode::N, ALT),
622            "ALT_O"          => (Keycode::O, ALT),
623            "ALT_P"          => (Keycode::P, ALT),
624            "ALT_Q"          => (Keycode::Q, ALT),
625            "ALT_R"          => (Keycode::R, ALT),
626            "ALT_S"          => (Keycode::S, ALT),
627            "ALT_T"          => (Keycode::T, ALT),
628            "ALT_U"          => (Keycode::U, ALT),
629            "ALT_V"          => (Keycode::V, ALT),
630            "ALT_W"          => (Keycode::W, ALT),
631            "ALT_X"          => (Keycode::X, ALT),
632            "ALT_Y"          => (Keycode::Y, ALT),
633            "ALT_Z"          => (Keycode::Z, ALT),
634            "ALT_BKSP"       => (Keycode::Backspace,   ALT),
635            "ALT_BQUOTE"     => (Keycode::Grave,  ALT),
636            "ALT_BSLASH"     => (Keycode::Backslash, ALT),
637            "^\\"            => (Keycode::LBracket, ALT),
638            "ALT_COMMA"      => (Keycode::Comma, ALT),
639            "ALT_DEL"        => (Keycode::Delete, ALT),
640            "ALT_END"        => (Keycode::End,    ALT),
641            "ALT_ENTER"      => (Keycode::Enter, ALT),
642            "ALT_EQUALS"     => (Keycode::Equal, ALT),
643            "ALT_HOME"       => (Keycode::Home,   ALT),
644            "ALT_INS"        => (Keycode::Insert, ALT),
645            "ALT_FQUOTE"     => (Keycode::Quote, ALT),
646            "ALT_FSLASH"     => (Keycode::Slash, ALT),
647            "ALT_MINUS"      => (Keycode::Minus, ALT),
648            "ALT_PGDN"       => (Keycode::PageDown, ALT),
649            "ALT_PGUP"       => (Keycode::PageUp, ALT),
650            "ALT_RBRACKET"   => (Keycode::RBracket, ALT),
651            "ALT_SCROLLLOCK" => (Keycode::ScrollLock, ALT),
652            "ALT_SEMICOLON"  => (Keycode::Semicolon, ALT),
653            "ALT_STOP"       => (Keycode::Period, ALT),
654            "ALT_PADENTER"   => (Keycode::NumpadEnter, ALT),
655            "ALT_PADMINUS"   => (Keycode::NumpadSubtract, ALT),
656            "ALT_PADSLASH"   => (Keycode::NumpadDivide, ALT),
657            "ALT_PADSTAR"    => (Keycode::NumpadMultiply, ALT),
658            "ALT_PADSTOP"    => (Keycode::NumpadDecimal, ALT),
659            ch => unhandled (ch)
660          }
661        }
662      },
663      Unknown(_) => unhandled (input),
664      KeyCodeYes => unhandled (input),
665
666      KeyBreak => (Keycode::Pause, EMPTY),
667      KeyDown => (Keycode::Down, EMPTY),
668      KeyUp => (Keycode::Up, EMPTY),
669      KeyLeft => (Keycode::Left, EMPTY),
670      KeyRight => (Keycode::Right, EMPTY),
671      KeyHome => (Keycode::Home, EMPTY),
672      KeyBackspace => (Keycode::Backspace, EMPTY),
673      KeyF0 => unhandled (input),
674      KeyF1 => (Keycode::F1, EMPTY),
675      KeyF2 => (Keycode::F2, EMPTY),
676      KeyF3 => (Keycode::F3, EMPTY),
677      KeyF4 => (Keycode::F4, EMPTY),
678      KeyF5 => (Keycode::F5, EMPTY),
679      KeyF6 => (Keycode::F6, EMPTY),
680      KeyF7 => (Keycode::F7, EMPTY),
681      KeyF8 => (Keycode::F8, EMPTY),
682      KeyF9 => (Keycode::F9, EMPTY),
683      KeyF10 => (Keycode::F10, EMPTY),
684      KeyF11 => (Keycode::F11, EMPTY),
685      KeyF12 => (Keycode::F12, EMPTY),
686      KeyF13 => (Keycode::F13, EMPTY),
687      KeyF14 => (Keycode::F14, EMPTY),
688      KeyF15 => (Keycode::F15, EMPTY),
689
690      KeyDL => unhandled (input),
691      KeyIL => unhandled (input),
692      KeyDC => (Keycode::Delete, EMPTY),
693      KeyIC => (Keycode::Insert, EMPTY),
694      KeyEIC => unhandled (input),
695      KeyClear => unhandled (input),
696      KeyEOS => unhandled (input),
697      KeyEOL => unhandled (input),
698      KeySF => (Keycode::Down, SHIFT),
699      KeySR => (Keycode::Up, SHIFT),
700      KeyNPage => (Keycode::PageDown, EMPTY),
701      KeyPPage => (Keycode::PageUp, EMPTY),
702      KeySTab => unhandled (input),  // shift+tab generates KeyBTab
703      KeyCTab => unhandled (input),
704      KeyCATab => unhandled (input),
705      KeyEnter => (Keycode::Enter, EMPTY),
706      KeySReset => unhandled (input),
707      KeyReset => unhandled (input),
708      KeyPrint => unhandled (input),
709      KeyLL => unhandled (input),
710      KeyAbort => unhandled (input),
711      KeySHelp => unhandled (input),
712      KeyLHelp => unhandled (input),
713      KeyBTab => (Keycode::Tab, SHIFT),
714      KeyBeg => unhandled (input),
715      KeyCancel => unhandled (input),
716      KeyClose => unhandled (input),
717      KeyCommand => unhandled (input),
718      KeyCopy => (Keycode::Copy, EMPTY),
719      KeyCreate => unhandled (input),
720      KeyEnd => (Keycode::End, EMPTY),
721      KeyExit => unhandled (input),
722      KeyFind => unhandled (input),
723      KeyHelp => unhandled (input),
724      KeyMark => unhandled (input),
725      KeyMessage => unhandled (input),
726      KeyMove => unhandled (input),
727      KeyNext => unhandled (input),
728      KeyOpen => unhandled (input),
729      KeyOptions => unhandled (input),
730      KeyPrevious => unhandled (input),
731      KeyRedo => unhandled (input),
732      KeyReference => unhandled (input),
733      KeyRefresh => unhandled (input),
734      KeyReplace => unhandled (input),
735      KeyRestart => unhandled (input),
736      KeyResume => unhandled (input),
737      KeySave => unhandled (input),
738      KeySBeg => unhandled (input),
739      KeySCancel => unhandled (input),
740      KeySCommand => unhandled (input),
741      KeySCopy => unhandled (input),
742      KeySCreate => unhandled (input),
743      KeySDC => (Keycode::Delete, SHIFT),
744      KeySDL => unhandled (input),
745      KeySelect => unhandled (input),
746      KeySEnd => (Keycode::End, SHIFT),
747      KeySEOL => unhandled (input),
748      KeySExit => unhandled (input),
749      KeySFind => unhandled (input),
750      KeySHome => (Keycode::Home, SHIFT),
751      KeySIC => (Keycode::Insert, SHIFT),
752
753      // PDCurses produces KeySMessage on Shift+Left
754      KeySLeft | KeySMessage => (Keycode::Left, SHIFT),
755      KeySMove => unhandled (input),
756      KeySNext => (Keycode::PageDown, SHIFT),
757      KeySOptions => unhandled (input),
758      KeySPrevious => (Keycode::PageUp, SHIFT),
759      KeySPrint => unhandled (input),
760      KeySRedo => unhandled (input),
761      KeySReplace => unhandled (input),
762      // PDCurses produces KeySResume on Shift+Right
763      KeySRight | KeySResume => (Keycode::Right, SHIFT),
764      KeySSave => unhandled (input),
765      KeySSuspend => unhandled (input),
766      KeySUndo => unhandled (input),
767      KeySuspend => unhandled (input),
768      KeyUndo => unhandled (input),
769
770      // NOTE: because we don't have access to the pancurses window in this
771      // scope, we return a resize input event with zero dimensions; the
772      // get_input() impl will take care of setting the correct size before
773      // returning the event
774      KeyResize => return input::System::Resized { width: 0, height: 0 }.into(),
775      KeyEvent => unhandled (input),
776      KeyMouse => unhandled (input),
777
778      KeyA1 => unhandled (input),
779      KeyA3 => unhandled (input),
780      KeyB2 => unhandled (input),
781      KeyC1 => unhandled (input),
782      KeyC3 => unhandled (input)
783    };
784    ( input::Button {
785        variant: keycode.into(),
786        modifiers
787      },
788      input::button::State::Pressed
789    ).into()
790  }
791}
792
793/// Converts a character to a keycode+modifier. The modifier is 'SHIFT' if the
794/// character is uppercase or requires pressing shift to type under usual
795/// circumstances (e.g. '!'), and empty otherwise.
796fn char_to_keycode (ch : char)
797  -> (view::input::button::Keycode, view::input::Modifiers)
798{
799  use view::input::Modifiers;
800  use view::input::button::Keycode;
801  const EMPTY : Modifiers = Modifiers::empty();
802  const SHIFT : Modifiers = Modifiers::SHIFT;
803  match ch {
804    'a' => (Keycode::A, EMPTY),
805    'b' => (Keycode::B, EMPTY),
806    'c' => (Keycode::C, EMPTY),
807    'd' => (Keycode::D, EMPTY),
808    'e' => (Keycode::E, EMPTY),
809    'f' => (Keycode::F, EMPTY),
810    'g' => (Keycode::G, EMPTY),
811    'h' => (Keycode::H, EMPTY),
812    'i' => (Keycode::I, EMPTY),
813    'j' => (Keycode::J, EMPTY),
814    'k' => (Keycode::K, EMPTY),
815    'l' => (Keycode::L, EMPTY),
816    'm' => (Keycode::M, EMPTY),
817    'n' => (Keycode::N, EMPTY),
818    'o' => (Keycode::O, EMPTY),
819    'p' => (Keycode::P, EMPTY),
820    'q' => (Keycode::Q, EMPTY),
821    'r' => (Keycode::R, EMPTY),
822    's' => (Keycode::S, EMPTY),
823    't' => (Keycode::T, EMPTY),
824    'u' => (Keycode::U, EMPTY),
825    'v' => (Keycode::V, EMPTY),
826    'w' => (Keycode::W, EMPTY),
827    'x' => (Keycode::X, EMPTY),
828    'y' => (Keycode::Y, EMPTY),
829    'z' => (Keycode::Z, EMPTY),
830    '1' => (Keycode::Key1, EMPTY),
831    '2' => (Keycode::Key2, EMPTY),
832    '3' => (Keycode::Key3, EMPTY),
833    '4' => (Keycode::Key4, EMPTY),
834    '5' => (Keycode::Key5, EMPTY),
835    '6' => (Keycode::Key6, EMPTY),
836    '7' => (Keycode::Key7, EMPTY),
837    '8' => (Keycode::Key8, EMPTY),
838    '9' => (Keycode::Key9, EMPTY),
839    '0' => (Keycode::Key0, EMPTY),
840    '-' => (Keycode::Minus, EMPTY),
841    '=' => (Keycode::Equal, EMPTY),
842    '`' => (Keycode::Grave, EMPTY),
843    '[' => (Keycode::LBracket, EMPTY),
844    ']' => (Keycode::RBracket, EMPTY),
845    '\\' => (Keycode::Backslash, EMPTY),
846    ';' => (Keycode::Semicolon, EMPTY),
847    '\'' => (Keycode::Quote, EMPTY),
848    ',' => (Keycode::Comma, EMPTY),
849    '.' => (Keycode::Period, EMPTY),
850    '/' => (Keycode::Slash, EMPTY),
851    'A' => (Keycode::A, SHIFT),
852    'B' => (Keycode::B, SHIFT),
853    'C' => (Keycode::C, SHIFT),
854    'D' => (Keycode::D, SHIFT),
855    'E' => (Keycode::E, SHIFT),
856    'F' => (Keycode::F, SHIFT),
857    'G' => (Keycode::G, SHIFT),
858    'H' => (Keycode::H, SHIFT),
859    'I' => (Keycode::I, SHIFT),
860    'J' => (Keycode::J, SHIFT),
861    'K' => (Keycode::K, SHIFT),
862    'L' => (Keycode::L, SHIFT),
863    'M' => (Keycode::M, SHIFT),
864    'N' => (Keycode::N, SHIFT),
865    'O' => (Keycode::O, SHIFT),
866    'P' => (Keycode::P, SHIFT),
867    'Q' => (Keycode::Q, SHIFT),
868    'R' => (Keycode::R, SHIFT),
869    'S' => (Keycode::S, SHIFT),
870    'T' => (Keycode::T, SHIFT),
871    'U' => (Keycode::U, SHIFT),
872    'V' => (Keycode::V, SHIFT),
873    'W' => (Keycode::W, SHIFT),
874    'X' => (Keycode::X, SHIFT),
875    'Y' => (Keycode::Y, SHIFT),
876    'Z' => (Keycode::Z, SHIFT),
877    '!' => (Keycode::Key1, SHIFT),
878    '@' => (Keycode::Key2, SHIFT),
879    '#' => (Keycode::Key3, SHIFT),
880    '$' => (Keycode::Key4, SHIFT),
881    '%' => (Keycode::Key5, SHIFT),
882    '^' => (Keycode::Key6, SHIFT),
883    '&' => (Keycode::Key7, SHIFT),
884    '*' => (Keycode::Key8, SHIFT),
885    '(' => (Keycode::Key9, SHIFT),
886    ')' => (Keycode::Key0, SHIFT),
887    '_' => (Keycode::Minus, SHIFT),
888    '+' => (Keycode::Equal, SHIFT),
889    '~' => (Keycode::Grave, SHIFT),
890    '{' => (Keycode::LBracket, SHIFT),
891    '}' => (Keycode::RBracket, SHIFT),
892    '|' => (Keycode::Backslash, SHIFT),
893    ':' => (Keycode::Semicolon, SHIFT),
894    '"' => (Keycode::Quote, SHIFT),
895    '<' => (Keycode::Comma, SHIFT),
896    '>' => (Keycode::Period, SHIFT),
897    '?' => (Keycode::Slash, SHIFT),
898    ' ' => (Keycode::Space, EMPTY),
899    '\n' => (Keycode::Enter, EMPTY),
900    '\t' => (Keycode::Tab, EMPTY),
901    _ => unreachable!()
902  }
903}
904
905const fn convert_color (color : view::Color)
906  -> Result <easycurses::Color, view::Color>
907{
908  use view::color::*;
909  let color = match color {
910    Color::Named (named) => match named {
911      Named::Monochrome (Monochrome::White)  => easycurses::Color::White,
912      Named::Monochrome (Monochrome::Grey)   => return Err (color),
913      Named::Monochrome (Monochrome::Black)  => easycurses::Color::Black,
914      Named::Hue (hue) => match hue {
915        Hue::Primary    (Primary::Red)       => easycurses::Color::Red,
916        Hue::Primary    (Primary::Green)     => easycurses::Color::Green,
917        Hue::Primary    (Primary::Blue)      => easycurses::Color::Blue,
918        Hue::Secondary  (Secondary::Cyan)    => easycurses::Color::Cyan,
919        Hue::Secondary  (Secondary::Yellow)  => easycurses::Color::Yellow,
920        Hue::Secondary  (Secondary::Magenta) => easycurses::Color::Magenta,
921        _ => return Err (color)
922      }
923    },
924    Color::Raw (_) => return Err (color)
925  };
926  Ok (color)
927}