Support splitting of terminals using a Pane Grid
This commit is contained in:
parent
7f37ede453
commit
1940f2c056
5 changed files with 572 additions and 266 deletions
|
|
@ -48,3 +48,6 @@ menu-settings = Settings...
|
||||||
|
|
||||||
# Context menu
|
# Context menu
|
||||||
show-headerbar = Show header bar
|
show-headerbar = Show header bar
|
||||||
|
split-horizontal = Split Horizontal
|
||||||
|
split-vertical = Split Vertical
|
||||||
|
pane-toggle-maximize = Toggle Pane Maximized
|
||||||
|
|
|
||||||
756
src/main.rs
756
src/main.rs
|
|
@ -17,7 +17,7 @@ use cosmic::{
|
||||||
window, Alignment, Event, Length, Padding, Point,
|
window, Alignment, Event, Length, Padding, Point,
|
||||||
},
|
},
|
||||||
style,
|
style,
|
||||||
widget::{self, button, segmented_button},
|
widget::{self, button, container, pane_grid, segmented_button, PaneGrid},
|
||||||
Application, ApplicationExt, Element,
|
Application, ApplicationExt, Element,
|
||||||
};
|
};
|
||||||
use cosmic_text::{fontdb::FaceInfo, Family, Stretch, Weight};
|
use cosmic_text::{fontdb::FaceInfo, Family, Stretch, Weight};
|
||||||
|
|
@ -41,7 +41,7 @@ mod localize;
|
||||||
use menu::menu_bar;
|
use menu::menu_bar;
|
||||||
mod menu;
|
mod menu;
|
||||||
|
|
||||||
use terminal::{Terminal, TerminalScroll};
|
use terminal::{Terminal, TerminalPaneGrid, TerminalScroll};
|
||||||
mod terminal;
|
mod terminal;
|
||||||
|
|
||||||
use terminal_box::terminal_box;
|
use terminal_box::terminal_box;
|
||||||
|
|
@ -167,6 +167,9 @@ pub enum Action {
|
||||||
Settings,
|
Settings,
|
||||||
ShowHeaderBar(bool),
|
ShowHeaderBar(bool),
|
||||||
TabNew,
|
TabNew,
|
||||||
|
PaneSplitHorizontal,
|
||||||
|
PaneSplitVertical,
|
||||||
|
PaneToggleMaximized,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Action {
|
impl Action {
|
||||||
|
|
@ -178,6 +181,9 @@ impl Action {
|
||||||
Action::Settings => Message::ToggleContextPage(ContextPage::Settings),
|
Action::Settings => Message::ToggleContextPage(ContextPage::Settings),
|
||||||
Action::ShowHeaderBar(show_headerbar) => Message::ShowHeaderBar(show_headerbar),
|
Action::ShowHeaderBar(show_headerbar) => Message::ShowHeaderBar(show_headerbar),
|
||||||
Action::TabNew => Message::TabNew,
|
Action::TabNew => Message::TabNew,
|
||||||
|
Action::PaneSplitVertical => Message::PaneSplit(pane_grid::Axis::Vertical),
|
||||||
|
Action::PaneSplitHorizontal => Message::PaneSplit(pane_grid::Axis::Horizontal),
|
||||||
|
Action::PaneToggleMaximized => Message::PaneToggleMaximized,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -199,6 +205,12 @@ pub enum Message {
|
||||||
FindNext,
|
FindNext,
|
||||||
FindPrevious,
|
FindPrevious,
|
||||||
FindSearchValueChanged(String),
|
FindSearchValueChanged(String),
|
||||||
|
PaneClicked(pane_grid::Pane),
|
||||||
|
PaneSplit(pane_grid::Axis),
|
||||||
|
PaneToggleMaximized,
|
||||||
|
PaneFocusAdjacent(pane_grid::Direction),
|
||||||
|
PaneDragged(pane_grid::DragEvent),
|
||||||
|
PaneResized(pane_grid::ResizeEvent),
|
||||||
Modifiers(Modifiers),
|
Modifiers(Modifiers),
|
||||||
Paste(Option<segmented_button::Entity>),
|
Paste(Option<segmented_button::Entity>),
|
||||||
PasteValue(Option<segmented_button::Entity>, String),
|
PasteValue(Option<segmented_button::Entity>, String),
|
||||||
|
|
@ -212,8 +224,8 @@ pub enum Message {
|
||||||
TabContextAction(segmented_button::Entity, Action),
|
TabContextAction(segmented_button::Entity, Action),
|
||||||
TabContextMenu(segmented_button::Entity, Option<Point>),
|
TabContextMenu(segmented_button::Entity, Option<Point>),
|
||||||
TabNew,
|
TabNew,
|
||||||
TermEvent(segmented_button::Entity, TermEvent),
|
TermEvent(pane_grid::Pane, segmented_button::Entity, TermEvent),
|
||||||
TermEventTx(mpsc::Sender<(segmented_button::Entity, TermEvent)>),
|
TermEventTx(mpsc::Sender<(pane_grid::Pane, segmented_button::Entity, TermEvent)>),
|
||||||
ToggleContextPage(ContextPage),
|
ToggleContextPage(ContextPage),
|
||||||
ShowAdvancedFontSettings(bool),
|
ShowAdvancedFontSettings(bool),
|
||||||
WindowClose,
|
WindowClose,
|
||||||
|
|
@ -239,7 +251,7 @@ impl ContextPage {
|
||||||
/// The [`App`] stores application-specific state.
|
/// The [`App`] stores application-specific state.
|
||||||
pub struct App {
|
pub struct App {
|
||||||
core: Core,
|
core: Core,
|
||||||
tab_model: segmented_button::Model<segmented_button::SingleSelect>,
|
pane_model: TerminalPaneGrid,
|
||||||
config_handler: Option<cosmic_config::Config>,
|
config_handler: Option<cosmic_config::Config>,
|
||||||
config: Config,
|
config: Config,
|
||||||
app_themes: Vec<String>,
|
app_themes: Vec<String>,
|
||||||
|
|
@ -259,11 +271,11 @@ pub struct App {
|
||||||
theme_names: Vec<String>,
|
theme_names: Vec<String>,
|
||||||
themes: HashMap<String, TermColors>,
|
themes: HashMap<String, TermColors>,
|
||||||
context_page: ContextPage,
|
context_page: ContextPage,
|
||||||
terminal_id: widget::Id,
|
terminal_ids: HashMap<pane_grid::Pane, widget::Id>,
|
||||||
find: bool,
|
find: bool,
|
||||||
find_search_id: widget::Id,
|
find_search_id: widget::Id,
|
||||||
find_search_value: String,
|
find_search_value: String,
|
||||||
term_event_tx_opt: Option<mpsc::Sender<(segmented_button::Entity, TermEvent)>>,
|
term_event_tx_opt: Option<mpsc::Sender<(pane_grid::Pane, segmented_button::Entity, TermEvent)>>,
|
||||||
startup_options: Option<tty::Options>,
|
startup_options: Option<tty::Options>,
|
||||||
term_config: TermConfig,
|
term_config: TermConfig,
|
||||||
show_advanced_font_settings: bool,
|
show_advanced_font_settings: bool,
|
||||||
|
|
@ -273,11 +285,14 @@ pub struct App {
|
||||||
impl App {
|
impl App {
|
||||||
fn update_config(&mut self) -> Command<Message> {
|
fn update_config(&mut self) -> Command<Message> {
|
||||||
//TODO: provide iterator over data
|
//TODO: provide iterator over data
|
||||||
let entities: Vec<_> = self.tab_model.iter().collect();
|
let panes: Vec<_> = self.pane_model.panes.iter().collect();
|
||||||
for entity in entities {
|
for (_pane, tab_model) in panes {
|
||||||
if let Some(terminal) = self.tab_model.data::<Mutex<Terminal>>(entity) {
|
let entities: Vec<_> = tab_model.iter().collect();
|
||||||
let mut terminal = terminal.lock().unwrap();
|
for entity in entities {
|
||||||
terminal.set_config(&self.config, &self.themes, self.zoom_adj);
|
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
||||||
|
let mut terminal = terminal.lock().unwrap();
|
||||||
|
terminal.set_config(&self.config, &self.themes, self.zoom_adj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -303,22 +318,33 @@ impl App {
|
||||||
Command::none()
|
Command::none()
|
||||||
} else if self.find {
|
} else if self.find {
|
||||||
widget::text_input::focus(self.find_search_id.clone())
|
widget::text_input::focus(self.find_search_id.clone())
|
||||||
|
} else if let Some(terminal_id) = self.terminal_ids.get(&self.pane_model.focus).cloned() {
|
||||||
|
widget::text_input::focus(terminal_id)
|
||||||
} else {
|
} else {
|
||||||
widget::text_input::focus(self.terminal_id.clone())
|
Command::none()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call this any time the tab changes
|
// Call this any time the tab changes
|
||||||
fn update_title(&mut self) -> Command<Message> {
|
fn update_title(&mut self, pane: Option<pane_grid::Pane>) -> Command<Message> {
|
||||||
let (header_title, window_title) = match self.tab_model.text(self.tab_model.active()) {
|
let pane = pane.unwrap_or(self.pane_model.focus);
|
||||||
Some(tab_title) => (
|
if let Some(tab_model) = self.pane_model.panes.get(pane) {
|
||||||
tab_title.to_string(),
|
let (header_title, window_title) = match tab_model.text(tab_model.active()) {
|
||||||
format!("{tab_title} — COSMIC Terminal"),
|
Some(tab_title) => (
|
||||||
),
|
tab_title.to_string(),
|
||||||
None => (String::new(), "COSMIC Terminal".to_string()),
|
format!("{tab_title} — COSMIC Terminal"),
|
||||||
};
|
),
|
||||||
self.set_header_title(header_title);
|
None => (String::new(), "COSMIC Terminal".to_string()),
|
||||||
Command::batch([self.set_window_title(window_title), self.update_focus()])
|
};
|
||||||
|
self.set_header_title(header_title);
|
||||||
|
Command::batch([self.set_window_title(window_title), self.update_focus()])
|
||||||
|
} else {
|
||||||
|
log::error!("Failed to get the specific pane");
|
||||||
|
Command::batch([
|
||||||
|
self.set_window_title("COSMIC Terminal".to_string()),
|
||||||
|
self.update_focus(),
|
||||||
|
])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_curr_font_weights_and_stretches(&mut self) {
|
fn set_curr_font_weights_and_stretches(&mut self) {
|
||||||
|
|
@ -693,10 +719,13 @@ impl Application for App {
|
||||||
let themes = terminal_theme::terminal_themes();
|
let themes = terminal_theme::terminal_themes();
|
||||||
let mut theme_names: Vec<_> = themes.keys().map(|x| x.clone()).collect();
|
let mut theme_names: Vec<_> = themes.keys().map(|x| x.clone()).collect();
|
||||||
theme_names.sort();
|
theme_names.sort();
|
||||||
|
let pane_model = TerminalPaneGrid::new(segmented_button::ModelBuilder::default().build());
|
||||||
|
let mut terminal_ids = HashMap::new();
|
||||||
|
terminal_ids.insert(pane_model.focus, widget::Id::unique());
|
||||||
|
|
||||||
let mut app = App {
|
let mut app = App {
|
||||||
core,
|
core,
|
||||||
tab_model: segmented_button::ModelBuilder::default().build(),
|
pane_model,
|
||||||
config_handler: flags.config_handler,
|
config_handler: flags.config_handler,
|
||||||
config: flags.config,
|
config: flags.config,
|
||||||
app_themes,
|
app_themes,
|
||||||
|
|
@ -716,7 +745,7 @@ impl Application for App {
|
||||||
theme_names,
|
theme_names,
|
||||||
themes,
|
themes,
|
||||||
context_page: ContextPage::Settings,
|
context_page: ContextPage::Settings,
|
||||||
terminal_id: widget::Id::unique(),
|
terminal_ids,
|
||||||
find: false,
|
find: false,
|
||||||
find_search_id: widget::Id::unique(),
|
find_search_id: widget::Id::unique(),
|
||||||
find_search_value: String::new(),
|
find_search_value: String::new(),
|
||||||
|
|
@ -728,7 +757,7 @@ impl Application for App {
|
||||||
};
|
};
|
||||||
|
|
||||||
app.set_curr_font_weights_and_stretches();
|
app.set_curr_font_weights_and_stretches();
|
||||||
let command = app.update_title();
|
let command = app.update_title(None);
|
||||||
|
|
||||||
(app, command)
|
(app, command)
|
||||||
}
|
}
|
||||||
|
|
@ -763,13 +792,17 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::Copy(entity_opt) => {
|
Message::Copy(entity_opt) => {
|
||||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
if let Some(tab_model) = self.pane_model.active() {
|
||||||
if let Some(terminal) = self.tab_model.data::<Mutex<Terminal>>(entity) {
|
let entity = entity_opt.unwrap_or_else(|| tab_model.active());
|
||||||
let terminal = terminal.lock().unwrap();
|
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
||||||
let term = terminal.term.lock();
|
let terminal = terminal.lock().unwrap();
|
||||||
if let Some(text) = term.selection_to_string() {
|
let term = terminal.term.lock();
|
||||||
return clipboard::write(text);
|
if let Some(text) = term.selection_to_string() {
|
||||||
|
return clipboard::write(text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log::warn!("Failed to get focused pane");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::DefaultFont(index) => {
|
Message::DefaultFont(index) => {
|
||||||
|
|
@ -781,14 +814,16 @@ impl Application for App {
|
||||||
let mut font_system = font_system().write().unwrap();
|
let mut font_system = font_system().write().unwrap();
|
||||||
font_system.raw().db_mut().set_monospace_family(font_name);
|
font_system.raw().db_mut().set_monospace_family(font_name);
|
||||||
}
|
}
|
||||||
|
let panes: Vec<_> = self.pane_model.panes.iter().collect();
|
||||||
let entities: Vec<_> = self.tab_model.iter().collect();
|
for (_pane, tab_model) in panes {
|
||||||
for entity in entities {
|
let entities: Vec<_> = tab_model.iter().collect();
|
||||||
if let Some(terminal) =
|
for entity in entities {
|
||||||
self.tab_model.data::<Mutex<Terminal>>(entity)
|
if let Some(terminal) =
|
||||||
{
|
tab_model.data::<Mutex<Terminal>>(entity)
|
||||||
let mut terminal = terminal.lock().unwrap();
|
{
|
||||||
terminal.update_cell_size();
|
let mut terminal = terminal.lock().unwrap();
|
||||||
|
terminal.update_cell_size();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -868,10 +903,12 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
Message::FindNext => {
|
Message::FindNext => {
|
||||||
if !self.find_search_value.is_empty() {
|
if !self.find_search_value.is_empty() {
|
||||||
let entity = self.tab_model.active();
|
if let Some(tab_model) = self.pane_model.active() {
|
||||||
if let Some(terminal) = self.tab_model.data::<Mutex<Terminal>>(entity) {
|
let entity = tab_model.active();
|
||||||
let mut terminal = terminal.lock().unwrap();
|
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
||||||
terminal.search(&self.find_search_value, true);
|
let mut terminal = terminal.lock().unwrap();
|
||||||
|
terminal.search(&self.find_search_value, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -880,10 +917,12 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
Message::FindPrevious => {
|
Message::FindPrevious => {
|
||||||
if !self.find_search_value.is_empty() {
|
if !self.find_search_value.is_empty() {
|
||||||
let entity = self.tab_model.active();
|
if let Some(tab_model) = self.pane_model.active() {
|
||||||
if let Some(terminal) = self.tab_model.data::<Mutex<Terminal>>(entity) {
|
let entity = tab_model.active();
|
||||||
let mut terminal = terminal.lock().unwrap();
|
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
||||||
terminal.search(&self.find_search_value, false);
|
let mut terminal = terminal.lock().unwrap();
|
||||||
|
terminal.search(&self.find_search_value, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -896,6 +935,93 @@ impl Application for App {
|
||||||
Message::Modifiers(modifiers) => {
|
Message::Modifiers(modifiers) => {
|
||||||
self.modifiers = modifiers;
|
self.modifiers = modifiers;
|
||||||
}
|
}
|
||||||
|
Message::PaneClicked(pane) => {
|
||||||
|
self.pane_model.focus = pane;
|
||||||
|
return self.update_title(Some(pane));
|
||||||
|
}
|
||||||
|
Message::PaneSplit(axis) => {
|
||||||
|
let result = self.pane_model.panes.split(
|
||||||
|
axis,
|
||||||
|
self.pane_model.focus,
|
||||||
|
segmented_button::ModelBuilder::default().build(),
|
||||||
|
);
|
||||||
|
if let Some((pane, _)) = result {
|
||||||
|
self.terminal_ids.insert(pane, widget::Id::unique());
|
||||||
|
self.pane_model.focus = pane;
|
||||||
|
|
||||||
|
match &self.term_event_tx_opt {
|
||||||
|
Some(term_event_tx) => match self.themes.get(self.config.syntax_theme()) {
|
||||||
|
Some(colors) => {
|
||||||
|
let current_pane = self.pane_model.focus;
|
||||||
|
if let Some(tab_model) = self.pane_model.active_mut() {
|
||||||
|
let entity = tab_model
|
||||||
|
.insert()
|
||||||
|
.text("New Terminal")
|
||||||
|
.closable()
|
||||||
|
.activate()
|
||||||
|
.id();
|
||||||
|
// Use the startup options, or defaults
|
||||||
|
let options = self.startup_options.take().unwrap_or_default();
|
||||||
|
let mut terminal = Terminal::new(
|
||||||
|
current_pane,
|
||||||
|
entity,
|
||||||
|
term_event_tx.clone(),
|
||||||
|
self.term_config.clone(),
|
||||||
|
options,
|
||||||
|
&self.config,
|
||||||
|
*colors,
|
||||||
|
);
|
||||||
|
terminal.set_config(&self.config, &self.themes, self.zoom_adj);
|
||||||
|
tab_model
|
||||||
|
.data_set::<Mutex<Terminal>>(entity, Mutex::new(terminal));
|
||||||
|
} else {
|
||||||
|
log::error!("Found no active pane");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
log::error!(
|
||||||
|
"failed to find terminal theme {:?}",
|
||||||
|
self.config.syntax_theme()
|
||||||
|
);
|
||||||
|
//TODO: fall back to known good theme
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
log::warn!("tried to create new tab before having event channel");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.pane_model.panes_created += 1;
|
||||||
|
return self.update_title(Some(pane));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::PaneToggleMaximized => {
|
||||||
|
if self.pane_model.panes.maximized().is_some() {
|
||||||
|
self.pane_model.panes.restore();
|
||||||
|
} else {
|
||||||
|
self.pane_model.panes.maximize(self.pane_model.focus);
|
||||||
|
}
|
||||||
|
return self.update_focus();
|
||||||
|
}
|
||||||
|
Message::PaneFocusAdjacent(direction) => {
|
||||||
|
if let Some(adjacent) = self
|
||||||
|
.pane_model
|
||||||
|
.panes
|
||||||
|
.adjacent(self.pane_model.focus, direction)
|
||||||
|
{
|
||||||
|
self.pane_model.focus = adjacent;
|
||||||
|
return Command::batch([
|
||||||
|
self.update_focus(),
|
||||||
|
self.update_title(Some(adjacent)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::PaneResized(pane_grid::ResizeEvent { split, ratio }) => {
|
||||||
|
self.pane_model.panes.resize(split, ratio);
|
||||||
|
}
|
||||||
|
Message::PaneDragged(pane_grid::DragEvent::Dropped { pane, target }) => {
|
||||||
|
self.pane_model.panes.drop(pane, target);
|
||||||
|
}
|
||||||
|
Message::PaneDragged(_) => {}
|
||||||
Message::Paste(entity_opt) => {
|
Message::Paste(entity_opt) => {
|
||||||
return clipboard::read(move |value_opt| match value_opt {
|
return clipboard::read(move |value_opt| match value_opt {
|
||||||
Some(value) => message::app(Message::PasteValue(entity_opt, value)),
|
Some(value) => message::app(Message::PasteValue(entity_opt, value)),
|
||||||
|
|
@ -903,17 +1029,21 @@ impl Application for App {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Message::PasteValue(entity_opt, value) => {
|
Message::PasteValue(entity_opt, value) => {
|
||||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
if let Some(tab_model) = self.pane_model.active() {
|
||||||
if let Some(terminal) = self.tab_model.data::<Mutex<Terminal>>(entity) {
|
let entity = entity_opt.unwrap_or_else(|| tab_model.active());
|
||||||
let terminal = terminal.lock().unwrap();
|
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
||||||
terminal.paste(value);
|
let terminal = terminal.lock().unwrap();
|
||||||
|
terminal.paste(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::SelectAll(entity_opt) => {
|
Message::SelectAll(entity_opt) => {
|
||||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
if let Some(tab_model) = self.pane_model.active() {
|
||||||
if let Some(terminal) = self.tab_model.data::<Mutex<Terminal>>(entity) {
|
let entity = entity_opt.unwrap_or_else(|| tab_model.active());
|
||||||
let mut terminal = terminal.lock().unwrap();
|
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
||||||
terminal.select_all();
|
let mut terminal = terminal.lock().unwrap();
|
||||||
|
terminal.select_all();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::ShowHeaderBar(show_headerbar) => {
|
Message::ShowHeaderBar(show_headerbar) => {
|
||||||
|
|
@ -945,78 +1075,96 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Message::TabActivate(entity) => {
|
Message::TabActivate(entity) => {
|
||||||
self.tab_model.activate(entity);
|
if let Some(tab_model) = self.pane_model.active_mut() {
|
||||||
return self.update_title();
|
tab_model.activate(entity);
|
||||||
|
}
|
||||||
|
return self.update_title(None);
|
||||||
}
|
}
|
||||||
Message::TabClose(entity_opt) => {
|
Message::TabClose(entity_opt) => {
|
||||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
if let Some(tab_model) = self.pane_model.active_mut() {
|
||||||
|
let entity = entity_opt.unwrap_or_else(|| tab_model.active());
|
||||||
|
|
||||||
// Activate closest item
|
// Activate closest item
|
||||||
if let Some(position) = self.tab_model.position(entity) {
|
if let Some(position) = tab_model.position(entity) {
|
||||||
if position > 0 {
|
if position > 0 {
|
||||||
self.tab_model.activate_position(position - 1);
|
tab_model.activate_position(position - 1);
|
||||||
} else {
|
} else {
|
||||||
self.tab_model.activate_position(position + 1);
|
tab_model.activate_position(position + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove item
|
||||||
|
tab_model.remove(entity);
|
||||||
|
|
||||||
|
// If that was the last tab, close window
|
||||||
|
if tab_model.iter().next().is_none() {
|
||||||
|
if let Some((_state, sibling)) =
|
||||||
|
self.pane_model.panes.close(self.pane_model.focus)
|
||||||
|
{
|
||||||
|
self.pane_model.focus = sibling;
|
||||||
|
} else {
|
||||||
|
return window::close(window::Id::MAIN);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove item
|
return self.update_title(None);
|
||||||
self.tab_model.remove(entity);
|
|
||||||
|
|
||||||
// If that was the last tab, close window
|
|
||||||
if self.tab_model.iter().next().is_none() {
|
|
||||||
return window::close(window::Id::MAIN);
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.update_title();
|
|
||||||
}
|
}
|
||||||
Message::TabContextAction(entity, action) => {
|
Message::TabContextAction(entity, action) => {
|
||||||
match self.tab_model.data::<Mutex<Terminal>>(entity) {
|
if let Some(tab_model) = self.pane_model.active() {
|
||||||
Some(terminal) => {
|
match tab_model.data::<Mutex<Terminal>>(entity) {
|
||||||
// Close context menu
|
Some(terminal) => {
|
||||||
{
|
// Close context menu
|
||||||
let mut terminal = terminal.lock().unwrap();
|
{
|
||||||
terminal.context_menu = None;
|
let mut terminal = terminal.lock().unwrap();
|
||||||
|
terminal.context_menu = None;
|
||||||
|
}
|
||||||
|
// Run action's message
|
||||||
|
return self.update(action.message(entity));
|
||||||
}
|
}
|
||||||
// Run action's message
|
_ => {}
|
||||||
return self.update(action.message(entity));
|
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::TabContextMenu(entity, position_opt) => {
|
Message::TabContextMenu(entity, position_opt) => {
|
||||||
match self.tab_model.data::<Mutex<Terminal>>(entity) {
|
if let Some(tab_model) = self.pane_model.active() {
|
||||||
Some(terminal) => {
|
match tab_model.data::<Mutex<Terminal>>(entity) {
|
||||||
// Update context menu position
|
Some(terminal) => {
|
||||||
let mut terminal = terminal.lock().unwrap();
|
// Update context menu position
|
||||||
terminal.context_menu = position_opt;
|
let mut terminal = terminal.lock().unwrap();
|
||||||
|
terminal.context_menu = position_opt;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::TabNew => match &self.term_event_tx_opt {
|
Message::TabNew => match &self.term_event_tx_opt {
|
||||||
Some(term_event_tx) => match self.themes.get(self.config.syntax_theme()) {
|
Some(term_event_tx) => match self.themes.get(self.config.syntax_theme()) {
|
||||||
Some(colors) => {
|
Some(colors) => {
|
||||||
let entity = self
|
let current_pane = self.pane_model.focus;
|
||||||
.tab_model
|
if let Some(tab_model) = self.pane_model.active_mut() {
|
||||||
.insert()
|
let entity = tab_model
|
||||||
.text("New Terminal")
|
.insert()
|
||||||
.closable()
|
.text("New Terminal")
|
||||||
.activate()
|
.closable()
|
||||||
.id();
|
.activate()
|
||||||
// Use the startup options, or defaults
|
.id();
|
||||||
let options = self.startup_options.take().unwrap_or_default();
|
// Use the startup options, or defaults
|
||||||
let mut terminal = Terminal::new(
|
let options = self.startup_options.take().unwrap_or_default();
|
||||||
entity,
|
let mut terminal = Terminal::new(
|
||||||
term_event_tx.clone(),
|
current_pane,
|
||||||
self.term_config.clone(),
|
entity,
|
||||||
options,
|
term_event_tx.clone(),
|
||||||
&self.config,
|
self.term_config.clone(),
|
||||||
colors.clone(),
|
options,
|
||||||
);
|
&self.config,
|
||||||
terminal.set_config(&self.config, &self.themes, self.zoom_adj);
|
*colors,
|
||||||
self.tab_model
|
);
|
||||||
.data_set::<Mutex<Terminal>>(entity, Mutex::new(terminal));
|
terminal.set_config(&self.config, &self.themes, self.zoom_adj);
|
||||||
|
tab_model.data_set::<Mutex<Terminal>>(entity, Mutex::new(terminal));
|
||||||
|
} else {
|
||||||
|
log::error!("Found no active pane");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
log::error!(
|
log::error!(
|
||||||
|
|
@ -1030,52 +1178,66 @@ impl Application for App {
|
||||||
log::warn!("tried to create new tab before having event channel");
|
log::warn!("tried to create new tab before having event channel");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Message::TermEvent(entity, event) => match event {
|
Message::TermEvent(pane, entity, event) => {
|
||||||
TermEvent::Bell => {
|
match event {
|
||||||
//TODO: audible or visible bell options?
|
TermEvent::Bell => {
|
||||||
}
|
//TODO: audible or visible bell options?
|
||||||
TermEvent::ColorRequest(index, f) => {
|
}
|
||||||
if let Some(terminal) = self.tab_model.data::<Mutex<Terminal>>(entity) {
|
TermEvent::ColorRequest(index, f) => {
|
||||||
let terminal = terminal.lock().unwrap();
|
if let Some(tab_model) = self.pane_model.panes.get(pane) {
|
||||||
let rgb = terminal.colors()[index].unwrap_or_default();
|
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
||||||
let text = f(rgb);
|
let terminal = terminal.lock().unwrap();
|
||||||
terminal.input_no_scroll(text.into_bytes());
|
let rgb = terminal.colors()[index].unwrap_or_default();
|
||||||
|
let text = f(rgb);
|
||||||
|
terminal.input_no_scroll(text.into_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TermEvent::Exit => {
|
||||||
|
return self.update(Message::TabClose(Some(entity)));
|
||||||
|
}
|
||||||
|
TermEvent::PtyWrite(text) => {
|
||||||
|
if let Some(tab_model) = self.pane_model.panes.get(pane) {
|
||||||
|
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
||||||
|
let terminal = terminal.lock().unwrap();
|
||||||
|
terminal.input_no_scroll(text.into_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TermEvent::ResetTitle => {
|
||||||
|
if let Some(tab_model) = self.pane_model.panes.get_mut(pane) {
|
||||||
|
tab_model.text_set(entity, "New Terminal");
|
||||||
|
}
|
||||||
|
return self.update_title(Some(pane));
|
||||||
|
}
|
||||||
|
TermEvent::TextAreaSizeRequest(f) => {
|
||||||
|
if let Some(tab_model) = self.pane_model.panes.get(pane) {
|
||||||
|
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
||||||
|
let terminal = terminal.lock().unwrap();
|
||||||
|
let text = f(terminal.size().into());
|
||||||
|
terminal.input_no_scroll(text.into_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TermEvent::Title(title) => {
|
||||||
|
if let Some(tab_model) = self.pane_model.panes.get_mut(pane) {
|
||||||
|
tab_model.text_set(entity, title);
|
||||||
|
}
|
||||||
|
return self.update_title(Some(pane));
|
||||||
|
}
|
||||||
|
TermEvent::MouseCursorDirty | TermEvent::Wakeup => {
|
||||||
|
if let Some(tab_model) = self.pane_model.panes.get(pane) {
|
||||||
|
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
||||||
|
let mut terminal = terminal.lock().unwrap();
|
||||||
|
terminal.needs_update = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
log::warn!("TODO: {:?}", event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TermEvent::Exit => {
|
}
|
||||||
return self.update(Message::TabClose(Some(entity)));
|
|
||||||
}
|
|
||||||
TermEvent::PtyWrite(text) => {
|
|
||||||
if let Some(terminal) = self.tab_model.data::<Mutex<Terminal>>(entity) {
|
|
||||||
let terminal = terminal.lock().unwrap();
|
|
||||||
terminal.input_no_scroll(text.into_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TermEvent::ResetTitle => {
|
|
||||||
self.tab_model.text_set(entity, "New Terminal");
|
|
||||||
return self.update_title();
|
|
||||||
}
|
|
||||||
TermEvent::TextAreaSizeRequest(f) => {
|
|
||||||
if let Some(terminal) = self.tab_model.data::<Mutex<Terminal>>(entity) {
|
|
||||||
let terminal = terminal.lock().unwrap();
|
|
||||||
let text = f(terminal.size().into());
|
|
||||||
terminal.input_no_scroll(text.into_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TermEvent::Title(title) => {
|
|
||||||
self.tab_model.text_set(entity, title);
|
|
||||||
return self.update_title();
|
|
||||||
}
|
|
||||||
TermEvent::MouseCursorDirty | TermEvent::Wakeup => {
|
|
||||||
if let Some(terminal) = self.tab_model.data::<Mutex<Terminal>>(entity) {
|
|
||||||
let mut terminal = terminal.lock().unwrap();
|
|
||||||
terminal.needs_update = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
log::warn!("TODO: {:?}", event);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Message::TermEventTx(term_event_tx) => {
|
Message::TermEventTx(term_event_tx) => {
|
||||||
self.term_event_tx_opt = Some(term_event_tx);
|
self.term_event_tx_opt = Some(term_event_tx);
|
||||||
}
|
}
|
||||||
|
|
@ -1139,114 +1301,128 @@ impl Application for App {
|
||||||
/// Creates a view after each update.
|
/// Creates a view after each update.
|
||||||
fn view(&self) -> Element<Self::Message> {
|
fn view(&self) -> Element<Self::Message> {
|
||||||
let cosmic_theme::Spacing { space_xxs, .. } = self.core().system_theme().cosmic().spacing;
|
let cosmic_theme::Spacing { space_xxs, .. } = self.core().system_theme().cosmic().spacing;
|
||||||
|
let pane_grid = PaneGrid::new(&self.pane_model.panes, |pane, tab_model, _is_maximized| {
|
||||||
|
let mut tab_column = widget::column::with_capacity(1);
|
||||||
|
|
||||||
let mut tab_column = widget::column::with_capacity(1);
|
if tab_model.iter().count() > 1 {
|
||||||
|
tab_column = tab_column.push(
|
||||||
if self.tab_model.iter().count() > 1 {
|
widget::container(
|
||||||
tab_column = tab_column.push(
|
widget::view_switcher::horizontal(tab_model)
|
||||||
widget::container(
|
.button_height(32)
|
||||||
widget::view_switcher::horizontal(&self.tab_model)
|
.button_spacing(space_xxs)
|
||||||
.button_height(32)
|
.on_activate(Message::TabActivate)
|
||||||
.button_spacing(space_xxs)
|
.on_close(|entity| Message::TabClose(Some(entity))),
|
||||||
.on_activate(Message::TabActivate)
|
|
||||||
.on_close(|entity| Message::TabClose(Some(entity))),
|
|
||||||
)
|
|
||||||
.style(style::Container::Background)
|
|
||||||
.width(Length::Fill),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let entity = self.tab_model.active();
|
|
||||||
match self.tab_model.data::<Mutex<Terminal>>(entity) {
|
|
||||||
Some(terminal) => {
|
|
||||||
let terminal_box = terminal_box(terminal)
|
|
||||||
.id(self.terminal_id.clone())
|
|
||||||
.on_context_menu(move |position_opt| {
|
|
||||||
Message::TabContextMenu(entity, position_opt)
|
|
||||||
});
|
|
||||||
|
|
||||||
let context_menu = {
|
|
||||||
let terminal = terminal.lock().unwrap();
|
|
||||||
terminal.context_menu
|
|
||||||
};
|
|
||||||
|
|
||||||
let tab_element: Element<'_, Message> = match context_menu {
|
|
||||||
Some(position) => widget::popover(
|
|
||||||
terminal_box.context_menu(position),
|
|
||||||
menu::context_menu(&self.config, entity),
|
|
||||||
)
|
)
|
||||||
.position(position)
|
.style(style::Container::Background)
|
||||||
.into(),
|
.width(Length::Fill),
|
||||||
None => terminal_box.into(),
|
);
|
||||||
};
|
|
||||||
tab_column = tab_column.push(tab_element);
|
|
||||||
}
|
}
|
||||||
None => {
|
|
||||||
//TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.find {
|
let entity = tab_model.active();
|
||||||
let find_input =
|
let terminal_id = self
|
||||||
widget::text_input::text_input(fl!("find-placeholder"), &self.find_search_value)
|
.terminal_ids
|
||||||
.id(self.find_search_id.clone())
|
.get(&pane)
|
||||||
.on_input(Message::FindSearchValueChanged)
|
.cloned()
|
||||||
// This is inverted for ease of use, usually in terminals you want to search
|
.unwrap_or_else(widget::Id::unique);
|
||||||
// upwards, which is FindPrevious
|
match tab_model.data::<Mutex<Terminal>>(entity) {
|
||||||
.on_submit(if self.modifiers.contains(Modifiers::SHIFT) {
|
Some(terminal) => {
|
||||||
Message::FindNext
|
let terminal_box = terminal_box(terminal).id(terminal_id).on_context_menu(
|
||||||
} else {
|
move |position_opt| Message::TabContextMenu(entity, position_opt),
|
||||||
Message::FindPrevious
|
|
||||||
})
|
|
||||||
.width(Length::Fixed(320.0))
|
|
||||||
.trailing_icon(
|
|
||||||
button(icon_cache_get("edit-clear-symbolic", 16))
|
|
||||||
.on_press(Message::FindSearchValueChanged(String::new()))
|
|
||||||
.style(style::Button::Icon)
|
|
||||||
.into(),
|
|
||||||
);
|
);
|
||||||
let find_widget = widget::row::with_children(vec![
|
|
||||||
find_input.into(),
|
let context_menu = {
|
||||||
widget::tooltip(
|
let terminal = terminal.lock().unwrap();
|
||||||
button(icon_cache_get("go-up-symbolic", 16))
|
terminal.context_menu
|
||||||
.on_press(Message::FindPrevious)
|
};
|
||||||
.padding(space_xxs)
|
|
||||||
.style(style::Button::Icon),
|
let tab_element: Element<'_, Message> = match context_menu {
|
||||||
fl!("find-previous"),
|
Some(position) => widget::popover(
|
||||||
widget::tooltip::Position::Top,
|
terminal_box.context_menu(position),
|
||||||
|
menu::context_menu(&self.config, entity),
|
||||||
|
)
|
||||||
|
.position(position)
|
||||||
|
.into(),
|
||||||
|
None => terminal_box.into(),
|
||||||
|
};
|
||||||
|
tab_column = tab_column.push(tab_element);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.find {
|
||||||
|
let find_input = widget::text_input::text_input(
|
||||||
|
fl!("find-placeholder"),
|
||||||
|
&self.find_search_value,
|
||||||
)
|
)
|
||||||
.into(),
|
.id(self.find_search_id.clone())
|
||||||
widget::tooltip(
|
.on_input(Message::FindSearchValueChanged)
|
||||||
button(icon_cache_get("go-down-symbolic", 16))
|
// This is inverted for ease of use, usually in terminals you want to search
|
||||||
.on_press(Message::FindNext)
|
// upwards, which is FindPrevious
|
||||||
.padding(space_xxs)
|
.on_submit(if self.modifiers.contains(Modifiers::SHIFT) {
|
||||||
.style(style::Button::Icon),
|
Message::FindNext
|
||||||
fl!("find-next"),
|
} else {
|
||||||
widget::tooltip::Position::Top,
|
Message::FindPrevious
|
||||||
)
|
})
|
||||||
.into(),
|
.width(Length::Fixed(320.0))
|
||||||
widget::horizontal_space(Length::Fill).into(),
|
.trailing_icon(
|
||||||
button(icon_cache_get("window-close-symbolic", 16))
|
button(icon_cache_get("edit-clear-symbolic", 16))
|
||||||
.on_press(Message::Find(false))
|
.on_press(Message::FindSearchValueChanged(String::new()))
|
||||||
.padding(space_xxs)
|
.style(style::Button::Icon)
|
||||||
.style(style::Button::Icon)
|
.into(),
|
||||||
|
);
|
||||||
|
let find_widget = widget::row::with_children(vec![
|
||||||
|
find_input.into(),
|
||||||
|
widget::tooltip(
|
||||||
|
button(icon_cache_get("go-up-symbolic", 16))
|
||||||
|
.on_press(Message::FindPrevious)
|
||||||
|
.padding(space_xxs)
|
||||||
|
.style(style::Button::Icon),
|
||||||
|
fl!("find-previous"),
|
||||||
|
widget::tooltip::Position::Top,
|
||||||
|
)
|
||||||
.into(),
|
.into(),
|
||||||
])
|
widget::tooltip(
|
||||||
.align_items(Alignment::Center)
|
button(icon_cache_get("go-down-symbolic", 16))
|
||||||
|
.on_press(Message::FindNext)
|
||||||
|
.padding(space_xxs)
|
||||||
|
.style(style::Button::Icon),
|
||||||
|
fl!("find-next"),
|
||||||
|
widget::tooltip::Position::Top,
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
widget::horizontal_space(Length::Fill).into(),
|
||||||
|
button(icon_cache_get("window-close-symbolic", 16))
|
||||||
|
.on_press(Message::Find(false))
|
||||||
|
.padding(space_xxs)
|
||||||
|
.style(style::Button::Icon)
|
||||||
|
.into(),
|
||||||
|
])
|
||||||
|
.align_items(Alignment::Center)
|
||||||
|
.padding(space_xxs)
|
||||||
|
.spacing(space_xxs);
|
||||||
|
|
||||||
|
tab_column = tab_column.push(
|
||||||
|
widget::cosmic_container::container(find_widget)
|
||||||
|
.layer(cosmic_theme::Layer::Primary),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pane_grid::Content::new(tab_column)
|
||||||
|
})
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.spacing(space_xxs)
|
||||||
|
.on_click(Message::PaneClicked)
|
||||||
|
.on_resize(space_xxs, Message::PaneResized)
|
||||||
|
.on_drag(Message::PaneDragged);
|
||||||
|
|
||||||
|
container(pane_grid)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
.padding(space_xxs)
|
.padding(space_xxs)
|
||||||
.spacing(space_xxs);
|
.into()
|
||||||
|
|
||||||
tab_column = tab_column.push(
|
|
||||||
widget::cosmic_container::container(find_widget)
|
|
||||||
.layer(cosmic_theme::Layer::Primary),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let content: Element<_> = tab_column.into();
|
|
||||||
|
|
||||||
// Uncomment to debug layout:
|
|
||||||
//content.explain(cosmic::iced::Color::WHITE)
|
|
||||||
content
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Self::Message> {
|
fn subscription(&self) -> Subscription<Self::Message> {
|
||||||
|
|
@ -1296,6 +1472,76 @@ impl Application for App {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Event::Keyboard(KeyEvent::KeyPressed {
|
||||||
|
key_code: KeyCode::R,
|
||||||
|
modifiers,
|
||||||
|
}) => {
|
||||||
|
if modifiers == Modifiers::CTRL | Modifiers::ALT {
|
||||||
|
Some(Message::PaneSplit(pane_grid::Axis::Vertical))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Keyboard(KeyEvent::KeyPressed {
|
||||||
|
key_code: KeyCode::D,
|
||||||
|
modifiers,
|
||||||
|
}) => {
|
||||||
|
if modifiers == Modifiers::CTRL | Modifiers::ALT {
|
||||||
|
Some(Message::PaneSplit(pane_grid::Axis::Horizontal))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Keyboard(KeyEvent::KeyPressed {
|
||||||
|
key_code: KeyCode::X,
|
||||||
|
modifiers,
|
||||||
|
}) => {
|
||||||
|
if modifiers == Modifiers::CTRL | Modifiers::SHIFT {
|
||||||
|
Some(Message::PaneToggleMaximized)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Keyboard(KeyEvent::KeyPressed {
|
||||||
|
key_code: KeyCode::Left,
|
||||||
|
modifiers,
|
||||||
|
}) => {
|
||||||
|
if modifiers == Modifiers::CTRL | Modifiers::SHIFT {
|
||||||
|
Some(Message::PaneFocusAdjacent(pane_grid::Direction::Left))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Keyboard(KeyEvent::KeyPressed {
|
||||||
|
key_code: KeyCode::Right,
|
||||||
|
modifiers,
|
||||||
|
}) => {
|
||||||
|
if modifiers == Modifiers::CTRL | Modifiers::SHIFT {
|
||||||
|
Some(Message::PaneFocusAdjacent(pane_grid::Direction::Right))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Keyboard(KeyEvent::KeyPressed {
|
||||||
|
key_code: KeyCode::Up,
|
||||||
|
modifiers,
|
||||||
|
}) => {
|
||||||
|
if modifiers == Modifiers::CTRL | Modifiers::SHIFT {
|
||||||
|
Some(Message::PaneFocusAdjacent(pane_grid::Direction::Up))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Keyboard(KeyEvent::KeyPressed {
|
||||||
|
key_code: KeyCode::Down,
|
||||||
|
modifiers,
|
||||||
|
}) => {
|
||||||
|
if modifiers == Modifiers::CTRL | Modifiers::SHIFT {
|
||||||
|
Some(Message::PaneFocusAdjacent(pane_grid::Direction::Down))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
Event::Keyboard(KeyEvent::KeyPressed {
|
Event::Keyboard(KeyEvent::KeyPressed {
|
||||||
key_code: KeyCode::V,
|
key_code: KeyCode::V,
|
||||||
modifiers,
|
modifiers,
|
||||||
|
|
@ -1354,9 +1600,9 @@ impl Application for App {
|
||||||
// Create first terminal tab
|
// Create first terminal tab
|
||||||
output.send(Message::TabNew).await.unwrap();
|
output.send(Message::TabNew).await.unwrap();
|
||||||
|
|
||||||
while let Some((entity, event)) = event_rx.recv().await {
|
while let Some((pane, entity, event)) = event_rx.recv().await {
|
||||||
output
|
output
|
||||||
.send(Message::TermEvent(entity, event))
|
.send(Message::TermEvent(pane, entity, event))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,10 @@ pub fn context_menu<'a>(config: &Config, entity: segmented_button::Entity) -> El
|
||||||
menu_action(fl!("paste"), Action::Paste),
|
menu_action(fl!("paste"), Action::Paste),
|
||||||
menu_action(fl!("select-all"), Action::SelectAll),
|
menu_action(fl!("select-all"), Action::SelectAll),
|
||||||
horizontal_rule(1),
|
horizontal_rule(1),
|
||||||
|
menu_action(fl!("split-horizontal"), Action::PaneSplitHorizontal),
|
||||||
|
menu_action(fl!("split-vertical"), Action::PaneSplitVertical),
|
||||||
|
menu_action(fl!("pane-toggle-maximize"), Action::PaneToggleMaximized),
|
||||||
|
horizontal_rule(1),
|
||||||
menu_action(fl!("new-tab"), Action::TabNew),
|
menu_action(fl!("new-tab"), Action::TabNew),
|
||||||
menu_action(fl!("settings"), Action::Settings),
|
menu_action(fl!("settings"), Action::Settings),
|
||||||
menu_checkbox(
|
menu_checkbox(
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,10 @@ use alacritty_terminal::{
|
||||||
vte::ansi::{Color, NamedColor, Rgb},
|
vte::ansi::{Color, NamedColor, Rgb},
|
||||||
Term,
|
Term,
|
||||||
};
|
};
|
||||||
use cosmic::{iced::advanced::graphics::text::font_system, widget::segmented_button};
|
use cosmic::{
|
||||||
|
iced::advanced::graphics::text::font_system,
|
||||||
|
widget::{pane_grid, segmented_button},
|
||||||
|
};
|
||||||
use cosmic_text::{
|
use cosmic_text::{
|
||||||
Attrs, AttrsList, Buffer, BufferLine, CacheKeyFlags, Family, Metrics, Shaping, Weight, Wrap,
|
Attrs, AttrsList, Buffer, BufferLine, CacheKeyFlags, Family, Metrics, Shaping, Weight, Wrap,
|
||||||
};
|
};
|
||||||
|
|
@ -67,14 +70,15 @@ impl From<Size> for WindowSize {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct EventProxy(
|
pub struct EventProxy(
|
||||||
|
pane_grid::Pane,
|
||||||
segmented_button::Entity,
|
segmented_button::Entity,
|
||||||
mpsc::Sender<(segmented_button::Entity, Event)>,
|
mpsc::Sender<(pane_grid::Pane, segmented_button::Entity, Event)>,
|
||||||
);
|
);
|
||||||
|
|
||||||
impl EventListener for EventProxy {
|
impl EventListener for EventProxy {
|
||||||
fn send_event(&self, event: Event) {
|
fn send_event(&self, event: Event) {
|
||||||
//TODO: handle error
|
//TODO: handle error
|
||||||
let _ = self.1.blocking_send((self.0, event));
|
let _ = self.2.blocking_send((self.0, self.1, event));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,6 +136,33 @@ fn linear_color(color: cosmic_text::Color) -> cosmic_text::Color {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TabModel = segmented_button::Model<segmented_button::SingleSelect>;
|
||||||
|
pub struct TerminalPaneGrid {
|
||||||
|
pub panes: pane_grid::State<TabModel>,
|
||||||
|
pub panes_created: usize,
|
||||||
|
pub focus: pane_grid::Pane,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TerminalPaneGrid {
|
||||||
|
pub fn new(model: TabModel) -> Self {
|
||||||
|
let (panes, pane) = pane_grid::State::new(model);
|
||||||
|
let mut terminal_ids = HashMap::new();
|
||||||
|
terminal_ids.insert(pane, cosmic::widget::Id::unique());
|
||||||
|
|
||||||
|
Self {
|
||||||
|
panes,
|
||||||
|
panes_created: 1,
|
||||||
|
focus: pane,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn active(&self) -> Option<&TabModel> {
|
||||||
|
self.panes.get(self.focus)
|
||||||
|
}
|
||||||
|
pub fn active_mut(&mut self) -> Option<&mut TabModel> {
|
||||||
|
self.panes.get_mut(self.focus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Terminal {
|
pub struct Terminal {
|
||||||
default_attrs: Attrs<'static>,
|
default_attrs: Attrs<'static>,
|
||||||
buffer: Arc<Buffer>,
|
buffer: Arc<Buffer>,
|
||||||
|
|
@ -151,8 +182,9 @@ pub struct Terminal {
|
||||||
impl Terminal {
|
impl Terminal {
|
||||||
//TODO: error handling
|
//TODO: error handling
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
pane: pane_grid::Pane,
|
||||||
entity: segmented_button::Entity,
|
entity: segmented_button::Entity,
|
||||||
event_tx: mpsc::Sender<(segmented_button::Entity, Event)>,
|
event_tx: mpsc::Sender<(pane_grid::Pane, segmented_button::Entity, Event)>,
|
||||||
config: Config,
|
config: Config,
|
||||||
options: Options,
|
options: Options,
|
||||||
app_config: &AppConfig,
|
app_config: &AppConfig,
|
||||||
|
|
@ -191,7 +223,7 @@ impl Terminal {
|
||||||
cell_width,
|
cell_width,
|
||||||
cell_height,
|
cell_height,
|
||||||
};
|
};
|
||||||
let event_proxy = EventProxy(entity, event_tx);
|
let event_proxy = EventProxy(pane, entity, event_tx);
|
||||||
let term = Arc::new(FairMutex::new(Term::new(
|
let term = Arc::new(FairMutex::new(Term::new(
|
||||||
config,
|
config,
|
||||||
&size,
|
&size,
|
||||||
|
|
|
||||||
|
|
@ -251,6 +251,17 @@ where
|
||||||
// Render default background
|
// Render default background
|
||||||
{
|
{
|
||||||
let background_color = cosmic_text::Color(terminal.default_attrs().metadata as u32);
|
let background_color = cosmic_text::Color(terminal.default_attrs().metadata as u32);
|
||||||
|
//TODO: get shaded background color from theme
|
||||||
|
let mut shade: f32 = 1.0;
|
||||||
|
if !state.is_focused {
|
||||||
|
shade = 0.90;
|
||||||
|
}
|
||||||
|
let background = Color::new(
|
||||||
|
background_color.r() as f32 * shade / 255.0,
|
||||||
|
background_color.g() as f32 * shade / 255.0,
|
||||||
|
background_color.b() as f32 * shade / 255.0,
|
||||||
|
background_color.a() as f32 * shade / 255.0,
|
||||||
|
);
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
Quad {
|
Quad {
|
||||||
bounds: Rectangle::new(
|
bounds: Rectangle::new(
|
||||||
|
|
@ -261,12 +272,7 @@ where
|
||||||
border_width: 0.0,
|
border_width: 0.0,
|
||||||
border_color: Color::TRANSPARENT,
|
border_color: Color::TRANSPARENT,
|
||||||
},
|
},
|
||||||
Color::new(
|
background,
|
||||||
background_color.r() as f32 / 255.0,
|
|
||||||
background_color.g() as f32 / 255.0,
|
|
||||||
background_color.b() as f32 / 255.0,
|
|
||||||
background_color.a() as f32 / 255.0,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -711,6 +717,21 @@ where
|
||||||
(true, _, _, _) => {
|
(true, _, _, _) => {
|
||||||
// Ignore super
|
// Ignore super
|
||||||
}
|
}
|
||||||
|
(false, true, true, false) => {
|
||||||
|
// Handle ctrl-alt for non-control characters
|
||||||
|
// Or should I try to minimize this to only
|
||||||
|
// catch control sequences that conflicts with
|
||||||
|
// keykodes for Split
|
||||||
|
// if character != '\u{4}' && character != '\u{12}' {
|
||||||
|
// is there any valid case for control characters with modifers
|
||||||
|
// ctrl-alt?
|
||||||
|
if !character.is_control() {
|
||||||
|
let mut buf = [0, 0, 0, 0];
|
||||||
|
let str = character.encode_utf8(&mut buf);
|
||||||
|
terminal.input_scroll(str.as_bytes().to_vec());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
}
|
||||||
(false, true, _, false) => {
|
(false, true, _, false) => {
|
||||||
// Handle ctrl for control characters (Ctrl-A to Ctrl-Z)
|
// Handle ctrl for control characters (Ctrl-A to Ctrl-Z)
|
||||||
if character.is_control() {
|
if character.is_control() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue