rust-cgui 0.1.15

A low-level cross-platform GUI library
Documentation

pub use super::*;

/// X11 backend implementation
impl RenderBack for CWin {
  fn init(&mut self) {
    if self.rep.init_flag {
      return;
    }
    // Perform x11 init steps
    unsafe {
      match x11_dl::xlib::Xlib::open() {
        Ok(the_lib) => {
          // Write the_lib into the uninitialized memory held by xlib_dl_obj
          ptr::write(&mut (self.rep.xlib_dl_obj), the_lib);
        }
        Err(_e) => {
          println!("[ CGUI ] Fatal Error: we expect to be able to open XLib but cannot. You have uninitialized memory and cannot continue.");
          return;
        }
      }
      
      self.rep.x11_disp_ptr = (self.rep.xlib_dl_obj.XOpenDisplay)(null_mut()/*null*/);
      let screen = (self.rep.xlib_dl_obj.XDefaultScreen)(self.rep.x11_disp_ptr);
      self.rep.x11_window_ptr = (self.rep.xlib_dl_obj.XCreateSimpleWindow)(
        self.rep.x11_disp_ptr, (self.rep.xlib_dl_obj.XRootWindow)(self.rep.x11_disp_ptr, screen),
        0, 0, // x and y
        self.rep.width as u32, self.rep.height as u32,
        0 /*border_width*/, (self.rep.xlib_dl_obj.XBlackPixel)(self.rep.x11_disp_ptr, screen) /*border*/, (self.rep.xlib_dl_obj.XWhitePixel)(self.rep.x11_disp_ptr, screen) /*background*/
      );
      let root_win_ptr = (self.rep.xlib_dl_obj.XDefaultRootWindow)(self.rep.x11_disp_ptr);
      
      (self.rep.xlib_dl_obj.XSelectInput)(self.rep.x11_disp_ptr, self.rep.x11_window_ptr, x11_dl::xlib::ExposureMask | x11_dl::xlib::KeyPressMask);
      (self.rep.xlib_dl_obj.XMapWindow)(self.rep.x11_disp_ptr, self.rep.x11_window_ptr);
      
      let title_ffi_content = CString::new(self.rep.title.clone()).expect("CString::new failed");
      (self.rep.xlib_dl_obj.XSetWMName)(self.rep.x11_disp_ptr, self.rep.x11_window_ptr, &mut x11_dl::xlib::XTextProperty {
        value: title_ffi_content.into_raw() as *mut _ as *mut u8,
        encoding: x11_dl::xlib::XA_STRING,
        format: 8, // 8, 16, or 32 (char width)
        nitems: self.rep.title.len() as u64,
      });
      
      self.rep.x11_gc_ptr = (self.rep.xlib_dl_obj.XCreateGC)(self.rep.x11_disp_ptr, root_win_ptr, 0, null_mut() /*null*/);
      
    }
    self.rep.init_flag = true;
  }
  
  fn redraw_dirty(&mut self) {
    let x1 = self.rep.dirty_extents[0];
    let y1 = self.rep.dirty_extents[1];
    let x2 = self.rep.dirty_extents[2];
    let y2 = self.rep.dirty_extents[3];
    self.redraw_box(x1, y1, x2, y2);
    self.rep.dirty_extents = [self.rep.width, self.rep.height, 0, 0]; // reset to inverted rectangle
  }
  
  fn redraw_box(&mut self, x1: usize, y1: usize, x2: usize, y2: usize) {
    self.rep.redraw_box(x1, y1, x2, y2);
  }
  
  /// Apps which call event_tick manually MUST call init() first. I refuse to throw a check for self.init every time the user moves their mouse.
  /// Also apps which explicitly use event_tick must free the c_string returned. (or do I need to add a str_free-ing function to do the free here?)
  fn event_tick(&self) -> String {
    unsafe {
      let mut event: x11_dl::xlib::XEvent = mem::zeroed(); // could also go for mem::uninitialized, but I'd like to be a little safer
      (self.rep.xlib_dl_obj.XNextEvent)(self.rep.x11_disp_ptr, &mut event);
      
      let evt_str: String = match event.get_type() {
        x11_dl::xlib::Expose => {
          "WinShown".to_string()
        }
        x11_dl::xlib::KeyPress => {
          match get_alpha_char_from_x11_evt(&self.rep, event) {
            Some(c) => {
              format!("KeyPress,{}", c)
            }
            None => {
              "KeyPress,UNKNOWN".to_string()
            }
          }
        }
        _ => {
          "UNKNOWN".to_string()
        }
      };
      
      //let safe_cstr : CString = CString::new(evt_str).unwrap();
      //let cstr_ptr = safe_cstr.as_ptr();
      //std::mem::forget(safe_cstr);
      return evt_str;
    }
  }
  
  fn event_loop(&mut self) {
    if ! self.rep.init_flag {
      self.init();
    }
    while ! self.rep.exit_flag {
      let evt_mess = self.event_tick();
      self.call_callbacks(evt_mess);
    }
  }
}

fn get_alpha_char_from_x11_evt(self_cwin_rep: &CWinRep, mut event: x11_dl::xlib::XEvent) -> Option<char> {
  unsafe {
    let key_pressed_buff = CString::new("buff-buff-buff-buff").expect("CString::new failed");
    let mut key_sym : x11_dl::xlib::KeySym = mem::zeroed();
    let c_str_ptr = key_pressed_buff.into_raw();
    (self_cwin_rep.xlib_dl_obj.XLookupString)(&mut event.key, c_str_ptr, 8 /*len of key_pressed_buff*/, &mut key_sym, null_mut());
    // We need to re-construct our reference b/c we gave ownership to xlib.
    let returned_data = CStr::from_ptr(c_str_ptr);
    if returned_data.to_bytes().len() < 1 {
      println!("TODO: handle an empty character (I think this happens on arrow keys)");
      return None;
    }
    // Don't shoot me
    let first_char : char = (returned_data.to_bytes()[0] as u8) as char;
    if first_char.is_alphabetic() || first_char.is_numeric() {
      return Some(first_char);
    }
    else {
      return None;
    }
  }
}

impl RenderBack for CWinRep {
  fn init(&mut self) {
    if self.init_flag {
      return;
    }
    // Perform x11 init steps
    unsafe {
      match x11_dl::xlib::Xlib::open() {
        Ok(the_lib) => {
          // Write the_lib into the uninitialized memory held by xlib_dl_obj
          ptr::write(&mut (self.xlib_dl_obj), the_lib);
        }
        Err(_e) => {
          println!("[ CGUI ] Fatal Error: we expect to be able to open XLib but cannot. You have uninitialized memory and cannot continue.");
          return;
        }
      }
      
      self.x11_disp_ptr = (self.xlib_dl_obj.XOpenDisplay)(null_mut()/*null*/);
      let screen = (self.xlib_dl_obj.XDefaultScreen)(self.x11_disp_ptr);
      self.x11_window_ptr = (self.xlib_dl_obj.XCreateSimpleWindow)(
        self.x11_disp_ptr, (self.xlib_dl_obj.XRootWindow)(self.x11_disp_ptr, screen),
        0, 0, // x and y
        self.width as u32, self.height as u32,
        0 /*border_width*/, (self.xlib_dl_obj.XBlackPixel)(self.x11_disp_ptr, screen) /*border*/, (self.xlib_dl_obj.XWhitePixel)(self.x11_disp_ptr, screen) /*background*/
      );
      let root_win_ptr = (self.xlib_dl_obj.XDefaultRootWindow)(self.x11_disp_ptr);
      
      (self.xlib_dl_obj.XSelectInput)(self.x11_disp_ptr, self.x11_window_ptr, x11_dl::xlib::ExposureMask | x11_dl::xlib::KeyPressMask);
      (self.xlib_dl_obj.XMapWindow)(self.x11_disp_ptr, self.x11_window_ptr);
      
      let title_ffi_content = CString::new(self.title.clone()).expect("CString::new failed");
      (self.xlib_dl_obj.XSetWMName)(self.x11_disp_ptr, self.x11_window_ptr, &mut x11_dl::xlib::XTextProperty {
        value: title_ffi_content.into_raw() as *mut _ as *mut u8,
        encoding: x11_dl::xlib::XA_STRING,
        format: 8, // 8, 16, or 32 (char width)
        nitems: self.title.len() as u64,
      });
      
      self.x11_gc_ptr = (self.xlib_dl_obj.XCreateGC)(self.x11_disp_ptr, root_win_ptr, 0, null_mut() /*null*/);
      
    }
    self.init_flag = true;
  }
  
  fn redraw_dirty(&mut self) {
    let x1 = self.dirty_extents[0];
    let y1 = self.dirty_extents[1];
    let x2 = self.dirty_extents[2];
    let y2 = self.dirty_extents[3];
    self.redraw_box(x1, y1, x2, y2);
    self.dirty_extents = [self.width, self.height, 0, 0]; // reset to inverted rectangle
  }
  
  fn redraw_box(&mut self, x1: usize, y1: usize, x2: usize, y2: usize) {
    
    if x1 == x2 || y1 == y2 {
      // Width or height = 0, no-op
      return;
    }
    if x1 > x2 || y1 > y2 {
      // Inverted extents, no-op
      return;
    }
    
    unsafe {
      // Loop over x1-x2, y1-y2 drawing single pixels. Not very fast.
      for x in x1..x2 {
        for y in y1..y2 {
          let color = self.pixels.get_pixel(x as u32, y as u32).to_rgb().data;
          let color_int: u64 = // X11 uses R-G-B
            ((color[0] as u64) << 16) |
            ((color[1] as u64) << 8) |
            ((color[2] as u64) << 0)
            ;
          //println!("color = {}, {}, {}", color[0], color[1], color[2]);
          //println!("color_int = {}", color_int);
          (self.xlib_dl_obj.XSetForeground)(self.x11_disp_ptr, self.x11_gc_ptr, color_int as u64);
          (self.xlib_dl_obj.XDrawPoint)(self.x11_disp_ptr, self.x11_window_ptr, self.x11_gc_ptr, x as i32, y as i32);
        }
      }
      
      let pixmap = (self.xlib_dl_obj.XCreatePixmap)(
        self.x11_disp_ptr, self.x11_window_ptr, self.width as u32, self.height as u32, 8 /*depth*/
      );
      (self.xlib_dl_obj.XFreePixmap)(self.x11_disp_ptr, pixmap);
      
      
      (self.xlib_dl_obj.XFlush)(self.x11_disp_ptr);
    }
  }
  
  /// Apps which call event_tick manually MUST call init() first.
  /// I refuse to throw a check for self.init every time the user moves their mouse.
  /// Also apps which explicitly use event_tick must free the c_string returned.
  // (or do I need to add a str_free-ing function to do the free here?)
  fn event_tick(&self) -> String {
    unsafe {
      let mut event: x11_dl::xlib::XEvent = mem::zeroed(); // could also go for mem::uninitialized, but I'd like to be a little safer
      (self.xlib_dl_obj.XNextEvent)(self.x11_disp_ptr, &mut event);
      
      let evt_str: String = match event.get_type() {
        x11_dl::xlib::Expose => {
          "WinShown".to_string()
        }
        x11_dl::xlib::KeyPress => {
          match get_alpha_char_from_x11_evt(self, event) {
            Some(c) => {
              format!("KeyPress,{}", c)
            }
            None => {
              "KeyPress,UNKNOWN".to_string()
            }
          }
        }
        _ => {
          "UNKNOWN".to_string()
        }
      };
      
      let safe_cstr : CString = CString::new(evt_str).unwrap();
      return format!("{}", safe_cstr.to_string_lossy().to_owned() );
    }
  }
  
  fn event_loop(&mut self) {
    // We can't do this as a rep
    println!("Warning: event_loop on a CWinRep is a no-op.");
  }
}