2016-03-31 10:09:34 +00:00
|
|
|
use std::cell::RefCell;
|
|
|
|
use std::thread;
|
2016-03-31 16:19:08 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::mem;
|
2016-04-03 10:06:35 +00:00
|
|
|
use std::string::String;
|
2016-03-31 16:19:08 +00:00
|
|
|
|
|
|
|
use rmp::Value;
|
|
|
|
use rmp::value::Integer;
|
2016-03-31 10:09:34 +00:00
|
|
|
|
2016-03-16 15:25:25 +00:00
|
|
|
use cairo;
|
2016-04-01 21:14:22 +00:00
|
|
|
use cairo::TextExtents;
|
|
|
|
use cairo::enums::{FontWeight, FontSlant};
|
2016-03-16 15:25:25 +00:00
|
|
|
use gtk;
|
|
|
|
use gtk::prelude::*;
|
2016-03-17 13:58:21 +00:00
|
|
|
use gtk::{Window, WindowType, DrawingArea, Grid, ToolButton, ButtonBox, Orientation, Image};
|
2016-03-31 13:52:22 +00:00
|
|
|
use gdk;
|
|
|
|
use gdk::EventKey;
|
|
|
|
use neovim_lib::{Neovim, NeovimApi};
|
2016-03-16 15:25:25 +00:00
|
|
|
|
2016-03-31 16:19:08 +00:00
|
|
|
use ui_model::{UiModel, Attrs, Color};
|
2016-03-23 11:59:18 +00:00
|
|
|
use nvim::RedrawEvents;
|
2016-03-19 08:47:23 +00:00
|
|
|
|
2016-04-01 21:14:22 +00:00
|
|
|
const FONT_NAME: &'static str = "Droid Sans Mono for Powerline";
|
|
|
|
const FONT_SIZE: f64 = 16.0;
|
|
|
|
|
2016-03-31 10:09:34 +00:00
|
|
|
thread_local!(pub static UI: RefCell<Ui> = {
|
|
|
|
let thread = thread::current();
|
|
|
|
let current_thread_name = thread.name();
|
|
|
|
if current_thread_name != Some("<main>") {
|
|
|
|
panic!("Can create UI only from main thread, {:?}", current_thread_name);
|
|
|
|
}
|
|
|
|
RefCell::new(Ui::new())
|
|
|
|
});
|
|
|
|
|
2016-03-19 10:27:39 +00:00
|
|
|
pub struct Ui {
|
2016-03-28 15:05:10 +00:00
|
|
|
pub model: UiModel,
|
2016-03-28 10:09:31 +00:00
|
|
|
nvim: Option<Neovim>,
|
2016-03-31 10:09:34 +00:00
|
|
|
drawing_area: DrawingArea,
|
2016-03-31 16:19:08 +00:00
|
|
|
cur_attrs: Option<Attrs>,
|
2016-03-19 10:27:39 +00:00
|
|
|
}
|
2016-03-16 15:25:25 +00:00
|
|
|
|
|
|
|
impl Ui {
|
|
|
|
pub fn new() -> Ui {
|
2016-03-19 10:27:39 +00:00
|
|
|
Ui {
|
|
|
|
model: UiModel::empty(),
|
2016-03-31 10:09:34 +00:00
|
|
|
drawing_area: DrawingArea::new(),
|
2016-03-28 10:09:31 +00:00
|
|
|
nvim: None,
|
2016-03-31 16:19:08 +00:00
|
|
|
cur_attrs: None,
|
2016-03-19 10:27:39 +00:00
|
|
|
}
|
2016-03-16 15:25:25 +00:00
|
|
|
}
|
|
|
|
|
2016-03-28 10:09:31 +00:00
|
|
|
pub fn set_nvim(&mut self, nvim: Neovim) {
|
|
|
|
self.nvim = Some(nvim);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn nvim(&mut self) -> &mut Neovim {
|
|
|
|
self.nvim.as_mut().unwrap()
|
|
|
|
}
|
|
|
|
|
2016-03-31 10:09:34 +00:00
|
|
|
pub fn init(&mut self) {
|
|
|
|
|
2016-03-16 15:25:25 +00:00
|
|
|
let window = Window::new(WindowType::Toplevel);
|
|
|
|
|
|
|
|
let grid = Grid::new();
|
|
|
|
|
|
|
|
let button_bar = ButtonBox::new(Orientation::Horizontal);
|
2016-03-17 13:58:21 +00:00
|
|
|
button_bar.set_hexpand(true);
|
|
|
|
button_bar.set_layout(gtk::ButtonBoxStyle::Start);
|
|
|
|
|
|
|
|
let open_image = Image::new_from_icon_name("document-open", 50);
|
|
|
|
let open_btn = ToolButton::new(Some(&open_image), None);
|
|
|
|
button_bar.add(&open_btn);
|
|
|
|
|
|
|
|
let save_image = Image::new_from_icon_name("document-save", 50);
|
|
|
|
let save_btn = ToolButton::new(Some(&save_image), None);
|
|
|
|
button_bar.add(&save_btn);
|
|
|
|
|
|
|
|
let exit_image = Image::new_from_icon_name("application-exit", 50);
|
|
|
|
let exit_btn = ToolButton::new(Some(&exit_image), None);
|
|
|
|
button_bar.add(&exit_btn);
|
|
|
|
|
2016-03-16 15:25:25 +00:00
|
|
|
grid.attach(&button_bar, 0, 0, 1, 1);
|
|
|
|
|
2016-03-31 10:09:34 +00:00
|
|
|
self.drawing_area.set_size_request(500, 500);
|
|
|
|
self.drawing_area.set_hexpand(true);
|
|
|
|
self.drawing_area.set_vexpand(true);
|
|
|
|
grid.attach(&self.drawing_area, 0, 1, 1, 1);
|
|
|
|
self.drawing_area.connect_draw(gtk_draw);
|
2016-03-16 15:25:25 +00:00
|
|
|
|
|
|
|
window.add(&grid);
|
|
|
|
window.show_all();
|
2016-03-31 13:52:22 +00:00
|
|
|
window.connect_key_press_event(gtk_key_press);
|
2016-03-31 10:09:34 +00:00
|
|
|
window.connect_delete_event(|_, _| {
|
2016-03-16 15:25:25 +00:00
|
|
|
gtk::main_quit();
|
|
|
|
Inhibit(false)
|
|
|
|
});
|
|
|
|
}
|
2016-03-31 10:09:34 +00:00
|
|
|
}
|
2016-03-16 15:25:25 +00:00
|
|
|
|
2016-04-02 20:00:18 +00:00
|
|
|
use phf;
|
|
|
|
include!(concat!(env!("OUT_DIR"), "/key_map_table.rs"));
|
|
|
|
|
2016-04-03 10:06:35 +00:00
|
|
|
|
|
|
|
fn keyval_to_input_string(val: &str, state: gdk::ModifierType) -> String {
|
|
|
|
let mut input = String::from("<");
|
|
|
|
if state.contains(gdk::enums::modifier_type::ShiftMask) {
|
|
|
|
input.push_str("S-");
|
|
|
|
}
|
|
|
|
if state.contains(gdk::enums::modifier_type::ControlMask) {
|
|
|
|
input.push_str("C-");
|
|
|
|
}
|
|
|
|
if state.contains(gdk::enums::modifier_type::Mod1Mask) {
|
|
|
|
input.push_str("A-");
|
|
|
|
}
|
|
|
|
input.push_str(val);
|
|
|
|
input.push_str(">");
|
|
|
|
input
|
|
|
|
}
|
|
|
|
|
|
|
|
fn convert_keyval(input: &str, state: gdk::ModifierType) -> String {
|
|
|
|
if let Some(cnvt) = KEYVAL_MAP.get(input).cloned() {
|
|
|
|
keyval_to_input_string(cnvt, state)
|
|
|
|
} else {
|
|
|
|
if state.is_empty() {
|
|
|
|
input.to_string()
|
|
|
|
} else {
|
|
|
|
keyval_to_input_string(input, state)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_keyval_ignore(keyval_name: &String) -> bool {
|
|
|
|
keyval_name.contains("Shift") || keyval_name.contains("Alt") || keyval_name.contains("Control")
|
2016-04-02 20:00:18 +00:00
|
|
|
}
|
|
|
|
|
2016-03-31 13:52:22 +00:00
|
|
|
fn gtk_key_press(_: &Window, ev: &EventKey) -> Inhibit {
|
|
|
|
let keyval = ev.get_keyval();
|
2016-04-03 10:06:35 +00:00
|
|
|
let state = ev.get_state();
|
2016-03-31 13:52:22 +00:00
|
|
|
if let Some(keyval_name) = gdk::keyval_name(keyval) {
|
2016-04-03 10:06:35 +00:00
|
|
|
if !is_keyval_ignore(&keyval_name) {
|
|
|
|
UI.with(|ui_cell| {
|
|
|
|
let mut ui = ui_cell.borrow_mut();
|
|
|
|
let input = if keyval_name.starts_with("KP_") {
|
|
|
|
keyval_name.chars().skip(3).collect()
|
|
|
|
} else {
|
|
|
|
keyval_name
|
|
|
|
};
|
|
|
|
|
|
|
|
let converted_input = convert_keyval(&input, state);
|
|
|
|
println!("{}", converted_input);
|
|
|
|
ui.nvim().input(&converted_input).expect("Error run input command to nvim");
|
|
|
|
});
|
|
|
|
}
|
2016-03-31 13:52:22 +00:00
|
|
|
}
|
|
|
|
Inhibit(true)
|
|
|
|
}
|
|
|
|
|
2016-04-01 21:14:22 +00:00
|
|
|
fn calc_char_bounds(ctx: &cairo::Context) -> TextExtents {
|
|
|
|
let font_face = cairo::FontFace::toy_create(FONT_NAME, FontSlant::Normal, FontWeight::Bold);
|
|
|
|
ctx.set_font_size(FONT_SIZE);
|
|
|
|
ctx.set_font_face(font_face);
|
|
|
|
ctx.text_extents("A")
|
|
|
|
}
|
|
|
|
|
2016-03-31 10:09:34 +00:00
|
|
|
fn gtk_draw(drawing_area: &DrawingArea, ctx: &cairo::Context) -> Inhibit {
|
|
|
|
let width = drawing_area.get_allocated_width() as f64;
|
|
|
|
let height = drawing_area.get_allocated_height() as f64;
|
|
|
|
|
2016-03-31 16:19:08 +00:00
|
|
|
ctx.set_source_rgb(0.0, 0.0, 0.0);
|
|
|
|
ctx.paint();
|
|
|
|
ctx.set_source_rgb(1.0, 1.0, 1.0);
|
|
|
|
|
2016-04-01 21:14:22 +00:00
|
|
|
let char_bounds = calc_char_bounds(ctx);
|
2016-03-31 13:52:22 +00:00
|
|
|
let font_extents = ctx.font_extents();
|
2016-04-01 21:14:22 +00:00
|
|
|
|
2016-03-31 10:09:34 +00:00
|
|
|
UI.with(|ui_cell| {
|
|
|
|
let ui = ui_cell.borrow();
|
2016-03-31 13:52:22 +00:00
|
|
|
|
|
|
|
let mut line_y = font_extents.height;
|
2016-03-31 16:19:08 +00:00
|
|
|
for line in ui.model.model() {
|
|
|
|
ctx.move_to(0.0, line_y - font_extents.descent);
|
|
|
|
for cell in line {
|
2016-04-01 09:34:55 +00:00
|
|
|
let slant = if cell.attrs.italic {
|
2016-04-01 21:14:22 +00:00
|
|
|
FontSlant::Italic
|
2016-04-01 09:34:55 +00:00
|
|
|
} else {
|
2016-04-01 21:14:22 +00:00
|
|
|
FontSlant::Normal
|
2016-04-01 09:34:55 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let weight = if cell.attrs.bold {
|
2016-04-01 21:14:22 +00:00
|
|
|
FontWeight::Bold
|
2016-04-01 09:34:55 +00:00
|
|
|
} else {
|
2016-04-01 21:14:22 +00:00
|
|
|
FontWeight::Normal
|
2016-04-01 09:34:55 +00:00
|
|
|
};
|
|
|
|
|
2016-04-01 21:14:22 +00:00
|
|
|
let font_face = cairo::FontFace::toy_create(FONT_NAME, slant, weight);
|
2016-04-01 09:34:55 +00:00
|
|
|
ctx.set_font_face(font_face);
|
2016-04-01 21:14:22 +00:00
|
|
|
ctx.set_font_size(FONT_SIZE);
|
2016-04-01 09:34:55 +00:00
|
|
|
|
2016-03-31 16:19:08 +00:00
|
|
|
let bg = &cell.attrs.background;
|
|
|
|
ctx.set_source_rgb(bg.0, bg.1, bg.2);
|
|
|
|
let current_point = ctx.get_current_point();
|
2016-04-01 09:34:55 +00:00
|
|
|
ctx.rectangle(current_point.0,
|
|
|
|
line_y - font_extents.height,
|
2016-04-01 21:14:22 +00:00
|
|
|
char_bounds.width,
|
2016-04-01 09:34:55 +00:00
|
|
|
font_extents.height);
|
2016-03-31 16:19:08 +00:00
|
|
|
ctx.fill();
|
|
|
|
|
2016-04-01 09:34:55 +00:00
|
|
|
ctx.move_to(current_point.0, current_point.1);
|
2016-03-31 16:19:08 +00:00
|
|
|
let fg = &cell.attrs.foreground;
|
|
|
|
ctx.set_source_rgb(fg.0, fg.1, fg.2);
|
|
|
|
ctx.show_text(&cell.ch.to_string());
|
2016-04-01 21:14:22 +00:00
|
|
|
ctx.move_to(current_point.0 + char_bounds.width, current_point.1);
|
2016-03-31 16:19:08 +00:00
|
|
|
}
|
2016-03-31 13:52:22 +00:00
|
|
|
line_y += font_extents.height;
|
2016-03-31 10:09:34 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Inhibit(true)
|
2016-03-16 15:25:25 +00:00
|
|
|
}
|
2016-03-23 11:59:18 +00:00
|
|
|
|
|
|
|
impl RedrawEvents for Ui {
|
2016-03-28 14:03:21 +00:00
|
|
|
fn on_cursor_goto(&mut self, row: u64, col: u64) {
|
|
|
|
self.model.set_cursor(row, col);
|
|
|
|
}
|
2016-03-28 15:05:10 +00:00
|
|
|
|
|
|
|
fn on_put(&mut self, text: &str) {
|
2016-03-31 16:19:08 +00:00
|
|
|
self.model.put(text, &self.cur_attrs);
|
2016-03-28 15:05:10 +00:00
|
|
|
}
|
2016-03-29 09:22:16 +00:00
|
|
|
|
|
|
|
fn on_clear(&mut self) {
|
|
|
|
self.model.clear();
|
|
|
|
}
|
2016-03-29 09:43:01 +00:00
|
|
|
|
|
|
|
fn on_resize(&mut self, columns: u64, rows: u64) {
|
|
|
|
self.model = UiModel::new(rows, columns);
|
|
|
|
}
|
2016-03-31 13:52:22 +00:00
|
|
|
|
|
|
|
fn on_redraw(&self) {
|
|
|
|
self.drawing_area.queue_draw();
|
|
|
|
}
|
2016-03-31 16:19:08 +00:00
|
|
|
|
|
|
|
fn on_highlight_set(&mut self, attrs: &HashMap<String, Value>) {
|
|
|
|
let mut model_attrs = Attrs::new();
|
|
|
|
if let Some(&Value::Integer(Integer::U64(fg))) = attrs.get("foreground") {
|
|
|
|
model_attrs.foreground = split_color(fg);
|
|
|
|
}
|
|
|
|
if let Some(&Value::Integer(Integer::U64(fg))) = attrs.get("background") {
|
|
|
|
model_attrs.background = split_color(fg);
|
|
|
|
}
|
|
|
|
if attrs.contains_key("reverse") {
|
|
|
|
mem::swap(&mut model_attrs.foreground, &mut model_attrs.background);
|
|
|
|
}
|
2016-04-01 09:34:55 +00:00
|
|
|
model_attrs.bold = attrs.contains_key("bold");
|
|
|
|
model_attrs.italic = attrs.contains_key("italic");
|
2016-03-31 16:19:08 +00:00
|
|
|
self.cur_attrs = Some(model_attrs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn split_color(indexed_color: u64) -> Color {
|
|
|
|
let r = ((indexed_color >> 16) & 0xff) as f64;
|
|
|
|
let g = ((indexed_color >> 8) & 0xff) as f64;
|
|
|
|
let b = (indexed_color & 0xff) as f64;
|
|
|
|
Color(255.0 / r, 255.0 / g, 255.0 / b)
|
2016-03-23 11:59:18 +00:00
|
|
|
}
|