nlve/tests/zoomanchor.py
4aiman d0ec869743 Масштаб таймлайна берётся от точки под мышкой
Ctrl с колесом должен оставлять на месте то, на что смотрит мышь, а
оставлял точку левее — ровно на левый край таймлайна. При мыши у левого
края опора и вовсе уходила за пределы окна.

Причина: MousePos у DoMouseWheel по описанию экранный, но под gtk2 в нём
уже клиентские координаты, и ScreenToClient переводил их второй раз.
Берём Mouse.CursorPos — он экранный в любой системе.

Замерено на невидимом экране: до правки мышь в 700, 1000 и 1300 давала
опору в 260, 560 и 860; после — ровно 700, 1000 и 1300. Проверка
оформлена в tests/zoomanchor.py и прогнана на обеих сборках: на прежней
она падает, на новой проходит.
2026-09-30 20:45:32 +03:00

107 lines
4 KiB
Python
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Проверка того, что масштаб таймлайна берётся от точки под мышкой.
Ctrl с колесом меняет масштаб. Точка монтажа, на которую смотрит мышь,
обязана остаться на месте: иначе прицелиться масштабом нельзя — картинка
уезжает из-под курсора.
Как меряется. Курсор воспроизведения ставится в известное место и служит
меткой: он привязан ко времени, а не к пикселям, и при смене масштаба
едет вместе с монтажом. Зная, что один щелчок колеса меняет масштаб ровно
в 1.25 раза, неподвижную точку считаем по двум положениям метки:
p1 - a = k * (p0 - a) => a = (k * p0 - p1) / (k - 1)
Ошибку это не прячет: если опора уедет, a разойдётся с положением мыши.
Запуск (редактор уже должен идти на невидимом экране :99):
tests/zoomanchor.py <окно>
где окно — из `DISPLAY=:99 xdotool search --name nlve`.
"""
import os
import subprocess
import sys
import time
from PIL import Image
DISP = os.environ.get('ЭКРАН', ':99')
ENV = {'DISPLAY': DISP, 'PATH': '/usr/bin:/bin'}
# Один щелчок колеса. Должен совпадать с ZoomStep в uTimelineCtrl.
STEP = 1.25
# Полоса окна, по которой всё и меряется: линейка и первая дорожка.
BAND = '1500x120+0+450'
SHOT = os.path.join(os.path.dirname(os.path.abspath(__file__)),
'кадры-зума.png')
def run(*args):
subprocess.run(list(args), env=ENV, check=True)
def playhead(win):
"""Где сейчас курсор воспроизведения. Он единственный красный."""
run('import', '-window', win, '-crop', BAND, '+repage', SHOT)
im = Image.open(SHOT).convert('RGB')
px = im.load()
w, h = im.size
best = (0, None)
for x in range(10, w - 10):
red = sum(1 for y in range(h)
if px[x, y][0] > 120
and px[x, y][0] - px[x, y][1] > 40
and px[x, y][0] - px[x, y][2] > 40)
if red > best[0]:
best = (red, x)
return best[1] if best[0] > 60 else None
def measure(win, mouse_x):
before = playhead(win)
run('xdotool', 'mousemove', str(mouse_x), '520')
time.sleep(0.4)
run('xdotool', 'keydown', 'ctrl', 'click', '4', 'keyup', 'ctrl')
time.sleep(0.9)
after = playhead(win)
run('xdotool', 'keydown', 'ctrl', 'click', '5', 'keyup', 'ctrl')
time.sleep(0.9)
if before is None or after is None:
return None
return (STEP * before - after) / (STEP - 1)
def main():
if len(sys.argv) < 2:
print(__doc__)
return 2
win = sys.argv[1]
# Метку ставим щелчком по пустому месту под дорожками.
run('xdotool', 'mousemove', '900', '600', 'click', '1')
time.sleep(0.8)
bad = 0
for mouse_x in (700, 1000, 1300):
anchor = measure(win, mouse_x)
if anchor is None:
print('мышь %4d: метку не видно, замер не вышел' % mouse_x)
bad += 1
continue
off = anchor - mouse_x
ok = abs(off) <= 2
print('мышь %4d: опора %6.0f, промах %+.0f точек — %s'
% (mouse_x, anchor, off, 'ok' if ok else 'ПЛОХО'))
if not ok:
bad += 1
print()
if bad:
print('ВЕРДИКТ: масштаб берётся не от мыши, случаев: %d' % bad)
return 1
print('ВЕРДИКТ: точка под мышкой остаётся на месте')
return 0
if __name__ == '__main__':
sys.exit(main())