diff --git a/icons/log.png b/icons/log.png
deleted file mode 100644
index 00c89e0..0000000
Binary files a/icons/log.png and /dev/null differ
diff --git a/icons/log.svg b/icons/log.svg
deleted file mode 100644
index 2ee43c8..0000000
--- a/icons/log.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-
diff --git a/icons/ripple.png b/icons/ripple.png
deleted file mode 100644
index de2c28c..0000000
Binary files a/icons/ripple.png and /dev/null differ
diff --git a/icons/ripple.svg b/icons/ripple.svg
deleted file mode 100644
index b1ea32e..0000000
--- a/icons/ripple.svg
+++ /dev/null
@@ -1,9 +0,0 @@
-
diff --git a/icons/snap.png b/icons/snap.png
deleted file mode 100644
index 150c247..0000000
Binary files a/icons/snap.png and /dev/null differ
diff --git a/icons/snap.svg b/icons/snap.svg
deleted file mode 100644
index 1bec0db..0000000
--- a/icons/snap.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
diff --git a/icons_online/attribution.txt b/icons_online/attribution.txt
new file mode 100644
index 0000000..633e7a9
--- /dev/null
+++ b/icons_online/attribution.txt
@@ -0,0 +1,4 @@
+Log panel: Property icon by Icons8
+Ripple gaps: Merge Vertical icon by Icons8
+Magnet: Magnet icon by Icons8
+App icon: Showing Video Frames icon by Icons8
\ No newline at end of file
diff --git a/icons_online/icons8-merge-vertical-64.png b/icons_online/icons8-merge-vertical-64.png
new file mode 100644
index 0000000..d03c248
Binary files /dev/null and b/icons_online/icons8-merge-vertical-64.png differ
diff --git a/icons_online/icons8-property-30.png b/icons_online/icons8-property-30.png
new file mode 100644
index 0000000..f37634c
Binary files /dev/null and b/icons_online/icons8-property-30.png differ
diff --git a/icons_online/icons8-магнит-30.png b/icons_online/icons8-магнит-30.png
new file mode 100644
index 0000000..aae491a
Binary files /dev/null and b/icons_online/icons8-магнит-30.png differ
diff --git a/icons_online/icons8-объединение-по-вертикали-30.png b/icons_online/icons8-объединение-по-вертикали-30.png
new file mode 100644
index 0000000..0bcfe98
Binary files /dev/null and b/icons_online/icons8-объединение-по-вертикали-30.png differ
diff --git a/icons_online/icons8-показ-видеокадров-30.png b/icons_online/icons8-показ-видеокадров-30.png
new file mode 100644
index 0000000..28ea570
Binary files /dev/null and b/icons_online/icons8-показ-видеокадров-30.png differ
diff --git a/src/uAboutForm.pas b/src/uAboutForm.pas
new file mode 100644
index 0000000..7ca3a65
--- /dev/null
+++ b/src/uAboutForm.pas
@@ -0,0 +1,347 @@
+{
+ uAboutForm — окно «О программе».
+
+ Две вкладки. Первая обязательна: значки взяты у Icons8, а их бесплатная
+ лицензия требует указать авторство со ссылкой на каждый значок. Заодно
+ там же перечислено всё, на чём программа держится, и под какой лицензией
+ оно раздаётся — под Windows и под Linux наборы разные.
+
+ Вторая вкладка отвечает на вопрос «что эта штука умеет», который иначе
+ задают каждому новому человеку голосом.
+
+ Вкладки свои, а не TPageControl: системные вкладки рисует система, и под
+ Windows они остались бы светлой полосой поверх тёмного окна — ровно то,
+ из-за чего здесь уже переписаны кнопки, полосы прокрутки, журнал и меню.
+ Две кнопки-переключателя и две панели делают то же самое и выглядят как
+ остальная программа.
+
+ Текст показывается нашим журналом: он умеет прокрутку, выделение и
+ копирование в буфер — ссылку из списка можно взять и открыть в браузере.
+}
+unit uAboutForm;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+ Classes, SysUtils, Controls, Forms, Graphics, StdCtrls, Dialogs,
+ uTheme, uLogView;
+
+{ Показывает окно. Возвращается, когда его закрыли. }
+procedure ShowAboutForm;
+
+implementation
+
+{ Перенос по словам.
+
+ Журнал строки не переносит: он показывает сообщения программы, где
+ перенос сбил бы выравнивание. Здесь же текст сплошной, и без переноса
+ абзац уезжает за правый край.
+
+ Ссылки не трогаются вовсе: разорванную пополам ссылку не собрать
+ обратно. Отступ строки переносится на её продолжения — иначе список
+ рассыпается.
+
+ Ширина считается в знаках, а шрифт у нас пропорциональный, поэтому
+ строка выходит короче обещанного. Это лучше, чем длиннее: за правый край
+ не уедет ничего. }
+function Wrap(const AText: string; AWidth: Integer): string;
+var
+ Src: TStringList;
+ I, Space, Indent: Integer;
+ Line, Rest: string;
+begin
+ Result := '';
+ Src := TStringList.Create;
+ try
+ Src.Text := AText;
+ for I := 0 to Src.Count - 1 do
+ begin
+ Rest := Src[I];
+
+ if (Rest = '') or (Length(Rest) <= AWidth) or (Pos('http', Rest) > 0) then
+ begin
+ Result := Result + Rest + LineEnding;
+ Continue;
+ end;
+
+ { Отступ запоминается и повторяется на всех продолжениях. }
+ Indent := 0;
+ while (Indent < Length(Rest)) and (Rest[Indent + 1] = ' ') do
+ Inc(Indent);
+
+ while Length(Rest) > AWidth do
+ begin
+ { Ищем последний пробел в пределах ширины; не нашли — рвём по
+ ширине, иначе одно длинное слово повесило бы разбор. }
+ Space := AWidth;
+ while (Space > Indent + 1) and (Rest[Space] <> ' ') do
+ Dec(Space);
+ if Space <= Indent + 1 then
+ Space := AWidth;
+ Line := Copy(Rest, 1, Space);
+ Rest := StringOfChar(' ', Indent) + Copy(Rest, Space + 1, Length(Rest));
+ Result := Result + TrimRight(Line) + LineEnding;
+ end;
+ Result := Result + Rest + LineEnding;
+ end;
+ finally
+ Src.Free;
+ end;
+end;
+
+type
+ { Обработчики живут в объекте: событиям нужны методы. }
+ TAboutDialog = class(TComponent)
+ public
+ Form: TForm;
+ BtnCredits: TThemedButton;
+ BtnFeatures: TThemedButton;
+ ViewCredits: TLogView;
+ ViewFeatures: TLogView;
+ procedure ShowCredits(Sender: TObject);
+ procedure ShowFeatures(Sender: TObject);
+ procedure Select(ACredits: Boolean);
+ end;
+
+procedure TAboutDialog.Select(ACredits: Boolean);
+begin
+ BtnCredits.Down := ACredits;
+ BtnFeatures.Down := not ACredits;
+ ViewCredits.Visible := ACredits;
+ ViewFeatures.Visible := not ACredits;
+end;
+
+procedure TAboutDialog.ShowCredits(Sender: TObject);
+begin
+ Select(True);
+end;
+
+procedure TAboutDialog.ShowFeatures(Sender: TObject);
+begin
+ Select(False);
+end;
+
+{ Кто это написал и на чём оно держится.
+
+ Списки разные по системам не для красоты: под Linux ffmpeg и libmpv
+ ставит пакетный менеджер, под Windows их скачивает сама программа из
+ окна «Зависимости», и это разные сборки с разными лицензиями. Указывать
+ надо то, что стоит у человека, а не то, что бывает вообще. }
+function CreditsText: string;
+begin
+ Result :=
+ 'nlve — монтаж без перекодирования' + LineEnding +
+ LineEnding +
+ 'Автор: 4aiman' + LineEnding +
+ LineEnding +
+ 'Замысел в одном: куски видео вырезаются и склеиваются копированием ' +
+ 'потока, без пересчёта картинки. То, что обычный монтажный редактор ' +
+ 'считает часами, здесь собирается за время копирования файла. ' +
+ 'Пересобирается только то, что иначе никак: переходы, прозрачность, ' +
+ 'точные резы и куски файлов с чужими параметрами.' + LineEnding +
+ LineEnding +
+ '── Значки ──' + LineEnding +
+ LineEnding +
+ 'Значки взяты у Icons8 и требуют указания авторства.' + LineEnding +
+ LineEnding +
+ 'Прилипание — значок «Magnet» от Icons8:' + LineEnding +
+ ' https://icons8.com/icon/79942/magnet' + LineEnding +
+ 'Схлопывание — значок «Merge Vertical» от Icons8:' + LineEnding +
+ ' https://icons8.com/icon/2EQegp8tbR6Q/merge-vertical' + LineEnding +
+ 'Журнал — значок «Property» от Icons8:' + LineEnding +
+ ' https://icons8.com/icon/79872/property' + LineEnding +
+ 'Значок программы — «Showing Video Frames» от Icons8:' + LineEnding +
+ ' https://icons8.com/icon/69093/showing-video-frames' + LineEnding +
+ LineEnding +
+ 'Сам Icons8: https://icons8.com' + LineEnding +
+ LineEnding +
+ '── Чем режется и что показывает ──' + LineEnding +
+ LineEnding +
+ {$IFDEF WINDOWS}
+ 'ffmpeg и ffprobe — вся работа с видео и звуком: нарезка, склейка, ' +
+ 'перекодирование, разбор файлов.' + LineEnding +
+ ' Сборка BtbN, вариант gpl:' + LineEnding +
+ ' https://github.com/BtbN/FFmpeg-Builds' + LineEnding +
+ ' Лицензия сборки: GPL-3.0' + LineEnding +
+ ' Сам ffmpeg: https://ffmpeg.org' + LineEnding +
+ LineEnding +
+ 'libmpv — предпросмотр монтажа. Играет виртуальный список кусков, ' +
+ 'не собирая при этом ни одного файла.' + LineEnding +
+ ' Сборка shinchiro/mpv-winbuild-cmake:' + LineEnding +
+ ' https://github.com/shinchiro/mpv-winbuild-cmake' + LineEnding +
+ ' Лицензия сборки: GPL' + LineEnding +
+ ' Сам mpv: https://mpv.io' + LineEnding +
+ LineEnding +
+ '7-Zip (7zr.exe) — распаковка сборок libmpv, они раздаются в .7z.' +
+ LineEnding +
+ ' https://www.7-zip.org' + LineEnding +
+ ' Лицензия: LGPL-2.1' + LineEnding +
+ {$ELSE}
+ 'ffmpeg и ffprobe — вся работа с видео и звуком: нарезка, склейка, ' +
+ 'перекодирование, разбор файлов.' + LineEnding +
+ ' Ставится пакетным менеджером: sudo apt install ffmpeg' + LineEnding +
+ ' https://ffmpeg.org' + LineEnding +
+ ' Лицензия сборок в Ubuntu: GPL-2 и новее' + LineEnding +
+ LineEnding +
+ 'libmpv — предпросмотр монтажа. Играет виртуальный список кусков, ' +
+ 'не собирая при этом ни одного файла.' + LineEnding +
+ ' Ставится пакетным менеджером: sudo apt install mpv' + LineEnding +
+ ' https://mpv.io' + LineEnding +
+ ' Лицензия сборок в Ubuntu: GPL-3 и новее' + LineEnding +
+ {$ENDIF}
+ LineEnding +
+ '── На чём написано ──' + LineEnding +
+ LineEnding +
+ 'Free Pascal — компилятор.' + LineEnding +
+ ' https://www.freepascal.org' + LineEnding +
+ ' Лицензия: GPL с оговоркой о компоновке для библиотеки времени ' +
+ 'выполнения' + LineEnding +
+ LineEnding +
+ 'Lazarus LCL — библиотека окон и элементов управления.' + LineEnding +
+ ' https://www.lazarus-ide.org' + LineEnding +
+ ' Лицензия: LGPL-2 с оговоркой о компоновке' + LineEnding +
+ LineEnding +
+ 'Ни ffmpeg, ни libmpv в состав программы не входят и вместе с ней не ' +
+ 'раздаются: ffmpeg запускается отдельной программой, libmpv ' +
+ 'подключается на ходу и ставится отдельно.';
+end;
+
+{ Что программа умеет. Не список кнопок, а список того, ради чего её
+ открывают. }
+function FeaturesText: string;
+begin
+ Result :=
+ 'Монтаж' + LineEnding +
+ LineEnding +
+ ' Дорожки для картинки и для звука, сколько нужно. При перекрытии ' +
+ 'картинку даёт верхняя дорожка целиком, а звук всех дорожек ' +
+ 'смешивается: под голос кладётся музыка.' + LineEnding +
+ ' Клипы переносятся, растягиваются за края, режутся по курсору ' +
+ '(S) и удаляются. Прилипание к краям соседей и к курсору; Shift во ' +
+ 'время перетаскивания отменяет его.' + LineEnding +
+ ' Связки: видео и его звук ходят вместе, Ctrl+G связывает и ' +
+ 'развязывает. Развязанный звук двигается отдельно от своей картинки.' +
+ LineEnding +
+ ' Схлопывание дыр: вручную (Ctrl+Shift+R) или само при ' +
+ 'перетаскивании, на текущей дорожке или на всех сразу.' + LineEnding +
+ ' Отмена и повтор любого действия.' + LineEnding +
+ LineEnding +
+ 'Что видно на дорожках' + LineEnding +
+ LineEnding +
+ ' Форма звука на звуковых клипах; Shift со стрелками вверх и вниз ' +
+ 'усиливает её, когда запись тихая.' + LineEnding +
+ ' Громкость клипа — ручкой прямо на нём, прозрачность — так же на ' +
+ 'видеоклипе.' + LineEnding +
+ ' Появление и уход — за верхние углы клипа. Перекрытие двух клипов ' +
+ 'одной дорожки помечено косым крестом: там сам собой выходит переход.' +
+ LineEnding +
+ ' Цвет краёв показывает, притянут рез к опорному кадру или потребует ' +
+ 'перекодирования.' + LineEnding +
+ LineEnding +
+ 'Предпросмотр' + LineEnding +
+ LineEnding +
+ ' Играет монтаж целиком, не собирая ни одного файла: плееру ' +
+ 'скармливается виртуальный список кусков.' + LineEnding +
+ ' Показывает кадр проекта, а не кадр очередного исходника — что ' +
+ 'видно здесь, то и получится в ролике.' + LineEnding +
+ ' Участки с прозрачностью и переходами заранее сводятся в отдельные ' +
+ 'файлы, поэтому видны и они.' + LineEnding +
+ LineEnding +
+ 'Кадрирование' + LineEnding +
+ LineEnding +
+ ' У каждого клипа своё: вписать целиком, заполнить кадр с обрезкой ' +
+ 'или растянуть. Плюс увеличение, смещение, поворот и отражение.' +
+ LineEnding +
+ ' Рамку в окне кадрирования можно тянуть мышью, а не только задавать ' +
+ 'числами.' + LineEnding +
+ LineEnding +
+ 'Сборка ролика' + LineEnding +
+ LineEnding +
+ ' Обычная — копированием потоков. Час материала собирается за время ' +
+ 'копирования файла.' + LineEnding +
+ ' Перед сборкой программа говорит, сколько секунд придётся ' +
+ 'пересобрать и почему.' + LineEnding +
+ ' Точный рез перекодирует только огрызок до ближайшего опорного ' +
+ 'кадра, остальное копируется.' + LineEnding +
+ ' Отдельно — сборка с перекодированием: размер кадра от 720p до 4K, ' +
+ 'поток в килобитах, кодек HEVC или H.264. Считает видеокарта.' +
+ LineEnding +
+ ' Перепаковка готовых файлов в другой контейнер, без пересчёта.' +
+ LineEnding +
+ LineEnding +
+ 'Мелочи, которые важны' + LineEnding +
+ LineEnding +
+ ' Индекс опорных кадров читается из заголовка файла, а не ' +
+ 'перебором: на шестигигабайтном файле это двадцать миллисекунд ' +
+ 'вместо двенадцати секунд.' + LineEnding +
+ ' Фоновая работа видна внизу полоской и отмечена на монтаже ' +
+ 'штриховкой.' + LineEnding +
+ ' Все окна в своих цветах, а не в системных.' + LineEnding +
+ ' Настройки, недавние проекты и размеры окна переживают перезапуск.';
+end;
+
+procedure ShowAboutForm;
+var
+ Dlg: TAboutDialog;
+ BtnClose: TThemedButton;
+begin
+ Dlg := TAboutDialog.Create(nil);
+ try
+ Dlg.Form := TForm.CreateNew(nil);
+ Dlg.Form.Caption := 'О программе';
+ Dlg.Form.Position := poScreenCenter;
+ Dlg.Form.ClientWidth := 720;
+ Dlg.Form.ClientHeight := 560;
+ Dlg.Form.Constraints.MinWidth := 480;
+ Dlg.Form.Constraints.MinHeight := 320;
+
+ Dlg.BtnCredits := TThemedButton.Create(Dlg.Form);
+ Dlg.BtnCredits.Parent := Dlg.Form;
+ Dlg.BtnCredits.SetBounds(12, 12, 240, 28);
+ Dlg.BtnCredits.Caption := 'Библиотеки и авторство';
+ Dlg.BtnCredits.TabStop := False;
+ Dlg.BtnCredits.OnClick := @Dlg.ShowCredits;
+
+ Dlg.BtnFeatures := TThemedButton.Create(Dlg.Form);
+ Dlg.BtnFeatures.Parent := Dlg.Form;
+ Dlg.BtnFeatures.SetBounds(258, 12, 180, 28);
+ Dlg.BtnFeatures.Caption := 'Что умеет';
+ Dlg.BtnFeatures.TabStop := False;
+ Dlg.BtnFeatures.OnClick := @Dlg.ShowFeatures;
+
+ Dlg.ViewCredits := TLogView.Create(Dlg.Form);
+ Dlg.ViewCredits.Parent := Dlg.Form;
+ Dlg.ViewCredits.SetBounds(12, 48, 696, 460);
+ Dlg.ViewCredits.Anchors := [akTop, akLeft, akRight, akBottom];
+ { Ширина подобрана замером: при 720 точках окна в строку входит около
+ ста шестидесяти знаков, берём с запасом. }
+ Dlg.ViewCredits.SetText(Wrap(CreditsText, 150));
+
+ Dlg.ViewFeatures := TLogView.Create(Dlg.Form);
+ Dlg.ViewFeatures.Parent := Dlg.Form;
+ Dlg.ViewFeatures.SetBounds(12, 48, 696, 460);
+ Dlg.ViewFeatures.Anchors := [akTop, akLeft, akRight, akBottom];
+ Dlg.ViewFeatures.SetText(Wrap(FeaturesText, 150));
+
+ BtnClose := TThemedButton.Create(Dlg.Form);
+ BtnClose.Parent := Dlg.Form;
+ BtnClose.SetBounds(608, 520, 100, 28);
+ BtnClose.Anchors := [akRight, akBottom];
+ BtnClose.Caption := 'Закрыть';
+ BtnClose.ModalResult := mrOk;
+ BtnClose.Default := True;
+ BtnClose.Cancel := True;
+
+ ApplyDarkTheme(Dlg.Form);
+ { Значок окна берётся у главного окна: своего у диалога нет. }
+ Dlg.Select(True);
+ Dlg.Form.ShowModal;
+ finally
+ Dlg.Form.Free;
+ Dlg.Free;
+ end;
+end;
+
+end.
diff --git a/src/uIconData.pas b/src/uIconData.pas
new file mode 100644
index 0000000..881bffe
--- /dev/null
+++ b/src/uIconData.pas
@@ -0,0 +1,398 @@
+{
+ uIconData — значки, вшитые в программу.
+
+ Собран из файлов в icons_online командой:
+
+ python3 tools/make_icons.py
+
+ Правится не он, а исходные картинки: этот файл переписывается целиком.
+
+ Значки взяты у Icons8 и требуют указания авторства — оно есть в окне
+ «О программе», вкладка «Библиотеки и авторство». Ссылки на каждый значок
+ лежат в icons_online/attribution.txt.
+
+ Картинки чёрные на прозрачном фоне. Цвет им задаётся при отрисовке:
+ берётся прозрачность точки, а цвет — тот, каким сейчас рисуется кнопка.
+ Поэтому один и тот же значок годится и для тёмной кнопки, и для
+ оранжевой.
+}
+unit uIconData;
+
+{$mode objfpc}{$H+}
+
+interface
+
+const
+ { icons8-магнит-30.png, 24 на 24 точек }
+ IconSnapData: array[0..472] 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, $08, $16, $07, $2D, $16, $EF,
+ $B8, $28, $FD, $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,
+ $AD, $14, $AA, $A0, $B6, $E6, $39, $24, $17, $AE, $69, $32, $88, $D4, $77, $4B,
+ $EE, $DD, $EF, $7F, $EF, $DD, $FB, $07, $66, $1D, $6A, $6A, $EF, $B2, $4D, $85,
+ $22, $F3, $74, $B9, $E3, $9A, $1E, $92, $2D, $5F, $E0, $80, $0B, $9E, $78, $27,
+ $40, $F8, $E4, $85, $4B, $0E, $71, $B3, $F0, $35, $4E, $78, $45, $12, $6B, $40,
+ $83, $62, $1A, $BE, $44, $83, $AF, $29, $5C, $10, $46, $9C, $B2, $9C, $C4, $1D,
+ $EA, $0C, $53, $71, $41, $78, $A3, $8E, $33, $29, $28, $E1, $67, $E2, $82, $E0,
+ $53, $0A, $41, $2B, $9A, $CD, $1E, $65, $43, $3E, $C0, $C7, $67, $60, $64, $CA,
+ $EC, $86, $13, $D5, $82, $2A, $B9, $F8, $B0, $C7, $11, $35, $6A, $1C, $D3, $8F,
+ $73, $39, $AA, $A6, $05, $16, $9E, $51, $BE, $49, $01, $80, $02, $4D, $23, $EB,
+ $85, $97, $EB, $0A, $E6, $AC, $3B, $D1, $CD, $7D, $3A, $46, $D6, $35, $5B, $9A,
+ $74, $5C, $52, $BE, $62, $C2, $E2, $97, $31, $7B, $81, $F6, $4F, $F1, $41, $1B,
+ $61, $8E, $AD, $04, $71, $C3, $08, $C5, $86, $7E, $83, $16, $D8, $DC, $B3, $C3,
+ $37, $AB, $3C, $18, $03, $50, $0C, $D9, $A7, $8B, $4D, $0B, $FB, $8F, $2D, $8D,
+ $D9, $A4, $85, $E0, $90, $27, $88, $4F, $03, $F2, $78, $8C, $51, $54, $B8, $D5,
+ $AD, $84, $A5, $D7, $59, $C4, $02, $84, $47, $CE, $68, $47, $82, $02, $2B, $11,
+ $F1, $CC, $39, $57, $88, $B6, $43, $A1, $8C, $CE, $83, $D8, $30, $65, $B4, $1C,
+ $FE, $20, $FF, $10, $3F, $90, $74, $7D, $D8, $DF, $AB, $6B, $DD, $00, $00, $00,
+ $00, $49, $45, $4E, $44, $AE, $42, $60, $82
+ );
+
+ { icons8-merge-vertical-64.png, 24 на 24 точек }
+ IconRippleData: array[0..457] 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, $08, $16, $07, $2D, $16, $EF,
+ $B8, $28, $FD, $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,
+ $24, $35, $F4, $B4, $8F, $A1, $8A, $7A, $5A, $D2, $45, $FC, $C7, $73, $DF, $D7,
+ $7D, $AE, $EB, $3E, $87, $2E, $89, $52, $27, $3D, $7A, $50, $43, $2F, $EA, $EA,
+ $BF, $8D, $28, $3A, $B6, $A9, $5F, $BF, $4D, $C7, $8A, $DF, $CB, $71, $4A, $90,
+ $33, $27, $2B, $C6, $AC, $49, $BB, $69, $03, $5D, $F2, $77, $82, $A8, $CD, $BE,
+ $BE, $F2, $51, $6F, $0A, $46, $2D, $E8, $EB, $D8, $1E, $2B, $1A, $69, $15, $0C,
+ $DB, $B6, $66, $A8, $6D, $7B, $D6, $AA, $1D, $F9, $A6, $B6, $60, $40, $50, $90,
+ $55, $36, $A3, $FC, $CD, $5A, $64, $C2, $BA, $79, $91, $82, $07, $B1, $67, $F6,
+ $54, $BC, $78, $95, $48, $24, $AE, $1D, $49, $9C, $C9, $C8, $38, $93, $38, $74,
+ $F5, $5E, $79, $F5, $A2, $62, $2F, $16, $54, $D5, $BE, $CC, $4C, $3A, $E4, $08,
+ $AA, $EA, $02, $79, $E3, $C6, $94, $D4, $04, $07, $26, $2D, $B7, $DC, $B0, $6C,
+ $C2, $BE, $A0, $A6, $64, $CC, $B8, $7C, $EC, $E6, $3D, $F4, $93, $2D, $1B, $1E,
+ $4D, $B7, $CC, $4D, $9C, $5B, $B2, $A2, $E4, $C6, $45, $23, $74, $83, $5B, $8B,
+ $4E, $54, $DB, $9A, $79, $B2, $EE, $D4, $5D, $73, $4B, $0D, $2E, $5D, $76, $74,
+ $4F, $70, $F4, $F9, $24, $3F, $47, $4D, $AD, $E2, $1F, $FF, $D6, $4F, $AA, $2A,
+ $EE, $05, $C1, $BD, $4A, $87, $BD, $B5, $30, $68, $4A, $4E, $24, $92, $33, $65,
+ $B0, $5B, $07, $29, $DE, $00, $F6, $77, $56, $B3, $E2, $94, $4B, $80, $00, $00,
+ $00, $00, $49, $45, $4E, $44, $AE, $42, $60, $82
+ );
+
+ { icons8-property-30.png, 24 на 24 точек }
+ IconLogData: array[0..425] 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, $08, $16, $07, $2D, $16, $EF,
+ $B8, $28, $FD, $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,
+ $3A, $28, $AD, $F7, $18, $AA, $F5, $36, $DA, $B7, $25, $3C, $67, $38, $27, $4F,
+ $9E, $EF, $79, $FE, $9C, $F3, $E3, $BF, $2D, $42, $24, $12, $8D, $10, $1B, $04,
+ $21, $8B, $39, $5B, $96, $65, $84, $D4, $8B, $63, $E7, $F6, $3C, $42, $51, $A3,
+ $4D, $0F, $59, $2F, $36, $C8, $60, $C6, $C4, $48, $E5, $4F, $CA, $B5, $81, $1F,
+ $59, $F6, $73, $7F, $D6, $1C, $12, $39, $2E, $97, $04, $8A, $2E, $BA, $E7, $7E,
+ $D6, $B2, $E4, $38, $09, $D4, $3C, $0D, $C9, $50, $EB, $2D, $09, $F2, $0A, $0E,
+ $B0, $69, $36, $E1, $6D, $3A, $72, $F2, $BD, $07, $98, $B7, $EA, $50, $B0, $66,
+ $B1, $FB, $26, $91, $86, $DB, $41, $C0, $A9, $92, $86, $60, $A5, $67, $76, $C1,
+ $5B, $BF, $29, $B5, $33, $E4, $95, $50, $90, $4B, $64, $68, $B9, $74, $DD, $1F,
+ $C8, $DB, $76, $23, $D8, $B5, $90, $F0, $BE, $DA, $19, $04, $9C, $B9, $73, $8F,
+ $75, $53, $09, $6F, $EC, $61, $50, $49, $55, $55, $70, $95, $36, $DD, $5F, $7F,
+ $8D, $08, $63, $29, $71, $EF, $1D, $C5, $74, $80, $7D, $F5, $54, $11, $05, $D3,
+ $5F, $40, $45, $59, $3C, $44, $73, $91, $BA, $B2, $4A, $47, $A2, $A3, $76, $12,
+ $A7, $AA, $F2, $8F, $EC, $03, $0F, $C8, $51, $5A, $EC, $EA, $63, $7C, $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,
+ $00, $00, $16, $00, $00, $00, $28, $00, $00, $00, $20, $00, $00, $00, $40, $00,
+ $00, $00, $01, $00, $20, $00, $00, $00, $00, $00, $00, $10, $00, $00, $13, $0B,
+ $00, $00, $13, $0B, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $37, $00, $00, $00, $A1, $00, $00, $00, $BB, $00, $00,
+ $00, $BA, $00, $00, $00, $BA, $00, $00, $00, $BA, $00, $00, $00, $BA, $00, $00,
+ $00, $BA, $00, $00, $00, $BA, $00, $00, $00, $BA, $00, $00, $00, $BA, $00, $00,
+ $00, $BB, $00, $00, $00, $A1, $00, $00, $00, $37, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $51, $00, $00, $00, $E5, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $E5, $00, $00, $00, $51, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $1E, $00, $00, $00, $58, $00, $00, $00, $66, $00, $00,
+ $00, $66, $00, $00, $00, $66, $00, $00, $00, $66, $00, $00, $00, $66, $00, $00,
+ $00, $66, $00, $00, $00, $66, $00, $00, $00, $66, $00, $00, $00, $66, $00, $00,
+ $00, $66, $00, $00, $00, $58, $00, $00, $00, $1E, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $15, $00, $00,
+ $00, $54, $00, $00, $00, $74, $00, $00, $00, $77, $00, $00, $00, $77, $00, $00,
+ $00, $77, $00, $00, $00, $77, $00, $00, $00, $77, $00, $00, $00, $77, $00, $00,
+ $00, $77, $00, $00, $00, $77, $00, $00, $00, $77, $00, $00, $00, $77, $00, $00,
+ $00, $77, $00, $00, $00, $77, $00, $00, $00, $77, $00, $00, $00, $77, $00, $00,
+ $00, $77, $00, $00, $00, $77, $00, $00, $00, $77, $00, $00, $00, $77, $00, $00,
+ $00, $77, $00, $00, $00, $77, $00, $00, $00, $74, $00, $00, $00, $54, $00, $00,
+ $00, $15, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $69, $00, $00,
+ $00, $DB, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $DA, $00, $00,
+ $00, $68, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $BE, $00, $00,
+ $00, $FF, $00, $00, $00, $C7, $00, $00, $00, $A4, $00, $00, $00, $D7, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $D7, $00, $00, $00, $A4, $00, $00, $00, $C7, $00, $00, $00, $FF, $00, $00,
+ $00, $BD, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $C9, $00, $00,
+ $00, $FF, $00, $00, $00, $55, $00, $00, $00, $00, $00, $00, $00, $88, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $88, $00, $00, $00, $00, $00, $00, $00, $55, $00, $00, $00, $FF, $00, $00,
+ $00, $C9, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $C9, $00, $00,
+ $00, $FF, $00, $00, $00, $79, $00, $00, $00, $29, $00, $00, $00, $A1, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $A1, $00, $00, $00, $29, $00, $00, $00, $79, $00, $00, $00, $FF, $00, $00,
+ $00, $C9, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $C9, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $C9, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $C9, $00, $00,
+ $00, $FF, $00, $00, $00, $EC, $00, $00, $00, $E1, $00, $00, $00, $F2, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $F2, $00, $00, $00, $E1, $00, $00, $00, $EC, $00, $00, $00, $FF, $00, $00,
+ $00, $C9, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $C9, $00, $00,
+ $00, $FF, $00, $00, $00, $5C, $00, $00, $00, $00, $00, $00, $00, $8D, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $8D, $00, $00, $00, $00, $00, $00, $00, $5C, $00, $00, $00, $FF, $00, $00,
+ $00, $C9, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $C9, $00, $00,
+ $00, $FF, $00, $00, $00, $5C, $00, $00, $00, $00, $00, $00, $00, $8D, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $8D, $00, $00, $00, $00, $00, $00, $00, $5C, $00, $00, $00, $FF, $00, $00,
+ $00, $C9, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $C9, $00, $00,
+ $00, $FF, $00, $00, $00, $EC, $00, $00, $00, $E1, $00, $00, $00, $F2, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $F2, $00, $00, $00, $E1, $00, $00, $00, $EC, $00, $00, $00, $FF, $00, $00,
+ $00, $C9, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $C9, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $C9, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $C9, $00, $00,
+ $00, $FF, $00, $00, $00, $79, $00, $00, $00, $29, $00, $00, $00, $A1, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $A1, $00, $00, $00, $29, $00, $00, $00, $79, $00, $00, $00, $FF, $00, $00,
+ $00, $C9, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $C9, $00, $00,
+ $00, $FF, $00, $00, $00, $55, $00, $00, $00, $00, $00, $00, $00, $88, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $88, $00, $00, $00, $00, $00, $00, $00, $55, $00, $00, $00, $FF, $00, $00,
+ $00, $C9, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $C9, $00, $00,
+ $00, $FF, $00, $00, $00, $C6, $00, $00, $00, $A4, $00, $00, $00, $D7, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $D7, $00, $00, $00, $A4, $00, $00, $00, $C6, $00, $00, $00, $FF, $00, $00,
+ $00, $C9, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $C9, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $C9, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $C9, $00, $00,
+ $00, $FF, $00, $00, $00, $A4, $00, $00, $00, $6E, $00, $00, $00, $C0, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $C0, $00, $00, $00, $6E, $00, $00, $00, $A4, $00, $00, $00, $FF, $00, $00,
+ $00, $C9, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $C9, $00, $00,
+ $00, $FF, $00, $00, $00, $55, $00, $00, $00, $00, $00, $00, $00, $88, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $88, $00, $00, $00, $00, $00, $00, $00, $55, $00, $00, $00, $FF, $00, $00,
+ $00, $C9, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $C3, $00, $00,
+ $00, $FF, $00, $00, $00, $99, $00, $00, $00, $5C, $00, $00, $00, $B8, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $B8, $00, $00, $00, $5C, $00, $00, $00, $99, $00, $00, $00, $FF, $00, $00,
+ $00, $C3, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $8A, $00, $00,
+ $00, $EA, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00,
+ $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $FF, $00, $00, $00, $EA, $00, $00,
+ $00, $89, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $26, $00, $00,
+ $00, $87, $00, $00, $00, $B6, $00, $00, $00, $BA, $00, $00, $00, $BA, $00, $00,
+ $00, $BA, $00, $00, $00, $BA, $00, $00, $00, $BA, $00, $00, $00, $BA, $00, $00,
+ $00, $BA, $00, $00, $00, $BA, $00, $00, $00, $BA, $00, $00, $00, $BA, $00, $00,
+ $00, $BA, $00, $00, $00, $BA, $00, $00, $00, $BA, $00, $00, $00, $BA, $00, $00,
+ $00, $BA, $00, $00, $00, $BA, $00, $00, $00, $BA, $00, $00, $00, $BA, $00, $00,
+ $00, $BA, $00, $00, $00, $BA, $00, $00, $00, $B6, $00, $00, $00, $86, $00, $00,
+ $00, $24, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
+ $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $FF, $FF,
+ $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $80,
+ $01, $FF, $FF, $80, $01, $FF, $FF, $80, $01, $FF, $FF, $FF, $FF, $FF, $E0, $00,
+ $00, $07, $E0, $00, $00, $07, $E0, $00, $00, $07, $E2, $00, $00, $47, $E0, $00,
+ $00, $07, $E0, $00, $00, $07, $E0, $00, $00, $07, $E2, $00, $00, $47, $E2, $00,
+ $00, $47, $E0, $00, $00, $07, $E0, $00, $00, $07, $E0, $00, $00, $07, $E2, $00,
+ $00, $47, $E0, $00, $00, $07, $E0, $00, $00, $07, $E0, $00, $00, $07, $E2, $00,
+ $00, $47, $E0, $00, $00, $07, $E0, $00, $00, $07, $E0, $00, $00, $07, $FF, $FF,
+ $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF
+ );
+
+implementation
+
+end.
diff --git a/src/uMainForm.pas b/src/uMainForm.pas
index 834d8d7..376b358 100644
--- a/src/uMainForm.pas
+++ b/src/uMainForm.pas
@@ -25,10 +25,11 @@ uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls,
ComCtrls, Menus, Buttons, LCLType, LMessages,
uAppTypes, uEdl, uMpvLib, uMpvPlayer, uProject, uMediaProbe, uTimelineCtrl,
- uFlatten, uGenerate, uExport, uEncodeForm, uProgressForm, uRemuxForm,
- uMismatchForm,
+ uFlatten, uGenerate, uExport, uEncodeForm, uAboutForm, uProgressForm,
+ uRemuxForm, uMismatchForm,
uFfmpegJob, uSettings, uThumbs, uTransform, uPanCropForm,
- uProjectInfoForm, ImgList, uTheme, uLogView, uDepsForm, uWaveform;
+ uProjectInfoForm, ImgList, uTheme, uLogView, uDepsForm, uWaveform,
+ uIconData;
const
{ Номера системных сообщений о перерисовке рамки окна. Берём числами, а
@@ -207,11 +208,13 @@ type
{ --- окно и меню --- }
procedure BuildMenu;
+ procedure ApplyWindowIcon;
procedure BuildJobIndicator;
procedure JobTimerTick(Sender: TObject);
procedure AdvanceJobBar;
procedure LogButtonClick(Sender: TObject);
procedure MenuToggleLog(Sender: TObject);
+ procedure MenuAbout(Sender: TObject);
procedure ApplyLogVisible(AVisible: Boolean);
procedure BuildTimeline;
procedure StartPlayer;
@@ -566,7 +569,7 @@ procedure TMainForm.BuildMenu;
end;
var
- MFile, MEdit, MView, MProject: TMenuItem;
+ MFile, MEdit, MView, MProject, MHelp: TMenuItem;
begin
{ Подсказки всплывают почти сразу: на кнопке со значком они не
украшение, а единственный способ узнать, что она делает. Держатся
@@ -575,6 +578,8 @@ begin
Application.HintPause := 100;
Application.HintHidePause := 15000;
+ ApplyWindowIcon;
+
BuildJobIndicator;
FMenu := TMainMenu.Create(Self);
@@ -635,6 +640,9 @@ begin
AddItem(MProject, 'Добавить дорожку', @MenuAddTrack);
AddItem(MProject, 'Удалить последнюю дорожку', @MenuRemoveTrack);
+ MHelp := AddTop('Справка');
+ AddItem(MHelp, 'О программе...', @MenuAbout);
+
{ Дважды, и оба раза нужны.
До присваивания — чтобы обработчики отрисовки стояли к первому показу.
@@ -656,6 +664,28 @@ end;
Место справа в нижней полосе отводится сразу и насовсем, а надпись с
полоской показываются только когда работа есть. Так строка состояния и
показатель не наезжают друг на друга. }
+{ Значок окна. Вшит в программу, потому что рядом с ней может ничего не
+ лежать. Значок самого файла программы под Windows задаётся не здесь, а
+ ресурсом при сборке — это отдельная история. }
+procedure TMainForm.ApplyWindowIcon;
+var
+ Stream: TMemoryStream;
+begin
+ Stream := TMemoryStream.Create;
+ try
+ Stream.WriteBuffer(IconAppData[0], Length(IconAppData));
+ Stream.Position := 0;
+ try
+ Icon.LoadFromStream(Stream);
+ Application.Icon.Assign(Icon);
+ except
+ { Со значком не вышло — работать это не мешает. }
+ end;
+ finally
+ Stream.Free;
+ end;
+end;
+
procedure TMainForm.BuildJobIndicator;
const
JobWidth = 330;
@@ -724,6 +754,11 @@ begin
ApplyLogVisible(not memoLog.Visible);
end;
+procedure TMainForm.MenuAbout(Sender: TObject);
+begin
+ ShowAboutForm;
+end;
+
procedure TMainForm.MenuToggleLog(Sender: TObject);
begin
ApplyLogVisible(not memoLog.Visible);
diff --git a/src/uTheme.pas b/src/uTheme.pas
index 23af205..ac94142 100644
--- a/src/uTheme.pas
+++ b/src/uTheme.pas
@@ -31,7 +31,8 @@ interface
uses
Classes, SysUtils, Types, Controls, Graphics, Forms, StdCtrls, ExtCtrls,
- ComCtrls, Grids, Menus, Dialogs, Math, LCLType, LCLIntf, LCLProc;
+ ComCtrls, Grids, Menus, Dialogs, Math, IntfGraphics, FPimage,
+ LCLType, LCLIntf, LCLProc, uIconData;
const
{ Порядок байт в TColor — $00BBGGRR, а не привычный RGB. У серых цветов
@@ -55,10 +56,13 @@ const
type
{ Значок на кнопке.
- Рисуется кодом, а не берётся из файла: программа не должна зависеть
- от того, лежит ли рядом с ней картинка. Рисунки повторяют icons/*.svg
- — там они и придуманы, оттуда их можно перегнать в png для чего
- угодно ещё. }
+ Картинки вшиты в программу (uIconData): зависеть от того, лежит ли
+ рядом с ней файл, она не должна. Исходники — в icons_online, оттуда
+ же требование указать авторство, см. окно «О программе».
+
+ Цвет значку задаётся при отрисовке: берётся прозрачность точки, а
+ цвет — тот, каким сейчас рисуется кнопка. Поэтому один значок годится
+ и для тёмной кнопки, и для оранжевой, и для недоступной. }
TThemedIcon = (
tiNone,
tiSnap, // прилипание: края притягиваются к общей линии
@@ -296,6 +300,8 @@ type
var
Painter: TThemePainter;
+ { Перекрашенные значки: ключ «номер значка : цвет». }
+ IconCache: TStringList;
{$IFDEF WINDOWS}
{ Кисть под полосу меню. Создаётся один раз: Windows держит на неё
ссылку, пока меню живо, и удалять её раньше нельзя. }
@@ -383,96 +389,101 @@ begin
Invalidate;
end;
-{ Значки рисуются по клетке 24 на 24 — той же, что в icons/*.svg, — и
- переносятся в заданный прямоугольник. Так рисунок один и тот же при
- любом размере кнопки, и правится он в одном месте. }
+{ Значок в нужном цвете.
+
+ Картинка чёрная с прозрачностью, поэтому перекрашивается прямо по
+ точкам: прозрачность остаётся своя, цвет ставится наш. Результат
+ запоминается — цветов ровно три (обычный, на оранжевом, недоступный), и
+ пересчитывать их на каждую перерисовку незачем. }
+function ThemedIconImage(AIcon: TThemedIcon; AColor: TColor):
+ TPortableNetworkGraphic;
+var
+ Key: string;
+ Idx, X, Y: Integer;
+ Stream: TMemoryStream;
+ Img: TLazIntfImage;
+ Px: TFPColor;
+ R, G, B: Byte;
+begin
+ Result := nil;
+ if AIcon = tiNone then
+ Exit;
+
+ Key := IntToStr(Ord(AIcon)) + ':' + IntToStr(AColor);
+ if IconCache = nil then
+ begin
+ IconCache := TStringList.Create;
+ IconCache.Sorted := True;
+ IconCache.OwnsObjects := True;
+ end;
+ Idx := IconCache.IndexOf(Key);
+ if Idx >= 0 then
+ Exit(TPortableNetworkGraphic(IconCache.Objects[Idx]));
+
+ Stream := TMemoryStream.Create;
+ try
+ case AIcon of
+ tiSnap: Stream.WriteBuffer(IconSnapData[0], Length(IconSnapData));
+ tiRipple: Stream.WriteBuffer(IconRippleData[0], Length(IconRippleData));
+ tiLog: Stream.WriteBuffer(IconLogData[0], Length(IconLogData));
+ else
+ Exit;
+ end;
+ Stream.Position := 0;
+
+ Result := TPortableNetworkGraphic.Create;
+ try
+ Result.LoadFromStream(Stream);
+ { Порядок байт в TColor обратный привычному. }
+ R := AColor and $FF;
+ G := (AColor shr 8) and $FF;
+ B := (AColor shr 16) and $FF;
+
+ Img := Result.CreateIntfImage;
+ try
+ for Y := 0 to Img.Height - 1 do
+ for X := 0 to Img.Width - 1 do
+ begin
+ Px := Img.Colors[X, Y];
+ { Прозрачное трогать незачем, а видимое красим целиком. }
+ if Px.Alpha = 0 then
+ Continue;
+ Px.Red := R * 257;
+ Px.Green := G * 257;
+ Px.Blue := B * 257;
+ Img.Colors[X, Y] := Px;
+ end;
+ Result.LoadFromIntfImage(Img);
+ finally
+ Img.Free;
+ end;
+ except
+ FreeAndNil(Result);
+ end;
+ finally
+ Stream.Free;
+ end;
+
+ { Неудачу тоже запоминаем: иначе разбирать картинку заново пришлось бы
+ на каждой перерисовке. }
+ IconCache.AddObject(Key, Result);
+end;
+
+{ Значок рисуется без растягивания: картинка вшита ровно того размера, под
+ который сделана кнопка. Если кнопка оказалась крупнее — значок остаётся
+ своего размера и стоит по центру, а не расплывается. }
procedure TThemedButton.DrawIcon(const ARect: TRect; AColor: TColor);
var
- Side, X0, Y0: Integer;
-
- function PX(AValue: Integer): Integer;
- begin
- Result := X0 + Round(AValue * Side / 24);
- end;
-
- function PY(AValue: Integer): Integer;
- begin
- Result := Y0 + Round(AValue * Side / 24);
- end;
-
- procedure Line(X1, Y1, X2, Y2: Integer);
- begin
- Canvas.Line(PX(X1), PY(Y1), PX(X2), PY(Y2));
- end;
-
- procedure Box(X1, Y1, X2, Y2: Integer; AFilled: Boolean);
- begin
- if AFilled then
- Canvas.Brush.Style := bsSolid
- else
- Canvas.Brush.Style := bsClear;
- Canvas.Rectangle(PX(X1), PY(Y1), PX(X2), PY(Y2));
- end;
-
- { Треугольник остриём влево или вправо. Не Arrow: так называется
- свойство самой кнопки. }
- procedure Tri(ATipX, ABaseX, ACenterY, AHalf: Integer);
- begin
- Canvas.Brush.Style := bsSolid;
- Canvas.Polygon([Types.Point(PX(ATipX), PY(ACenterY)),
- Types.Point(PX(ABaseX), PY(ACenterY - AHalf)),
- Types.Point(PX(ABaseX), PY(ACenterY + AHalf))]);
- end;
-
+ Img: TPortableNetworkGraphic;
+ X, Y: Integer;
begin
- if FIcon = tiNone then
+ Img := ThemedIconImage(FIcon, AColor);
+ if (Img = nil) or (Img.Width = 0) then
Exit;
- Side := Min(ARect.Right - ARect.Left, ARect.Bottom - ARect.Top) - 6;
- if Side < 8 then
- Exit;
- X0 := (ARect.Left + ARect.Right - Side) div 2;
- Y0 := (ARect.Top + ARect.Bottom - Side) div 2;
-
- Canvas.Pen.Color := AColor;
- Canvas.Pen.Width := Max(1, Side div 12);
- Canvas.Brush.Color := AColor;
-
- case FIcon of
- tiSnap:
- begin
- { Подкова магнита: дуга сверху, две ноги вниз, полюса закрашены.
- Без закрашенных полюсов подкова читается как перевёрнутая
- буква U и смысла не передаёт. }
- Canvas.Pen.Width := Max(2, Side div 6);
- Canvas.Brush.Style := bsClear;
- { Дуга по верхней половине окружности с центром в (12,13). }
- Canvas.Arc(PX(5), PY(6), PX(19), PY(20), 0, 180 * 16);
- Line(5, 13, 5, 19);
- Line(19, 13, 19, 19);
- Canvas.Pen.Width := 1;
- Box(3, 18, 8, 23, True);
- Box(16, 18, 21, 23, True);
- end;
- tiRipple:
- begin
- { Два куска и дыра между ними; стрелка показывает, что правый
- переедет влево. }
- Box(1, 7, 8, 17, True);
- Box(16, 7, 23, 17, True);
- Tri(10, 15, 12, 4);
- end;
- tiLog:
- begin
- Box(2, 3, 22, 21, False);
- Line(6, 8, 18, 8);
- Line(6, 12, 18, 12);
- Line(6, 16, 13, 16);
- end;
- end;
-
- Canvas.Pen.Width := 1;
- Canvas.Brush.Style := bsClear;
+ X := (ARect.Left + ARect.Right - Img.Width) div 2;
+ Y := (ARect.Top + ARect.Bottom - Img.Height) div 2;
+ Canvas.Draw(X, Y, Img);
end;
procedure TThemedButton.Paint;
@@ -1286,5 +1297,6 @@ initialization
finalization
Painter.Free;
+ IconCache.Free;
end.
diff --git a/tools/make_icons.py b/tools/make_icons.py
new file mode 100644
index 0000000..b98f26e
--- /dev/null
+++ b/tools/make_icons.py
@@ -0,0 +1,135 @@
+#!/usr/bin/env python3
+"""
+Готовит модуль с данными значков из файлов в icons_online.
+
+Зачем модулем, а не файлами рядом с программой: программа не должна
+зависеть от того, лежит ли что-то в её каталоге. Значки маленькие, все
+вместе занимают единицы килобайт.
+
+Размер выбран под кнопку: у неё двадцать восемь точек стороны, значку
+достаётся клетка двадцать четыре на двадцать четыре. Уменьшение делает
+convert с обычным сглаживанием, пропорции при этом сохраняются — все
+исходники квадратные, растягивать нечего.
+
+Запуск: python3 tools/make_icons.py
+"""
+
+import subprocess
+import sys
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parent.parent
+SRC = ROOT / 'icons_online'
+OUT = ROOT / 'src' / 'uIconData.pas'
+
+# Имя в программе -> файл исходника и сторона в точках.
+# Для «схлопывания» берётся вариант на шестьдесят четыре точки: чем крупнее
+# исходник, тем чище получается уменьшение.
+ICONS = [
+ ('Snap', 'icons8-магнит-30.png', 24),
+ ('Ripple', 'icons8-merge-vertical-64.png', 24),
+ ('Log', 'icons8-property-30.png', 24),
+]
+
+# Значок самой программы: отдельно, потому что окну нужен формат ico —
+# только его умеет читать TIcon в LCL.
+APP_ICON = ('icons8-показ-видеокадров-30.png', 32)
+
+
+def make_ico(path: Path, side: int) -> bytes:
+ out = subprocess.run(
+ ['convert', str(path), '-background', 'none', '-alpha', 'on',
+ '-resize', '%dx%d' % (side, side), 'ico:-'],
+ check=True, capture_output=True)
+ return out.stdout
+
+
+def scale(path: Path, side: int) -> bytes:
+ out = subprocess.run(
+ ['convert', str(path), '-background', 'none', '-alpha', 'on',
+ '-resize', '%dx%d' % (side, side), 'png:-'],
+ check=True, capture_output=True)
+ return out.stdout
+
+
+def pascal_bytes(data: bytes, indent: str = ' ') -> str:
+ parts = []
+ for i in range(0, len(data), 16):
+ chunk = ', '.join('$%02X' % b for b in data[i:i + 16])
+ parts.append(indent + chunk)
+ return ',\n'.join(parts)
+
+
+def main() -> int:
+ if not SRC.is_dir():
+ print('нет каталога с исходниками:', SRC, file=sys.stderr)
+ return 1
+
+ blocks = []
+ names = []
+ for name, filename, side in ICONS:
+ path = SRC / filename
+ if not path.is_file():
+ print('нет файла:', path, file=sys.stderr)
+ return 1
+ data = scale(path, side)
+ names.append((name, len(data)))
+ blocks.append(
+ ' { %s, %d на %d точек }\n'
+ ' Icon%sData: array[0..%d] of Byte = (\n%s\n );'
+ % (filename, side, side, name, len(data) - 1, pascal_bytes(data)))
+
+ app_src, app_side = APP_ICON
+ app_path = SRC / app_src
+ if not app_path.is_file():
+ print('нет файла:', app_path, file=sys.stderr)
+ return 1
+ app_data = make_ico(app_path, app_side)
+ names.append(('App (ico)', len(app_data)))
+ blocks.append(
+ ' { %s, значок окна, %d на %d точек, формат ico }\n'
+ ' IconAppData: array[0..%d] of Byte = (\n%s\n );'
+ % (app_src, app_side, app_side, len(app_data) - 1,
+ pascal_bytes(app_data)))
+
+ text = '''{
+ uIconData — значки, вшитые в программу.
+
+ Собран из файлов в icons_online командой:
+
+ python3 tools/make_icons.py
+
+ Правится не он, а исходные картинки: этот файл переписывается целиком.
+
+ Значки взяты у Icons8 и требуют указания авторства — оно есть в окне
+ «О программе», вкладка «Библиотеки и авторство». Ссылки на каждый значок
+ лежат в icons_online/attribution.txt.
+
+ Картинки чёрные на прозрачном фоне. Цвет им задаётся при отрисовке:
+ берётся прозрачность точки, а цвет — тот, каким сейчас рисуется кнопка.
+ Поэтому один и тот же значок годится и для тёмной кнопки, и для
+ оранжевой.
+}
+unit uIconData;
+
+{$mode objfpc}{$H+}
+
+interface
+
+const
+%s
+
+implementation
+
+end.
+''' % ('\n\n'.join(blocks))
+
+ OUT.write_text(text, encoding='utf-8')
+ print('записан', OUT)
+ for name, size in names:
+ print(' %-8s %d байт' % (name, size))
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())