Merge branch 'master' into primary
This commit is contained in:
commit
d38ac0a0f7
10 changed files with 1509 additions and 426 deletions
528
Cargo.lock
generated
528
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -2,6 +2,7 @@
|
||||||
name = "cosmic-term"
|
name = "cosmic-term"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
rust-version = "1.71"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
|
@ -27,7 +28,6 @@ git = "https://github.com/pop-os/cosmic-text.git"
|
||||||
git = "https://github.com/pop-os/libcosmic.git"
|
git = "https://github.com/pop-os/libcosmic.git"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["tokio", "winit"]
|
features = ["tokio", "winit"]
|
||||||
#path = "../libcosmic"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["wgpu"]
|
default = ["wgpu"]
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,29 @@ theme = Theme
|
||||||
match-desktop = Match desktop
|
match-desktop = Match desktop
|
||||||
dark = Dark
|
dark = Dark
|
||||||
light = Light
|
light = Light
|
||||||
syntax-dark = Syntax dark
|
syntax-dark = Color scheme dark
|
||||||
syntax-light = Syntax light
|
syntax-light = Color scheme light
|
||||||
|
default-zoom-step = Zoom steps
|
||||||
|
|
||||||
|
### Font
|
||||||
|
font = Font
|
||||||
advanced-font-settings = Advanced Font Settings
|
advanced-font-settings = Advanced Font Settings
|
||||||
default-font = Default font
|
default-font = Font
|
||||||
default-font-stretch = Default font stretch
|
default-font-size = Font size
|
||||||
default-font-weight = Default font weight
|
default-font-stretch = Font stretch
|
||||||
default-dim-font-weight = Default dim font weight
|
default-font-weight = Normal font weight
|
||||||
default-bold-font-weight = Default bold font weight
|
default-dim-font-weight = Dim font weight
|
||||||
use-bright-bold = Use bright colors with bold text
|
default-bold-font-weight = Bold font weight
|
||||||
default-font-size = Default font size
|
use-bright-bold = Make bold text brighter
|
||||||
default-zoom-step = Default zoom step
|
|
||||||
|
### Splits
|
||||||
|
splits = Splits
|
||||||
|
focus-follow-mouse = Typing focus follows mouse
|
||||||
|
|
||||||
|
### Advanced
|
||||||
|
advanced = Advanced
|
||||||
|
show-headerbar = Show header
|
||||||
|
show-header-description = Reveal the header from the right-click menu.
|
||||||
|
|
||||||
# Find
|
# Find
|
||||||
find-placeholder = Find...
|
find-placeholder = Find...
|
||||||
|
|
@ -53,6 +65,3 @@ split-horizontal = Split horizontal
|
||||||
split-vertical = Split vertical
|
split-vertical = Split vertical
|
||||||
pane-toggle-maximize = Toggle maximized
|
pane-toggle-maximize = Toggle maximized
|
||||||
menu-settings = Settings...
|
menu-settings = Settings...
|
||||||
|
|
||||||
# Context menu
|
|
||||||
show-headerbar = Show header bar
|
|
||||||
|
|
|
||||||
50
i18n/ja/cosmic_term.ftl
Normal file
50
i18n/ja/cosmic_term.ftl
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# Context Pages
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
settings = 設定
|
||||||
|
|
||||||
|
### Appearance
|
||||||
|
appearance = 外観
|
||||||
|
theme = テーマ
|
||||||
|
match-desktop = デスクトップに合わす
|
||||||
|
dark = 暗い
|
||||||
|
light = 明かり
|
||||||
|
syntax-dark = 暗いシンタックス
|
||||||
|
syntax-light = 明かりシンタックス
|
||||||
|
advanced-font-settings = 詳細なフォント設定
|
||||||
|
default-font = デフォルトフォント
|
||||||
|
default-font-stretch = デフォルトのフォント幅
|
||||||
|
default-font-weight = デフォルトのフォントの太さ
|
||||||
|
default-dim-font-weight = デフォルトの暗いフォントの太さ
|
||||||
|
default-bold-font-weight = デフォルトの太字の太さ
|
||||||
|
use-bright-bold = 太字を明るい色で表示する
|
||||||
|
default-font-size = デフォルトのフォントサイズ
|
||||||
|
default-zoom-step =ズームの間隔
|
||||||
|
|
||||||
|
# Find
|
||||||
|
find-placeholder = 検索...
|
||||||
|
find-previous = 前を検索
|
||||||
|
find-next = 次を検索
|
||||||
|
|
||||||
|
# Menu
|
||||||
|
|
||||||
|
## File
|
||||||
|
file = ファイル
|
||||||
|
new-tab = 新しいタブ
|
||||||
|
new-window = 新しいウィンドウ
|
||||||
|
close-tab = タブを閉じる
|
||||||
|
quit = 終了
|
||||||
|
|
||||||
|
## Edit
|
||||||
|
edit = 編集
|
||||||
|
copy = コピー
|
||||||
|
paste = 貼り付け
|
||||||
|
select-all = すべて選択
|
||||||
|
find = 検索
|
||||||
|
|
||||||
|
## View
|
||||||
|
view = 表示
|
||||||
|
menu-settings = 設定...
|
||||||
|
|
||||||
|
# Context menu
|
||||||
|
show-headerbar = ヘッダーバーを表す
|
||||||
|
|
@ -43,6 +43,7 @@ pub struct Config {
|
||||||
pub use_bright_bold: bool,
|
pub use_bright_bold: bool,
|
||||||
pub syntax_theme_dark: String,
|
pub syntax_theme_dark: String,
|
||||||
pub syntax_theme_light: String,
|
pub syntax_theme_light: String,
|
||||||
|
pub focus_follow_mouse: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
|
|
@ -60,6 +61,7 @@ impl Default for Config {
|
||||||
use_bright_bold: false,
|
use_bright_bold: false,
|
||||||
syntax_theme_dark: "COSMIC Dark".to_string(),
|
syntax_theme_dark: "COSMIC Dark".to_string(),
|
||||||
syntax_theme_light: "COSMIC Light".to_string(),
|
syntax_theme_light: "COSMIC Light".to_string(),
|
||||||
|
focus_follow_mouse: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
231
src/main.rs
231
src/main.rs
|
|
@ -15,7 +15,7 @@ use cosmic::{
|
||||||
keyboard::{Event as KeyEvent, KeyCode, Modifiers},
|
keyboard::{Event as KeyEvent, KeyCode, Modifiers},
|
||||||
mouse::{Button as MouseButton, Event as MouseEvent},
|
mouse::{Button as MouseButton, Event as MouseEvent},
|
||||||
subscription::{self, Subscription},
|
subscription::{self, Subscription},
|
||||||
window, Alignment, Event, Length, Padding, Point,
|
window, Alignment, Color, Event, Length, Limits, Padding, Point,
|
||||||
},
|
},
|
||||||
style,
|
style,
|
||||||
widget::{self, button, container, pane_grid, segmented_button, PaneGrid},
|
widget::{self, button, container, pane_grid, segmented_button, PaneGrid},
|
||||||
|
|
@ -26,13 +26,14 @@ use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
collections::{BTreeMap, BTreeSet, HashMap},
|
collections::{BTreeMap, BTreeSet, HashMap},
|
||||||
env, process,
|
env, process,
|
||||||
sync::Mutex,
|
sync::{atomic::Ordering, Mutex},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
use config::{AppTheme, Config, CONFIG_VERSION};
|
use config::{AppTheme, Config, CONFIG_VERSION};
|
||||||
mod config;
|
mod config;
|
||||||
|
mod mouse_reporter;
|
||||||
|
|
||||||
use icon_cache::IconCache;
|
use icon_cache::IconCache;
|
||||||
mod icon_cache;
|
mod icon_cache;
|
||||||
|
|
@ -141,8 +142,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
settings = settings.client_decorations(false);
|
settings = settings.client_decorations(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: allow size limits on iced_winit
|
settings = settings.size_limits(Limits::NONE.min_width(360.0).min_height(180.0));
|
||||||
//settings = settings.size_limits(Limits::NONE.min_width(400.0).min_height(200.0));
|
|
||||||
|
|
||||||
let flags = Flags {
|
let flags = Flags {
|
||||||
config_handler,
|
config_handler,
|
||||||
|
|
@ -267,6 +267,7 @@ pub enum Message {
|
||||||
PaneDragged(pane_grid::DragEvent),
|
PaneDragged(pane_grid::DragEvent),
|
||||||
PaneResized(pane_grid::ResizeEvent),
|
PaneResized(pane_grid::ResizeEvent),
|
||||||
Modifiers(Modifiers),
|
Modifiers(Modifiers),
|
||||||
|
MouseEnter(pane_grid::Pane),
|
||||||
Paste(Option<segmented_button::Entity>),
|
Paste(Option<segmented_button::Entity>),
|
||||||
PastePrimary(Option<segmented_button::Entity>),
|
PastePrimary(Option<segmented_button::Entity>),
|
||||||
PasteValue(Option<segmented_button::Entity>, String),
|
PasteValue(Option<segmented_button::Entity>, String),
|
||||||
|
|
@ -274,7 +275,7 @@ pub enum Message {
|
||||||
UseBrightBold(bool),
|
UseBrightBold(bool),
|
||||||
ShowHeaderBar(bool),
|
ShowHeaderBar(bool),
|
||||||
SyntaxTheme(usize, bool),
|
SyntaxTheme(usize, bool),
|
||||||
SystemThemeModeChange(cosmic_theme::ThemeMode),
|
SystemThemeChange,
|
||||||
TabActivate(segmented_button::Entity),
|
TabActivate(segmented_button::Entity),
|
||||||
TabActivateJump(usize),
|
TabActivateJump(usize),
|
||||||
TabClose(Option<segmented_button::Entity>),
|
TabClose(Option<segmented_button::Entity>),
|
||||||
|
|
@ -292,6 +293,7 @@ pub enum Message {
|
||||||
ZoomIn,
|
ZoomIn,
|
||||||
ZoomOut,
|
ZoomOut,
|
||||||
ZoomReset,
|
ZoomReset,
|
||||||
|
FocusFollowMouse(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
|
@ -530,6 +532,67 @@ impl App {
|
||||||
.iter()
|
.iter()
|
||||||
.position(|zoom_step| zoom_step == &self.config.font_size_zoom_step_mul_100);
|
.position(|zoom_step| zoom_step == &self.config.font_size_zoom_step_mul_100);
|
||||||
|
|
||||||
|
let appearance_section = widget::settings::view_section(fl!("appearance"))
|
||||||
|
.add(
|
||||||
|
widget::settings::item::builder(fl!("theme")).control(widget::dropdown(
|
||||||
|
&self.app_themes,
|
||||||
|
Some(app_theme_selected),
|
||||||
|
move |index| {
|
||||||
|
Message::AppTheme(match index {
|
||||||
|
1 => AppTheme::Dark,
|
||||||
|
2 => AppTheme::Light,
|
||||||
|
_ => AppTheme::System,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
//TODO: rename to color-scheme-dark?
|
||||||
|
widget::settings::item::builder(fl!("syntax-dark")).control(widget::dropdown(
|
||||||
|
&self.theme_names,
|
||||||
|
dark_selected,
|
||||||
|
move |index| Message::SyntaxTheme(index, true),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
//TODO: rename to color-scheme-light?
|
||||||
|
widget::settings::item::builder(fl!("syntax-light")).control(widget::dropdown(
|
||||||
|
&self.theme_names,
|
||||||
|
light_selected,
|
||||||
|
move |index| Message::SyntaxTheme(index, false),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
widget::settings::item::builder(fl!("default-zoom-step")).control(
|
||||||
|
widget::dropdown(&self.zoom_step_names, zoom_step_selected, |index| {
|
||||||
|
Message::DefaultZoomStep(index)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
//TODO: background opacity
|
||||||
|
|
||||||
|
let mut font_section = widget::settings::view_section(fl!("font"))
|
||||||
|
.add(
|
||||||
|
widget::settings::item::builder(fl!("default-font")).control(widget::dropdown(
|
||||||
|
&self.font_names,
|
||||||
|
font_selected,
|
||||||
|
|index| Message::DefaultFont(index),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
widget::settings::item::builder(fl!("default-font-size")).control(
|
||||||
|
widget::dropdown(&self.font_size_names, font_size_selected, |index| {
|
||||||
|
Message::DefaultFontSize(index)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
widget::settings::item::builder(fl!("advanced-font-settings")).toggler(
|
||||||
|
self.show_advanced_font_settings,
|
||||||
|
Message::ShowAdvancedFontSettings,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
let advanced_font_settings = || {
|
let advanced_font_settings = || {
|
||||||
let section = widget::settings::view_section("")
|
let section = widget::settings::view_section("")
|
||||||
.add(
|
.add(
|
||||||
|
|
@ -567,6 +630,10 @@ impl App {
|
||||||
|index| Message::DefaultBoldFontWeight(index),
|
|index| Message::DefaultBoldFontWeight(index),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
widget::settings::item::builder(fl!("use-bright-bold"))
|
||||||
|
.toggler(self.config.use_bright_bold, Message::UseBrightBold),
|
||||||
);
|
);
|
||||||
let padding = Padding {
|
let padding = Padding {
|
||||||
top: 0.0,
|
top: 0.0,
|
||||||
|
|
@ -577,77 +644,28 @@ impl App {
|
||||||
widget::container(section).padding(padding)
|
widget::container(section).padding(padding)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut settings_view = widget::settings::view_section(fl!("appearance"))
|
|
||||||
.add(
|
|
||||||
widget::settings::item::builder(fl!("theme")).control(widget::dropdown(
|
|
||||||
&self.app_themes,
|
|
||||||
Some(app_theme_selected),
|
|
||||||
move |index| {
|
|
||||||
Message::AppTheme(match index {
|
|
||||||
1 => AppTheme::Dark,
|
|
||||||
2 => AppTheme::Light,
|
|
||||||
_ => AppTheme::System,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.add(
|
|
||||||
widget::settings::item::builder(fl!("syntax-dark")).control(widget::dropdown(
|
|
||||||
&self.theme_names,
|
|
||||||
dark_selected,
|
|
||||||
move |index| Message::SyntaxTheme(index, true),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.add(
|
|
||||||
widget::settings::item::builder(fl!("syntax-light")).control(widget::dropdown(
|
|
||||||
&self.theme_names,
|
|
||||||
light_selected,
|
|
||||||
move |index| Message::SyntaxTheme(index, false),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.add(
|
|
||||||
widget::settings::item::builder(fl!("default-font")).control(widget::dropdown(
|
|
||||||
&self.font_names,
|
|
||||||
font_selected,
|
|
||||||
|index| Message::DefaultFont(index),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.add(
|
|
||||||
widget::settings::item::builder(fl!("advanced-font-settings")).toggler(
|
|
||||||
self.show_advanced_font_settings,
|
|
||||||
Message::ShowAdvancedFontSettings,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.show_advanced_font_settings {
|
if self.show_advanced_font_settings {
|
||||||
settings_view = settings_view.add(advanced_font_settings());
|
font_section = font_section.add(advanced_font_settings());
|
||||||
}
|
}
|
||||||
|
|
||||||
let settings_view = settings_view
|
let splits_section = widget::settings::view_section(fl!("splits")).add(
|
||||||
.add(
|
widget::settings::item::builder(fl!("focus-follow-mouse"))
|
||||||
widget::settings::item::builder(fl!("use-bright-bold"))
|
.toggler(self.config.focus_follow_mouse, Message::FocusFollowMouse),
|
||||||
.toggler(self.config.use_bright_bold, Message::UseBrightBold),
|
);
|
||||||
)
|
|
||||||
.add(
|
|
||||||
widget::settings::item::builder(fl!("default-font-size")).control(
|
|
||||||
widget::dropdown(&self.font_size_names, font_size_selected, |index| {
|
|
||||||
Message::DefaultFontSize(index)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.add(
|
|
||||||
widget::settings::item::builder(fl!("default-zoom-step")).control(
|
|
||||||
widget::dropdown(&self.zoom_step_names, zoom_step_selected, |index| {
|
|
||||||
Message::DefaultZoomStep(index)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.add(
|
|
||||||
widget::settings::item::builder(fl!("show-headerbar"))
|
|
||||||
.toggler(self.config.show_headerbar, Message::ShowHeaderBar),
|
|
||||||
);
|
|
||||||
|
|
||||||
widget::settings::view_column(vec![settings_view.into()]).into()
|
let advanced_section = widget::settings::view_section(fl!("advanced")).add(
|
||||||
|
widget::settings::item::builder(fl!("show-headerbar"))
|
||||||
|
.description(fl!("show-header-description"))
|
||||||
|
.toggler(self.config.show_headerbar, Message::ShowHeaderBar),
|
||||||
|
);
|
||||||
|
|
||||||
|
widget::settings::view_column(vec![
|
||||||
|
appearance_section.into(),
|
||||||
|
font_section.into(),
|
||||||
|
splits_section.into(),
|
||||||
|
advanced_section.into(),
|
||||||
|
])
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_and_focus_new_terminal(&mut self, pane: pane_grid::Pane) {
|
fn create_and_focus_new_terminal(&mut self, pane: pane_grid::Pane) {
|
||||||
|
|
@ -719,8 +737,7 @@ impl Application for App {
|
||||||
|
|
||||||
/// Creates the application, and optionally emits command on initialize.
|
/// Creates the application, and optionally emits command on initialize.
|
||||||
fn init(mut core: Core, flags: Self::Flags) -> (Self, Command<Self::Message>) {
|
fn init(mut core: Core, flags: Self::Flags) -> (Self, Command<Self::Message>) {
|
||||||
//TODO: fix window resizing interfering with scrolling when not using content container
|
core.window.content_container = false;
|
||||||
//core.window.content_container = false;
|
|
||||||
core.window.show_headerbar = flags.config.show_headerbar;
|
core.window.show_headerbar = flags.config.show_headerbar;
|
||||||
|
|
||||||
// Update font name from config
|
// Update font name from config
|
||||||
|
|
@ -1079,6 +1096,10 @@ impl Application for App {
|
||||||
Message::Modifiers(modifiers) => {
|
Message::Modifiers(modifiers) => {
|
||||||
self.modifiers = modifiers;
|
self.modifiers = modifiers;
|
||||||
}
|
}
|
||||||
|
Message::MouseEnter(pane) => {
|
||||||
|
self.pane_model.focus = pane;
|
||||||
|
return self.update_focus();
|
||||||
|
}
|
||||||
Message::PaneClicked(pane) => {
|
Message::PaneClicked(pane) => {
|
||||||
self.pane_model.focus = pane;
|
self.pane_model.focus = pane;
|
||||||
return self.update_title(Some(pane));
|
return self.update_title(Some(pane));
|
||||||
|
|
@ -1163,7 +1184,13 @@ impl Application for App {
|
||||||
return self.save_config();
|
return self.save_config();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::SystemThemeModeChange(_theme_mode) => {
|
Message::FocusFollowMouse(focus_follow_mouse) => {
|
||||||
|
if focus_follow_mouse != self.config.focus_follow_mouse {
|
||||||
|
self.config.focus_follow_mouse = focus_follow_mouse;
|
||||||
|
return self.save_config();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::SystemThemeChange => {
|
||||||
return self.update_config();
|
return self.update_config();
|
||||||
}
|
}
|
||||||
Message::SyntaxTheme(index, dark) => match self.theme_names.get(index) {
|
Message::SyntaxTheme(index, dark) => match self.theme_names.get(index) {
|
||||||
|
|
@ -1413,9 +1440,28 @@ impl Application for App {
|
||||||
vec![menu_bar(&self.key_binds).into()]
|
vec![menu_bar(&self.key_binds).into()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn header_end(&self) -> Vec<Element<Self::Message>> {
|
||||||
|
let cosmic_theme::Spacing { space_xxs, .. } = self.core().system_theme().cosmic().spacing;
|
||||||
|
vec![widget::button(widget::icon::from_name("list-add-symbolic"))
|
||||||
|
.on_press(Message::TabNew)
|
||||||
|
.padding(space_xxs)
|
||||||
|
.style(style::Button::Icon)
|
||||||
|
.into()]
|
||||||
|
}
|
||||||
|
|
||||||
/// 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 = self.core().system_theme().cosmic();
|
||||||
|
let cosmic_theme::Spacing { space_xxs, .. } = cosmic_theme.spacing;
|
||||||
|
{
|
||||||
|
let color = Color::from(cosmic_theme.bg_color());
|
||||||
|
let bytes = color.into_rgba8();
|
||||||
|
let data = (bytes[2] as u32)
|
||||||
|
| ((bytes[1] as u32) << 8)
|
||||||
|
| ((bytes[0] as u32) << 16)
|
||||||
|
| 0xFF000000;
|
||||||
|
terminal::WINDOW_BG_COLOR.store(data, Ordering::SeqCst);
|
||||||
|
}
|
||||||
let pane_grid = PaneGrid::new(&self.pane_model.panes, |pane, tab_model, _is_maximized| {
|
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);
|
||||||
|
|
||||||
|
|
@ -1442,7 +1488,7 @@ impl Application for App {
|
||||||
.unwrap_or_else(widget::Id::unique);
|
.unwrap_or_else(widget::Id::unique);
|
||||||
match tab_model.data::<Mutex<Terminal>>(entity) {
|
match tab_model.data::<Mutex<Terminal>>(entity) {
|
||||||
Some(terminal) => {
|
Some(terminal) => {
|
||||||
let terminal_box = terminal_box(terminal)
|
let mut terminal_box = terminal_box(terminal)
|
||||||
.id(terminal_id)
|
.id(terminal_id)
|
||||||
.on_context_menu(move |position_opt| {
|
.on_context_menu(move |position_opt| {
|
||||||
Message::TabContextMenu(entity, position_opt)
|
Message::TabContextMenu(entity, position_opt)
|
||||||
|
|
@ -1451,6 +1497,11 @@ impl Application for App {
|
||||||
Message::MiddleClick(pane, Some(entity_middle_click))
|
Message::MiddleClick(pane, Some(entity_middle_click))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if self.config.focus_follow_mouse {
|
||||||
|
terminal_box =
|
||||||
|
terminal_box.on_mouse_enter(move || Message::MouseEnter(pane));
|
||||||
|
}
|
||||||
|
|
||||||
let context_menu = {
|
let context_menu = {
|
||||||
let terminal = terminal.lock().unwrap();
|
let terminal = terminal.lock().unwrap();
|
||||||
terminal.context_menu
|
terminal.context_menu
|
||||||
|
|
@ -1543,6 +1594,7 @@ impl Application for App {
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.padding(space_xxs)
|
.padding(space_xxs)
|
||||||
|
.style(style::Container::Background)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1550,6 +1602,7 @@ impl Application for App {
|
||||||
struct ConfigSubscription;
|
struct ConfigSubscription;
|
||||||
struct TerminalEventSubscription;
|
struct TerminalEventSubscription;
|
||||||
struct ThemeSubscription;
|
struct ThemeSubscription;
|
||||||
|
struct ThemeModeSubscription;
|
||||||
|
|
||||||
Subscription::batch([
|
Subscription::batch([
|
||||||
event::listen_with(|event, _status| match event {
|
event::listen_with(|event, _status| match event {
|
||||||
|
|
@ -1603,21 +1656,23 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
Message::Config(update.config)
|
Message::Config(update.config)
|
||||||
}),
|
}),
|
||||||
cosmic_config::config_subscription::<_, cosmic_theme::ThemeMode>(
|
cosmic_config::config_subscription::<_, cosmic_theme::Theme>(
|
||||||
TypeId::of::<ThemeSubscription>(),
|
TypeId::of::<ThemeSubscription>(),
|
||||||
|
if self.core.system_theme_mode().is_dark {
|
||||||
|
cosmic_theme::DARK_THEME_ID
|
||||||
|
} else {
|
||||||
|
cosmic_theme::LIGHT_THEME_ID
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
cosmic_theme::Theme::version(),
|
||||||
|
)
|
||||||
|
.map(|_update| Message::SystemThemeChange),
|
||||||
|
cosmic_config::config_subscription::<_, cosmic_theme::ThemeMode>(
|
||||||
|
TypeId::of::<ThemeModeSubscription>(),
|
||||||
cosmic_theme::THEME_MODE_ID.into(),
|
cosmic_theme::THEME_MODE_ID.into(),
|
||||||
cosmic_theme::ThemeMode::version(),
|
cosmic_theme::ThemeMode::version(),
|
||||||
)
|
)
|
||||||
.map(|update| {
|
.map(|_update| Message::SystemThemeChange),
|
||||||
if !update.errors.is_empty() {
|
|
||||||
log::debug!(
|
|
||||||
"errors loading theme mode {:?}: {:?}",
|
|
||||||
update.keys,
|
|
||||||
update.errors
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Message::SystemThemeModeChange(update.config)
|
|
||||||
}),
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
238
src/mouse_reporter.rs
Normal file
238
src/mouse_reporter.rs
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
use cosmic::{
|
||||||
|
iced::mouse::{Event as MouseEvent, ScrollDelta},
|
||||||
|
iced::{keyboard::Modifiers, mouse::Button, Event},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::terminal::Terminal;
|
||||||
|
|
||||||
|
const SCROLL_SPEED: u32 = 3;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct MouseReporter {
|
||||||
|
last_movment_x: Option<u32>,
|
||||||
|
last_movment_y: Option<u32>,
|
||||||
|
button: Option<Button>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MouseReporter {
|
||||||
|
fn button_number(&self, button: Button) -> Option<u8> {
|
||||||
|
match button {
|
||||||
|
Button::Left => Some(0),
|
||||||
|
Button::Middle => Some(1),
|
||||||
|
Button::Right => Some(2),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Implemented according to
|
||||||
|
//https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||||
|
pub fn normal_mouse_code(
|
||||||
|
&mut self,
|
||||||
|
event: Event,
|
||||||
|
modifiers: &Modifiers,
|
||||||
|
is_utf8: bool,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
) -> Option<Vec<u8>> {
|
||||||
|
//Buttons are handle slightly different between normal and sgr
|
||||||
|
//for normal/utf8 the button release is always reported as button 3
|
||||||
|
let Some(mut button) = (match event {
|
||||||
|
Event::Mouse(MouseEvent::ButtonPressed(b)) => {
|
||||||
|
self.button = Some(b);
|
||||||
|
self.button_number(b)
|
||||||
|
}
|
||||||
|
Event::Mouse(MouseEvent::ButtonReleased(_b)) => {
|
||||||
|
self.button = None;
|
||||||
|
Some(3)
|
||||||
|
}
|
||||||
|
Event::Mouse(MouseEvent::CursorMoved { .. }) => {
|
||||||
|
//Button pressed is reported as button 32 + 0,1,2 and event code M
|
||||||
|
//And only reported if a button is previously pressed
|
||||||
|
if (self.last_movment_x, self.last_movment_y) == (Some(x), Some(y)) {
|
||||||
|
return None;
|
||||||
|
} else {
|
||||||
|
self.last_movment_x = Some(x);
|
||||||
|
self.last_movment_y = Some(y);
|
||||||
|
}
|
||||||
|
//It seems that we should add 32 to signal movement even for normal mode
|
||||||
|
//On button-motion events, xterm adds 32 to the event code (the third
|
||||||
|
//character, Cb).
|
||||||
|
//For example, motion into cell x,y with button 1 down is reported as
|
||||||
|
//CSI M @ CxCy ( @ = 32 + 0 (button 1) + 32 (motion indicator) ).
|
||||||
|
self.button
|
||||||
|
.and_then(|button| self.button_number(button))
|
||||||
|
.map(|b| b + 32)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
if modifiers.shift() {
|
||||||
|
button += 4;
|
||||||
|
}
|
||||||
|
if modifiers.alt() {
|
||||||
|
button += 8;
|
||||||
|
}
|
||||||
|
if modifiers.control() {
|
||||||
|
button += 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Normal mode have a max of 223 (255 - 32), while utf8 extend this to 2015
|
||||||
|
let max_point: usize = if is_utf8 { 2015 } else { 223 };
|
||||||
|
if x as usize >= max_point || y as usize >= max_point {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let utf8_encode_and_append = |mut pos: u32, dest: &mut Vec<u8>| {
|
||||||
|
pos += 1 + 32;
|
||||||
|
let mut utf8 = [0; 2]; //This is large enough since we have a max of 2015
|
||||||
|
dest.extend_from_slice(
|
||||||
|
(char::from_u32(pos).unwrap()) //This unwrap and encode_utf8 is safe due to our
|
||||||
|
//specific range, pos will max be 2047
|
||||||
|
.encode_utf8(&mut utf8)
|
||||||
|
.as_bytes(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//SPEC: Likewise, Cb will be UTF-8 encoded, to reduce confusion with wheel mouse events.
|
||||||
|
//Always, or only when the the cooardinates is used? No other terminal seems to do this a
|
||||||
|
//all? Doing what they are doing for now.
|
||||||
|
let mut buf: Vec<u8> = vec![b'\x1b', b'[', b'M', 32 + button];
|
||||||
|
//Should we remove 32+button from previous line, and use this instead? Or only on >= 95
|
||||||
|
//utf8_encode_and_append(32 + button, &mut buf);
|
||||||
|
|
||||||
|
//For utf8 spec say: For positions less than 95, the resulting output is identical under both modes.
|
||||||
|
//But also: Under normal mouse mode, positions outside (160,94) result in byte pairs which can be interpreted as a single UTF-8
|
||||||
|
if is_utf8 && x >= 95 {
|
||||||
|
utf8_encode_and_append(x, &mut buf);
|
||||||
|
} else {
|
||||||
|
//SPEC: For positions less than 95, the resulting output is identical under both modes.
|
||||||
|
buf.push(32 + 1 + x as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_utf8 && y >= 95 {
|
||||||
|
utf8_encode_and_append(y, &mut buf);
|
||||||
|
} else {
|
||||||
|
//SPEC For positions less than 95, the resulting output is identical under both modes.
|
||||||
|
buf.push(32 + 1 + y as u8);
|
||||||
|
}
|
||||||
|
Some(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sgr_mouse_code(
|
||||||
|
&mut self,
|
||||||
|
event: Event,
|
||||||
|
modifiers: &Modifiers,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
) -> Option<Vec<u8>> {
|
||||||
|
let Some((button_no, event_code)) = (match event {
|
||||||
|
Event::Mouse(MouseEvent::ButtonPressed(button)) => {
|
||||||
|
//Button pressed is reported as button 0,1,2 and event code M
|
||||||
|
self.button = Some(button);
|
||||||
|
Some((self.button_number(button), "M"))
|
||||||
|
}
|
||||||
|
Event::Mouse(MouseEvent::ButtonReleased(button)) => {
|
||||||
|
//Button pressed is reported as button 0,1,2 and event code m
|
||||||
|
self.button = None;
|
||||||
|
Some((self.button_number(button), "m"))
|
||||||
|
}
|
||||||
|
Event::Mouse(MouseEvent::CursorMoved { .. }) => {
|
||||||
|
//Button pressed is reported as button 32 + 0,1,2 and event code M
|
||||||
|
//And only reported if a button is previously pressed
|
||||||
|
if (self.last_movment_x, self.last_movment_y) == (Some(x), Some(y)) {
|
||||||
|
return None;
|
||||||
|
} else {
|
||||||
|
self.last_movment_x = Some(x);
|
||||||
|
self.last_movment_y = Some(y);
|
||||||
|
}
|
||||||
|
self.button
|
||||||
|
.map(|button| (self.button_number(button).map(|b| b + 32), "M"))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(mut button_no) = button_no {
|
||||||
|
if modifiers.shift() {
|
||||||
|
button_no += 4;
|
||||||
|
}
|
||||||
|
if modifiers.alt() {
|
||||||
|
button_no += 8;
|
||||||
|
}
|
||||||
|
if modifiers.control() {
|
||||||
|
button_no += 16;
|
||||||
|
}
|
||||||
|
let term_code = format!("\x1b[<{};{};{}{}", button_no, x + 1, y + 1, event_code);
|
||||||
|
Some(term_code.as_bytes().to_vec())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn report_sgr_mouse_wheel_scroll(
|
||||||
|
&self,
|
||||||
|
terminal: &Terminal,
|
||||||
|
term_cell_width: f32,
|
||||||
|
term_cell_height: f32,
|
||||||
|
delta: ScrollDelta,
|
||||||
|
modifiers: &Modifiers,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
) {
|
||||||
|
let (delta_x, delta_y) = match delta {
|
||||||
|
ScrollDelta::Lines { x, y } => (x, y),
|
||||||
|
ScrollDelta::Pixels { x, y } => (x / term_cell_width, y / term_cell_height),
|
||||||
|
};
|
||||||
|
let (mut button_no, amount) = if delta_y > 0.0 {
|
||||||
|
(64, delta_y.abs()) //Wheel UP
|
||||||
|
} else if delta_y < 0.0 {
|
||||||
|
(65, delta_y.abs()) //Wheel Down
|
||||||
|
} else if delta_x < 0.0 {
|
||||||
|
(66, delta_x.abs()) //Wheel Left
|
||||||
|
} else if delta_x > 0.0 {
|
||||||
|
(67, delta_x.abs()) //Wheel Right
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if modifiers.shift() {
|
||||||
|
button_no += 4;
|
||||||
|
}
|
||||||
|
if modifiers.alt() {
|
||||||
|
button_no += 8;
|
||||||
|
}
|
||||||
|
if modifiers.control() {
|
||||||
|
button_no += 16;
|
||||||
|
}
|
||||||
|
let term_code = format!("\x1b[<{};{};{}M", button_no, x + 1, y + 1);
|
||||||
|
for _ in 0..amount as u32 {
|
||||||
|
terminal.input_no_scroll(term_code.as_bytes().to_vec());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Emulate mouse wheel scroll with up/down arrows. Using mouse spec uses
|
||||||
|
//scroll-back and scroll-forw actions, which moves whole windows like page up/page down.
|
||||||
|
pub fn report_mouse_wheel_as_arrows(
|
||||||
|
&self,
|
||||||
|
terminal: &Terminal,
|
||||||
|
term_cell_width: f32,
|
||||||
|
term_cell_height: f32,
|
||||||
|
delta: ScrollDelta,
|
||||||
|
) {
|
||||||
|
let (_delta_x, delta_y) = match delta {
|
||||||
|
ScrollDelta::Lines { x, y } => (x, y),
|
||||||
|
ScrollDelta::Pixels { x, y } => (x / term_cell_width, y / term_cell_height),
|
||||||
|
};
|
||||||
|
//Send delta_y * SCROLL_SPEED number of Up/Down arrows
|
||||||
|
for _ in 0..(delta_y.abs() as u32 * SCROLL_SPEED) {
|
||||||
|
if delta_y > 0.0 {
|
||||||
|
terminal.input_no_scroll(b"\x1B[A".as_slice())
|
||||||
|
} else if delta_y < 0.0 {
|
||||||
|
terminal.input_no_scroll(b"\x1B[B".as_slice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@ use alacritty_terminal::{
|
||||||
};
|
};
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
iced::advanced::graphics::text::font_system,
|
iced::advanced::graphics::text::font_system,
|
||||||
|
iced::mouse::ScrollDelta,
|
||||||
widget::{pane_grid, segmented_button},
|
widget::{pane_grid, segmented_button},
|
||||||
};
|
};
|
||||||
use cosmic_text::{
|
use cosmic_text::{
|
||||||
|
|
@ -27,14 +28,17 @@ use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
mem,
|
mem,
|
||||||
sync::{Arc, Weak},
|
sync::{
|
||||||
|
atomic::{AtomicU32, Ordering},
|
||||||
|
Arc, Weak,
|
||||||
|
},
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
pub use alacritty_terminal::grid::Scroll as TerminalScroll;
|
pub use alacritty_terminal::grid::Scroll as TerminalScroll;
|
||||||
|
|
||||||
use crate::config::Config as AppConfig;
|
use crate::{config::Config as AppConfig, mouse_reporter::MouseReporter};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Size {
|
pub struct Size {
|
||||||
|
|
@ -97,14 +101,22 @@ fn as_dim(mut color: Color) -> Color {
|
||||||
color
|
color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static WINDOW_BG_COLOR: AtomicU32 = AtomicU32::new(0xFF000000);
|
||||||
|
|
||||||
fn convert_color(colors: &Colors, color: Color) -> cosmic_text::Color {
|
fn convert_color(colors: &Colors, color: Color) -> cosmic_text::Color {
|
||||||
let rgb = match color {
|
let rgb = match color {
|
||||||
Color::Named(named_color) => match colors[named_color] {
|
Color::Named(named_color) => match colors[named_color] {
|
||||||
Some(rgb) => rgb,
|
Some(rgb) => rgb,
|
||||||
None => {
|
None => match named_color {
|
||||||
log::warn!("missing named color {:?}", named_color);
|
NamedColor::Background => {
|
||||||
Rgb::default()
|
// Allow using an unset background
|
||||||
}
|
return cosmic_text::Color(WINDOW_BG_COLOR.load(Ordering::SeqCst));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
log::warn!("missing named color {:?}", named_color);
|
||||||
|
Rgb::default()
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Color::Spec(rgb) => rgb,
|
Color::Spec(rgb) => rgb,
|
||||||
Color::Indexed(index) => match colors[index as usize] {
|
Color::Indexed(index) => match colors[index as usize] {
|
||||||
|
|
@ -189,6 +201,7 @@ pub struct Terminal {
|
||||||
search_regex_opt: Option<RegexSearch>,
|
search_regex_opt: Option<RegexSearch>,
|
||||||
search_value: String,
|
search_value: String,
|
||||||
pub metadata_set: IndexSet<Metadata>,
|
pub metadata_set: IndexSet<Metadata>,
|
||||||
|
mouse_reporter: MouseReporter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Terminal {
|
impl Terminal {
|
||||||
|
|
@ -275,6 +288,7 @@ impl Terminal {
|
||||||
search_regex_opt: None,
|
search_regex_opt: None,
|
||||||
search_value: String::new(),
|
search_value: String::new(),
|
||||||
metadata_set,
|
metadata_set,
|
||||||
|
mouse_reporter: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -633,7 +647,10 @@ impl Terminal {
|
||||||
buffer.set_redraw(true);
|
buffer.set_redraw(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if buffer.lines[line_i].set_text(text.clone(), attrs_list.clone()) {
|
// Tab skip/stop is handled by alacritty_terminal
|
||||||
|
if buffer.lines[line_i]
|
||||||
|
.set_text(text.replace('\t', " "), attrs_list.clone())
|
||||||
|
{
|
||||||
buffer.set_redraw(true);
|
buffer.set_redraw(true);
|
||||||
}
|
}
|
||||||
line_i += 1;
|
line_i += 1;
|
||||||
|
|
@ -773,6 +790,60 @@ impl Terminal {
|
||||||
let term = self.term.lock();
|
let term = self.term.lock();
|
||||||
viewport_to_point(term.grid().display_offset(), point)
|
viewport_to_point(term.grid().display_offset(), point)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn report_mouse(
|
||||||
|
&mut self,
|
||||||
|
event: cosmic::iced::Event,
|
||||||
|
modifiers: &cosmic::iced::keyboard::Modifiers,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
) {
|
||||||
|
let term_lock = self.term.lock();
|
||||||
|
let mode = term_lock.mode();
|
||||||
|
if mode.contains(TermMode::SGR_MOUSE) {
|
||||||
|
if let Some(code) = self.mouse_reporter.sgr_mouse_code(event, modifiers, x, y) {
|
||||||
|
self.input_no_scroll(code)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Some(code) = self.mouse_reporter.normal_mouse_code(
|
||||||
|
event,
|
||||||
|
modifiers,
|
||||||
|
mode.contains(TermMode::UTF8_MOUSE),
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
) {
|
||||||
|
self.input_no_scroll(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn scroll_mouse(
|
||||||
|
&mut self,
|
||||||
|
delta: ScrollDelta,
|
||||||
|
modifiers: &cosmic::iced::keyboard::Modifiers,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
) {
|
||||||
|
let term_lock = self.term.lock();
|
||||||
|
let mode = term_lock.mode();
|
||||||
|
if mode.contains(TermMode::SGR_MOUSE) {
|
||||||
|
self.mouse_reporter.report_sgr_mouse_wheel_scroll(
|
||||||
|
self,
|
||||||
|
self.size().cell_width,
|
||||||
|
self.size().cell_height,
|
||||||
|
delta,
|
||||||
|
modifiers,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.mouse_reporter.report_mouse_wheel_as_arrows(
|
||||||
|
self,
|
||||||
|
self.size().cell_width,
|
||||||
|
self.size().cell_height,
|
||||||
|
delta,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Terminal {
|
impl Drop for Terminal {
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,8 @@ pub struct TerminalBox<'a, Message> {
|
||||||
click_timing: Duration,
|
click_timing: Duration,
|
||||||
context_menu: Option<Point>,
|
context_menu: Option<Point>,
|
||||||
on_context_menu: Option<Box<dyn Fn(Option<Point>) -> Message + 'a>>,
|
on_context_menu: Option<Box<dyn Fn(Option<Point>) -> Message + 'a>>,
|
||||||
|
on_mouse_enter: Option<Box<dyn Fn() -> Message + 'a>>,
|
||||||
|
mouse_inside_boundary: Option<bool>,
|
||||||
on_middle_click: Option<Box<dyn Fn() -> Message + 'a>>,
|
on_middle_click: Option<Box<dyn Fn() -> Message + 'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,6 +64,8 @@ where
|
||||||
click_timing: Duration::from_millis(500),
|
click_timing: Duration::from_millis(500),
|
||||||
context_menu: None,
|
context_menu: None,
|
||||||
on_context_menu: None,
|
on_context_menu: None,
|
||||||
|
on_mouse_enter: None,
|
||||||
|
mouse_inside_boundary: None,
|
||||||
on_middle_click: None,
|
on_middle_click: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -94,6 +98,11 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_enter(mut self, on_mouse_enter: impl Fn() -> Message + 'a) -> Self {
|
||||||
|
self.on_mouse_enter = Some(Box::new(on_mouse_enter));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn on_middle_click(mut self, on_middle_click: impl Fn() -> Message + 'a) -> Self {
|
pub fn on_middle_click(mut self, on_middle_click: impl Fn() -> Message + 'a) -> Self {
|
||||||
self.on_middle_click = Some(Box::new(on_middle_click));
|
self.on_middle_click = Some(Box::new(on_middle_click));
|
||||||
self
|
self
|
||||||
|
|
@ -592,6 +601,7 @@ where
|
||||||
let buffer_size = terminal.with_buffer(|buffer| buffer.size());
|
let buffer_size = terminal.with_buffer(|buffer| buffer.size());
|
||||||
|
|
||||||
let is_app_cursor = terminal.term.lock().mode().contains(TermMode::APP_CURSOR);
|
let is_app_cursor = terminal.term.lock().mode().contains(TermMode::APP_CURSOR);
|
||||||
|
let is_mouse_mode = terminal.term.lock().mode().intersects(TermMode::MOUSE_MODE);
|
||||||
|
|
||||||
let mut status = Status::Ignored;
|
let mut status = Status::Ignored;
|
||||||
match event {
|
match event {
|
||||||
|
|
@ -632,6 +642,10 @@ where
|
||||||
terminal.input_scroll(b"\x1B[1;5H".as_slice());
|
terminal.input_scroll(b"\x1B[1;5H".as_slice());
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
}
|
}
|
||||||
|
KeyCode::Insert => {
|
||||||
|
terminal.input_scroll(b"\x1B[2;5~".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
KeyCode::Delete => {
|
KeyCode::Delete => {
|
||||||
terminal.input_scroll(b"\x1B[3;5~".as_slice());
|
terminal.input_scroll(b"\x1B[3;5~".as_slice());
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
|
|
@ -694,10 +708,102 @@ where
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
(_, _, true, _) => {
|
// Handle alt keys
|
||||||
// Ignore alt keys
|
(_, _, true, _) => match key_code {
|
||||||
//TODO: alt keys for control characters
|
KeyCode::Up => {
|
||||||
}
|
terminal.input_scroll(b"\x1B[1;3A".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::Down => {
|
||||||
|
terminal.input_scroll(b"\x1B[1;3B".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::Right => {
|
||||||
|
terminal.input_scroll(b"\x1B[1;3C".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::Left => {
|
||||||
|
terminal.input_scroll(b"\x1B[1;3D".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::End => {
|
||||||
|
terminal.input_scroll(b"\x1B[1;3F".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::Home => {
|
||||||
|
terminal.input_scroll(b"\x1B[1;3H".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::Insert => {
|
||||||
|
terminal.input_scroll(b"\x1B[2;3~".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::Delete => {
|
||||||
|
terminal.input_scroll(b"\x1B[3;3~".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::PageUp => {
|
||||||
|
terminal.input_scroll(b"\x1B[5;3~".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::PageDown => {
|
||||||
|
terminal.input_scroll(b"\x1B[6;3~".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::F1 => {
|
||||||
|
terminal.input_scroll(b"\x1B[1;3P".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::F2 => {
|
||||||
|
terminal.input_scroll(b"\x1B1;3Q".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::F3 => {
|
||||||
|
terminal.input_scroll(b"\x1B1;3R".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::F4 => {
|
||||||
|
terminal.input_scroll(b"\x1B1;3S".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::F5 => {
|
||||||
|
terminal.input_scroll(b"\x1B[15;3~".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::F6 => {
|
||||||
|
terminal.input_scroll(b"\x1B[17;3~".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::F7 => {
|
||||||
|
terminal.input_scroll(b"\x1B[18;3~".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::F8 => {
|
||||||
|
terminal.input_scroll(b"\x1B[19;3~".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::F9 => {
|
||||||
|
terminal.input_scroll(b"\x1B[20;3~".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::F10 => {
|
||||||
|
terminal.input_scroll(b"\x1B[21;3~".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::F11 => {
|
||||||
|
terminal.input_scroll(b"\x1B[23;3~".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::F12 => {
|
||||||
|
terminal.input_scroll(b"\x1B[24;3~".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
terminal.input_scroll(b"\x1B\x7F".as_slice());
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
// Handle shift keys
|
// Handle shift keys
|
||||||
(_, _, _, true) => match key_code {
|
(_, _, _, true) => match key_code {
|
||||||
KeyCode::End => {
|
KeyCode::End => {
|
||||||
|
|
@ -911,113 +1017,36 @@ where
|
||||||
}
|
}
|
||||||
Event::Mouse(MouseEvent::ButtonPressed(button)) => {
|
Event::Mouse(MouseEvent::ButtonPressed(button)) => {
|
||||||
if let Some(p) = cursor_position.position_in(layout.bounds()) {
|
if let Some(p) = cursor_position.position_in(layout.bounds()) {
|
||||||
state.is_focused = true;
|
let x = p.x - self.padding.left;
|
||||||
|
let y = p.y - self.padding.top;
|
||||||
|
//TODO: better calculation of position
|
||||||
|
let col = x / terminal.size().cell_width;
|
||||||
|
let row = y / terminal.size().cell_height;
|
||||||
|
|
||||||
// Handle left click drag
|
if is_mouse_mode {
|
||||||
if let Button::Left = button {
|
terminal.report_mouse(event, &state.modifiers, col as u32, row as u32);
|
||||||
let x = p.x - self.padding.left;
|
} else {
|
||||||
let y = p.y - self.padding.top;
|
state.is_focused = true;
|
||||||
if x >= 0.0 && x < buffer_size.0 && y >= 0.0 && y < buffer_size.1 {
|
|
||||||
let click_kind =
|
// Handle left click drag
|
||||||
if let Some((click_kind, click_time)) = state.click.take() {
|
if let Button::Left = button {
|
||||||
if click_time.elapsed() < self.click_timing {
|
let x = p.x - self.padding.left;
|
||||||
match click_kind {
|
let y = p.y - self.padding.top;
|
||||||
ClickKind::Single => ClickKind::Double,
|
if x >= 0.0 && x < buffer_size.0 && y >= 0.0 && y < buffer_size.1 {
|
||||||
ClickKind::Double => ClickKind::Triple,
|
let click_kind =
|
||||||
ClickKind::Triple => ClickKind::Single,
|
if let Some((click_kind, click_time)) = state.click.take() {
|
||||||
|
if click_time.elapsed() < self.click_timing {
|
||||||
|
match click_kind {
|
||||||
|
ClickKind::Single => ClickKind::Double,
|
||||||
|
ClickKind::Double => ClickKind::Triple,
|
||||||
|
ClickKind::Triple => ClickKind::Single,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ClickKind::Single
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ClickKind::Single
|
ClickKind::Single
|
||||||
}
|
};
|
||||||
} else {
|
|
||||||
ClickKind::Single
|
|
||||||
};
|
|
||||||
//TODO: better calculation of position
|
|
||||||
let col = x / terminal.size().cell_width;
|
|
||||||
let row = y / terminal.size().cell_height;
|
|
||||||
let location = terminal.viewport_to_point(TermPoint::new(
|
|
||||||
row as usize,
|
|
||||||
TermColumn(col as usize),
|
|
||||||
));
|
|
||||||
let side = if col.fract() < 0.5 {
|
|
||||||
TermSide::Left
|
|
||||||
} else {
|
|
||||||
TermSide::Right
|
|
||||||
};
|
|
||||||
let selection = match click_kind {
|
|
||||||
ClickKind::Single => {
|
|
||||||
Selection::new(SelectionType::Simple, location, side)
|
|
||||||
}
|
|
||||||
ClickKind::Double => {
|
|
||||||
Selection::new(SelectionType::Semantic, location, side)
|
|
||||||
}
|
|
||||||
ClickKind::Triple => {
|
|
||||||
Selection::new(SelectionType::Lines, location, side)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
{
|
|
||||||
let mut term = terminal.term.lock();
|
|
||||||
term.selection = Some(selection);
|
|
||||||
}
|
|
||||||
terminal.needs_update = true;
|
|
||||||
state.click = Some((click_kind, Instant::now()));
|
|
||||||
state.dragging = Some(Dragging::Buffer);
|
|
||||||
} else if scrollbar_rect.contains(Point::new(x, y)) {
|
|
||||||
if let Some(start_scroll) = terminal.scrollbar() {
|
|
||||||
state.dragging = Some(Dragging::Scrollbar {
|
|
||||||
start_y: y,
|
|
||||||
start_scroll,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if x >= scrollbar_rect.x
|
|
||||||
&& x < (scrollbar_rect.x + scrollbar_rect.width)
|
|
||||||
{
|
|
||||||
if terminal.scrollbar().is_some() {
|
|
||||||
let scroll_ratio =
|
|
||||||
terminal.with_buffer(|buffer| y / buffer.size().1);
|
|
||||||
terminal.scroll_to(scroll_ratio);
|
|
||||||
if let Some(start_scroll) = terminal.scrollbar() {
|
|
||||||
state.dragging = Some(Dragging::Scrollbar {
|
|
||||||
start_y: y,
|
|
||||||
start_scroll,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if button == Button::Middle {
|
|
||||||
if let Some(on_middle_click) = &self.on_middle_click {
|
|
||||||
shell.publish(on_middle_click());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update context menu state
|
|
||||||
if let Some(on_context_menu) = &self.on_context_menu {
|
|
||||||
shell.publish((on_context_menu)(match self.context_menu {
|
|
||||||
Some(_) => None,
|
|
||||||
None => match button {
|
|
||||||
Button::Right => Some(p),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => {
|
|
||||||
state.dragging = None;
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
Event::Mouse(MouseEvent::CursorMoved { .. }) => {
|
|
||||||
if let Some(dragging) = &state.dragging {
|
|
||||||
if let Some(p) = cursor_position.position() {
|
|
||||||
let x = (p.x - layout.bounds().x) - self.padding.left;
|
|
||||||
let y = (p.y - layout.bounds().y) - self.padding.top;
|
|
||||||
match dragging {
|
|
||||||
Dragging::Buffer => {
|
|
||||||
//TODO: better calculation of position
|
|
||||||
let col = x / terminal.size().cell_width;
|
|
||||||
let row = y / terminal.size().cell_height;
|
|
||||||
let location = terminal.viewport_to_point(TermPoint::new(
|
let location = terminal.viewport_to_point(TermPoint::new(
|
||||||
row as usize,
|
row as usize,
|
||||||
TermColumn(col as usize),
|
TermColumn(col as usize),
|
||||||
|
|
@ -1027,56 +1056,189 @@ where
|
||||||
} else {
|
} else {
|
||||||
TermSide::Right
|
TermSide::Right
|
||||||
};
|
};
|
||||||
|
let selection = match click_kind {
|
||||||
|
ClickKind::Single => {
|
||||||
|
Selection::new(SelectionType::Simple, location, side)
|
||||||
|
}
|
||||||
|
ClickKind::Double => {
|
||||||
|
Selection::new(SelectionType::Semantic, location, side)
|
||||||
|
}
|
||||||
|
ClickKind::Triple => {
|
||||||
|
Selection::new(SelectionType::Lines, location, side)
|
||||||
|
}
|
||||||
|
};
|
||||||
{
|
{
|
||||||
let mut term = terminal.term.lock();
|
let mut term = terminal.term.lock();
|
||||||
if let Some(selection) = &mut term.selection {
|
term.selection = Some(selection);
|
||||||
selection.update(location, side);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
terminal.needs_update = true;
|
terminal.needs_update = true;
|
||||||
|
state.click = Some((click_kind, Instant::now()));
|
||||||
|
state.dragging = Some(Dragging::Buffer);
|
||||||
|
} else if scrollbar_rect.contains(Point::new(x, y)) {
|
||||||
|
if let Some(start_scroll) = terminal.scrollbar() {
|
||||||
|
state.dragging = Some(Dragging::Scrollbar {
|
||||||
|
start_y: y,
|
||||||
|
start_scroll,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if x >= scrollbar_rect.x
|
||||||
|
&& x < (scrollbar_rect.x + scrollbar_rect.width)
|
||||||
|
{
|
||||||
|
if terminal.scrollbar().is_some() {
|
||||||
|
let scroll_ratio =
|
||||||
|
terminal.with_buffer(|buffer| y / buffer.size().1);
|
||||||
|
terminal.scroll_to(scroll_ratio);
|
||||||
|
if let Some(start_scroll) = terminal.scrollbar() {
|
||||||
|
state.dragging = Some(Dragging::Scrollbar {
|
||||||
|
start_y: y,
|
||||||
|
start_scroll,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Dragging::Scrollbar {
|
} else if button == Button::Middle {
|
||||||
start_y,
|
if let Some(on_middle_click) = &self.on_middle_click {
|
||||||
start_scroll,
|
shell.publish(on_middle_click());
|
||||||
} => {
|
|
||||||
let scroll_offset = terminal
|
|
||||||
.with_buffer(|buffer| ((y - start_y) / buffer.size().1));
|
|
||||||
terminal.scroll_to(start_scroll.0 + scroll_offset);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Update context menu state
|
||||||
|
if let Some(on_context_menu) = &self.on_context_menu {
|
||||||
|
shell.publish((on_context_menu)(match self.context_menu {
|
||||||
|
Some(_) => None,
|
||||||
|
None => match button {
|
||||||
|
Button::Right => Some(p),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
status = Status::Captured;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => {
|
||||||
|
state.dragging = None;
|
||||||
|
if let Some(p) = cursor_position.position_in(layout.bounds()) {
|
||||||
|
let x = p.x - self.padding.left;
|
||||||
|
let y = p.y - self.padding.top;
|
||||||
|
//TODO: better calculation of position
|
||||||
|
let col = x / terminal.size().cell_width;
|
||||||
|
let row = y / terminal.size().cell_height;
|
||||||
|
if is_mouse_mode {
|
||||||
|
terminal.report_mouse(event, &state.modifiers, col as u32, row as u32);
|
||||||
|
} else {
|
||||||
|
status = Status::Captured;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Mouse(MouseEvent::WheelScrolled { delta }) => {
|
Event::Mouse(MouseEvent::ButtonReleased(_button)) => {
|
||||||
if let Some(_p) = cursor_position.position_in(layout.bounds()) {
|
if let Some(p) = cursor_position.position_in(layout.bounds()) {
|
||||||
match delta {
|
let x = p.x - self.padding.left;
|
||||||
ScrollDelta::Lines { x: _, y } => {
|
let y = p.y - self.padding.top;
|
||||||
//TODO: this adjustment is just a guess!
|
//TODO: better calculation of position
|
||||||
state.scroll_pixels = 0.0;
|
let col = x / terminal.size().cell_width;
|
||||||
let lines = (-y * 6.0) as i32;
|
let row = y / terminal.size().cell_height;
|
||||||
if lines != 0 {
|
if is_mouse_mode {
|
||||||
terminal.scroll(TerminalScroll::Delta(-lines));
|
terminal.report_mouse(event, &state.modifiers, col as u32, row as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Mouse(MouseEvent::CursorMoved { .. }) => {
|
||||||
|
if let Some(on_mouse_enter) = &self.on_mouse_enter {
|
||||||
|
let mouse_is_inside = cursor_position.position_in(layout.bounds()).is_some();
|
||||||
|
if let Some(known_is_inside) = self.mouse_inside_boundary {
|
||||||
|
if mouse_is_inside != known_is_inside {
|
||||||
|
if mouse_is_inside {
|
||||||
|
shell.publish(on_mouse_enter());
|
||||||
}
|
}
|
||||||
status = Status::Captured;
|
self.mouse_inside_boundary = Some(mouse_is_inside);
|
||||||
}
|
}
|
||||||
ScrollDelta::Pixels { x: _, y } => {
|
} else {
|
||||||
//TODO: this adjustment is just a guess!
|
self.mouse_inside_boundary = Some(mouse_is_inside);
|
||||||
state.scroll_pixels -= y * 6.0;
|
}
|
||||||
let mut lines = 0;
|
}
|
||||||
let metrics = terminal.with_buffer(|buffer| buffer.metrics());
|
if let Some(p) = cursor_position.position() {
|
||||||
while state.scroll_pixels <= -metrics.line_height {
|
let x = (p.x - layout.bounds().x) - self.padding.left;
|
||||||
lines -= 1;
|
let y = (p.y - layout.bounds().y) - self.padding.top;
|
||||||
state.scroll_pixels += metrics.line_height;
|
//TODO: better calculation of position
|
||||||
|
let col = x / terminal.size().cell_width;
|
||||||
|
let row = y / terminal.size().cell_height;
|
||||||
|
if is_mouse_mode {
|
||||||
|
terminal.report_mouse(event, &state.modifiers, col as u32, row as u32);
|
||||||
|
} else {
|
||||||
|
if let Some(dragging) = &state.dragging {
|
||||||
|
match dragging {
|
||||||
|
Dragging::Buffer => {
|
||||||
|
let location = terminal.viewport_to_point(TermPoint::new(
|
||||||
|
row as usize,
|
||||||
|
TermColumn(col as usize),
|
||||||
|
));
|
||||||
|
let side = if col.fract() < 0.5 {
|
||||||
|
TermSide::Left
|
||||||
|
} else {
|
||||||
|
TermSide::Right
|
||||||
|
};
|
||||||
|
{
|
||||||
|
let mut term = terminal.term.lock();
|
||||||
|
if let Some(selection) = &mut term.selection {
|
||||||
|
selection.update(location, side);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
terminal.needs_update = true;
|
||||||
|
}
|
||||||
|
Dragging::Scrollbar {
|
||||||
|
start_y,
|
||||||
|
start_scroll,
|
||||||
|
} => {
|
||||||
|
let scroll_offset = terminal
|
||||||
|
.with_buffer(|buffer| ((y - start_y) / buffer.size().1));
|
||||||
|
terminal.scroll_to(start_scroll.0 + scroll_offset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
while state.scroll_pixels >= metrics.line_height {
|
}
|
||||||
lines += 1;
|
status = Status::Captured;
|
||||||
state.scroll_pixels -= metrics.line_height;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Mouse(MouseEvent::WheelScrolled { delta }) => {
|
||||||
|
if let Some(p) = cursor_position.position_in(layout.bounds()) {
|
||||||
|
if is_mouse_mode {
|
||||||
|
let x = (p.x - layout.bounds().x) - self.padding.left;
|
||||||
|
let y = (p.y - layout.bounds().y) - self.padding.top;
|
||||||
|
//TODO: better calculation of position
|
||||||
|
let col = x / terminal.size().cell_width;
|
||||||
|
let row = y / terminal.size().cell_height;
|
||||||
|
terminal.scroll_mouse(delta, &state.modifiers, col as u32, row as u32);
|
||||||
|
} else {
|
||||||
|
match delta {
|
||||||
|
ScrollDelta::Lines { x: _, y } => {
|
||||||
|
//TODO: this adjustment is just a guess!
|
||||||
|
state.scroll_pixels = 0.0;
|
||||||
|
let lines = (-y * 6.0) as i32;
|
||||||
|
if lines != 0 {
|
||||||
|
terminal.scroll(TerminalScroll::Delta(-lines));
|
||||||
|
}
|
||||||
|
status = Status::Captured;
|
||||||
}
|
}
|
||||||
if lines != 0 {
|
ScrollDelta::Pixels { x: _, y } => {
|
||||||
terminal.scroll(TerminalScroll::Delta(-lines));
|
//TODO: this adjustment is just a guess!
|
||||||
|
state.scroll_pixels -= y * 6.0;
|
||||||
|
let mut lines = 0;
|
||||||
|
let metrics = terminal.with_buffer(|buffer| buffer.metrics());
|
||||||
|
while state.scroll_pixels <= -metrics.line_height {
|
||||||
|
lines -= 1;
|
||||||
|
state.scroll_pixels += metrics.line_height;
|
||||||
|
}
|
||||||
|
while state.scroll_pixels >= metrics.line_height {
|
||||||
|
lines += 1;
|
||||||
|
state.scroll_pixels -= metrics.line_height;
|
||||||
|
}
|
||||||
|
if lines != 0 {
|
||||||
|
terminal.scroll(TerminalScroll::Delta(-lines));
|
||||||
|
}
|
||||||
|
status = Status::Captured;
|
||||||
}
|
}
|
||||||
status = Status::Captured;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,313 @@ fn auto_colors() -> Colors {
|
||||||
colors
|
colors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tango_palette() -> Colors {
|
||||||
|
let mut colors = auto_colors();
|
||||||
|
|
||||||
|
let encode_rgb = |data: u32| -> Rgb {
|
||||||
|
Rgb {
|
||||||
|
r: (data >> 16) as u8,
|
||||||
|
g: (data >> 8) as u8,
|
||||||
|
b: data as u8,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
colors[NamedColor::Black] = Some(encode_rgb(0x2E3436));
|
||||||
|
colors[NamedColor::Red] = Some(encode_rgb(0xCC0000));
|
||||||
|
colors[NamedColor::Green] = Some(encode_rgb(0x4E9A06));
|
||||||
|
colors[NamedColor::Yellow] = Some(encode_rgb(0xC4A000));
|
||||||
|
colors[NamedColor::Blue] = Some(encode_rgb(0x3465A4));
|
||||||
|
colors[NamedColor::Magenta] = Some(encode_rgb(0x75507B));
|
||||||
|
colors[NamedColor::Cyan] = Some(encode_rgb(0x06989A));
|
||||||
|
colors[NamedColor::White] = Some(encode_rgb(0xD3D7CF));
|
||||||
|
|
||||||
|
colors[NamedColor::BrightBlack] = Some(encode_rgb(0x555753));
|
||||||
|
colors[NamedColor::BrightRed] = Some(encode_rgb(0xEF2929));
|
||||||
|
colors[NamedColor::BrightGreen] = Some(encode_rgb(0x8AE234));
|
||||||
|
colors[NamedColor::BrightYellow] = Some(encode_rgb(0xFCE94F));
|
||||||
|
colors[NamedColor::BrightBlue] = Some(encode_rgb(0x729FCF));
|
||||||
|
colors[NamedColor::BrightMagenta] = Some(encode_rgb(0xAD7FA8));
|
||||||
|
colors[NamedColor::BrightCyan] = Some(encode_rgb(0x34E2E2));
|
||||||
|
colors[NamedColor::BrightWhite] = Some(encode_rgb(0xEEEEEC));
|
||||||
|
|
||||||
|
colors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tango_dark() -> Colors {
|
||||||
|
let mut colors = tango_palette();
|
||||||
|
|
||||||
|
// Set special colors
|
||||||
|
colors[NamedColor::Foreground] = colors[NamedColor::White];
|
||||||
|
colors[NamedColor::Background] = colors[NamedColor::Black];
|
||||||
|
colors[NamedColor::BrightForeground] = colors[NamedColor::BrightWhite];
|
||||||
|
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
|
||||||
|
|
||||||
|
// Fill missing dim colors
|
||||||
|
ColorDerive::new()
|
||||||
|
// Dim less so colors are readable with default bg
|
||||||
|
.with_dim_lightness_adjustment(-0.10)
|
||||||
|
.fill_missing_dims(&mut colors);
|
||||||
|
|
||||||
|
colors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tango_light() -> Colors {
|
||||||
|
let mut colors = tango_palette();
|
||||||
|
|
||||||
|
// Set special colors
|
||||||
|
colors[NamedColor::Foreground] = colors[NamedColor::Black];
|
||||||
|
colors[NamedColor::Background] = colors[NamedColor::BrightWhite];
|
||||||
|
colors[NamedColor::BrightForeground] = colors[NamedColor::BrightBlack];
|
||||||
|
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
|
||||||
|
|
||||||
|
// Fill missing dim colors
|
||||||
|
ColorDerive::new().fill_missing_dims(&mut colors);
|
||||||
|
|
||||||
|
colors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn linux_console_palette() -> Colors {
|
||||||
|
let mut colors = auto_colors();
|
||||||
|
|
||||||
|
let encode_rgb = |data: u32| -> Rgb {
|
||||||
|
Rgb {
|
||||||
|
r: (data >> 16) as u8,
|
||||||
|
g: (data >> 8) as u8,
|
||||||
|
b: data as u8,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
colors[NamedColor::Black] = Some(encode_rgb(0x000000));
|
||||||
|
colors[NamedColor::Red] = Some(encode_rgb(0xAA0000));
|
||||||
|
colors[NamedColor::Green] = Some(encode_rgb(0x00AA00));
|
||||||
|
colors[NamedColor::Yellow] = Some(encode_rgb(0xAA5500));
|
||||||
|
colors[NamedColor::Blue] = Some(encode_rgb(0x0000AA));
|
||||||
|
colors[NamedColor::Magenta] = Some(encode_rgb(0xAA00AA));
|
||||||
|
colors[NamedColor::Cyan] = Some(encode_rgb(0x00AAAA));
|
||||||
|
colors[NamedColor::White] = Some(encode_rgb(0xAAAAAA));
|
||||||
|
|
||||||
|
colors[NamedColor::BrightBlack] = Some(encode_rgb(0x555555));
|
||||||
|
colors[NamedColor::BrightRed] = Some(encode_rgb(0xFF5555));
|
||||||
|
colors[NamedColor::BrightGreen] = Some(encode_rgb(0x55FF55));
|
||||||
|
colors[NamedColor::BrightYellow] = Some(encode_rgb(0xFFFF55));
|
||||||
|
colors[NamedColor::BrightBlue] = Some(encode_rgb(0x5555FF));
|
||||||
|
colors[NamedColor::BrightMagenta] = Some(encode_rgb(0xFF55FF));
|
||||||
|
colors[NamedColor::BrightCyan] = Some(encode_rgb(0x55FFFF));
|
||||||
|
colors[NamedColor::BrightWhite] = Some(encode_rgb(0xFFFFFF));
|
||||||
|
|
||||||
|
colors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn linux_console() -> Colors {
|
||||||
|
let mut colors = linux_console_palette();
|
||||||
|
|
||||||
|
// Set special colors
|
||||||
|
colors[NamedColor::Foreground] = colors[NamedColor::BrightWhite];
|
||||||
|
colors[NamedColor::Background] = colors[NamedColor::Black];
|
||||||
|
colors[NamedColor::BrightForeground] = colors[NamedColor::White];
|
||||||
|
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
|
||||||
|
|
||||||
|
// Fill missing dim colors
|
||||||
|
ColorDerive::new()
|
||||||
|
// Dim less so colors are readable with default bg
|
||||||
|
.with_dim_lightness_adjustment(-0.10)
|
||||||
|
.fill_missing_dims(&mut colors);
|
||||||
|
|
||||||
|
colors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn xterm_palette() -> Colors {
|
||||||
|
let mut colors = auto_colors();
|
||||||
|
|
||||||
|
let encode_rgb = |data: u32| -> Rgb {
|
||||||
|
Rgb {
|
||||||
|
r: (data >> 16) as u8,
|
||||||
|
g: (data >> 8) as u8,
|
||||||
|
b: data as u8,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
colors[NamedColor::Black] = Some(encode_rgb(0x000000));
|
||||||
|
colors[NamedColor::Red] = Some(encode_rgb(0xCD0000));
|
||||||
|
colors[NamedColor::Green] = Some(encode_rgb(0x00CD00));
|
||||||
|
colors[NamedColor::Yellow] = Some(encode_rgb(0xCDCD00));
|
||||||
|
colors[NamedColor::Blue] = Some(encode_rgb(0x0000EE));
|
||||||
|
colors[NamedColor::Magenta] = Some(encode_rgb(0xCD00CD));
|
||||||
|
colors[NamedColor::Cyan] = Some(encode_rgb(0x00CDCD));
|
||||||
|
colors[NamedColor::White] = Some(encode_rgb(0xE5E5E5));
|
||||||
|
|
||||||
|
colors[NamedColor::BrightBlack] = Some(encode_rgb(0x7F7F7F));
|
||||||
|
colors[NamedColor::BrightRed] = Some(encode_rgb(0xFF0000));
|
||||||
|
colors[NamedColor::BrightGreen] = Some(encode_rgb(0x00FF00));
|
||||||
|
colors[NamedColor::BrightYellow] = Some(encode_rgb(0xFFFF00));
|
||||||
|
colors[NamedColor::BrightBlue] = Some(encode_rgb(0x5C5CFF));
|
||||||
|
colors[NamedColor::BrightMagenta] = Some(encode_rgb(0xFF00FF));
|
||||||
|
colors[NamedColor::BrightCyan] = Some(encode_rgb(0x00FFFF));
|
||||||
|
colors[NamedColor::BrightWhite] = Some(encode_rgb(0xFFFFFF));
|
||||||
|
|
||||||
|
colors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn xterm_dark() -> Colors {
|
||||||
|
let mut colors = xterm_palette();
|
||||||
|
|
||||||
|
// Set special colors
|
||||||
|
colors[NamedColor::Foreground] = colors[NamedColor::BrightWhite];
|
||||||
|
colors[NamedColor::Background] = colors[NamedColor::Black];
|
||||||
|
colors[NamedColor::BrightForeground] = colors[NamedColor::White];
|
||||||
|
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
|
||||||
|
|
||||||
|
// Fill missing dim colors
|
||||||
|
ColorDerive::new()
|
||||||
|
// Dim less so colors are readable with default bg
|
||||||
|
.with_dim_lightness_adjustment(-0.12)
|
||||||
|
.fill_missing_dims(&mut colors);
|
||||||
|
|
||||||
|
colors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn xterm_light() -> Colors {
|
||||||
|
let mut colors = xterm_palette();
|
||||||
|
|
||||||
|
// Set special colors
|
||||||
|
colors[NamedColor::Foreground] = colors[NamedColor::Black];
|
||||||
|
colors[NamedColor::Background] = colors[NamedColor::BrightWhite];
|
||||||
|
colors[NamedColor::BrightForeground] = colors[NamedColor::BrightBlack];
|
||||||
|
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
|
||||||
|
|
||||||
|
// Fill missing dim colors
|
||||||
|
ColorDerive::new().fill_missing_dims(&mut colors);
|
||||||
|
|
||||||
|
colors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rxvt_palette() -> Colors {
|
||||||
|
let mut colors = auto_colors();
|
||||||
|
|
||||||
|
let encode_rgb = |data: u32| -> Rgb {
|
||||||
|
Rgb {
|
||||||
|
r: (data >> 16) as u8,
|
||||||
|
g: (data >> 8) as u8,
|
||||||
|
b: data as u8,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
colors[NamedColor::Black] = Some(encode_rgb(0x000000));
|
||||||
|
colors[NamedColor::Red] = Some(encode_rgb(0xCD0000));
|
||||||
|
colors[NamedColor::Green] = Some(encode_rgb(0x00CD00));
|
||||||
|
colors[NamedColor::Yellow] = Some(encode_rgb(0xCDCD00));
|
||||||
|
colors[NamedColor::Blue] = Some(encode_rgb(0x0000CD));
|
||||||
|
colors[NamedColor::Magenta] = Some(encode_rgb(0xCD00CD));
|
||||||
|
colors[NamedColor::Cyan] = Some(encode_rgb(0x00CDCD));
|
||||||
|
colors[NamedColor::White] = Some(encode_rgb(0xFAEBD7));
|
||||||
|
|
||||||
|
colors[NamedColor::BrightBlack] = Some(encode_rgb(0x404040));
|
||||||
|
colors[NamedColor::BrightRed] = Some(encode_rgb(0xFF0000));
|
||||||
|
colors[NamedColor::BrightGreen] = Some(encode_rgb(0x00FF00));
|
||||||
|
colors[NamedColor::BrightYellow] = Some(encode_rgb(0xFFFF00));
|
||||||
|
colors[NamedColor::BrightBlue] = Some(encode_rgb(0x0000FF));
|
||||||
|
colors[NamedColor::BrightMagenta] = Some(encode_rgb(0xFF00FF));
|
||||||
|
colors[NamedColor::BrightCyan] = Some(encode_rgb(0x00FFFF));
|
||||||
|
colors[NamedColor::BrightWhite] = Some(encode_rgb(0xFFFFFF));
|
||||||
|
|
||||||
|
colors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rxvt_dark() -> Colors {
|
||||||
|
let mut colors = rxvt_palette();
|
||||||
|
|
||||||
|
// Set special colors
|
||||||
|
colors[NamedColor::Foreground] = colors[NamedColor::BrightWhite];
|
||||||
|
colors[NamedColor::Background] = colors[NamedColor::Black];
|
||||||
|
colors[NamedColor::BrightForeground] = colors[NamedColor::White];
|
||||||
|
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
|
||||||
|
|
||||||
|
// Fill missing dim colors
|
||||||
|
ColorDerive::new()
|
||||||
|
// Dim less so colors are readable with default bg
|
||||||
|
.with_dim_lightness_adjustment(-0.12)
|
||||||
|
.fill_missing_dims(&mut colors);
|
||||||
|
|
||||||
|
colors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rxvt_light() -> Colors {
|
||||||
|
let mut colors = rxvt_palette();
|
||||||
|
|
||||||
|
// Set special colors
|
||||||
|
colors[NamedColor::Foreground] = colors[NamedColor::Black];
|
||||||
|
colors[NamedColor::Background] = colors[NamedColor::BrightWhite];
|
||||||
|
colors[NamedColor::BrightForeground] = colors[NamedColor::BrightBlack];
|
||||||
|
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
|
||||||
|
|
||||||
|
// Fill missing dim colors
|
||||||
|
ColorDerive::new().fill_missing_dims(&mut colors);
|
||||||
|
|
||||||
|
colors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solarized_palette() -> Colors {
|
||||||
|
let mut colors = auto_colors();
|
||||||
|
|
||||||
|
let encode_rgb = |data: u32| -> Rgb {
|
||||||
|
Rgb {
|
||||||
|
r: (data >> 16) as u8,
|
||||||
|
g: (data >> 8) as u8,
|
||||||
|
b: data as u8,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
colors[NamedColor::Black] = Some(encode_rgb(0x073642));
|
||||||
|
colors[NamedColor::Red] = Some(encode_rgb(0xDC322F));
|
||||||
|
colors[NamedColor::Green] = Some(encode_rgb(0x859900));
|
||||||
|
colors[NamedColor::Yellow] = Some(encode_rgb(0xB58900));
|
||||||
|
colors[NamedColor::Blue] = Some(encode_rgb(0x268BD2));
|
||||||
|
colors[NamedColor::Magenta] = Some(encode_rgb(0xD33682));
|
||||||
|
colors[NamedColor::Cyan] = Some(encode_rgb(0x2AA198));
|
||||||
|
colors[NamedColor::White] = Some(encode_rgb(0xEEE8D5));
|
||||||
|
|
||||||
|
colors[NamedColor::BrightBlack] = Some(encode_rgb(0x002B36));
|
||||||
|
colors[NamedColor::BrightRed] = Some(encode_rgb(0xCB4B16));
|
||||||
|
colors[NamedColor::BrightGreen] = Some(encode_rgb(0x586E75));
|
||||||
|
colors[NamedColor::BrightYellow] = Some(encode_rgb(0x657B83));
|
||||||
|
colors[NamedColor::BrightBlue] = Some(encode_rgb(0x839496));
|
||||||
|
colors[NamedColor::BrightMagenta] = Some(encode_rgb(0x6C71C4));
|
||||||
|
colors[NamedColor::BrightCyan] = Some(encode_rgb(0x93A1A1));
|
||||||
|
colors[NamedColor::BrightWhite] = Some(encode_rgb(0xFDF6E3));
|
||||||
|
|
||||||
|
colors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solarized_dark() -> Colors {
|
||||||
|
let mut colors = solarized_palette();
|
||||||
|
|
||||||
|
// Set special colors
|
||||||
|
colors[NamedColor::Foreground] = colors[NamedColor::BrightBlue];
|
||||||
|
colors[NamedColor::Background] = colors[NamedColor::BrightBlack];
|
||||||
|
colors[NamedColor::BrightForeground] = colors[NamedColor::Blue];
|
||||||
|
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
|
||||||
|
|
||||||
|
// Fill missing dim colors
|
||||||
|
ColorDerive::new().fill_missing_dims(&mut colors);
|
||||||
|
|
||||||
|
colors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solarized_light() -> Colors {
|
||||||
|
let mut colors = solarized_palette();
|
||||||
|
|
||||||
|
// Set special colors
|
||||||
|
colors[NamedColor::Foreground] = colors[NamedColor::BrightYellow];
|
||||||
|
colors[NamedColor::Background] = colors[NamedColor::BrightWhite];
|
||||||
|
colors[NamedColor::BrightForeground] = colors[NamedColor::Yellow];
|
||||||
|
colors[NamedColor::Cursor] = colors[NamedColor::Foreground];
|
||||||
|
|
||||||
|
// Fill missing dim colors
|
||||||
|
ColorDerive::new().fill_missing_dims(&mut colors);
|
||||||
|
|
||||||
|
colors
|
||||||
|
}
|
||||||
|
|
||||||
fn cosmic_dark() -> Colors {
|
fn cosmic_dark() -> Colors {
|
||||||
let mut colors = auto_colors();
|
let mut colors = auto_colors();
|
||||||
|
|
||||||
|
|
@ -169,7 +476,7 @@ fn cosmic_dark() -> Colors {
|
||||||
|
|
||||||
// Set special colors
|
// Set special colors
|
||||||
colors[NamedColor::Foreground] = colors[NamedColor::BrightWhite];
|
colors[NamedColor::Foreground] = colors[NamedColor::BrightWhite];
|
||||||
colors[NamedColor::Background] = colors[NamedColor::Black];
|
// Background comes from theme settings: colors[NamedColor::Background] = colors[NamedColor::Black];
|
||||||
colors[NamedColor::Cursor] = colors[NamedColor::BrightWhite];
|
colors[NamedColor::Cursor] = colors[NamedColor::BrightWhite];
|
||||||
colors[NamedColor::BrightForeground] = colors[NamedColor::BrightWhite];
|
colors[NamedColor::BrightForeground] = colors[NamedColor::BrightWhite];
|
||||||
|
|
||||||
|
|
@ -210,7 +517,7 @@ fn cosmic_light() -> Colors {
|
||||||
|
|
||||||
// Set special colors
|
// Set special colors
|
||||||
colors[NamedColor::Foreground] = colors[NamedColor::Black];
|
colors[NamedColor::Foreground] = colors[NamedColor::Black];
|
||||||
colors[NamedColor::Background] = colors[NamedColor::BrightWhite];
|
// Background comes from theme settings: colors[NamedColor::Background] = colors[NamedColor::BrightWhite];
|
||||||
colors[NamedColor::Cursor] = colors[NamedColor::Black];
|
colors[NamedColor::Cursor] = colors[NamedColor::Black];
|
||||||
colors[NamedColor::BrightForeground] = colors[NamedColor::BrightBlack];
|
colors[NamedColor::BrightForeground] = colors[NamedColor::BrightBlack];
|
||||||
|
|
||||||
|
|
@ -351,6 +658,15 @@ fn pop_dark() -> Colors {
|
||||||
|
|
||||||
pub fn terminal_themes() -> HashMap<String, Colors> {
|
pub fn terminal_themes() -> HashMap<String, Colors> {
|
||||||
let mut themes = HashMap::new();
|
let mut themes = HashMap::new();
|
||||||
|
themes.insert("Tango Dark".to_string(), tango_dark());
|
||||||
|
themes.insert("Tango Light".to_string(), tango_light());
|
||||||
|
themes.insert("XTerm Dark".to_string(), xterm_dark());
|
||||||
|
themes.insert("XTerm Light".to_string(), xterm_light());
|
||||||
|
themes.insert("Linux Console".to_string(), linux_console());
|
||||||
|
themes.insert("Rxvt Dark".to_string(), rxvt_dark());
|
||||||
|
themes.insert("Rxvt Light".to_string(), rxvt_light());
|
||||||
|
themes.insert("Solarized Dark".to_string(), solarized_dark());
|
||||||
|
themes.insert("Solarized Light".to_string(), solarized_light());
|
||||||
themes.insert("COSMIC Dark".to_string(), cosmic_dark());
|
themes.insert("COSMIC Dark".to_string(), cosmic_dark());
|
||||||
themes.insert("COSMIC Light".to_string(), cosmic_light());
|
themes.insert("COSMIC Light".to_string(), cosmic_light());
|
||||||
themes.insert("gruvbox-dark".to_string(), gruvbox_dark());
|
themes.insert("gruvbox-dark".to_string(), gruvbox_dark());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue