Выделение по времени, рез по краям промежутка, курсор идёт за резом
Режимов выделения стало два, переключаются клавишей D и кнопкой внизу, режим показан её значком. Рамкой по клипам — как было. По времени — протяжка отмечает промежуток по всем дорожкам сразу, он остаётся виден пунктиром, и S режет все дорожки по обоим его краям. Выделенной после этого остаётся ровно середина: следующим действием её обычно удаляют, а выделение на старых клипах убрало бы не то. Курсор теперь идёт за резом: после S встаёт на место реза (в режиме по времени — на начало промежутка), после удаления куска — на место стыка, после схлопывания дыры — туда же. Раньше в это место приходилось попадать мышью заново. Рез кладётся на кадровую сетку: между кадрами времени нет, и рез на доле кадра оставлял бы в монтаже время, которого не бывает. Выделение рамкой требует настоящего перекрытия, а не касания краями: сосед, кончающийся ровно на краю рамки, попадал в выделение из-за разницы в последнем знаке дробей. Кнопка журнала и строка состояния сдвинуты вправо: переключатель выделения встал в один ряд с прилипанием и схлопыванием. Значки — icons8, набор ios-glyphs, авторство дописано в attribution.txt. Набора «ios26» на их раздаче нет (404), взят ближайший по виду к нынешним. Проверки: рез по курсору и по краям промежутка, кадровая сетка, выделение середины, курсор после удаления и после схлопывания.
This commit is contained in:
parent
37787ce96c
commit
6617e29514
11 changed files with 538 additions and 31 deletions
27
README.md
27
README.md
|
|
@ -81,6 +81,7 @@ xdg-mime query default application/x-nlve-project
|
|||
| Удалить выделенное | Delete |
|
||||
| Выделить всё | Ctrl+A |
|
||||
| Выделить областью | протяжка по пустому месту дорожек |
|
||||
| Режим выделения: рамкой или по времени | D |
|
||||
| Схлопнуть дыру под курсором | Ctrl+Shift+R |
|
||||
| Отменить, повторить | Ctrl+Z, Ctrl+Y |
|
||||
| Масштаб таймлайна | Ctrl + колесо мыши |
|
||||
|
|
@ -272,6 +273,32 @@ Ctrl+Shift+R, который схлопывает дыру под курсоро
|
|||
удаляет: она лежит, пока проект не сохранят заново. После сохранения
|
||||
копия не нужна и убирается.
|
||||
|
||||
## Два режима выделения
|
||||
|
||||
Кнопка внизу (справа от схлопывания) и клавиша `D`. Режим показан
|
||||
значком: рамка — выделение клипов, промежуток со стрелкой — выделение по
|
||||
времени.
|
||||
|
||||
**Рамкой по клипам.** Протяжка по пустому месту дорожек выделяет всё, что
|
||||
рамка задела. Обычное выделение: выделил, нажал Delete.
|
||||
|
||||
**По времени.** Протяжка отмечает промежуток от и до — по всем дорожкам
|
||||
сразу, сколько бы их ни было. Промежуток виден пунктиром и после того,
|
||||
как кнопку отпустили: по нему режет `S`.
|
||||
|
||||
Зачем второй режим: вырезать из фильма кусок целиком. Отметил промежуток,
|
||||
нажал `S` — все дорожки разрезаны в двух местах, по краям промежутка, и
|
||||
выделенной осталась ровно середина. Дальше Delete, и при включённом
|
||||
автосхлопывании дыра закрывается сама.
|
||||
|
||||
Края промежутка кладутся на кадровую сетку: между кадрами времени нет.
|
||||
|
||||
**Курсор идёт за резом.** После `S` он встаёт ровно на место реза (в
|
||||
режиме по времени — на начало промежутка), после удаления куска и
|
||||
схлопывания — на место стыка. Дальше с этим местом и работают: следующий
|
||||
рез, схлопывание дыры, просмотр перехода. Раньше курсор оставался там, где
|
||||
был, и в место реза приходилось попадать мышью заново.
|
||||
|
||||
## Починка проекта
|
||||
|
||||
Меню «Проект» → **Проверить и починить проект...**. Показывает, что нашла,
|
||||
|
|
|
|||
|
|
@ -2,4 +2,6 @@ Log panel: <a target="_blank" href="https://icons8.com/icon/79872/property">Prop
|
|||
Ripple gaps: <a target="_blank" href="https://icons8.com/icon/2EQegp8tbR6Q/merge-vertical">Merge Vertical</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>
|
||||
Magnet: <a target="_blank" href="https://icons8.com/icon/79942/magnet">Magnet</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>
|
||||
Playhead snapping: <a target="_blank" href="https://icons8.com/icon/111296/glue">Клей</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>
|
||||
App icon: <a target="_blank" href="https://icons8.com/icon/69093/showing-video-frames">Showing Video Frames</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>
|
||||
App icon: <a target="_blank" href="https://icons8.com/icon/69093/showing-video-frames">Showing Video Frames</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>
|
||||
Selection mode, clips: <a target="_blank" href="https://icons8.com/icon/set/select-all/ios-glyphs">Select All</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>
|
||||
Selection mode, time range: <a target="_blank" href="https://icons8.com/icon/set/width/ios-glyphs">Width</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>
|
||||
|
|
|
|||
BIN
icons_online/icons8-select-all-90.png
Normal file
BIN
icons_online/icons8-select-all-90.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 476 B |
BIN
icons_online/icons8-width-90.png
Normal file
BIN
icons_online/icons8-width-90.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 656 B |
|
|
@ -32,8 +32,8 @@ const
|
|||
$60, $00, $00, $3A, $98, $00, $00, $17, $70, $9C, $BA, $51, $3C, $00, $00, $00,
|
||||
$02, $62, $4B, $47, $44, $00, $00, $AA, $8D, $23, $32, $00, $00, $00, $09, $70,
|
||||
$48, $59, $73, $00, $00, $0B, $13, $00, $00, $0B, $13, $01, $00, $9A, $9C, $18,
|
||||
$00, $00, $00, $07, $74, $49, $4D, $45, $07, $EA, $08, $1F, $07, $2E, $1B, $C7,
|
||||
$2C, $48, $09, $00, $00, $01, $3E, $49, $44, $41, $54, $38, $CB, $BD, $92, $31,
|
||||
$00, $00, $00, $07, $74, $49, $4D, $45, $07, $EA, $09, $1D, $14, $21, $19, $27,
|
||||
$B3, $C9, $F8, $00, $00, $01, $3E, $49, $44, $41, $54, $38, $CB, $BD, $92, $31,
|
||||
$4B, $C3, $40, $14, $C7, $7F, $97, $44, $29, $68, $03, $5A, $45, $EA, $D0, $A5,
|
||||
$6B, $41, $44, $5C, $0A, $0E, $3A, $15, $DC, $1D, $DD, $FB, $51, $74, $71, $29,
|
||||
$F8, $39, $EC, $62, $06, $17, $5D, $EA, $22, $D2, $88, $93, $E0, $22, $68, $A1,
|
||||
|
|
@ -66,8 +66,8 @@ const
|
|||
$60, $00, $00, $3A, $98, $00, $00, $17, $70, $9C, $BA, $51, $3C, $00, $00, $00,
|
||||
$02, $62, $4B, $47, $44, $00, $00, $AA, $8D, $23, $32, $00, $00, $00, $09, $70,
|
||||
$48, $59, $73, $00, $00, $0B, $13, $00, $00, $0B, $13, $01, $00, $9A, $9C, $18,
|
||||
$00, $00, $00, $07, $74, $49, $4D, $45, $07, $EA, $08, $1F, $07, $2E, $1B, $C7,
|
||||
$2C, $48, $09, $00, $00, $01, $2F, $49, $44, $41, $54, $38, $CB, $CD, $D2, $3D,
|
||||
$00, $00, $00, $07, $74, $49, $4D, $45, $07, $EA, $09, $1D, $14, $21, $19, $27,
|
||||
$B3, $C9, $F8, $00, $00, $01, $2F, $49, $44, $41, $54, $38, $CB, $CD, $D2, $3D,
|
||||
$2F, $83, $51, $18, $C6, $F1, $DF, $C3, $63, $31, $34, $11, $B5, $35, $D1, $A9,
|
||||
$C1, $E0, $25, $4C, $66, $49, $F9, $16, $24, $96, $C6, $37, $A8, $18, $7D, $02,
|
||||
$31, $98, $18, $99, $25, $44, $F8, $02, $26, $B1, $30, $30, $78, $D9, $90, $18,
|
||||
|
|
@ -99,8 +99,8 @@ const
|
|||
$60, $00, $00, $3A, $98, $00, $00, $17, $70, $9C, $BA, $51, $3C, $00, $00, $00,
|
||||
$02, $62, $4B, $47, $44, $00, $00, $AA, $8D, $23, $32, $00, $00, $00, $09, $70,
|
||||
$48, $59, $73, $00, $00, $0B, $13, $00, $00, $0B, $13, $01, $00, $9A, $9C, $18,
|
||||
$00, $00, $00, $07, $74, $49, $4D, $45, $07, $EA, $08, $1F, $07, $2E, $1B, $C7,
|
||||
$2C, $48, $09, $00, $00, $01, $0F, $49, $44, $41, $54, $38, $CB, $B5, $D3, $BF,
|
||||
$00, $00, $00, $07, $74, $49, $4D, $45, $07, $EA, $09, $1D, $14, $21, $19, $27,
|
||||
$B3, $C9, $F8, $00, $00, $01, $0F, $49, $44, $41, $54, $38, $CB, $B5, $D3, $BF,
|
||||
$2E, $83, $51, $18, $C7, $F1, $CF, $5B, $15, $0C, $74, $93, $88, $91, $0B, $E8,
|
||||
$68, $60, $33, $34, $B1, $1A, $F4, $1E, $0C, $66, $8B, $C5, $05, $B8, $01, $89,
|
||||
$2B, $30, $18, $24, $4C, $0D, $8B, $41, $13, $46, $62, $90, $18, $A4, $4B, $55,
|
||||
|
|
@ -130,8 +130,8 @@ const
|
|||
$60, $00, $00, $3A, $98, $00, $00, $17, $70, $9C, $BA, $51, $3C, $00, $00, $00,
|
||||
$02, $62, $4B, $47, $44, $00, $00, $AA, $8D, $23, $32, $00, $00, $00, $09, $70,
|
||||
$48, $59, $73, $00, $00, $0B, $13, $00, $00, $0B, $13, $01, $00, $9A, $9C, $18,
|
||||
$00, $00, $00, $07, $74, $49, $4D, $45, $07, $EA, $08, $1F, $07, $2E, $1B, $C7,
|
||||
$2C, $48, $09, $00, $00, $01, $67, $49, $44, $41, $54, $38, $CB, $B5, $D3, $3D,
|
||||
$00, $00, $00, $07, $74, $49, $4D, $45, $07, $EA, $09, $1D, $14, $21, $19, $27,
|
||||
$B3, $C9, $F8, $00, $00, $01, $67, $49, $44, $41, $54, $38, $CB, $B5, $D3, $3D,
|
||||
$4F, $93, $61, $14, $C6, $F1, $5F, $AB, $80, $9D, $A8, $A1, $71, $95, $08, $14,
|
||||
$3F, $80, $71, $D2, $8D, $C5, $D8, $A4, $A6, $61, $31, $74, $32, $31, $1A, $99,
|
||||
$89, $B3, $8B, $71, $B0, $7C, $0F, $37, $42, $4B, $07, $07, $D0, $98, $30, $E0,
|
||||
|
|
@ -167,8 +167,8 @@ const
|
|||
$60, $00, $00, $3A, $98, $00, $00, $17, $70, $9C, $BA, $51, $3C, $00, $00, $00,
|
||||
$02, $62, $4B, $47, $44, $00, $00, $AA, $8D, $23, $32, $00, $00, $00, $09, $70,
|
||||
$48, $59, $73, $00, $00, $0B, $13, $00, $00, $0B, $13, $01, $00, $9A, $9C, $18,
|
||||
$00, $00, $00, $07, $74, $49, $4D, $45, $07, $EA, $08, $1F, $07, $2E, $1B, $C7,
|
||||
$2C, $48, $09, $00, $00, $02, $71, $49, $44, $41, $54, $38, $CB, $95, $D4, $41,
|
||||
$00, $00, $00, $07, $74, $49, $4D, $45, $07, $EA, $09, $1D, $14, $21, $19, $27,
|
||||
$B3, $C9, $F8, $00, $00, $02, $71, $49, $44, $41, $54, $38, $CB, $95, $D4, $41,
|
||||
$68, $DB, $65, $18, $06, $F0, $5F, $E2, $DA, $2A, $2E, $59, $B2, $65, $29, $4B,
|
||||
$B4, $A7, $C9, $B0, $DB, $A0, $8C, $49, $63, $55, $50, $0F, $56, $31, $ED, $68,
|
||||
$BA, $CB, $70, $3B, $C8, $F0, $24, $08, $0A, $1E, $44, $99, $4E, $AA, $B2, $5D,
|
||||
|
|
@ -211,6 +211,76 @@ const
|
|||
$00, $00, $00, $00, $49, $45, $4E, $44, $AE, $42, $60, $82
|
||||
);
|
||||
|
||||
{ icons8-select-all-90.png, 24 на 24 точек }
|
||||
IconSelectClipsData: array[0..509] of Byte = (
|
||||
$89, $50, $4E, $47, $0D, $0A, $1A, $0A, $00, $00, $00, $0D, $49, $48, $44, $52,
|
||||
$00, $00, $00, $18, $00, $00, $00, $18, $08, $04, $00, $00, $00, $4A, $7E, $F5,
|
||||
$73, $00, $00, $00, $20, $63, $48, $52, $4D, $00, $00, $7A, $26, $00, $00, $80,
|
||||
$84, $00, $00, $FA, $00, $00, $00, $80, $E8, $00, $00, $75, $30, $00, $00, $EA,
|
||||
$60, $00, $00, $3A, $98, $00, $00, $17, $70, $9C, $BA, $51, $3C, $00, $00, $00,
|
||||
$02, $62, $4B, $47, $44, $00, $00, $AA, $8D, $23, $32, $00, $00, $00, $09, $70,
|
||||
$48, $59, $73, $00, $00, $0B, $13, $00, $00, $0B, $13, $01, $00, $9A, $9C, $18,
|
||||
$00, $00, $00, $07, $74, $49, $4D, $45, $07, $EA, $09, $1D, $14, $21, $19, $27,
|
||||
$B3, $C9, $F8, $00, $00, $01, $63, $49, $44, $41, $54, $38, $CB, $B5, $D3, $BD,
|
||||
$6E, $13, $41, $14, $05, $E0, $6F, $76, $13, $7B, $17, $21, $A4, $98, $48, $34,
|
||||
$3C, $00, $05, $0A, $52, $78, $05, $AA, $50, $24, $8F, $C1, $1B, $E4, $1D, $D2,
|
||||
$51, $41, $49, $43, $EB, $CE, $A9, $90, $A5, $28, $D6, $26, $15, $55, $8A, $3C,
|
||||
$00, $42, $32, $08, $2A, $62, $25, $CE, $7A, $87, $C2, $E3, $3F, $61, $0A, $17,
|
||||
$39, $53, $CC, $E8, $9E, $39, $33, $F7, $DC, $99, $CB, $43, $23, $A4, $79, $CB,
|
||||
$36, $EE, $4D, $B4, $05, $DC, $A1, $8D, $E8, $4E, $6E, $1B, $63, $93, $85, $2C,
|
||||
$77, $EC, $5C, $E5, $C8, $AE, $AE, $4A, $DF, $9E, $3D, $7D, $95, $AE, $5D, $47,
|
||||
$2A, $E7, $8E, $E5, $90, $81, $96, $03, $FB, $32, $71, $6D, $16, $51, $66, $DF,
|
||||
$81, $D6, $22, $54, $1A, $B8, $B0, $23, $17, $14, $4A, $A5, $4C, $A6, $54, $2A,
|
||||
$04, $B9, $1D, $17, $06, $CA, $69, $32, $53, $FC, $34, $70, $65, $82, $5A, $AD,
|
||||
$16, $C5, $B4, $22, $AA, $FD, $50, $B9, $D6, $CC, $4C, $07, $4F, $F1, $4B, $14,
|
||||
$74, $3C, $9E, $A7, $16, $FC, $F1, $5B, $5C, $E2, $13, $0A, $5D, $5D, $05, $3A,
|
||||
$4E, $0D, $7D, $4F, $63, $E8, $54, $67, $85, $B7, $95, $4E, $7A, $96, $4A, $7C,
|
||||
$EB, $8B, $6F, $9A, $14, $0D, $AE, $DD, $AE, $F0, $49, $30, $AD, $3B, $8C, $BC,
|
||||
$9F, $C7, $EE, $93, $70, $99, $4F, $1E, $32, $2F, $71, $A5, $F1, $C4, $89, $57,
|
||||
$1A, $44, $1F, $7C, $4E, $BB, $16, $7C, $7A, $87, $8D, $51, $EA, $EB, $4F, $EB,
|
||||
$2C, $D7, $4E, $23, $5B, $C7, $CF, $F2, $6D, $A7, $F9, $91, $77, $5E, $AC, $98,
|
||||
$FE, $68, $B4, $C4, $27, $41, $34, $44, $44, $E1, $8D, $D7, $F3, $6F, $96, $7B,
|
||||
$EE, $93, $D1, $12, $BF, $F9, $C3, $85, $74, $CF, $5B, $F4, $D4, $FF, $F1, $F8,
|
||||
$0F, $BF, $C1, $E7, $9B, $09, $CE, $DC, $B8, $74, $B8, $B6, $1F, $0E, $5D, $BA,
|
||||
$71, $B6, $5C, $A5, $B1, $9E, $4C, $3E, $EF, $BF, $55, $04, $8D, $AF, $7A, $C6,
|
||||
$0B, $D3, $E4, $5A, $36, $68, $D1, $87, $C4, $5F, $C8, $81, $83, $A2, $85, $86,
|
||||
$DA, $DD, $00, $00, $00, $00, $49, $45, $4E, $44, $AE, $42, $60, $82
|
||||
);
|
||||
|
||||
{ icons8-width-90.png, 24 на 24 точек }
|
||||
IconSelectRangeData: array[0..473] of Byte = (
|
||||
$89, $50, $4E, $47, $0D, $0A, $1A, $0A, $00, $00, $00, $0D, $49, $48, $44, $52,
|
||||
$00, $00, $00, $18, $00, $00, $00, $18, $08, $04, $00, $00, $00, $4A, $7E, $F5,
|
||||
$73, $00, $00, $00, $20, $63, $48, $52, $4D, $00, $00, $7A, $26, $00, $00, $80,
|
||||
$84, $00, $00, $FA, $00, $00, $00, $80, $E8, $00, $00, $75, $30, $00, $00, $EA,
|
||||
$60, $00, $00, $3A, $98, $00, $00, $17, $70, $9C, $BA, $51, $3C, $00, $00, $00,
|
||||
$02, $62, $4B, $47, $44, $00, $00, $AA, $8D, $23, $32, $00, $00, $00, $09, $70,
|
||||
$48, $59, $73, $00, $00, $0B, $13, $00, $00, $0B, $13, $01, $00, $9A, $9C, $18,
|
||||
$00, $00, $00, $07, $74, $49, $4D, $45, $07, $EA, $09, $1D, $14, $21, $19, $27,
|
||||
$B3, $C9, $F8, $00, $00, $01, $3F, $49, $44, $41, $54, $38, $CB, $CD, $D2, $BF,
|
||||
$4A, $1C, $51, $18, $05, $F0, $DF, $8C, $3B, $8E, $53, $64, $70, $94, $2D, $5D,
|
||||
$B0, $90, $B0, $C5, $DA, $A6, $14, $C1, $28, $E8, $1B, $F8, $0C, $61, $FB, $7D,
|
||||
$02, $5F, $40, $D8, $07, $91, $6D, $92, $52, $2D, $6C, $BD, $45, $08, $29, $84,
|
||||
$ED, $C4, $C2, $90, $26, $8C, $6E, $66, $53, $B8, $7F, $26, $82, $85, $0A, $E2,
|
||||
$B9, $C5, $2D, $CE, $3D, $DF, $F7, $9D, $F3, $5D, $DE, $1D, $16, $26, $77, $2E,
|
||||
$55, $CA, $7C, $50, $4A, $14, $4A, $91, $15, $23, $63, $85, $C8, $68, $C2, $CF,
|
||||
$D0, $D0, $D7, $17, $EB, $3A, $91, $DB, $73, $66, $C3, $86, $33, $7B, $72, $27,
|
||||
$BA, $62, $7D, $7D, $8D, $87, $A7, $90, $D8, $44, $22, $A0, $34, $34, $F0, $0B,
|
||||
$03, $43, $A5, $6F, $C2, $8C, $1F, $4D, $3B, $64, $CE, $9D, $CB, $9E, $1C, $BC,
|
||||
$C6, $C7, $CF, $35, $FD, $2A, $C1, $58, $C7, $BE, $C5, $FF, $F8, $45, $FB, $3A,
|
||||
$C6, $F5, $7C, $A6, $A8, $7C, $72, $AC, $12, $DC, $88, $66, $25, $9A, $8E, $C4,
|
||||
$BE, $A8, $A6, $A5, $A3, $89, $A9, $AF, $D6, $FD, $D6, $76, $EB, $87, $BF, $35,
|
||||
$C1, $82, $8F, $0A, $DF, $E5, $AE, $EC, $FA, $F3, $02, $0F, $F3, $D8, $4E, $6D,
|
||||
$09, $2E, $AD, $59, $92, $4D, $CE, $92, $35, $97, $82, $2D, $A7, $D3, $58, $E7,
|
||||
$1E, $62, $17, $0E, $B5, $5C, $BB, $AB, $95, $BA, $D6, $33, $F4, $73, $3E, $49,
|
||||
$A3, $46, $46, $82, $F0, $A8, $F7, $9D, $01, $F5, $95, $BE, $ED, $E2, $2A, $DB,
|
||||
$BA, $52, $6D, $3D, $4D, $4D, $3D, $6D, $A9, $AE, $6D, $D5, $63, $C1, $48, $10,
|
||||
$DC, $EB, $D8, $91, $6A, $39, $B0, $6C, $D9, $81, $96, $D4, $67, $1D, $F7, $82,
|
||||
$30, $FF, $AB, $90, $CB, $91, $29, $44, $12, $AB, $62, $B1, $55, $89, $48, $21,
|
||||
$9B, $F1, $EF, $13, $FF, $00, $8F, $00, $4E, $D0, $9E, $28, $A1, $C0, $00, $00,
|
||||
$00, $00, $49, $45, $4E, $44, $AE, $42, $60, $82
|
||||
);
|
||||
|
||||
{ icons8-показ-видеокадров-30.png, значок окна, 32 на 32 точек, формат ico }
|
||||
IconAppData: array[0..4285] of Byte = (
|
||||
$00, $00, $01, $00, $01, $00, $20, $20, $00, $00, $01, $00, $20, $00, $A8, $10,
|
||||
|
|
|
|||
|
|
@ -97,11 +97,21 @@ object MainForm: TMainForm
|
|||
OnClick = btnRippleMenuClick
|
||||
TabStop = False
|
||||
end
|
||||
object btnSelectMode: TThemedButton
|
||||
Left = 448
|
||||
Height = 28
|
||||
Top = 6
|
||||
Width = 28
|
||||
Icon = tiSelectClips
|
||||
OnClick = btnSelectModeClick
|
||||
ShowHint = True
|
||||
TabStop = False
|
||||
end
|
||||
object lblStatus: TLabel
|
||||
Left = 488
|
||||
Left = 518
|
||||
Height = 20
|
||||
Top = 10
|
||||
Width = 780
|
||||
Width = 750
|
||||
Anchors = [akTop, akLeft, akRight]
|
||||
AutoSize = False
|
||||
Caption = 'Готов.'
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ type
|
|||
btnCenter: TThemedButton;
|
||||
btnRipple: TThemedButton;
|
||||
btnRippleMenu: TThemedButton;
|
||||
btnSelectMode: TThemedButton;
|
||||
lblPoolInfo: TLabel;
|
||||
lblStatus: TLabel;
|
||||
lblTime: TLabel;
|
||||
|
|
@ -72,6 +73,7 @@ type
|
|||
procedure btnCenterClick(Sender: TObject);
|
||||
procedure btnRippleClick(Sender: TObject);
|
||||
procedure btnRippleMenuClick(Sender: TObject);
|
||||
procedure btnSelectModeClick(Sender: TObject);
|
||||
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
|
||||
procedure FormCreate(Sender: TObject);
|
||||
procedure FormDestroy(Sender: TObject);
|
||||
|
|
@ -156,6 +158,7 @@ type
|
|||
{ Пункт «Ключ и прозрачность в предпросмотре»: галку в нём приходится
|
||||
держать за руку — она не следует за настройкой сама. }
|
||||
FKeyPreviewItem: TMenuItem;
|
||||
FSelectModeItem: TMenuItem;
|
||||
{ Показанная доля выполнения. Отдельно от настоящей, потому что растёт
|
||||
она по своим правилам — см. AdvanceJobBar. }
|
||||
FJobShown: Double;
|
||||
|
|
@ -240,6 +243,8 @@ type
|
|||
procedure TrackMenuDown(Sender: TObject);
|
||||
procedure UpdateSnapButton;
|
||||
procedure UpdateGlueButton;
|
||||
procedure UpdateSelectModeButton;
|
||||
procedure TimelineSelectModeChange(Sender: TObject);
|
||||
procedure UpdateCenterButton;
|
||||
procedure ToggleCenterPlayhead;
|
||||
procedure RippleModeChosen(Sender: TObject);
|
||||
|
|
@ -372,6 +377,7 @@ type
|
|||
{ Что и откуда собирать: с учётом отмеченного участка. }
|
||||
procedure DescribeRegion(out AFrom, ATo: Double);
|
||||
procedure MenuSelectAll(Sender: TObject);
|
||||
procedure MenuSelectMode(Sender: TObject);
|
||||
procedure MenuRipple(Sender: TObject);
|
||||
procedure MenuToggleRipple(Sender: TObject);
|
||||
procedure MenuGoStart(Sender: TObject);
|
||||
|
|
@ -525,6 +531,7 @@ begin
|
|||
UpdateRippleButton;
|
||||
UpdateSnapButton;
|
||||
UpdateGlueButton;
|
||||
UpdateSelectModeButton;
|
||||
UpdateCenterButton;
|
||||
UpdateCaption;
|
||||
SetStatus('Готов. Добавьте файлы в проект.');
|
||||
|
|
@ -662,6 +669,7 @@ begin
|
|||
FTimeline.OnTrackMenu := @TimelineTrackMenu;
|
||||
FTimeline.OnTrackRename := @TimelineTrackRename;
|
||||
FTimeline.OnRegionChange := @TimelineRegionChange;
|
||||
FTimeline.OnSelectionModeChange := @TimelineSelectModeChange;
|
||||
FTimeline.OnActiveTrackChange := @TimelineActiveTrackChange;
|
||||
end;
|
||||
|
||||
|
|
@ -768,6 +776,10 @@ begin
|
|||
AddItem(MEdit, 'Разрезать по курсору', @MenuSplit, ShortCut(VK_S, []));
|
||||
AddItem(MEdit, 'Удалить выделенное', @MenuDelete, ShortCut(VK_DELETE, []));
|
||||
AddItem(MEdit, 'Выделить всё', @MenuSelectAll, ShortCut(VK_A, [ssCtrl]));
|
||||
{ Пункт держит и подпись режима: по одному значку на кнопке не угадать,
|
||||
что сейчас делает протяжка мышью. }
|
||||
FSelectModeItem := AddItem(MEdit, 'Режим выделения', @MenuSelectMode,
|
||||
ShortCut(VK_D, []));
|
||||
AddItem(MEdit, 'Цветовой ключ клипа...', @MenuColorKey);
|
||||
AddSep(MEdit);
|
||||
AddItem(MEdit, 'Копировать клипы', @MenuCopyClips, ShortCut(VK_C, [ssCtrl]));
|
||||
|
|
@ -900,7 +912,9 @@ procedure TMainForm.BuildJobIndicator;
|
|||
const
|
||||
JobWidth = 330;
|
||||
LogButtonWidth = 28;
|
||||
LogButtonLeft = 448;
|
||||
{ Журнал стоит после переключателя выделения: сперва то, что меняет
|
||||
работу с монтажом, потом то, что показывает сообщения. }
|
||||
LogButtonLeft = 478;
|
||||
begin
|
||||
FJobLabel := TLabel.Create(Self);
|
||||
FJobLabel.Parent := pnlBottom;
|
||||
|
|
@ -2575,6 +2589,60 @@ begin
|
|||
UpdateGlueButton;
|
||||
end;
|
||||
|
||||
{ Кнопка режима выделения.
|
||||
|
||||
Режима два, и кнопка показывает не «включено-выключено», а какой сейчас:
|
||||
значок рамки — выделение клипов, значок промежутка — выделение по
|
||||
времени. Поэтому она не утапливается, а меняет картинку. }
|
||||
procedure TMainForm.UpdateSelectModeButton;
|
||||
begin
|
||||
if FSelectModeItem <> nil then
|
||||
if FTimeline.SelectionMode = smRange then
|
||||
FSelectModeItem.Caption := 'Режим выделения: по времени (сейчас)'
|
||||
else
|
||||
FSelectModeItem.Caption := 'Режим выделения: рамкой по клипам (сейчас)';
|
||||
|
||||
if FTimeline.SelectionMode = smRange then
|
||||
begin
|
||||
btnSelectMode.Icon := tiSelectRange;
|
||||
btnSelectMode.Hint := 'Выделение по времени: протяжка отмечает ' +
|
||||
'промежуток по всем дорожкам.' + LineEnding +
|
||||
'S режет все дорожки по обоим краям промежутка.' + LineEnding +
|
||||
'Переключить (D) на выделение клипов рамкой.';
|
||||
end
|
||||
else
|
||||
begin
|
||||
btnSelectMode.Icon := tiSelectClips;
|
||||
btnSelectMode.Hint := 'Выделение клипов: рамка выделяет всё, что ' +
|
||||
'задела.' + LineEnding +
|
||||
'S режет по курсору.' + LineEnding +
|
||||
'Переключить (D) на выделение промежутка по времени.';
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMainForm.btnSelectModeClick(Sender: TObject);
|
||||
begin
|
||||
FTimeline.ToggleSelectionMode;
|
||||
end;
|
||||
|
||||
procedure TMainForm.MenuSelectMode(Sender: TObject);
|
||||
begin
|
||||
FTimeline.ToggleSelectionMode;
|
||||
end;
|
||||
|
||||
{ Режим сменили — сказать об этом словами: по одному значку не угадать,
|
||||
что теперь делает протяжка мышью. }
|
||||
procedure TMainForm.TimelineSelectModeChange(Sender: TObject);
|
||||
begin
|
||||
UpdateSelectModeButton;
|
||||
if FTimeline.SelectionMode = smRange then
|
||||
SetStatus('Выделение по времени: протяжка отмечает промежуток, ' +
|
||||
'S режет по его краям.', True)
|
||||
else
|
||||
SetStatus('Выделение клипов: рамка выделяет всё, что задела, ' +
|
||||
'S режет по курсору.', True);
|
||||
end;
|
||||
|
||||
{ Держать ли курсор посередине при воспроизведении.
|
||||
|
||||
Включено — монтаж едет под курсором, и всегда видно, что будет дальше.
|
||||
|
|
|
|||
|
|
@ -70,7 +70,9 @@ type
|
|||
tiRipple, // схлопывание: дыра между кусками закрывается
|
||||
tiLog, // журнал: полоса с сообщениями
|
||||
tiGlue, // курсор липнет к границам клипов
|
||||
tiTarget // курсор держится середины монтажа
|
||||
tiTarget, // курсор держится середины монтажа
|
||||
tiSelectClips, // выделение рамкой: что задело, то и выделено
|
||||
tiSelectRange // выделение по времени: от и до, по всем дорожкам
|
||||
);
|
||||
|
||||
{ Полоска выполнения, которая рисует себя сама.
|
||||
|
|
@ -460,6 +462,10 @@ begin
|
|||
tiLog: Stream.WriteBuffer(IconLogData[0], Length(IconLogData));
|
||||
tiGlue: Stream.WriteBuffer(IconGlueData[0], Length(IconGlueData));
|
||||
tiTarget: Stream.WriteBuffer(IconTargetData[0], Length(IconTargetData));
|
||||
tiSelectClips: Stream.WriteBuffer(IconSelectClipsData[0],
|
||||
Length(IconSelectClipsData));
|
||||
tiSelectRange: Stream.WriteBuffer(IconSelectRangeData[0],
|
||||
Length(IconSelectRangeData));
|
||||
else
|
||||
Exit;
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,16 @@ type
|
|||
taRubberBand // обтягиваем рамкой клипы по пустому месту
|
||||
);
|
||||
|
||||
{ Как понимать протяжку мышью по пустому месту.
|
||||
|
||||
smClips — что рамка задела, то и выделено: привычное выделение
|
||||
прямоугольником.
|
||||
|
||||
smRange — отмечается промежуток времени по всем дорожкам сразу, от и
|
||||
до. Нужен, чтобы вырезать кусок фильма целиком: отметил промежуток,
|
||||
нажал S — и все дорожки разрезаны в двух местах, по краям промежутка. }
|
||||
TSelectionMode = (smClips, smRange);
|
||||
|
||||
{ Что находится под указателем в полосе заголовков дорожек. }
|
||||
THeaderPart = (hpNone, hpBody, hpMute, hpSolo, hpUp, hpDown, hpName);
|
||||
|
||||
|
|
@ -139,6 +149,14 @@ type
|
|||
FBandToTime: Double;
|
||||
FBandFromY: Integer;
|
||||
FBandToY: Integer;
|
||||
{ Режим выделения и отмеченный им промежуток времени.
|
||||
|
||||
Промежуток живёт до следующего выделения или щелчка: по нему режет
|
||||
клавиша S, и человек должен видеть, где именно это произойдёт. }
|
||||
FSelectionMode: TSelectionMode;
|
||||
FRangeFrom: Double;
|
||||
FRangeTo: Double;
|
||||
FOnSelectionModeChange: TNotifyEvent;
|
||||
FGroupClips: array of TClip;
|
||||
FGroupOrigStarts: array of Double;
|
||||
|
||||
|
|
@ -251,6 +269,9 @@ type
|
|||
const ATime, ATolerance: Double; out AFrom, ATo: Double): Boolean;
|
||||
{ Допуск прилипания в секундах: те же экранные точки, что у краёв. }
|
||||
function SnapTolerance: Double;
|
||||
procedure SetSelectionMode(AValue: TSelectionMode);
|
||||
{ Рез в указанном месте. Возвращает, сколько клипов разрезано. }
|
||||
function SplitAt(const ATime: Double; APrecise: Boolean): Integer;
|
||||
protected
|
||||
procedure Paint; override;
|
||||
procedure DragOver(Source: TObject; X, Y: Integer; State: TDragState;
|
||||
|
|
@ -332,6 +353,22 @@ type
|
|||
procedure SelectInRange(const AFrom, ATo: Double;
|
||||
ATrackFrom, ATrackTo: Integer; AAdd: Boolean);
|
||||
|
||||
{ Режим выделения: рамкой по клипам или промежутком времени по всем
|
||||
дорожкам. Переключается клавишей D и кнопкой внизу. }
|
||||
property SelectionMode: TSelectionMode read FSelectionMode
|
||||
write SetSelectionMode;
|
||||
procedure ToggleSelectionMode;
|
||||
{ Отмечен ли промежуток времени и какой. }
|
||||
function HasSelectionRange: Boolean;
|
||||
property RangeFrom: Double read FRangeFrom;
|
||||
property RangeTo: Double read FRangeTo;
|
||||
{ Отметить промежуток. Мышь зовёт это же, и проверки тоже: так
|
||||
поведение реза проверяется числами, без беготни указателем. }
|
||||
procedure SetSelectionRange(const AFrom, ATo: Double);
|
||||
procedure ClearSelectionRange;
|
||||
property OnSelectionModeChange: TNotifyEvent read FOnSelectionModeChange
|
||||
write FOnSelectionModeChange;
|
||||
|
||||
{ Номер дорожки под указателем мыши, минус один если ни одной. }
|
||||
property HoverTrack: Integer read FHoverTrack;
|
||||
{ Дорожка, выбранная щелчком по заголовку. }
|
||||
|
|
@ -1381,14 +1418,45 @@ end;
|
|||
procedure TTimelineControl.DrawRubberBand(ACanvas: TCanvas);
|
||||
var
|
||||
L, R, T, B: Integer;
|
||||
Pulling: Boolean;
|
||||
begin
|
||||
if (FAction <> taRubberBand) or (not FMovedWhileDown) then
|
||||
{ Имя не Dragging: так называется метод самого элемента управления. }
|
||||
Pulling := (FAction = taRubberBand) and FMovedWhileDown;
|
||||
|
||||
{ Отмеченный промежуток виден и после протяжки: по нему режет клавиша S,
|
||||
и человек должен знать, где это произойдёт. }
|
||||
if (not Pulling) and HasSelectionRange then
|
||||
begin
|
||||
L := Max(TimeToX(FRangeFrom), FHeaderWidth);
|
||||
R := Min(TimeToX(FRangeTo), ClientWidth);
|
||||
if R <= L then
|
||||
Exit;
|
||||
ACanvas.Pen.Color := ColorBand;
|
||||
ACanvas.Pen.Style := psDot;
|
||||
ACanvas.Brush.Style := bsClear;
|
||||
ACanvas.Rectangle(L, FRulerHeight, R, ClientHeight);
|
||||
ACanvas.Pen.Style := psSolid;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if not Pulling then
|
||||
Exit;
|
||||
|
||||
L := Min(TimeToX(FBandFromTime), TimeToX(FBandToTime));
|
||||
R := Max(TimeToX(FBandFromTime), TimeToX(FBandToTime));
|
||||
T := Min(FBandFromY, FBandToY) - FScrollY;
|
||||
B := Max(FBandFromY, FBandToY) - FScrollY;
|
||||
|
||||
if FSelectionMode = smRange then
|
||||
begin
|
||||
{ По времени отмечают промежуток, а не прямоугольник: рамка идёт во
|
||||
всю высоту дорожек, сколько бы их ни было. }
|
||||
T := FRulerHeight;
|
||||
B := ClientHeight;
|
||||
end
|
||||
else
|
||||
begin
|
||||
T := Min(FBandFromY, FBandToY) - FScrollY;
|
||||
B := Max(FBandFromY, FBandToY) - FScrollY;
|
||||
end;
|
||||
|
||||
L := Max(L, FHeaderWidth);
|
||||
R := Min(R, ClientWidth);
|
||||
|
|
@ -2618,10 +2686,22 @@ begin
|
|||
begin
|
||||
FAction := taNone;
|
||||
if FMovedWhileDown then
|
||||
SelectInRange(FBandFromTime, FBandToTime,
|
||||
(FBandFromY - FRulerHeight) div FTrackHeight,
|
||||
(FBandToY - FRulerHeight) div FTrackHeight,
|
||||
ssCtrl in Shift)
|
||||
begin
|
||||
if FSelectionMode = smRange then
|
||||
begin
|
||||
{ Промежуток времени: по всем дорожкам, от и до. Края кладутся на
|
||||
кадровую сетку — по ним потом режет клавиша S. }
|
||||
SetSelectionRange(FBandFromTime, FBandToTime);
|
||||
if (FProject <> nil) and (FRangeTo > FRangeFrom) then
|
||||
SelectInRange(FRangeFrom, FRangeTo, 0, FProject.TrackCount - 1,
|
||||
ssCtrl in Shift);
|
||||
end
|
||||
else
|
||||
SelectInRange(FBandFromTime, FBandToTime,
|
||||
(FBandFromY - FRulerHeight) div FTrackHeight,
|
||||
(FBandToY - FRulerHeight) div FTrackHeight,
|
||||
ssCtrl in Shift);
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (FProject <> nil) and not (ssCtrl in Shift) then
|
||||
|
|
@ -2630,6 +2710,8 @@ begin
|
|||
if Assigned(FOnSelectionChange) then
|
||||
FOnSelectionChange(Self);
|
||||
end;
|
||||
{ Щелчок снимает и отметку промежутка: она про прошлое выделение. }
|
||||
ClearSelectionRange;
|
||||
Playhead := Max(0, SnapPlayhead(XToTime(X)));
|
||||
end;
|
||||
FMovedWhileDown := False;
|
||||
|
|
@ -2720,12 +2802,18 @@ end;
|
|||
|
||||
{ --- Правки --- }
|
||||
|
||||
function TTimelineControl.SplitAtPlayhead(APrecise: Boolean): Integer;
|
||||
{ Рез в указанном месте.
|
||||
|
||||
Место кладётся на кадровую сетку: между кадрами времени нет, и рез на
|
||||
доле кадра оставил бы в монтаже время, которого не бывает. Сдвиг не
|
||||
больше полкадра. }
|
||||
function TTimelineControl.SplitAt(const ATime: Double;
|
||||
APrecise: Boolean): Integer;
|
||||
var
|
||||
T: Integer;
|
||||
Track: TTrack;
|
||||
Clip, Tail: TClip;
|
||||
Offset: Double;
|
||||
Offset, At: Double;
|
||||
{ Старый номер связки — новый. Одна связка при резе становится двумя:
|
||||
голова со своей парой и хвост со своей. }
|
||||
NewGroups: TStringList;
|
||||
|
|
@ -2735,6 +2823,7 @@ begin
|
|||
if FProject = nil then
|
||||
Exit;
|
||||
|
||||
At := QuantizeTime(ATime);
|
||||
FProject.BeginChange;
|
||||
NewGroups := TStringList.Create;
|
||||
try
|
||||
|
|
@ -2745,11 +2834,11 @@ begin
|
|||
for T := 0 to FProject.TrackCount - 1 do
|
||||
begin
|
||||
Track := FProject.Tracks[T];
|
||||
Clip := Track.ClipAt(FPlayhead);
|
||||
Clip := Track.ClipAt(At);
|
||||
if Clip = nil then
|
||||
Continue;
|
||||
|
||||
Offset := FPlayhead - Clip.TlStart;
|
||||
Offset := At - Clip.TlStart;
|
||||
{ Рез вплотную к краю ничего не даёт, только плодит клипы нулевой длины. }
|
||||
if (Offset < 0.001) or (Offset > Clip.Duration - 0.001) then
|
||||
Continue;
|
||||
|
|
@ -2758,7 +2847,7 @@ begin
|
|||
Tail.Id := Clip.Id + '_b';
|
||||
Tail.MediaId := Clip.MediaId;
|
||||
Tail.SrcIn := Clip.SrcIn + Offset;
|
||||
Tail.TlStart := FPlayhead;
|
||||
Tail.TlStart := At;
|
||||
Tail.Duration := Clip.Duration - Offset;
|
||||
Tail.EdgeOut := Clip.EdgeOut;
|
||||
{ Хвост — тот же клип, только вторая его половина. Всё настроенное
|
||||
|
|
@ -2819,6 +2908,90 @@ begin
|
|||
FProject.Undo;
|
||||
end;
|
||||
|
||||
{ Режим выделения: рамкой по клипам или промежутком по времени.
|
||||
|
||||
Смена режима снимает отмеченный промежуток: он принадлежит режиму «по
|
||||
времени», и оставлять его в другом режиме значило бы резать по невидимой
|
||||
метке. }
|
||||
procedure TTimelineControl.SetSelectionMode(AValue: TSelectionMode);
|
||||
begin
|
||||
if FSelectionMode = AValue then
|
||||
Exit;
|
||||
FSelectionMode := AValue;
|
||||
ClearSelectionRange;
|
||||
Invalidate;
|
||||
if Assigned(FOnSelectionModeChange) then
|
||||
FOnSelectionModeChange(Self);
|
||||
end;
|
||||
|
||||
procedure TTimelineControl.ToggleSelectionMode;
|
||||
begin
|
||||
if FSelectionMode = smClips then
|
||||
SelectionMode := smRange
|
||||
else
|
||||
SelectionMode := smClips;
|
||||
end;
|
||||
|
||||
function TTimelineControl.HasSelectionRange: Boolean;
|
||||
begin
|
||||
Result := (FSelectionMode = smRange) and (FRangeTo - FRangeFrom > 0.0005);
|
||||
end;
|
||||
|
||||
procedure TTimelineControl.SetSelectionRange(const AFrom, ATo: Double);
|
||||
begin
|
||||
FRangeFrom := Max(0, QuantizeTime(Min(AFrom, ATo)));
|
||||
FRangeTo := Max(0, QuantizeTime(Max(AFrom, ATo)));
|
||||
Invalidate;
|
||||
end;
|
||||
|
||||
procedure TTimelineControl.ClearSelectionRange;
|
||||
begin
|
||||
if (FRangeFrom = 0) and (FRangeTo = 0) then
|
||||
Exit;
|
||||
FRangeFrom := 0;
|
||||
FRangeTo := 0;
|
||||
Invalidate;
|
||||
end;
|
||||
|
||||
{ Рез по команде человека.
|
||||
|
||||
Обычно — по курсору. Если отмечен промежуток времени (режим выделения
|
||||
«по времени»), режем по обоим его краям: это и есть «вырезать кусок
|
||||
фильма» — два реза, между ними то, что уйдёт.
|
||||
|
||||
Курсор после реза встаёт ровно на место реза: дальше с этим местом и
|
||||
работают — удаляют кусок, схлопывают дыру, ставят следующий рез. }
|
||||
function TTimelineControl.SplitAtPlayhead(APrecise: Boolean): Integer;
|
||||
var
|
||||
At: Double;
|
||||
begin
|
||||
Result := 0;
|
||||
if FProject = nil then
|
||||
Exit;
|
||||
|
||||
if HasSelectionRange then
|
||||
begin
|
||||
{ Сперва дальний край: рез не двигает то, что левее, но порядок
|
||||
всё равно понятнее от конца к началу. }
|
||||
Result := SplitAt(FRangeTo, APrecise);
|
||||
Result := Result + SplitAt(FRangeFrom, APrecise);
|
||||
Playhead := Max(0, QuantizeTime(FRangeFrom));
|
||||
|
||||
{ После реза выделенной остаётся ровно середина — то, что между
|
||||
краями. Иначе Delete убрал бы не её: выделение осталось бы на
|
||||
клипах, какими они были до реза, а середина оказалась бы новой,
|
||||
невыделенной половиной. }
|
||||
if Result > 0 then
|
||||
SelectInRange(FRangeFrom, FRangeTo, 0, FProject.TrackCount - 1, False);
|
||||
Exit;
|
||||
end;
|
||||
|
||||
At := QuantizeTime(FPlayhead);
|
||||
Result := SplitAt(At, APrecise);
|
||||
if Result > 0 then
|
||||
Playhead := Max(0, At);
|
||||
end;
|
||||
|
||||
{ Расширяет выделение на всю связку: клипы, связанные с уже выделенными,
|
||||
тоже считаются выделенными. Иначе удаление или перенос разорвали бы
|
||||
связку пополам. }
|
||||
|
|
@ -2974,7 +3147,7 @@ var
|
|||
T, C: Integer;
|
||||
Track: TTrack;
|
||||
AnyChange: Boolean;
|
||||
HoleEnd: Double;
|
||||
HoleEnd, HoleStart: Double;
|
||||
Touched: TStringList;
|
||||
begin
|
||||
if FProject = nil then
|
||||
|
|
@ -2986,6 +3159,7 @@ begin
|
|||
|
||||
AnyChange := False;
|
||||
HoleEnd := 0;
|
||||
HoleStart := -1;
|
||||
FProject.BeginChange;
|
||||
Touched := TStringList.Create;
|
||||
try
|
||||
|
|
@ -2997,6 +3171,9 @@ begin
|
|||
begin
|
||||
{ Правый край дыры: дальше него всё поедет влево. }
|
||||
HoleEnd := Max(HoleEnd, Track[C].TlEnd);
|
||||
{ Левый край — место будущего стыка: туда встанет курсор. }
|
||||
if (HoleStart < 0) or (Track[C].TlStart < HoleStart) then
|
||||
HoleStart := Track[C].TlStart;
|
||||
if Touched.IndexOf(IntToStr(T)) < 0 then
|
||||
Touched.Add(IntToStr(T));
|
||||
Track.Remove(Track[C]);
|
||||
|
|
@ -3014,7 +3191,17 @@ begin
|
|||
end;
|
||||
|
||||
if AnyChange then
|
||||
StructureChanged
|
||||
begin
|
||||
{ Курсор — на место, где был вырезанный кусок: при схлопывании там
|
||||
теперь стык, без схлопывания — начало дыры. С этим местом и работают
|
||||
дальше, и искать его мышью заново незачем. }
|
||||
if HoleStart >= 0 then
|
||||
Playhead := Max(0, HoleStart);
|
||||
{ Отметка промежутка пережила бы удаление и резала бы потом по
|
||||
пустому месту. }
|
||||
ClearSelectionRange;
|
||||
StructureChanged;
|
||||
end
|
||||
else
|
||||
FProject.Undo;
|
||||
end;
|
||||
|
|
@ -3095,6 +3282,9 @@ begin
|
|||
|
||||
if AnyChange then
|
||||
begin
|
||||
{ Курсор — на стык: дыры больше нет, а работать дальше будут именно с
|
||||
этим местом. }
|
||||
Playhead := Max(0, GapStart);
|
||||
StructureChanged;
|
||||
Result := True;
|
||||
end
|
||||
|
|
@ -3120,7 +3310,7 @@ procedure TTimelineControl.SelectInRange(const AFrom, ATo: Double;
|
|||
ATrackFrom, ATrackTo: Integer; AAdd: Boolean);
|
||||
var
|
||||
T, C, T1, T2: Integer;
|
||||
From_, To_: Double;
|
||||
From_, To_, Eps: Double;
|
||||
Clip: TClip;
|
||||
begin
|
||||
if FProject = nil then
|
||||
|
|
@ -3129,6 +3319,14 @@ begin
|
|||
{ Рамку тянут в любую сторону — приводим её к возрастающему виду. }
|
||||
From_ := Min(AFrom, ATo);
|
||||
To_ := Max(AFrom, ATo);
|
||||
|
||||
{ Нужно настоящее перекрытие, а не касание краями. Сосед, кончающийся
|
||||
ровно там, где рамка начинается, в неё не попал — но сложение долей
|
||||
даёт разницу в последнем знаке, и «больше» срабатывает. Порог —
|
||||
полкадра: перекрытия короче кадра не бывает. }
|
||||
Eps := 0.0005;
|
||||
if (FProject.Master.FpsNum > 0) and (FProject.Master.FpsDen > 0) then
|
||||
Eps := FProject.Master.FpsDen / FProject.Master.FpsNum / 2;
|
||||
T1 := Max(0, Min(ATrackFrom, ATrackTo));
|
||||
T2 := Min(FProject.TrackCount - 1, Max(ATrackFrom, ATrackTo));
|
||||
|
||||
|
|
@ -3139,7 +3337,7 @@ begin
|
|||
for C := 0 to FProject.Tracks[T].Count - 1 do
|
||||
begin
|
||||
Clip := FProject.Tracks[T][C];
|
||||
if (Clip.TlEnd > From_) and (Clip.TlStart < To_) then
|
||||
if (Clip.TlEnd > From_ + Eps) and (Clip.TlStart < To_ - Eps) then
|
||||
Clip.Selected := True;
|
||||
end;
|
||||
|
||||
|
|
@ -3189,6 +3387,7 @@ begin
|
|||
SelectAll;
|
||||
Key := 0;
|
||||
end;
|
||||
|
||||
VK_HOME:
|
||||
begin
|
||||
{ К началу самого раннего клипа, а не просто в ноль: если монтаж
|
||||
|
|
|
|||
|
|
@ -631,6 +631,128 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
{ Рез и курсор: где режет S и куда после этого встаёт курсор.
|
||||
|
||||
Раньше курсор оставался там, где был, а рез мог попасть на долю кадра.
|
||||
Теперь рез кладётся на кадровую сетку, а курсор встаёт ровно на место
|
||||
реза — с ним и работают дальше. }
|
||||
procedure CheckSplitAndPlayhead;
|
||||
var
|
||||
Proj: TProject;
|
||||
Info: TMediaInfo;
|
||||
Media: TMediaItem;
|
||||
Timeline: TTimelineControl;
|
||||
Step: Double;
|
||||
I, N: Integer;
|
||||
begin
|
||||
WriteLn;
|
||||
WriteLn('Рез, промежуток и курсор');
|
||||
WriteLn;
|
||||
|
||||
Info := TestInfo;
|
||||
Info.FpsNum := 60000;
|
||||
Info.FpsDen := 1001;
|
||||
Step := Info.FpsDen / Info.FpsNum;
|
||||
|
||||
Proj := TProject.Create;
|
||||
Timeline := TTimelineControl.Create(nil);
|
||||
try
|
||||
Proj.Clear;
|
||||
Proj.AddTrack('V1');
|
||||
Proj.AddTrack('A1');
|
||||
Media := Proj.AddMedia('/несуществующий/видео.mp4', Info);
|
||||
Proj.Master := Info;
|
||||
{ По два клипа на каждой дорожке, встык: 0..10 и 10..20. }
|
||||
Proj.AddClip(Proj.Tracks[0], Media, 0, 10, 0);
|
||||
Proj.AddClip(Proj.Tracks[0], Media, 20, 10, 10);
|
||||
Proj.AddClip(Proj.Tracks[1], Media, 0, 10, 0);
|
||||
Proj.AddClip(Proj.Tracks[1], Media, 20, 10, 10);
|
||||
|
||||
Timeline.Project := Proj;
|
||||
Timeline.PixelsPerSecond := 100;
|
||||
|
||||
{ Рез по курсору: место кладётся на кадр, курсор встаёт на него. }
|
||||
Timeline.Playhead := 3.3177;
|
||||
CheckTrue('разрезаны обе дорожки', Timeline.SplitAtPlayhead(False) = 2);
|
||||
CheckTrue('курсор встал на место реза',
|
||||
Abs(Timeline.Playhead - Round(3.3177 / Step) * Step) < 0.0001);
|
||||
CheckTrue('курсор на кадровой сетке',
|
||||
Abs(Timeline.Playhead / Step - Round(Timeline.Playhead / Step)) < 0.001);
|
||||
N := 0;
|
||||
for I := 0 to Proj.Tracks[0].Count - 1 do
|
||||
if Abs(Proj.Tracks[0][I].TlStart - Timeline.Playhead) < 0.0001 then
|
||||
Inc(N);
|
||||
CheckTrue('рез пришёлся ровно туда, где курсор', N = 1);
|
||||
|
||||
{ Промежуток по времени: рез по обоим краям, курсор — на начало. }
|
||||
Timeline.SelectionMode := smRange;
|
||||
Timeline.SetSelectionRange(12.13, 16.77);
|
||||
CheckTrue('промежуток отмечен', Timeline.HasSelectionRange);
|
||||
CheckTrue('разрезано по два клипа на дорожке',
|
||||
Timeline.SplitAtPlayhead(False) = 4);
|
||||
CheckTrue('курсор — на начале промежутка',
|
||||
Abs(Timeline.Playhead - Timeline.RangeFrom) < 0.0001);
|
||||
|
||||
N := 0;
|
||||
for I := 0 to Proj.Tracks[1].Count - 1 do
|
||||
if (Abs(Proj.Tracks[1][I].TlStart - Timeline.RangeFrom) < 0.0001) or
|
||||
(Abs(Proj.Tracks[1][I].TlStart - Timeline.RangeTo) < 0.0001) then
|
||||
Inc(N);
|
||||
CheckTrue('на звуковой дорожке резы по обоим краям', N = 2);
|
||||
|
||||
{ После реза выделена середина — то, что между краями: следующим
|
||||
действием её обычно удаляют. }
|
||||
N := 0;
|
||||
for I := 0 to Proj.Tracks[0].Count - 1 do
|
||||
if Proj.Tracks[0][I].Selected then
|
||||
begin
|
||||
Inc(N);
|
||||
CheckTrue('выделенное лежит внутри промежутка',
|
||||
(Proj.Tracks[0][I].TlStart >= Timeline.RangeFrom - 0.0001) and
|
||||
(Proj.Tracks[0][I].TlEnd <= Timeline.RangeTo + 0.0001));
|
||||
end;
|
||||
CheckTrue('на дорожке картинки выделена одна середина', N = 1);
|
||||
|
||||
{ Смена режима снимает промежуток: резать по невидимой метке нельзя. }
|
||||
Timeline.SelectionMode := smClips;
|
||||
CheckTrue('смена режима сняла промежуток',
|
||||
not Timeline.HasSelectionRange);
|
||||
|
||||
{ Удаление со схлопыванием: курсор встаёт на место стыка. }
|
||||
Proj.Clear;
|
||||
Proj.AddTrack('V1');
|
||||
Media := Proj.AddMedia('/несуществующий/видео.mp4', Info);
|
||||
Proj.Master := Info;
|
||||
Proj.RippleEnabled := True;
|
||||
Proj.AddClip(Proj.Tracks[0], Media, 0, 5, 0);
|
||||
Proj.AddClip(Proj.Tracks[0], Media, 10, 5, 5);
|
||||
Proj.AddClip(Proj.Tracks[0], Media, 20, 5, 10);
|
||||
Timeline.Playhead := 0;
|
||||
Proj.Tracks[0][1].Selected := True;
|
||||
Timeline.DeleteSelected;
|
||||
CheckTrue('после удаления клипов стало два', Proj.Tracks[0].Count = 2);
|
||||
CheckTrue('курсор — на месте вырезанного куска',
|
||||
Abs(Timeline.Playhead - 5) < 0.0001);
|
||||
CheckTrue('хвост придвинут к стыку',
|
||||
Abs(Proj.Tracks[0][1].TlStart - 5) < 0.0001);
|
||||
|
||||
{ Схлопывание дыры с клавиатуры: курсор тоже на стык. }
|
||||
Proj.Clear;
|
||||
Proj.AddTrack('V1');
|
||||
Media := Proj.AddMedia('/несуществующий/видео.mp4', Info);
|
||||
Proj.Master := Info;
|
||||
Proj.AddClip(Proj.Tracks[0], Media, 0, 5, 0);
|
||||
Proj.AddClip(Proj.Tracks[0], Media, 20, 5, 12);
|
||||
Timeline.ActiveTrack := 0;
|
||||
Timeline.Playhead := 8;
|
||||
CheckTrue('дыра схлопнута', Timeline.RippleCloseGap);
|
||||
CheckTrue('курсор — на стыке', Abs(Timeline.Playhead - 5) < 0.0001);
|
||||
finally
|
||||
Timeline.Free;
|
||||
Proj.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
begin
|
||||
WriteLn('Свойства из файлов форм');
|
||||
WriteLn;
|
||||
|
|
@ -663,6 +785,7 @@ begin
|
|||
CheckClipCopy;
|
||||
CheckRubberBand;
|
||||
CheckFrameGridRepair;
|
||||
CheckSplitAndPlayhead;
|
||||
CheckSplitAllTracks;
|
||||
CheckGapTolerance;
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ ICONS = [
|
|||
('Log', 'icons8-property-30.png', 24),
|
||||
('Glue', 'icons8-клей-30.png', 24),
|
||||
('Target', 'icons8-цель-30.png', 24),
|
||||
('SelectClips', 'icons8-select-all-90.png', 24),
|
||||
('SelectRange', 'icons8-width-90.png', 24),
|
||||
]
|
||||
|
||||
# Значок самой программы: отдельно, потому что окну нужен формат ico —
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue