// **************************************************************************************************
//
// Unit Vcl.Styles.SysStyleHook
// unit for the VCL Styles Utils
// https://github.com/RRUZ/vcl-styles-utils/
//
// The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License");
// you may not use this file except in compliance with the License. You may obtain a copy of the
// License at http://www.mozilla.org/MPL/
//
// 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.
//
// The Original Code is uSysStyleHook.pas.
//
// Portions created by Mahdi Safsafi [SMP3]   e-mail SMP@LIVE.FR
// Portions created by Rodrigo Ruz V. are Copyright (C) 2013-2023 Rodrigo Ruz V.
// All Rights Reserved.
//
// **************************************************************************************************
unit Vcl.Styles.Utils.SysStyleHook;

interface

uses
  System.Classes,
  System.Types,
  System.SysUtils,
  Winapi.Windows,
  Winapi.Messages,
  Winapi.UxTheme,
  Winapi.CommCtrl,
  Vcl.Themes,
  Vcl.ExtCtrls,
  Vcl.Controls,
  Vcl.Graphics;

const
  CM_BASE = WM_USER + $113;
  CM_CTLCOLORBTN = CM_BASE + WM_CTLCOLORBTN;
  CM_CTLCOLORDLG = CM_BASE + WM_CTLCOLORDLG;
  CM_CTLCOLOREDIT = CM_BASE + WM_CTLCOLOREDIT;
  CM_CTLCOLORLISTBOX = CM_BASE + WM_CTLCOLORLISTBOX;
  CM_CTLCOLORMSGBOX = CM_BASE + WM_CTLCOLORMSGBOX;
  CM_CTLCOLORSCROLLBAR = CM_BASE + WM_CTLCOLORSCROLLBAR;
  CM_CTLCOLORSTATIC = CM_BASE + WM_CTLCOLORSTATIC;
  CM_SCROLLTRACKING = CM_BASE + 350;
  CM_PARENTHOOKED = CM_BASE + 360;
  CM_CONTROLHOOKED = CM_BASE + 361;
  CM_INITCHILDS = CM_BASE + 362;
  CM_CONTROLHOOKEDDIRECTLY = CM_BASE + 363;

type
  TBidiModeDirection = (bmLeftToRight, bmRightToLeft);

type
  TSysStyleHook = class;
  TMouseTrackSysControlStyleHook = class;
  TSysControl = class;
  TSysStyleHookClass = class of TSysStyleHook;

{$REGION 'TSysControl'}

  TSysControl = class
  private
    FFont: TFont;
    FParent: TSysControl;
    FHandle: THandle;
    FWindowClassName: string;
    FDestroyed: Boolean;
    function GetParent: TSysControl;
    function GetParentHandle: THandle;
    function GetText: String;
    function GetStyle: NativeInt;
    function GetExStyle: NativeInt;
    function GetWidth: Integer;
    function GetHeight: Integer;
    function GetLeft: Integer;
    function GetTop: Integer;
    function GetBorder: Boolean;
    function GetEnabled: Boolean;
    function GetVisible: Boolean;
    function GetClientRect: TRect;
    function GetWinRect: TRect;
    function GetClientEdge: Boolean;
    function GetControlClassName: String;
    function GetWndProc: NativeInt;
    procedure SetWndProc(Value: NativeInt);
    function GetBidiMode: TBidiModeDirection;
    procedure SetExStyle(const Value: NativeInt);
    procedure SetStyle(const Value: NativeInt);
    function GetControlID: Integer;
    function GetBoundsRect: TRect;
    function GetFont: TFont;
    function IsControlChild: Boolean;
    function GetClientHeight: Integer;
    function GetClientWidth: Integer;
  public
    constructor Create(AHandle: THandle); virtual;
    Destructor Destroy; override;
    property ClientHeight: Integer read GetClientHeight;
    property ClientWidth: Integer read GetClientWidth;
    property Font: TFont read GetFont;
    property Parent: TSysControl read GetParent;
    property ParentHandle: THandle read GetParentHandle;
    property Handle: THandle read FHandle write FHandle;
    property Text: String read GetText;
    property Style: NativeInt read GetStyle write SetStyle;
    property ExStyle: NativeInt read GetExStyle write SetExStyle;
    property Width: Integer read GetWidth;
    property Height: Integer read GetHeight;
    property Left: Integer read GetLeft;
    property Top: Integer read GetTop;
    property HasBorder: Boolean read GetBorder;
    property Enabled: Boolean read GetEnabled;
    property Visible: Boolean read GetVisible;
    property ClientRect: TRect read GetClientRect;
    property WindowRect: TRect read GetWinRect;
    property HasClientEdge: Boolean read GetClientEdge;
    property ControlClassName: string read GetControlClassName;
    property WndProc: NativeInt read GetWndProc write SetWndProc;
    property BidiMode: TBidiModeDirection read GetBidiMode;
    property ControlID: Integer read GetControlID;
    property BoundsRect: TRect read GetBoundsRect;
    property IsChild: Boolean read IsControlChild;
    property Destroyed: Boolean read FDestroyed write FDestroyed;     //WM_DESTROY
    function DrawTextBiDiModeFlags(const Flags: Longint): Longint;
    function UseRightToLeftAlignment: Boolean; dynamic;
    function DrawTextBiDiModeFlagsReadingOnly: Longint;
    function UseRightToLeftReading: Boolean;
    function Focused: Boolean; dynamic;

  end;
{$ENDREGION}
{$REGION 'TSysStyleHook'}

  TSysStyleHook = class
  private
    FHandle: HWND;
    FProcInstance: Pointer;
    FOrgWndProc: NativeInt;
    FSysControl: TSysControl;
    FOverrideEraseBkgnd: Boolean;
    FOverridePaint: Boolean;
    FOverridePaintNC: Boolean;
    FOverrideFont: Boolean;
    FDoubleBuffered: Boolean;
    FPaintOnEraseBkgnd: Boolean;
    FFontColor: TColor;
    FBrush: TBrush;
    FHandled: Boolean;
    FParentColor: Boolean;
{$IF CompilerVersion > 23}
    FStyleElements: TStyleElements;
{$IFEND}
    FColor: TColor;
    FFont: TFont;
    FText: string;
    FHookedDirectly, FMustRemove: Boolean;
    procedure WMPaint(var Message: TMessage); message WM_PAINT;
    procedure WMNCPaint(var Message: TMessage); message WM_NCPAINT;
    procedure WMEraseBkgnd(var Message: TMessage); message WM_ERASEBKGND;
{$IF CompilerVersion > 23}
    procedure SetStyleElements(Value: TStyleElements);
{$IFEND}
    function GetFontColor: TColor;
    function GetColor: TColor;
    procedure SetColor(const Value: TColor);
    procedure SetOverridePaint(const Value: Boolean);
    function GetFocused: Boolean;
    function GetParentHandle: HWND;
    procedure SetFont(Value: TFont);
    function UseLeftScrollBar: Boolean;
    function GetText: string;
  protected
    function GetBorderSize: TRect; virtual;
    function CheckIfParentBkGndPainted: Boolean; virtual;
    function CheckIfParentHooked: Boolean;
    procedure Paint(Canvas: TCanvas); virtual;
    procedure DrawParentBackground(DC: HDC); overload;
    procedure DrawParentBackground(DC: HDC; const ARect: PRect); overload;
    procedure PaintBorder(Control: TSysControl; const EraseLRCorner: Boolean);
    procedure DrawBorder(Canvas: TCanvas); virtual;
    procedure PaintBackground(Canvas: TCanvas); virtual;
    procedure PaintNC(Canvas: TCanvas); virtual;
    function CallDefaultProc(var Msg: TMessage): LRESULT;
    procedure SetRedraw(const Value: Boolean); overload;
    procedure SetRedraw(AHandle: HWND; const Value: Boolean); overload; virtual;
    function StyleServicesEnabled: Boolean;
    procedure WndProc(var Message: TMessage); virtual;
    function InternalPaint(DC: HDC): Boolean; virtual;
    procedure UpdateColors; virtual;
    function PaintControls(AControl: HWND; DC: HDC): Boolean;
    property HookedDirectly: Boolean read FHookedDirectly write FHookedDirectly;
    property MustRemove: Boolean read FMustRemove;
  public
    constructor Create(AHandle: THandle); virtual;
    Destructor Destroy; override;
    procedure Invalidate; virtual;
    procedure InvalidateNC; virtual;
    procedure Refresh; virtual;
    procedure DrawControlText(Canvas: TCanvas; Details: TThemedElementDetails; const S: string; var R: TRect; const Flags: Cardinal);
    function DrawTextCentered(DC: HDC; Details: TThemedElementDetails; const R: TRect; S: String; Const Flags: DWORD = 0): Integer;
    function DrawText(DC: HDC; Details: TThemedElementDetails; S: String; var R: TRect; Const Flags: TTextFormat = []): Integer;
    property Handle: HWND read FHandle;
    property ParentHandle: HWND read GetParentHandle;
    property Handled: Boolean read FHandled write FHandled;
    property SysControl: TSysControl read FSysControl write FSysControl;
{$IF CompilerVersion > 23}
    property StyleElements: TStyleElements read FStyleElements write SetStyleElements;
{$IFEND}
    property DoubleBuffered: Boolean read FDoubleBuffered write FDoubleBuffered;
    property OverridePaint: Boolean read FOverridePaint write SetOverridePaint;
    property OverridePaintNC: Boolean read FOverridePaintNC write FOverridePaintNC;
    property OverrideFont: Boolean read FOverrideFont write FOverrideFont;
    property OverrideEraseBkgnd: Boolean read FOverrideEraseBkgnd write FOverrideEraseBkgnd;
    property FontColor: TColor read GetFontColor write FFontColor;
    property Color: TColor read GetColor write SetColor;
    property Brush: TBrush read FBrush;
    property Font: TFont read FFont write SetFont;
    property Focused: Boolean read GetFocused;
    property ParentBkGndPainted: Boolean read CheckIfParentBkGndPainted;
    property ParentColor: Boolean read FParentColor write FParentColor;
    property Text: string read GetText;
  end;

{$ENDREGION}
{$REGION 'TMouseTrackSysControlStyleHook'}

  TMouseTrackSysControlStyleHook = class(TSysStyleHook)
  private
    FMouseInControl: Boolean;
    FMouseInNCArea: Boolean;
    FHotTrackTimer: TComponent;
    FMouseDown: Boolean;
    procedure WMMouseMove(var Message: TWMMouse); message WM_MOUSEMOVE;
    procedure WMNCMouseMove(var Message: TWMMouse); message WM_NCMOUSEMOVE;
    procedure WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;
    procedure WMLButtonUp(var Message: TWMLButtonUp); message WM_LBUTTONUP;
  protected
    procedure MouseEnter; virtual;
    procedure MouseLeave; virtual;
    function IsChildHandle(AHandle: HWND): Boolean; virtual;
    procedure StartHotTrackTimer;
    procedure StopHotTrackTimer;
    procedure DoHotTrackTimer(Sender: TObject); virtual;
  public
    constructor Create(AHandle: THandle); override;
    Destructor Destroy; override;

    property MouseInControl: Boolean read FMouseInControl write FMouseInControl;
    property MouseInNCArea: Boolean read FMouseInNCArea write FMouseInNCArea;
    property MouseDown: Boolean read FMouseDown;

  end;

{$ENDREGION}

function IsControlHooked(Handle: HWND): Boolean;

implementation

uses
  System.UITypes,
  Vcl.Styles.Utils.Misc,
  Vcl.Styles.Utils.SysControls;

// ------------------------------------------------------------------------------

function IsControlHooked(Handle: HWND): Boolean;
begin
  { Return True if Control is already hooked ! }
  Result := False;
  if Handle > 0 then
    Result := (SendMessage(Handle, CM_CONTROLHOOKED, 0, 0) = $77);
end;
// ------------------------------------------------------------------------------

{ TSysControl }
{$REGION 'TSysControl'}

constructor TSysControl.Create(AHandle: THandle);
begin
  inherited Create;
  FFont := nil;
  FParent := nil;
  Handle := AHandle;
  FWindowClassName := '';
  FDestroyed := False;
end;

destructor TSysControl.Destroy;
begin
  if Assigned(FParent) then
    FreeAndNil(FParent);
  if FFont <> nil then
    FFont.Free;
  inherited;
end;

function TSysControl.DrawTextBiDiModeFlags(const Flags: Integer): Longint;
begin
  Result := Flags;
  { do not change center alignment }
  if UseRightToLeftAlignment then
    if Result and DT_RIGHT = DT_RIGHT then
      Result := Result and not DT_RIGHT { removing DT_RIGHT, makes it DT_LEFT }
    else if not(Result and DT_CENTER = DT_CENTER) then
      Result := Result or DT_RIGHT;
  Result := Result or DrawTextBiDiModeFlagsReadingOnly;
end;

function TSysControl.DrawTextBiDiModeFlagsReadingOnly: Longint;
begin
  if UseRightToLeftReading then
    Result := DT_RTLREADING
  else
    Result := 0;
end;

function TSysControl.Focused: Boolean;
begin
  Result := (Handle <> 0) and (GetFocus = Handle);
end;

function TSysControl.GetBidiMode: TBidiModeDirection;
begin
  Result := bmLeftToRight;
  if Style <> 0 then
    if (ExStyle and WS_EX_RIGHT = WS_EX_RIGHT) or (ExStyle and WS_EX_RTLREADING = WS_EX_RTLREADING) or (ExStyle and WS_EX_LAYOUTRTL = WS_EX_LAYOUTRTL) then
      Result := bmRightToLeft;
end;

function TSysControl.GetBorder: Boolean;
begin
  Result := (Style and WS_BORDER = WS_BORDER) or (ExStyle and WS_EX_CLIENTEDGE = WS_EX_CLIENTEDGE);
end;

function TSysControl.GetBoundsRect: TRect;
begin
  Result.Left := Left;
  Result.Top := Top;
  Result.Right := Left + Width;
  Result.Bottom := Top + Height;
end;

function TSysControl.GetClientEdge: Boolean;
begin
  Result := ExStyle and WS_EX_CLIENTEDGE = WS_EX_CLIENTEDGE;
end;

function TSysControl.GetClientHeight: Integer;
begin
  Result := ClientRect.Bottom;
end;

function TSysControl.GetClientRect: TRect;
begin
  Result := Rect(0, 0, 0, 0);
  Winapi.Windows.GetClientRect(Handle, Result);
end;

function TSysControl.GetClientWidth: Integer;
begin
  Result := ClientRect.Right;
end;

function TSysControl.GetControlClassName: String;
begin
  if FWindowClassName='' then
    FWindowClassName := GetWindowClassName(Handle);
  Result:=FWindowClassName;
end;

function TSysControl.GetControlID: Integer;
begin
  Result := GetWindowLongPtr(Handle, GWL_ID);
end;

function TSysControl.GetEnabled: Boolean;
begin
  Result := False;
  if Handle > 0 then
    Result := IsWindowEnabled(Handle);
end;

function TSysControl.GetHeight: Integer;
begin
  Result := WindowRect.Height;
end;

function TSysControl.GetLeft: Integer;
begin
  Result := WindowRect.Left;
end;

function TSysControl.GetParent: TSysControl;
begin
  Result := nil;
  if Assigned(FParent) then
    FreeAndNil(FParent);
  if ParentHandle <> 0 then
  begin
    FParent := TSysControl.Create(ParentHandle);
    Result := FParent;
  end;
end;

function TSysControl.GetParentHandle: THandle;
begin
  Result := Winapi.Windows.GetParent(Handle);
end;

function TSysControl.GetStyle: NativeInt;
begin
  Result := GetWindowLongPtr(Handle, GWL_STYLE);
end;

function TSysControl.GetExStyle: NativeInt;
begin
  Result := GetWindowLongPtr(Handle, GWL_EXSTYLE);
end;

function TSysControl.GetFont: TFont;
var
  LogFont: TLogFont;
  hFont: HGDIOBJ;
begin
  if FFont <> nil then
    Exit(FFont);

  hFont := HGDIOBJ(SendMessage(Handle, WM_GETFONT, 0, 0));
  Result := TFont.Create;
  FillChar(LogFont, SizeOf(LogFont), 0);
  GetObject(hFont, SizeOf(LogFont), @LogFont);
  Result.Name := StrPas(LogFont.lffaceName);
  Result.Height := LogFont.lfHeight;
  if LogFont.lfWeight >= FW_MEDIUM then
    Result.Style := Result.Style + [fsBold];
  if LogFont.lfItalic <> 0 then
    Result.Style := Result.Style + [fsItalic];
  if LogFont.lfUnderline <> 0 then
    Result.Style := Result.Style + [fsUnderline];
  if LogFont.lfStrikeout <> 0 then
    Result.Style := Result.Style + [fsStrikeout];
  case (LogFont.lfPitchAndFamily and 3) of
    VARIABLE_PITCH: Result.Pitch := fpVariable;
    FIXED_PITCH: Result.Pitch := fpFixed;
  end;

  FFont := Result;
end;

function TSysControl.GetText: String;
var
  Buffer: array [0 .. 1023] of Char;
begin
  SetString(Result, Buffer, Winapi.Windows.GetWindowText(Handle, Buffer, Length(Buffer)));
end;

function TSysControl.GetTop: Integer;
begin
  Result := WindowRect.Top;
end;

function TSysControl.GetVisible: Boolean;
begin
  Result := IsWindowVisible(Handle);
end;

function TSysControl.GetWidth: Integer;
begin
  Result := WindowRect.Width;
end;

function TSysControl.GetWinRect: TRect;
begin
  Result := Rect(0, 0, 0, 0);
  GetWindowRect(Handle, Result);
end;

function TSysControl.GetWndProc: NativeInt;
begin
  Result := GetWindowLongPtr(Handle, GWL_WNDPROC);
end;

function TSysControl.IsControlChild: Boolean;
begin
  Result := (Style and WS_CHILD = WS_CHILD);
end;

procedure TSysControl.SetExStyle(const Value: NativeInt);
begin
  SetWindowLongPtr(Handle, GWL_EXSTYLE, Value);
end;

procedure TSysControl.SetStyle(const Value: NativeInt);
begin
  SetWindowLongPtr(Handle, GWL_STYLE, Value);
end;

procedure TSysControl.SetWndProc(Value: NativeInt);
begin
  if Value <> WndProc then
    SetWindowLongPtr(Handle, GWL_WNDPROC, Value);
end;

function TSysControl.UseRightToLeftAlignment: Boolean;
begin
  Result := SysLocale.MiddleEast and (BidiMode = TBidiModeDirection.bmRightToLeft);
end;

function TSysControl.UseRightToLeftReading: Boolean;
begin
  Result := SysLocale.MiddleEast and (BidiMode <> TBidiModeDirection.bmLeftToRight);
end;

{$ENDREGION}
{ TSysStyleHook }
{$REGION 'TSysStyleHook'}

constructor TSysStyleHook.Create(AHandle: THandle);
begin
  FHandled := False;
  FSysControl := nil;
  FHandle := AHandle;
  FOrgWndProc := 0;
  FProcInstance := nil;
  FBrush := nil;
  FFont := TFont.Create;
{$IF CompilerVersion > 23}
  StyleElements := [];
{$IFEND}
  FMustRemove := False;
  FParentColor := False;
  FDoubleBuffered := False;
  FPaintOnEraseBkgnd := False;
  FHookedDirectly := False;
  OverridePaint := False;
  OverridePaintNC := False;
  OverrideEraseBkgnd := False;
  OverrideFont := False;
  if AHandle > 0 then
  begin
    FProcInstance := MakeObjectInstance(WndProc);
    FSysControl := TSysControl.Create(AHandle);
    FOrgWndProc := FSysControl.WndProc;
    // if FOrgWndProc > 0 then
    begin
      FSysControl.WndProc := LONG_PTR(FProcInstance);
      FBrush := TBrush.Create;
      UpdateColors;
    end;
  end;
end;

destructor TSysStyleHook.Destroy;
begin
  if FOrgWndProc <> 0 then
    FSysControl.WndProc := FOrgWndProc;

  if Assigned(FProcInstance) then
    FreeObjectInstance(FProcInstance);

  if Assigned(FSysControl) then
    FreeAndNil(FSysControl);

  if Assigned(FBrush) then
    FreeAndNil(FBrush);

  if Assigned(FFont) then
    FreeAndNil(FFont);

  inherited;
end;

function TSysStyleHook.CallDefaultProc(var Msg: TMessage): LRESULT;
begin
  Result := 0;
  try
    if (FOrgWndProc <> 0) then
      Result := CallWindowProc(Pointer(FOrgWndProc), Handle, Msg.Msg, Msg.wParam, Msg.lParam);
  except
    on e: exception do
      OutputDebugString(PWideChar('CallDefaultProc error: ' + e.message + chr(0)));
  end;
end;

procedure TSysStyleHook.DrawBorder(Canvas: TCanvas);
var
  BorderSize: TRect;
begin
  BorderSize := GetBorderSize;
  with BorderSize do
    if (Left > 0) and (Right > 0) and (Top > 0) and (Bottom > 0) then
      PaintBorder(SysControl, True);
end;

procedure TSysStyleHook.DrawControlText(Canvas: TCanvas; Details: TThemedElementDetails; const S: string; var R: TRect; const Flags: Cardinal);
var
  ThemeTextColor: TColor;
  TextFormat: TTextFormatFlags;
begin
  Canvas.Font := SysControl.Font;
  TextFormat := TTextFormatFlags(Flags);
  if StyleServices.GetElementColor(Details, ecTextColor, ThemeTextColor) then
  begin
    Canvas.Font.Color := ThemeTextColor;
    StyleServices.DrawText(Canvas.Handle, Details, S, R, TextFormat, Canvas.Font.Color);
  end
  else
  begin
    Canvas.Refresh;
    StyleServices.DrawText(Canvas.Handle, Details, S, R, TextFormat);
  end;
end;

procedure TSysStyleHook.DrawParentBackground(DC: HDC; const ARect: PRect);
var
  Bmp: TBitmap;
  P: TPoint;
begin
  P := Point(0, 0);
  if ARect <> nil then
    P := Point(ARect.Left, ARect.Top);

  Bmp := TBitmap.Create;
  try
    Bmp.SetSize(SysControl.Parent.Width, SysControl.Parent.Height);
    SendMessage(ParentHandle, WM_ERASEBKGND, Bmp.Canvas.Handle, $93);
    ClientToScreen(Handle, P);
    ScreenToClient(ParentHandle, P);
    if ARect <> nil then
      BitBlt(DC, ARect.Left, ARect.Top, ARect.Width, ARect.Height, Bmp.Canvas.Handle, P.X, P.Y, SRCCOPY)
    else
      BitBlt(DC, 0, 0, SysControl.Width, SysControl.Height, Bmp.Canvas.Handle, P.X, P.Y, SRCCOPY);
  finally
    Bmp.Free;
  end;

end;

function TSysStyleHook.DrawText(DC: HDC; Details: TThemedElementDetails; S: String; var R: TRect; const Flags: TTextFormat): Integer;
var
  DrawFlags: Cardinal;
  SaveIndex: Integer;
  LColor: TColor;
begin
  SaveIndex := SaveDC(DC);
  try
    SetBkMode(DC, TRANSPARENT);
    if not StyleServices.GetElementColor(Details, ecTextColor, LColor) then
      LColor := FontColor;
    if not OverrideFont then
      LColor := FontColor;
    SetTextColor(DC, ColorToRGB(LColor));
    DrawFlags := TTextFormatFlags(Flags);
    Result := Winapi.Windows.DrawText(DC, S, -1, R, DrawFlags);
  finally
    RestoreDC(DC, SaveIndex);
  end;
end;

function TSysStyleHook.DrawTextCentered(DC: HDC; Details: TThemedElementDetails; const R: TRect; S: String; Const Flags: DWORD = 0): Integer;
var
  DrawRect: TRect;
  DrawFlags: Cardinal;
  DrawParams: TDrawTextParams;
  SaveIndex: Integer;
  LColor: TColor;
begin
  SaveIndex := SaveDC(DC);
  try
    SetBkMode(DC, TRANSPARENT);
    if not StyleServices.GetElementColor(Details, ecTextColor, LColor) then
      LColor := FontColor;
    if not OverrideFont then
      LColor := FontColor;
    SetTextColor(DC, ColorToRGB(LColor));
    DrawRect := R;
    DrawFlags := DT_END_ELLIPSIS or DT_WORDBREAK or DT_EDITCONTROL or DT_CENTER;
    if DrawFlags <> 0 then
      DrawFlags := DrawFlags or Flags;

    Winapi.Windows.DrawText(DC, PChar(S), -1, DrawRect, DrawFlags or DT_CALCRECT);
    DrawRect.Right := R.Right;
    if DrawRect.Bottom < R.Bottom then
      OffsetRect(DrawRect, 0, (R.Bottom - DrawRect.Bottom) div 2)
    else
      DrawRect.Bottom := R.Bottom;
    ZeroMemory(@DrawParams, SizeOf(DrawParams));
    DrawParams.cbSize := SizeOf(DrawParams);
    DrawTextEx(DC, PChar(S), -1, DrawRect, DrawFlags, @DrawParams);
    Result := DrawParams.uiLengthDrawn;
  finally
    RestoreDC(DC, SaveIndex);
  end;
end;

function TSysStyleHook.GetFocused: Boolean;
begin
  Result := (GetFocus = Handle);
end;

function TSysStyleHook.GetBorderSize: TRect;
begin
  Result := Rect(0, 0, 0, 0);
end;

function TSysStyleHook.GetColor: TColor;
begin
  // if OverrideEraseBkgnd then
  // Result := StyleServices.GetStyleColor(scWindow)
  // else
  Result := FColor;
end;

function TSysStyleHook.GetFontColor: TColor;
begin
  // if OverrideFont then
  // Result := StyleServices.GetSystemColor(clWindowText)
  // else
  Result := FFontColor;
end;

function TSysStyleHook.GetParentHandle: HWND;
begin
  Result := GetParent(Handle);
end;

function TSysStyleHook.GetText: string;
var
  Buffer: array [0 .. 255] of Char;
begin
  if (Handle <> 0) then
    SetString(Result, Buffer, Winapi.Windows.GetWindowText(Handle, Buffer, Length(Buffer)));
  FText := Result;
end;

function TSysStyleHook.InternalPaint(DC: HDC): Boolean;
begin
  Result := False;
end;

procedure TSysStyleHook.SetColor(const Value: TColor);
begin
  if (FBrush <> nil) and ((Value <> FColor) or (Value <> FBrush.Color)) then
  begin
    FColor := Value;
    FBrush.Color := Value;
  end;
end;

procedure TSysStyleHook.SetFont(Value: TFont);
begin
  if Value <> FFont then
    FFont.Assign(Value);
end;

procedure TSysStyleHook.SetOverridePaint(const Value: Boolean);
begin
  if Value then
    OverrideEraseBkgnd := Value;
  FOverridePaint := Value;
end;

procedure TSysStyleHook.SetRedraw(AHandle: HWND; const Value: Boolean);
begin
  SendMessage(AHandle, WM_SETREDRAW, wParam(Value), 0);
end;

procedure TSysStyleHook.SetRedraw(const Value: Boolean);
begin
  SetRedraw(Handle, Value);
end;

{$IF CompilerVersion > 23}

procedure TSysStyleHook.SetStyleElements(Value: TStyleElements);
begin
  if Value <> FStyleElements then
  begin
    FStyleElements := Value;
    OverridePaint := (seClient in FStyleElements);
    // OverrideEraseBkgnd := OverridePaint;
    OverridePaintNC := (seBorder in FStyleElements);
    OverrideFont := (seFont in FStyleElements);
  end;
end;
{$IFEND}

function TSysStyleHook.StyleServicesEnabled: Boolean;
begin
  Result := (StyleServices.Available) and not(StyleServices.IsSystemStyle);
  if Result then
    if not TSysStyleManager.HookVclControls then
      Result := not(IsVCLControl(Handle));
end;

procedure TSysStyleHook.UpdateColors;
begin
  if (OverrideEraseBkgnd) or (OverridePaint) then
    Color := StyleServices.GetStyleColor(scWindow)
  else
    Color := clBtnFace;
  if OverrideFont then
    FontColor := StyleServices.GetSystemColor(clWindowText)
  else
    FontColor := clBlack;
end;

function TSysStyleHook.UseLeftScrollBar: Boolean;
begin
  Result := (SysControl.ExStyle and WS_EX_LEFTSCROLLBAR = WS_EX_LEFTSCROLLBAR)
end;

procedure TSysStyleHook.Invalidate;
begin
  if FOverridePaintNC then
    InvalidateNC;
  InvalidateRect(Handle, nil, False);
end;

procedure TSysStyleHook.InvalidateNC;
begin
  SendMessage(Handle, WM_NCPAINT, 0, 0);
end;

procedure TSysStyleHook.Paint(Canvas: TCanvas);
begin
 //
end;

procedure TSysStyleHook.PaintBackground(Canvas: TCanvas);
begin
  Canvas.Brush.Color := Color;
  Canvas.FillRect(SysControl.ClientRect);
end;

procedure TSysStyleHook.PaintBorder(Control: TSysControl; const EraseLRCorner: Boolean);
var
  EmptyRect, DrawRect: TRect;
  DC: HDC;
  H, W: Integer;
  AStyle: Integer;
  Details: TThemedElementDetails;
  BorderSize: TRect;
begin
  BorderSize := GetBorderSize;
    Control.ExStyle := GetWindowLong(Handle, GWL_EXSTYLE);
    if (Control.ExStyle and WS_EX_CLIENTEDGE) <> 0 then
    begin
      GetWindowRect(Control.Handle, DrawRect);
      OffsetRect(DrawRect, -DrawRect.Left, -DrawRect.Top);
      DC := GetWindowDC(Control.Handle);
      try
        EmptyRect := DrawRect;
        if EraseLRCorner then
        begin
          AStyle := GetWindowLong(Control.Handle, GWL_STYLE);
          if ((AStyle and WS_HSCROLL) <> 0) and ((AStyle and WS_VSCROLL) <> 0) then
          begin
            W := GetSystemMetrics(SM_CXVSCROLL);
            H := GetSystemMetrics(SM_CYHSCROLL);
            InflateRect(EmptyRect, -2, -2);
            with EmptyRect do
              if not UseLeftScrollBar then
                EmptyRect := Rect(Left, Bottom - H, Left + W, Bottom)
              else
                EmptyRect := Rect(Right - W, Bottom - H, Right, Bottom);
            FillRect(DC, EmptyRect, GetSysColorBrush(COLOR_BTNFACE));
          end;
        end;
        with DrawRect do
          ExcludeClipRect(DC, Left + BorderSize.Left, Top + BorderSize.Top, Right - BorderSize.Right, Bottom - BorderSize.Bottom);
        Details := StyleServices.GetElementDetails(teEditTextNormal);
        StyleServices.DrawElement(DC, Details, DrawRect);
      finally
        ReleaseDC(Control.Handle, DC);
      end;
    end;
end;

function TSysStyleHook.PaintControls(AControl: HWND; DC: HDC): Boolean;
var
  Child: HWND;
  SavedDC: HDC;
  SysChild: TSysControl;
  P: TPoint;
  FrameBrush: HBRUSH;
begin
  Result := False;
  Child := GetTopWindow(AControl);
  while Child <> 0 do
  begin
    Result := True;
    SysChild := TSysControl.Create(Child);
    with SysChild do
    begin
      SavedDC := SaveDC(DC);
      P := Point(Left, Top);
      ScreenToClient(ParentHandle, P);
      if Visible and IsChild and RectVisible(DC, Rect(P.X, P.Y, P.X + Width, P.Y + Height)) then
      begin
        MoveWindowOrg(DC, P.X, P.Y);
        IntersectClipRect(DC, 0, 0, Width, Height);
        SendMessage(Child, WM_PAINT, lParam(DC), 0);
        if SysChild.HasBorder then
        begin
          // SendMessage(Child, WM_NCPAINT, 0, 0);
          FrameBrush := CreateSolidBrush(ColorToRGB(clBtnShadow));
          FrameRect(DC, System.Types.Rect(0, 0, Width, Height), FrameBrush);
          DeleteObject(FrameBrush);
          FrameBrush := CreateSolidBrush(ColorToRGB(clBtnHighlight));
          FrameRect(DC, System.Types.Rect(0, 0, Width + 1, Height + 1), FrameBrush);
          DeleteObject(FrameBrush);
        end;
      end;
      FreeAndNil(SysChild);
      PaintControls(Child, DC);
      RestoreDC(DC, SavedDC);
      Child := GetNextWindow(Child, GW_HWNDNEXT);
    end;
  end;
end;

procedure TSysStyleHook.PaintNC(Canvas: TCanvas);
begin

end;

procedure TSysStyleHook.DrawParentBackground(DC: HDC);
begin
  DrawParentBackground(DC, nil);
end;

procedure TSysStyleHook.Refresh;
begin
  SendMessage(Handle, WM_PAINT, 0, 0);
end;

procedure TSysStyleHook.WMEraseBkgnd(var Message: TMessage);
var
  DC: HDC;
  Canvas: TCanvas;
  SaveIndex: Integer;
begin
  Handled := False;

  if not StyleServicesEnabled then
    Exit;

  UpdateColors;

  if FOverrideEraseBkgnd then
  begin
    if not FDoubleBuffered then
    begin
      DC := HDC(Message.wParam);

      SaveIndex := 0;
      if DC = 0 then
        DC := GetDC(Handle)
      else
        SaveIndex := SaveDC(DC);

      Canvas := TCanvas.Create;
      try
        Canvas.Handle := DC;
        if Assigned(FFont) then
          Canvas.Font.Assign(FFont);

        if (FParentColor) and (ParentHandle > 0) then
          DrawParentBackground(Canvas.Handle)
        else
          PaintBackground(Canvas);

        if (FPaintOnEraseBkgnd) and (Message.lParam <> $93) then
          Paint(Canvas);
      finally
        Canvas.Handle := 0;
        Canvas.Free;
        if Message.wParam = 0 then
          ReleaseDC(Handle, DC)
        else if SaveIndex <> 0 then
          RestoreDC(DC, SaveIndex);
      end;
    end;
    Handled := True;
    Message.Result := 1;
  end;
end;

function TSysStyleHook.CheckIfParentBkGndPainted: Boolean;
var
  Test: Integer;
  PTest: PInteger;
  LParentHandle: HWND;
begin
  //Exit(True);
  Test := $93;
  PTest := @Test;
  Result := False;
  LParentHandle := GetParent(Handle);
  if LParentHandle > 0 then
  begin
    if not IsControlHooked(LParentHandle) then
      Exit(False);
    SendMessage(LParentHandle, WM_ERASEBKGND, 0, lParam(PTest));
    Result := (PTest^ = $11);
  end;
end;

function TSysStyleHook.CheckIfParentHooked: Boolean;
begin
  Result := (SendMessage(ParentHandle, CM_PARENTHOOKED, 0, 0) = $77);
end;

procedure TSysStyleHook.WMNCPaint(var Message: TMessage);
var
  Canvas: TCanvas;
begin
  Handled := False;
  if not StyleServicesEnabled then
    Exit;

  if FOverridePaintNC then
  begin
    Canvas := TCanvas.Create;
    try
      Canvas.Handle := GetWindowDC(SysControl.Handle);
      if Assigned(FFont) then
        Canvas.Font.Assign(FFont);
      DrawBorder(Canvas);
      PaintNC(Canvas);
    finally
      ReleaseDC(Handle, Canvas.Handle);
      Canvas.Handle := 0;
      Canvas.Free;
    end;
    Handled := True;
  end;
end;

procedure TSysStyleHook.WMPaint(var Message: TMessage);
var
  OrgDC, DC: HDC;
  Buffer: TBitmap;
  Canvas: TCanvas;
  PS: TPaintStruct;

  function ClipControls(AControl: HWND; Siblings: Boolean): Boolean;
  var
    Child: HWND;
    SysChild: TSysControl;
    P: TPoint;
  begin
    Result := False;
    SysChild := nil;
    Child := GetTopWindow(AControl);
    if GetParent(Child) = Handle then
      while Child <> 0 do
      begin
        Result := True;
        SysChild := TSysControl.Create(Child);
        with SysChild, P do
        begin
          P := Point(Left, Top);
          ScreenToClient(Self.Handle, P);
          if Visible and IsChild and RectVisible(DC, Rect(X, Y, X + Width, Y + Height)) then
          begin
            ExcludeClipRect(DC, X, Y, X + Width, Y + Height);
          end;
          FreeAndNil(SysChild);
          if Siblings then
            ClipControls(Child, Siblings);
          Child := GetNextWindow(Child, GW_HWNDNEXT);
        end;
      end;
    if Assigned(SysChild) then
      FreeAndNil(SysChild);
  end;

  function DoClipControls: Boolean;
  begin
    Result := False;
    if SysControl.Style and WS_CLIPSIBLINGS = WS_CLIPSIBLINGS then
      Result := ClipControls(Handle, True)
    else if SysControl.Style and WS_CLIPCHILDREN = WS_CLIPCHILDREN then
      Result := ClipControls(Handle, False);
  end;

begin
  Handled := False;
  if not StyleServicesEnabled then
    Exit;

  if OverridePaint then
  begin
    OrgDC := HDC(Message.wParam);
    Canvas := TCanvas.Create;
    try

      if OrgDC <> 0 then
      begin
        Canvas.Handle := OrgDC;
        DC:= OrgDC;
      end
      else
      begin
        DC := GetDC(Handle);
        BeginPaint(SysControl.Handle, PS);
        Canvas.Handle := DC;
      end;

      if Assigned(FFont) then
        Canvas.Font.Assign(FFont);

      if not InternalPaint(Canvas.Handle) then
        if FDoubleBuffered and (DC = 0) then
        begin
          Buffer := TBitmap.Create;
          try
            Buffer.SetSize(SysControl.Width, SysControl.Height);
            DoClipControls;
            PaintBackground(Buffer.Canvas);
            Paint(Buffer.Canvas);
            // PaintControls(Handle,Canvas.Handle);
            Canvas.Draw(0, 0, Buffer);
          finally
            Buffer.Free;
          end;
        end
        else
        begin
          DoClipControls;
          Paint(Canvas);
          // PaintControls(Handle,Canvas.Handle);
        end;

      if OrgDC = 0 then
      begin
        ReleaseDC(SysControl.Handle, DC);
        EndPaint(SysControl.Handle, PS);
      end;

    finally
      Canvas.Handle := 0;
      Canvas.Free;
    end;
    Handled := True;
  end;

end;


procedure TSysStyleHook.WndProc(var Message: TMessage);
var
  TempResult: LRESULT;
  ChildHandle: HWND;
  ItemRemoved: Boolean;
begin
  case Message.Msg of

    CM_CONTROLHOOKEDDIRECTLY:
      begin
        { Child controls are not hooked inside the parent . }
        FHookedDirectly := True;
        Exit;
      end;

    CM_INITCHILDS:
      begin
        Message.Result := 0;
        with TSysStyleManager do
        begin
          for ChildHandle in ChildRegSysStylesList.Keys do
            if (not IsControlHooked(ChildHandle)) and (ChildRegSysStylesList[ChildHandle].Parent = Handle) then
            begin
              if not SysStyleHookList.ContainsKey(ChildHandle) then
              begin
                SysStyleHookList.Add(ChildHandle, ChildRegSysStylesList[ChildHandle].StyleHookClass.Create(ChildHandle));
                { Child control need to be repainted . }
                RedrawWindow(ChildHandle, nil, 0, RDW_ERASE or RDW_FRAME or RDW_INTERNALPAINT or RDW_INVALIDATE);
                { Send WM_NCCALCSIZE message to the child control . }
                SetWindowPos(ChildHandle, 0, 0, 0, 0, 0, SWP_NOSIZE or SWP_NOMOVE or SWP_NOZORDER or SWP_FRAMECHANGED);
                Message.Result := 1;
              end;
            end;
        end;
        Exit;
      end;

    CM_PARENTHOOKED, CM_CONTROLHOOKED:
      begin
        Message.Result := $77;
        Exit;
      end;

    WM_CHANGEUISTATE, WM_PARENTNOTIFY, WM_QUERYUISTATE:
      begin
        CallDefaultProc(Message);
        {
          Do not Send ===> Use Post
          Return first then hook child !
        }
        PostMessage(Handle, CM_INITCHILDS, 0, 0);
        Exit;
      end;

    WM_ERASEBKGND:
      begin
        if (Message.lParam > 0) and (Message.wParam = 0) and (FOverrideEraseBkgnd or FOverridePaint or FPaintOnEraseBkgnd) then
          if PInteger(Message.lParam)^ = $93 then
          begin
            { lParam = Result
              if (lParam=$11) then Parent background was painted .
            }
            PInteger(Message.lParam)^ := $11;
            { Do not process the default message ..
              this is only for test !! .
            }
            Exit; { Do not Dispatch . }
          end;
      end;

    WM_SETREDRAW:
      begin
        Message.Result := CallDefaultProc(Message);
        Dispatch(Message);
        Exit;
      end;

    WM_CTLCOLORMSGBOX .. WM_CTLCOLORSTATIC:
      begin
        // avoid use cuurent style colors on ignored controls
        if (not StyleServicesEnabled) or (not TSysStyleManager.UseStyleColorsChildControls and (not TSysStyleManager.SysStyleHookList.ContainsKey(Message.lParam))) then
        // if (not StyleServicesEnabled) then
        begin
          Message.Result := CallDefaultProc(Message);
          Exit;
        end;

        TempResult := SendMessage(Handle, CM_BASE + Message.Msg, Message.wParam, Message.lParam);
        Message.Result := SendMessage(Message.lParam, CM_BASE + Message.Msg, Message.wParam, Message.lParam);
        if Message.Result = 0 then
          Message.Result := TempResult;
        Exit;
      end;

    CM_CTLCOLORMSGBOX .. CM_CTLCOLORSTATIC:
      begin
        SetTextColor(Message.wParam, ColorToRGB(FontColor));
        SetBkColor(Message.wParam, ColorToRGB(FBrush.Color));
        Message.Result := LRESULT(FBrush.Handle);
        Exit;
      end;

    WM_DESTROY:
      begin
        Message.Result := CallDefaultProc(Message);
        Dispatch(Message);
        Exit;
      end;

    //The WM_NCDESTROY message is sent after the child windows have been destroyed.
    //In contrast, WM_DESTROY is sent before the child windows are destroyed.
    WM_NCDESTROY:
      begin
        Message.Result := CallDefaultProc(Message);
        ItemRemoved:=False;
        if TSysStyleManager.SysStyleHookList.ContainsKey(FHandle) then
        begin
//          OutputDebugString(PChar('SysStyleHookList WM_NCDESTROY Removed '+IntToHex(Handle, 8)));
//          TSysStyleManager.SysStyleHookList.Remove(FHandle);
          FMustRemove:=True;
          ItemRemoved:=True;
        end;

        if not ItemRemoved and TSysStyleManager.ChildRegSysStylesList.ContainsKey(FHandle) then
        begin
          TSysStyleManager.ChildRegSysStylesList.Remove(Handle);
          //OutputDebugString(PChar('ChildRegSysStylesList WM_NCDESTROY Removed '+IntToHex(Handle, 8)));
        end;

        for ChildHandle in TSysStyleManager.ChildRegSysStylesList.Keys do
            if (TSysStyleManager.ChildRegSysStylesList[ChildHandle].Parent = FHandle)  then
            begin
               TSysStyleManager.ChildRegSysStylesList.Remove(ChildHandle);
               //OutputDebugString(PChar('Sub ChildRegSysStylesList WM_NCDESTROY Removed '+IntToHex(ChildHandle, 8)));
            end;
        Exit;
      end;

  end;

  Dispatch(Message);
  if not Handled then
    Message.Result := CallDefaultProc(Message);
  Handled := False;
end;
{$ENDREGION}
{ TMouseTrackSysControlStyleHook }
{$REGION 'TMouseTrackSysControlStyleHook'}

constructor TMouseTrackSysControlStyleHook.Create(AHandle: THandle);
begin
  inherited;
  FMouseInControl := False;
  FMouseInNCArea := False;
  FHotTrackTimer := nil;
end;

destructor TMouseTrackSysControlStyleHook.Destroy;
begin
  if Assigned(FHotTrackTimer) then
    FreeAndNil(FHotTrackTimer);

  inherited;
end;

procedure TMouseTrackSysControlStyleHook.WMLButtonDown(var Message: TWMLButtonDown);
begin
  FMouseDown := True;
  inherited;
end;

procedure TMouseTrackSysControlStyleHook.WMLButtonUp(var Message: TWMLButtonUp);
begin
  FMouseDown := False;
  inherited;
end;

procedure TMouseTrackSysControlStyleHook.WMMouseMove(var Message: TWMMouse);
begin
  inherited;
  if not FMouseInControl and not FMouseInNCArea then
  begin
    FMouseInControl := True;
    StartHotTrackTimer;
    MouseEnter;
  end
  else if FMouseInNCArea and FMouseInControl then
  begin
    StopHotTrackTimer;
    FMouseInControl := False;
    MouseLeave;
  end;
end;

procedure TMouseTrackSysControlStyleHook.WMNCMouseMove(var Message: TWMMouse);
begin
  inherited;
  if not FMouseInControl then
  begin
    FMouseInControl := True;
    StartHotTrackTimer;
    MouseEnter;
  end;
end;

procedure TMouseTrackSysControlStyleHook.StartHotTrackTimer;
begin
  if FHotTrackTimer <> nil then
    StopHotTrackTimer;
  FHotTrackTimer := TTimer.Create(nil);
  TTimer(FHotTrackTimer).Interval := 100;
  TTimer(FHotTrackTimer).OnTimer := DoHotTrackTimer;
  TTimer(FHotTrackTimer).Enabled := True;

end;

procedure TMouseTrackSysControlStyleHook.StopHotTrackTimer;
begin
  if FHotTrackTimer <> nil then
  begin
    TTimer(FHotTrackTimer).Enabled := False;
    FreeAndNil(FHotTrackTimer);
  end;
end;

function TMouseTrackSysControlStyleHook.IsChildHandle(AHandle: HWND): Boolean;
begin
  Result := False;
end;

procedure TMouseTrackSysControlStyleHook.DoHotTrackTimer(Sender: TObject);
var
  P: TPoint;
  FWindowHandle: HWND;
begin
  GetCursorPos(P);
  FWindowHandle := WindowFromPoint(P);
  if (FWindowHandle <> Handle) and not IsChildHandle(FWindowHandle) then
  begin
    StopHotTrackTimer;
    FMouseInControl := False;
    MouseLeave;
  end;
end;

procedure TMouseTrackSysControlStyleHook.MouseEnter;
begin

end;

procedure TMouseTrackSysControlStyleHook.MouseLeave;
begin

end;
{$ENDREGION}

end.
