feat: support zooming images with a default parameter
Closes https://github.com/sxyazi/yazi/issues/3000
This commit is contained in:
@@ -37,6 +37,16 @@ run = "plugin zoom -1"
|
|||||||
desc = "Zoom out hovered file"
|
desc = "Zoom out hovered file"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Advanced
|
||||||
|
|
||||||
|
If you want to apply a default zoom parameter to image previews, you can specify it while setting this plugin up as a custom previewer, for example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[plugin.prepend_previewers]]
|
||||||
|
mime = "image/{jpeg,png,webp}"
|
||||||
|
run = "zoom 5"
|
||||||
|
```
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- [ ] Support more file types (e.g., videos, PDFs), PRs welcome!
|
- [ ] Support more file types (e.g., videos, PDFs), PRs welcome!
|
||||||
|
|||||||
@@ -1,68 +1,85 @@
|
|||||||
--- @since 25.6.11
|
--- @since 25.6.11
|
||||||
|
|
||||||
local get = ya.sync(function(st)
|
local get = ya.sync(function(st, url) return st.last == url and st.level end)
|
||||||
|
|
||||||
|
local save = ya.sync(function(st, url, new)
|
||||||
|
local h = cx.active.current.hovered
|
||||||
|
if h and h.url == url then
|
||||||
|
st.last, st.level = url, new
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local lock = ya.sync(function(st, url, old, new)
|
||||||
|
if st.last == url and st.level == old then
|
||||||
|
st.level = new
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local move = ya.sync(function(st)
|
||||||
local h = cx.active.current.hovered
|
local h = cx.active.current.hovered
|
||||||
if not h then
|
if not h then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if st.last ~= h:hash() then
|
if st.last ~= h.url then
|
||||||
st.level, st.last = 0, h:hash()
|
st.last, st.level = Url(h.url), 0
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return { url = h.url, level = st.level }
|
||||||
url = h.url,
|
|
||||||
mime = h:mime() or "",
|
|
||||||
level = st.level,
|
|
||||||
}
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local save = ya.sync(function(st, before, after)
|
local function end_(job, err)
|
||||||
if st.level == before then
|
if not job.old_level then
|
||||||
st.level = after
|
ya.preview_widget(job, err and ui.Text(err):area(job.area):wrap(ui.Wrap.YES))
|
||||||
return true
|
elseif err then
|
||||||
|
ya.notify { title = "Zoom", content = tostring(err), timeout = 5, level = "error" }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end)
|
|
||||||
|
|
||||||
local function fail(...) return ya.notify { title = "Zoom", content = string.format(...), timeout = 5, level = "error" } end
|
local function canvas(area)
|
||||||
|
|
||||||
local function canvas()
|
|
||||||
local cw, ch = rt.term.cell_size()
|
local cw, ch = rt.term.cell_size()
|
||||||
if not cw then
|
if not cw then
|
||||||
return rt.preview.max_width, rt.preview.max_height
|
return rt.preview.max_width, rt.preview.max_height
|
||||||
end
|
end
|
||||||
|
|
||||||
local area = ui.area("preview")
|
|
||||||
return math.min(rt.preview.max_width, math.floor(area.w * cw)),
|
return math.min(rt.preview.max_width, math.floor(area.w * cw)),
|
||||||
math.min(rt.preview.max_height, math.floor(area.h * ch))
|
math.min(rt.preview.max_height, math.floor(area.h * ch))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function entry(_, job)
|
local function peek(_, job)
|
||||||
local st = get(job.args[1])
|
local url = job.file.url
|
||||||
if not st then
|
local info, err = ya.image_info(url)
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local info, err = ya.image_info(st.url)
|
|
||||||
if not info then
|
if not info then
|
||||||
return fail("Failed to get image info: %s", err)
|
return end_(job, Err("Failed to get image info: %s", err))
|
||||||
end
|
end
|
||||||
|
|
||||||
local motion = tonumber(job.args[1]) or 0
|
local level = ya.clamp(-10, job.new_level or get(Url(url)) or tonumber(job.args[1]) or 0, 10)
|
||||||
local level = ya.clamp(-10, st.level + motion, 10)
|
local sync = function()
|
||||||
|
if job.old_level then
|
||||||
|
return lock(url, job.old_level, level)
|
||||||
|
else
|
||||||
|
return save(url, level)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local max_w, max_h = canvas()
|
local max_w, max_h = canvas(job.area)
|
||||||
local min_w, min_h = math.min(max_w, info.w), math.min(max_h, info.h)
|
local min_w, min_h = math.min(max_w, info.w), math.min(max_h, info.h)
|
||||||
local new_w = min_w + math.floor(min_w * level * 0.1)
|
local new_w = min_w + math.floor(min_w * level * 0.1)
|
||||||
local new_h = min_h + math.floor(min_h * level * 0.1)
|
local new_h = min_h + math.floor(min_h * level * 0.1)
|
||||||
if new_w > max_w or new_h > max_h then
|
if new_w > max_w or new_h > max_h then
|
||||||
return -- Image larger than available preview area after zooming
|
if job.old_level then
|
||||||
|
return sync() -- Image larger than available preview area after zooming
|
||||||
|
else
|
||||||
|
new_w, new_h = max_w, max_h -- Run as a previewer, render the image anyway
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local tmp = os.tmpname()
|
local tmp = os.tmpname()
|
||||||
-- stylua: ignore
|
-- stylua: ignore
|
||||||
local status, err = Command("magick"):arg {
|
local status, err = Command("magick"):arg {
|
||||||
tostring(st.url),
|
tostring(url),
|
||||||
"-auto-orient", "-strip",
|
"-auto-orient", "-strip",
|
||||||
"-sample", string.format("%dx%d", new_w, new_h),
|
"-sample", string.format("%dx%d", new_w, new_h),
|
||||||
"-quality", rt.preview.image_quality,
|
"-quality", rt.preview.image_quality,
|
||||||
@@ -70,12 +87,33 @@ local function entry(_, job)
|
|||||||
}:status()
|
}:status()
|
||||||
|
|
||||||
if not status then
|
if not status then
|
||||||
fail("Failed to run `magick` command: %s", err)
|
end_(job, Err("Failed to run `magick` command: %s", err))
|
||||||
elseif not status.success then
|
elseif not status.success then
|
||||||
fail("`magick` command exited with error code %d", status.code)
|
end_(job, Err("`magick` command exited with error code %d", status.code))
|
||||||
elseif save(st.level, level) then
|
elseif sync() then
|
||||||
ya.image_show(Url(tmp), ui.area("preview"))
|
ya.image_show(Url(tmp), job.area)
|
||||||
|
end
|
||||||
|
end_(job)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function entry(self, job)
|
||||||
|
local st = move()
|
||||||
|
if not st then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local motion = tonumber(job.args[1]) or 0
|
||||||
|
local new = ya.clamp(-10, st.level + motion, 10)
|
||||||
|
if new ~= st.level then
|
||||||
|
peek(self, {
|
||||||
|
area = ui.area("preview"),
|
||||||
|
args = {},
|
||||||
|
file = { url = st.url }, -- FIXME: use `File` instead of a dummy file
|
||||||
|
skip = 0,
|
||||||
|
new_level = new,
|
||||||
|
old_level = st.level,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return { entry = entry }
|
return { peek = peek, entry = entry }
|
||||||
|
|||||||
Reference in New Issue
Block a user