Use hollow block cursor for non-focused terminals

This commit is contained in:
Mattias Eriksson 2025-01-21 17:07:45 +01:00 committed by Jeremy Soller
parent 75787a8766
commit 23a5851ca9
3 changed files with 123 additions and 29 deletions

View file

@ -359,6 +359,8 @@ pub enum Message {
UseBrightBold(bool), UseBrightBold(bool),
WindowClose, WindowClose,
WindowNew, WindowNew,
WindowFocused,
WindowUnfocused,
ZoomIn, ZoomIn,
ZoomOut, ZoomOut,
ZoomReset, ZoomReset,
@ -575,7 +577,8 @@ impl App {
fn update_focus(&self) -> Task<Message> { fn update_focus(&self) -> Task<Message> {
if self.find { 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() { } else if let Some(terminal_id) = self.terminal_ids.get(&self.pane_model.focused()).cloned()
{
widget::text_input::focus(terminal_id) widget::text_input::focus(terminal_id)
} else { } else {
Task::none() Task::none()
@ -584,7 +587,7 @@ impl App {
// Call this any time the tab changes // Call this any time the tab changes
fn update_title(&mut self, pane: Option<pane_grid::Pane>) -> Task<Message> { fn update_title(&mut self, pane: Option<pane_grid::Pane>) -> Task<Message> {
let pane = pane.unwrap_or(self.pane_model.focus); let pane = pane.unwrap_or(self.pane_model.focused());
if let Some(tab_model) = self.pane_model.panes.get(pane) { if let Some(tab_model) = self.pane_model.panes.get(pane) {
let (header_title, window_title) = match tab_model.text(tab_model.active()) { let (header_title, window_title) = match tab_model.text(tab_model.active()) {
Some(tab_title) => ( Some(tab_title) => (
@ -1221,7 +1224,7 @@ impl App {
pane: pane_grid::Pane, pane: pane_grid::Pane,
profile_id_opt: Option<ProfileId>, profile_id_opt: Option<ProfileId>,
) -> Task<Message> { ) -> Task<Message> {
self.pane_model.focus = pane; self.pane_model.set_focus(pane);
match &self.term_event_tx_opt { match &self.term_event_tx_opt {
Some(term_event_tx) => { Some(term_event_tx) => {
let colors = self let colors = self
@ -1238,7 +1241,7 @@ impl App {
}); });
match colors { match colors {
Some(colors) => { Some(colors) => {
let current_pane = self.pane_model.focus; let current_pane = self.pane_model.focused();
if let Some(tab_model) = self.pane_model.active_mut() { if let Some(tab_model) = self.pane_model.active_mut() {
// Use the startup options, profile options, or defaults // Use the startup options, profile options, or defaults
let (options, tab_title_override) = match self.startup_options.take() { let (options, tab_title_override) = match self.startup_options.take() {
@ -1482,7 +1485,7 @@ impl Application for App {
let pane_model = TerminalPaneGrid::new(segmented_button::ModelBuilder::default().build()); let pane_model = TerminalPaneGrid::new(segmented_button::ModelBuilder::default().build());
let mut terminal_ids = HashMap::new(); let mut terminal_ids = HashMap::new();
terminal_ids.insert(pane_model.focus, widget::Id::unique()); terminal_ids.insert(pane_model.focused(), widget::Id::unique());
let mut app = Self { let mut app = Self {
core, core,
@ -1949,7 +1952,7 @@ impl Application for App {
} }
} }
Message::Drop(Some((pane, entity, data))) => { Message::Drop(Some((pane, entity, data))) => {
self.pane_model.focus = pane; self.pane_model.set_focus(pane);
if let Ok(value) = shlex::try_join(data.paths.iter().filter_map(|p| p.to_str())) { if let Ok(value) = shlex::try_join(data.paths.iter().filter_map(|p| p.to_str())) {
return Task::batch([ return Task::batch([
self.update_focus(), self.update_focus(),
@ -2012,7 +2015,7 @@ impl Application for App {
self.find_search_value = value; self.find_search_value = value;
} }
Message::MiddleClick(pane, entity_opt) => { Message::MiddleClick(pane, entity_opt) => {
self.pane_model.focus = pane; self.pane_model.set_focus(pane);
return Task::batch([ return Task::batch([
self.update_focus(), self.update_focus(),
clipboard::read_primary().map(move |value_opt| match value_opt { clipboard::read_primary().map(move |value_opt| match value_opt {
@ -2040,20 +2043,20 @@ impl Application for App {
self.modifiers = modifiers; self.modifiers = modifiers;
} }
Message::MouseEnter(pane) => { Message::MouseEnter(pane) => {
self.pane_model.focus = pane; self.pane_model.set_focus(pane);
return self.update_focus(); return self.update_focus();
} }
Message::Opacity(opacity) => { Message::Opacity(opacity) => {
config_set!(opacity, cmp::min(100, opacity)); config_set!(opacity, cmp::min(100, opacity));
} }
Message::PaneClicked(pane) => { Message::PaneClicked(pane) => {
self.pane_model.focus = pane; self.pane_model.set_focus(pane);
return self.update_title(Some(pane)); return self.update_title(Some(pane));
} }
Message::PaneSplit(axis) => { Message::PaneSplit(axis) => {
let result = self.pane_model.panes.split( let result = self.pane_model.panes.split(
axis, axis,
self.pane_model.focus, self.pane_model.focused(),
segmented_button::ModelBuilder::default().build(), segmented_button::ModelBuilder::default().build(),
); );
if let Some((pane, _)) = result { if let Some((pane, _)) = result {
@ -2068,7 +2071,7 @@ impl Application for App {
if self.pane_model.panes.maximized().is_some() { if self.pane_model.panes.maximized().is_some() {
self.pane_model.panes.restore(); self.pane_model.panes.restore();
} else { } else {
self.pane_model.panes.maximize(self.pane_model.focus); self.pane_model.panes.maximize(self.pane_model.focused());
} }
return self.update_focus(); return self.update_focus();
} }
@ -2076,9 +2079,9 @@ impl Application for App {
if let Some(adjacent) = self if let Some(adjacent) = self
.pane_model .pane_model
.panes .panes
.adjacent(self.pane_model.focus, direction) .adjacent(self.pane_model.focused(), direction)
{ {
self.pane_model.focus = adjacent; self.pane_model.set_focus(adjacent);
return self.update_title(Some(adjacent)); return self.update_title(Some(adjacent));
} }
} }
@ -2154,7 +2157,8 @@ impl Application for App {
return self.save_profiles(); return self.save_profiles();
} }
Message::ProfileOpen(profile_id) => { Message::ProfileOpen(profile_id) => {
return self.create_and_focus_new_terminal(self.pane_model.focus, Some(profile_id)); return self
.create_and_focus_new_terminal(self.pane_model.focused(), Some(profile_id));
} }
Message::ProfileRemove(profile_id) => { Message::ProfileRemove(profile_id) => {
// Reset matching terminals to default profile // Reset matching terminals to default profile
@ -2292,10 +2296,10 @@ impl Application for App {
// If that was the last tab, close current pane // If that was the last tab, close current pane
if tab_model.iter().next().is_none() { if tab_model.iter().next().is_none() {
if let Some((_state, sibling)) = if let Some((_state, sibling)) =
self.pane_model.panes.close(self.pane_model.focus) self.pane_model.panes.close(self.pane_model.focused())
{ {
self.terminal_ids.remove(&self.pane_model.focus); self.terminal_ids.remove(&self.pane_model.focused());
self.pane_model.focus = sibling; self.pane_model.set_focus(sibling);
} else { } else {
//Last pane, closing window //Last pane, closing window
if let Some(window_id) = self.core.main_window_id() { if let Some(window_id) = self.core.main_window_id() {
@ -2343,17 +2347,17 @@ impl Application for App {
// Shift focus to the pane / terminal // Shift focus to the pane / terminal
// with the context menu // with the context menu
self.pane_model.focus = pane; self.pane_model.set_focus(pane);
return self.update_title(Some(pane)); return self.update_title(Some(pane));
} }
Message::TabNew => { Message::TabNew => {
return self.create_and_focus_new_terminal( return self.create_and_focus_new_terminal(
self.pane_model.focus, self.pane_model.focused(),
self.get_default_profile(), self.get_default_profile(),
) )
} }
Message::TabNewNoProfile => { Message::TabNewNoProfile => {
return self.create_and_focus_new_terminal(self.pane_model.focus, None) return self.create_and_focus_new_terminal(self.pane_model.focused(), None)
} }
Message::TabNext => { Message::TabNext => {
if let Some(tab_model) = self.pane_model.active() { if let Some(tab_model) = self.pane_model.active() {
@ -2500,10 +2504,10 @@ impl Application for App {
// First, close other panes // First, close other panes
while let Some((_state, sibling)) = while let Some((_state, sibling)) =
self.pane_model.panes.close(self.pane_model.focus) self.pane_model.panes.close(self.pane_model.focused())
{ {
self.terminal_ids.remove(&self.pane_model.focus); self.terminal_ids.remove(&self.pane_model.focused());
self.pane_model.focus = sibling; self.pane_model.set_focus(sibling);
} }
// Next, close all tabs in the active pane // Next, close all tabs in the active pane
@ -2573,6 +2577,13 @@ impl Application for App {
log::error!("failed to get current executable path: {}", err); log::error!("failed to get current executable path: {}", err);
} }
}, },
Message::WindowFocused => {
self.pane_model.update_terminal_focus();
return self.update_focus();
}
Message::WindowUnfocused => {
self.pane_model.unfocus_all_terminals();
}
Message::ZoomIn => { Message::ZoomIn => {
return self.update_render_active_pane_zoom(message); return self.update_render_active_pane_zoom(message);
} }
@ -2673,6 +2684,8 @@ impl Application for App {
}) })
.on_middle_click(move || Message::MiddleClick(pane, Some(entity_middle_click))) .on_middle_click(move || Message::MiddleClick(pane, Some(entity_middle_click)))
.on_open_hyperlink(Some(Box::new(Message::LaunchUrl))) .on_open_hyperlink(Some(Box::new(Message::LaunchUrl)))
.on_window_focused(|| Message::WindowFocused)
.on_window_unfocused(|| Message::WindowUnfocused)
.opacity(self.config.opacity_ratio()) .opacity(self.config.opacity_ratio())
.padding(space_xxs) .padding(space_xxs)
.show_headerbar(self.config.show_headerbar); .show_headerbar(self.config.show_headerbar);
@ -2697,7 +2710,7 @@ impl Application for App {
} }
//Only draw find in the currently focused pane //Only draw find in the currently focused pane
if self.find && pane == self.pane_model.focus { if self.find && pane == self.pane_model.focused() {
let find_input = widget::text_input::text_input( let find_input = widget::text_input::text_input(
fl!("find-placeholder"), fl!("find-placeholder"),
&self.find_search_value, &self.find_search_value,

View file

@ -16,8 +16,7 @@ use alacritty_terminal::{
Term, Term,
}; };
use cosmic::{ use cosmic::{
iced::advanced::graphics::text::font_system, iced::{advanced::graphics::text::font_system, mouse::ScrollDelta},
iced::mouse::ScrollDelta,
widget::{pane_grid, segmented_button}, widget::{pane_grid, segmented_button},
}; };
use cosmic_text::{ use cosmic_text::{
@ -31,7 +30,7 @@ use std::{
io, mem, io, mem,
sync::{ sync::{
atomic::{AtomicU32, Ordering}, atomic::{AtomicU32, Ordering},
Arc, Weak, Arc, Mutex, Weak,
}, },
time::Instant, time::Instant,
}; };
@ -155,7 +154,7 @@ type TabModel = segmented_button::Model<segmented_button::SingleSelect>;
pub struct TerminalPaneGrid { pub struct TerminalPaneGrid {
pub panes: pane_grid::State<TabModel>, pub panes: pane_grid::State<TabModel>,
pub panes_created: usize, pub panes_created: usize,
pub focus: pane_grid::Pane, focus: pane_grid::Pane,
} }
impl TerminalPaneGrid { impl TerminalPaneGrid {
@ -176,6 +175,34 @@ impl TerminalPaneGrid {
pub fn active_mut(&mut self) -> Option<&mut TabModel> { pub fn active_mut(&mut self) -> Option<&mut TabModel> {
self.panes.get_mut(self.focus) self.panes.get_mut(self.focus)
} }
pub fn set_focus(&mut self, pane: pane_grid::Pane) {
self.focus = pane;
self.update_terminal_focus();
}
pub fn focused(&self) -> pane_grid::Pane {
self.focus
}
pub fn update_terminal_focus(&self) {
for (pane, tab_model) in self.panes.panes.iter() {
let entity = tab_model.active();
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
let mut terminal = terminal.lock().unwrap();
terminal.is_focused = self.focus == *pane;
terminal.update();
}
}
}
pub fn unfocus_all_terminals(&self) {
for (_pane, tab_model) in self.panes.panes.iter() {
let entity = tab_model.active();
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
let mut terminal = terminal.lock().unwrap();
terminal.is_focused = false;
terminal.update();
}
}
}
} }
#[derive(Debug, PartialEq, Eq, Hash, Clone)] #[derive(Debug, PartialEq, Eq, Hash, Clone)]
@ -219,6 +246,7 @@ pub struct Terminal {
pub active_regex_match: Option<alacritty_terminal::term::search::Match>, pub active_regex_match: Option<alacritty_terminal::term::search::Match>,
bold_font_weight: Weight, bold_font_weight: Weight,
buffer: Arc<Buffer>, buffer: Arc<Buffer>,
is_focused: bool,
colors: Colors, colors: Colors,
default_attrs: Attrs<'static>, default_attrs: Attrs<'static>,
dim_font_weight: Weight, dim_font_weight: Weight,
@ -324,6 +352,7 @@ impl Terminal {
term, term,
use_bright_bold, use_bright_bold,
zoom_adj: Default::default(), zoom_adj: Default::default(),
is_focused: true,
}) })
} }
@ -787,6 +816,7 @@ impl Terminal {
// Change color if cursor // Change color if cursor
if indexed.point == grid.cursor.point if indexed.point == grid.cursor.point
&& term.renderable_content().cursor.shape == CursorShape::Block && term.renderable_content().cursor.shape == CursorShape::Block
&& self.is_focused
{ {
//Use specific cursor color if requested //Use specific cursor color if requested
if term.colors()[NamedColor::Cursor].is_some() { if term.colors()[NamedColor::Cursor].is_some() {

View file

@ -59,6 +59,8 @@ pub struct TerminalBox<'a, Message> {
mouse_inside_boundary: Option<bool>, mouse_inside_boundary: Option<bool>,
on_middle_click: Option<Box<dyn Fn() -> Message + 'a>>, on_middle_click: Option<Box<dyn Fn() -> Message + 'a>>,
on_open_hyperlink: Option<Box<dyn Fn(String) -> Message + 'a>>, on_open_hyperlink: Option<Box<dyn Fn(String) -> Message + 'a>>,
on_window_focused: Option<Box<dyn Fn() -> Message + 'a>>,
on_window_unfocused: Option<Box<dyn Fn() -> Message + 'a>>,
key_binds: HashMap<KeyBind, Action>, key_binds: HashMap<KeyBind, Action>,
} }
@ -82,6 +84,8 @@ where
on_middle_click: None, on_middle_click: None,
key_binds: key_binds(), key_binds: key_binds(),
on_open_hyperlink: None, on_open_hyperlink: None,
on_window_focused: None,
on_window_unfocused: None,
} }
} }
@ -145,6 +149,16 @@ where
self.on_open_hyperlink = on_open_hyperlink; self.on_open_hyperlink = on_open_hyperlink;
self self
} }
pub fn on_window_focused(mut self, on_window_focused: impl Fn() -> Message + 'a) -> Self {
self.on_window_focused = Some(Box::new(on_window_focused));
self
}
pub fn on_window_unfocused(mut self, on_window_unfocused: impl Fn() -> Message + 'a) -> Self {
self.on_window_unfocused = Some(Box::new(on_window_unfocused));
self
}
} }
pub fn terminal_box<Message>(terminal: &Mutex<Terminal>) -> TerminalBox<'_, Message> pub fn terminal_box<Message>(terminal: &Mutex<Terminal>) -> TerminalBox<'_, Message>
@ -662,7 +676,30 @@ where
}; };
renderer.fill_quad(quad, color); renderer.fill_quad(quad, color);
} }
CursorShape::HollowBlock => {} // TODO not sure when this would even be activated CursorShape::Block if !state.is_focused => {
let quad = Quad {
bounds: Rectangle::new(top_left, Size::new(width, height)),
border: Border {
width: 1.0,
color,
..Default::default()
},
..Default::default()
};
renderer.fill_quad(quad, Color::TRANSPARENT);
}
CursorShape::HollowBlock => {
let quad = Quad {
bounds: Rectangle::new(top_left, Size::new(width, height)),
border: Border {
width: 1.0,
color,
..Default::default()
},
..Default::default()
};
renderer.fill_quad(quad, Color::TRANSPARENT);
}
CursorShape::Block | CursorShape::Hidden => {} // Block is handled seperately CursorShape::Block | CursorShape::Hidden => {} // Block is handled seperately
} }
} }
@ -691,6 +728,20 @@ where
let is_mouse_mode = terminal.term.lock().mode().intersects(TermMode::MOUSE_MODE); 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 {
Event::Window(event) => match event {
cosmic::iced::window::Event::Focused => {
if let Some(on_window_focused) = &self.on_window_focused {
shell.publish(on_window_focused());
}
}
cosmic::iced::window::Event::Unfocused => {
state.is_focused = false;
if let Some(on_window_unfocused) = &self.on_window_unfocused {
shell.publish(on_window_unfocused());
}
}
_ => {}
},
Event::Keyboard(KeyEvent::KeyPressed { Event::Keyboard(KeyEvent::KeyPressed {
key: Key::Named(named), key: Key::Named(named),
modified_key: Key::Named(modified_named), modified_key: Key::Named(modified_named),