mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-24 10:27:44 -06:00
macOS: Control-click opens the context menu, with its spelling suggestions (#1025)
This commit is contained in:
@@ -3308,6 +3308,54 @@ mod tests {
|
||||
assert_eq!(o.frame().unwrap().2.wrap, wordcraft_doc::para::Wrap::Square);
|
||||
}
|
||||
|
||||
/// #996: on macOS a Control-click is the right-click (the trackpad's and the one-button
|
||||
/// mouse's way to it), so it opens the same menu and leaves the caret where the menu needs it.
|
||||
#[cfg(target_os = "macos")]
|
||||
#[test]
|
||||
fn control_click_on_macos_opens_the_context_menu() {
|
||||
use egui_kittest::kittest::Queryable;
|
||||
let mut a = WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::new()), Default::default());
|
||||
a.run("text.insert", json!({"text": "The quick brwn fox"})).unwrap();
|
||||
a.session.sel = wordcraft_engine::Selection::caret(Pos::body(0, 12));
|
||||
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1440.0, 900.0)).build_ui_state(
|
||||
|ui, app: &mut WordApp| {
|
||||
let ctx = ui.ctx().clone();
|
||||
app.logic(&ctx);
|
||||
app.ui(ui);
|
||||
},
|
||||
a,
|
||||
);
|
||||
for _ in 0..6 {
|
||||
h.step();
|
||||
}
|
||||
let (p, ht) = caret_screen(h.state_mut()).unwrap();
|
||||
let at = p + egui::vec2(0.0, ht / 2.0);
|
||||
h.state_mut().session.sel = wordcraft_engine::Selection::caret(Pos::body(0, 0));
|
||||
h.step();
|
||||
let ctrl = egui::Modifiers { ctrl: true, ..Default::default() };
|
||||
// The events winit delivers for a Control-click, through the app's input hook as eframe runs it.
|
||||
let send = |h: &mut egui_kittest::Harness<'_, WordApp>, events: Vec<egui::Event>| {
|
||||
let mut raw = egui::RawInput { events, ..Default::default() };
|
||||
h.state_mut().raw_input_hook(&mut raw);
|
||||
for e in raw.events {
|
||||
h.event(e);
|
||||
}
|
||||
h.step();
|
||||
};
|
||||
send(&mut h, vec![egui::Event::ModifiersChanged(ctrl), egui::Event::PointerMoved(at)]);
|
||||
for pressed in [true, false] {
|
||||
send(&mut h, vec![egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: ctrl }]);
|
||||
}
|
||||
h.event(egui::Event::ModifiersChanged(Default::default()));
|
||||
h.step();
|
||||
assert!(h.state().canvas.context_menu_open, "the Control-click opened the menu");
|
||||
h.get_by_label("brown").click();
|
||||
for _ in 0..3 {
|
||||
h.step();
|
||||
}
|
||||
assert_eq!(h.state().session.doc.plain_text(StoryRef::Body), "The quick brown fox");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_web_and_email_links_are_followed() {
|
||||
for ok in ["https://example.com/a?b#c", "http://example.com", "HTTPS://EXAMPLE.COM", "mailto:someone@example.com"] {
|
||||
|
||||
@@ -442,6 +442,8 @@ pub struct WordApp {
|
||||
queued_shots: Vec<(u64, f64, u32)>,
|
||||
pending_shots: Vec<(u64, Option<String>, std::sync::mpsc::Sender<Value>, f64)>,
|
||||
pub(crate) synthetic: Vec<egui::Event>,
|
||||
/// macOS: the primary button went down with Control, so it is being read as the secondary.
|
||||
control_click: bool,
|
||||
/// Badge rects recorded during layout for the current keytip phase (cleared by `logic`, painted by `show`).
|
||||
pub(crate) keytip_rects: Vec<(egui::Rect, String)>,
|
||||
/// The Navigation pane's search box takes the keyboard on its next frame (Ctrl+F, #712).
|
||||
@@ -571,6 +573,7 @@ impl WordApp {
|
||||
queued_shots: Vec::new(),
|
||||
pending_shots: Vec::new(),
|
||||
synthetic: Vec::new(),
|
||||
control_click: false,
|
||||
keytip_rects: Vec::new(),
|
||||
nav_search_focus: false,
|
||||
styled: false,
|
||||
@@ -1776,8 +1779,12 @@ impl WordApp {
|
||||
}
|
||||
}
|
||||
|
||||
/// Inject synthetic input events from the control channel (one pointer event per frame).
|
||||
/// Read a Control-click as the right-click on macOS, then inject synthetic input events from
|
||||
/// the control channel (one pointer event per frame).
|
||||
pub fn raw_input_hook(&mut self, raw: &mut egui::RawInput) {
|
||||
if cfg!(target_os = "macos") {
|
||||
control_click_as_secondary(&mut raw.events, &mut self.control_click);
|
||||
}
|
||||
if self.synthetic.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -2227,6 +2234,25 @@ pub fn now_ms() -> f64 {
|
||||
}
|
||||
}
|
||||
|
||||
/// macOS reads a Control-click as the right-click (winit reports the primary button with Control
|
||||
/// held), so menus open on it as on a two-finger click. The release follows its press even if
|
||||
/// Control was let go in between.
|
||||
fn control_click_as_secondary(events: &mut [egui::Event], held: &mut bool) {
|
||||
for e in events {
|
||||
if let egui::Event::PointerButton { button: button @ egui::PointerButton::Primary, pressed, modifiers, .. } = e {
|
||||
if *pressed {
|
||||
*held = modifiers.ctrl && !modifiers.mac_cmd;
|
||||
}
|
||||
if *held {
|
||||
*button = egui::PointerButton::Secondary;
|
||||
}
|
||||
if !*pressed {
|
||||
*held = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_perf;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user