diff --git a/resources/side-panel.ui b/resources/side-panel.ui
new file mode 100644
index 0000000..2e595cb
--- /dev/null
+++ b/resources/side-panel.ui
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/file_browser.rs b/src/file_browser.rs
new file mode 100644
index 0000000..04f0f8b
--- /dev/null
+++ b/src/file_browser.rs
@@ -0,0 +1,540 @@
+use std::cell::RefCell;
+use std::cmp::Ordering;
+use std::io;
+use std::fs;
+use std::fs::DirEntry;
+use std::path::{Component, Path, PathBuf};
+use std::rc::Rc;
+use std::ops::Deref;
+
+use gio;
+use gio::prelude::*;
+use gtk;
+use gtk::MenuExt;
+use gtk::prelude::*;
+
+use neovim_lib::NeovimApi;
+
+use nvim::{ErrorReport, NeovimClient, NeovimRef};
+use shell;
+
+const ICON_FOLDER_CLOSED: &str = "folder-symbolic";
+const ICON_FOLDER_OPEN: &str = "folder-open-symbolic";
+const ICON_FILE: &str = "text-x-generic-symbolic";
+
+struct Components {
+ dir_list_model: gtk::TreeStore,
+ dir_list: gtk::ComboBox,
+ context_menu: gtk::Menu,
+ show_hidden_checkbox: gtk::CheckMenuItem,
+ cd_action: gio::SimpleAction,
+}
+
+struct State {
+ current_dir: String,
+ show_hidden: bool,
+ selected_path: Option,
+}
+
+pub struct FileBrowserWidget {
+ store: gtk::TreeStore,
+ tree: gtk::TreeView,
+ widget: gtk::Box,
+ nvim: Option>,
+ comps: Components,
+ state: Rc>,
+}
+
+impl Deref for FileBrowserWidget {
+ type Target = gtk::Box;
+
+ fn deref(&self) -> >k::Box {
+ &self.widget
+ }
+}
+
+#[derive(Copy, Clone, Debug)]
+enum FileType {
+ File,
+ Dir,
+}
+
+#[allow(dead_code)]
+enum Column {
+ Filename,
+ Path,
+ FileType,
+ IconName,
+}
+
+impl FileBrowserWidget {
+ pub fn new() -> Self {
+ let builder = gtk::Builder::new_from_string(include_str!("../resources/side-panel.ui"));
+ let widget: gtk::Box = builder.get_object("file_browser").unwrap();
+ let tree: gtk::TreeView = builder.get_object("file_browser_tree_view").unwrap();
+ let store: gtk::TreeStore = builder.get_object("file_browser_tree_store").unwrap();
+ let dir_list_model: gtk::TreeStore = builder.get_object("dir_list_model").unwrap();
+ let dir_list: gtk::ComboBox = builder.get_object("dir_list").unwrap();
+ let context_menu: gtk::Menu = builder.get_object("file_browser_context_menu").unwrap();
+ let show_hidden_checkbox: gtk::CheckMenuItem = builder
+ .get_object("file_browser_show_hidden_checkbox")
+ .unwrap();
+
+ let file_browser = FileBrowserWidget {
+ store,
+ tree,
+ widget,
+ nvim: None,
+ comps: Components {
+ dir_list_model,
+ dir_list,
+ context_menu,
+ show_hidden_checkbox,
+ cd_action: gio::SimpleAction::new("cd", None),
+ },
+ state: Rc::new(RefCell::new(State {
+ current_dir: "".to_owned(),
+ show_hidden: false,
+ selected_path: None,
+ })),
+ };
+ file_browser
+ }
+
+ fn nvim(&self) -> Option {
+ self.nvim.as_ref().unwrap().nvim()
+ }
+
+ pub fn init(&mut self, shell_state: &shell::State) {
+ // Initialize values.
+ let nvim = shell_state.nvim_clone();
+ self.nvim = Some(nvim);
+ let dir = get_current_dir(&mut self.nvim().unwrap());
+ update_dir_list(&dir, &self.comps.dir_list_model, &self.comps.dir_list);
+ self.state.borrow_mut().current_dir = dir;
+
+ // Populate tree.
+ tree_reload(&self.store, &self.state.borrow());
+
+ let store = &self.store;
+ let state_ref = &self.state;
+ self.tree.connect_test_expand_row(clone!(store, state_ref => move |_, iter, _| {
+ store.set(&iter, &[Column::IconName as u32], &[&ICON_FOLDER_OPEN]);
+ // We cannot recursively populate all directories. Instead, we have prepared a single
+ // empty child entry for all non-empty directories, so the row will be expandable. Now,
+ // when a directory is expanded, populate its children.
+ let state = state_ref.borrow();
+ if let Some(child) = store.iter_children(iter) {
+ let filename = store.get_value(&child, Column::Filename as i32);
+ if filename.get::<&str>().is_none() {
+ store.remove(&child);
+ let dir_value = store.get_value(&iter, Column::Path as i32);
+ if let Some(dir) = dir_value.get() {
+ populate_tree_nodes(&store, &state, dir, Some(iter));
+ }
+ } else {
+ // This directory is already populated, i.e. it has been expanded and collapsed
+ // again. Rows further down the tree might have been silently collapsed without
+ // getting an event. Update their folder icon.
+ let mut tree_path = store.get_path(&child).unwrap();
+ while let Some(iter) = store.get_iter(&tree_path) {
+ tree_path.next();
+ let file_type = store
+ .get_value(&iter, Column::FileType as i32)
+ .get::();
+ if file_type == Some(FileType::Dir as u8) {
+ store.set(&iter, &[Column::IconName as u32], &[&ICON_FOLDER_CLOSED]);
+ }
+ }
+ }
+ }
+ Inhibit(false)
+ }));
+
+ self.tree.connect_row_collapsed(clone!(store => move |_, iter, _| {
+ store.set(&iter, &[Column::IconName as u32], &[&ICON_FOLDER_CLOSED]);
+ }));
+
+ // Further initialization.
+ self.init_actions();
+ self.init_subscriptions(shell_state);
+ self.connect_events();
+ }
+
+ fn init_actions(&self) {
+ let actions = gio::SimpleActionGroup::new();
+
+ let store = &self.store;
+ let state_ref = &self.state;
+ let nvim_ref = self.nvim.as_ref().unwrap();
+
+ let reload_action = gio::SimpleAction::new("reload", None);
+ reload_action.connect_activate(clone!(store, state_ref => move |_, _| {
+ tree_reload(&store, &state_ref.borrow());
+ }));
+ actions.add_action(&reload_action);
+
+ let cd_action = &self.comps.cd_action;
+ cd_action.connect_activate(clone!(state_ref, nvim_ref => move |_, _| {
+ let mut nvim = nvim_ref.nvim().unwrap();
+ if let Some(ref path) = state_ref.borrow().selected_path {
+ nvim.set_current_dir(&path).report_err();
+ }
+ }));
+ actions.add_action(cd_action);
+
+ self.comps
+ .context_menu
+ .insert_action_group("filebrowser", &actions);
+ }
+
+ fn init_subscriptions(&self, shell_state: &shell::State) {
+ // Always set the current working directory as the root of the file browser.
+ let store = &self.store;
+ let state_ref = &self.state;
+ let dir_list_model = &self.comps.dir_list_model;
+ let dir_list = &self.comps.dir_list;
+ shell_state.subscribe(
+ "DirChanged",
+ &["getcwd()"],
+ clone!(store, state_ref, dir_list_model, dir_list => move |args| {
+ let dir = args.into_iter().next().unwrap();
+ let mut state = state_ref.borrow_mut();
+ if dir != *state.current_dir {
+ update_dir_list(&dir, &dir_list_model, &dir_list);
+ state.current_dir = dir;
+ tree_reload(&store, &state);
+ }
+ }),
+ );
+
+ // Reveal the file of an entered buffer in the file browser and select the entry.
+ let tree = &self.tree;
+ let subscription = shell_state.subscribe(
+ "BufEnter",
+ &["getcwd()", "expand('%:p')"],
+ clone!(tree, store => move |args| {
+ let mut args_iter = args.into_iter();
+ let dir = args_iter.next().unwrap();
+ let file_path = args_iter.next().unwrap();
+ let could_reveal =
+ if let Ok(rel_path) = Path::new(&file_path).strip_prefix(&Path::new(&dir)) {
+ reveal_path_in_tree(&store, &tree, &rel_path)
+ } else {
+ false
+ };
+ if !could_reveal {
+ tree.get_selection().unselect_all();
+ }
+ }),
+ );
+ shell_state.run_now(&subscription);
+ }
+
+ fn connect_events(&self) {
+ // Open file / go to dir, when user clicks on an entry.
+ let store = &self.store;
+ let nvim_ref = self.nvim.as_ref().unwrap();
+ self.tree.connect_row_activated(clone!(store, nvim_ref => move |tree, path, _| {
+ let mut nvim = nvim_ref.nvim().unwrap();
+ let iter = store.get_iter(path).unwrap();
+ let file_type = store
+ .get_value(&iter, Column::FileType as i32)
+ .get::()
+ .unwrap();
+ let file_path = store
+ .get_value(&iter, Column::Path as i32)
+ .get::()
+ .unwrap();
+ if file_type == FileType::Dir as u8 {
+ let expanded = tree.row_expanded(path);
+ if expanded {
+ tree.collapse_row(path);
+ } else {
+ tree.expand_row(path, false);
+ }
+ } else {
+ // FileType::File
+ let dir = get_current_dir(&mut nvim);
+ let dir = Path::new(&dir);
+ let file_path = if let Some(rel_path) = Path::new(&file_path)
+ .strip_prefix(&dir)
+ .ok()
+ .and_then(|p| p.to_str())
+ {
+ rel_path
+ } else {
+ &file_path
+ };
+ nvim.command(&format!(":e {}", file_path)).report_err();
+ }
+ }));
+
+ // Connect directory list.
+ let nvim_ref = self.nvim.as_ref().unwrap();
+ self.comps.dir_list.connect_changed(clone!(nvim_ref => move |dir_list| {
+ if let Some(iter) = dir_list.get_active_iter() {
+ let model = dir_list.get_model().unwrap();
+ if let Some(dir) = model.get_value(&iter, 2).get::<&str>() {
+ let mut nvim = nvim_ref.nvim().unwrap();
+ nvim.set_current_dir(dir).report_err();
+ }
+ }
+ }));
+
+ let store = &self.store;
+ let state_ref = &self.state;
+ let context_menu = &self.comps.context_menu;
+ let cd_action = &self.comps.cd_action;
+ self.tree.connect_button_press_event(
+ clone!(store, state_ref, context_menu, cd_action => move |tree, ev_btn| {
+ // Open context menu on right click.
+ if ev_btn.get_button() == 3 {
+ context_menu.popup_at_pointer(&**ev_btn);
+ let (pos_x, pos_y) = ev_btn.get_position();
+ let iter = tree
+ .get_path_at_pos(pos_x as i32, pos_y as i32)
+ .and_then(|(path, _, _, _)| path)
+ .and_then(|path| store.get_iter(&path));
+ let file_type = iter
+ .as_ref()
+ .and_then(|iter| {
+ store
+ .get_value(&iter, Column::FileType as i32)
+ .get::()
+ });
+ // Enable the "Go To Directory" action only if the user clicked on a folder.
+ cd_action.set_enabled(file_type == Some(FileType::Dir as u8));
+ let path = iter
+ .and_then(|iter| {
+ store
+ .get_value(&iter, Column::Path as i32)
+ .get::()
+ });
+ state_ref.borrow_mut().selected_path = path;
+ }
+ Inhibit(false)
+ }),
+ );
+
+ // Show / hide hidden files when corresponding menu item is toggled.
+ self.comps.show_hidden_checkbox.connect_toggled(clone!(state_ref, store => move |ev| {
+ let mut state = state_ref.borrow_mut();
+ state.show_hidden = ev.get_active();
+ tree_reload(&store, &state);
+ }));
+ }
+}
+
+/// Compare function for dir entries.
+///
+/// Sorts directories above files.
+fn cmp_dirs_first(lhs: &DirEntry, rhs: &DirEntry) -> io::Result {
+ let lhs_metadata = fs::metadata(lhs.path())?;
+ let rhs_metadata = fs::metadata(rhs.path())?;
+ if lhs_metadata.file_type() == rhs_metadata.file_type() {
+ Ok(lhs.path().cmp(&rhs.path()))
+ } else {
+ if lhs_metadata.is_dir() {
+ Ok(Ordering::Less)
+ } else {
+ Ok(Ordering::Greater)
+ }
+ }
+}
+
+/// Clears an repopulate the entire tree.
+fn tree_reload(store: >k::TreeStore, state: &State) {
+ let dir = &state.current_dir;
+ store.clear();
+ populate_tree_nodes(store, state, dir, None);
+}
+
+/// Updates the dirctory list on top of the file browser.
+///
+/// The list represents the path the the current working directory. If the new cwd is a parent of
+/// the old one, the list is kept and only the active entry is updated. Otherwise, the list is
+/// replaced with the new path and the last entry is marked active.
+fn update_dir_list(dir: &str, dir_list_model: >k::TreeStore, dir_list: >k::ComboBox) {
+ // The current working directory path.
+ let complete_path = Path::new(dir).canonicalize().unwrap();
+ let mut path = PathBuf::new();
+ let mut components = complete_path.components();
+ let mut next = components.next();
+
+ // Iterator over existing dir_list model.
+ let mut dir_list_iter = dir_list_model.get_iter_first();
+
+ // Whether existing entries up to the current position of dir_list_iter are a prefix of the
+ // new current working directory path.
+ let mut is_prefix = true;
+
+ // Iterate over components of the cwd. Simultaneously move dir_list_iter forward.
+ while let Some(dir) = next {
+ next = components.next();
+ let dir_name = &*dir.as_os_str().to_string_lossy();
+ // Assemble path up to current component.
+ path.push(Path::new(&dir));
+ let path_str = path.to_str().unwrap_or_else(|| {
+ error!(
+ "Could not convert path to string: {}\n
+ Directory chooser will not work for that entry.",
+ path.to_string_lossy()
+ );
+ ""
+ });
+ // Use the current entry of dir_list, if any, otherwise append a new one.
+ let current_iter = dir_list_iter.unwrap_or_else(|| dir_list_model.append(None));
+ // Check if the current entry is still part of the new cwd.
+ if is_prefix && dir_list_model.get_value(¤t_iter, 0).get::<&str>() != Some(&dir_name)
+ {
+ is_prefix = false;
+ }
+ if next.is_some() {
+ // Update dir_list entry.
+ dir_list_model.set(
+ ¤t_iter,
+ &[0, 1, 2],
+ &[&dir_name, &ICON_FOLDER_CLOSED, &path_str],
+ );
+ } else {
+ // We reached the last component of the new cwd path. Set the active entry of dir_list
+ // to this one.
+ dir_list_model.set(
+ ¤t_iter,
+ &[0, 1, 2],
+ &[&dir_name, &ICON_FOLDER_OPEN, &path_str],
+ );
+ dir_list.set_active_iter(¤t_iter);
+ };
+ // Advance dir_list_iter.
+ dir_list_iter = if dir_list_model.iter_next(¤t_iter) {
+ Some(current_iter)
+ } else {
+ None
+ }
+ }
+ // We updated the dir list to the point of the current working directory.
+ if let Some(iter) = dir_list_iter {
+ if is_prefix {
+ // If we didn't change any entries to this point and the list contains further entries,
+ // the remaining ones are subdirectories of the cwd and we keep them.
+ loop {
+ dir_list_model.set(&iter, &[1], &[&ICON_FOLDER_CLOSED]);
+ if !dir_list_model.iter_next(&iter) {
+ break;
+ }
+ }
+ } else {
+ // If we needed to change entries, the following ones are not directories under the
+ // cwd and we clear them.
+ while dir_list_model.remove(&iter) {}
+ }
+ }
+}
+
+/// Populates one level, i.e. one directory of the file browser tree.
+fn populate_tree_nodes(
+ store: >k::TreeStore,
+ state: &State,
+ dir: &str,
+ parent: Option<>k::TreeIter>,
+) {
+ let path = Path::new(dir);
+ let iter = path.read_dir()
+ .expect("read dir failed")
+ .filter_map(Result::ok);
+ let mut entries: Vec = if state.show_hidden {
+ iter.collect()
+ } else {
+ iter.filter(|entry| !entry.file_name().to_string_lossy().starts_with("."))
+ .filter(|entry| !entry.file_name().to_string_lossy().ends_with("~"))
+ .collect()
+ };
+ entries.sort_unstable_by(|lhs, rhs| cmp_dirs_first(lhs, rhs).unwrap_or(Ordering::Equal));
+ for entry in entries {
+ let path = if let Some(path) = entry.path().to_str() {
+ path.to_owned()
+ } else {
+ // Skip paths that contain invalid unicode.
+ continue;
+ };
+ let filename = entry.file_name().to_str().unwrap().to_owned();
+ let file_type = if let Ok(metadata) = fs::metadata(entry.path()) {
+ let file_type = metadata.file_type();
+ if file_type.is_dir() {
+ FileType::Dir
+ } else if file_type.is_file() {
+ FileType::File
+ } else {
+ continue;
+ }
+ } else {
+ // In case of invalid symlinks, we cannot obtain metadata.
+ continue;
+ };
+ let icon = match file_type {
+ FileType::Dir => ICON_FOLDER_CLOSED,
+ FileType::File => ICON_FILE,
+ };
+ // When we get until here, we want to show the entry. Append it to the tree.
+ let iter = store.append(parent);
+ store.set(
+ &iter,
+ &[0, 1, 2, 3],
+ &[&filename, &path, &(file_type as u8), &icon],
+ );
+ // For directories, check whether the directory is empty. If not, append a single empty
+ // entry, so the expand arrow is shown. Its contents are dynamically populated when
+ // expanded (see `init`).
+ if let FileType::Dir = file_type {
+ let not_empty = if let Ok(mut dir) = entry.path().read_dir() {
+ dir.next().is_some()
+ } else {
+ false
+ };
+ if not_empty {
+ let iter = store.append(&iter);
+ store.set(&iter, &[], &[]);
+ }
+ }
+ }
+}
+
+fn get_current_dir(nvim: &mut NeovimRef) -> String {
+ nvim.eval("getcwd()")
+ .as_ref()
+ .ok()
+ .and_then(|s| s.as_str())
+ .expect("Couldn't get working directory")
+ .to_owned()
+}
+
+/// Reveals and selects the given file in the file browser.
+///
+/// Returns `true` if the file could be successfully revealed.
+fn reveal_path_in_tree(store: >k::TreeStore, tree: >k::TreeView, rel_file_path: &Path) -> bool {
+ let mut tree_path = gtk::TreePath::new();
+ 'components: for component in rel_file_path.components() {
+ if let Component::Normal(component) = component {
+ tree_path.down();
+ while let Some(iter) = store.get_iter(&tree_path) {
+ let entry_value = store.get_value(&iter, Column::Filename as i32);
+ let entry = entry_value.get::<&str>().unwrap();
+ if component == entry {
+ tree.expand_row(&tree_path, false);
+ continue 'components;
+ }
+ tree_path.next();
+ }
+ return false;
+ } else {
+ return false;
+ }
+ }
+ if tree_path.get_depth() == 0 {
+ return false;
+ }
+ tree.set_cursor(&tree_path, None, false);
+ true
+}
diff --git a/src/main.rs b/src/main.rs
index 92f0cb9..75f9600 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -48,6 +48,7 @@ mod popup_menu;
mod project;
mod tabline;
mod error;
+mod file_browser;
mod subscriptions;
use std::env;
diff --git a/src/ui.rs b/src/ui.rs
index 5b4bc37..a01706f 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -7,7 +7,7 @@ use std::sync::Arc;
use gdk;
use gtk;
use gtk::prelude::*;
-use gtk::{AboutDialog, ApplicationWindow, Button, HeaderBar, SettingsExt};
+use gtk::{AboutDialog, ApplicationWindow, Button, HeaderBar, Orientation, Paned, SettingsExt};
use gio::prelude::*;
use gio::{Menu, MenuExt, MenuItem, SimpleAction};
use toml;
@@ -17,6 +17,7 @@ use shell::{self, Shell, ShellOptions};
use shell_dlg;
use project::Projects;
use plug_manager;
+use file_browser::FileBrowserWidget;
use subscriptions::SubscriptionHandle;
macro_rules! clone {
@@ -38,6 +39,7 @@ macro_rules! clone {
const DEFAULT_WIDTH: i32 = 800;
const DEFAULT_HEIGHT: i32 = 600;
+const DEFAULT_SIDEBAR_WIDTH: i32 = 200;
pub struct Ui {
initialized: bool,
@@ -46,6 +48,7 @@ pub struct Ui {
shell: Rc>,
projects: Rc>,
plug_manager: Arc>,
+ file_browser: Arc>,
}
pub struct Components {
@@ -88,6 +91,7 @@ impl Ui {
let plug_manager = plug_manager::Manager::new();
let plug_manager = Arc::new(UiMutex::new(plug_manager));
+ let file_browser = Arc::new(UiMutex::new(FileBrowserWidget::new()));
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)));
@@ -102,6 +106,7 @@ impl Ui {
settings,
projects,
plug_manager,
+ file_browser,
}
}
@@ -115,6 +120,7 @@ impl Ui {
settings.init();
let window = ApplicationWindow::new(app);
+ let main = Paned::new(Orientation::Horizontal);
{
// initialize window from comps
@@ -144,8 +150,11 @@ impl Ui {
comps.window_state.current_width,
comps.window_state.current_height,
);
+
+ main.set_position(comps.window_state.sidebar_width);
} else {
window.set_default_size(DEFAULT_WIDTH, DEFAULT_HEIGHT);
+ main.set_position(DEFAULT_SIDEBAR_WIDTH);
}
}
@@ -164,10 +173,28 @@ impl Ui {
None
};
+ let show_sidebar_action =
+ SimpleAction::new_stateful("show-sidebar", None, &false.to_variant());
+ let file_browser_ref = self.file_browser.clone();
let comps_ref = self.comps.clone();
- window.connect_size_allocate(move |window, _| {
- gtk_window_size_allocate(window, &mut *comps_ref.borrow_mut())
+ show_sidebar_action.connect_change_state(move |action, value| {
+ if let Some(ref value) = *value {
+ action.set_state(value);
+ let is_active = value.get::().unwrap();
+ file_browser_ref.borrow().set_visible(is_active);
+ comps_ref.borrow_mut().window_state.show_sidebar = is_active;
+ }
});
+ app.add_action(&show_sidebar_action);
+
+ let comps_ref = self.comps.clone();
+ window.connect_size_allocate(clone!(main => move |window, _| {
+ gtk_window_size_allocate(
+ window,
+ &mut *comps_ref.borrow_mut(),
+ &main,
+ );
+ }));
let comps_ref = self.comps.clone();
window.connect_window_state_event(move |_, event| {
@@ -181,10 +208,21 @@ impl Ui {
});
let shell = self.shell.borrow();
- window.add(&**shell);
+ let file_browser = self.file_browser.borrow();
+ main.pack1(&**file_browser, false, false);
+ main.pack2(&**shell, true, false);
+
+ window.add(&main);
window.show_all();
+ if restore_win_state {
+ // Hide sidebar, if it wasn't shown last time.
+ // Has to be done after show_all(), so it won't be shown again.
+ let show_sidebar = self.comps.borrow().window_state.show_sidebar;
+ show_sidebar_action.change_state(&show_sidebar.to_variant());
+ }
+
let comps_ref = self.comps.clone();
let update_title = shell.state.borrow().subscribe(
"BufEnter,DirChanged",
@@ -225,12 +263,14 @@ impl Ui {
}));
let state_ref = self.shell.borrow().state.clone();
+ let file_browser_ref = self.file_browser.clone();
let plug_manager_ref = self.plug_manager.clone();
shell.set_nvim_started_cb(Some(move || {
let state = state_ref.borrow();
plug_manager_ref
.borrow_mut()
.init_nvim_client(state_ref.borrow().nvim_clone());
+ file_browser_ref.borrow_mut().init(&state);
state.set_autocmds();
state.run_now(&update_title);
if let Some(ref update_subtitle) = update_subtitle {
@@ -297,6 +337,10 @@ impl Ui {
section.append_item(&MenuItem::new("New Window", "app.new-window"));
menu.append_section(None, §ion);
+ let section = Menu::new();
+ section.append_item(&MenuItem::new("Sidebar", "app.show-sidebar"));
+ menu.append_section(None, §ion);
+
let section = Menu::new();
section.append_item(&MenuItem::new("Plugins", "app.Plugins"));
section.append_item(&MenuItem::new("About", "app.HelpAbout"));
@@ -353,12 +397,19 @@ fn gtk_delete(comps: &UiMutex, shell: &RefCell) -> Inhibit {
})
}
-fn gtk_window_size_allocate(app_window: >k::ApplicationWindow, comps: &mut Components) {
+fn gtk_window_size_allocate(
+ app_window: >k::ApplicationWindow,
+ comps: &mut Components,
+ main: &Paned,
+) {
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;
}
+ if comps.window_state.show_sidebar {
+ comps.window_state.sidebar_width = main.get_position();
+ }
}
fn gtk_window_state_event(event: &gdk::EventWindowState, comps: &mut Components) {
@@ -372,6 +423,8 @@ struct WindowState {
current_width: i32,
current_height: i32,
is_maximized: bool,
+ show_sidebar: bool,
+ sidebar_width: i32,
}
impl WindowState {
@@ -380,6 +433,8 @@ impl WindowState {
current_width: DEFAULT_WIDTH,
current_height: DEFAULT_HEIGHT,
is_maximized: false,
+ show_sidebar: false,
+ sidebar_width: DEFAULT_SIDEBAR_WIDTH,
}
}
}