Files
lazarus-ccr/components/tvplanit/source/vpnavbarpainter.pas

1166 lines
36 KiB
ObjectPascal
Raw Normal View History

{$I vp.inc}
unit VpNavBarPainter;
interface
uses
{$IFDEF LCL}
LCLProc, LCLType, LCLIntf, LCLVersion,
{$ELSE}
Windows, Messages, MMSystem,
{$ENDIF}
Graphics, Classes, SysUtils, Controls, Buttons,
VpNavBar;
type
PRect = ^TRect;
TVpNavBarPainter = class
private
FNavBar: TVpCustomNavBar;
// Protected properties of the TVpCustomNavBar.
FActiveFolder: Integer;
FActiveItem: Integer;
FBackgroundColor: TColor;
FBackgroundImage: TBitmap;
FBackgroundMethod: TVpBackgroundMethod;
FButtonHeight: Integer;
FClientWidth: Integer;
FClientHeight: Integer;
FDrawingStyle: TVpFolderDrawingStyle;
FHotFolder: Integer;
FImages: TImageList;
FItemFont: TFont;
FItemSpacing: Integer;
FSelectedItem: Integer;
FSelectedItemFont: TFont;
FShowButtons: Boolean;
FSmallImagesSize: Integer;
FLargeImagesSize: Integer;
nabItemsRect: PRect;
nabLastMouseOverItem: Integer;
nabMouseDown: Boolean;
nabScrollUpBtn: TSpeedButton;
nabScrollDownBtn: TSpeedButton;
nabTopItem: Integer;
FFolderArea: TRect;
procedure DrawBackground(Canvas: TCanvas; R: TRect);
function DrawCoolTab(Canvas: TCanvas; R: TRect;
ATabIndex: Integer; ATabColor: TColor): TRect;
function DrawDefButton(Canvas: TCanvas; R: TRect;
ATabIndex: Integer): TRect;
function DrawEtchedButton(Canvas: TCanvas; R: TRect;
ATabIndex: Integer): TRect;
function DrawStandardTab(Canvas: TCanvas; R: TRect;
ATabIndex: Integer; ATabColor: TColor): TRect;
procedure DrawItemHighlight(Canvas: TCanvas; R: TRect; Enable: Boolean);
function DrawItemText(Canvas: TCanvas; AItem: TVpNavBtnItem; CurPos: Integer;
AText: String; AtLargeIcon: Boolean; out AHeight: Integer): Boolean;
function DrawLargeIcon(Canvas: TCanvas; AItem: TVpNavBtnItem;
CurPos: Integer): Boolean;
function DrawSmallIcon(Canvas: TCanvas; AItem: TVpNavBtnItem;
CurPos: Integer): Boolean;
function IsFocused(ATabIndex: Integer): Boolean;
function IsMouseOverFolder(ATabIndex: Integer): Boolean;
function IsMouseOverItem(ATabIndex: Integer): Boolean;
protected
procedure DrawActiveFolderItems(Canvas: TCanvas; var CurPos: Integer);
procedure DrawBottomFolderButtons(Canvas: TCanvas; ARect: TRect;
var CurPos: Integer);
procedure DrawTab(Canvas: TCanvas; R: TRect; ATabIndex: Integer);
procedure DrawTopFolderButtons(Canvas: TCanvas; ARect: TRect;
DrawFolder: Boolean; var CurPos: Integer);
procedure ProcessScrollButtons;
public
constructor Create(ANavBar: TVpCustomNavBar);
procedure Paint;
end;
function GetLargeIconDisplayName(Canvas: TCanvas; Rect: TRect; const Name: string): string;
implementation
uses
Math, Themes, imglist,
VpConst, VpMisc;
type
TVpNavBarOpener = class(TVpCustomNavBar);
constructor TVpNavBarPainter.Create(ANavBar: TVpCustomNavBar);
begin
inherited Create;
FNavBar := ANavBar;
FActiveFolder := TVpNavBarOpener(FNavBar).ActiveFolder;
FActiveItem := TVpNavBarOpener(FNavBar).ActiveItem;
FBackgroundColor := TVpNavBarOpener(FNavBar).BackgroundColor;
FBackgroundImage := TVpNavBarOpener(FNavBar).BackgroundImage;
FBackgroundMethod := TVpNavBarOpener(FNavBar).BackgroundMethod;
FButtonHeight := TVpNavBarOpener(FNavBar).GetRealButtonHeight;
FClientWidth := TVpNavBarOpener(FNavBar).ClientWidth;
FClientHeight := TVpNavBarOpener(FNavBar).ClientHeight;
FDrawingStyle := TVpNavBarOpener(FNavBar).DrawingStyle;
FHotFolder := TVpNavBarOpener(FNavBar).FHotFolder;
FImages := TVpNavBarOpener(FNavBar).Images;
FItemFont := TVpNavBarOpener(FNavBar).FItemFont;
FItemSpacing := TVpNavBarOpener(FNavBar).FItemSpacing;
FSelectedItem := TVpNavBarOpener(FNavBar).FSelectedItem;
FSelectedItemFont := TVpNavBarOpener(FNavBar).FSelectedItemFont;
FShowButtons := TVpNavBarOpener(FNavBar).FShowButtons;
// The nabItemsRect is populated in the Paint procedure, and it is needed in
// the NavBar as well. Therefore we use a pointer here!
nabItemsRect := @TVpNavBarOpener(FNavBar).nabItemsRect;
nabLastMouseOverItem := TVpNavBarOpener(FNavBar).nabLastMouseOverItem;
nabMouseDown := TVpNavBarOpener(FNavBar).nabMouseDown;
nabScrollUpBtn := TVpNavBarOpener(FNavBar).nabScrollUpBtn;
nabScrollDownBtn := TVpNavBarOpener(FNavBar).nabScrollDownBtn;
nabTopItem := TVpNavBarOpener(FNavBar).nabTopItem;
FFolderArea := TVpNavBarOpener(FNavBar).nabGetFolderArea(FActiveFolder);
end;
{ Draw the items for the active folder }
procedure TVpNavBarPainter.DrawActiveFolderItems(Canvas: TCanvas; var CurPos: Integer);
const
BUTTON_DISTANCE = 8;
LARGE_ICON_TEXT_DISTANCE = 2;
SMALL_ICON_TEXT_DISTANCE = 6;
var
folder: TVpNavFolder;
item: TVpNavBtnItem;
J: Integer;
text: String;
h: Integer;
R: TRect;
dx, dy: Integer;
{$IFDEF LCL}
{$IF LCL_FullVersion >= 1090000}
imgres: TScaledImageListResolution;
f: Double;
ppi: Integer;
{$IFEND}
{$ENDIF}
begin
folder := FNavBar.Folders[FActiveFolder];
// Distance between icon and text, for large icons vertically, for small icons
// horizontally
dy := ScaleY(LARGE_ICON_TEXT_DISTANCE, DesignTimeDPI);
dx := ScaleX(SMALL_ICON_TEXT_DISTANCE, DesignTimeDPI);
{ If an image list is assigned then use the image size.
If no image list is assinged then assume a 32 x 32 image size.
The size of the small images is always half size of the large images. }
if FImages <> nil then begin
{$IFDEF LCL}{$IF LCL_FullVersion >= 1090000}
with TVpNavBarOpener(FNavBar) do begin
f := FCanvasScaleFactor;
ppi := Font.PixelsPerInch;
imgRes := FImages.ResolutionForPPI[FImagesWidth, ppi, f];
end;
FLargeImagesSize := imgRes.Width;
{$ELSE}
FLargeImagesSize := FImages.Width;
{$ENDIF}{$ENDIF}
end else
FLargeImagesSize := 32;
FSmallImagesSize := FLargeImagesSize div 2;
if folder.FolderType = ftDefault then begin
if folder.ItemCount = 0 then
exit;
// Distance of top-most icon to the last upper button
Inc(CurPos, ScaleY(BUTTON_DISTANCE, DesignTimeDPI));
with nabItemsRect^ do begin
Top := CurPos;
Left := 0;
Right := FNavBar.ClientWidth;
Bottom := FNavBar.ClientHeight - (FNavBar.FolderCount - FActiveFolder - 1) * FButtonHeight;
end;
for J := 0 to folder.ItemCount-1 do begin
R := TVpNavBtnItem(folder.Items[J]).LabelRect;
R.Bottom := nabItemsRect^.Bottom + 1;
TVpNavBtnItem(folder.Items[J]).LabelRect := R;
end;
for J := nabTopItem to folder.ItemCount-1 do begin
if (FSelectedItem = J) then
Canvas.Font := FSelectedItemFont
else
Canvas.Font := FItemFont;
item := Folder.Items[J];
{ If the caption is empty at design time display the item's name instead }
if (csDesigning in FNavBar.ComponentState) and (item.Caption = '') then
text := item.Name
else
text := item.Caption;
if folder.IconSize = isLarge then begin
{ Large icons }
if not DrawLargeIcon(Canvas, item, CurPos) then
Continue;
{make the icon's bottom blend into the label's top}
R := item.IconRect;
inc(R.Bottom, dy);
item.IconRect := R;
CurPos := item.IconRect.Bottom;
{now draw the text}
if not DrawItemText(Canvas, item, CurPos, text, true, h) then
Continue;
Inc(CurPos, FItemSpacing + h);
end else
begin
{ Small Icons }
if not DrawSmallIcon(Canvas, item, CurPos) then
Continue;
{make the icon's right blend into the label's left}
R := item.IconRect;
inc(R.Right, dx);
item.IconRect := R;
{now draw the text}
if not DrawItemText(Canvas, item, CurPos, text, false, h) then
Continue;
Inc(CurPos, FItemSpacing + h);
end; { if folder.IconSize ... }
end; { for J }
end; { if folder.FolderType = ftDefault ... }
end;
procedure TVpNavBarPainter.DrawBackground(Canvas: TCanvas; R: TRect);
var
rowStart: Integer;
lLeft, lHeight, lWidth: Integer;
begin
if FBackgroundImage.Empty or (FBackgroundMethod = bmNone) or (FNavBar.FolderCount = 0) then
begin
Canvas.Brush.Color := FBackgroundColor;
Canvas.FillRect(R.Left, R.Top, R.Right, R.Bottom);
end else
begin
case FBackgroundMethod of
bmNormal:
begin
if (FBackgroundImage.Width < WidthOf(R)) or (FBackgroundImage.Height < HeightOf(R))
then begin
Canvas.Brush.Color := FBackgroundColor;
Canvas.Rectangle(R.Left, R.Top, R.Right, R.Bottom);
end;
Canvas.Draw(R.Left, R.Top, FBackgroundImage);
end;
bmStretch:
Canvas.StretchDraw(R, FBackgroundImage);
bmTile:
begin
{Tile the background in the default folder}
rowStart := 0;
lHeight := FBackgroundImage.Height;
lWidth := FBackgroundImage.Width;
lLeft := 0;
while (rowStart < FNavBar.ClientRect.Bottom) do begin
while (lLeft < FNavBar.ClientRect.Right) do begin
Canvas.Draw(R.Left + lLeft, rowStart, FBackgroundImage);
Inc(lLeft, lWidth);
end;
lLeft := 0;
Inc(rowStart, lHeight)
end;
end;
end;
end;
end;
{ Draw the folder buttons at the bottom }
procedure TVpNavBarPainter.DrawBottomFolderButtons(Canvas: TCanvas; ARect: TRect;
var CurPos: Integer);
var
I: Integer;
MyRect: TRect;
begin
MyRect := ARect;
Canvas.Font := FNavBar.Font;
// SetBkMode(Canvas.Handle, bkMode);
// todo---> SetBkColor(Canvas.Handle, bkColor);
CurPos := FNavBar.ClientHeight - FButtonHeight;
for I := FNavBar.FolderCount-1 downto FActiveFolder+1 do begin
MyRect.Top := CurPos;
MyRect.Bottom := CurPos + FButtonHeight;
FNavBar.Folders[I].Rect := MyRect;
{Draw the bottom tabs based on the selected style...}
DrawTab(Canvas, MyRect, I);
Dec(CurPos, FButtonHeight);
end;
end;
{ Draw a "cool" tab button.
Returns the usable text area inside the tab rect.}
function TVpNavBarPainter.DrawCoolTab(Canvas: TCanvas; R: TRect;
ATabIndex: Integer; ATabColor: TColor): TRect;
var
Points: array[1..5] of TPoint;
begin
Result := R;
with Canvas do begin
{Fill the tab area}
Brush.Style := bsSolid;
if (ATabIndex = 0) then
Brush.Color := clBtnFace
else
Brush.Color := ATabColor;
FillRect(R);
if IsMouseOverFolder(ATabIndex) then
; // do what?
{Draw the bottom, left line}
Pen.Color := clBlack;
MoveTo(R.Left, R.Bottom - 1);
LineTo(R.Left + 5, R.Bottom - 1);
{Draw the bottom, left curve}
Points[1] := Point(R.Left + 5, R.Bottom - 1); {Start point}
Points[2] := Point(R.Left + 11, R.Bottom - 2); {Control point}
Points[3] := Point(R.Left + 12, R.Bottom - 7); {Control point}
Points[4] := Point(R.Left + 13, R.Bottom - 9); {End point}
{$IFNDEF VERSION4}
{$IFDEF CBuilder}
PolyBezier(Points);
{$ELSE}
Polyline(Points);
{$ENDIF}
{$ELSE}
PolyBezier([Points[1], Points[2], Points[3], Points[4]]);
{$ENDIF}
{Draw the left side of the tab}
MoveTo(R.Left + 13, R.Bottom - 9);
LineTo(R.Left + 13, R.Top + 8);
{Draw the top, left corner of the tab}
Points[1] := Point(R.Left + 13, R.Top + 8); {Start point}
Points[2] := Point(R.Left + 14, R.Top + 6); {Control point}
Points[3] := Point(R.Left + 15, R.Top + 1); {Control point}
Points[4] := Point(R.Left + 21, R.Top + 0); {End point}
{$IFNDEF VERSION4}
{$IFDEF CBuilder}
PolyBezier(Points);
{$ELSE}
Polyline(Points);
{$ENDIF}
{$ELSE}
PolyBezier([Points[1], Points[2], Points[3], Points[4]]);
{$ENDIF}
{Draw the top of the tab}
MoveTo(R.Left + 21, R.Top);
LineTo(R.Right - 16, R.Top);
{Draw the top right corner of the tab}
Points[1] := Point(R.Right - 16, R.Top);
Points[2] := Point(R.Right - 10, R.Top + 1);
Points[3] := Point(R.Right - 9, R.Top + 6);
Points[4] := Point(R.Right - 8, R.Top + 8);
{$IFNDEF VERSION4}
{$IFDEF CBuilder}
PolyBezier(Points);
{$ELSE}
Polyline(Points);
{$ENDIF}
{$ELSE}
PolyBezier([Points[1], Points[2], Points[3], Points[4]]);
{$ENDIF}
{Draw the right side of the tab}
MoveTo(R.Right - 8, R.Top + 8);
LineTo(R.Right - 8, R.Bottom - 9);
{Draw the bottom, Right curve of the tab which should finish against the
right side.}
Points[1] := Point(R.Right - 8, R.Bottom - 9);
Points[2] := Point(R.Right - 7, R.Bottom - 7);
Points[3] := Point(R.Right - 6, R.Bottom - 2);
Points[4] := Point(R.Right, R.Bottom - 1);
{$IFNDEF VERSION4}
{$IFDEF CBuilder}
Canvas.PolyBezier(Points);
{$ELSE}
Canvas.Polyline(Points);
{$ENDIF}
{$ELSE}
PolyBezier([Points[1], Points[2], Points[3], Points[4]]);
{$ENDIF}
if ATabIndex = 0 then begin
Brush.Color := ATabColor;
FloodFill((R.Left + R.Right) div 2, (R.Top + R.Bottom) div 2, clBtnFace, fsSurface);
end;
end;
Result := Rect(R.Left + 1, R.Top + 2, R.Right - 2, R.Bottom);
end;
{ Draw regular buttons
Returns the usable text area inside the tab rect.}
function TVpNavBarPainter.DrawDefButton(Canvas: TCanvas; R: TRect;
ATabIndex: Integer): TRect;
var
tb: TThemedButton;
details: TThemedElementDetails;
begin
Result := R;
if ThemeServices.ThemesEnabled then begin
// themed button
if IsMouseOverFolder(ATabIndex) and nabMouseDown then
tb := tbPushButtonPressed
else
if IsMouseOverFolder(ATabIndex) then
tb := tbPushButtonHot
else
tb := tbPushButtonNormal;
details := ThemeServices.GetElementDetails(tb);
InflateRect(R, 1, 1);
ThemeServices.DrawElement(Canvas.Handle, details, R);
end else
begin
// non-themed button
Canvas.Brush.Color := clBtnFace;
Canvas.FillRect(R);
if nabMouseDown and IsMouseOverFolder(ATabIndex) then
begin
if R.Top = 0 then R.Top := 1;
Canvas.Pen.Color := clBtnHighlight; // bright at bottom/right
Canvas.MoveTo(R.Left, R.Bottom-1);
Canvas.LineTo(R.Right-1, R.Bottom-1);
Canvas.LineTo(R.Right-1, R.Top-1);
Canvas.Pen.Color := clGray; // dark at top/left
Canvas.MoveTo(R.Left, R.Bottom-2);
Canvas.LineTo(R.Left, R.Top);
Canvas.LineTo(R.Right-1, R.Top);
Canvas.Pen.Color := clBtnShadow; // shadow at top/left
Canvas.MoveTo(R.Left+1, R.Bottom-2);
Canvas.LineTo(R.Left+1, R.Top);
Canvas.LineTo(R.Right-2, R.Top);
end else
begin
Canvas.Pen.Color := clGray; // bottom/right
Canvas.MoveTo(R.Left, R.Bottom-1);
Canvas.LineTo(R.Right-1, R.Bottom-1);
Canvas.LineTo(R.Right-1, R.Top-1);
Canvas.Pen.Color := clBtnHighlight; // top/left
Canvas.MoveTo(R.Left, R.Bottom-2);
Canvas.LineTo(R.Left, R.Top);
Canvas.LineTo(R.Right-1, R.Top);
Canvas.Pen.Color := clBtnShadow; // bottom/right shadow
Canvas.MoveTo(R.Left+1, R.Bottom-2);
Canvas.LineTo(R.Right-2, R.Bottom-2);
Canvas.LineTo(R.Right-2, R.Top);
end;
end;
end;
{ Draw regular etched (Win98 style) buttons
Returns the usable text area inside the tab rect.}
function TVpNavBarPainter.DrawEtchedButton(Canvas: TCanvas; R: TRect;
ATabIndex: Integer): TRect;
begin
with Canvas do begin
Brush.Color := clBtnFace;
FillRect(R);
Frame3D(R, 1, bvLowered);
if not IsMouseOverFolder(aTabIndex) then
Frame3D(R, 1, bvRaised);
{
// InflateRect(R, -1, -1);
if IsMouseOverFolder(ATabIndex) then
Frame3D(R, 1, bvLowered) else
Frame3D(r, 1, bvRaised);
}
end;
Result := R;
end;
procedure TVpNavBarPainter.DrawItemHighlight(Canvas: TCanvas; R: TRect;
Enable: Boolean);
const
PUSHBUTTON_DETAILS: array[boolean] of TThemedButton = (tbPushButtonHot, tbPushButtonPressed);
TOOLBAR_DETAILS: array[boolean] of TThemedToolbar = (ttbButtonHot, ttbButtonPressed);
var
details: TThemedElementDetails;
margin: integer;
begin
if Enable then begin
if ThemeServices.ThemesEnabled and (TVpNavBarOpener(FNavBar).ItemTheme <> itNoTheme) then
begin
margin := ScaleX(2, DesigntimeDPI);
InflateRect(R, margin, margin);
case TVpNavBarOpener(FNavBar).ItemTheme of
itPushButton:
details := ThemeServices.GetElementDetails(PUSHBUTTON_DETAILS[nabMousedown]);
itToolbar:
details := ThemeServices.GetElementDetails(TOOLBAR_DETAILS[nabMouseDown]);
end;
ThemeServices.DrawElement(Canvas.Handle, details, R);
end else
begin
if nabMouseDown then
Canvas.Pen.Color := clBlack
else
Canvas.Pen.Color := clWhite;
Canvas.MoveTo(R.Left-1, R.Bottom+1);
Canvas.LineTo(R.Left-1, R.Top-1);
Canvas.LineTo(R.Right+1, R.Top-1);
if nabMouseDown then
Canvas.Pen.Color := clWhite
else
Canvas.Pen.Color := clBlack;
Canvas.LineTo(R.Right+1, R.Bottom+1);
Canvas.LineTo(R.Left-1, R.Bottom+1);
Canvas.Brush.Color := FBackgroundColor;
end;
end;
end;
function TVpNavBarPainter.DrawItemText(Canvas: TCanvas; AItem: TVpNavBtnItem;
CurPos: Integer; AText: String; AtLargeIcon: Boolean; out AHeight: Integer): Boolean;
const
HOR_MARGIN = 5;
var
R: TRect;
s: String;
txtWidth: Integer;
bkMode: Integer;
horDist: Integer;
begin
Result := false;
horDist := ScaleX(HOR_MARGIN, DesignTimeDPI);
if AtLargeIcon then
begin
R.Top := CurPos;
R.Bottom := CurPos + FButtonHeight div 2 - 7; // what is -7 good for?
R.Left := 0;
R.Right := FNavBar.ClientWidth - 1;
AItem.LabelRect := R;
AItem.DisplayName := GetLargeIconDisplayName(Canvas, R, AText);
txtWidth := Canvas.TextWidth(AItem.DisplayName);
R.Left := Max(horDist, (FNavBar.ClientWidth - txtWidth) div 2);
R.Right := Min(R.Left + txtWidth, FNavBar.ClientWidth - hordist);
AItem.LabelRect := R;
if R.Top > nabItemsRect^.Bottom then
Exit;
s := AItem.DisplayName;
DrawText(Canvas.Handle, PChar(s), Length(s), R, DT_CENTER or DT_VCENTER or DT_WORDBREAK or DT_CALCRECT);
txtWidth := WidthOf(R);
R.Left := (FNavBar.ClientWidth - txtWidth) div 2;
R.Right := R.Left + txtWidth + 1;
AItem.LabelRect := R;
bkMode := SetBkMode(Canvas.Handle, TRANSPARENT);
AHeight:= DrawText(Canvas.Handle, PChar(s), Length(s), R, DT_CENTER or DT_VCENTER or DT_WORDBREAK);
SetBkMode(Canvas.Handle, bkMode);
end else
begin
R.Top := CurPos;
R.Bottom := CurPos + Canvas.TextHeight('Tg');
// R.Bottom := CurPos + FButtonHeight div 2 - 7;
R.Left := AItem.IconRect.Right;
R.Right := FNavBar.ClientWidth - 2*AItem.IconRect.Left; // - 2*horDist;
// R.Right := R.Left + FNavBar.ClientWidth - R.Left - ScaleX(7, DesignTimeDPI);
AItem.LabelRect := R;
if R.Top > nabItemsRect^.Bottom then
Exit;
// Measure size of display string
R := AItem.LabelRect;
s := GetDisplayString(Canvas, AText, 1, WidthOf(R));
AItem.DisplayName := s;
DrawText(Canvas.Handle, PChar(s), Length(s), R, DT_LEFT or DT_CALCRECT);
txtWidth := WidthOf(R);
AHeight := HeightOf(R);
R.Right := R.Left + txtWidth + 1;
{$IFDEF MSWINDOWS}
OffsetRect(R, 0, -1); // Better centering of text
{$ENDIF}
AItem.LabelRect := R;
bkMode := SetBkMode(Canvas.Handle, TRANSPARENT);
Canvas.TextOut(R.Left, (R.Top + R.Bottom - AHeight) div 2, s);
// AHeight := DrawText(Canvas.Handle, PChar(s), Length(s), R, DT_LEFT or DT_VCENTER);
SetBkMode(Canvas.Handle, bkMode);
if AHeight < FSmallImagesSize then
AHeight := FSmallImagesSize;
end;
Result := true;
end;
{ Draw a large icon: centered horizontally, text to be drawn underneath icon.
CurPos is upper edge of the icon. }
function TVpNavBarPainter.DrawLargeIcon(Canvas: TCanvas; AItem: TVpNavBtnItem;
CurPos: Integer): Boolean;
const
MARGIN = 2;
var
W, H: Integer;
R: TRect;
dist: Integer;
{$IFDEF LCL}{$IF LCL_FullVersion >= 1090000}
imgres: TScaledImageListResolution;
f: Double;
ppi: Integer;
{$ENDIF}{$ENDIF}
begin
Result := false;
dist := ScaleX(MARGIN, DesignTimeDPI);
W := FLargeImagesSize + 2*dist;
H := FLargeImagesSize + 2*dist;
R.Top := CurPos;
R.Bottom := CurPos + H;
R.Left := (FNavBar.ClientWidth - W) div 2;
R.Right := R.Left + W;
if R.Top > nabItemsRect^.Bottom then
exit;
AItem.IconRect := R;
if FShowButtons then begin
DrawItemHighlight(Canvas, R, FActiveItem = AItem.Index);
if Assigned(FImages) and (AItem.IconIndex >= 0) and (AItem.IconIndex < FImages.Count) then
begin
{$IFDEF LCL}{$IF LCL_FullVersion >= 1090000}
with TVpNavBarOpener(FNavBar) do begin
f := FCanvasScalefactor;
ppi := Font.PixelsPerInch;
end;
imgRes := FImages.ResolutionForPPI[FImages.Width, ppi, f];
imgRes.Draw(Canvas, R.Left + dist, R.Top + dist, AItem.IconIndex);
{$ELSE}
FImages.Draw(Canvas, R.Left + dist, R.Top + dist, AItem.IconIndex);
{$ENDIF}{$ENDIF}
end;
end;
Result := true;
end;
{ Draw a small icon (16x16) }
function TVpNavBarPainter.DrawSmallIcon(Canvas: TCanvas; AItem: TVpNavBtnItem;
CurPos: Integer): Boolean;
const
DELTA = 8;
var
lOffset: Integer;
R: TRect;
del: Integer;
{$IFDEF LCL}{$IF LCL_FullVersion >= 1090000}
imgres: TScaledImageListResolution;
f: Double;
ppi: Integer;
{$ELSE}
bmp: TBitmap;
{$ENDIF}{$ENDIF}
begin
Result := false;
{glyph is at the left}
R.Top := CurPos;
del := ScaleY(DELTA, DesignTimeDPI);
lOffset := abs(Canvas.Font.Height) div 2;
if lOffset > del then
R.Top := R.Top + lOffset - del;
R.Bottom := R.Top + FSmallImagesSize;
R.Left := del;
R.Right := R.Left + FSmallImagesSize;
AItem.IconRect := R;
if R.Top > nabItemsRect^.Bottom then
Exit; // Returns false
if FShowButtons then begin
DrawItemHighlight(Canvas, R, FActiveItem = AItem.Index);
if Assigned(FImages) then begin
{$IFDEF LCL}{$IF LCL_FullVersion >= 1090000}
with TVpNavBarOpener(FNavBar) do begin
f := FCanvasScalefactor;
ppi := Font.PixelsPerInch;
end;
imgRes := FImages.ResolutionForPPI[FImages.Width div 2, ppi, f];
imgRes.Draw(Canvas, R.Left, R.Top, AItem.IconIndex);
{$ELSE}
bmp := TBitmap.Create;
try
FImages.GetBitmap(AItem.IconIndex, bmp);
bmp.Transparent := true;
Canvas.StretchDraw(AItem.IconRect, bmp);
finally
bmp.Free;
end;
{$ENDIF}{$ENDIF}
end;
end;
Result := true;
end;
{ Draw a "standard" tab button.
Returns the usable text area inside the tab rect.}
function TVpNavBarPainter.DrawStandardTab(Canvas: TCanvas; R: TRect;
ATabIndex: Integer; ATabColor: TColor): TRect;
const
_LEFT_DISTANCE = 10;
_RIGHT_DISTANCE = 2;
_RADIUS = 2;
var
leftDist: Integer;
rightDist: Integer;
radius: Integer;
begin
Result := R;
leftDist := ScaleX(_LEFT_DISTANCE, DesignTimeDPI);
rightDist := ScaleX(_RIGHT_DISTANCE, DesignTimeDPI);
radius := ScaleX(_RADIUS, DesignTimeDPI);
{fill the tab area}
Canvas.Brush.Style := bsSolid;
Canvas.Brush.Color := clBtnFace;
Canvas.FillRect(R);
{fill the tab area}
if ATabIndex > 0 then begin
Canvas.Brush.Color := ATabColor;
Canvas.Brush.Style := bsSolid;
Canvas.Pen.Color := ATabColor;
Canvas.Polygon([
Point(R.Left, R.Bottom),
Point(R.Left, R.Top),
Point(R.Right, R.Top),
Point(R.Right, R.Bottom)
]);
end;
{Draw Tab}
Canvas.Brush.Color := ATabColor;
Canvas.Brush.Style := bsSolid;
Canvas.Pen.Color := ATabColor;
Canvas.Polygon([
Point(R.Left + leftDist, R.Bottom - 1), // 10 -1
Point(R.Left + leftDist, R.Top + radius + 1), // 10 3
Point(R.Left + leftDist + radius, R.Top + 1), // 12 1
Point(R.Right - rightDist - radius, R.Top + 1), // -4 1
Point(R.Right - rightDist, R.Top + radius + 1), // -2 3
Point(R.Right - rightDist, R.Bottom - 1) // -2 -1
]);
{
Point(R.Left + 10, R.Bottom - 1),
Point(R.Left + 10, R.Top + 3),
Point(R.Left + 12, R.Top + 1),
Point(R.Right - 4, R.Top + 1),
Point(R.Right - 2, R.Top + 3),
Point(R.Right - 2, R.Bottom - 1)
]); }
{highlight tab}
Canvas.Pen.Color := clBtnHighlight;
{
Canvas.PolyLine([
Point(R.Left, R.Bottom - 2),
Point(R.Left + 8, R.Bottom - 2),
Point(R.Left + 9, R.Bottom - 3),
Point(R.Left + 9, R.Top + 3),
Point(R.Left + 11, R.Top + 1),
Point(R.Right - 1, R.Top + 1)
]);}
Canvas.PolyLine([
Point(R.Left, R.Bottom - radius), // 0 -2
Point(R.Left + leftDist - radius, R.Bottom - radius), // 8 -2
Point(R.Left + leftdist - 1, R.Bottom - radius - 1), // 9 -3
Point(R.Left + leftDist - 1, R.Top + radius + 1), // 9 3
Point(R.Left + leftDist + 1, R.Top + 1), // 11 1
Point(R.Right - 1, R.Top + 1) // -1 1
]);
{draw border}
Canvas.Pen.Color := clBlack;
{
Canvas.PolyLine([
Point(R.Left, R.Bottom - 1),
Point(R.Left + 9, R.Bottom - 1),
Point(R.Left + 10, R.Bottom - 2),
Point(R.Left + 10, R.Top + 4),
Point(R.Left + 11, R.Top + 3),
Point(R.Left + 12, R.Top + 2),
Point(R.Right - 2, R.Top + 2),
Point(R.Right - 1, R.Top + 3),
Point(R.Right - 1, R.Bottom - 1)
]);}
Canvas.PolyLine([
Point(R.Left, R.Bottom - 1), // 0 -1
Point(R.Left + leftDist - 1, R.Bottom - 1), // 9 -1
Point(R.Left + leftDist, R.Bottom - radius), // 10 -2
Point(R.Left + leftdist, R.Top + radius + 2), // 10 +4
Point(R.Left + leftdist + 1, R.Top + radius + 1), // 11 +3
Point(R.Left + leftdist + 2, R.Top + radius), // 12 +2
Point(R.Right - radius, R.Top + radius), // -2 +2
Point(R.Right - radius + 1, R.Top + radius + 1), // -1 +3
Point(R.Right -1, R.Bottom - 1) // -1 -1
]);
Result := Rect(R.Left + 1, R.Top + 2, R.Right - 2, R.Bottom);
end;
procedure TVpNavBarPainter.DrawTab(Canvas: TCanvas; R: TRect; ATabIndex: Integer);
var
displayTxt: String;
TR: TRect;
Flags: Integer;
folder: TVpNavFolder;
savedFontstyle: TFontStyles;
begin
case FDrawingStyle of
dsDefButton:
TR := DrawDefButton(Canvas, R, ATabIndex);
dsEtchedButton:
TR := DrawEtchedButton(Canvas, R,ATabIndex);
dsCoolTab:
TR := DrawCoolTab(Canvas, R, ATabIndex, FBackgroundColor);
dsStandardTab:
TR := DrawStandardTab(Canvas, R, ATabIndex, FBackgroundColor);
end;
// if IsMouseOverFolder(ATabIndex) then
// OffsetRect(TR, -1, -1);
//inc(TR.Top);
folder := FNavBar.Folders[ATabIndex];
displayTxt := folder.DisplayName;
savedFontstyle := Canvas.Font.Style;
if folder.Enabled then begin
SetBkMode(Canvas.Handle, TRANSPARENT);
if IsMouseOverFolder(ATabIndex) then
Canvas.Font.Style := Canvas.Font.Style + [fsBold];
Flags := DT_CENTER or DT_VCENTER or DT_SINGLELINE or DT_NOPREFIX;
DrawText(Canvas.Handle, PChar(displayTxt), Length(displayTxt), TR, Flags);
if IsMouseOverFolder(ATabIndex) and not nabMouseDown then begin
case FDrawingStyle of
dsDefButton:
begin { Regular button style. }
// InflateRect(TR, 1, 1);
// inc(TR.Left);
// Canvas.Frame3D(TR, 1, bvRaised);
end;
dsEtchedButton:
begin { Etched style (Outlook98). }
// InflateRect(TR, 1, 1);
// inc(TR.Top);
// inc(TR.Left);
// Canvas.Frame3D(TR, 1, bvRaised);
{
Canvas.Pen.Color := clWindowFrame;
Canvas.MoveTo(TR.Right - 2, TR.Top);
Canvas.LineTo(TR.Right - 2, TR.Bottom - 1);
Canvas.LineTo(0, TR.Bottom - 1);
Canvas.Pen.Color := clBtnShadow;
if ATabIndex = FActiveFolder then
lOffset := 1
else
lOffset := 2;
Canvas.MoveTo(TR.Right - 3, TR.Top - 2);
Canvas.LineTo(TR.Right - 3, TR.Bottom - lOffset);
Canvas.LineTo(1, TR.Bottom - lOffset);
if ATabIndex = FActiveFolder then
Canvas.Pixels[1, TR.Bottom - lOffset] := clBtnHighlight;
}
end;
end; // case
end;
end
else
begin
{use shadow text for inactive folder text}
Canvas.Font.Color := clHighlightText;
SetBkMode(Canvas.Handle, OPAQUE);
DrawText(Canvas.Handle, PChar(displayTxt), Length(displayTxt), TR, Flags);
SetBkMode(Canvas.Handle, TRANSPARENT);
Canvas.Font.Color := clBtnShadow;
OffsetRect(TR, -2, -1);
DrawText(Canvas.Handle, PChar(displayTxt), Length(displayTxt), TR, Flags);
Canvas.Font.Color := FNavBar.Font.Color;
end;
Canvas.Font.Style := savedFontStyle;
end;
procedure TVpNavBarPainter.DrawTopFolderButtons(Canvas: TCanvas;
ARect: TRect; DrawFolder: Boolean; var CurPos: Integer);
var
I: Integer;
MyRect: TRect;
begin
CurPos := 0;
MyRect := ARect;
{ Draw the folder buttons at the top }
if DrawFolder then begin
for I := 0 to FActiveFolder do begin
MyRect.Top := CurPos;
MyRect.Bottom := CurPos + FButtonHeight;
FNavBar.Folders[I].Rect := MyRect;
{Draw the top tabs based on the selected style...}
DrawTab(Canvas, MyRect, I);
Inc(CurPos, FButtonHeight);
end;
end else begin
if FDrawingStyle = dsEtchedButton then begin
{ Draw border around control. }
Canvas.Pen.Color := clBtnHighlight;
Canvas.MoveTo(FNavBar.Width - 1, FNavBar.Top);
Canvas.LineTo(FNavBar.Width - 1, FNavBar.Height - 1);
Canvas.LineTo(0, FNavBar.Height - 1);
Canvas.Pen.Color := clWindowFrame;
Canvas.MoveTo(0, FNavBar.Height - 1);
Canvas.LineTo(0, 1);
Canvas.LineTo(FNavBar.Width - 2, 1);
end;
CurPos := 0;
end;
end;
function TVpNavBarPainter.IsFocused(ATabIndex: Integer): Boolean;
begin
Result := ATabIndex = FHotFolder;
end;
function TVpNavBarPainter.IsMouseOverFolder(ATabIndex: Integer): Boolean;
begin
Result := ATabIndex = FHotFolder;
end;
function TVpNavBarPainter.IsMouseOverItem(ATabIndex: Integer): Boolean;
begin
Result := ATabIndex = nabLastMouseOverItem;
end;
procedure TVpNavBarPainter.Paint;
var
DrawBmp: TBitmap;
DrawFolder: Boolean;
TR: TRect;
CurPos: Integer = 0;
MyRect: TRect;
begin
MyRect := FNavBar.ClientRect;
DrawBmp := TBitmap.Create;
try
DrawBmp.Width := FClientWidth;
DrawBmp.Height := FClientHeight;
DrawBmp.Transparent := false;
DrawBmp.Canvas.Font := FNavBar.Font;
DrawBmp.Canvas.Pen.Color := FBackgroundColor;
DrawBmp.Canvas.Brush.Color := FBackgroundColor;
DrawFolder := (FNavBar.FolderCount > 0);
if DrawFolder then
TR := FFolderArea
else
TR := FNavBar.ClientRect;
DrawBackground(DrawBmp.Canvas, TR);
{ Draw background }
if FNavBar.FolderCount = 0 then begin
nabScrollUpBtn.Visible := False;
nabScrollDownBtn.Visible := False;
FNavBar.Canvas.CopyMode := cmSrcCopy;
FNavBar.Canvas.CopyRect(MyRect, DrawBmp.Canvas, Rect(0, 0, DrawBmp.Width,DrawBmp.Height));
Exit;
end;
{ Draw the folder buttons at the top }
DrawTopFolderButtons(DrawBmp.Canvas, MyRect, DrawFolder, CurPos);
{ Draw active folder items }
DrawActiveFolderItems(DrawBmp.Canvas, CurPos);
{ Draw the folder buttons at the bottom }
DrawBottomFolderButtons(DrawBmp.Canvas, MyRect, CurPos);
{ Copy the buffer bitmap to the control }
FNavBar.Canvas.CopyMode := cmSrcCopy;
FNavBar.Canvas.CopyRect(MyRect, DrawBmp.Canvas, Rect(0, 0, DrawBmp.Width, DrawBmp.Height));
{ Show/hide scroll buttons }
ProcessScrollButtons;
finally
DrawBmp.Free;
end;
end;
procedure TVpNavBarPainter.ProcessScrollButtons;
const
DISTANCE = 5;
var
dist: Integer;
w, h: Integer;
begin
if not (csDesigning in FNavBar.ComponentState) then begin
dist := ScaleY(DISTANCE, DesignTimeDPI);
{show the top scroll button}
if TVpNavBarOpener(FNavBar).nabShowScrollUp() then begin
w := nabScrollUpBtn.Width;
nabScrollUpBtn.Top := FNavBar.Folders[FActiveFolder].Rect.Bottom + dist;
nabScrollUpBtn.Left := FNavBar.ClientWidth - w - dist;
nabScrollUpBtn.Visible := True;
end else
nabScrollUpBtn.Visible := False;
{show the bottom scroll button}
if TVpNavBarOpener(FnavBar).nabShowScrollDown() then begin
w := nabScrollDownBtn.Width;
h := nabScrollDownBtn.Height;
if FActiveFolder = FNavBar.FolderCount-1 then
{there are no folders beyond the active one}
nabScrollDownBtn.Top := FNavBar.ClientHeight - h - dist
else
nabScrollDownBtn.Top := FNavBar.Folders[FActiveFolder+1].Rect.Top - h - dist;
nabScrollDownBtn.Left := FNavBar.ClientWidth - w - dist;
nabScrollDownBtn.Visible := True;
end else
nabScrollDownBtn.Visible := False;
end;
end;
{ Given a string, and a rectangle, find the string that can be displayed
using two lines. Add ellipsis to the end of each line if necessary and
possible}
function GetLargeIconDisplayName(Canvas: TCanvas; Rect: TRect;
const Name: string): string;
var
TestRect: TRect;
SH, DH: Integer;
Buf: array[0..255] of Char;
I: Integer;
TempName: string;
Temp2: string;
begin
TempName := Trim(Name);
{get single line height}
with TestRect do begin
Left := 0;
Top := 0;
Right := 1;
Bottom := 1;
end;
SH := DrawText(Canvas.Handle, 'W W', 3, TestRect, DT_SINGLELINE or DT_CALCRECT);
{get double line height}
with TestRect do begin
Left := 0;
Top := 0;
Right := 1;
Bottom := 1;
end;
DH := DrawText(Canvas.Handle, 'W W', 3, TestRect, DT_WORDBREAK or DT_CALCRECT);
{see if the text can fit within the existing rect without growing}
TestRect := Rect;
StrPLCopy(Buf, TempName, 255);
DrawText(Canvas.Handle, Buf, Length(TempName), TestRect, DT_WORDBREAK or DT_CALCRECT);
I := Pos(' ', TempName);
if (HeightOf(TestRect) = SH) or (I < 2) then
Result := GetDisplayString(Canvas, TempName, 1, WidthOf(Rect))
else begin
{the first line only has ellipsis if there's only one word on it and
that word won't fit}
Temp2 := GetDisplayString(Canvas, Copy(TempName, 1, I-1), 1, WidthOf(Rect));
if CompareStr(Temp2, Copy(TempName, 1, I-1)) <> 0 then begin
Result := GetDisplayString(Canvas, Copy(TempName, 1, I-1), 1, WidthOf(Rect)) + ' ' +
GetDisplayString(Canvas, Copy(TempName, I+1, Length(TempName) - I), 1, WidthOf(Rect));
end else begin
{2 or more lines, and the first line isn't getting an ellipsis}
if (HeightOf(TestRect) = DH) and (WidthOF(TestRect) <= WidthOf(Rect)) then
{it will fit}
Result := TempName
else begin
{it won't fit, but the first line wraps OK - 2nd line needs an ellipsis}
TestRect.Right := Rect.Right + 1;
while (WidthOf(TestRect) > WidthOf(Rect)) or (HeightOf(TestRect) > DH) do
begin
if Length(TempName) > 1 then begin
TestRect := Rect;
Delete(TempName, Length(TempName), 1);
TempName := Trim(TempName);
StrPLCopy(Buf, TempName + '...', 255);
DrawText(Canvas.Handle, Buf, Length(TempName) + 3, TestRect, DT_WORDBREAK or DT_CALCRECT);
Result := TempName + '...';
end else begin
Result := TempName + '..';
TestRect := Rect;
StrPLCopy(Buf, Result, 255);
DrawText(Canvas.Handle, Buf, Length(Result), TestRect, DT_WORDBREAK or DT_CALCRECT);
if (WidthOf(TestRect) <= WidthOf(Rect)) and (HeightOf(TestRect) > DH) then
Break;
Result := TempName + '.';
TestRect := Rect;
StrPLCopy(Buf, Result, 255);
DrawText(Canvas.Handle, Buf, Length(Result), TestRect, DT_WORDBREAK or DT_CALCRECT);
if (WidthOf(TestRect) <= WidthOf(Rect)) and (HeightOf(TestRect) > DH) then
Break;
Result := TempName;
end;
end;
end;
end;
end;
end;
end.