Масштаб таймлайна берётся от точки под мышкой
Ctrl с колесом должен оставлять на месте то, на что смотрит мышь, а оставлял точку левее — ровно на левый край таймлайна. При мыши у левого края опора и вовсе уходила за пределы окна. Причина: MousePos у DoMouseWheel по описанию экранный, но под gtk2 в нём уже клиентские координаты, и ScreenToClient переводил их второй раз. Берём Mouse.CursorPos — он экранный в любой системе. Замерено на невидимом экране: до правки мышь в 700, 1000 и 1300 давала опору в 260, 560 и 860; после — ровно 700, 1000 и 1300. Проверка оформлена в tests/zoomanchor.py и прогнана на обеих сборках: на прежней она падает, на новой проходит.
This commit is contained in:
parent
4526f65fcc
commit
d0ec869743
4 changed files with 130 additions and 2 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -51,6 +51,9 @@ shot*.png
|
|||
/tests/lfmcheck/lfmcheck
|
||||
/tests/lfmcheck/units/
|
||||
|
||||
# Снимок, который делает проверка масштаба таймлайна.
|
||||
/tests/кадры-зума.png
|
||||
|
||||
# Проверка хода полоски: двоичный файл и промежуточные модули.
|
||||
/tests/progress/progtest
|
||||
/tests/progress/*.o
|
||||
|
|
|
|||
10
README.md
10
README.md
|
|
@ -84,7 +84,7 @@ xdg-mime query default application/x-nlve-project
|
|||
| Режим выделения: рамкой или по времени | D |
|
||||
| Схлопнуть дыру под курсором | Ctrl+Shift+R |
|
||||
| Отменить, повторить | Ctrl+Z, Ctrl+Y |
|
||||
| Масштаб таймлайна | Ctrl + колесо мыши |
|
||||
| Масштаб таймлайна, от точки под мышкой | Ctrl + колесо мыши |
|
||||
| Прокрутка по дорожкам | колесо мыши |
|
||||
| Прокрутка по времени | Shift + колесо, средняя кнопка |
|
||||
| Прокрутка журнала вбок | Shift + колесо над журналом |
|
||||
|
|
@ -829,6 +829,14 @@ Wine с новой библиотекой не грузилось ничего.
|
|||
привычный RGB. Записанный по привычке код даёт цвет с переставленными
|
||||
красным и синим.
|
||||
|
||||
**MousePos у колеса мыши — не экранный.** По описанию `DoMouseWheel`
|
||||
получает точку в координатах экрана, но под gtk2 в ней уже клиентские:
|
||||
`ScreenToClient` переводит их второй раз, и точка уезжает влево ровно на
|
||||
левый край элемента. У масштаба таймлайна это выглядело так, будто он
|
||||
берётся не от мыши: замерено на невидимом экране — мышь в 700, 1000 и
|
||||
1300, опора в 260, 560 и 860, всегда на 440 левее. Лечится обращением к
|
||||
`Mouse.CursorPos`: он экранный в любой системе, и перевод остаётся один.
|
||||
|
||||
**Имена, занятые предками.** Локальная переменная перекрывает одноимённое
|
||||
свойство класса: `Text` и `Position` заняты у `TControl`, `Finished` — у
|
||||
`TThread`, `Name` и `Caption` — у формы. Компилятор ловит это как
|
||||
|
|
|
|||
|
|
@ -2758,7 +2758,17 @@ function TTimelineControl.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
|
|||
var
|
||||
Pt: TPoint;
|
||||
begin
|
||||
Pt := ScreenToClient(MousePos);
|
||||
{ Точка берётся у самой мыши, а не из MousePos.
|
||||
|
||||
MousePos по описанию приходит в координатах экрана, но под gtk2 в нём
|
||||
уже клиентские: ScreenToClient переводил их второй раз, и точка опоры
|
||||
уезжала влево ровно на левый край таймлайна. Замерено на невидимом
|
||||
экране: мышь в 700, 1000 и 1300 — опора в 260, 560 и 860, то есть
|
||||
всегда на 440 левее, а 440 — это и есть левый край. При мыши слева
|
||||
опора и вовсе уходила за пределы окна.
|
||||
|
||||
Mouse.CursorPos экранный в любой системе, и перевод остаётся один. }
|
||||
Pt := ScreenToClient(Mouse.CursorPos);
|
||||
|
||||
if ssCtrl in Shift then
|
||||
begin
|
||||
|
|
|
|||
107
tests/zoomanchor.py
Executable file
107
tests/zoomanchor.py
Executable file
|
|
@ -0,0 +1,107 @@
|
|||
#!/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())
|
||||
Loading…
Add table
Add a link
Reference in a new issue