Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7039563f23 | ||
|
|
6d78be7aa2 | ||
|
|
1a1b58e9a8 |
Generated
+2
@@ -3723,6 +3723,8 @@ dependencies = [
|
|||||||
"image",
|
"image",
|
||||||
"libc",
|
"libc",
|
||||||
"nexus-core",
|
"nexus-core",
|
||||||
|
"objc2 0.6.5",
|
||||||
|
"objc2-app-kit 0.3.2",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"regex",
|
"regex",
|
||||||
"rfd",
|
"rfd",
|
||||||
|
|||||||
@@ -35,3 +35,7 @@ winresource = "0.1"
|
|||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
objc2 = "0.6.4"
|
||||||
|
objc2-app-kit = { version = "0.3.2", default-features = false, features = ["std", "NSApplication", "NSResponder"] }
|
||||||
|
|||||||
@@ -992,9 +992,31 @@ impl eframe::App for App {
|
|||||||
self.screenshot.is_none()
|
self.screenshot.is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn raw_input_hook(&mut self, _ctx: &egui::Context, raw: &mut egui::RawInput) {
|
||||||
|
if raw.events.iter().any(|e| matches!(e, egui::Event::WindowFocused(false))) {
|
||||||
|
// winit can miss mouse-up events when macOS hides or minimizes the window.
|
||||||
|
// Release egui's pointer buttons too, so every widget remains interactive on return.
|
||||||
|
let pos = raw.screen_rect.map_or(egui::pos2(-1000.0, -1000.0), |r| r.max + egui::vec2(100.0, 100.0));
|
||||||
|
for button in [egui::PointerButton::Primary, egui::PointerButton::Secondary, egui::PointerButton::Middle] {
|
||||||
|
raw.events.push(egui::Event::PointerButton { pos, button, pressed: false, modifiers: egui::Modifiers::default() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut Ui, _frame: &mut eframe::Frame) {
|
fn ui(&mut self, ui: &mut Ui, _frame: &mut eframe::Frame) {
|
||||||
let ctx = ui.ctx().clone();
|
let ctx = ui.ctx().clone();
|
||||||
let t = Tokens::get(&ctx);
|
let t = Tokens::get(&ctx);
|
||||||
|
// If the window is hidden or minimized during a drag, macOS may not deliver the
|
||||||
|
// matching mouse-up. Clear terminal-side pointer capture on every pane, including
|
||||||
|
// panes that are currently hidden behind another tab.
|
||||||
|
let lost_focus = ctx.input(|i| i.events.iter().any(|e| matches!(e, egui::Event::WindowFocused(false))));
|
||||||
|
if lost_focus {
|
||||||
|
for pane in self.s.panes.values_mut() {
|
||||||
|
if let crate::views::View::Terminal(term) = &mut pane.view {
|
||||||
|
term.cancel_pointer_interaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
self.s.now = ctx.input(|i| i.time);
|
self.s.now = ctx.input(|i| i.time);
|
||||||
self.drain_events();
|
self.drain_events();
|
||||||
self.handle_requests();
|
self.handle_requests();
|
||||||
|
|||||||
+14
-1
@@ -238,7 +238,7 @@ fn window_controls(ui: &mut Ui, area: Rect) {
|
|||||||
ui.painter().text(r.center(), Align2::CENTER_CENTER, glyph, egui::FontId::proportional(14.0), c);
|
ui.painter().text(r.center(), Align2::CENTER_CENTER, glyph, egui::FontId::proportional(14.0), c);
|
||||||
if resp.clicked() {
|
if resp.clicked() {
|
||||||
match k {
|
match k {
|
||||||
0 => ctx.send_viewport_cmd(ViewportCommand::Minimized(true)),
|
0 => minimize_window(&ctx),
|
||||||
1 => ctx.send_viewport_cmd(ViewportCommand::Maximized(!max)),
|
1 => ctx.send_viewport_cmd(ViewportCommand::Maximized(!max)),
|
||||||
_ => ctx.send_viewport_cmd(ViewportCommand::Close),
|
_ => ctx.send_viewport_cmd(ViewportCommand::Close),
|
||||||
}
|
}
|
||||||
@@ -246,6 +246,19 @@ fn window_controls(ui: &mut Ui, area: Rect) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `ViewportCommand::Minimized(true)` can leave eframe unresponsive after restore on macOS.
|
||||||
|
/// Hiding the app follows the normal macOS app lifecycle and avoids that minimize event path.
|
||||||
|
fn minimize_window(_ctx: &egui::Context) {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
if let Some(mtm) = objc2::MainThreadMarker::new() {
|
||||||
|
objc2_app_kit::NSApplication::sharedApplication(mtm).hide(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
_ctx.send_viewport_cmd(ViewportCommand::Minimized(true));
|
||||||
|
}
|
||||||
|
|
||||||
/// Invisible window edges that resize the undecorated window.
|
/// Invisible window edges that resize the undecorated window.
|
||||||
pub fn resize_edges(ctx: &egui::Context) {
|
pub fn resize_edges(ctx: &egui::Context) {
|
||||||
use egui::{CursorIcon, ResizeDirection as D};
|
use egui::{CursorIcon, ResizeDirection as D};
|
||||||
|
|||||||
@@ -396,6 +396,13 @@ impl TermView {
|
|||||||
self.focus_requested = true;
|
self.focus_requested = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A window can lose its mouse-up event when it is hidden or minimized mid-drag.
|
||||||
|
/// Drop the transient pointer state while keeping any text already selected.
|
||||||
|
pub fn cancel_pointer_interaction(&mut self) {
|
||||||
|
self.drag = None;
|
||||||
|
self.mouse_down = None;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn selection_text(&self) -> Option<String> {
|
pub fn selection_text(&self) -> Option<String> {
|
||||||
self.term.selection_to_string().filter(|s| !s.is_empty())
|
self.term.selection_to_string().filter(|s| !s.is_empty())
|
||||||
}
|
}
|
||||||
@@ -780,13 +787,31 @@ impl TermView {
|
|||||||
self.term.scroll_display(Scroll::Delta(n));
|
self.term.scroll_display(Scroll::Delta(n));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::PointerButton { pos, button, pressed, modifiers } if resp.rect.contains(*pos) => {
|
Event::PointerButton { pos, button, pressed, modifiers } => {
|
||||||
let b = match button {
|
let b = match button {
|
||||||
PointerButton::Primary => 0,
|
PointerButton::Primary => 0,
|
||||||
PointerButton::Middle => 1,
|
PointerButton::Middle => 1,
|
||||||
PointerButton::Secondary => 2,
|
PointerButton::Secondary => 2,
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
|
// A mouse-up outside this widget still ends a drag that began here.
|
||||||
|
// Without this, the next pointer movement keeps extending the selection.
|
||||||
|
let was_mouse_down = self.mouse_down == Some(b);
|
||||||
|
if !*pressed {
|
||||||
|
if b == 0 {
|
||||||
|
self.drag = None;
|
||||||
|
}
|
||||||
|
if was_mouse_down {
|
||||||
|
self.mouse_down = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !resp.rect.contains(*pos) {
|
||||||
|
if !*pressed && was_mouse_down {
|
||||||
|
let (c, r) = to_cell(*pos);
|
||||||
|
out.input.extend(mouse_report(b, c, r, false, mode));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if reporting && b != 2 || reporting && mode.contains(TermMode::MOUSE_MODE) && b == 2 && !*pressed {
|
if reporting && b != 2 || reporting && mode.contains(TermMode::MOUSE_MODE) && b == 2 && !*pressed {
|
||||||
let (c, r) = to_cell(*pos);
|
let (c, r) = to_cell(*pos);
|
||||||
out.input.extend(mouse_report(b, c, r, *pressed, mode));
|
out.input.extend(mouse_report(b, c, r, *pressed, mode));
|
||||||
|
|||||||
Reference in New Issue
Block a user