// SPDX-License-Identifier: LGPL-3.0-linking-exception { Created by BGRA Controls Team Dibo, Circular, lainz (007) and contributors. For detailed information see readme.txt Site: https://sourceforge.net/p/bgra-controls/ Wiki: http://wiki.lazarus.freepascal.org/BGRAControls Forum: http://forum.lazarus.freepascal.org/index.php/board,46.0.html } {******************************* CONTRIBUTOR(S) ****************************** - Edivando S. Santos Brasil | mailedivando@gmail.com (Compatibility with delphi VCL 11/2018) ***************************** END CONTRIBUTOR(S) *****************************} unit BGRAVirtualScreen; {$I bgracontrols.inc} interface uses Classes, SysUtils, {$IFDEF FPC}LMessages, LResources, LCLIntf,{$ENDIF} Types, Forms, BCBaseCtrls, Controls, Graphics, Dialogs, {$IFNDEF FPC}Windows, Messages, BGRAGraphics, GraphType, FPImage, {$ENDIF} ExtCtrls, BGRABitmap, BCTypes; type { TCustomBGRAVirtualScreen } TCustomBGRAVirtualScreen = class(TBGRACustomPanel) private { Private declarations } FBGRA: TBGRABitmap; FOnRedraw: TBGRARedrawEvent; FDiscardedRect: TRect; FBevelInner, FBevelOuter: TPanelBevel; FBevelWidth: TBevelWidth; FBorderWidth: TBorderWidth; FAlignment: TAlignment; FBitmapAutoScale: boolean; function GetBitmapHeight: integer; function GetBitmapScale: double; function GetBitmapWidth: integer; function GetVSCaption: string; procedure SetAlignment(const Value: TAlignment); procedure SetBevelInner(const AValue: TPanelBevel); procedure SetBevelOuter(const AValue: TPanelBevel); procedure SetBevelWidth(const AValue: TBevelWidth); procedure SetBitmapAutoScale(AValue: boolean); procedure SetBorderWidth(const AValue: TBorderWidth); procedure SetVSCaption(AValue: string); protected { Protected declarations } procedure Paint; override; procedure Resize; override; procedure BGRASetSize(AWidth, AHeight: integer); procedure RedrawBitmapContent; virtual; procedure SetColor(Value: TColor); {$IFDEF FPC}override;{$ENDIF} procedure WMEraseBkgnd(var Message: {$IFDEF FPC}TLMEraseBkgnd{$ELSE}TWMEraseBkgnd{$ENDIF}); message {$IFDEF FPC}LM_ERASEBKGND{$ELSE}WM_ERASEBKGND{$ENDIF}; procedure SetEnabled(Value: boolean); override; public { Public declarations } constructor Create(TheOwner: TComponent); override; function BitmapRectToClient(ARect: TRect): TRect; procedure RedrawBitmap; overload; procedure RedrawBitmap(ARect: TRect); overload; procedure RedrawBitmap(ARectArray: array of TRect); overload; procedure DiscardBitmap; overload; procedure DiscardBitmap(ARect: TRect); overload; procedure InvalidateBitmap(ARect: TRect); destructor Destroy; override; public property OnRedraw: TBGRARedrawEvent Read FOnRedraw Write FOnRedraw; property Bitmap: TBGRABitmap Read FBGRA; property BitmapAutoScale: boolean read FBitmapAutoScale write SetBitmapAutoScale default true; property BitmapScale: double read GetBitmapScale; property BitmapWidth: integer read GetBitmapWidth; property BitmapHeight: integer read GetBitmapHeight; property BorderWidth: TBorderWidth Read FBorderWidth Write SetBorderWidth default 0; property BevelInner: TPanelBevel Read FBevelInner Write SetBevelInner default bvNone; property BevelOuter: TPanelBevel Read FBevelOuter Write SetBevelOuter default bvNone; property BevelWidth: TBevelWidth Read FBevelWidth Write SetBevelWidth default 1; property Alignment: TAlignment Read FAlignment Write SetAlignment; property Caption: string read GetVSCaption write SetVSCaption; end; TBGRAVirtualScreen = class(TCustomBGRAVirtualScreen) published property OnRedraw; property Bitmap; property BitmapAutoScale; // TPanel property Align; property Alignment; property Anchors; property AutoSize; property BorderSpacing; property ChildSizing; {$IFDEF FPC} //# property OnGetDockCaption; {$ENDIF} property BevelInner; property BevelOuter; property BevelWidth; property BidiMode; property BorderWidth; property BorderStyle; property Caption; property ClientHeight; property ClientWidth; property Color; property Constraints; property DockSite; property DragCursor; property DragKind; property DragMode; property Enabled; property Font; property FullRepaint; property ParentBidiMode; property ParentColor; property ParentFont; property ParentShowHint; property PopupMenu; property ShowHint; property TabOrder; property TabStop; property UseDockManager default True; property Visible; property OnClick; property OnContextPopup; property OnDockDrop; property OnDockOver; property OnDblClick; property OnDragDrop; property OnDragOver; property OnEndDock; property OnEndDrag; property OnEnter; property OnExit; property OnGetSiteInfo; property OnMouseDown; property OnMouseEnter; property OnMouseLeave; property OnMouseMove; property OnMouseUp; property OnMouseWheel; property OnMouseWheelDown; property OnMouseWheelUp; property OnResize; property OnStartDock; property OnStartDrag; property OnUnDock; end; {$IFDEF FPC}procedure Register;{$ENDIF} implementation uses BGRABitmapTypes, math, LazVersion; {$IFDEF FPC} procedure Register; begin RegisterComponents('BGRA Controls', [TBGRAVirtualScreen]); end; {$ENDIF} { TCustomBGRAVirtualScreen } procedure TCustomBGRAVirtualScreen.SetAlignment(const Value: TAlignment); begin if FAlignment = Value then exit; FAlignment := Value; DiscardBitmap; end; function TCustomBGRAVirtualScreen.GetVSCaption: string; begin result := inherited Caption; end; function TCustomBGRAVirtualScreen.GetBitmapScale: double; begin {$if laz_fullversion >= 2000000} if not FBitmapAutoScale then result := GetCanvasScaleFactor else result := 1; {$else} result := 1; {$endif} end; function TCustomBGRAVirtualScreen.GetBitmapHeight: integer; begin result := round(ClientHeight * BitmapScale); end; function TCustomBGRAVirtualScreen.GetBitmapWidth: integer; begin result := round(ClientWidth * BitmapScale); end; procedure TCustomBGRAVirtualScreen.SetBevelInner(const AValue: TPanelBevel); begin if FBevelInner = AValue then exit; FBevelInner := AValue; DiscardBitmap; end; procedure TCustomBGRAVirtualScreen.SetBevelOuter(const AValue: TPanelBevel); begin if FBevelOuter = AValue then exit; FBevelOuter := AValue; DiscardBitmap; end; procedure TCustomBGRAVirtualScreen.SetBevelWidth(const AValue: TBevelWidth); begin if FBevelWidth = AValue then exit; FBevelWidth := AValue; DiscardBitmap; end; procedure TCustomBGRAVirtualScreen.SetBitmapAutoScale(AValue: boolean); begin if FBitmapAutoScale=AValue then Exit; DiscardBitmap; //before to get correct invalidate bounds FBitmapAutoScale:=AValue; end; procedure TCustomBGRAVirtualScreen.SetBorderWidth(const AValue: TBorderWidth); begin if FBorderWidth = AValue then exit; FBorderWidth := AValue; DiscardBitmap; end; procedure TCustomBGRAVirtualScreen.SetVSCaption(AValue: string); begin inherited Caption := AValue; DiscardBitmap; end; procedure TCustomBGRAVirtualScreen.Paint; begin {$IFDEF WINDOWS} // to avoid flickering in Windows running without themes (classic style) DoubleBuffered := ControlCount <> 0; {$ENDIF} BGRASetSize(BitmapWidth, BitmapHeight); if FBGRA <> nil then begin if not FDiscardedRect.IsEmpty then begin FBGRA.ClipRect := FDiscardedRect; FDiscardedRect := EmptyRect; RedrawBitmapContent; FBGRA.NoClip; end; FBGRA.Draw(Canvas, rect(0, 0, ClientWidth, ClientHeight)); end; end; procedure TCustomBGRAVirtualScreen.Resize; begin inherited Resize; if (FBGRA <> nil) and ((ClientWidth <> FBGRA.Width) or (ClientHeight <> FBGRA.Height)) then DiscardBitmap; end; procedure TCustomBGRAVirtualScreen.BGRASetSize(AWidth, AHeight: integer); begin if (FBGRA <> nil) and ((AWidth <> FBGRA.Width) or (AHeight <> FBGRA.Height)) then begin FBGRA.SetSize(AWidth, AHeight); RedrawBitmapContent; FDiscardedRect := EmptyRect; end; end; procedure TCustomBGRAVirtualScreen.RedrawBitmapContent; var ARect: TRect; TS: TTextStyle; scale: Double; begin if (FBGRA <> nil) and (FBGRA.NbPixels <> 0) then begin FBGRA.FillRect(FBGRA.ClipRect, ColorToRGB(Color)); scale := BitmapScale; ARect := GetClientRect; ARect.Left := round(ARect.Left*scale); ARect.Top := round(ARect.Top*scale); ARect.Right := round(ARect.Right*scale); ARect.Bottom := round(ARect.Bottom*scale); // if BevelOuter is set then draw a frame with BevelWidth if BevelOuter <> bvNone then FBGRA.CanvasBGRA.Frame3d(ARect, round(BevelWidth*scale), BevelOuter, BGRA(255, 255, 255, 200), BGRA(0, 0, 0, 160)); // Note: Frame3D inflates ARect InflateRect(ARect, -round(BorderWidth*scale), -round(BorderWidth*scale)); // if BevelInner is set then skip the BorderWidth and draw a frame with BevelWidth if BevelInner <> bvNone then FBGRA.CanvasBGRA.Frame3d(ARect, round(BevelWidth*scale), BevelInner, BGRA(255, 255, 255, 160), BGRA(0, 0, 0, 160)); // Note: Frame3D inflates ARect if Caption <> '' then begin FBGRA.CanvasBGRA.Font.Assign(Canvas.Font); FBGRA.CanvasBGRA.Font.Height:= round(FBGRA.CanvasBGRA.Font.Height*scale); {$IFDEF FPC}//# TS := Canvas.TextStyle; {$ENDIF} TS.Alignment := Alignment; TS.Layout := tlTop; TS.Opaque := False; TS.Clipping := False; {$IFDEF FPC}//# TS.SystemFont := Canvas.Font.IsDefault; {$ENDIF} FBGRA.CanvasBGRA.Font.Color := Color xor $FFFFFF; if not Enabled then FBGRA.CanvasBGRA.Font.Style := [fsStrikeOut] else FBGRA.CanvasBGRA.Font.Style := []; FBGRA.CanvasBGRA.TextRect(ARect, ARect.Left, ARect.Top, Caption, TS); end; if Assigned(FOnRedraw) then FOnRedraw(self, FBGRA); end; end; procedure TCustomBGRAVirtualScreen.SetColor(Value: TColor); begin if Value <> Color then DiscardBitmap; {$IFDEF FPC} inherited SetColor(Value); {$ENDIF} end; {$hints off} procedure TCustomBGRAVirtualScreen.WMEraseBkgnd(var Message: {$IFDEF FPC}TLMEraseBkgnd{$ELSE}TWMEraseBkgnd{$ENDIF}); begin //do nothing end; {$hints on} procedure TCustomBGRAVirtualScreen.SetEnabled(Value: boolean); begin if Value <> Enabled then DiscardBitmap; inherited SetEnabled(Value); end; constructor TCustomBGRAVirtualScreen.Create(TheOwner: TComponent); begin inherited Create(TheOwner); inherited BevelOuter := bvNone; FBGRA := TBGRABitmap.Create; FBitmapAutoScale := true; FBevelWidth := 1; FAlignment := taLeftJustify; FDiscardedRect := EmptyRect; Color := clWhite; end; function TCustomBGRAVirtualScreen.BitmapRectToClient(ARect: TRect): TRect; var scale: Double; begin scale := BitmapScale; result := rect(floor(ARect.Left/scale), floor(ARect.Top/scale), ceil(ARect.Right/scale), ceil(ARect.Bottom/scale)); end; procedure TCustomBGRAVirtualScreen.RedrawBitmap; begin RedrawBitmapContent; FDiscardedRect := EmptyRect; Repaint; end; procedure TCustomBGRAVirtualScreen.RedrawBitmap(ARect: TRect); var All, displayRect: TRect; begin if Assigned(FBGRA) then begin All := Rect(0,0,FBGRA.Width,FBGRA.Height); ARect.Intersect(All); if not FDiscardedRect.IsEmpty then begin if ARect.IsEmpty then ARect := FDiscardedRect else ARect.Union(FDiscardedRect); FDiscardedRect := EmptyRect; end; if ARect.IsEmpty then exit; if ARect.Contains(All) then begin RedrawBitmap; end else begin FBGRA.ClipRect := ARect; RedrawBitmapContent; FBGRA.NoClip; displayRect := BitmapRectToClient(ARect); {$IFDEF LINUX} FBGRA.DrawPart(ARect, Canvas, displayRect, True); {$ELSE} InvalidateRect(Handle, @displayRect, False); Update; {$ENDIF} end; end; end; procedure TCustomBGRAVirtualScreen.RedrawBitmap(ARectArray: array of TRect); const cellShift = 6; cellSize = 1 shl cellShift; var grid: array of array of boolean; gAll: TRect; procedure IncludeRect(ARect: TRect); var gR: TRect; y,x: LongInt; begin with ARect do gR := rect(max(Left,0) shr cellShift, max(Top,0) shr cellShift, (max(Right,0)+cellSize-1) shr cellShift, (max(Bottom,0)+cellSize-1) shr cellShift); gR.Intersect(gAll); if gR.IsEmpty then exit; for y := gR.Top to gR.Bottom-1 do for x := gR.Left to gR.Right-1 do grid[y,x] := true; end; var gW,gH, i,gCount: integer; gR: TRect; y,x: LongInt; expand: boolean; begin if not Assigned(FBGRA) then exit; gW := (Bitmap.Width+cellSize-1) shr cellShift; gH := (Bitmap.Height+cellSize-1) shr cellShift; gAll := rect(0,0,gW,gH); //determine which cells of the grid to redraw setlength(grid,gH,gW); for i := 0 to high(ARectArray) do IncludeRect(ARectArray[i]); if not FDiscardedRect.IsEmpty then begin IncludeRect(FDiscardedRect); FDiscardedRect := EmptyRect; end; gCount := 0; for y := 0 to gH-1 do for x := 0 to gW-1 do if grid[y,x] then inc(gCount); if gCount >= gH*gW div 5 then begin RedrawBitmap(rect(0,0,Width,Height)); end else for y := 0 to gH-1 do begin x := 0; while x < gW do begin if grid[y,x] then begin gR.Left := x; grid[y,x] := false; inc(x); while (x < gW) and grid[y,x] do begin grid[y,x] := false; inc(x); end; gR.Right := x; gR.Top := y; gR.Bottom := y+1; expand := true; while expand and (gR.Bottom < gH) do begin expand := true; for x := gR.Left to gR.Right-1 do if not grid[gR.Bottom, x] then begin expand := false; break; end; if expand then begin for x := gR.Left to gR.Right-1 do grid[gR.Bottom,x] := false; inc(gR.Bottom); end; end; RedrawBitmap(rect(gR.Left shl cellShift,gR.Top shl cellShift,gr.Right shl cellShift,gr.Bottom shl cellShift)); end else inc(x); end; end; end; procedure TCustomBGRAVirtualScreen.DiscardBitmap; begin if FBGRA <> nil then DiscardBitmap(rect(0,0,FBGRA.Width,FBGRA.Height)); end; procedure TCustomBGRAVirtualScreen.DiscardBitmap(ARect: TRect); var displayRect: TRect; begin ARect.Intersect(rect(0,0,FBGRA.Width,FBGRA.Height)); if ARect.IsEmpty then exit; if FBGRA <> nil then begin if FDiscardedRect.IsEmpty then FDiscardedRect := ARect else FDiscardedRect.Union(ARect); displayRect := BitmapRectToClient(ARect); InvalidateRect(self.Handle, @displayRect, false); end; end; procedure TCustomBGRAVirtualScreen.InvalidateBitmap(ARect: TRect); var displayRect: TRect; begin displayRect := BitmapRectToClient(ARect); InvalidateRect(self.Handle, @displayRect, false); end; destructor TCustomBGRAVirtualScreen.Destroy; begin FBGRA.Free; inherited Destroy; end; end.