// ************************************************************************ // ***************************** CEF4Delphi ******************************* // ************************************************************************ // // CEF4Delphi is based on DCEF3 which uses CEF to embed a chromium-based // browser in Delphi applications. // // The original license of DCEF3 still applies to CEF4Delphi. // // For more information about CEF4Delphi visit : // https://www.briskbard.com/index.php?lang=en&pageid=cef // // Copyright © 2022 Salvador Diaz Fau. All rights reserved. // // ************************************************************************ // ************ vvvv Original license and comments below vvvv ************* // ************************************************************************ (* * Delphi Chromium Embedded 3 * * Usage allowed under the restrictions of the Lesser GNU General Public License * or alternatively the restrictions of the Mozilla Public License 1.1 * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for * the specific language governing rights and limitations under the License. * * Unit owner : Henri Gourvest * Web site : http://www.progdigy.com * Repository : http://code.google.com/p/delphichromiumembedded/ * Group : http://groups.google.com/group/delphichromiumembedded * * Embarcadero Technologies, Inc is not permitted to use or redistribute * this source code without explicit permission. * *) unit uCEFFMXBufferPanel; {$I cef.inc} interface uses {$IFDEF MSWINDOWS} Winapi.Windows, FMX.Platform.Win, {$ELSE} System.SyncObjs, {$ENDIF} System.Classes, System.UIConsts, System.Types, System.UITypes, {$IFDEF DELPHI19_UP} FMX.Graphics, {$ENDIF} FMX.Types, FMX.Controls, FMX.Forms, uCEFTypes; type TDialogKeyEvent = procedure(Sender: TObject; var Key: Word; Shift: TShiftState) of object; {$IFNDEF FPC}{$IFDEF DELPHI16_UP}[ComponentPlatformsAttribute(pidWin32 or pidWin64)]{$ENDIF}{$ENDIF} TFMXBufferPanel = class(TControl) protected {$IFDEF MSWINDOWS} FMutex : THandle; {$ELSE} FBufferCS : TCriticalSection; {$ENDIF} FBuffer : TBitmap; FScanlineSize : integer; FColor : TAlphaColor; FHighSpeedDrawing : boolean; FOnDialogKey : TDialogKeyEvent; FForcedDeviceScaleFactor : single; procedure CreateSyncObj; procedure DestroySyncObj; procedure DestroyBuffer; function GetScreenScale : single; virtual; function GetBufferWidth : integer; function GetBufferHeight : integer; function GetParentForm : TCustomForm; function GetParentFormHandle : TCefWindowHandle; function GetRealScreenScale(var aResultScale : single) : boolean; virtual; function CopyBuffer : boolean; function SaveBufferToFile(const aFilename : string) : boolean; procedure Paint; override; procedure DialogKey(var Key: Word; Shift: TShiftState); override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure AfterConstruction; override; function SaveToFile(const aFilename : string) : boolean; procedure InvalidatePanel; function BeginBufferDraw : boolean; procedure EndBufferDraw; procedure BufferDraw(const aBitmap : TBitmap; const aSrcRect, aDstRect : TRectF); function UpdateBufferDimensions(aWidth, aHeight : integer) : boolean; function BufferIsResized(aUseMutex : boolean = True) : boolean; function ScreenToClient(aPoint : TPoint) : TPoint; overload; function ScreenToClient(aPoint : TPointF) : TPointF; overload; function ClientToScreen(aPoint : TPoint) : TPoint; overload; function ClientToScreen(aPoint : TPointF) : TPointF; overload; property Buffer : TBitmap read FBuffer; property ScanlineSize : integer read FScanlineSize; property BufferWidth : integer read GetBufferWidth; property BufferHeight : integer read GetBufferHeight; property ScreenScale : single read GetScreenScale; property ForcedDeviceScaleFactor : single read FForcedDeviceScaleFactor write FForcedDeviceScaleFactor; published property Align; property Anchors; property Visible; property Enabled; property TabOrder; property Color : TAlphaColor read FColor write FColor default claWhite; property HighSpeedDrawing : boolean read FHighSpeedDrawing write FHighSpeedDrawing default True; {$IFDEF DELPHI17_UP} property CanFocus; property CanParentFocus; property Height; property Width; property Padding; property Opacity; property Margins; property Position; property RotationAngle; property RotationCenter; property Scale; {$ENDIF} {$IFDEF DELPHI18_UP} property TabStop; property Size; property OnResized; {$ENDIF} {$IFNDEF DELPHI23_UP} property Hint; property ShowHint; {$ENDIF} property OnEnter; property OnExit; property OnResize; property OnClick; property OnMouseDown; property OnMouseMove; property OnMouseUp; property OnMouseEnter; property OnMouseLeave; property OnMouseWheel; property OnKeyUp; property OnKeyDown; property OnDialogKey : TDialogKeyEvent read FOnDialogKey write FOnDialogKey; end; implementation uses System.SysUtils, System.Math, {$IFDEF MSWINDOWS}{$IFDEF DELPHI24_UP}FMX.Helpers.Win,{$ENDIF}{$ENDIF} FMX.Platform, {$IFDEF MACOS}FMX.Platform.Mac,{$ENDIF} uCEFMiscFunctions, uCEFApplicationCore; constructor TFMXBufferPanel.Create(AOwner: TComponent); begin inherited Create(AOwner); {$IFDEF MSWINDOWS} FMutex := 0; {$ELSE} FBufferCS := nil; {$ENDIF} FBuffer := nil; FScanlineSize := 0; FColor := claWhite; FOnDialogKey := nil; FHighSpeedDrawing := True; if (GlobalCEFApp <> nil) and (GlobalCEFApp.ForcedDeviceScaleFactor <> 0) then FForcedDeviceScaleFactor := GlobalCEFApp.ForcedDeviceScaleFactor else FForcedDeviceScaleFactor := 0; end; destructor TFMXBufferPanel.Destroy; begin DestroyBuffer; DestroySyncObj; inherited Destroy; end; procedure TFMXBufferPanel.AfterConstruction; begin inherited AfterConstruction; CreateSyncObj; end; procedure TFMXBufferPanel.CreateSyncObj; begin {$IFDEF MSWINDOWS} FMutex := CreateMutex(nil, False, nil); {$ELSE} FBufferCS := TCriticalSection.Create; {$ENDIF} end; procedure TFMXBufferPanel.DestroySyncObj; begin {$IFDEF MSWINDOWS} if (FMutex <> 0) then begin CloseHandle(FMutex); FMutex := 0; end; {$ELSE} if (FBufferCS <> nil) then FreeAndNil(FBufferCS); {$ENDIF} end; procedure TFMXBufferPanel.DestroyBuffer; begin if BeginBufferDraw then begin if (FBuffer <> nil) then FreeAndNil(FBuffer); EndBufferDraw; end; end; function TFMXBufferPanel.SaveBufferToFile(const aFilename : string) : boolean; begin Result := False; try if (FBuffer <> nil) then begin FBuffer.SaveToFile(aFilename); Result := True; end; except on e : exception do if CustomExceptionHandler('TFMXBufferPanel.SaveBufferToFile', e) then raise; end; end; function TFMXBufferPanel.SaveToFile(const aFilename : string) : boolean; begin Result := False; if BeginBufferDraw then begin Result := SaveBufferToFile(aFilename); EndBufferDraw; end; end; procedure TFMXBufferPanel.InvalidatePanel; begin InvalidateRect(TRectF.Create(0, 0, Width, Height)); end; function TFMXBufferPanel.BeginBufferDraw : boolean; begin {$IFDEF MSWINDOWS} Result := (FMutex <> 0) and (WaitForSingleObject(FMutex, 5000) = WAIT_OBJECT_0); {$ELSE} if (FBufferCS <> nil) then begin FBufferCS.Acquire; Result := True; end else Result := False; {$ENDIF} end; procedure TFMXBufferPanel.EndBufferDraw; begin {$IFDEF MSWINDOWS} if (FMutex <> 0) then ReleaseMutex(FMutex); {$ELSE} if (FBufferCS <> nil) then FBufferCS.Release; {$ENDIF} end; function TFMXBufferPanel.CopyBuffer : boolean; var TempSrc, TempDst, TempClip : TRectF; TempState : TCanvasSaveState; TempScale : single; begin Result := False; if Canvas.BeginScene then try if BeginBufferDraw then try if (FBuffer <> nil) then begin TempScale := ScreenScale; TempSrc := TRectF.Create(0, 0, FBuffer.Width, FBuffer.Height); TempDst := TRectF.Create(0, 0, FBuffer.Width / TempScale, FBuffer.Height / TempScale); TempClip := TRectF.Create(0, 0, Width, Height); TempState := Canvas.SaveState; try Canvas.IntersectClipRect(TempClip); Canvas.DrawBitmap(FBuffer, TempSrc, TempDst, 1, FHighSpeedDrawing); Result := True; finally Canvas.RestoreState(TempState); end; end; finally EndBufferDraw; end; finally Canvas.EndScene; end; end; procedure TFMXBufferPanel.DialogKey(var Key: Word; Shift: TShiftState); begin if assigned(FOnDialogKey) then FOnDialogKey(self, Key, Shift); inherited DialogKey(Key, Shift); end; procedure TFMXBufferPanel.Paint; var TempRect : TRectF; begin if (csDesigning in ComponentState) or not(CopyBuffer) then begin TempRect := TRectF.Create(0, 0, Width, Height); if Canvas.BeginScene then try Canvas.ClearRect(TempRect, FColor); finally Canvas.EndScene; end; end; end; function TFMXBufferPanel.GetParentForm : TCustomForm; var TempComp : TComponent; begin Result := nil; TempComp := Owner; while (TempComp <> nil) do if (TempComp is TCustomForm) then begin Result := TCustomForm(TempComp); exit; end else TempComp := TempComp.owner; end; function TFMXBufferPanel.GetParentFormHandle : TCefWindowHandle; {$IFDEF MSWINDOWS} var TempForm : TCustomForm; {$ENDIF} begin InitializeWindowHandle(Result); {$IFDEF MSWINDOWS} TempForm := GetParentForm; if (TempForm <> nil) then Result := FmxHandleToHWND(TempForm.Handle) else if (Application <> nil) and (Application.MainForm <> nil) then Result := FmxHandleToHWND(Application.MainForm.Handle); {$ENDIF} end; function TFMXBufferPanel.GetRealScreenScale(var aResultScale : single) : boolean; {$IFDEF DELPHI24_UP}{$IFDEF MSWINDOWS} var TempHandle : TCefWindowHandle; {$ENDIF}{$ENDIF} begin {$IFDEF MSWINDOWS} {$IFDEF DELPHI24_UP} TempHandle := GetParentFormHandle; if (TempHandle <> 0) then begin Result := True; aResultScale := GetWndScale(TempHandle); end else begin Result := False; aResultScale := 1; end; {$ELSE} Result := False; aResultScale := 1; {$ENDIF} {$ENDIF} {$IFDEF LINUX} // TODO: Get the scale of the screen where the parent form is located in FMXLinux Result := False; aResultScale := 1; {$ENDIF} {$IFDEF MACOS} Result := True; aResultScale := TMacWindowHandle(GetParentForm.Handle).Wnd.backingScaleFactor; {$ENDIF} end; function TFMXBufferPanel.GetScreenScale : single; var TempScale : single; begin if (FForcedDeviceScaleFactor <> 0) then Result := FForcedDeviceScaleFactor else if GetRealScreenScale(TempScale) then Result := TempScale else if (GlobalCEFApp <> nil) then Result := GlobalCEFApp.DeviceScaleFactor else Result := 1; end; function TFMXBufferPanel.GetBufferWidth : integer; begin if (FBuffer <> nil) then Result := FBuffer.Width else Result := 0; end; function TFMXBufferPanel.GetBufferHeight : integer; begin if (FBuffer <> nil) then Result := FBuffer.Height else Result := 0; end; procedure TFMXBufferPanel.BufferDraw(const aBitmap : TBitmap; const aSrcRect, aDstRect : TRectF); begin if (FBuffer <> nil) then if FBuffer.Canvas.BeginScene then try FBuffer.Canvas.DrawBitmap(aBitmap, aSrcRect, aDstRect, 1, FHighSpeedDrawing); finally FBuffer.Canvas.EndScene; end; end; function TFMXBufferPanel.UpdateBufferDimensions(aWidth, aHeight : integer) : boolean; {$IFDEF DELPHI18_UP} var TempScale : single; {$ENDIF} begin Result := False; {$IFDEF DELPHI18_UP} TempScale := ScreenScale; {$ENDIF} if ((FBuffer = nil) or {$IFDEF DELPHI18_UP} (FBuffer.BitmapScale <> TempScale) or {$ENDIF} (FBuffer.Width <> aWidth) or (FBuffer.Height <> aHeight)) then begin if (FBuffer <> nil) then FreeAndNil(FBuffer); FBuffer := TBitmap.Create(aWidth, aHeight); {$IFDEF DELPHI18_UP} FBuffer.BitmapScale := TempScale; FScanlineSize := FBuffer.BytesPerLine; {$ELSE} FScanlineSize := aWidth * SizeOf(TRGBQuad); {$ENDIF} Result := True; end; end; function TFMXBufferPanel.BufferIsResized(aUseMutex : boolean) : boolean; var TempWidth, TempHeight : integer; TempScale : single; begin Result := False; if not(aUseMutex) or BeginBufferDraw then begin TempScale := ScreenScale; TempWidth := round(Width * TempScale); TempHeight := round(Height * TempScale); Result := (FBuffer <> nil) and {$IFDEF DELPHI18_UP} (FBuffer.BitmapScale = TempScale) and {$ENDIF} (FBuffer.Width = TempWidth) and (FBuffer.Height = TempHeight); if aUseMutex then EndBufferDraw; end; end; function TFMXBufferPanel.ScreenToClient(aPoint : TPoint) : TPoint; var TempPoint : TPointF; begin TempPoint.x := aPoint.x; TempPoint.y := aPoint.y; TempPoint := ScreenToLocal(TempPoint); Result.x := round(TempPoint.x); Result.y := round(TempPoint.y); end; function TFMXBufferPanel.ScreenToClient(aPoint : TPointF) : TPointF; begin Result := ScreenToLocal(aPoint); end; function TFMXBufferPanel.ClientToScreen(aPoint : TPoint) : TPoint; var TempPoint : TPointF; begin TempPoint.x := aPoint.x; TempPoint.y := aPoint.y; TempPoint := LocalToScreen(TempPoint); Result.x := round(TempPoint.x); Result.y := round(TempPoint.y); end; function TFMXBufferPanel.ClientToScreen(aPoint : TPointF) : TPointF; begin Result := LocalToScreen(aPoint); end; end.