diff --git a/Cargo.lock b/Cargo.lock index 52920979..0def5422 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2751,6 +2751,7 @@ version = "0.2.5" dependencies = [ "anyhow", "arc-swap", + "bitflags 2.5.0", "crossterm", "globset", "indexmap", diff --git a/yazi-adaptor/src/chafa.rs b/yazi-adaptor/src/chafa.rs index fc117538..1e3a7428 100644 --- a/yazi-adaptor/src/chafa.rs +++ b/yazi-adaptor/src/chafa.rs @@ -53,6 +53,7 @@ impl Chafa { height: lines.len() as u16, }; + Adaptor::Chafa.image_hide()?; Adaptor::shown_store(area); Emulator::move_lock((max.x, max.y), |stderr| { for (i, line) in lines.into_iter().enumerate() { diff --git a/yazi-config/Cargo.toml b/yazi-config/Cargo.toml index 18f5fafa..7d239099 100644 --- a/yazi-config/Cargo.toml +++ b/yazi-config/Cargo.toml @@ -14,6 +14,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" } # External dependencies anyhow = "1.0.86" arc-swap = "1.7.1" +bitflags = "2.5.0" crossterm = "0.27.0" globset = "0.4.14" indexmap = "2.2.6" diff --git a/yazi-config/preset/yazi.toml b/yazi-config/preset/yazi.toml index 5470abc0..74782dfd 100644 --- a/yazi-config/preset/yazi.toml +++ b/yazi-config/preset/yazi.toml @@ -13,6 +13,7 @@ linemode = "none" show_hidden = false show_symlink = true scrolloff = 5 +mouse_events = [ "click", "scroll" ] [preview] tab_size = 2 @@ -86,10 +87,8 @@ fetchers = [ ] preloaders = [ # Image - { mime = "image/svg+xml", run = "magick" }, - { mime = "image/heic", run = "magick" }, - { mime = "image/jxl", run = "magick" }, - { mime = "image/*", run = "image" }, + { mime = "image/{heic,jxl,svg+xml}", run = "magick" }, + { mime = "image/*", run = "image" }, # Video { mime = "video/*", run = "video" }, # PDF @@ -106,10 +105,8 @@ previewers = [ # JSON { mime = "application/json", run = "json" }, # Image - { mime = "image/svg+xml", run = "magick" }, - { mime = "image/heic", run = "magick" }, - { mime = "image/jxl", run = "magick" }, - { mime = "image/*", run = "image" }, + { mime = "image/{heic,jxl,svg+xml}", run = "magick" }, + { mime = "image/*", run = "image" }, # Video { mime = "video/*", run = "video" }, # PDF diff --git a/yazi-config/src/manager/manager.rs b/yazi-config/src/manager/manager.rs index 9386a9dd..e4064f01 100644 --- a/yazi-config/src/manager/manager.rs +++ b/yazi-config/src/manager/manager.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use validator::Validate; -use super::{ManagerRatio, SortBy}; +use super::{ManagerRatio, MouseEvents, SortBy}; use crate::{validation::check_validation, MERGED_YAZI}; #[derive(Debug, Deserialize, Serialize, Validate)] @@ -21,6 +21,7 @@ pub struct Manager { pub show_hidden: bool, pub show_symlink: bool, pub scrolloff: u8, + pub mouse_events: MouseEvents, } impl Default for Manager { diff --git a/yazi-config/src/manager/mod.rs b/yazi-config/src/manager/mod.rs index 2ddd6599..f8b9b13c 100644 --- a/yazi-config/src/manager/mod.rs +++ b/yazi-config/src/manager/mod.rs @@ -1,7 +1,9 @@ mod manager; +mod mouse; mod ratio; mod sorting; pub use manager::*; +pub use mouse::*; pub use ratio::*; pub use sorting::*; diff --git a/yazi-config/src/manager/mouse.rs b/yazi-config/src/manager/mouse.rs new file mode 100644 index 00000000..812bd06b --- /dev/null +++ b/yazi-config/src/manager/mouse.rs @@ -0,0 +1,63 @@ +use anyhow::{bail, Result}; +use bitflags::bitflags; +use crossterm::event::MouseEventKind; +use serde::{Deserialize, Serialize}; + +bitflags! { + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize, Serialize)] + #[serde(try_from = "Vec", into = "Vec")] + pub struct MouseEvents: u8 { + const CLICK = 0b00001; + const SCROLL = 0b00010; + const TOUCH = 0b00100; + const MOVE = 0b01000; + const DRAG = 0b10000; + } +} + +impl MouseEvents { + #[inline] + pub const fn draggable(self) -> bool { self.contains(Self::DRAG) } +} + +impl TryFrom> for MouseEvents { + type Error = anyhow::Error; + + fn try_from(value: Vec) -> Result { + value.into_iter().try_fold(Self::empty(), |aac, s| { + Ok(match s.as_str() { + "click" => aac | Self::CLICK, + "scroll" => aac | Self::SCROLL, + "touch" => aac | Self::TOUCH, + "move" => aac | Self::MOVE, + "drag" => aac | Self::DRAG, + _ => bail!("Invalid mouse event: {s}"), + }) + }) + } +} + +impl From for Vec { + fn from(value: MouseEvents) -> Self { + let events = [ + (MouseEvents::CLICK, "click"), + (MouseEvents::SCROLL, "scroll"), + (MouseEvents::TOUCH, "touch"), + (MouseEvents::MOVE, "move"), + (MouseEvents::DRAG, "drag"), + ]; + events.into_iter().filter(|v| value.contains(v.0)).map(|v| v.1.to_owned()).collect() + } +} + +impl From for MouseEvents { + fn from(value: crossterm::event::MouseEventKind) -> Self { + match value { + MouseEventKind::Down(_) | MouseEventKind::Up(_) => Self::CLICK, + MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => Self::SCROLL, + MouseEventKind::ScrollLeft | MouseEventKind::ScrollRight => Self::TOUCH, + MouseEventKind::Moved => Self::MOVE, + MouseEventKind::Drag(_) => Self::DRAG, + } + } +} diff --git a/yazi-core/src/manager/commands/update_files.rs b/yazi-core/src/manager/commands/update_files.rs index 675c5bd2..10a73c8a 100644 --- a/yazi-core/src/manager/commands/update_files.rs +++ b/yazi-core/src/manager/commands/update_files.rs @@ -111,9 +111,10 @@ impl Manager { |(p, pp)| matches!(*op, FilesOp::Deleting(ref parent, ref urls) if *parent == pp && urls.contains(p)), ); - if let Some(f) = tab.history.get_mut(op.url()) { - let hovered = f.hovered().filter(|_| f.tracing).map(|h| h.url()); - _ = f.update(op.into_owned()) && f.repos(hovered); + let folder = tab.history.entry(op.url().clone()).or_insert_with(|| Folder::from(op.url())); + let hovered = folder.hovered().filter(|_| folder.tracing).map(|h| h.url()); + if folder.update(op.into_owned()) { + folder.repos(hovered); } if leave { diff --git a/yazi-fm/src/app/app.rs b/yazi-fm/src/app/app.rs index f69da7e3..95ed35b5 100644 --- a/yazi-fm/src/app/app.rs +++ b/yazi-fm/src/app/app.rs @@ -56,6 +56,7 @@ impl App { Event::Seq(cmds, layer) => self.dispatch_seq(cmds, layer), Event::Render => self.dispatch_render(), Event::Key(key) => self.dispatch_key(key), + Event::Mouse(mouse) => self.mouse(mouse), Event::Resize => self.resize(()), Event::Paste(str) => self.dispatch_paste(str), Event::Quit(opt) => self.quit(opt), diff --git a/yazi-fm/src/app/commands/mod.rs b/yazi-fm/src/app/commands/mod.rs index 72d279a4..4cc55b54 100644 --- a/yazi-fm/src/app/commands/mod.rs +++ b/yazi-fm/src/app/commands/mod.rs @@ -1,4 +1,5 @@ mod accept_payload; +mod mouse; mod notify; mod plugin; mod quit; diff --git a/yazi-fm/src/app/commands/mouse.rs b/yazi-fm/src/app/commands/mouse.rs new file mode 100644 index 00000000..5e786efc --- /dev/null +++ b/yazi-fm/src/app/commands/mouse.rs @@ -0,0 +1,65 @@ +use crossterm::event::{MouseEvent, MouseEventKind}; +use mlua::Table; +use ratatui::layout::{Position, Rect}; +use tracing::error; +use yazi_config::{LAYOUT, MANAGER}; +use yazi_plugin::{bindings::Cast, LUA}; + +use crate::{app::App, components, lives::Lives}; + +pub struct Opt { + event: MouseEvent, +} + +impl From for Opt { + fn from(event: MouseEvent) -> Self { Self { event } } +} + +impl App { + pub(crate) fn mouse(&mut self, opt: impl Into) { + let event = (opt.into() as Opt).event; + + let layout = LAYOUT.load(); + let position = Position { x: event.column, y: event.row }; + + if matches!(event.kind, MouseEventKind::Moved | MouseEventKind::Drag(_)) { + self.mouse_do(crate::Root::mouse, event, None); + return; + } + + if layout.current.contains(position) { + self.mouse_do(components::Current::mouse, event, Some(layout.current)); + } else if layout.preview.contains(position) { + self.mouse_do(components::Preview::mouse, event, Some(layout.preview)); + } else if layout.parent.contains(position) { + self.mouse_do(components::Parent::mouse, event, Some(layout.parent)); + } else if layout.header.contains(position) { + self.mouse_do(components::Header::mouse, event, Some(layout.header)); + } else if layout.status.contains(position) { + self.mouse_do(components::Status::mouse, event, Some(layout.status)); + } + } + + fn mouse_do( + &self, + f: impl FnOnce(MouseEvent) -> mlua::Result<()>, + mut event: MouseEvent, + rect: Option, + ) { + if matches!(event.kind, MouseEventKind::Down(_) if MANAGER.mouse_events.draggable()) { + let evt = yazi_plugin::bindings::MouseEvent::cast(&LUA, event); + if let (Ok(evt), Ok(root)) = (evt, LUA.globals().raw_get::<_, Table>("Root")) { + root.raw_set("drag_start", evt).ok(); + } + } + + if let Some(rect) = rect { + event.row -= rect.y; + event.column -= rect.x; + } + + if let Err(e) = Lives::scope(&self.cx, move |_| f(event)) { + error!("{:?}", e); + } + } +} diff --git a/yazi-fm/src/components/current.rs b/yazi-fm/src/components/current.rs new file mode 100644 index 00000000..b8f125e0 --- /dev/null +++ b/yazi-fm/src/components/current.rs @@ -0,0 +1,23 @@ +use crossterm::event::MouseEventKind; +use mlua::{Table, TableExt}; +use yazi_plugin::{bindings::{Cast, MouseEvent}, LUA}; + +pub(crate) struct Current; + +impl Current { + pub fn mouse(event: crossterm::event::MouseEvent) -> mlua::Result<()> { + let evt = MouseEvent::cast(&LUA, event)?; + let comp: Table = LUA.globals().raw_get("Current")?; + + match event.kind { + MouseEventKind::Down(_) => comp.call_method("click", (evt, false))?, + MouseEventKind::Up(_) => comp.call_method("click", (evt, true))?, + MouseEventKind::ScrollDown => comp.call_method("scroll", (evt, 1))?, + MouseEventKind::ScrollUp => comp.call_method("scroll", (evt, -1))?, + MouseEventKind::ScrollRight => comp.call_method("touch", (evt, 1))?, + MouseEventKind::ScrollLeft => comp.call_method("touch", (evt, -1))?, + _ => (), + } + Ok(()) + } +} diff --git a/yazi-fm/src/components/header.rs b/yazi-fm/src/components/header.rs index 12dcca5d..d366a19c 100644 --- a/yazi-fm/src/components/header.rs +++ b/yazi-fm/src/components/header.rs @@ -1,7 +1,8 @@ +use crossterm::event::MouseEventKind; use mlua::{Table, TableExt}; use ratatui::{buffer::Buffer, widgets::Widget}; use tracing::error; -use yazi_plugin::{bindings::Cast, elements::{render_widgets, Rect}, LUA}; +use yazi_plugin::{bindings::{Cast, MouseEvent}, elements::{render_widgets, Rect}, LUA}; pub(crate) struct Header; @@ -18,3 +19,21 @@ impl Widget for Header { } } } + +impl Header { + pub fn mouse(event: crossterm::event::MouseEvent) -> mlua::Result<()> { + let evt = MouseEvent::cast(&LUA, event)?; + let comp: Table = LUA.globals().raw_get("Header")?; + + match event.kind { + MouseEventKind::Down(_) => comp.call_method("click", (evt, false))?, + MouseEventKind::Up(_) => comp.call_method("click", (evt, true))?, + MouseEventKind::ScrollDown => comp.call_method("scroll", (evt, 1))?, + MouseEventKind::ScrollUp => comp.call_method("scroll", (evt, -1))?, + MouseEventKind::ScrollRight => comp.call_method("touch", (evt, 1))?, + MouseEventKind::ScrollLeft => comp.call_method("touch", (evt, -1))?, + _ => (), + } + Ok(()) + } +} diff --git a/yazi-fm/src/components/mod.rs b/yazi-fm/src/components/mod.rs index 3549f1df..1898af85 100644 --- a/yazi-fm/src/components/mod.rs +++ b/yazi-fm/src/components/mod.rs @@ -1,13 +1,17 @@ #![allow(clippy::module_inception)] +mod current; mod header; mod manager; +mod parent; mod preview; mod progress; mod status; +pub(super) use current::*; pub(super) use header::*; pub(super) use manager::*; +pub(super) use parent::*; pub(super) use preview::*; pub(super) use progress::*; pub(super) use status::*; diff --git a/yazi-fm/src/components/parent.rs b/yazi-fm/src/components/parent.rs new file mode 100644 index 00000000..92869478 --- /dev/null +++ b/yazi-fm/src/components/parent.rs @@ -0,0 +1,23 @@ +use crossterm::event::MouseEventKind; +use mlua::{Table, TableExt}; +use yazi_plugin::{bindings::{Cast, MouseEvent}, LUA}; + +pub(crate) struct Parent; + +impl Parent { + pub fn mouse(event: crossterm::event::MouseEvent) -> mlua::Result<()> { + let evt = MouseEvent::cast(&LUA, event)?; + let comp: Table = LUA.globals().raw_get("Parent")?; + + match event.kind { + MouseEventKind::Down(_) => comp.call_method("click", (evt, false))?, + MouseEventKind::Up(_) => comp.call_method("click", (evt, true))?, + MouseEventKind::ScrollDown => comp.call_method("scroll", (evt, 1))?, + MouseEventKind::ScrollUp => comp.call_method("scroll", (evt, -1))?, + MouseEventKind::ScrollRight => comp.call_method("touch", (evt, 1))?, + MouseEventKind::ScrollLeft => comp.call_method("touch", (evt, -1))?, + _ => (), + } + Ok(()) + } +} diff --git a/yazi-fm/src/components/preview.rs b/yazi-fm/src/components/preview.rs index 897a9f13..1b11a975 100644 --- a/yazi-fm/src/components/preview.rs +++ b/yazi-fm/src/components/preview.rs @@ -1,4 +1,7 @@ +use crossterm::event::MouseEventKind; +use mlua::{Table, TableExt}; use ratatui::{buffer::Buffer, widgets::Widget}; +use yazi_plugin::{bindings::{Cast, MouseEvent}, LUA}; use crate::Ctx; @@ -9,6 +12,22 @@ pub(crate) struct Preview<'a> { impl<'a> Preview<'a> { #[inline] pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } } + + pub fn mouse(event: crossterm::event::MouseEvent) -> mlua::Result<()> { + let evt = MouseEvent::cast(&LUA, event)?; + let comp: Table = LUA.globals().raw_get("Preview")?; + + match event.kind { + MouseEventKind::Down(_) => comp.call_method("click", (evt, false))?, + MouseEventKind::Up(_) => comp.call_method("click", (evt, true))?, + MouseEventKind::ScrollDown => comp.call_method("scroll", (evt, 1))?, + MouseEventKind::ScrollUp => comp.call_method("scroll", (evt, -1))?, + MouseEventKind::ScrollRight => comp.call_method("touch", (evt, 1))?, + MouseEventKind::ScrollLeft => comp.call_method("touch", (evt, -1))?, + _ => (), + } + Ok(()) + } } impl Widget for Preview<'_> { diff --git a/yazi-fm/src/components/status.rs b/yazi-fm/src/components/status.rs index dc0e3e0a..0208d781 100644 --- a/yazi-fm/src/components/status.rs +++ b/yazi-fm/src/components/status.rs @@ -1,7 +1,8 @@ +use crossterm::event::MouseEventKind; use mlua::{Table, TableExt}; use ratatui::widgets::Widget; use tracing::error; -use yazi_plugin::{bindings::Cast, elements::{render_widgets, Rect}, LUA}; +use yazi_plugin::{bindings::{Cast, MouseEvent}, elements::{render_widgets, Rect}, LUA}; pub(crate) struct Status; @@ -18,3 +19,21 @@ impl Widget for Status { } } } + +impl Status { + pub fn mouse(event: crossterm::event::MouseEvent) -> mlua::Result<()> { + let evt = MouseEvent::cast(&LUA, event)?; + let comp: Table = LUA.globals().raw_get("Status")?; + + match event.kind { + MouseEventKind::Down(_) => comp.call_method("click", (evt, false))?, + MouseEventKind::Up(_) => comp.call_method("click", (evt, true))?, + MouseEventKind::ScrollDown => comp.call_method("scroll", (evt, 1))?, + MouseEventKind::ScrollUp => comp.call_method("scroll", (evt, -1))?, + MouseEventKind::ScrollRight => comp.call_method("touch", (evt, 1))?, + MouseEventKind::ScrollLeft => comp.call_method("touch", (evt, -1))?, + _ => (), + } + Ok(()) + } +} diff --git a/yazi-fm/src/root.rs b/yazi-fm/src/root.rs index f5dfdda0..3ffeaaf8 100644 --- a/yazi-fm/src/root.rs +++ b/yazi-fm/src/root.rs @@ -1,4 +1,7 @@ +use crossterm::event::MouseEventKind; +use mlua::{Table, TableExt}; use ratatui::{buffer::Buffer, layout::{Constraint, Layout, Rect}, widgets::Widget}; +use yazi_plugin::{bindings::{Cast, MouseEvent}, LUA}; use super::{completion, input, select, tasks, which}; use crate::{components, help, Ctx}; @@ -47,3 +50,17 @@ impl<'a> Widget for Root<'a> { } } } + +impl Root<'_> { + pub(super) fn mouse(event: crossterm::event::MouseEvent) -> mlua::Result<()> { + let evt = MouseEvent::cast(&LUA, event)?; + let comp: Table = LUA.globals().raw_get("Root")?; + + match event.kind { + MouseEventKind::Moved => comp.call_method("move", evt)?, + MouseEventKind::Drag(_) => comp.call_method("drag", evt)?, + _ => (), + } + Ok(()) + } +} diff --git a/yazi-fm/src/signals.rs b/yazi-fm/src/signals.rs index 8d1e7193..1798909e 100644 --- a/yazi-fm/src/signals.rs +++ b/yazi-fm/src/signals.rs @@ -3,6 +3,7 @@ use crossterm::event::{Event as CrosstermEvent, EventStream, KeyEvent, KeyEventK use futures::StreamExt; use tokio::{select, task::JoinHandle}; use tokio_util::sync::CancellationToken; +use yazi_config::MANAGER; use yazi_shared::event::Event; pub(super) struct Signals { @@ -71,6 +72,11 @@ impl Signals { Some(Ok(event)) = reader.next() => { match event { CrosstermEvent::Key(key @ KeyEvent { kind: KeyEventKind::Press, .. }) => Event::Key(key).emit(), + CrosstermEvent::Mouse(mouse) => { + if MANAGER.mouse_events.contains(mouse.kind.into()) { + Event::Mouse(mouse).emit(); + } + }, CrosstermEvent::Paste(str) => Event::Paste(str).emit(), CrosstermEvent::Resize(..) => Event::Resize.emit(), _ => {}, diff --git a/yazi-plugin/preset/components/current.lua b/yazi-plugin/preset/components/current.lua index 5916ef1b..fa57e79e 100644 --- a/yazi-plugin/preset/components/current.lua +++ b/yazi-plugin/preset/components/current.lua @@ -42,3 +42,18 @@ function Current:render(area) Folder:markers(area, markers), } end + +function Current:click(event, up) + if up or not event.is_left then + return + end + + local f = Folder:by_kind(Folder.CURRENT) + if event.y <= #f.window and f.hovered then + ya.manager_emit("arrow", { event.y + f.offset - f.hovered.idx }) + end +end + +function Current:scroll(event, step) ya.manager_emit("arrow", { step }) end + +function Current:touch(event, step) end diff --git a/yazi-plugin/preset/components/folder.lua b/yazi-plugin/preset/components/folder.lua index aaaa7df5..05b73860 100644 --- a/yazi-plugin/preset/components/folder.lua +++ b/yazi-plugin/preset/components/folder.lua @@ -82,3 +82,8 @@ function Folder:by_kind(kind) return cx.active.preview.folder end end + +function Folder:window(kind) + local folder = self:by_kind(kind) + return folder and folder.window +end diff --git a/yazi-plugin/preset/components/header.lua b/yazi-plugin/preset/components/header.lua index b22eed20..2443a065 100644 --- a/yazi-plugin/preset/components/header.lua +++ b/yazi-plugin/preset/components/header.lua @@ -66,3 +66,9 @@ function Header:render(area) ui.Paragraph(area, { right }):align(ui.Paragraph.RIGHT), } end + +function Header:click(event, up) end + +function Header:scroll(event, step) end + +function Header:touch(event, step) end diff --git a/yazi-plugin/preset/components/parent.lua b/yazi-plugin/preset/components/parent.lua index 8d76f77f..a60ef3e8 100644 --- a/yazi-plugin/preset/components/parent.lua +++ b/yazi-plugin/preset/components/parent.lua @@ -26,3 +26,20 @@ function Parent:render(area) Folder:markers(area, markers), } end + +function Parent:click(event, up) + if up or not event.is_left then + return + end + + local window = Folder:window(Folder.PARENT) or {} + if window[event.y] then + ya.manager_emit("reveal", { window[event.y].url }) + else + ya.manager_emit("leave", {}) + end +end + +function Parent:scroll(event, step) end + +function Parent:touch(event, step) end diff --git a/yazi-plugin/preset/components/preview.lua b/yazi-plugin/preset/components/preview.lua index dd6b73cc..4bf5866d 100644 --- a/yazi-plugin/preset/components/preview.lua +++ b/yazi-plugin/preset/components/preview.lua @@ -6,3 +6,20 @@ function Preview:render(area) self.area = area return {} end + +function Preview:click(event, up) + if up or not event.is_left then + return + end + + local window = Folder:window(Folder.PREVIEW) or {} + if window[event.y] then + ya.manager_emit("reveal", { window[event.y].url }) + else + ya.manager_emit("enter", {}) + end +end + +function Preview:scroll(event, step) ya.manager_emit("seek", { step }) end + +function Preview:touch(event, step) end diff --git a/yazi-plugin/preset/components/root.lua b/yazi-plugin/preset/components/root.lua new file mode 100644 index 00000000..cccc315e --- /dev/null +++ b/yazi-plugin/preset/components/root.lua @@ -0,0 +1,7 @@ +Root = { + drag_start = ui.Rect.default, +} + +function Root:move(event) end + +function Root:drag(event) end diff --git a/yazi-plugin/preset/components/status.lua b/yazi-plugin/preset/components/status.lua index de3d4014..571b9469 100644 --- a/yazi-plugin/preset/components/status.lua +++ b/yazi-plugin/preset/components/status.lua @@ -121,3 +121,9 @@ function Status:render(area) table.unpack(Progress:render(area, right:width())), } end + +function Status:click(event, up) end + +function Status:scroll(event, step) end + +function Status:touch(event, step) end diff --git a/yazi-plugin/src/bindings/mod.rs b/yazi-plugin/src/bindings/mod.rs index 31f2ac4c..6a7f716e 100644 --- a/yazi-plugin/src/bindings/mod.rs +++ b/yazi-plugin/src/bindings/mod.rs @@ -5,6 +5,7 @@ mod cha; mod file; mod icon; mod input; +mod mouse; mod permit; mod position; mod range; @@ -15,6 +16,7 @@ pub use cha::*; pub use file::*; pub use icon::*; pub use input::*; +pub use mouse::*; pub use permit::*; pub use position::*; pub use range::*; diff --git a/yazi-plugin/src/bindings/mouse.rs b/yazi-plugin/src/bindings/mouse.rs new file mode 100644 index 00000000..a3e7727f --- /dev/null +++ b/yazi-plugin/src/bindings/mouse.rs @@ -0,0 +1,35 @@ +use crossterm::event::MouseButton; +use mlua::{AnyUserData, Lua, UserDataFields}; + +use super::Cast; + +pub struct MouseEvent; + +impl MouseEvent { + pub fn register(lua: &Lua) -> mlua::Result<()> { + lua.register_userdata_type::(|reg| { + reg.add_field_method_get("x", |_, me| Ok(me.column as u32 + 1)); + reg.add_field_method_get("y", |_, me| Ok(me.row as u32 + 1)); + reg.add_field_method_get("is_left", |_, me| { + use crossterm::event::MouseEventKind as K; + Ok(matches!(me.kind, K::Down(b) | K::Up(b) | K::Drag(b) if b == MouseButton::Left)) + }); + reg.add_field_method_get("is_right", |_, me| { + use crossterm::event::MouseEventKind as K; + Ok(matches!(me.kind, K::Down(b) | K::Up(b) | K::Drag(b) if b == MouseButton::Right)) + }); + reg.add_field_method_get("is_middle", |_, me| { + use crossterm::event::MouseEventKind as K; + Ok(matches!(me.kind, K::Down(b) | K::Up(b) | K::Drag(b) if b == MouseButton::Middle)) + }); + })?; + + Ok(()) + } +} + +impl Cast for MouseEvent { + fn cast(lua: &Lua, data: crossterm::event::MouseEvent) -> mlua::Result { + lua.create_any_userdata(data) + } +} diff --git a/yazi-plugin/src/lua.rs b/yazi-plugin/src/lua.rs index a320c7bd..1b3c5a15 100644 --- a/yazi-plugin/src/lua.rs +++ b/yazi-plugin/src/lua.rs @@ -24,6 +24,7 @@ fn stage_1(lua: &'static Lua) -> Result<()> { crate::bindings::Cha::register(lua)?; crate::bindings::File::register(lua)?; crate::bindings::Icon::register(lua)?; + crate::bindings::MouseEvent::register(lua)?; crate::elements::pour(lua)?; crate::loader::install(lua)?; crate::pubsub::install(lua)?; @@ -38,6 +39,7 @@ fn stage_1(lua: &'static Lua) -> Result<()> { lua.load(include_str!("../preset/components/parent.lua")).exec()?; lua.load(include_str!("../preset/components/preview.lua")).exec()?; lua.load(include_str!("../preset/components/progress.lua")).exec()?; + lua.load(include_str!("../preset/components/root.lua")).exec()?; lua.load(include_str!("../preset/components/status.lua")).exec()?; Ok(()) diff --git a/yazi-shared/src/event/event.rs b/yazi-shared/src/event/event.rs index 6acb504d..bee1f911 100644 --- a/yazi-shared/src/event/event.rs +++ b/yazi-shared/src/event/event.rs @@ -1,6 +1,6 @@ use std::{collections::VecDeque, ffi::OsString}; -use crossterm::event::KeyEvent; +use crossterm::event::{KeyEvent, MouseEvent}; use tokio::sync::mpsc; use super::Cmd; @@ -15,6 +15,7 @@ pub enum Event { Seq(VecDeque, Layer), Render, Key(KeyEvent), + Mouse(MouseEvent), Resize, Paste(String), Quit(EventQuit), diff --git a/yazi-shared/src/term/term.rs b/yazi-shared/src/term/term.rs index 3be23370..cc4b90ff 100644 --- a/yazi-shared/src/term/term.rs +++ b/yazi-shared/src/term/term.rs @@ -1,7 +1,7 @@ use std::{io::{self, stderr, BufWriter, Stderr, Write}, mem, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, Ordering}}; use anyhow::Result; -use crossterm::{cursor::{RestorePosition, SavePosition}, event::{DisableBracketedPaste, DisableFocusChange, EnableBracketedPaste, EnableFocusChange, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, style::Print, terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen, SetTitle, WindowSize}}; +use crossterm::{cursor::{RestorePosition, SavePosition}, event::{DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, style::Print, terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen, SetTitle, WindowSize}}; use ratatui::{backend::CrosstermBackend, buffer::Buffer, layout::Rect, CompletedFrame, Frame, Terminal}; static CSI_U: AtomicBool = AtomicBool::new(false); @@ -25,7 +25,7 @@ impl Term { BufWriter::new(stderr()), EnterAlternateScreen, EnableBracketedPaste, - EnableFocusChange, + EnableMouseCapture, SavePosition, Print("\x1b[?u\x1b[c"), RestorePosition @@ -56,7 +56,7 @@ impl Term { execute!( stderr(), - DisableFocusChange, + DisableMouseCapture, DisableBracketedPaste, LeaveAlternateScreen, crossterm::cursor::SetCursorStyle::DefaultUserShape @@ -74,7 +74,7 @@ impl Term { execute!( stderr(), SetTitle(""), - DisableFocusChange, + DisableMouseCapture, DisableBracketedPaste, LeaveAlternateScreen, crossterm::cursor::SetCursorStyle::DefaultUserShape,