neovim-gtk/src/ui.rs

374 lines
11 KiB
Rust
Raw Normal View History

2018-01-23 20:22:19 +00:00
use std::cell::{Ref, RefCell, RefMut};
use std::{env, thread};
use std::rc::Rc;
use std::sync::Arc;
2016-03-31 16:19:08 +00:00
2018-01-23 20:22:19 +00:00
use gdk;
2016-03-16 15:25:25 +00:00
use gtk;
use gtk_sys;
2016-03-16 15:25:25 +00:00
use gtk::prelude::*;
2018-01-23 20:22:19 +00:00
use gtk::{AboutDialog, ApplicationWindow, HeaderBar, Image, SettingsExt, ToolButton};
use gio::prelude::*;
use gio::{Menu, MenuExt, MenuItem, SimpleAction};
2018-01-23 20:22:19 +00:00
use toml;
2018-01-23 20:22:19 +00:00
use settings::{Settings, SettingsLoader};
use shell::{self, Shell, ShellOptions};
2017-04-01 10:00:14 +00:00
use shell_dlg;
2017-05-13 14:31:19 +00:00
use project::Projects;
use plug_manager;
2017-03-13 15:03:32 +00:00
2017-11-05 18:07:02 +00:00
macro_rules! clone {
(@param _) => ( _ );
(@param $x:ident) => ( $x );
($($n:ident),+ => move || $body:expr) => (
{
$( let $n = $n.clone(); )+
move || $body
}
);
($($n:ident),+ => move |$($p:tt),+| $body:expr) => (
{
$( let $n = $n.clone(); )+
move |$(clone!(@param $p),)+| $body
}
);
}
2018-02-16 09:57:26 +00:00
const DEFAULT_WIDTH: i32 = 800;
const DEFAULT_HEIGHT: i32 = 600;
pub struct Ui {
initialized: bool,
comps: Arc<UiMutex<Components>>,
settings: Rc<RefCell<Settings>>,
shell: Rc<RefCell<Shell>>,
2017-05-13 14:31:19 +00:00
projects: Rc<RefCell<Projects>>,
2017-10-15 19:50:59 +00:00
plug_manager: Arc<UiMutex<plug_manager::Manager>>,
}
pub struct Components {
window: Option<ApplicationWindow>,
2018-01-23 20:22:19 +00:00
window_state: WindowState,
2017-05-13 14:31:19 +00:00
open_btn: ToolButton,
2016-03-19 10:27:39 +00:00
}
2016-03-16 15:25:25 +00:00
impl Components {
fn new() -> Components {
let save_image =
Image::new_from_icon_name("document-open", gtk_sys::GTK_ICON_SIZE_SMALL_TOOLBAR as i32);
2017-05-13 14:31:19 +00:00
Components {
2017-05-13 14:31:19 +00:00
open_btn: ToolButton::new(Some(&save_image), "Open"),
2017-03-06 13:58:10 +00:00
window: None,
2018-01-23 20:22:19 +00:00
window_state: WindowState::load(),
2016-03-19 10:27:39 +00:00
}
2016-03-16 15:25:25 +00:00
}
2017-03-16 10:18:13 +00:00
pub fn close_window(&self) {
2017-03-06 13:58:10 +00:00
self.window.as_ref().unwrap().destroy();
}
pub fn window(&self) -> &ApplicationWindow {
self.window.as_ref().unwrap()
}
}
impl Ui {
pub fn new(options: ShellOptions) -> Ui {
let plug_manager = plug_manager::Manager::new();
let plug_manager = Arc::new(UiMutex::new(plug_manager));
let comps = Arc::new(UiMutex::new(Components::new()));
let settings = Rc::new(RefCell::new(Settings::new()));
let shell = Rc::new(RefCell::new(Shell::new(settings.clone(), options)));
settings.borrow_mut().set_shell(Rc::downgrade(&shell));
2017-05-13 14:31:19 +00:00
let projects = Projects::new(&comps.borrow().open_btn, shell.clone());
Ui {
initialized: false,
2017-05-13 14:31:19 +00:00
comps,
shell,
settings,
projects,
2017-10-15 19:50:59 +00:00
plug_manager,
}
2017-03-16 10:18:13 +00:00
}
2018-02-16 09:57:26 +00:00
pub fn init(&mut self, app: &gtk::Application, restore_win_state: bool) {
if self.initialized {
return;
2017-03-14 20:12:31 +00:00
}
self.initialized = true;
let mut settings = self.settings.borrow_mut();
settings.init();
2018-01-23 20:22:19 +00:00
let window = ApplicationWindow::new(app);
2018-01-23 20:22:19 +00:00
{
// initialize window from comps
// borrowing of comps must be leaved
// for event processing
let mut comps = self.comps.borrow_mut();
self.shell.borrow_mut().init();
comps.window = Some(window.clone());
let prefer_dark_theme = env::var("NVIM_GTK_PREFER_DARK_THEME")
.map(|opt| opt.trim() == "1")
.unwrap_or(false);
if prefer_dark_theme {
if let Some(settings) = window.get_settings() {
settings.set_property_gtk_application_prefer_dark_theme(true);
}
}
2017-05-13 14:31:19 +00:00
2018-01-23 20:22:19 +00:00
// Client side decorations including the toolbar are disabled via NVIM_GTK_NO_HEADERBAR=1
let use_header_bar = env::var("NVIM_GTK_NO_HEADERBAR")
.map(|opt| opt.trim() != "1")
.unwrap_or(true);
2016-03-17 13:58:21 +00:00
2018-01-23 20:22:19 +00:00
if app.prefers_app_menu() || use_header_bar {
2018-02-02 09:17:19 +00:00
self.create_main_menu(app, &window);
2018-01-23 20:22:19 +00:00
}
2016-03-17 13:58:21 +00:00
2018-01-23 20:22:19 +00:00
if use_header_bar {
let header_bar = HeaderBar::new();
let projects = self.projects.clone();
header_bar.pack_start(&comps.open_btn);
comps
.open_btn
.connect_clicked(move |_| projects.borrow_mut().show());
let save_image = Image::new_from_icon_name(
"document-save",
gtk_sys::GTK_ICON_SIZE_SMALL_TOOLBAR as i32,
);
let save_btn = ToolButton::new(Some(&save_image), "Save");
let shell = self.shell.clone();
save_btn.connect_clicked(move |_| shell.borrow_mut().edit_save_all());
header_bar.pack_start(&save_btn);
let paste_image = Image::new_from_icon_name(
"edit-paste",
gtk_sys::GTK_ICON_SIZE_SMALL_TOOLBAR as i32,
);
let paste_btn = ToolButton::new(Some(&paste_image), "Paste");
let shell = self.shell.clone();
paste_btn.connect_clicked(move |_| shell.borrow_mut().edit_paste());
header_bar.pack_start(&paste_btn);
header_bar.set_show_close_button(true);
window.set_titlebar(Some(&header_bar));
}
2016-03-16 15:25:25 +00:00
2018-02-16 09:57:26 +00:00
if restore_win_state {
if comps.window_state.is_maximized {
window.maximize();
}
window.set_default_size(
comps.window_state.current_width,
comps.window_state.current_height,
);
} else {
window.set_default_size(DEFAULT_WIDTH, DEFAULT_HEIGHT);
2018-01-23 20:22:19 +00:00
}
}
2018-01-23 20:22:19 +00:00
let comps_ref = self.comps.clone();
window.connect_size_allocate(move |window, _| {
gtk_window_size_allocate(window, &mut *comps_ref.borrow_mut())
});
2018-01-23 20:22:19 +00:00
let comps_ref = self.comps.clone();
window.connect_window_state_event(move |_, event| {
gtk_window_state_event(event, &mut *comps_ref.borrow_mut());
Inhibit(false)
});
2017-03-06 13:58:10 +00:00
2018-01-23 20:22:19 +00:00
let comps_ref = self.comps.clone();
window.connect_destroy(move |_| {
comps_ref.borrow().window_state.save();
});
2017-03-06 21:05:48 +00:00
let shell = self.shell.borrow();
2017-05-27 16:50:25 +00:00
window.add(&**shell);
window.show_all();
window.set_title("NeovimGtk");
let comps_ref = self.comps.clone();
let shell_ref = self.shell.clone();
window.connect_delete_event(move |_, _| gtk_delete(&*comps_ref, &*shell_ref));
2017-05-27 16:50:25 +00:00
shell.grab_focus();
let comps_ref = self.comps.clone();
shell.set_detach_cb(Some(move || {
2017-07-09 11:05:55 +00:00
let comps_ref = comps_ref.clone();
gtk::idle_add(move || {
comps_ref.borrow().close_window();
Continue(false)
});
}));
2017-10-15 19:50:59 +00:00
let state_ref = self.shell.borrow().state.clone();
let plug_manager_ref = self.plug_manager.clone();
shell.set_nvim_started_cb(Some(move || {
2018-01-23 20:22:19 +00:00
plug_manager_ref
.borrow_mut()
.init_nvim_client(state_ref.borrow().nvim_clone());
2017-10-15 19:50:59 +00:00
}));
2017-03-06 21:05:48 +00:00
}
2017-03-31 20:19:50 +00:00
2018-02-02 09:17:19 +00:00
fn create_main_menu(&self, app: &gtk::Application, window: &gtk::ApplicationWindow) {
2017-11-05 18:07:02 +00:00
let plug_manager = self.plug_manager.clone();
2017-03-31 20:19:50 +00:00
let menu = Menu::new();
let section = Menu::new();
section.append_item(&MenuItem::new("New Window", "app.new-window"));
menu.append_section(None, &section);
2017-11-05 18:07:02 +00:00
let section = Menu::new();
section.append_item(&MenuItem::new("Plugins", "app.Plugins"));
section.append_item(&MenuItem::new("About", "app.HelpAbout"));
menu.append_section(None, &section);
2017-03-31 20:19:50 +00:00
menu.freeze();
2017-05-31 14:14:58 +00:00
app.set_app_menu(Some(&menu));
2017-03-31 20:19:50 +00:00
2017-11-05 18:07:02 +00:00
let plugs_action = SimpleAction::new("Plugins", None);
plugs_action.connect_activate(
2018-02-02 09:17:19 +00:00
clone!(window => move |_, _| plug_manager::Ui::new(&plug_manager).show(&window)),
2017-11-05 18:07:02 +00:00
);
2017-03-31 20:19:50 +00:00
let about_action = SimpleAction::new("HelpAbout", None);
2018-02-02 09:17:19 +00:00
about_action.connect_activate(clone!(window => move |_, _| on_help_about(&window)));
2017-03-31 20:19:50 +00:00
about_action.set_enabled(true);
2017-11-05 18:07:02 +00:00
2017-03-31 20:19:50 +00:00
app.add_action(&about_action);
2017-11-05 18:07:02 +00:00
app.add_action(&plugs_action);
2017-03-31 20:19:50 +00:00
}
}
2018-02-02 09:17:19 +00:00
fn on_help_about(window: &gtk::ApplicationWindow) {
let about = AboutDialog::new();
2018-02-02 09:17:19 +00:00
about.set_transient_for(window);
about.set_program_name("NeovimGtk");
about.set_version(env!("CARGO_PKG_VERSION"));
about.set_logo_icon_name("org.daa.NeovimGtk");
about.set_authors(&[env!("CARGO_PKG_AUTHORS")]);
about.set_comments(
format!(
"Build on top of neovim\n\
2018-01-23 20:22:19 +00:00
Minimum supported neovim version: {}",
shell::MINIMUM_SUPPORTED_NVIM_VERSION
).as_str(),
);
2017-03-31 20:19:50 +00:00
about.connect_response(|about, _| about.destroy());
about.show();
}
fn gtk_delete(comps: &UiMutex<Components>, shell: &RefCell<Shell>) -> Inhibit {
if !shell.borrow().is_nvim_initialized() {
return Inhibit(false);
}
Inhibit(if shell_dlg::can_close_window(comps, shell) {
let comps = comps.borrow();
comps.close_window();
shell.borrow_mut().detach_ui();
false
} else {
true
})
2016-05-04 08:52:57 +00:00
}
2018-01-23 20:22:19 +00:00
fn gtk_window_size_allocate(app_window: &gtk::ApplicationWindow, comps: &mut Components) {
if !app_window.is_maximized() {
let (current_width, current_height) = app_window.get_size();
comps.window_state.current_width = current_width;
comps.window_state.current_height = current_height;
}
}
fn gtk_window_state_event(event: &gdk::EventWindowState, comps: &mut Components) {
comps.window_state.is_maximized = event
.get_new_window_state()
.contains(gdk::WindowState::MAXIMIZED);
}
#[derive(Serialize, Deserialize)]
struct WindowState {
current_width: i32,
current_height: i32,
is_maximized: bool,
}
impl WindowState {
pub fn new() -> Self {
WindowState {
2018-02-16 09:57:26 +00:00
current_width: DEFAULT_WIDTH,
current_height: DEFAULT_HEIGHT,
2018-01-23 20:22:19 +00:00
is_maximized: false,
}
}
}
impl SettingsLoader for WindowState {
const SETTINGS_FILE: &'static str = "window.toml";
fn empty() -> WindowState {
WindowState::new()
}
fn from_str(s: &str) -> Result<Self, String> {
toml::from_str(&s).map_err(|e| format!("{}", e))
}
}
2017-03-26 11:34:38 +00:00
pub struct UiMutex<T: ?Sized> {
2017-09-14 13:29:03 +00:00
thread: thread::ThreadId,
2017-03-26 11:34:38 +00:00
data: RefCell<T>,
}
unsafe impl<T: ?Sized> Send for UiMutex<T> {}
unsafe impl<T: ?Sized> Sync for UiMutex<T> {}
2017-03-26 11:34:38 +00:00
impl<T> UiMutex<T> {
pub fn new(t: T) -> UiMutex<T> {
UiMutex {
2017-09-14 13:29:03 +00:00
thread: thread::current().id(),
data: RefCell::new(t),
}
2017-03-26 11:34:38 +00:00
}
}
impl<T: ?Sized> UiMutex<T> {
pub fn borrow(&self) -> Ref<T> {
self.assert_ui_thread();
2017-03-26 11:34:38 +00:00
self.data.borrow()
}
pub fn borrow_mut(&self) -> RefMut<T> {
self.assert_ui_thread();
2017-03-26 11:34:38 +00:00
self.data.borrow_mut()
}
#[inline]
fn assert_ui_thread(&self) {
2017-09-14 13:29:03 +00:00
if thread::current().id() != self.thread {
panic!("Can access to UI only from main thread");
2017-03-26 11:34:38 +00:00
}
}
}