← luna

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))