Document tabs: a middle-click closes the tab, as the × does (#6506)

This commit is contained in:
HassanKhan
2026-11-11 15:19:26 +05:00
parent e1c199a184
commit 03a7320a0c
3 changed files with 55 additions and 2 deletions
+4 -2
View File
@@ -136,7 +136,7 @@ pub(super) fn first_tab_shown(widths: &[f32], active: Option<usize>, room: f32)
/// strip shows is the highlighted one. A tab dragged more than [`crate::doc_windows::TEAR_OFF`]
/// above or below the strip floats its view in a window under the pointer; one dragged along the
/// strip moves its document among the others (`document.move`, #5663), a line marking where it
/// will go.
/// will go. A middle-click on a tab closes it, as its × does (#6506).
pub fn doc_tabs(app: &mut VectorcraftApp, ui: &mut Ui) {
/// The width of the » button.
const MORE: f32 = 30.0;
@@ -200,7 +200,9 @@ pub fn doc_tabs(app: &mut VectorcraftApp, ui: &mut Ui) {
icons::paint(ui, "x", xr, if xresp.hovered() { t.text_strong } else { t.text });
ui.painter().galley(egui::pos2(title_left, r.center().y - galley.size().y / 2.0), galley.clone(), t.text);
hovering_art |= crate::document_drag::hover_tab(app, &resp, win, xr.expand(3.0));
if xresp.clicked() {
// A middle-click anywhere on the tab closes it as the × does (#6506), so a modified
// document still asks to save first.
if xresp.clicked() || resp.middle_clicked() || xresp.middle_clicked() {
close = Some(win);
} else if resp.clicked() {
activate = Some(win);
+50
View File
@@ -505,6 +505,56 @@ fn a_tabs_close_box_sits_where_the_platform_puts_it() {
assert!(super::CLOSE_BEFORE_TITLE == cfg!(target_os = "macos"));
}
/// A middle-click on a document tab closes it as the × does (#6506): a saved document closes, and
/// a modified one asks to save first instead of losing its changes.
#[test]
fn a_middle_click_on_a_tab_closes_it_as_the_close_box_does() {
let mut app = crate::VectorcraftApp::new(Session::new(), Default::default());
for _ in 0..3 {
app.session.execute("file.new", &json!({"width": 100, "height": 100})).unwrap();
}
// The second document has unsaved changes; the third stays the current one.
app.session.set_active(1);
app.session.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap();
app.session.set_active(2);
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1600.0, 200.0));
let frame = |app: &mut crate::VectorcraftApp, events: Vec<egui::Event>| {
let mut out = ctx.run_ui(egui::RawInput { screen_rect: Some(screen), events, ..Default::default() }, |ui| super::doc_tabs(app, ui));
out.textures_delta.clear();
let mut titles = vec![];
for c in &out.shapes {
if let egui::Shape::Text(t) = &c.shape
&& t.visual_bounding_rect().width() > 30.0
{
titles.push(t.visual_bounding_rect());
}
}
titles.sort_by(|a, b| a.left().total_cmp(&b.left()));
titles
};
let middle_click = |app: &mut crate::VectorcraftApp, at: egui::Pos2| {
let press = |pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Middle, pressed, modifiers: Default::default() };
frame(app, vec![egui::Event::PointerMoved(at), press(true)]);
frame(app, vec![press(false)])
};
let titles = frame(&mut app, vec![]);
assert_eq!(titles.len(), 3, "{titles:?}");
// The modified document's tab: the save prompt comes up and the document stays open.
let titles = middle_click(&mut app, titles[1].center());
assert_eq!(app.session.documents().len(), 3, "a modified document is not closed without asking");
assert_eq!(app.ui.dialog.as_ref().map(|d| d.kind.as_str()), Some(crate::unsaved::KIND), "a modified document asks first");
app.ui.dialog = None;
// The first, saved document's tab: it closes, and the others stay open.
let uid = |app: &crate::VectorcraftApp, i: usize| app.session.documents()[i].uid;
let (second, third) = (uid(&app, 1), uid(&app, 2));
middle_click(&mut app, titles[0].center());
assert_eq!(app.session.documents().len(), 2, "the middle-click closed the first document");
assert_eq!((uid(&app, 0), uid(&app, 1)), (second, third));
assert!(app.ui.dialog.is_none(), "a saved document closes without asking");
}
/// One headless frame of the status bar; returns the artboard navigator's buttons, left to
/// right: first, previous, next, last.
fn navigator(app: &mut crate::VectorcraftApp, ctx: &egui::Context, events: Vec<egui::Event>) -> Vec<egui::Rect> {
+1
View File
@@ -363,6 +363,7 @@ next unit in the order a right-click on a ruler lists them, from Feet back to Po
|---|---|---|
| Selection artwork drag hovers a visible document tab to activate it, then drops editable artwork on its canvas | done | `tests_document_drag`, `edit.dropSelection`, lossless resource and one-step Undo regressions; overflow and cross-window drag remain separate |
| A document tab dragged along the tab strip moves its document; a line marks where it goes, the current document and every document's view stay as they were, and a drag off the strip still floats it | done | #5663, [public source](https://helpx.adobe.com/illustrator/using/customizing-workspace.html); `document.move`; `tests::tab_reorder`, `chrome::tests` (drop index with floating and view tabs), `engine` `tests::document_move` |
| A middle-click on a document tab closes it as its × does; a modified document asks to save first (VectorCraft addition, as in PhotoCraft) | done | #6506; `chrome::tests::a_middle_click_on_a_tab_closes_it_as_the_close_box_does`; not scored |
| Behaviour | Status | Notes |
|---|---|---|