diff --git a/DOCS/man/mpv.rst b/DOCS/man/mpv.rst index 93ba8efe99..088eba6170 100644 --- a/DOCS/man/mpv.rst +++ b/DOCS/man/mpv.rst @@ -1532,6 +1532,8 @@ works like in older mpv releases: .. include:: select.rst +.. include:: positioning.rst + .. include:: lua.rst .. include:: javascript.rst diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 5c9efe8184..29062e09f0 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -1067,6 +1067,10 @@ Program Behavior Enable the builtin script that lets you select from lists of items (default: yes). By default, its keybindings start with the ``g`` key. +``--load-positioning=`` + Enable the builtin script that provides various keybindings to pan videos + and images (default: yes). + ``--player-operation-mode=`` For enabling "pseudo GUI mode", which means that the defaults for some options are changed. This option should not normally be used directly, but diff --git a/DOCS/man/positioning.rst b/DOCS/man/positioning.rst new file mode 100644 index 0000000000..f18155b572 --- /dev/null +++ b/DOCS/man/positioning.rst @@ -0,0 +1,60 @@ +POSITIONING +=========== + +This script provides script bindings to pan videos and images. It can be +disabled using the ``--load-positioning=no`` option. + +Script bindings +--------------- + +``pan-x `` + Adjust ``--video-align-x`` relatively to the OSD width, rather than + relatively to the video width like the option. This is useful to pan large + images consistently. + + ``amount`` is a number such that an amount of 1 scrolls as much as the OSD + width. + +``pan-y `` + Adjust ``--video-align-y`` relatively to the OSD height, rather than + relatively to the video height like the option. + + ``amount`` is a number such that an amount of 1 scrolls as much as the OSD + height. + +``drag-to-pan`` + Pan the video while holding a mouse button, keeping the clicked part of the + video under the cursor. + +``align-to-cursor`` + Pan through the whole video while holding a mouse button, or after clicking + once if ``toggle_align_to_cursor`` is ``yes``. + +``cursor-centric-zoom `` + Increase ``--video-zoom`` by ``amount`` while keeping the part of the video + hovered by the cursor under it, or the average position of touch points if + known. + +Configuration +------------- + +This script can be customized through a config file +``script-opts/positioning.conf`` placed in mpv's user directory and through the +``--script-opts`` command-line option. The configuration syntax is described in +`mp.options functions`_. + +Configurable Options +~~~~~~~~~~~~~~~~~~~~ + +``toggle_align_to_cursor`` + Default: no + + Whether ``align-to-cursor`` requires holding down a mouse button to pan. If + ``no``, dragging pans. If ``yes``, clicking the first time makes pan follow + the cursor, and clicking a second time disables this. + +``suppress_osd`` + Default: no + + Whether to not print the new value of ``--video-zoom`` when using + ``cursor-centric-zoom``. diff --git a/options/options.c b/options/options.c index 80996293cd..848d4ae2f9 100644 --- a/options/options.c +++ b/options/options.c @@ -554,6 +554,7 @@ static const m_option_t mp_opts[] = { OPT_CHOICE(lua_load_auto_profiles, {"no", 0}, {"yes", 1}, {"auto", -1}), .flags = UPDATE_BUILTIN_SCRIPTS}, {"load-select", OPT_BOOL(lua_load_select), .flags = UPDATE_BUILTIN_SCRIPTS}, + {"load-positioning", OPT_BOOL(lua_load_positioning), .flags = UPDATE_BUILTIN_SCRIPTS}, #endif // ------------------------- stream options -------------------- @@ -986,6 +987,7 @@ static const struct MPOpts mp_default_opts = { .lua_load_console = true, .lua_load_auto_profiles = -1, .lua_load_select = true, + .lua_load_positioning = true, #endif .auto_load_scripts = true, .loop_times = 1, diff --git a/options/options.h b/options/options.h index 8504845e1f..bf8dd4be30 100644 --- a/options/options.h +++ b/options/options.h @@ -174,6 +174,7 @@ typedef struct MPOpts { bool lua_load_console; int lua_load_auto_profiles; bool lua_load_select; + bool lua_load_positioning; bool auto_load_scripts; diff --git a/player/core.h b/player/core.h index c77b14e51b..f0f2c75ce8 100644 --- a/player/core.h +++ b/player/core.h @@ -444,7 +444,7 @@ typedef struct MPContext { struct mp_ipc_ctx *ipc_ctx; - int64_t builtin_script_ids[6]; + int64_t builtin_script_ids[7]; mp_mutex abort_lock; diff --git a/player/lua.c b/player/lua.c index c8dd5b007f..8d88be5c82 100644 --- a/player/lua.c +++ b/player/lua.c @@ -84,6 +84,9 @@ static const char * const builtin_lua_scripts[][2] = { }, {"@select.lua", # include "player/lua/select.lua.inc" + }, + {"@positioning.lua", +# include "player/lua/positioning.lua.inc" }, {0} }; diff --git a/player/lua/meson.build b/player/lua/meson.build index ac4c54b4dd..a8b8704957 100644 --- a/player/lua/meson.build +++ b/player/lua/meson.build @@ -1,6 +1,6 @@ lua_files = ['defaults.lua', 'assdraw.lua', 'options.lua', 'osc.lua', 'ytdl_hook.lua', 'stats.lua', 'console.lua', 'auto_profiles.lua', - 'input.lua', 'fzy.lua', 'select.lua'] + 'input.lua', 'fzy.lua', 'select.lua', 'positioning.lua'] foreach file: lua_files lua_file = custom_target(file, input: file, diff --git a/player/lua/positioning.lua b/player/lua/positioning.lua new file mode 100644 index 0000000000..679bfe52b9 --- /dev/null +++ b/player/lua/positioning.lua @@ -0,0 +1,155 @@ +--[[ +This file is part of mpv. + +mpv is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +mpv is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with mpv. If not, see . +]] + +local options = { + toggle_align_to_cursor = false, + suppress_osd = false, +} + +require "mp.options".read_options(options, nil, function () end) + +local function clamp(value, min, max) + return math.min(math.max(value, min), max) +end + +mp.add_key_binding(nil, "pan-x", function (t) + if t.event == "up" then + return + end + + local dims = mp.get_property_native("osd-dimensions") + local width = dims.w - dims.ml - dims.mr + -- 1 video-align shifts the OSD by (width - osd-width) / 2 pixels, so the + -- equation to find how much video-align to add to offset the OSD by + -- osd-width is: + -- x/1 = osd-width / ((width - osd-width) / 2) + local amount = t.arg * t.scale * 2 * dims.w / (width - dims.w) + mp.commandv("add", "video-align-x", clamp(amount, -2, 2)) +end, { complex = true, scalable = true }) + +mp.add_key_binding(nil, "pan-y", function (t) + if t.event == "up" then + return + end + + local dims = mp.get_property_native("osd-dimensions") + local height = dims.h - dims.mt - dims.mb + local amount = t.arg * t.scale * 2 * dims.h / (height - dims.h) + mp.commandv("add", "video-align-y", clamp(amount, -2, 2)) +end, { complex = true, scalable = true }) + +mp.add_key_binding(nil, "drag-to-pan", function (t) + if t.event == "up" then + mp.remove_key_binding("drag-to-pan-mouse-move") + return + end + + local dims = mp.get_property_native("osd-dimensions") + local old_mouse_pos = mp.get_property_native("mouse-pos") + local old_align_x = mp.get_property_native("video-align-x") + local old_align_y = mp.get_property_native("video-align-y") + + mp.add_forced_key_binding("MOUSE_MOVE", "drag-to-pan-mouse-move", function () + local mouse_pos = mp.get_property_native("mouse-pos") + -- 1 video-align shifts the OSD by (dimension - osd_dimension) / 2 pixels, + -- so the equation to find how much video-align to add to offset the OSD + -- by the difference in mouse position is: + -- x/1 = (mouse_pos - old_mouse_pos) / ((dimension - osd_dimension) / 2) + local align = old_align_x + 2 * (mouse_pos.x - old_mouse_pos.x) + / (dims.ml + dims.mr) + mp.set_property("video-align-x", clamp(align, -1, 1)) + align = old_align_y + 2 * (mouse_pos.y - old_mouse_pos.y) + / (dims.mt + dims.mb) + mp.set_property("video-align-y", clamp(align, -1, 1)) + end) +end, { complex = true }) + + +local align_to_cursor_bound = false + +local function align_to_cursor(_, mouse_pos) + local dims = mp.get_property_native("osd-dimensions") + mp.set_property("video-align-x", (mouse_pos.x * 2 - dims.w) / dims.w) + mp.set_property("video-align-y", (mouse_pos.y * 2 - dims.h) / dims.h) +end + +mp.add_key_binding(nil, "align-to-cursor", function (t) + if options.toggle_align_to_cursor == false then + if t.event == "down" then + mp.observe_property("mouse-pos", "native", align_to_cursor) + else + mp.unobserve_property(align_to_cursor) + end + + return + end + + if t.event ~= "up" then + return + end + + if align_to_cursor_bound then + mp.unobserve_property(align_to_cursor) + else + mp.observe_property("mouse-pos", "native", align_to_cursor) + end + align_to_cursor_bound = not align_to_cursor_bound +end, { complex = true }) + + +mp.add_key_binding(nil, "cursor-centric-zoom", function (t) + local amount = t.arg * t.scale + + local command = (options.suppress_osd and "no-osd " or "") .. + "add video-zoom " .. amount .. ";" + + local x, y + local touch_positions = mp.get_property_native("touch-pos") + if touch_positions[1] then + for _, position in pairs(touch_positions) do + x = x + position.x + y = y + position.y + end + x = x / #touch_positions + y = y / #touch_positions + else + local mouse_pos = mp.get_property_native("mouse-pos") + x = mouse_pos.x + y = mouse_pos.y + end + + local dims = mp.get_property_native("osd-dimensions") + local width = (dims.w - dims.ml - dims.mr) * 2^amount + local height = (dims.h - dims.mt - dims.mb) * 2^amount + + local old_cursor_ml = dims.ml - x + local cursor_ml = old_cursor_ml * 2^amount + local ml = cursor_ml + x + -- video/out/aspect.c:src_dst_split_scaling() defines ml as: + -- ml = (osd-width - width) * (video-align-x + 1) / 2 + pan-x * width + -- So video-align-x is: + local align = 2 * (ml - mp.get_property_native("video-pan-x") * width) + / (dims.w - width) - 1 + command = command .. "no-osd set video-align-x " .. clamp(align, -1, 1) .. ";" + + local mt = (dims.mt - y) * 2^amount + y + align = 2 * (mt - mp.get_property_native("video-pan-y") * height) + / (dims.h - height) - 1 + command = command .. "no-osd set video-align-y " .. clamp(align, -1, 1) + + mp.command(command) +end, { complex = true, scalable = true }) diff --git a/player/scripting.c b/player/scripting.c index 5af9e1f359..b8325dc000 100644 --- a/player/scripting.c +++ b/player/scripting.c @@ -263,6 +263,7 @@ void mp_load_builtin_scripts(struct MPContext *mpctx) load_builtin_script(mpctx, 4, mpctx->opts->lua_load_auto_profiles, "@auto_profiles.lua"); load_builtin_script(mpctx, 5, mpctx->opts->lua_load_select, "@select.lua"); + load_builtin_script(mpctx, 6, mpctx->opts->lua_load_positioning, "@positioning.lua"); } bool mp_load_scripts(struct MPContext *mpctx)