Start extracting vim shell ui processing to shell file
This commit is contained in:
parent
e719535d2a
commit
61a429d2f1
@ -11,6 +11,7 @@ extern crate neovim_lib;
|
|||||||
extern crate phf;
|
extern crate phf;
|
||||||
|
|
||||||
mod nvim;
|
mod nvim;
|
||||||
|
mod shell;
|
||||||
mod ui_model;
|
mod ui_model;
|
||||||
mod ui;
|
mod ui;
|
||||||
mod input;
|
mod input;
|
||||||
|
12
src/nvim.rs
12
src/nvim.rs
@ -3,7 +3,7 @@ use std::io::{Result, Error, ErrorKind};
|
|||||||
use std::result;
|
use std::result;
|
||||||
use ui_model::UiModel;
|
use ui_model::UiModel;
|
||||||
use ui;
|
use ui;
|
||||||
use ui::Ui;
|
use shell::Shell;
|
||||||
use glib;
|
use glib;
|
||||||
|
|
||||||
pub trait RedrawEvents {
|
pub trait RedrawEvents {
|
||||||
@ -64,7 +64,7 @@ macro_rules! try_uint {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initialize(ui: &mut Ui,
|
pub fn initialize(ui: &mut Shell,
|
||||||
nvim_bin_path: Option<&String>)
|
nvim_bin_path: Option<&String>)
|
||||||
-> Result<()> {
|
-> Result<()> {
|
||||||
let session = if let Some(path) = nvim_bin_path {
|
let session = if let Some(path) = nvim_bin_path {
|
||||||
@ -140,7 +140,7 @@ fn nvim_cb(method: &str, params: Vec<Value>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call_gui_event(ui: &mut Ui, method: &str, args: &Vec<Value>) -> result::Result<(), String> {
|
fn call_gui_event(ui: &mut Shell, method: &str, args: &Vec<Value>) -> result::Result<(), String> {
|
||||||
match method {
|
match method {
|
||||||
"Font" => ui.set_font(try_str!(args[0])),
|
"Font" => ui.set_font(try_str!(args[0])),
|
||||||
_ => return Err(format!("Unsupported event {}({:?})", method, args)),
|
_ => return Err(format!("Unsupported event {}({:?})", method, args)),
|
||||||
@ -148,7 +148,7 @@ fn call_gui_event(ui: &mut Ui, method: &str, args: &Vec<Value>) -> result::Resul
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(ui: &mut Ui, method: &str, args: &Vec<Value>) -> result::Result<(), String> {
|
fn call(ui: &mut Shell, method: &str, args: &Vec<Value>) -> result::Result<(), String> {
|
||||||
match method {
|
match method {
|
||||||
"cursor_goto" => ui.on_cursor_goto(try_uint!(args[0]), try_uint!(args[1])),
|
"cursor_goto" => ui.on_cursor_goto(try_uint!(args[0]), try_uint!(args[1])),
|
||||||
"put" => ui.on_put(try_str!(args[0])),
|
"put" => ui.on_put(try_str!(args[0])),
|
||||||
@ -182,10 +182,10 @@ fn call(ui: &mut Ui, method: &str, args: &Vec<Value>) -> result::Result<(), Stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn safe_call<F>(cb: F)
|
fn safe_call<F>(cb: F)
|
||||||
where F: Fn(&mut Ui) -> result::Result<(), String> + 'static + Send
|
where F: Fn(&mut Shell) -> result::Result<(), String> + 'static + Send
|
||||||
{
|
{
|
||||||
glib::idle_add(move || {
|
glib::idle_add(move || {
|
||||||
ui::UI.with(|ui_cell| if let Err(msg) = cb(&mut *ui_cell.borrow_mut()) {
|
ui::UI.with(|ui_cell| if let Err(msg) = cb(&mut ui_cell.borrow_mut().shell) {
|
||||||
println!("Error call function: {}", msg);
|
println!("Error call function: {}", msg);
|
||||||
});
|
});
|
||||||
glib::Continue(false)
|
glib::Continue(false)
|
||||||
|
557
src/shell.rs
Normal file
557
src/shell.rs
Normal file
@ -0,0 +1,557 @@
|
|||||||
|
use std::string::String;
|
||||||
|
|
||||||
|
use cairo;
|
||||||
|
use pangocairo as pc;
|
||||||
|
use pango;
|
||||||
|
use pango::FontDescription;
|
||||||
|
use gdk::{ModifierType, Event, EventKey, EventConfigure, EventButton, EventMotion, EventType};
|
||||||
|
use gdk_sys;
|
||||||
|
use glib;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk;
|
||||||
|
use gtk::{ApplicationWindow, HeaderBar, DrawingArea, ToolButton, Image};
|
||||||
|
|
||||||
|
use neovim_lib::{Neovim, NeovimApi, Value, Integer};
|
||||||
|
|
||||||
|
use settings;
|
||||||
|
use ui_model::{UiModel, Cell, Attrs, Color, COLOR_BLACK, COLOR_WHITE, COLOR_RED};
|
||||||
|
use nvim::{RedrawEvents, GuiApi, ErrorReport};
|
||||||
|
use input::{convert_key, keyval_to_input_string};
|
||||||
|
use ui::{UI, Ui, SET};
|
||||||
|
|
||||||
|
const DEFAULT_FONT_NAME: &'static str = "DejaVu Sans Mono 12";
|
||||||
|
|
||||||
|
macro_rules! SHELL {
|
||||||
|
($id:ident = $expr:expr) => (
|
||||||
|
UI.with(|ui_cell| {
|
||||||
|
let mut $id = ui_cell.borrow_mut().shell;
|
||||||
|
$expr
|
||||||
|
});
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub enum NvimMode {
|
||||||
|
Normal,
|
||||||
|
Insert,
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Shell {
|
||||||
|
pub model: UiModel,
|
||||||
|
pub drawing_area: DrawingArea,
|
||||||
|
nvim: Option<Neovim>,
|
||||||
|
cur_attrs: Option<Attrs>,
|
||||||
|
bg_color: Color,
|
||||||
|
fg_color: Color,
|
||||||
|
sp_color: Color,
|
||||||
|
line_height: Option<f64>,
|
||||||
|
char_width: Option<f64>,
|
||||||
|
pub mode: NvimMode,
|
||||||
|
mouse_enabled: bool,
|
||||||
|
mouse_pressed: bool,
|
||||||
|
font_desc: FontDescription,
|
||||||
|
resize_timer: Option<glib::SourceId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Shell {
|
||||||
|
pub fn new() -> Shell {
|
||||||
|
Shell {
|
||||||
|
model: UiModel::empty(),
|
||||||
|
drawing_area: DrawingArea::new(),
|
||||||
|
nvim: None,
|
||||||
|
cur_attrs: None,
|
||||||
|
bg_color: COLOR_BLACK,
|
||||||
|
fg_color: COLOR_WHITE,
|
||||||
|
sp_color: COLOR_RED,
|
||||||
|
line_height: None,
|
||||||
|
char_width: None,
|
||||||
|
mode: NvimMode::Normal,
|
||||||
|
mouse_enabled: false,
|
||||||
|
mouse_pressed: false,
|
||||||
|
font_desc: FontDescription::from_string(DEFAULT_FONT_NAME),
|
||||||
|
resize_timer: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(&mut self) {
|
||||||
|
self.drawing_area.set_size_request(500, 300);
|
||||||
|
self.drawing_area.set_hexpand(true);
|
||||||
|
self.drawing_area.set_vexpand(true);
|
||||||
|
|
||||||
|
self.drawing_area
|
||||||
|
.set_events((gdk_sys::GDK_BUTTON_RELEASE_MASK | gdk_sys::GDK_BUTTON_PRESS_MASK |
|
||||||
|
gdk_sys::GDK_BUTTON_MOTION_MASK)
|
||||||
|
.bits() as i32);
|
||||||
|
self.drawing_area.connect_button_press_event(gtk_button_press);
|
||||||
|
self.drawing_area.connect_button_release_event(gtk_button_release);
|
||||||
|
self.drawing_area.connect_motion_notify_event(gtk_motion_notify);
|
||||||
|
self.drawing_area.connect_draw(gtk_draw);
|
||||||
|
self.drawing_area.connect_key_press_event(gtk_key_press);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_configure_event(&mut self) {
|
||||||
|
self.drawing_area.connect_configure_event(gtk_configure_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_nvim(&mut self, nvim: Neovim) {
|
||||||
|
self.nvim = Some(nvim);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nvim(&mut self) -> &mut Neovim {
|
||||||
|
self.nvim.as_mut().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_pango_font(&self) -> FontDescription {
|
||||||
|
self.font_desc.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_font_desc(&mut self, desc: &str) {
|
||||||
|
self.font_desc = FontDescription::from_string(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn colors<'a>(&'a self, cell: &'a Cell) -> (&'a Color, &'a Color) {
|
||||||
|
let bg = if let Some(ref bg) = cell.attrs.background {
|
||||||
|
bg
|
||||||
|
} else {
|
||||||
|
&self.bg_color
|
||||||
|
};
|
||||||
|
let fg = if let Some(ref fg) = cell.attrs.foreground {
|
||||||
|
fg
|
||||||
|
} else {
|
||||||
|
&self.fg_color
|
||||||
|
};
|
||||||
|
|
||||||
|
if cell.attrs.reverse {
|
||||||
|
(fg, bg)
|
||||||
|
} else {
|
||||||
|
(bg, fg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gtk_button_press(_: &DrawingArea, ev: &EventButton) -> Inhibit {
|
||||||
|
if ev.get_event_type() != EventType::ButtonPress {
|
||||||
|
return Inhibit(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
SHELL!(shell = {
|
||||||
|
if !shell.mouse_enabled {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse_input(&mut shell, "LeftMouse", ev.get_state(), ev.get_position());
|
||||||
|
});
|
||||||
|
Inhibit(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_input(shell: &mut Shell, input: &str, state: ModifierType, position: (f64, f64)) {
|
||||||
|
if let Some(line_height) = shell.line_height {
|
||||||
|
if let Some(char_width) = shell.char_width {
|
||||||
|
shell.mouse_pressed = true;
|
||||||
|
|
||||||
|
let nvim = shell.nvim();
|
||||||
|
let (x, y) = position;
|
||||||
|
let col = (x / char_width).trunc() as u64;
|
||||||
|
let row = (y / line_height).trunc() as u64;
|
||||||
|
let input_str = format!("{}<{},{}>", keyval_to_input_string(input, state), col, row);
|
||||||
|
nvim.input(&input_str).expect("Can't send mouse input event");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gtk_button_release(_: &DrawingArea, _: &EventButton) -> Inhibit {
|
||||||
|
SHELL!(shell = {
|
||||||
|
shell.mouse_pressed = false;
|
||||||
|
});
|
||||||
|
Inhibit(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gtk_motion_notify(_: &DrawingArea, ev: &EventMotion) -> Inhibit {
|
||||||
|
SHELL!(shell = {
|
||||||
|
if !shell.mouse_enabled || !shell.mouse_pressed {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse_input(&mut shell, "LeftDrag", ev.get_state(), ev.get_position());
|
||||||
|
});
|
||||||
|
Inhibit(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gtk_key_press(_: &DrawingArea, ev: &EventKey) -> Inhibit {
|
||||||
|
if let Some(input) = convert_key(ev) {
|
||||||
|
SHELL!(shell = {
|
||||||
|
shell.nvim().input(&input).expect("Error run input command to nvim");
|
||||||
|
});
|
||||||
|
Inhibit(true)
|
||||||
|
} else {
|
||||||
|
Inhibit(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gtk_draw(_: &DrawingArea, ctx: &cairo::Context) -> Inhibit {
|
||||||
|
UI.with(|ui_cell| {
|
||||||
|
let mut ui = ui_cell.borrow_mut();
|
||||||
|
|
||||||
|
let (width, height) = calc_char_bounds(&ui.shell, ctx);
|
||||||
|
ui.shell.line_height = Some(height as f64);
|
||||||
|
ui.shell.char_width = Some(width as f64);
|
||||||
|
|
||||||
|
draw(&ui.shell, ctx);
|
||||||
|
request_width(&ui);
|
||||||
|
});
|
||||||
|
|
||||||
|
Inhibit(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn draw_joined_rect(shell: &Shell,
|
||||||
|
ctx: &cairo::Context,
|
||||||
|
from_col_idx: usize,
|
||||||
|
col_idx: usize,
|
||||||
|
char_width: f64,
|
||||||
|
line_height: f64,
|
||||||
|
color: &Color) {
|
||||||
|
let current_point = ctx.get_current_point();
|
||||||
|
let rect_width = char_width * (col_idx - from_col_idx) as f64;
|
||||||
|
|
||||||
|
if &shell.bg_color != color {
|
||||||
|
ctx.set_source_rgb(color.0, color.1, color.2);
|
||||||
|
ctx.rectangle(current_point.0, current_point.1, rect_width, line_height);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.move_to(current_point.0 + rect_width, current_point.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(shell: &Shell, ctx: &cairo::Context) {
|
||||||
|
ctx.set_source_rgb(shell.bg_color.0, shell.bg_color.1, shell.bg_color.2);
|
||||||
|
ctx.paint();
|
||||||
|
|
||||||
|
let line_height = shell.line_height.unwrap();
|
||||||
|
let char_width = shell.char_width.unwrap();
|
||||||
|
let (row, col) = shell.model.get_cursor();
|
||||||
|
let mut buf = String::with_capacity(4);
|
||||||
|
|
||||||
|
let mut line_y: f64 = 0.0;
|
||||||
|
|
||||||
|
|
||||||
|
let layout = pc::create_layout(ctx);
|
||||||
|
let mut desc = shell.create_pango_font();
|
||||||
|
|
||||||
|
for (line_idx, line) in shell.model.model().iter().enumerate() {
|
||||||
|
ctx.move_to(0.0, line_y);
|
||||||
|
|
||||||
|
// first draw background
|
||||||
|
// here we join same bg color for given line
|
||||||
|
// this gives less drawing primitives
|
||||||
|
let mut from_col_idx = 0;
|
||||||
|
let mut from_bg = None;
|
||||||
|
for (col_idx, cell) in line.iter().enumerate() {
|
||||||
|
let (bg, _) = shell.colors(cell);
|
||||||
|
|
||||||
|
if from_bg.is_none() {
|
||||||
|
from_bg = Some(bg);
|
||||||
|
from_col_idx = col_idx;
|
||||||
|
} else if from_bg != Some(bg) {
|
||||||
|
draw_joined_rect(shell,
|
||||||
|
ctx,
|
||||||
|
from_col_idx,
|
||||||
|
col_idx,
|
||||||
|
char_width,
|
||||||
|
line_height,
|
||||||
|
from_bg.take().unwrap());
|
||||||
|
from_bg = Some(bg);
|
||||||
|
from_col_idx = col_idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
draw_joined_rect(shell,
|
||||||
|
ctx,
|
||||||
|
from_col_idx,
|
||||||
|
line.len(),
|
||||||
|
char_width,
|
||||||
|
line_height,
|
||||||
|
from_bg.take().unwrap());
|
||||||
|
|
||||||
|
ctx.move_to(0.0, line_y);
|
||||||
|
|
||||||
|
for (col_idx, cell) in line.iter().enumerate() {
|
||||||
|
let double_width = line.get(col_idx + 1).map(|c| c.attrs.double_width).unwrap_or(false);
|
||||||
|
let current_point = ctx.get_current_point();
|
||||||
|
|
||||||
|
let (bg, fg) = shell.colors(cell);
|
||||||
|
|
||||||
|
if row == line_idx && col == col_idx {
|
||||||
|
ctx.set_source_rgba(1.0 - bg.0, 1.0 - bg.1, 1.0 - bg.2, 0.5);
|
||||||
|
|
||||||
|
let cursor_width = if shell.mode == NvimMode::Insert {
|
||||||
|
char_width / 5.0
|
||||||
|
} else {
|
||||||
|
if double_width {
|
||||||
|
char_width * 2.0
|
||||||
|
} else {
|
||||||
|
char_width
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.rectangle(current_point.0, line_y, cursor_width, line_height);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.move_to(current_point.0, current_point.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if !cell.ch.is_whitespace() {
|
||||||
|
update_font_description(&mut desc, &cell.attrs);
|
||||||
|
|
||||||
|
layout.set_font_description(Some(&desc));
|
||||||
|
buf.clear();
|
||||||
|
buf.push(cell.ch);
|
||||||
|
layout.set_text(&buf, -1);
|
||||||
|
|
||||||
|
// correct layout for double_width chars
|
||||||
|
if double_width {
|
||||||
|
let (dw_width, dw_height) = layout.get_pixel_size();
|
||||||
|
let x_offset = (char_width * 2.0 - dw_width as f64) / 2.0;
|
||||||
|
let y_offset = (line_height - dw_height as f64) / 2.0;
|
||||||
|
ctx.rel_move_to(x_offset, y_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.set_source_rgb(fg.0, fg.1, fg.2);
|
||||||
|
pc::update_layout(ctx, &layout);
|
||||||
|
pc::show_layout(ctx, &layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if cell.attrs.underline || cell.attrs.undercurl {
|
||||||
|
// [TODO]: Current gtk-rs bindings does not provide fontmetrics access
|
||||||
|
// so it is not possible to find right position for underline or undercurl position
|
||||||
|
// > update_font_description(&mut desc, &cell.attrs);
|
||||||
|
// > layout.get_context().unwrap().get_metrics();
|
||||||
|
let top_offset = line_height * 0.9;
|
||||||
|
|
||||||
|
let sp = if let Some(ref sp) = cell.attrs.special {
|
||||||
|
sp
|
||||||
|
} else {
|
||||||
|
&shell.sp_color
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.set_source_rgba(sp.0, sp.1, sp.2, 0.7);
|
||||||
|
if cell.attrs.undercurl {
|
||||||
|
ctx.set_dash(&[4.0, 2.0], 0.0);
|
||||||
|
ctx.set_line_width(2.0);
|
||||||
|
ctx.move_to(current_point.0, line_y + top_offset);
|
||||||
|
ctx.line_to(current_point.0 + char_width, line_y + top_offset);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.set_dash(&[], 0.0);
|
||||||
|
} else if cell.attrs.underline {
|
||||||
|
ctx.set_line_width(1.0);
|
||||||
|
ctx.move_to(current_point.0, line_y + top_offset);
|
||||||
|
ctx.line_to(current_point.0 + char_width, line_y + top_offset);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.move_to(current_point.0 + char_width, current_point.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
line_y += line_height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn update_font_description(desc: &mut FontDescription, attrs: &Attrs) {
|
||||||
|
desc.unset_fields(pango::FONT_MASK_STYLE | pango::FONT_MASK_WEIGHT);
|
||||||
|
if attrs.italic {
|
||||||
|
desc.set_style(pango::Style::Italic);
|
||||||
|
}
|
||||||
|
if attrs.bold {
|
||||||
|
desc.set_weight(pango::Weight::Bold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_char_bounds(shell: &Shell, ctx: &cairo::Context) -> (i32, i32) {
|
||||||
|
let layout = pc::create_layout(ctx);
|
||||||
|
|
||||||
|
let desc = shell.create_pango_font();
|
||||||
|
layout.set_font_description(Some(&desc));
|
||||||
|
layout.set_text("A", -1);
|
||||||
|
|
||||||
|
layout.get_pixel_size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_width(ui: &Ui) {
|
||||||
|
if ui.shell.resize_timer.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = ui.shell.drawing_area.get_allocated_width();
|
||||||
|
let height = ui.shell.drawing_area.get_allocated_height();
|
||||||
|
let request_height = (ui.shell.model.rows as f64 * ui.shell.line_height.unwrap()) as i32;
|
||||||
|
let request_width = (ui.shell.model.columns as f64 * ui.shell.char_width.unwrap()) as i32;
|
||||||
|
|
||||||
|
if width != request_width || height != request_height {
|
||||||
|
let window = ui.window.as_ref().unwrap();
|
||||||
|
let (win_width, win_height) = window.get_size();
|
||||||
|
let h_border = win_width - width;
|
||||||
|
let v_border = win_height - height;
|
||||||
|
window.resize(request_width + h_border, request_height + v_border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(r / 255.0, g / 255.0, b / 255.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gtk_configure_event(_: &DrawingArea, ev: &EventConfigure) -> bool {
|
||||||
|
SHELL!(shell = {
|
||||||
|
let (width, height) = ev.get_size();
|
||||||
|
|
||||||
|
if let Some(timer) = shell.resize_timer {
|
||||||
|
glib::source_remove(timer);
|
||||||
|
}
|
||||||
|
if let Some(line_height) = shell.line_height {
|
||||||
|
if let Some(char_width) = shell.char_width {
|
||||||
|
|
||||||
|
shell.resize_timer = Some(glib::timeout_add(250, move || {
|
||||||
|
SHELL!(shell = {
|
||||||
|
shell.resize_timer = None;
|
||||||
|
|
||||||
|
let rows = (height as f64 / line_height).trunc() as usize;
|
||||||
|
let columns = (width as f64 / char_width).trunc() as usize;
|
||||||
|
if shell.model.rows != rows || shell.model.columns != columns {
|
||||||
|
if let Err(err) = shell.nvim().ui_try_resize(columns as u64, rows as u64) {
|
||||||
|
println!("Error trying resize nvim {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Continue(false)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RedrawEvents for Shell {
|
||||||
|
fn on_cursor_goto(&mut self, row: u64, col: u64) {
|
||||||
|
self.model.set_cursor(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_put(&mut self, text: &str) {
|
||||||
|
self.model.put(text, self.cur_attrs.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_clear(&mut self) {
|
||||||
|
self.model.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_eol_clear(&mut self) {
|
||||||
|
self.model.eol_clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_resize(&mut self, columns: u64, rows: u64) {
|
||||||
|
self.model = UiModel::new(rows, columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_redraw(&self) {
|
||||||
|
self.drawing_area.queue_draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_set_scroll_region(&mut self, top: u64, bot: u64, left: u64, right: u64) {
|
||||||
|
self.model.set_scroll_region(top, bot, left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_scroll(&mut self, count: i64) {
|
||||||
|
self.model.scroll(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_highlight_set(&mut self, attrs: &Vec<(Value, Value)>) {
|
||||||
|
let mut model_attrs = Attrs::new();
|
||||||
|
|
||||||
|
for &(ref key_val, ref val) in attrs {
|
||||||
|
if let &Value::String(ref key) = key_val {
|
||||||
|
match key.as_ref() {
|
||||||
|
"foreground" => {
|
||||||
|
if let &Value::Integer(Integer::U64(fg)) = val {
|
||||||
|
model_attrs.foreground = Some(split_color(fg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"background" => {
|
||||||
|
if let &Value::Integer(Integer::U64(bg)) = val {
|
||||||
|
model_attrs.background = Some(split_color(bg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"special" => {
|
||||||
|
if let &Value::Integer(Integer::U64(bg)) = val {
|
||||||
|
model_attrs.special = Some(split_color(bg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"reverse" => model_attrs.reverse = true,
|
||||||
|
"bold" => model_attrs.bold = true,
|
||||||
|
"italic" => model_attrs.italic = true,
|
||||||
|
"underline" => model_attrs.underline = true,
|
||||||
|
"undercurl" => model_attrs.undercurl = true,
|
||||||
|
attr_key => println!("unknown attribute {}", attr_key),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
panic!("attr key must be string");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cur_attrs = Some(model_attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_update_bg(&mut self, bg: i64) {
|
||||||
|
if bg >= 0 {
|
||||||
|
self.bg_color = split_color(bg as u64);
|
||||||
|
} else {
|
||||||
|
self.bg_color = COLOR_BLACK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_update_fg(&mut self, fg: i64) {
|
||||||
|
if fg >= 0 {
|
||||||
|
self.fg_color = split_color(fg as u64);
|
||||||
|
} else {
|
||||||
|
self.fg_color = COLOR_WHITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_update_sp(&mut self, sp: i64) {
|
||||||
|
if sp >= 0 {
|
||||||
|
self.sp_color = split_color(sp as u64);
|
||||||
|
} else {
|
||||||
|
self.sp_color = COLOR_RED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_mode_change(&mut self, mode: &str) {
|
||||||
|
match mode {
|
||||||
|
"normal" => self.mode = NvimMode::Normal,
|
||||||
|
"insert" => self.mode = NvimMode::Insert,
|
||||||
|
_ => self.mode = NvimMode::Other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_mouse_on(&mut self) {
|
||||||
|
self.mouse_enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_mouse_off(&mut self) {
|
||||||
|
self.mouse_enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GuiApi for Shell {
|
||||||
|
fn set_font(&mut self, font_desc: &str) {
|
||||||
|
self.set_font_desc(font_desc);
|
||||||
|
|
||||||
|
SET.with(|settings| {
|
||||||
|
let mut settings = settings.borrow_mut();
|
||||||
|
settings.set_font_source(settings::FontSource::Rpc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
549
src/ui.rs
549
src/ui.rs
@ -1,27 +1,19 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::string::String;
|
|
||||||
|
|
||||||
use cairo;
|
|
||||||
use pangocairo as pc;
|
|
||||||
use pango;
|
|
||||||
use pango::FontDescription;
|
|
||||||
use gtk;
|
use gtk;
|
||||||
|
use gtk_sys;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{ApplicationWindow, HeaderBar, DrawingArea, ToolButton, Image};
|
use gtk::{ApplicationWindow, HeaderBar, DrawingArea, ToolButton, Image};
|
||||||
use gtk_sys;
|
|
||||||
use gdk::{ModifierType, Event, EventKey, EventConfigure, EventButton, EventMotion, EventType};
|
use gdk::{ModifierType, Event, EventKey, EventConfigure, EventButton, EventMotion, EventType};
|
||||||
use gdk_sys;
|
|
||||||
use glib;
|
use glib;
|
||||||
|
|
||||||
use neovim_lib::{Neovim, NeovimApi, Value, Integer};
|
use neovim_lib::{Neovim, NeovimApi, Value, Integer};
|
||||||
|
|
||||||
use ui_model::{UiModel, Cell, Attrs, Color, COLOR_BLACK, COLOR_WHITE, COLOR_RED};
|
|
||||||
use nvim::{RedrawEvents, GuiApi, ErrorReport};
|
|
||||||
use settings;
|
use settings;
|
||||||
|
use shell::{Shell, NvimMode};
|
||||||
|
use nvim::{RedrawEvents, GuiApi, ErrorReport};
|
||||||
|
|
||||||
use input::{convert_key, keyval_to_input_string};
|
|
||||||
|
|
||||||
const FONT_NAME: &'static str = "DejaVu Sans Mono 12";
|
|
||||||
|
|
||||||
macro_rules! ui_thread_var {
|
macro_rules! ui_thread_var {
|
||||||
($id:ident, $ty:ty, $expr:expr) => (thread_local!(pub static $id: RefCell<$ty> = {
|
($id:ident, $ty:ty, $expr:expr) => (thread_local!(pub static $id: RefCell<$ty> = {
|
||||||
@ -34,74 +26,34 @@ macro_rules! ui_thread_var {
|
|||||||
});)
|
});)
|
||||||
}
|
}
|
||||||
|
|
||||||
ui_thread_var![UI, Ui, Ui::new()];
|
ui_thread_var!(UI, Ui, Ui::new());
|
||||||
ui_thread_var![SET, settings::Settings, settings::Settings::new()];
|
ui_thread_var!(SET, settings::Settings, settings::Settings::new());
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
enum NvimMode {
|
|
||||||
Normal,
|
|
||||||
Insert,
|
|
||||||
Other,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Ui {
|
pub struct Ui {
|
||||||
pub model: UiModel,
|
|
||||||
pub initialized: bool,
|
pub initialized: bool,
|
||||||
nvim: Option<Neovim>,
|
pub window: Option<ApplicationWindow>,
|
||||||
drawing_area: DrawingArea,
|
|
||||||
window: Option<ApplicationWindow>,
|
|
||||||
header_bar: HeaderBar,
|
header_bar: HeaderBar,
|
||||||
cur_attrs: Option<Attrs>,
|
pub shell: Shell,
|
||||||
bg_color: Color,
|
|
||||||
fg_color: Color,
|
|
||||||
sp_color: Color,
|
|
||||||
line_height: Option<f64>,
|
|
||||||
char_width: Option<f64>,
|
|
||||||
resize_timer: Option<glib::SourceId>,
|
|
||||||
mode: NvimMode,
|
|
||||||
mouse_enabled: bool,
|
|
||||||
mouse_pressed: bool,
|
|
||||||
font_desc: FontDescription,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ui {
|
impl Ui {
|
||||||
pub fn new() -> Ui {
|
pub fn new() -> Ui {
|
||||||
Ui {
|
Ui {
|
||||||
model: UiModel::empty(),
|
|
||||||
drawing_area: DrawingArea::new(),
|
|
||||||
window: None,
|
window: None,
|
||||||
header_bar: HeaderBar::new(),
|
header_bar: HeaderBar::new(),
|
||||||
nvim: None,
|
|
||||||
cur_attrs: None,
|
|
||||||
bg_color: COLOR_BLACK,
|
|
||||||
fg_color: COLOR_WHITE,
|
|
||||||
sp_color: COLOR_RED,
|
|
||||||
line_height: None,
|
|
||||||
char_width: None,
|
|
||||||
resize_timer: None,
|
|
||||||
mode: NvimMode::Normal,
|
|
||||||
mouse_enabled: false,
|
|
||||||
mouse_pressed: false,
|
|
||||||
font_desc: FontDescription::from_string(FONT_NAME),
|
|
||||||
initialized: false,
|
initialized: false,
|
||||||
|
shell: Shell::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_nvim(&mut self, nvim: Neovim) {
|
|
||||||
self.nvim = Some(nvim);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn nvim(&mut self) -> &mut Neovim {
|
|
||||||
self.nvim.as_mut().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn destroy(&self) {
|
pub fn destroy(&self) {
|
||||||
self.window.as_ref().unwrap().destroy();
|
self.window.as_ref().unwrap().destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(&mut self, app: >k::Application) {
|
pub fn init(&mut self, app: >k::Application) {
|
||||||
if self.initialized {
|
if self.initialized {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
self.initialized = true;
|
self.initialized = true;
|
||||||
|
|
||||||
@ -124,119 +76,33 @@ impl Ui {
|
|||||||
paste_btn.connect_clicked(|_| edit_paste());
|
paste_btn.connect_clicked(|_| edit_paste());
|
||||||
self.header_bar.pack_start(&paste_btn);
|
self.header_bar.pack_start(&paste_btn);
|
||||||
|
|
||||||
|
self.shell.init();
|
||||||
self.drawing_area.set_size_request(500, 300);
|
|
||||||
self.drawing_area.set_hexpand(true);
|
|
||||||
self.drawing_area.set_vexpand(true);
|
|
||||||
|
|
||||||
self.drawing_area
|
|
||||||
.set_events((gdk_sys::GDK_BUTTON_RELEASE_MASK | gdk_sys::GDK_BUTTON_PRESS_MASK |
|
|
||||||
gdk_sys::GDK_BUTTON_MOTION_MASK)
|
|
||||||
.bits() as i32);
|
|
||||||
self.drawing_area.connect_button_press_event(gtk_button_press);
|
|
||||||
self.drawing_area.connect_button_release_event(gtk_button_release);
|
|
||||||
self.drawing_area.connect_motion_notify_event(gtk_motion_notify);
|
|
||||||
self.drawing_area.connect_draw(gtk_draw);
|
|
||||||
|
|
||||||
self.window = Some(ApplicationWindow::new(app));
|
self.window = Some(ApplicationWindow::new(app));
|
||||||
let window = self.window.as_ref().unwrap();
|
let window = self.window.as_ref().unwrap();
|
||||||
|
|
||||||
window.set_titlebar(Some(&self.header_bar));
|
window.set_titlebar(Some(&self.header_bar));
|
||||||
window.add(&self.drawing_area);
|
window.add(&self.shell.drawing_area);
|
||||||
window.show_all();
|
window.show_all();
|
||||||
window.connect_key_press_event(gtk_key_press);
|
|
||||||
window.connect_delete_event(gtk_delete);
|
window.connect_delete_event(gtk_delete);
|
||||||
window.set_title("Neovim-gtk");
|
window.set_title("Neovim-gtk");
|
||||||
self.drawing_area.connect_configure_event(gtk_configure_event);
|
|
||||||
|
self.shell.add_configure_event();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_pango_font(&self) -> FontDescription {
|
|
||||||
self.font_desc.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_font_desc(&mut self, desc: &str) {
|
|
||||||
self.font_desc = FontDescription::from_string(desc);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn colors<'a>(&'a self, cell: &'a Cell) -> (&'a Color, &'a Color) {
|
|
||||||
let bg = if let Some(ref bg) = cell.attrs.background {
|
|
||||||
bg
|
|
||||||
} else {
|
|
||||||
&self.bg_color
|
|
||||||
};
|
|
||||||
let fg = if let Some(ref fg) = cell.attrs.foreground {
|
|
||||||
fg
|
|
||||||
} else {
|
|
||||||
&self.fg_color
|
|
||||||
};
|
|
||||||
|
|
||||||
if cell.attrs.reverse {
|
|
||||||
(fg, bg)
|
|
||||||
} else {
|
|
||||||
(bg, fg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gtk_button_press(_: &DrawingArea, ev: &EventButton) -> Inhibit {
|
|
||||||
if ev.get_event_type() != EventType::ButtonPress {
|
|
||||||
return Inhibit(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
UI.with(|ui_cell| {
|
|
||||||
let mut ui = ui_cell.borrow_mut();
|
|
||||||
if !ui.mouse_enabled {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mouse_input(&mut *ui, "LeftMouse", ev.get_state(), ev.get_position());
|
|
||||||
});
|
|
||||||
Inhibit(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mouse_input(ui: &mut Ui, input: &str, state: ModifierType, position: (f64, f64)) {
|
|
||||||
if let Some(line_height) = ui.line_height {
|
|
||||||
if let Some(char_width) = ui.char_width {
|
|
||||||
ui.mouse_pressed = true;
|
|
||||||
|
|
||||||
let nvim = ui.nvim();
|
|
||||||
let (x, y) = position;
|
|
||||||
let col = (x / char_width).trunc() as u64;
|
|
||||||
let row = (y / line_height).trunc() as u64;
|
|
||||||
let input_str = format!("{}<{},{}>", keyval_to_input_string(input, state), col, row);
|
|
||||||
nvim.input(&input_str).expect("Can't send mouse input event");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gtk_button_release(_: &DrawingArea, _: &EventButton) -> Inhibit {
|
|
||||||
UI.with(|ui_cell| ui_cell.borrow_mut().mouse_pressed = false);
|
|
||||||
Inhibit(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gtk_motion_notify(_: &DrawingArea, ev: &EventMotion) -> Inhibit {
|
|
||||||
UI.with(|ui_cell| {
|
|
||||||
let mut ui = ui_cell.borrow_mut();
|
|
||||||
if !ui.mouse_enabled || !ui.mouse_pressed {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mouse_input(&mut *ui, "LeftDrag", ev.get_state(), ev.get_position());
|
|
||||||
});
|
|
||||||
Inhibit(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edit_paste() {
|
fn edit_paste() {
|
||||||
UI.with(|ui_cell| {
|
UI.with(|ui_cell| {
|
||||||
let mut ui = ui_cell.borrow_mut();
|
let mut ui = ui_cell.borrow_mut();
|
||||||
|
|
||||||
let paste_command = if ui.mode == NvimMode::Normal {
|
let paste_command = if ui.shell.mode == NvimMode::Normal {
|
||||||
"\"*p"
|
"\"*p"
|
||||||
} else {
|
} else {
|
||||||
"<Esc>\"*pa"
|
"<Esc>\"*pa"
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut nvim = ui.nvim();
|
let mut nvim = ui.shell.nvim();
|
||||||
nvim.input(paste_command).report_err(nvim);
|
nvim.input(paste_command).report_err(nvim);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -245,7 +111,7 @@ fn edit_save_all() {
|
|||||||
UI.with(|ui_cell| {
|
UI.with(|ui_cell| {
|
||||||
let mut ui = ui_cell.borrow_mut();
|
let mut ui = ui_cell.borrow_mut();
|
||||||
|
|
||||||
let mut nvim = ui.nvim();
|
let mut nvim = ui.shell.nvim();
|
||||||
nvim.command(":wa").report_err(nvim);
|
nvim.command(":wa").report_err(nvim);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -266,382 +132,3 @@ fn gtk_delete(_: &ApplicationWindow, _: &Event) -> Inhibit {
|
|||||||
Inhibit(false)
|
Inhibit(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtk_key_press(_: &ApplicationWindow, ev: &EventKey) -> Inhibit {
|
|
||||||
if let Some(input) = convert_key(ev) {
|
|
||||||
UI.with(|ui_cell| {
|
|
||||||
let mut ui = ui_cell.borrow_mut();
|
|
||||||
ui.nvim().input(&input).expect("Error run input command to nvim");
|
|
||||||
});
|
|
||||||
Inhibit(true)
|
|
||||||
} else {
|
|
||||||
Inhibit(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gtk_draw(_: &DrawingArea, ctx: &cairo::Context) -> Inhibit {
|
|
||||||
UI.with(|ui_cell| {
|
|
||||||
let mut ui = ui_cell.borrow_mut();
|
|
||||||
|
|
||||||
let (width, height) = calc_char_bounds(&*ui, ctx);
|
|
||||||
ui.line_height = Some(height as f64);
|
|
||||||
ui.char_width = Some(width as f64);
|
|
||||||
|
|
||||||
draw(&*ui, ctx);
|
|
||||||
request_width(&*ui);
|
|
||||||
});
|
|
||||||
|
|
||||||
Inhibit(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gtk_configure_event(_: &DrawingArea, ev: &EventConfigure) -> bool {
|
|
||||||
UI.with(|ui_cell| {
|
|
||||||
let mut ui = ui_cell.borrow_mut();
|
|
||||||
let (width, height) = ev.get_size();
|
|
||||||
|
|
||||||
if let Some(timer) = ui.resize_timer {
|
|
||||||
glib::source_remove(timer);
|
|
||||||
}
|
|
||||||
if let Some(line_height) = ui.line_height {
|
|
||||||
if let Some(char_width) = ui.char_width {
|
|
||||||
|
|
||||||
ui.resize_timer = Some(glib::timeout_add(250, move || {
|
|
||||||
UI.with(|ui_cell| {
|
|
||||||
let mut ui = ui_cell.borrow_mut();
|
|
||||||
ui.resize_timer = None;
|
|
||||||
|
|
||||||
let rows = (height as f64 / line_height).trunc() as usize;
|
|
||||||
let columns = (width as f64 / char_width).trunc() as usize;
|
|
||||||
if ui.model.rows != rows || ui.model.columns != columns {
|
|
||||||
if let Err(err) = ui.nvim().ui_try_resize(columns as u64, rows as u64) {
|
|
||||||
println!("Error trying resize nvim {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Continue(false)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn draw_joined_rect(ui: &Ui,
|
|
||||||
ctx: &cairo::Context,
|
|
||||||
from_col_idx: usize,
|
|
||||||
col_idx: usize,
|
|
||||||
char_width: f64,
|
|
||||||
line_height: f64,
|
|
||||||
color: &Color) {
|
|
||||||
let current_point = ctx.get_current_point();
|
|
||||||
let rect_width = char_width * (col_idx - from_col_idx) as f64;
|
|
||||||
|
|
||||||
if &ui.bg_color != color {
|
|
||||||
ctx.set_source_rgb(color.0, color.1, color.2);
|
|
||||||
ctx.rectangle(current_point.0, current_point.1, rect_width, line_height);
|
|
||||||
ctx.fill();
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.move_to(current_point.0 + rect_width, current_point.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(ui: &Ui, ctx: &cairo::Context) {
|
|
||||||
ctx.set_source_rgb(ui.bg_color.0, ui.bg_color.1, ui.bg_color.2);
|
|
||||||
ctx.paint();
|
|
||||||
|
|
||||||
let line_height = ui.line_height.unwrap();
|
|
||||||
let char_width = ui.char_width.unwrap();
|
|
||||||
let (row, col) = ui.model.get_cursor();
|
|
||||||
let mut buf = String::with_capacity(4);
|
|
||||||
|
|
||||||
let mut line_y: f64 = 0.0;
|
|
||||||
|
|
||||||
|
|
||||||
let layout = pc::create_layout(ctx);
|
|
||||||
let mut desc = ui.create_pango_font();
|
|
||||||
|
|
||||||
for (line_idx, line) in ui.model.model().iter().enumerate() {
|
|
||||||
ctx.move_to(0.0, line_y);
|
|
||||||
|
|
||||||
// first draw background
|
|
||||||
// here we join same bg color for given line
|
|
||||||
// this gives less drawing primitives
|
|
||||||
let mut from_col_idx = 0;
|
|
||||||
let mut from_bg = None;
|
|
||||||
for (col_idx, cell) in line.iter().enumerate() {
|
|
||||||
let (bg, _) = ui.colors(cell);
|
|
||||||
|
|
||||||
if from_bg.is_none() {
|
|
||||||
from_bg = Some(bg);
|
|
||||||
from_col_idx = col_idx;
|
|
||||||
} else if from_bg != Some(bg) {
|
|
||||||
draw_joined_rect(ui,
|
|
||||||
ctx,
|
|
||||||
from_col_idx,
|
|
||||||
col_idx,
|
|
||||||
char_width,
|
|
||||||
line_height,
|
|
||||||
from_bg.take().unwrap());
|
|
||||||
from_bg = Some(bg);
|
|
||||||
from_col_idx = col_idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
draw_joined_rect(ui,
|
|
||||||
ctx,
|
|
||||||
from_col_idx,
|
|
||||||
line.len(),
|
|
||||||
char_width,
|
|
||||||
line_height,
|
|
||||||
from_bg.take().unwrap());
|
|
||||||
|
|
||||||
ctx.move_to(0.0, line_y);
|
|
||||||
|
|
||||||
for (col_idx, cell) in line.iter().enumerate() {
|
|
||||||
let double_width = line.get(col_idx + 1).map(|c| c.attrs.double_width).unwrap_or(false);
|
|
||||||
let current_point = ctx.get_current_point();
|
|
||||||
|
|
||||||
let (bg, fg) = ui.colors(cell);
|
|
||||||
|
|
||||||
if row == line_idx && col == col_idx {
|
|
||||||
ctx.set_source_rgba(1.0 - bg.0, 1.0 - bg.1, 1.0 - bg.2, 0.5);
|
|
||||||
|
|
||||||
let cursor_width = if ui.mode == NvimMode::Insert {
|
|
||||||
char_width / 5.0
|
|
||||||
} else {
|
|
||||||
if double_width {
|
|
||||||
char_width * 2.0
|
|
||||||
} else {
|
|
||||||
char_width
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ctx.rectangle(current_point.0, line_y, cursor_width, line_height);
|
|
||||||
ctx.fill();
|
|
||||||
ctx.move_to(current_point.0, current_point.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if !cell.ch.is_whitespace() {
|
|
||||||
update_font_description(&mut desc, &cell.attrs);
|
|
||||||
|
|
||||||
layout.set_font_description(Some(&desc));
|
|
||||||
buf.clear();
|
|
||||||
buf.push(cell.ch);
|
|
||||||
layout.set_text(&buf, -1);
|
|
||||||
|
|
||||||
// correct layout for double_width chars
|
|
||||||
if double_width {
|
|
||||||
let (dw_width, dw_height) = layout.get_pixel_size();
|
|
||||||
let x_offset = (char_width * 2.0 - dw_width as f64) / 2.0;
|
|
||||||
let y_offset = (line_height - dw_height as f64) / 2.0;
|
|
||||||
ctx.rel_move_to(x_offset, y_offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.set_source_rgb(fg.0, fg.1, fg.2);
|
|
||||||
pc::update_layout(ctx, &layout);
|
|
||||||
pc::show_layout(ctx, &layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
if cell.attrs.underline || cell.attrs.undercurl {
|
|
||||||
// [TODO]: Current gtk-rs bindings does not provide fontmetrics access
|
|
||||||
// so it is not possible to find right position for underline or undercurl position
|
|
||||||
// > update_font_description(&mut desc, &cell.attrs);
|
|
||||||
// > layout.get_context().unwrap().get_metrics();
|
|
||||||
let top_offset = line_height * 0.9;
|
|
||||||
|
|
||||||
let sp = if let Some(ref sp) = cell.attrs.special {
|
|
||||||
sp
|
|
||||||
} else {
|
|
||||||
&ui.sp_color
|
|
||||||
};
|
|
||||||
|
|
||||||
ctx.set_source_rgba(sp.0, sp.1, sp.2, 0.7);
|
|
||||||
if cell.attrs.undercurl {
|
|
||||||
ctx.set_dash(&[4.0, 2.0], 0.0);
|
|
||||||
ctx.set_line_width(2.0);
|
|
||||||
ctx.move_to(current_point.0, line_y + top_offset);
|
|
||||||
ctx.line_to(current_point.0 + char_width, line_y + top_offset);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.set_dash(&[], 0.0);
|
|
||||||
} else if cell.attrs.underline {
|
|
||||||
ctx.set_line_width(1.0);
|
|
||||||
ctx.move_to(current_point.0, line_y + top_offset);
|
|
||||||
ctx.line_to(current_point.0 + char_width, line_y + top_offset);
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.move_to(current_point.0 + char_width, current_point.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
line_y += line_height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn update_font_description(desc: &mut FontDescription, attrs: &Attrs) {
|
|
||||||
desc.unset_fields(pango::FONT_MASK_STYLE | pango::FONT_MASK_WEIGHT);
|
|
||||||
if attrs.italic {
|
|
||||||
desc.set_style(pango::Style::Italic);
|
|
||||||
}
|
|
||||||
if attrs.bold {
|
|
||||||
desc.set_weight(pango::Weight::Bold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calc_char_bounds(ui: &Ui, ctx: &cairo::Context) -> (i32, i32) {
|
|
||||||
let layout = pc::create_layout(ctx);
|
|
||||||
|
|
||||||
let desc = ui.create_pango_font();
|
|
||||||
layout.set_font_description(Some(&desc));
|
|
||||||
layout.set_text("A", -1);
|
|
||||||
|
|
||||||
layout.get_pixel_size()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn request_width(ui: &Ui) {
|
|
||||||
if ui.resize_timer.is_some() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let width = ui.drawing_area.get_allocated_width();
|
|
||||||
let height = ui.drawing_area.get_allocated_height();
|
|
||||||
let request_height = (ui.model.rows as f64 * ui.line_height.unwrap()) as i32;
|
|
||||||
let request_width = (ui.model.columns as f64 * ui.char_width.unwrap()) as i32;
|
|
||||||
|
|
||||||
if width != request_width || height != request_height {
|
|
||||||
let window = ui.window.as_ref().unwrap();
|
|
||||||
let (win_width, win_height) = window.get_size();
|
|
||||||
let h_border = win_width - width;
|
|
||||||
let v_border = win_height - height;
|
|
||||||
window.resize(request_width + h_border, request_height + v_border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GuiApi for Ui {
|
|
||||||
fn set_font(&mut self, font_desc: &str) {
|
|
||||||
self.set_font_desc(font_desc);
|
|
||||||
|
|
||||||
SET.with(|settings| {
|
|
||||||
let mut settings = settings.borrow_mut();
|
|
||||||
settings.set_font_source(settings::FontSource::Rpc);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RedrawEvents for Ui {
|
|
||||||
fn on_cursor_goto(&mut self, row: u64, col: u64) {
|
|
||||||
self.model.set_cursor(row, col);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_put(&mut self, text: &str) {
|
|
||||||
self.model.put(text, self.cur_attrs.as_ref());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_clear(&mut self) {
|
|
||||||
self.model.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_eol_clear(&mut self) {
|
|
||||||
self.model.eol_clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_resize(&mut self, columns: u64, rows: u64) {
|
|
||||||
self.model = UiModel::new(rows, columns);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_redraw(&self) {
|
|
||||||
self.drawing_area.queue_draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_set_scroll_region(&mut self, top: u64, bot: u64, left: u64, right: u64) {
|
|
||||||
self.model.set_scroll_region(top, bot, left, right);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_scroll(&mut self, count: i64) {
|
|
||||||
self.model.scroll(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_highlight_set(&mut self, attrs: &Vec<(Value, Value)>) {
|
|
||||||
let mut model_attrs = Attrs::new();
|
|
||||||
|
|
||||||
for &(ref key_val, ref val) in attrs {
|
|
||||||
if let &Value::String(ref key) = key_val {
|
|
||||||
match key.as_ref() {
|
|
||||||
"foreground" => {
|
|
||||||
if let &Value::Integer(Integer::U64(fg)) = val {
|
|
||||||
model_attrs.foreground = Some(split_color(fg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"background" => {
|
|
||||||
if let &Value::Integer(Integer::U64(bg)) = val {
|
|
||||||
model_attrs.background = Some(split_color(bg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"special" => {
|
|
||||||
if let &Value::Integer(Integer::U64(bg)) = val {
|
|
||||||
model_attrs.special = Some(split_color(bg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"reverse" => model_attrs.reverse = true,
|
|
||||||
"bold" => model_attrs.bold = true,
|
|
||||||
"italic" => model_attrs.italic = true,
|
|
||||||
"underline" => model_attrs.underline = true,
|
|
||||||
"undercurl" => model_attrs.undercurl = true,
|
|
||||||
attr_key => println!("unknown attribute {}", attr_key),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
panic!("attr key must be string");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.cur_attrs = Some(model_attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_update_bg(&mut self, bg: i64) {
|
|
||||||
if bg >= 0 {
|
|
||||||
self.bg_color = split_color(bg as u64);
|
|
||||||
} else {
|
|
||||||
self.bg_color = COLOR_BLACK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_update_fg(&mut self, fg: i64) {
|
|
||||||
if fg >= 0 {
|
|
||||||
self.fg_color = split_color(fg as u64);
|
|
||||||
} else {
|
|
||||||
self.fg_color = COLOR_WHITE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_update_sp(&mut self, sp: i64) {
|
|
||||||
if sp >= 0 {
|
|
||||||
self.sp_color = split_color(sp as u64);
|
|
||||||
} else {
|
|
||||||
self.sp_color = COLOR_RED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_mode_change(&mut self, mode: &str) {
|
|
||||||
match mode {
|
|
||||||
"normal" => self.mode = NvimMode::Normal,
|
|
||||||
"insert" => self.mode = NvimMode::Insert,
|
|
||||||
_ => self.mode = NvimMode::Other,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_mouse_on(&mut self) {
|
|
||||||
self.mouse_enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_mouse_off(&mut self) {
|
|
||||||
self.mouse_enabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(r / 255.0, g / 255.0, b / 255.0)
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user