GLS.ObjectManager.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. //
  2. // The multimedia graphics platform GLScene https://github.com/glscene
  3. //
  4. unit GLS.ObjectManager;
  5. (*
  6. The object manager is used for registering classes together with a category,
  7. description + icon, so that they can be displayed visually. This can then
  8. be used by run-time or design-time scene editors for choosing which
  9. scene objects to place into a scene.
  10. TODO: add some notification code, so that when a scene object is registered/
  11. unregistered, any editor that is using the object manager can be notified.
  12. *)
  13. interface
  14. {$I GLScene.inc}
  15. uses
  16. System.Classes,
  17. System.SysUtils,
  18. VCL.Graphics,
  19. VCL.Controls,
  20. VCL.Menus,
  21. GLS.Scene,
  22. GLS.Utils;
  23. type
  24. PSceneObjectEntry = ^TGLSceneObjectEntry;
  25. // holds a relation between an scene object class, its global identification,
  26. // its location in the object stock and its icon reference
  27. TGLSceneObjectEntry = record
  28. ObjectClass: TGLSceneObjectClass;
  29. Name: string; // type name of the object
  30. Category: string; // category of object
  31. Index, // index into "FSceneObjectList"
  32. ImageIndex: Integer; // index into "FObjectIcons"
  33. end;
  34. TGLObjectManager = class(TComponent)
  35. private
  36. FSceneObjectList: TList;
  37. FObjectIcons: TImageList; // a list of icons for scene objects
  38. FOverlayIndex, // indices into the object icon list
  39. FSceneRootIndex, FCameraRootIndex, FLightsourceRootIndex,
  40. FObjectRootIndex: Integer;
  41. protected
  42. procedure DestroySceneObjectList;
  43. function FindSceneObjectClass(AObjectClass: TGLSceneObjectClass;
  44. const ASceneObject: string = ''): PSceneObjectEntry;
  45. public
  46. constructor Create(AOwner: TComponent); override;
  47. destructor Destroy; override;
  48. procedure CreateDefaultObjectIcons(ResourceModule: Cardinal);
  49. function GetClassFromIndex(Index: Integer): TGLSceneObjectClass;
  50. function GetImageIndex(ASceneObject: TGLSceneObjectClass): Integer;
  51. function GetCategory(ASceneObject: TGLSceneObjectClass): string;
  52. procedure GetRegisteredSceneObjects(ObjectList: TStringList);
  53. procedure PopulateMenuWithRegisteredSceneObjects(AMenuItem: TMenuItem;
  54. aClickEvent: TNotifyEvent);
  55. // Registers a stock object and adds it to the stock object list
  56. procedure RegisterSceneObject(ASceneObject: TGLSceneObjectClass;
  57. const aName, aCategory: string); overload;
  58. procedure RegisterSceneObject(ASceneObject: TGLSceneObjectClass;
  59. const aName, aCategory: string; aBitmap: TBitmap); overload;
  60. procedure RegisterSceneObject(ASceneObject: TGLSceneObjectClass;
  61. const aName, aCategory: string; ResourceModule: Cardinal;
  62. ResourceName: string = ''); overload;
  63. // Unregisters a stock object and removes it from the stock object list
  64. procedure UnRegisterSceneObject(ASceneObject: TGLSceneObjectClass);
  65. property ObjectIcons: TImageList read FObjectIcons;
  66. property SceneRootIndex: Integer read FSceneRootIndex;
  67. property LightsourceRootIndex: Integer read FLightsourceRootIndex;
  68. property CameraRootIndex: Integer read FCameraRootIndex;
  69. property ObjectRootIndex: Integer read FObjectRootIndex;
  70. end;
  71. // ------------------------------------------------------------------
  72. implementation
  73. // ------------------------------------------------------------------
  74. constructor TGLObjectManager.Create(AOwner: TComponent);
  75. begin
  76. inherited;
  77. FSceneObjectList := TList.Create;
  78. // FObjectIcons Width + Height are set when you add the first bitmap
  79. FObjectIcons := TImageList.CreateSize(16, 16);
  80. end;
  81. destructor TGLObjectManager.Destroy;
  82. begin
  83. DestroySceneObjectList;
  84. FObjectIcons.Free;
  85. inherited Destroy;
  86. end;
  87. function TGLObjectManager.FindSceneObjectClass(AObjectClass
  88. : TGLSceneObjectClass; const ASceneObject: string = ''): PSceneObjectEntry;
  89. var
  90. I: Integer;
  91. Found: Boolean;
  92. begin
  93. Result := nil;
  94. Found := False;
  95. with FSceneObjectList do
  96. begin
  97. for I := 0 to Count - 1 do
  98. with TGLSceneObjectEntry(Items[I]^) do
  99. if (ObjectClass = AObjectClass) and (Length(ASceneObject) = 0) or
  100. (CompareText(Name, ASceneObject) = 0) then
  101. begin
  102. Found := True;
  103. Break;
  104. end;
  105. if Found then
  106. Result := Items[I];
  107. end;
  108. end;
  109. function TGLObjectManager.GetClassFromIndex(Index: Integer)
  110. : TGLSceneObjectClass;
  111. begin
  112. if Index < 0 then
  113. Index := 0;
  114. if Index > FSceneObjectList.Count - 1 then
  115. Index := FSceneObjectList.Count - 1;
  116. Result := TGLSceneObjectEntry(FSceneObjectList.Items[Index + 1]^).ObjectClass;
  117. end;
  118. function TGLObjectManager.GetImageIndex(ASceneObject
  119. : TGLSceneObjectClass): Integer;
  120. var
  121. classEntry: PSceneObjectEntry;
  122. begin
  123. classEntry := FindSceneObjectClass(ASceneObject);
  124. if Assigned(classEntry) then
  125. Result := classEntry^.ImageIndex
  126. else
  127. Result := 0;
  128. end;
  129. function TGLObjectManager.GetCategory(ASceneObject
  130. : TGLSceneObjectClass): string;
  131. var
  132. classEntry: PSceneObjectEntry;
  133. begin
  134. classEntry := FindSceneObjectClass(ASceneObject);
  135. if Assigned(classEntry) then
  136. Result := classEntry^.Category
  137. else
  138. Result := '';
  139. end;
  140. procedure TGLObjectManager.GetRegisteredSceneObjects(ObjectList: TStringList);
  141. var
  142. I: Integer;
  143. begin
  144. if Assigned(ObjectList) then
  145. with ObjectList do
  146. begin
  147. Clear;
  148. for I := 0 to FSceneObjectList.Count - 1 do
  149. with TGLSceneObjectEntry(FSceneObjectList.Items[I]^) do
  150. AddObject(Name, Pointer(ObjectClass));
  151. end;
  152. end;
  153. procedure TGLObjectManager.PopulateMenuWithRegisteredSceneObjects
  154. (AMenuItem: TMenuItem; aClickEvent: TNotifyEvent);
  155. var
  156. ObjectList: TStringList;
  157. I, j: Integer;
  158. Item, CurrentParent: TMenuItem;
  159. CurrentCategory: string;
  160. Soc: TGLSceneObjectClass;
  161. begin
  162. ObjectList := TStringList.Create;
  163. try
  164. GetRegisteredSceneObjects(ObjectList);
  165. for I := 0 to ObjectList.Count - 1 do
  166. if ObjectList[I] <> '' then
  167. begin
  168. CurrentCategory :=
  169. GetCategory(TGLSceneObjectClass(ObjectList.Objects[I]));
  170. if CurrentCategory = '' then
  171. CurrentParent := AMenuItem
  172. else
  173. begin
  174. CurrentParent := NewItem(CurrentCategory, 0, False, True, nil, 0, '');
  175. AMenuItem.Add(CurrentParent);
  176. end;
  177. for j := I to ObjectList.Count - 1 do
  178. if ObjectList[j] <> '' then
  179. begin
  180. Soc := TGLSceneObjectClass(ObjectList.Objects[j]);
  181. if CurrentCategory = GetCategory(Soc) then
  182. begin
  183. Item := NewItem(ObjectList[j], 0, False, True,
  184. aClickEvent, 0, '');
  185. Item.ImageIndex := GetImageIndex(Soc);
  186. CurrentParent.Add(Item);
  187. ObjectList[j] := '';
  188. if CurrentCategory = '' then
  189. Break;
  190. end;
  191. end;
  192. end;
  193. finally
  194. ObjectList.Free;
  195. end;
  196. end;
  197. procedure TGLObjectManager.RegisterSceneObject(ASceneObject
  198. : TGLSceneObjectClass; const aName, aCategory: string);
  199. var
  200. resBitmapName: string;
  201. bmp: TBitmap;
  202. begin
  203. // Since no resource name was provided, assume it's the same as class name
  204. resBitmapName := ASceneObject.ClassName;
  205. bmp := TBitmap.Create;
  206. try
  207. // Try loading bitmap from module that class is in
  208. GLLoadBitmapFromInstance(FindClassHInstance(ASceneObject), bmp,
  209. resBitmapName);
  210. if bmp.Width = 0 then
  211. GLLoadBitmapFromInstance(HInstance, bmp, resBitmapName);
  212. // If resource was found, register scene object with bitmap
  213. if bmp.Width <> 0 then
  214. begin
  215. RegisterSceneObject(ASceneObject, aName, aCategory, bmp);
  216. end
  217. else
  218. // Resource not found, so register without bitmap
  219. RegisterSceneObject(ASceneObject, aName, aCategory, nil);
  220. finally
  221. bmp.Free;
  222. end;
  223. end;
  224. procedure TGLObjectManager.RegisterSceneObject(ASceneObject
  225. : TGLSceneObjectClass; const aName, aCategory: string; aBitmap: TBitmap);
  226. var
  227. newEntry: PSceneObjectEntry;
  228. bmp: TBitmap;
  229. begin
  230. if Assigned(RegisterNoIconProc) then
  231. RegisterNoIcon([ASceneObject]);
  232. with FSceneObjectList do
  233. begin
  234. // make sure no class is registered twice
  235. if Assigned(FindSceneObjectClass(ASceneObject, aName)) then
  236. Exit;
  237. New(newEntry);
  238. try
  239. with newEntry^ do
  240. begin
  241. // object stock stuff
  242. // registered objects list stuff
  243. ObjectClass := ASceneObject;
  244. newEntry^.Name := aName;
  245. newEntry^.Category := aCategory;
  246. Index := FSceneObjectList.Count;
  247. if Assigned(aBitmap) then
  248. begin
  249. bmp := TBitmap.Create;
  250. try
  251. // If we just add the bitmap, and it has different dimensions, then
  252. // all icons will be cleared, so ensure this doesn't happen
  253. bmp.PixelFormat := pf24bit;
  254. bmp.Width := FObjectIcons.Width;
  255. bmp.Height := FObjectIcons.Height;
  256. bmp.Canvas.Draw(0, 0, aBitmap);
  257. FObjectIcons.AddMasked(bmp, bmp.Canvas.Pixels[0, 0]);
  258. ImageIndex := FObjectIcons.Count - 1;
  259. finally
  260. bmp.Free;
  261. end;
  262. end
  263. else
  264. ImageIndex := 0;
  265. end;
  266. Add(newEntry);
  267. finally
  268. //
  269. end;
  270. end;
  271. end;
  272. procedure TGLObjectManager.RegisterSceneObject(ASceneObject
  273. : TGLSceneObjectClass; const aName, aCategory: string;
  274. ResourceModule: Cardinal; ResourceName: string = '');
  275. var
  276. bmp: TBitmap;
  277. resBitmapName: string;
  278. begin
  279. if ResourceName = '' then
  280. resBitmapName := ASceneObject.ClassName
  281. else
  282. resBitmapName := ResourceName;
  283. bmp := TBitmap.Create;
  284. try
  285. // Load resource
  286. if (ResourceModule <> 0) then
  287. GLLoadBitmapFromInstance(ResourceModule, bmp, resBitmapName);
  288. // If the resource was found, then register scene object using the bitmap
  289. if bmp.Width > 0 then
  290. RegisterSceneObject(ASceneObject, aName, aCategory, bmp)
  291. else
  292. // Register the scene object with no icon
  293. RegisterSceneObject(ASceneObject, aName, aCategory, nil);
  294. finally
  295. bmp.Free;
  296. end;
  297. end;
  298. procedure TGLObjectManager.UnRegisterSceneObject(ASceneObject
  299. : TGLSceneObjectClass);
  300. var
  301. oldEntry: PSceneObjectEntry;
  302. begin
  303. // find the class in the scene object list
  304. oldEntry := FindSceneObjectClass(ASceneObject);
  305. // found?
  306. if Assigned(oldEntry) then
  307. begin
  308. // remove its entry from the list of registered objects
  309. FSceneObjectList.Remove(oldEntry);
  310. // finally free the memory for the entry
  311. Dispose(oldEntry);
  312. end;
  313. end;
  314. procedure TGLObjectManager.CreateDefaultObjectIcons(ResourceModule: Cardinal);
  315. var
  316. bmp: TBitmap;
  317. begin
  318. bmp := TBitmap.Create;
  319. with FObjectIcons, bmp.Canvas do
  320. begin
  321. try
  322. // There's a more direct way for loading images into the image list, but
  323. // the image quality suffers too much
  324. {$IFDEF WIN32}
  325. GLLoadBitmapFromInstance(ResourceModule, bmp, 'gls_cross');
  326. FOverlayIndex := AddMasked(bmp, Pixels[0, 0]);
  327. Overlay(FOverlayIndex, 0); // used as indicator for disabled objects
  328. {$ENDIF}
  329. GLLoadBitmapFromInstance(ResourceModule, bmp, 'gls_root');
  330. FSceneRootIndex := AddMasked(bmp, Pixels[0, 0]);
  331. GLLoadBitmapFromInstance(ResourceModule, bmp, 'gls_camera');
  332. FCameraRootIndex := AddMasked(bmp, Pixels[0, 0]);
  333. GLLoadBitmapFromInstance(ResourceModule, bmp, 'gls_lights');
  334. FLightsourceRootIndex := AddMasked(bmp, Pixels[0, 0]);
  335. GLLoadBitmapFromInstance(ResourceModule, bmp, 'gls_objects');
  336. FObjectRootIndex := AddMasked(bmp, Pixels[0, 0]);
  337. finally
  338. bmp.Free;
  339. end;
  340. end;
  341. end;
  342. procedure TGLObjectManager.DestroySceneObjectList;
  343. var
  344. I: Integer;
  345. begin
  346. with FSceneObjectList do
  347. begin
  348. for I := 0 to Count - 1 do
  349. Dispose(PSceneObjectEntry(Items[I]));
  350. Free;
  351. end;
  352. end;
  353. // -------------------------------------------
  354. initialization
  355. // -------------------------------------------
  356. end.