// ************************************************************************************************** // // Unit Vcl.Styles.ColorTabs // 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 Vcl.Styles.ColorTabs // // The Initial Developer of the Original Code is Rodrigo Ruz V. // Portions created by Rodrigo Ruz V. are Copyright (C) 2012-2023 Rodrigo Ruz V. // All Rights Reserved. // // ************************************************************************************************** unit Vcl.Styles.ColorTabs; interface uses Winapi.Messages, Vcl.Graphics, Vcl.ComCtrls; type TTabSheet = class(Vcl.ComCtrls.TTabSheet) private procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND; end; TTabColorControlStyleHook = class(TTabControlStyleHook) private class var FUseBorder: Boolean; procedure WMEraseBkgnd(var Message: TMessage); message WM_ERASEBKGND; protected class constructor Create; procedure PaintBackground(Canvas: TCanvas); override; procedure Paint(Canvas: TCanvas); override; procedure DrawTab(Canvas: TCanvas; Index: Integer); override; class property UseBorder: Boolean read FUseBorder write FUseBorder; end; implementation uses System.Classes, System.SysUtils, System.Types, System.Rtti, System.Generics.Collections, Winapi.Windows, Vcl.Styles, Vcl.Themes, Vcl.Controls; type TStyleHookList = TList; TPageControlHelper = class helper for TPageControl public procedure UpdateTab2(Page: Vcl.ComCtrls.TTabSheet); end; TWinControlClass = class(TWinControl); TCustomTabControlClass = class(TCustomTabControl); TTabControlStyleHookHelper = class helper for TTabControlStyleHook public procedure AngleTextOut2(Canvas: TCanvas; Angle: Integer; X, Y: Integer; const Text: string); end; TStyleHookDictionary = TDictionary; TCustomStyleEngineHelper = Class Helper for TCustomStyleEngine public class function GetRegisteredStyleHooks: TStyleHookDictionary; End; class function TCustomStyleEngineHelper.GetRegisteredStyleHooks : TStyleHookDictionary; {$IF (CompilerVersion >=31)} var p: Pointer; {$IFEND} begin {$IF (CompilerVersion <31)} Result := Self.FRegisteredStyleHooks; {$ELSE} { TCustomStyleEngine.FRegisteredStyleHooks: 00651030 3052AA xor [edx-$56],dl 00651033 02F7 add dh,bh 00651035 097623 or [esi+$23],esi TCustomStyleEngine.$ClassInitFlag: 00651038 FFFF db $ff $ff 0065103A FFFF db $ff $ff TCustomStyleEngine.FRegSysStylesList: 0065103C D037 shl [edi],1 } {$IFDEF CPUX64} p := Pointer(PByte(@Self.FRegSysStylesList) - 24); {$ELSE} p := Pointer(PByte(@Self.FRegSysStylesList) - 12); {$ENDIF CPUX64} Result := TStyleHookDictionary(p^); {$IFEND} end; function GetBorderColorTab: TColor; begin Result := clBlack; end; function GetColorTab(Index: Integer): TColor; Const MaxColors = 9; Colors: Array [0 .. MaxColors - 1] of TColor = (6512214, 16755712, 8355381, 1085522, 115885, 1098495, 1735163, 2248434, 4987610); begin Result := Colors[Index mod MaxColors]; end; function GetColorTextTab(ThemedTab: TThemedTab): TColor; Const ColorSelected = clYellow; ColorHot = clGray; ColorNormal = clWhite; begin Result := ColorNormal; case ThemedTab of ttTabItemSelected, ttTabItemLeftEdgeSelected, ttTabItemBothEdgeSelected, ttTabItemRightEdgeSelected: Result := ColorSelected; ttTabItemHot, ttTabItemLeftEdgeHot, ttTabItemBothEdgeHot, ttTabItemRightEdgeHot: Result := ColorHot; ttTabItemNormal, ttTabItemLeftEdgeNormal, ttTabItemBothEdgeNormal, ttTabItemRightEdgeNormal: Result := ColorNormal; end; end; function IsStyleHookRegistered(ControlClass: TClass; StyleHookClass: TStyleHookClass): Boolean; var List: TStyleHookList; begin Result := False; if TCustomStyleEngine.GetRegisteredStyleHooks.ContainsKey(ControlClass) then begin List := TCustomStyleEngine.GetRegisteredStyleHooks[ControlClass]; Result := List.IndexOf(StyleHookClass) <> -1; end; end; { TTabSheet } procedure TTabSheet.WMEraseBkgnd(var Message: TWMEraseBkgnd); var LRect: TRect; LSize: Integer; LCanvas: TCanvas; begin // check if the TTabColorControlStyleHook is registered if (not IsStyleHookRegistered(TCustomTabControl, TTabColorControlStyleHook)) and (not IsStyleHookRegistered(TTabControl, TTabColorControlStyleHook)) then inherited else if (PageControl <> nil) and StyleServices.Enabled and TStyleManager.IsCustomStyleActive then begin GetWindowRect(Handle, LRect); OffsetRect(LRect, -LRect.Left, -LRect.Top); LSize := ClientToParent(Point(0, 0)).X; InflateRect(LRect, LSize, LSize); // remove border // create a TCanvas for erase the background, using the DC of the message LCanvas := TCanvas.Create; try LCanvas.Handle := Message.DC; LCanvas.Brush.Color := GetColorTab(TabIndex); LCanvas.FillRect(LRect); finally LCanvas.Handle := 0; LCanvas.Free; end; Message.Result := 1; if PageControl.ActivePage <> nil then PageControl.UpdateTab2(PageControl.ActivePage); end else inherited; end; { TPageControlHelper } procedure TPageControlHelper.UpdateTab2(Page: Vcl.ComCtrls.TTabSheet); begin {$IF (CompilerVersion <31)} Self.UpdateTab(Page); {$ELSE} Self.Tabs[Page.TabIndex] := Page.Caption; {$IFEND} end; { TTabControlStyleHookHelper } procedure TTabControlStyleHookHelper.AngleTextOut2(Canvas: TCanvas; Angle, X, Y: Integer; const Text: string); var LSavedDC: Integer; begin LSavedDC := SaveDC(Canvas.Handle); try SetBkMode(Canvas.Handle, TRANSPARENT); Canvas.Font.Orientation := Angle; Canvas.TextOut(X, Y, Text); finally RestoreDC(Canvas.Handle, LSavedDC); end; end; { TTabColorControlStyleHook } class constructor TTabColorControlStyleHook.Create; begin FUseBorder := True; end; procedure TTabColorControlStyleHook.DrawTab(Canvas: TCanvas; Index: Integer); var LDetails: TThemedElementDetails; LImageIndex: Integer; LThemedTab: TThemedTab; LIconRect: TRect; R, LayoutR: TRect; LImageW, LImageH, DxImage: Integer; LTextX, LTextY: Integer; LTextColor: TColor; procedure DrawControlText(const S: string; var R: TRect; Flags: Cardinal); var TextFormat: TTextFormatFlags; begin Canvas.Font := TWinControlClass(Control).Font; TextFormat := TTextFormatFlags(Flags); Canvas.Font.Color := LTextColor; StyleServices.DrawText(Canvas.Handle, LDetails, S, R, TextFormat, Canvas.Font.Color); end; begin if (Images <> nil) and (Index < Images.Count) then begin LImageW := Images.Width; LImageH := Images.Height; DxImage := 3; end else begin LImageW := 0; LImageH := 0; DxImage := 0; end; R := TabRect[Index]; if R.Left < 0 then Exit; if TabPosition in [tpTop, tpBottom] then begin if Index = TabIndex then InflateRect(R, 0, 2); end else if Index = TabIndex then Dec(R.Left, 2) else Dec(R.Right, 2); Canvas.Font.Assign(TCustomTabControlClass(Control).Font); LayoutR := R; LThemedTab := ttTabDontCare; // Get the type of the active tab case TabPosition of tpTop: begin if Index = TabIndex then LThemedTab := ttTabItemSelected else if (Index = HotTabIndex) and MouseInControl then LThemedTab := ttTabItemHot else LThemedTab := ttTabItemNormal; end; tpLeft: begin if Index = TabIndex then LThemedTab := ttTabItemLeftEdgeSelected else if (Index = HotTabIndex) and MouseInControl then LThemedTab := ttTabItemLeftEdgeHot else LThemedTab := ttTabItemLeftEdgeNormal; end; tpBottom: begin if Index = TabIndex then LThemedTab := ttTabItemBothEdgeSelected else if (Index = HotTabIndex) and MouseInControl then LThemedTab := ttTabItemBothEdgeHot else LThemedTab := ttTabItemBothEdgeNormal; end; tpRight: begin if Index = TabIndex then LThemedTab := ttTabItemRightEdgeSelected else if (Index = HotTabIndex) and MouseInControl then LThemedTab := ttTabItemRightEdgeHot else LThemedTab := ttTabItemRightEdgeNormal; end; end; // draw the tab if StyleServices.Available then begin LDetails := StyleServices.GetElementDetails(LThemedTab); // necesary for DrawControlText if FUseBorder then begin case TabPosition of tpTop: begin InflateRect(R, -1, 0); if TabIndex <> Index then R.Bottom := R.Bottom + 1 else R.Bottom := R.Bottom - 1; Canvas.Brush.Color := GetBorderColorTab; Canvas.FillRect(R); if TabIndex = Index then begin InflateRect(R, -1, -1); R.Bottom := R.Bottom + 1; end else InflateRect(R, -1, -1); end; tpBottom: begin InflateRect(R, -1, 0); if TabIndex <> Index then R.Bottom := R.Bottom + 1 else R.Top := R.Top + 3; Canvas.Brush.Color := GetBorderColorTab; Canvas.FillRect(R); if TabIndex = Index then begin InflateRect(R, -1, 0); R.Bottom := R.Bottom - 1; end else InflateRect(R, -1, -1); end; tpLeft: begin InflateRect(R, 0, -1); if TabIndex <> Index then R.Left := R.Left + 1 else R.Right := R.Right - 1; Canvas.Brush.Color := GetBorderColorTab; Canvas.FillRect(R); if TabIndex = Index then begin InflateRect(R, -1, -1); R.Right := R.Right + 1; end else InflateRect(R, -1, -1); end; tpRight: begin InflateRect(R, 0, -1); if TabIndex <> Index then // R.Left:=R.Left+1 else R.Left := R.Left + 3; Canvas.Brush.Color := GetBorderColorTab; Canvas.FillRect(R); if TabIndex = Index then begin InflateRect(R, -1, -1); R.Left := R.Left - 1; end else InflateRect(R, -1, -1); end; end; Canvas.Brush.Color := GetColorTab(Index); Canvas.FillRect(R); end else Begin InflateRect(R, -1, 0); // adjust the size of the tab creating blanks space between the tabs Canvas.Brush.Color := GetColorTab(Index); Canvas.FillRect(R); end; end; // get the index of the image (icon) if Control is TCustomTabControl then LImageIndex := TCustomTabControlClass(Control).GetImageIndex(Index) else LImageIndex := Index; // draw the image if (Images <> nil) and (LImageIndex >= 0) and (LImageIndex < Images.Count) then begin LIconRect := LayoutR; case TabPosition of tpTop, tpBottom: begin LIconRect.Left := LIconRect.Left + DxImage; LIconRect.Right := LIconRect.Left + LImageW; LayoutR.Left := LIconRect.Right; LIconRect.Top := LIconRect.Top + (LIconRect.Bottom - LIconRect.Top) div 2 - LImageH div 2; if (TabPosition = tpTop) and (Index = TabIndex) then OffsetRect(LIconRect, 0, -1) else if (TabPosition = tpBottom) and (Index = TabIndex) then OffsetRect(LIconRect, 0, 1); end; tpLeft: begin LIconRect.Bottom := LIconRect.Bottom - DxImage; LIconRect.Top := LIconRect.Bottom - LImageH; LayoutR.Bottom := LIconRect.Top; LIconRect.Left := LIconRect.Left + (LIconRect.Right - LIconRect.Left) div 2 - LImageW div 2; end; tpRight: begin LIconRect.Top := LIconRect.Top + DxImage; LIconRect.Bottom := LIconRect.Top + LImageH; LayoutR.Top := LIconRect.Bottom; LIconRect.Left := LIconRect.Left + (LIconRect.Right - LIconRect.Left) div 2 - LImageW div 2; end; end; if StyleServices.Available then StyleServices.DrawIcon(Canvas.Handle, LDetails, LIconRect, Images.Handle, LImageIndex); end; // draw the text of the tab if StyleServices.Available then begin LTextColor := GetColorTextTab(LThemedTab); if (TabPosition = tpTop) and (Index = TabIndex) then OffsetRect(LayoutR, 0, -1) else if (TabPosition = tpBottom) and (Index = TabIndex) then OffsetRect(LayoutR, 0, 1); if TabPosition = tpLeft then begin LTextX := LayoutR.Left + (LayoutR.Right - LayoutR.Left) div 2 - Canvas.TextHeight(Tabs[Index]) div 2; LTextY := LayoutR.Top + (LayoutR.Bottom - LayoutR.Top) div 2 + Canvas.TextWidth(Tabs[Index]) div 2; Canvas.Font.Color := LTextColor; AngleTextOut2(Canvas, 900, LTextX, LTextY, Tabs[Index]); end else if TabPosition = tpRight then begin LTextX := LayoutR.Left + (LayoutR.Right - LayoutR.Left) div 2 + Canvas.TextHeight(Tabs[Index]) div 2; LTextY := LayoutR.Top + (LayoutR.Bottom - LayoutR.Top) div 2 - Canvas.TextWidth(Tabs[Index]) div 2; Canvas.Font.Color := LTextColor; AngleTextOut2(Canvas, -900, LTextX, LTextY, Tabs[Index]); end else DrawControlText(Tabs[Index], LayoutR, DT_VCENTER or DT_CENTER or DT_SINGLELINE or DT_NOCLIP); end; end; procedure TTabColorControlStyleHook.Paint(Canvas: TCanvas); var LRect: TRect; LIndex: Integer; SavedDC: Integer; begin SavedDC := SaveDC(Canvas.Handle); try LRect := DisplayRect; ExcludeClipRect(Canvas.Handle, LRect.Left, LRect.Top, LRect.Right, LRect.Bottom); PaintBackground(Canvas); finally RestoreDC(Canvas.Handle, SavedDC); end; // Draw tabs , except the active for LIndex := 0 to TabCount - 1 do begin if LIndex = TabIndex then Continue; DrawTab(Canvas, LIndex); end; // Draw the body case TabPosition of tpTop: InflateRect(LRect, Control.Width - LRect.Right, Control.Height - LRect.Bottom); tpLeft: InflateRect(LRect, Control.Width - LRect.Right, Control.Height - LRect.Bottom); tpBottom: InflateRect(LRect, LRect.Left, LRect.Top); tpRight: InflateRect(LRect, LRect.Left, LRect.Top); end; if StyleServices.Available then begin if FUseBorder then begin Canvas.Brush.Color := GetBorderColorTab; Canvas.Rectangle(LRect.Left, LRect.Top, LRect.Right, LRect.Bottom); InflateRect(LRect, -1, -1); Canvas.Brush.Color := GetColorTab(TabIndex); Canvas.FillRect(LRect); end else begin Canvas.Brush.Color := GetColorTab(TabIndex); Canvas.FillRect(LRect); end; end; // Draw active tab if TabIndex >= 0 then DrawTab(Canvas, TabIndex); // paint the controls of the tab TWinControlClass(Control).PaintControls(Canvas.Handle, nil); end; procedure TTabColorControlStyleHook.PaintBackground(Canvas: TCanvas); var LColor: TColor; begin if StyleServices.Available then begin if Control.Parent is TTabSheet then LColor := GetColorTab(TTabSheet(Control.Parent).PageIndex) else LColor := StyleServices.GetSystemColor(clWindowFrame); Canvas.Brush.Color := LColor; Canvas.FillRect(Control.ClientRect); end; end; procedure TTabColorControlStyleHook.WMEraseBkgnd(var Message: TMessage); var LCanvas: TCanvas; begin if (Message.LParam = 1) and StyleServices.Available then begin LCanvas := TCanvas.Create; try LCanvas.Handle := HDC(Message.WParam); LCanvas.Brush.Color := GetColorTab(TabIndex); LCanvas.FillRect(Control.ClientRect); finally LCanvas.Handle := 0; LCanvas.Free; end; end; Message.Result := 1; Handled := True; end; end.