Merge branch 'main' into sonar/format-mandated-sha1

This commit is contained in:
FredApps
2026-11-10 18:23:24 +01:00
committed by GitHub
8 changed files with 154 additions and 4 deletions
+3 -1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -92,6 +92,7 @@ pub fn show(app: &mut WordApp, ui: &mut Ui) {
if drag.drag_started() {
ui.ctx().send_viewport_cmd(egui::ViewportCommand::StartDrag);
}
crate::chrome::note_title_drag(ui.ctx(), band);
// The chevron's stroke starts at the content edge.
let c = if rtl {
pos2(band.max.x - 26.0, band.center().y)
+22
View File
@@ -281,6 +281,24 @@ fn autosave_knob_x(rtl: bool, on: bool, r: Rect) -> f32 {
crate::rtl::lead_x(rtl != on, r, 8.0)
}
/// The `ctx.data` key for the part of the title bar that drags this document window.
fn title_drag_id(ctx: &egui::Context) -> egui::Id {
egui::Id::unique(("title_drag_rect", ctx.viewport_id()))
}
/// Notes `rect` as the part of the title bar that drags the window in this frame: behind a modal
/// dialog its backdrop has the pointer there, and moves the window instead ([`title_drag_rect`]).
pub(crate) fn note_title_drag(ctx: &egui::Context, rect: Rect) {
let (key, pass) = (title_drag_id(ctx), ctx.cumulative_pass_nr());
ctx.data_mut(|d| d.insert_temp(key, (rect, pass)));
}
/// The part of the title bar that drags the window, if it was drawn in this frame.
pub(crate) fn title_drag_rect(ctx: &egui::Context) -> Option<Rect> {
let (key, pass) = (title_drag_id(ctx), ctx.cumulative_pass_nr());
ctx.data(|d| d.get_temp::<(Rect, u64)>(key)).filter(|(_, p)| *p == pass).map(|(r, _)| r)
}
/// Draws the title bar with window dragging, AutoSave, quick access, account, community and search
/// controls, and the centred clickable document title.
pub fn title_bar(app: &mut WordApp, ui: &mut Ui) {
@@ -301,6 +319,10 @@ pub fn title_bar(app: &mut WordApp, ui: &mut Ui) {
if drag.drag_started() {
ui.ctx().send_viewport_cmd(egui::ViewportCommand::StartDrag);
}
// Elsewhere the system's own title bar above this one moves the window.
if mac {
note_title_drag(ui.ctx(), full);
}
if drag.double_clicked() {
let max = ui.ctx().input(|i| i.viewport().maximized.unwrap_or(false));
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Maximized(!max));
+1
View File
@@ -455,6 +455,7 @@ pub fn show(app: &mut WordApp, ctx: &egui::Context) {
let resp = egui::Modal::new(Id::unique("wordcraft.colorsDialog")).show(ctx, |ui| body(ui, &mut d));
// egui's Modal blocks the window behind it, but not the document's keyboard.
crate::dialogs::release_keyboard(app, ctx);
crate::dialogs::drag_by_title_bar(ctx, &resp.backdrop_response, resp.response.rect);
let mut done = resp.inner;
if done.is_none() && resp.should_close() {
done = Some(false);
+3 -1
View File
@@ -34,7 +34,9 @@ use bookmarks::bookmark_at_caret;
#[cfg(test)]
use bookmarks::bookmark_names;
pub use chrome::show;
pub(crate) use chrome::{block_behind, buttons, cancel_key_pressed, color_menu, forget_nudge, ok_cancel_responses, release_keyboard};
pub(crate) use chrome::{
block_behind, buttons, cancel_key_pressed, color_menu, drag_by_title_bar, forget_nudge, ok_cancel_responses, release_keyboard,
};
pub(crate) use dispatch::search_results;
pub(crate) use find::swallow_shortcut_text;
pub use find::{FindExtra, FindTab, GoToWhat};
+11
View File
@@ -67,6 +67,16 @@ fn count_dialog_frame(ctx: &egui::Context, id: egui::Id) {
}
}
/// Behind a modal window, whose `backdrop` has the pointer, the app still moves by its title bar,
/// as in Word for Mac (#5124); not from just inside the window's edge, where it reaches over the
/// title bar.
pub(crate) fn drag_by_title_bar(ctx: &egui::Context, backdrop: &egui::Response, window: egui::Rect) {
let origin = ctx.input(|i| i.pointer.press_origin()).filter(|p| !window.contains(*p));
if backdrop.drag_started() && origin.is_some_and(|p| crate::chrome::title_drag_rect(ctx).is_some_and(|r| r.contains(p))) {
ctx.send_viewport_cmd(egui::ViewportCommand::StartDrag);
}
}
/// Makes `window` modal (#3859): the document gives up the keyboard now, and from the next frame
/// nothing beneath the window takes the pointer or Tab. A press beside it nudges it ([`nudge`]).
pub(crate) fn block_behind(app: &WordApp, ctx: &egui::Context, window: &egui::Response) {
@@ -81,6 +91,7 @@ pub(crate) fn block_behind(app: &WordApp, ctx: &egui::Context, window: &egui::Re
.inner;
// The backdrop also has the pointer just inside the window's edge (egui's hit radius).
let beside = ctx.input(|i| i.pointer.any_pressed() && i.pointer.press_origin().is_some_and(|p| !window.rect.contains(p)));
drag_by_title_bar(ctx, &backdrop, window.rect);
ctx.move_to_top(layer);
ctx.memory_mut(|m| m.set_modal_layer(layer));
ctx.accesskit_node_builder(window.id, |node| node.set_modal());
+110
View File
@@ -2078,6 +2078,116 @@ fn the_page_waits_for_the_keyboard_until_a_modal_dialog_closes() {
assert_eq!(h.state().session.doc.plain_text(wordcraft_doc::StoryRef::Body), "!", "the page has it after the dialog");
}
/// Drags from `at` to 30 points right in `h`, and counts the window drags the app asked for.
fn window_drags(h: &mut egui_kittest::Harness<WordApp>, at: egui::Pos2) -> usize {
let mut drags = 0;
let button = |pos, pressed| egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers: egui::Modifiers::NONE };
let events = [egui::Event::PointerMoved(at), button(at, true)]
.into_iter()
.chain([10.0, 20.0, 30.0].map(|dx| egui::Event::PointerMoved(at + vec2(dx, 0.0))))
.chain([button(at + vec2(30.0, 0.0), false)]);
for e in events {
h.event(e);
h.step();
drags += h.output().viewport_output.values().flat_map(|v| &v.commands).filter(|c| matches!(c, egui::ViewportCommand::StartDrag)).count();
}
drags
}
/// #5124: behind a modal dialog the window still moves by its title bar, as in Word for Mac, where
/// WordCraft draws it, or by the top of File's side menu, which covers it there; elsewhere the
/// system's title bar does, and WordCraft's bar under it doesn't.
#[test]
fn the_window_moves_by_its_title_bar_behind_a_modal_dialog() {
for (mac, backstage) in [(true, false), (true, true), (false, false), (false, true)] {
let mut a = WordApp::new(Session::new(wordcraft_doc::Document::new()), crate::Services::default());
a.integrated_titlebar = mac;
a.ui.backstage = backstage;
let mut h = app_harness(a);
h.run_steps(6);
let font = Dialog::open("font", h.state_mut());
h.state_mut().dialog = font;
h.run_steps(4);
let at = egui::pos2(if backstage { 100.0 } else { 300.0 }, 12.0);
assert_eq!(window_drags(&mut h, at), usize::from(mac), "macOS: {mac}, File: {backstage}");
assert!(matches!(h.state().dialog, Some(Dialog::Font { .. })), "Font stays");
assert_eq!(h.state().ui.backstage, backstage, "nothing behind it opened or closed");
}
}
/// Opens a modal state in `a`: the built-in file browser, a dialog by name, or Colors.
fn open_modal_state(a: &mut WordApp, state: &str) {
match state {
"none" => {}
"fileBrowser" => {
let document = a.session.document_id();
a.show_file_browser(
crate::file_dialogs::FileDialogRequest::Open { purpose: "document".into() },
crate::file_dialogs::AfterPick::OpenDocument { document },
false,
);
}
"colors" => a.colors = Some(crate::colors::ColorsDialog::new(None, crate::colors::Target::cmd("format.color"))),
dialog => a.dialog = Dialog::open(dialog, a),
}
}
/// The file browser, a modal dialog and Colors each block the window; nothing open or a modeless
/// dialog doesn't.
#[test]
fn modal_open_is_true_for_the_file_browser_a_modal_dialog_and_colors() {
for (state, modal) in [("none", false), ("find", false), ("font", true), ("fileBrowser", true), ("colors", true)] {
let mut a = WordApp::new(Session::new(wordcraft_doc::Document::new()), crate::Services::default());
open_modal_state(&mut a, state);
assert_eq!(a.modal_open(), modal, "{state}");
}
}
/// #5124: whatever is open, a drag on the title bar macOS draws moves the window; where the system
/// draws it, WordCraft's bar under it only does while nothing blocks it.
#[test]
fn the_title_bar_moves_the_window_in_every_modal_state() {
for (state, modal) in [("none", false), ("find", false), ("font", true), ("fileBrowser", true), ("colors", true)] {
for mac in [true, false] {
let mut a = WordApp::new(Session::new(wordcraft_doc::Document::new()), crate::Services::default());
a.integrated_titlebar = mac;
let mut h = app_harness(a);
h.run_steps(6);
open_modal_state(h.state_mut(), state);
h.run_steps(4);
assert_eq!(h.state().modal_open(), modal, "{state}");
assert_eq!(window_drags(&mut h, egui::pos2(300.0, 12.0)), usize::from(mac || !modal), "{state}, macOS: {mac}");
assert_eq!(h.state().modal_open(), modal, "{state} stays open");
}
}
}
/// #5124: where a dialog reaches over the title bar (a short window), a drag on the dialog is the
/// dialog's own and doesn't move the window.
#[test]
fn a_drag_on_a_dialog_over_the_title_bar_doesnt_move_the_window() {
let mut a = WordApp::new(Session::new(wordcraft_doc::Document::new()), crate::Services::default());
a.integrated_titlebar = true;
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1440.0, 420.0)).build_ui_state(
|ui, app: &mut WordApp| {
let ctx = ui.ctx().clone();
app.logic(&ctx);
app.ui(ui);
},
a,
);
h.run_steps(6);
let font = Dialog::open("font", h.state_mut());
h.state_mut().dialog = font;
h.run_steps(4);
let rect = h.ctx.memory(|m| m.area_rect(egui::Id::unique(("dialog", "Font")))).expect("the dialog is open");
let at = egui::pos2(rect.center().x, rect.top() + 8.0);
assert!(at.y < 30.0, "the dialog reaches over the title bar: {rect:?}");
assert_eq!(window_drags(&mut h, at), 0, "a drag on its title");
let rect = h.ctx.memory(|m| m.area_rect(egui::Id::unique(("dialog", "Font")))).expect("the dialog is open");
assert_eq!(window_drags(&mut h, egui::pos2(rect.center().x, rect.top() + 1.0)), 0, "nor just inside its edge");
}
#[test]
fn font_sample_props_map_each_field_to_its_property() {
use super::font::font_sample_props;
+3 -2
View File
File diff suppressed because one or more lines are too long