Ручку затухания можно взять и во второй раз

Зона захвата совпадала с нарисованным квадратиком — девять точек на
девять. Пока затухание нулевое, ручка стоит в углу клипа и целиться есть
куда; стоит её сдвинуть, она висит посреди клипа сама по себе, и попасть
в неё почти нельзя.

Зона захвата теперь шире нарисованного: по шесть точек в стороны и семь
вниз, внутрь клипа, — рука уводит курсор туда, а не выше клипа. Если на
узком клипе зоны обеих ручек налезли друг на друга, берётся та, к которой
ближе.

Заодно над зоной меняется курсор. Раньше над ручкой он был такой же, как
над всем клипом, и где она — узнать было нельзя.

Проверено протяжкой мыши на невидимом экране (tests/fadegrab.sh): первый
захват за угол даёт затухание 1.387 с, второй — с прицелом на пять точек
ниже и четыре в стороне от середины ручки — доводит до 2.261 с. На сборке
из прошлого коммита второй захват не делает ничего.
This commit is contained in:
4aiman 2026-10-01 16:13:37 +03:00
commit e8cb3cc392
3 changed files with 141 additions and 17 deletions

View file

@ -224,6 +224,13 @@ Ctrl+Shift+R, который схлопывает дыру под курсоро
двойной. Ручка стоит посередине именно видимой части, а не всего клипа:
до середины часового клипа, уехавшей за край экрана, мышью не дотянуться.
**Затухания тянутся за ручки в верхних углах клипа.** Взять их можно и
промахнувшись: зона захвата шире нарисованного квадратика и уходит внутрь
клипа. Пока затухание нулевое, ручка стоит в самом углу и целиться есть
куда; стоит её сдвинуть — она висит посреди клипа сама по себе, и попасть
в девять точек почти нельзя. Над зоной захвата меняется курсор — по нему
и видно, где взять (`tests/fadegrab.sh`).
Громкость, отличная от обычной, — единственное место, где звук
перекодируется: изменить громкость, не тронув сам звук, нельзя. Видео при
этом по-прежнему копируется. Проверено: клип с громкостью 30% дал в ролике

View file

@ -211,6 +211,14 @@ type
procedure DrawFades(ACanvas: TCanvas; AClip: TClip; const ABody: TRect);
{ Ручка затухания. AIn = True — появление (левый угол), иначе уход. }
function FadeHandleRect(AClip: TClip; ATop: Integer; AIn: Boolean): TRect;
{ Зона, в которой ручку затухания можно взять. Она шире нарисованной
ручки: пока затухание нулевое, ручка стоит в углу клипа и целиться
есть куда, а после протяжки она висит посреди клипа сама по себе, и
попасть в девять точек почти нельзя. }
function FadeGrabRect(AClip: TClip; ATop: Integer; AIn: Boolean): TRect;
{ Какую из двух ручек затухания берут в этой точке. Пусто — ни одну. }
function FadeAtPoint(AClip: TClip; const AX, AY, ATop: Integer;
out AIn: Boolean): Boolean;
{ Полоса перекрытия двух клипов одной дорожки: там сам собой выходит
переход, и это надо видеть. }
procedure DrawOverlaps(ACanvas: TCanvas; ATrack: TTrack; ATop: Integer);
@ -1192,6 +1200,52 @@ begin
Exit(Rect(0, 0, 0, 0));
end;
function TTimelineControl.FadeGrabRect(AClip: TClip; ATop: Integer;
AIn: Boolean): TRect;
const
{ По сторонам — чтобы не целиться по горизонтали в девять точек. Вниз —
потому что ручка сидит на верхней грани клипа, и рука уводит курсор
внутрь клипа, а не выше него. }
PadX = 6;
PadDown = 7;
begin
Result := FadeHandleRect(AClip, ATop, AIn);
if Result.Right <= Result.Left then
Exit;
Dec(Result.Left, PadX);
Inc(Result.Right, PadX);
Inc(Result.Bottom, PadDown);
if Result.Left < FHeaderWidth then
Result.Left := FHeaderWidth;
end;
function TTimelineControl.FadeAtPoint(AClip: TClip; const AX, AY,
ATop: Integer; out AIn: Boolean): Boolean;
var
RIn, ROut: TRect;
HitIn, HitOut: Boolean;
begin
AIn := False;
RIn := FadeGrabRect(AClip, ATop, True);
ROut := FadeGrabRect(AClip, ATop, False);
HitIn := (RIn.Right > RIn.Left) and PtInRect(RIn, Point(AX, AY));
HitOut := (ROut.Right > ROut.Left) and PtInRect(ROut, Point(AX, AY));
{ Зоны расширены, и на коротком клипе они могут налезть друг на друга.
Тогда берём ту ручку, к которой ближе: другого разумного ответа нет. }
if HitIn and HitOut then
HitOut := Abs(AX - (ROut.Left + ROut.Right) div 2) <
Abs(AX - (RIn.Left + RIn.Right) div 2);
if HitOut then
begin
AIn := False;
Exit(True);
end;
Result := HitIn;
AIn := HitIn;
end;
procedure TTimelineControl.DrawFades(ACanvas: TCanvas; AClip: TClip;
const ABody: TRect);
var
@ -2028,7 +2082,7 @@ var
Part: THeaderPart;
VolHandle: TRect;
CropRect: TRect;
FadeHandle: TRect;
FadeIsIn: Boolean;
begin
inherited MouseDown(Button, Shift, X, Y);
SetFocus;
@ -2204,23 +2258,12 @@ begin
{ Ручки затухания стоят в верхних углах, а там же начинается зона
растягивания за край. Приоритет у ручек: они мелкие, промахнуться
мимо них легче, а растянуть клип можно, взявшись за край ниже. }
FadeHandle := FadeHandleRect(Clip, TrackTop(TrackIndexAt(Y)), True);
if (FadeHandle.Right > FadeHandle.Left) and
PtInRect(FadeHandle, Point(X, Y)) then
if FadeAtPoint(Clip, X, Y, TrackTop(TrackIndexAt(Y)), FadeIsIn) then
begin
FAction := taFadeIn;
FProject.BeginChange;
FPressX := X;
FPressY := Y;
FMovedWhileDown := False;
Invalidate;
Exit;
end;
FadeHandle := FadeHandleRect(Clip, TrackTop(TrackIndexAt(Y)), False);
if (FadeHandle.Right > FadeHandle.Left) and
PtInRect(FadeHandle, Point(X, Y)) then
begin
FAction := taFadeOut;
if FadeIsIn then
FAction := taFadeIn
else
FAction := taFadeOut;
FProject.BeginChange;
FPressX := X;
FPressY := Y;
@ -2278,6 +2321,7 @@ var
Clip: TClip;
Track, TargetTrack: TTrack;
VolHandle: TRect;
FadeIsIn: Boolean;
begin
inherited MouseMove(Shift, X, Y);
VolHandle := Rect(0, 0, 0, 0);
@ -2307,6 +2351,12 @@ begin
(X >= VolHandle.Left) and (X <= VolHandle.Right) and
(Y >= VolHandle.Top - 3) and (Y <= VolHandle.Bottom + 3) then
Cursor := crSizeNS
{ Ручка затухания мелкая и после протяжки висит посреди клипа, где
её ничем не выдать. Пусть выдаёт курсор: он меняется над зоной
захвата, и становится видно, где взять. }
else if (Clip <> nil) and (Idx >= 0) and
FadeAtPoint(Clip, X, Y, TrackTop(Idx), FadeIsIn) then
Cursor := crSizeWE
else if Edge <> 0 then
Cursor := crSizeWE
else if Clip <> nil then

67
tests/fadegrab.sh Executable file
View file

@ -0,0 +1,67 @@
#!/bin/sh
# Повторный захват рампы затухания.
#
# Первый раз ручка стоит в правом верхнем углу клипа — целиться есть куда.
# Второй раз она висит посреди клипа сама по себе, и целятся в неё на
# глаз: чуть ниже и чуть в стороне от середины. Оба захвата обязаны
# сработать.
# Запуск (редактор должен собираться в этом дереве, экран :99 поднят):
# tests/fadegrab.sh [<путь к nlve>]
BIN=${1:-$PWD/nlve}
cd "$(dirname "$0")/.."
BIN=${1:-$PWD/nlve}
PROJ=$PWD/test/поток/рампа.nlve
# Монтаж для проверки: два клипа без затуханий. В репозиторий не входит —
# ссылается на съёмку, которой здесь нет.
[ -f test/поток/пара.nlve ] || { echo "нет монтажа test/поток/пара.nlve"; exit 2; }
cp test/поток/пара.nlve "$PROJ"
for p in $(pgrep -x nlve); do
tr '\0' '\n' < /proc/$p/environ 2>/dev/null | grep -qx "DISPLAY=:99" && kill $p
done
sleep 1
rm -rf test/поток/профиль-рампа; mkdir -p test/поток/профиль-рампа
rm -f test/поток/*.autosave
SP=$PWD/test/поток/профиль-рампа
XDG_CONFIG_HOME=$SP XDG_CACHE_HOME=$SP XDG_DATA_HOME=$SP DISPLAY=:99 \
setsid "$BIN" "$PROJ" >/dev/null 2>&1 < /dev/null &
sleep 14
X="timeout 8 env DISPLAY=:99 xdotool"
$X windowmove 2097206 0 0 windowsize 2097206 1500 860 2>/dev/null
sleep 2
fade() {
$X key ctrl+s
sleep 2
python3 -c "
import json
d=json.load(open('$PROJ'))
for t in d['tracks']:
for c in t['clips']:
if c['kind']=='video' and c['tl_start']<0.001:
print('%.3f' % c.get('fade_out',0)); raise SystemExit
print('нет клипа')
"
}
echo " затухание до правок: $(fade) с"
$X mousemove 783 490 mousedown 1; sleep 0.3
$X mousemove 745 490; sleep 0.2; $X mousemove 720 490; sleep 0.2
$X mouseup 1; sleep 1.5
A=$(fade)
echo " после первого захвата (ручка в углу): $A с"
$X mousemove 724 495 mousedown 1; sleep 0.3
$X mousemove 700 495; sleep 0.2; $X mousemove 680 495; sleep 0.2
$X mouseup 1; sleep 1.5
B=$(fade)
echo " после второго захвата (ручка посреди клипа, мимо центра): $B с"
python3 -c "
a, b = $A, $B
if a <= 0.01:
print(' ПЛОХО: не сработал даже первый захват')
elif b <= a + 0.01:
print(' ПЛОХО: второй захват не сработал')
else:
print(' ok: обе протяжки сработали')
"