Commit a68af56
4
files
+122
added
-7
deleted
@@ -561,9 +561,26 @@ impl App {
561
561
}
562
562
563
563
pub fn new_tab(&mut self) {
564
- let p = self.pane_mut();
565
- p.tabs.push(p.dir.path.clone());
566
- p.tab = p.tabs.len() - 1;
564
+ let path = self.pane().dir.path.clone();
565
+ self.new_tab_at(path);
566
+ }
567
+
568
+ /// Otwiera `path` w nowej karcie (np. środkowym przyciskiem myszy).
569
+ pub fn new_tab_at(&mut self, path: PathBuf) {
570
+ if !path.is_dir() {
571
+ return;
572
+ }
573
+ {
574
+ let p = self.pane_mut();
575
+ p.tabs.push(path);
576
+ p.tab = p.tabs.len() - 1;
577
+ if let Ok(dir) = DirView::load(&p.tabs[p.tab]) {
578
+ p.dir = dir;
579
+ p.sync_view();
580
+ }
581
+ }
582
+ let path = self.pane().dir.path.clone();
583
+ self.select_sidebar_for(&path);
567
584
}
568
585
569
586
pub fn close_tab(&mut self) {
@@ -1033,6 +1033,9 @@ pub fn handle_events(
1033
1033
(PointerButton::Right, ButtonState::Pressed) => {
1034
1034
on_right_press(app, pe.position, &lay, size, theme, text)
1035
1035
}
1036
+ (PointerButton::Middle, ButtonState::Pressed) => {
1037
+ on_middle_press(app, pe.position, &lay, theme)
1038
+ }
1036
1039
(PointerButton::Back, ButtonState::Pressed) => app.back(),
1037
1040
(PointerButton::Forward, ButtonState::Pressed) => app.forward(),
1038
1041
(_, ButtonState::Released) => {}
@@ -1400,6 +1403,42 @@ fn on_right_press(
1400
1403
}
1401
1404
}
1402
1405
1406
+/// Środkowy przycisk myszy: otwiera folder (albo miejsce z panelu bocznego)
1407
+/// w nowej karcie — jak w Nautilusie. Dla plików nic nie robi.
1408
+fn on_middle_press(app: &mut App, p: Point, lay: &Layout, theme: &Theme) {
1409
+ let count = if app.split { 2 } else { 1 };
1410
+ for i in 0..count {
1411
+ if !lay.panes[i].contains(p) {
1412
+ continue;
1413
+ }
1414
+ let pr = pane_rects(app, lay, i, theme);
1415
+ let rows = file_rows(&app.panes[i], pr.items);
1416
+ if let Some(index) = hit_index(&app.panes[i], rows, p, theme) {
1417
+ if let Some(entry) = app.panes[i].dir.entry(index) {
1418
+ let path = entry.path.clone();
1419
+ if entry.is_dir {
1420
+ app.new_tab_at(path);
1421
+ }
1422
+ }
1423
+ }
1424
+ return;
1425
+ }
1426
+
1427
+ if lay.sidebar.contains(p) {
1428
+ let hovered = app
1429
+ .sidebar
1430
+ .index_at(lay.sidebar.deflate(Insets::splat(6.0)), p, theme);
1431
+ if let Some(entry) = hovered {
1432
+ if let Some(action) = app.sidebar_entry_action.get(entry).copied().flatten() {
1433
+ if let Some(SideAction::Go(path)) = app.sidebar_actions.get(action) {
1434
+ let path = path.clone();
1435
+ app.new_tab_at(path);
1436
+ }
1437
+ }
1438
+ }
1439
+ }
1440
+}
1441
+
1403
1442
fn on_key(
1404
1443
app: &mut App,
1405
1444
ke: &KeyEvent,
@@ -2175,6 +2214,31 @@ mod tests {
2175
2214
);
2176
2215
}
2177
2216
2217
+ #[test]
2218
+ fn middle_click_opens_folder_in_new_tab() {
2219
+ let dir = std::env::temp_dir().join("luna-files-middle-click");
2220
+ let _ = std::fs::remove_dir_all(&dir);
2221
+ std::fs::create_dir_all(dir.join("podkatalog")).unwrap();
2222
+ let mut app = App::new(dir.clone());
2223
+ let theme = Theme::light();
2224
+ let text = TextEngine::system();
2225
+ let size = Size::new(1120.0, 720.0);
2226
+ let lay = layout(&app, size, &theme, &text);
2227
+ let pr = pane_rects(&app, &lay, 0, &theme);
2228
+ let rows = file_rows(&app.panes[0], pr.items);
2229
+ let row_h = theme.metrics.itemview.row_h;
2230
+ on_middle_press(
2231
+ &mut app,
2232
+ Point::new(rows.center().x, rows.y() + row_h / 2.0),
2233
+ &lay,
2234
+ &theme,
2235
+ );
2236
+ assert_eq!(app.pane().tabs.len(), 2);
2237
+ assert_eq!(app.pane().dir.path, dir.join("podkatalog"));
2238
+ assert_eq!(app.pane().tab, 1);
2239
+ let _ = std::fs::remove_dir_all(&dir);
2240
+ }
2241
+
2178
2242
#[test]
2179
2243
fn about_dialog_can_be_opened() {
2180
2244
let mut app = app();
@@ -1110,8 +1110,11 @@ fn map_button(code: u32) -> PointerButton {
1110
1110
0x110 => PointerButton::Left,
1111
1111
0x111 => PointerButton::Right,
1112
1112
0x112 => PointerButton::Middle,
1113
- 0x116 => PointerButton::Back,
1114
- 0x115 => PointerButton::Forward,
1113
+ // Myszy raportują „wstecz/dalej” na dwa sposoby: BTN_SIDE/BTN_EXTRA
1114
+ // (0x113/0x114 — tak robi większość myszy i X11 jako 8/9) oraz
1115
+ // BTN_BACK/BTN_FORWARD (0x116/0x115). Obsługujemy oba warianty.
1116
+ 0x113 | 0x116 => PointerButton::Back,
1117
+ 0x114 | 0x115 => PointerButton::Forward,
1115
1118
other => PointerButton::Other(other),
1116
1119
}
1117
1120
}
@@ -1505,4 +1508,16 @@ mod tests {
1505
1508
assert_eq!(resize_edge(2 | 8), xdg_toplevel::ResizeEdge::BottomRight);
1506
1509
assert_eq!(resize_edge(0), xdg_toplevel::ResizeEdge::None);
1507
1510
}
1511
+
1512
+ #[test]
1513
+ fn mouse_side_buttons_map_to_back_and_forward() {
1514
+ // Większość myszy: BTN_SIDE/BTN_EXTRA (X11: 8/9).
1515
+ assert_eq!(map_button(0x113), PointerButton::Back);
1516
+ assert_eq!(map_button(0x114), PointerButton::Forward);
1517
+ // Niektóre urządzenia raportują BTN_BACK/BTN_FORWARD.
1518
+ assert_eq!(map_button(0x116), PointerButton::Back);
1519
+ assert_eq!(map_button(0x115), PointerButton::Forward);
1520
+ assert_eq!(map_button(0x112), PointerButton::Middle);
1521
+ assert_eq!(map_button(0x999), PointerButton::Other(0x999));
1522
+ }
1508
1523
}
@@ -358,8 +358,10 @@ fn decode(bytes: &[u8]) -> Option<Decoded> {
358
358
0x110 => Some(Decoded::Button(PointerButton::Left, pressed)),
359
359
0x111 => Some(Decoded::Button(PointerButton::Right, pressed)),
360
360
0x112 => Some(Decoded::Button(PointerButton::Middle, pressed)),
361
- 0x115 => Some(Decoded::Button(PointerButton::Forward, pressed)),
362
- 0x116 => Some(Decoded::Button(PointerButton::Back, pressed)),
361
+ // „Wstecz/dalej” bywa raportowane jako BTN_SIDE/BTN_EXTRA
362
+ // (większość myszy, X11: przyciski 8/9) albo BTN_BACK/FORWARD.
363
+ 0x113 | 0x116 => Some(Decoded::Button(PointerButton::Back, pressed)),
364
+ 0x114 | 0x115 => Some(Decoded::Button(PointerButton::Forward, pressed)),
363
365
// Pozostałe `BTN_*` (touchpad, tablet, pióro, joystick) **nie** są
364
366
// klawiszami — bez tego filtr przesyłał dotknięcia panelu jako klawisze.
365
367
0x100..=0x15f => None,
@@ -637,6 +639,23 @@ mod tests {
637
639
decode(&raw(EV_KEY, 0x110, 1)),
638
640
Some(Decoded::Button(PointerButton::Left, true))
639
641
);
642
+ // Boki myszy — oba warianty kodów (BTN_SIDE/EXTRA oraz BTN_BACK/FORWARD).
643
+ assert_eq!(
644
+ decode(&raw(EV_KEY, 0x113, 1)),
645
+ Some(Decoded::Button(PointerButton::Back, true))
646
+ );
647
+ assert_eq!(
648
+ decode(&raw(EV_KEY, 0x114, 1)),
649
+ Some(Decoded::Button(PointerButton::Forward, true))
650
+ );
651
+ assert_eq!(
652
+ decode(&raw(EV_KEY, 0x116, 1)),
653
+ Some(Decoded::Button(PointerButton::Back, true))
654
+ );
655
+ assert_eq!(
656
+ decode(&raw(EV_KEY, 0x115, 1)),
657
+ Some(Decoded::Button(PointerButton::Forward, true))
658
+ );
640
659
assert_eq!(
641
660
decode(&raw(EV_KEY, 30, 0)),
642
661
Some(Decoded::Key(30, 0x0061, false))