From 9d6e5580a1e0f2212d10cb964765e1882c500c47 Mon Sep 17 00:00:00 2001 From: 4aiman Date: Sat, 22 Aug 2026 01:08:58 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9A=D0=B2=D0=B0=D0=B4=D1=80=D0=B0=D1=82?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B8=20?= =?UTF-8?q?=D1=81=D0=BE=20=D0=B7=D0=BD=D0=B0=D1=87=D0=BA=D0=B0=D0=BC=D0=B8?= =?UTF-8?q?,=20=D0=B6=D1=83=D1=80=D0=BD=D0=B0=D0=BB=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=B0=D0=BB=20=D1=83=D0=B5=D0=B7=D0=B6=D0=B0?= =?UTF-8?q?=D1=82=D1=8C=20=D0=B8=D0=B7-=D0=BF=D0=BE=D0=B4=20=D0=BA=D1=83?= =?UTF-8?q?=D1=80=D1=81=D0=BE=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Двойной щелчок по кнопке журнала. Причина оказалась не в кнопке: журнал лежал ниже полосы состояния. Спрятали журнал — полоса уехала вниз на сто тридцать пикселей, кнопка ушла из-под курсора, и следующий щелчок в то же место попадал уже мимо. Порядок исправлен: полоса состояния теперь самая нижняя, журнал над ней, и при переключении ничего не движется. Прилипание, схлопывание и журнал стали квадратными кнопками со значками и собраны в один набор. Надписи переехали в подсказки — целиком, вместе с состоянием: у значка нет другого способа рассказать о себе. Подсказка всплывает через десятую долю секунды и держится пятнадцать. Значки рисуются кодом, а не берутся из файла: программа не должна зависеть от того, лежит ли рядом с ней картинка. Рисунки придуманы в icons/*.svg — оттуда их можно перегнать в png чем угодно, проверено командой convert. Свойства из файлов форм теперь проверяются отдельной программой по сведениям о типах. Файл формы разбирается при запуске, а не при сборке: опечатка в имени свойства или в значении перечисления даёт не ошибку компиляции, а падение окна на старте. Проверка включена в build.sh. --- .gitignore | 4 ++ build.sh | 16 +++++ icons/log.png | Bin 0 -> 482 bytes icons/log.svg | 11 ++++ icons/ripple.png | Bin 0 -> 589 bytes icons/ripple.svg | 9 +++ icons/snap.png | Bin 0 -> 783 bytes icons/snap.svg | 14 +++++ src/uMainForm.lfm | 16 ++--- src/uMainForm.pas | 55 ++++++++++++----- src/uTheme.pas | 117 +++++++++++++++++++++++++++++++++++- tests/lfmcheck/lfmcheck.lpi | 37 ++++++++++++ tests/lfmcheck/lfmcheck.lpr | 67 +++++++++++++++++++++ 13 files changed, 321 insertions(+), 25 deletions(-) create mode 100644 icons/log.png create mode 100644 icons/log.svg create mode 100644 icons/ripple.png create mode 100644 icons/ripple.svg create mode 100644 icons/snap.png create mode 100644 icons/snap.svg create mode 100644 tests/lfmcheck/lfmcheck.lpi create mode 100644 tests/lfmcheck/lfmcheck.lpr diff --git a/.gitignore b/.gitignore index 77cd272..5cf5375 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,10 @@ shot*.png # Журнал последнего запуска /nlve.log +# Проверка свойств из файлов форм. +/tests/lfmcheck/lfmcheck +/tests/lfmcheck/units/ + # Проверка хода полоски: двоичный файл и промежуточные модули. /tests/progress/progtest /tests/progress/*.o diff --git a/build.sh b/build.sh index 6b2082d..6a03f16 100755 --- a/build.sh +++ b/build.sh @@ -123,6 +123,22 @@ run_tests() { status=1 fi + # Свойства, записанные в файлах форм, проверяются по сведениям о типах. + # Файл формы разбирается при запуске, а не при сборке: опечатка в имени + # свойства даёт не ошибку компиляции, а падение окна на старте. + note "Проверка свойств из файлов форм" + if "$LAZBUILD" --primary-config-path="$LAZCONF" \ + "$HERE/tests/lfmcheck/lfmcheck.lpi" >/dev/null 2>&1; then + if "$HERE/tests/lfmcheck/lfmcheck"; then + : + else + status=1 + fi + else + echo "не собралось" >&2 + status=1 + fi + # Ход полоски выполнения проверяется отдельной программой: расчёт вынесен # в uAppTypes именно затем, чтобы его можно было прогнать числами. note "Проверка хода полоски выполнения" diff --git a/icons/log.png b/icons/log.png new file mode 100644 index 0000000000000000000000000000000000000000..00c89e07da8f3c60ccbf123f56e1358f2ee2e510 GIT binary patch literal 482 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}tg=CK)Uj~LMH3o);76yi2K%s^g z3=E|}g|8AA7_4S6Fo+k-*%fF5lweBoc6VW5Sk?NMQuI#TkM8%l6o_*0OV_;xZ@^ooDvSBFIh2#9Q{n{=SI{<=mVAjh8Z##t zT$9u=y5<)Y_+9?J?T=dKg_fHYf4i;gbaXlN#C4N1)3zlkX`)BMQsgI|ZtT2=O~ z#`#$njyS$Ovp=ZO`0u~NtFLNKIwB)$bN{iHa%gi$i)Egez}kZk_L=Pn*0v3J^2K(| zM$1*Lu6N3v>K7Qlm&|(Ab#=ceFaRY>Tq8 + + + + + + + + diff --git a/icons/ripple.png b/icons/ripple.png new file mode 100644 index 0000000000000000000000000000000000000000..de2c28cd2a2e9b6f111f59d9ca550bc426cd704c GIT binary patch literal 589 zcmV-T0004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0063uBQgL0010qN zS#tmY4#NNd4#NS*Z>VGd000McNliru>IfAU0t=Dz8sGo`0gXvSK~z}7?bXd{)L<0G z@n7b%mC}?>OVv%Mf`}kfs5&4DLKmgiA$nEnHMsHybSW+xaMwlcMv!qnq)f5%5m(*R z&LkOvAm+bJ@|@>=4)4jKprD}OKOjJ;X$|o;@A^&*=I9f(6>NLqvDoCwM`eM}-Z+|L z(Dc&78QM?YiYf@TJeg5oEWGyl4eQ1_R_ADc38^gD+6_-ld@%i^IakPP5D2W;vS(jc zB+lQUZpVRV9*C?YmCt5SQnjwBZQq_vr>glTsA<@BsBK$AkX$^CK`gfHjt#M8n|3{S zPfd{aR7nO?6HTwQ#bQ;VTUpAdNidx|CyA6673P&7P|_1w5}PUu-L{(LSV;yYWhY*H zCsxFPSOo@atfi!;YAluh(C@=kC`$46rwcheXQMCvov-qDPWJTwG9 zMXEEjk-=YB`U`fbprD}O-{Lnj_;g!XKe-D4000?uMObuGZ)S9NVRB^vcXxL#X>MzC bV_|S*E^l&Yo9;Xs00000NkvXXu0mjfLFf77 literal 0 HcmV?d00001 diff --git a/icons/ripple.svg b/icons/ripple.svg new file mode 100644 index 0000000..b1ea32e --- /dev/null +++ b/icons/ripple.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/icons/snap.png b/icons/snap.png new file mode 100644 index 0000000000000000000000000000000000000000..150c24749583b48323eeabbb58ecb3282b9c24ff GIT binary patch literal 783 zcmV+q1MvKbP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0063uBQgL0010qN zS#tmY4#NNd4#NS*Z>VGd000McNliru>IfAU0t=Dz8sGo`0#8XqK~z}7?bu&S6k!y{ z@z3hE{s|p5D5yvyBE858Ybg{W@O5-iy{zgTL@%H}5GX_#re0`8Xhc+q>1>$mYHk-R zf%VKdD_R)-ZqIq1@AI2EGv~~($I8`_8|l`GMeoH~7d6%a=ylso!;YEtCF`PmT@Eg} zhILW)MGbWTcBBKqzX#+I>DjFBx$O0wqAmO7(e0M2c;T*zT>$jBqloA3nXEbJSHx;P z^_}YAf+D_){)e0VSE?vY%_79g!C0+U%4r*H=b+Po0iD5Em!hJsojWLK(`!iCtWQDs zWi6JiiB)z2h+K5tu#X-Eu2{}icwc>~GHj-Q<0U{R- z8g^d0s%kK&s;W)DMzkim2muNreTED=r=5iDZARLhRY6(R(w{CufDV@oxuH*+EQ*}h zq(Q4OpMMXA%Cy;Wr*-RcC=(#B(*- zzM1pZ2P@eCB@^lAeQ- zDHT*)kxO`E<>Zu%d*-oerSL{cfHhyeQE*7Rd8@&kig`0ejCgG!$zfs!lbwcc<;W4YQySoXwF>^)IJtB9qh))yVb!j?f;v60N7UbeCfx=>d8l&ZgLjV8(8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H1 N002ovPDHLkV1m1+T%iB} literal 0 HcmV?d00001 diff --git a/icons/snap.svg b/icons/snap.svg new file mode 100644 index 0000000..51604bc --- /dev/null +++ b/icons/snap.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/src/uMainForm.lfm b/src/uMainForm.lfm index 857c099..2f29898 100644 --- a/src/uMainForm.lfm +++ b/src/uMainForm.lfm @@ -16,7 +16,7 @@ object MainForm: TMainForm object memoLog: TLogView Left = 0 Height = 130 - Top = 770 + Top = 730 Width = 1600 Align = alBottom Font.Height = -11 @@ -27,7 +27,7 @@ object MainForm: TMainForm object pnlBottom: TPanel Left = 0 Height = 40 - Top = 730 + Top = 860 Width = 1600 Align = alBottom BevelOuter = bvNone @@ -56,24 +56,24 @@ object MainForm: TMainForm ShowHint = True end object btnRipple: TThemedButton - Left = 470 + Left = 326 Height = 28 Top = 6 - Width = 280 - Caption = 'Схлопывание: текущая дорожка' + Width = 28 + Icon = tiRipple OnClick = btnRippleClick ShowHint = True end object btnRippleMenu: TThemedButton - Left = 752 + Left = 356 Height = 28 Top = 6 - Width = 26 + Width = 22 Arrow = True OnClick = btnRippleMenuClick end object lblStatus: TLabel - Left = 790 + Left = 428 Height = 20 Top = 10 Width = 840 diff --git a/src/uMainForm.pas b/src/uMainForm.pas index 4489ac8..9aa2e99 100644 --- a/src/uMainForm.pas +++ b/src/uMainForm.pas @@ -568,6 +568,13 @@ procedure TMainForm.BuildMenu; var MFile, MEdit, MView, MProject: TMenuItem; begin + { Подсказки всплывают почти сразу: на кнопке со значком они не + украшение, а единственный способ узнать, что она делает. Держатся + долго — читать там больше, чем одно слово. } + Application.ShowHint := True; + Application.HintPause := 100; + Application.HintHidePause := 15000; + BuildJobIndicator; FMenu := TMainMenu.Create(Self); @@ -650,7 +657,8 @@ end; procedure TMainForm.BuildJobIndicator; const JobWidth = 330; - LogButtonWidth = 100; + LogButtonWidth = 28; + LogButtonLeft = 388; begin FJobLabel := TLabel.Create(Self); FJobLabel.Parent := pnlBottom; @@ -674,19 +682,18 @@ begin рассказала. } FLogButton := TThemedButton.Create(Self); FLogButton.Parent := pnlBottom; - FLogButton.Anchors := [akTop, akRight]; - FLogButton.SetBounds(pnlBottom.ClientWidth - JobWidth - LogButtonWidth - 20, - 6, LogButtonWidth, 28); - FLogButton.Caption := 'Журнал'; + { Рядом с прилипанием и схлопыванием: три переключателя читаются как + один набор, а не как случайно разбросанные значки. } + FLogButton.SetBounds(LogButtonLeft, 6, LogButtonWidth, 28); + FLogButton.Icon := tiLog; FLogButton.Down := True; FLogButton.OnClick := @LogButtonClick; FLogButton.ShowHint := True; - FLogButton.Hint := 'Спрятать или показать нижнюю полосу с сообщениями'; + FLogButton.Hint := 'Журнал: спрятать или показать полосу с сообщениями'; { Строка состояния ужимается, чтобы её длинные сообщения не залезали под показатель и под кнопку. } - lblStatus.Width := pnlBottom.ClientWidth - lblStatus.Left - JobWidth - - LogButtonWidth - 32; + lblStatus.Width := pnlBottom.ClientWidth - lblStatus.Left - JobWidth - 24; { Сто миллисекунд, а не триста: полоска должна ползти, а не дёргаться ступеньками. } @@ -715,8 +722,13 @@ end; procedure TMainForm.ApplyLogVisible(AVisible: Boolean); begin memoLog.Visible := AVisible; - if Assigned(FLogButton) then - FLogButton.Down := AVisible; + if not Assigned(FLogButton) then + Exit; + FLogButton.Down := AVisible; + if AVisible then + FLogButton.Hint := 'Журнал виден. Нажмите, чтобы спрятать.' + else + FLogButton.Hint := 'Журнал спрятан. Нажмите, чтобы показать.'; end; { Как растёт полоска. @@ -1815,10 +1827,15 @@ begin ModeText := 'все дорожки' else ModeText := 'текущая дорожка'; - btnRipple.Caption := 'Схлопывание: ' + ModeText; btnRipple.Down := FProject.RippleEnabled; - btnRipple.Hint := 'Ctrl+K — включить или выключить. ' + - 'Режим действует и при выключенном автосхлопывании: по нему работает Ctrl+Shift+R.'; + if FProject.RippleEnabled then + btnRipple.Hint := 'Автосхлопывание включено, режим: ' + ModeText + '.' + else + btnRipple.Hint := 'Автосхлопывание выключено, режим: ' + ModeText + '.'; + btnRipple.Hint := btnRipple.Hint + LineEnding + + 'Ctrl+K — включить или выключить. Режим действует и при выключенном ' + + 'автосхлопывании: по нему работает Ctrl+Shift+R.'; + btnRippleMenu.Hint := 'Выбрать режим схлопывания'; end; { Прилипание краёв клипов друг к другу. @@ -1826,14 +1843,20 @@ end; Shift работает как переключатель наоборот: при включённом прилипании временно отключает его, при выключенном — временно включает. Так одна клавиша полезна в обоих режимах, и не нужно помнить, какой сейчас. } +{ Кнопка со значком не может сказать словами, что она делает и в каком + она состоянии. Слова переезжают в подсказку — целиком, вместе с + состоянием: иначе о состоянии пришлось бы догадываться по цвету. } procedure TMainForm.UpdateSnapButton; +var + State: string; begin btnSnap.Down := FTimeline.SnapEnabled; if FTimeline.SnapEnabled then - btnSnap.Caption := 'Прилипание: вкл' + State := 'включено' else - btnSnap.Caption := 'Прилипание: выкл'; - btnSnap.Hint := 'Shift во время перетаскивания меняет решение на противоположное.'; + State := 'выключено'; + btnSnap.Hint := 'Прилипание: ' + State + '.' + LineEnding + + 'Shift во время перетаскивания меняет решение на противоположное.'; end; procedure TMainForm.btnSnapClick(Sender: TObject); diff --git a/src/uTheme.pas b/src/uTheme.pas index d4176dd..e68a12e 100644 --- a/src/uTheme.pas +++ b/src/uTheme.pas @@ -31,7 +31,7 @@ interface uses Classes, SysUtils, Types, Controls, Graphics, Forms, StdCtrls, ExtCtrls, - ComCtrls, Grids, Menus, Dialogs, LCLType, LCLIntf, LCLProc; + ComCtrls, Grids, Menus, Dialogs, Math, LCLType, LCLIntf, LCLProc; const { Порядок байт в TColor — $00BBGGRR, а не привычный RGB. У серых цветов @@ -53,6 +53,18 @@ const ThemeGridLine = TColor($3A3A3A); type + { Значок на кнопке. + + Рисуется кодом, а не берётся из файла: программа не должна зависеть + от того, лежит ли рядом с ней картинка. Рисунки повторяют icons/*.svg + — там они и придуманы, оттуда их можно перегнать в png для чего + угодно ещё. } + TThemedIcon = ( + tiNone, + tiSnap, // прилипание: края притягиваются к общей линии + tiRipple, // схлопывание: дыра между кусками закрывается + tiLog // журнал: полоса с сообщениями + ); { Полоска выполнения, которая рисует себя сама. @@ -82,6 +94,7 @@ type TThemedButton = class(TCustomControl) private FArrow: Boolean; + FIcon: TThemedIcon; FFlat: Boolean; FDown: Boolean; FHover: Boolean; @@ -90,6 +103,8 @@ type FCancel: Boolean; FModalResult: TModalResult; procedure SetArrow(AValue: Boolean); + procedure SetIcon(AValue: TThemedIcon); + procedure DrawIcon(const ARect: TRect; AColor: TColor); procedure SetFlat(AValue: Boolean); procedure SetDown(AValue: Boolean); procedure SetDefault(AValue: Boolean); @@ -122,6 +137,9 @@ type property Arrow: Boolean read FArrow write SetArrow default False; property Cancel: Boolean read FCancel write SetCancel default False; property Caption; + { Значок вместо надписи. Надпись при этом не пропадает зря: её место — + подсказка, которая всплывает по наведению. } + property Icon: TThemedIcon read FIcon write SetIcon default tiNone; property Default: Boolean read FDefault write SetDefault default False; property Down: Boolean read FDown write SetDown default False; property Enabled; @@ -357,6 +375,101 @@ begin Result.cy := 28; end; +procedure TThemedButton.SetIcon(AValue: TThemedIcon); +begin + if FIcon = AValue then + Exit; + FIcon := AValue; + Invalidate; +end; + +{ Значки рисуются по клетке 24 на 24 — той же, что в icons/*.svg, — и + переносятся в заданный прямоугольник. Так рисунок один и тот же при + любом размере кнопки, и правится он в одном месте. } +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; + +begin + if FIcon = tiNone 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 + { Средняя линия — граница, к которой прилипает край; по бокам + края, притягивающиеся к ней. } + Line(12, 2, 12, 22); + Line(2, 6, 2, 18); + Line(22, 6, 22, 18); + Tri(9, 5, 12, 4); + Tri(15, 19, 12, 4); + 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; +end; + procedure TThemedButton.Paint; var R: TRect; @@ -422,6 +535,8 @@ begin Canvas.Polygon([Point(CX - 5, CY - 2), Point(CX + 5, CY - 2), Point(CX, CY + 4)]); end + else if FIcon <> tiNone then + DrawIcon(R, Fore) else begin Canvas.Font.Assign(Font); diff --git a/tests/lfmcheck/lfmcheck.lpi b/tests/lfmcheck/lfmcheck.lpi new file mode 100644 index 0000000..705b2ff --- /dev/null +++ b/tests/lfmcheck/lfmcheck.lpi @@ -0,0 +1,37 @@ + + + + + + + + + + + </General> + <BuildModes Count="1"> + <Item1 Name="Default" Default="True"/> + </BuildModes> + <RequiredPackages Count="1"> + <Item1> + <PackageName Value="LCL"/> + </Item1> + </RequiredPackages> + <Units Count="1"> + <Unit0> + <Filename Value="lfmcheck.lpr"/> + <IsPartOfProject Value="True"/> + </Unit0> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <Target> + <Filename Value="lfmcheck"/> + </Target> + <SearchPaths> + <OtherUnitFiles Value="../../src"/> + <UnitOutputDirectory Value="units"/> + </SearchPaths> + </CompilerOptions> +</CONFIG> diff --git a/tests/lfmcheck/lfmcheck.lpr b/tests/lfmcheck/lfmcheck.lpr new file mode 100644 index 0000000..f9bb203 --- /dev/null +++ b/tests/lfmcheck/lfmcheck.lpr @@ -0,0 +1,67 @@ +{ + Проверка того, что свойства, записанные в файле формы, у наших + элементов действительно есть. + + Зачем отдельно. Файл формы разбирается при запуске, а не при сборке: + опечатка в имени свойства или в значении перечисления даёт не ошибку + компиляции, а падение окна на старте. Здесь то же самое проверяется без + окна — по сведениям о типах. +} +program lfmcheck; + +{$mode objfpc}{$H+} + +uses + { Interfaces нужен только для сборки: без него не связывается вся LCL. + Окно при этом не создаётся — Application.Initialize мы не зовём. } + Interfaces, SysUtils, TypInfo, uTheme; + +var + Failed: Integer = 0; + +procedure CheckProp(AClass: TClass; const AName: string); +begin + if GetPropInfo(AClass, AName) <> nil then + WriteLn(' ok ', AClass.ClassName, '.', AName) + else + begin + WriteLn(' ПЛОХО ', AClass.ClassName, '.', AName, ' — свойства нет'); + Inc(Failed); + end; +end; + +procedure CheckEnum(ATypeInfo: PTypeInfo; const AName: string); +begin + if GetEnumValue(ATypeInfo, AName) >= 0 then + WriteLn(' ok значение ', AName) + else + begin + WriteLn(' ПЛОХО значение ', AName, ' — такого нет'); + Inc(Failed); + end; +end; + +begin + WriteLn('Свойства из файлов форм'); + WriteLn; + + CheckProp(TThemedButton, 'Icon'); + CheckProp(TThemedButton, 'Down'); + CheckProp(TThemedButton, 'Arrow'); + CheckProp(TThemedButton, 'Caption'); + CheckProp(TThemedButton, 'ShowHint'); + + CheckEnum(TypeInfo(TThemedIcon), 'tiSnap'); + CheckEnum(TypeInfo(TThemedIcon), 'tiRipple'); + CheckEnum(TypeInfo(TThemedIcon), 'tiLog'); + CheckEnum(TypeInfo(TThemedIcon), 'tiNone'); + + WriteLn; + if Failed = 0 then + WriteLn('ВЕРДИКТ: все проверки пройдены') + else + begin + WriteLn('ВЕРДИКТ: не пройдено проверок: ', Failed); + Halt(1); + end; +end.