fresnel.controls.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. {
  2. This file is part of the Fresnel Library.
  3. Copyright (c) 2024 by the FPC & Lazarus teams.
  4. Basic Fresnel control classes
  5. See the file COPYING.modifiedLGPL.txt, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. unit Fresnel.Controls;
  12. {$mode ObjFPC}{$H+}
  13. {$IF FPC_FULLVERSION>30300}
  14. {$WARN 6060 off} // Case statement does not handle all possible cases
  15. {$ENDIF}
  16. interface
  17. uses
  18. Classes, SysUtils, Math, fpCSSTree, fpCSSResParser,
  19. fpImage, fresnel.images,
  20. Fresnel.Classes, Fresnel.Dom;
  21. type
  22. { TDiv - div element }
  23. TDiv = class(TFresnelElement)
  24. private
  25. class var FFresnelDivTypeID: TCSSNumericalID;
  26. class constructor InitFresnelDivClass;
  27. public
  28. class function CSSTypeID: TCSSNumericalID; override;
  29. class function CSSTypeName: TCSSString; override;
  30. class function GetCSSTypeStyle: TCSSString; override;
  31. end;
  32. { TSpan - span element }
  33. TSpan = class(TFresnelElement)
  34. private
  35. class var FFresnelSpanTypeID: TCSSNumericalID;
  36. class constructor InitFresnelSpanClass;
  37. public
  38. class function CSSTypeID: TCSSNumericalID; override;
  39. class function CSSTypeName: TCSSString; override;
  40. class function GetCSSTypeStyle: TCSSString; override;
  41. end;
  42. TFresnelLabelState = (
  43. flsMinCaptionValid,
  44. flsMaxWidthValid,
  45. flsMinWidthValid,
  46. flsLastSizeValid
  47. );
  48. TFresnelLabelStates = set of TFresnelLabelState;
  49. { TCustomLabel }
  50. TCustomLabel = class(TReplacedElement)
  51. private
  52. FCaption: TFresnelCaption;
  53. protected
  54. FLabelStates: TFresnelLabelStates;
  55. FMinCaption: String; // Caption with linebreak after each word
  56. FMaxWidthSize: TFresnelPoint; // size for biggest width, no extra line breaks
  57. FMinWidthSize: TFresnelPoint; // size for width of longest word
  58. FOldFont: IFresnelFont;
  59. FLastMax: TFresnelPoint;
  60. FLastSize: TFresnelPoint; // result for last call with fixed max width or height
  61. procedure ComputeMinCaption; virtual;
  62. function GetFont: IFresnelFont; override;
  63. procedure SetCaption(const AValue: TFresnelCaption); virtual;
  64. procedure SetName(const NewName: TComponentName); override;
  65. procedure DoRender(aRenderer: IFresnelRenderer); override;
  66. public
  67. function GetIntrinsicContentSize(aMode: TFresnelLayoutMode; aMaxWidth: TFresnelLength=NaN;
  68. aMaxHeight: TFresnelLength=NaN): TFresnelPoint; override;
  69. property Caption: TFresnelCaption read FCaption write SetCaption;
  70. end;
  71. { TLabel - label element }
  72. TLabel = class(TCustomLabel)
  73. private
  74. class var FFresnelLabelTypeID: TCSSNumericalID;
  75. class constructor InitFresnelLabelClass;
  76. public
  77. class function CSSTypeID: TCSSNumericalID; override;
  78. class function CSSTypeName: TCSSString; override;
  79. class function GetCSSTypeStyle: TCSSString; override;
  80. published
  81. property Caption;
  82. end;
  83. { TBody - body element }
  84. TBody = class(TFresnelElement)
  85. private
  86. class var FFresnelBodyTypeID: TCSSNumericalID;
  87. class constructor InitFresnelBodyClass;
  88. public
  89. class function CSSTypeID: TCSSNumericalID; override;
  90. class function CSSTypeName: TCSSString; override;
  91. class function GetCSSTypeStyle: TCSSString; override;
  92. end;
  93. { TCustomButton }
  94. TIconPosition = (ipTop,ipBottom,ipLeft,ipRight);
  95. TCustomButton = class(TFresnelElement)
  96. private
  97. FCaption: string;
  98. FIconMargin: Single;
  99. FIConPosition: TIconPosition;
  100. FImage: TImageData;
  101. function ImageHasData: Boolean;
  102. procedure SetCaption(AValue: string);
  103. procedure SetIconMargin(AValue: Single);
  104. procedure SetIconPosition(AValue: TIconPosition);
  105. procedure SetImage(AValue: TImageData);
  106. Protected
  107. procedure AllocateImage;
  108. procedure FPOObservedChanged(ASender: TObject; Operation: TFPObservedOperation; Data: Pointer); override;
  109. Public
  110. Property HaveImage : Boolean read ImageHasData;
  111. Property Caption : string Read FCaption Write SetCaption;
  112. Property Icon : TImageData Read FImage Write SetImage Stored ImageHasData;
  113. Property IconPosition : TIconPosition Read FIConPosition Write SetIconPosition;
  114. Property IconMargin : Single Read FIconMargin Write SetIconMargin;
  115. end;
  116. { TButton - button element }
  117. TButton = class(TCustomButton)
  118. private
  119. class var FFresnelButtonTypeID: TCSSNumericalID;
  120. class constructor InitFresnelButtonClass;
  121. public
  122. class function CSSTypeID: TCSSNumericalID; override;
  123. class function CSSTypeName: TCSSString; override;
  124. class function GetCSSTypeStyle: TCSSString; override;
  125. Published
  126. Property Caption;
  127. Property Icon;
  128. Property IconPosition;
  129. Property IconMargin;
  130. end;
  131. { TCustomImage }
  132. TCustomImage = class(TReplacedElement)
  133. private
  134. FImage: TImageData;
  135. procedure SetImage(AValue: TImageData);
  136. Protected
  137. procedure DoRender(aRenderer: IFresnelRenderer); override;
  138. Public
  139. constructor Create(AOwner: TComponent); override;
  140. destructor Destroy; override;
  141. function GetIntrinsicContentSize(aMode: TFresnelLayoutMode; aMaxWidth: TFresnelLength=NaN;
  142. aMaxHeight: TFresnelLength=NaN): TFresnelPoint; override;
  143. Property Image : TImageData Read FImage Write SetImage;
  144. end;
  145. { TImage - img element }
  146. TImage = class(TCustomImage)
  147. private
  148. class var FFresnelImageTypeID: TCSSNumericalID;
  149. class constructor InitFresnelImageClass;
  150. public
  151. class function CSSTypeID: TCSSNumericalID; override;
  152. class function CSSTypeName: TCSSString; override;
  153. class function GetCSSTypeStyle: TCSSString; override;
  154. Published
  155. Property Image;
  156. end;
  157. implementation
  158. { TSpan }
  159. class constructor TSpan.InitFresnelSpanClass;
  160. begin
  161. FFresnelSpanTypeID:=CSSRegistry.AddType(CSSTypeName).Index;
  162. end;
  163. class function TSpan.CSSTypeID: TCSSNumericalID;
  164. begin
  165. Result:=FFresnelSpanTypeID;
  166. end;
  167. class function TSpan.CSSTypeName: TCSSString;
  168. begin
  169. Result:='span';
  170. end;
  171. class function TSpan.GetCSSTypeStyle: TCSSString;
  172. begin
  173. Result:='span { display: inline flow; }';
  174. end;
  175. { TDiv }
  176. class constructor TDiv.InitFresnelDivClass;
  177. begin
  178. FFresnelDivTypeID:=CSSRegistry.AddType(CSSTypeName).Index;
  179. end;
  180. class function TDiv.CSSTypeID: TCSSNumericalID;
  181. begin
  182. Result:=FFresnelDivTypeID;
  183. end;
  184. class function TDiv.CSSTypeName: TCSSString;
  185. begin
  186. Result:='div';
  187. end;
  188. class function TDiv.GetCSSTypeStyle: TCSSString;
  189. begin
  190. Result:='div { display: block; }';
  191. end;
  192. { TBody }
  193. class constructor TBody.InitFresnelBodyClass;
  194. begin
  195. FFresnelBodyTypeID:=CSSRegistry.AddType(CSSTypeName).Index;
  196. end;
  197. class function TBody.CSSTypeID: TCSSNumericalID;
  198. begin
  199. Result:=FFresnelBodyTypeID;
  200. end;
  201. class function TBody.CSSTypeName: TCSSString;
  202. begin
  203. Result:='body';
  204. end;
  205. class function TBody.GetCSSTypeStyle: TCSSString;
  206. begin
  207. Result:='body { background-color: white; color: black; display: block; position: static; margin: 8px; }';
  208. end;
  209. { TCustomButton }
  210. procedure TCustomButton.SetCaption(AValue: string);
  211. begin
  212. if FCaption=AValue then Exit;
  213. FCaption:=AValue;
  214. DomChanged;
  215. end;
  216. function TCustomButton.ImageHasData: Boolean;
  217. begin
  218. Result:=Assigned(Fimage) and FImage.HasData;
  219. end;
  220. procedure TCustomButton.SetIconMargin(AValue: Single);
  221. begin
  222. if FIconMargin=AValue then Exit;
  223. FIconMargin:=AValue;
  224. DomChanged;
  225. end;
  226. procedure TCustomButton.SetIconPosition(AValue: TIconPosition);
  227. begin
  228. if FIConPosition=AValue then Exit;
  229. FIConPosition:=AValue;
  230. DomChanged;
  231. end;
  232. procedure TCustomButton.SetImage(AValue: TImageData);
  233. begin
  234. if FImage=AValue then Exit;
  235. if not Assigned(FImage) then
  236. AllocateImage;
  237. FImage.Assign(AValue);
  238. DomChanged;
  239. end;
  240. procedure TCustomButton.AllocateImage;
  241. begin
  242. if Assigned(FImage) then
  243. Fimage.FPODetachObserver(Self);
  244. FreeAndNil(Fimage);
  245. FImage:=DefaultImageDataClass.Create(Self);
  246. Fimage.FPOAttachObserver(Self);
  247. end;
  248. procedure TCustomButton.FPOObservedChanged(ASender: TObject; Operation: TFPObservedOperation; Data: Pointer);
  249. begin
  250. inherited FPOObservedChanged(ASender, Operation, Data);
  251. if aSender=FImage then
  252. begin
  253. If Operation=ooFree then
  254. FImage:=Nil;
  255. DomChanged;
  256. end;
  257. end;
  258. { TButton }
  259. class constructor TButton.InitFresnelButtonClass;
  260. begin
  261. FFresnelButtonTypeID:=CSSRegistry.AddType(CSSTypeName).Index;
  262. end;
  263. class function TButton.CSSTypeID: TCSSNumericalID;
  264. begin
  265. Result:=FFresnelButtonTypeID;
  266. end;
  267. class function TButton.CSSTypeName: TCSSString;
  268. begin
  269. Result:='button';
  270. end;
  271. class function TButton.GetCSSTypeStyle: TCSSString;
  272. begin
  273. Result:='';
  274. end;
  275. { TCustomImage }
  276. procedure TCustomImage.SetImage(AValue: TImageData);
  277. begin
  278. if FImage=AValue then Exit;
  279. FImage.Assign(AValue);
  280. DomChanged;
  281. end;
  282. procedure TCustomImage.DoRender(aRenderer: IFresnelRenderer);
  283. begin
  284. inherited DoRender(aRenderer);
  285. if Assigned(FImage.Data) then
  286. aRenderer.DrawImage(UsedClientBox.Left,UsedClientBox.Top,UsedClientBox.Width,UsedClientBox.Height,
  287. FImage.Data);
  288. end;
  289. constructor TCustomImage.Create(AOwner: TComponent);
  290. begin
  291. inherited Create(AOwner);
  292. FImage:=DefaultImageDataClass.Create(Self);
  293. end;
  294. destructor TCustomImage.Destroy;
  295. begin
  296. FreeAndNil(FImage);
  297. inherited Destroy;
  298. end;
  299. function TCustomImage.GetIntrinsicContentSize(aMode: TFresnelLayoutMode; aMaxWidth: TFresnelLength;
  300. aMaxHeight: TFresnelLength): TFresnelPoint;
  301. begin
  302. if FImage=nil then
  303. exit(Default(TFresnelPoint));
  304. case aMode of
  305. flmMinWidth,flmMinHeight:
  306. exit(Default(TFresnelPoint));
  307. flmMax:
  308. begin
  309. Result.X:=FImage.Width;
  310. Result.Y:=FImage.Height;
  311. if (Result.X=0) or (Result.Y=0) then exit;
  312. if (not IsNan(aMaxWidth)) and (Result.X>aMaxWidth) and (aMaxWidth>=0) then
  313. Result.Y:=Result.Y*(aMaxWidth/Result.X);
  314. if (not IsNan(aMaxHeight)) and (Result.Y>aMaxHeight) and (aMaxHeight>=0) then
  315. Result.X:=Result.X*(aMaxHeight/Result.Y);
  316. end;
  317. end;
  318. end;
  319. { TImage }
  320. class constructor TImage.InitFresnelImageClass;
  321. begin
  322. FFresnelImageTypeID:=CSSRegistry.AddType(CSSTypeName).Index;
  323. end;
  324. class function TImage.CSSTypeID: TCSSNumericalID;
  325. begin
  326. Result:=FFresnelImageTypeID;
  327. end;
  328. class function TImage.CSSTypeName: TCSSString;
  329. begin
  330. Result:='img';
  331. end;
  332. class function TImage.GetCSSTypeStyle: TCSSString;
  333. begin
  334. Result:='image { display: block; }';
  335. end;
  336. { TCustomLabel }
  337. procedure TCustomLabel.ComputeMinCaption;
  338. // create FMinCaption from FCaption by putting every word on a line of its own
  339. var
  340. LineBreakLen, SrcP, l, StartP, WordLen, TargetP: Integer;
  341. MyLineBreak: string;
  342. begin
  343. GetFont;
  344. if flsMinCaptionValid in FLabelStates then exit;
  345. Include(FLabelStates,flsMinCaptionValid);
  346. if FCaption='' then
  347. begin
  348. FMinCaption:='';
  349. exit;
  350. end;
  351. MyLineBreak:=sLineBreak;
  352. LineBreakLen:=length(MyLineBreak);
  353. SrcP:=1;
  354. l:=length(FCaption);
  355. SetLength(FMinCaption,l);
  356. TargetP:=1;
  357. while (SrcP<=l) and (FCaption[SrcP] in [' ',#9]) do inc(SrcP);
  358. if SrcP>l then
  359. begin
  360. // only spaces
  361. FMinCaption:=' ';
  362. exit;
  363. end;
  364. while SrcP<=l do begin
  365. StartP:=SrcP;
  366. while (SrcP<=l) and not (FCaption[SrcP] in [' ',#9]) do inc(SrcP);
  367. WordLen:=SrcP-StartP;
  368. if TargetP+WordLen+LineBreakLen>length(FMinCaption) then
  369. SetLength(FMinCaption,Max(TargetP+WordLen+LineBreakLen,length(FMinCaption)*5 div 4));
  370. System.Move(FCaption[StartP],FMinCaption[TargetP],WordLen);
  371. inc(TargetP,WordLen);
  372. if SrcP<=l then
  373. begin
  374. System.Move(MyLineBreak[1],FMinCaption[TargetP],LineBreakLen);
  375. inc(TargetP,LineBreakLen);
  376. end;
  377. while (SrcP<=l) and (FCaption[SrcP] in [' ',#9]) do inc(SrcP);
  378. end;
  379. SetLength(FMinCaption,TargetP-1);
  380. end;
  381. function TCustomLabel.GetFont: IFresnelFont;
  382. begin
  383. Result:=inherited GetFont;
  384. if Result<>FOldFont then
  385. begin
  386. FLabelStates:=FLabelStates-[flsMinCaptionValid,flsMinWidthValid,flsMaxWidthValid,flsLastSizeValid];
  387. FOldFont:=Result;
  388. end;
  389. end;
  390. procedure TCustomLabel.SetCaption(const AValue: TFresnelCaption);
  391. begin
  392. if FCaption=AValue then Exit;
  393. FCaption:=AValue;
  394. FMinCaption:='';
  395. FLabelStates:=FLabelStates-[flsMinCaptionValid,flsMinWidthValid,flsMaxWidthValid,flsLastSizeValid];
  396. DomChanged;
  397. end;
  398. procedure TCustomLabel.SetName(const NewName: TComponentName);
  399. var
  400. ChangeCaption: Boolean;
  401. begin
  402. if Name=NewName then exit;
  403. ChangeCaption :=
  404. not (csLoading in ComponentState)
  405. and (Name = Caption)
  406. and ((Owner = nil) or not (csLoading in Owner.ComponentState));
  407. inherited SetName(NewName);
  408. if ChangeCaption then Caption := NewName;
  409. end;
  410. procedure TCustomLabel.DoRender(aRenderer: IFresnelRenderer);
  411. var
  412. aCaption : string;
  413. aColorFP, ShadowColor: TFPColor;
  414. aOffsetX, aOffsetY, aRadius: TFresnelLength;
  415. HaveShadow : Boolean;
  416. begin
  417. aCaption:=Caption;
  418. if aCaption='' then
  419. exit;
  420. aColorFP:=GetComputedColor(fcaColor,colTransparent);
  421. if aColorFP.Alpha=alphaTransparent then
  422. exit;
  423. // Change to loop, later
  424. HaveShadow:=GetComputedTextShadow(aOffsetX, aOffsetY, aRadius, ShadowColor);
  425. if HaveShadow then
  426. aRenderer.AddTextShadow(aOffsetX,aOffsetY,ShadowColor,aRadius);
  427. aRenderer.TextOut(UsedClientBox.Left,UsedClientBox.Top,Font,aColorFP,aCaption);
  428. if HaveShadow then
  429. aRenderer.ClearTextShadows;
  430. end;
  431. function TCustomLabel.GetIntrinsicContentSize(aMode: TFresnelLayoutMode; aMaxWidth: TFresnelLength;
  432. aMaxHeight: TFresnelLength): TFresnelPoint;
  433. begin
  434. GetFont;
  435. // todo writing-mode
  436. if IsNan(aMaxHeight) then ;
  437. case aMode of
  438. flmMinWidth:
  439. begin
  440. // size when using the width of the longest word
  441. if not (flsMinWidthValid in FLabelStates) then
  442. begin
  443. if not (flsMinCaptionValid in FLabelStates) then
  444. ComputeMinCaption;
  445. FMinWidthSize:=Font.TextSize(FMinCaption);
  446. if FMinCaption<>FCaption then
  447. FMinWidthSize:=Font.TextSizeMaxWidth(FCaption,FMinWidthSize.X);
  448. Include(FLabelStates,flsMinWidthValid);
  449. end;
  450. Result:=FMinWidthSize;
  451. end;
  452. flmMax,flmMinHeight:
  453. begin
  454. if not (flsMaxWidthValid in FLabelStates) then
  455. begin
  456. FMaxWidthSize:=Font.TextSize(FCaption);
  457. Include(FLabelStates,flsMaxWidthValid);
  458. end;
  459. if IsNan(aMaxWidth) or (aMaxWidth<0) or (FMaxWidthSize.X<aMaxWidth) then
  460. Result:=FMaxWidthSize
  461. else begin
  462. if (not (flsLastSizeValid in FLabelStates)) or IsNan(FLastMax.X) then
  463. begin
  464. FLastMax.X:=aMaxWidth;
  465. FLastMax.Y:=NaN;
  466. FLastSize:=Font.TextSizeMaxWidth(FCaption,aMaxWidth);
  467. end;
  468. Result:=FLastSize;
  469. end;
  470. end;
  471. end;
  472. end;
  473. { TLabel }
  474. class constructor TLabel.InitFresnelLabelClass;
  475. begin
  476. FFresnelLabelTypeID:=CSSRegistry.AddType(CSSTypeName).Index;
  477. end;
  478. class function TLabel.CSSTypeID: TCSSNumericalID;
  479. begin
  480. Result:=FFresnelLabelTypeID;
  481. end;
  482. class function TLabel.CSSTypeName: TCSSString;
  483. begin
  484. Result:='label';
  485. end;
  486. class function TLabel.GetCSSTypeStyle: TCSSString;
  487. begin
  488. Result:='label { display: inline flow; }';
  489. end;
  490. end.