lazarus-ccr/components/mbColorLib/HexaColorPicker.pas

1427 lines
41 KiB
ObjectPascal

unit HexaColorPicker;
{$IFDEF FPC}
{$MODE DELPHI}
{$ENDIF}
interface
//{$I mxs.inc}
uses
LCLIntf, LCLType, LMessages, SysUtils, Classes, Controls, Graphics, StdCtrls,
Forms, Themes, Math,
HTMLColors, mbBasicPicker;
const
CustomCell = -2;
NoCell = -1;
type
TMarker = (smArrow, smRect);
TCombEntry = record
Position: TPoint;
Color: COLORREF;
TabIndex: integer;
end;
TCombArray = array of TCombEntry;
TFloatPoint = record
X, Y: Extended;
end;
TRGBrec = record
Red, Green, Blue: Single;
end;
TSelectionMode = (smNone, smColor, smBW, smRamp);
THexaColorPicker = class(TmbBasicPicker)
private
FIncrement: integer;
FSelectedCombIndex: integer;
mX, mY: integer;
FHintFormat: string;
FUnderCursor: TColor;
//FOnChange,
FOnIntensityChange: TNotifyEvent;
FCurrentColor: TColor;
FSelectedIndex: Integer;
FColorCombRect, FBWCombRect, FSliderRect, FCustomColorRect: TRect;
FCombSize, FLevels: Integer;
FBWCombs, FColorCombs: TCombArray;
FCombCorners: array[0..5] of TFloatPoint;
FCenterColor: TRGBrec;
FCenterIntensity: Single;
FSliderWidth: integer;
FCustomIndex: Integer; // If FSelectedIndex contains CustomCell then this index shows
// which index in the custom area has been selected.
// Positive values indicate the color comb and negative values
// indicate the B&W combs (complement). This value is offset with
// 1 to use index 0 to show no selection.
FRadius: Integer;
FSelectionMode: TSelectionMode;
FSliderVisible: boolean;
FMarker: TMarker;
FNewArrowStyle: boolean;
FIntensityText: string;
procedure CalculateCombLayout;
procedure ChangeIntensity(increase: boolean);
procedure DrawAll;
procedure DrawComb(ACanvas: TCanvas; X, Y, Size: Integer);
procedure DrawCombControls(ACanvas: TCanvas);
procedure EndSelection;
procedure EnumerateCombs;
function FindBWArea(X, Y: Integer): Integer;
function FindColorArea(X, Y: Integer): Integer;
function GetIntensity: integer;
function GetNextCombIndex(i: integer): integer;
function GetPreviousCombIndex(i: integer): integer;
procedure HandleCustomColors(var Message: TLMMouse);
function HandleBWArea(const Message: TLMMouse): Boolean;
function HandleColorComb(const Message: TLMMouse): Boolean;
function HandleSlider(const Message: TLMMouse): Boolean;
procedure Initialize;
function PtInComb(Comb: TCombEntry; P: TPoint; Scale: Integer): Boolean;
procedure SetIntensity(v: integer);
procedure SetNewArrowStyle(Value: boolean);
procedure SetMarker(Value: TMarker);
procedure SetRadius(r: integer);
procedure SetSliderVisible(Value: boolean);
procedure SetSliderWidth(w: integer);
function SelectAvailableColor(Color: TColor): boolean;
procedure SelectColor(Color: TColor);
protected
function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean; override;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
procedure Paint; override;
procedure Resize; override;
procedure SetSelectedColor(Value: TColor); override;
procedure CMHintShow(var Message: TLMessage); message CM_HINTSHOW;
procedure WMLButtonDown(var Message: TLMLButtonDown); message LM_LBUTTONDOWN;
procedure WMLButtonUp(var Message: TLMLButtonUp); message LM_LBUTTONUP;
procedure WMMouseMove(var Message: TLMMouseMove); message LM_MOUSEMOVE;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function GetColorAtPoint(X, Y: integer): TColor; override;
function GetColorUnderCursor: TColor; override;
function GetHexColorUnderCursor: string; override;
function GetHexColorAtPoint(X, Y: integer): string;
function GetSelectedCombIndex: integer;
procedure SelectCombIndex(i: integer);
property ColorUnderCursor: TColor read GetColorUnderCursor;
published
property Align;
property Anchors;
property HintFormat: string read FHintFormat write FHintFormat;
property Intensity: integer read GetIntensity write SetIntensity default 100;
property IntensityIncrement: integer read FIncrement write FIncrement default 1;
property IntensityText: string read FIntensityText write FIntensityText;
property NewArrowStyle: boolean read FNewArrowStyle write SetNewArrowStyle default false;
property SelectedColor: TColor read FCurrentColor write SetSelectedColor default clBlack;
property SliderVisible: boolean read FSliderVisible write SetSliderVisible default true;
property SliderWidth: integer read FSliderWidth write SetSliderWidth default 12;
property SliderMarker: TMarker read FMarker write SetMarker default smArrow;
property ShowHint default true;
property TabStop default true;
property Visible;
property Enabled;
property PopupMenu;
property TabOrder;
property Color;
property ParentColor;
property DragCursor;
property DragMode;
property DragKind;
property Constraints;
property OnChange; //: TNotifyEvent read FOnChange write FOnChange;
property OnIntensityChange: TNotifyEvent read FOnIntensityChange write FOnIntensityChange;
property OnDblClick;
property OnContextPopup;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnMouseWheel;
property OnMouseWheelUp;
property OnMouseWheelDown;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnResize;
property OnStartDrag;
end;
const
DefCenterColor: TRGBrec = (Red: 1; Green: 1; Blue: 1); // White
DefColors: array[0..5] of TRGBrec = (
(Red: 1; Green: 0; Blue: 1), // Magenta
(Red: 1; Green: 0; Blue: 0), // Red
(Red: 1; Green: 1; Blue: 0), // Yellow
(Red: 0; Green: 1; Blue: 0), // Green
(Red: 0; Green: 1; Blue: 1), // Cyan
(Red: 0; Green: 0; Blue: 1) // Blue
);
DefCenter: TFloatPoint = (X: 0; Y: 0);
implementation
uses
PalUtils, mbUtils;
{ THexaColorPicker }
constructor THexaColorPicker.Create(AOwner: TComponent);
begin
inherited;
//ControlStyle := ControlStyle - [csAcceptsControls] + [csOpaque];
FRadius := 90;
FSliderWidth := 12;
DoubleBuffered := true;
SetInitialBounds(0, 0, 204, 204);
Constraints.MinHeight := 85;
Constraints.MinWidth := 93;
TabStop := true;
FSelectedCombIndex := 0;
FHintFormat := 'RGB(%r, %g, %b)'#13'Hex: #%hex';
ShowHint := True;
FSliderVisible := true;
FMarker := smArrow;
FNewArrowStyle := false;
Initialize;
DrawAll;
FIntensityText := 'Intensity';
{
MaxHue := 360;
MaxLum := 255;
MaxSat := 255;
}
end;
destructor THexaColorPicker.Destroy;
begin
FBWCombs := nil;
FColorCombs := nil;
// FBufferBmp.Free; is already destroyed by ancestor TmbBasicPicker
inherited;
end;
procedure THexaColorPicker.ChangeIntensity(increase: boolean);
var
i: integer;
begin
i := round(FCenterIntensity * 100);
if increase then
begin
Inc(i, FIncrement);
if i > 100 then i := 100;
SetIntensity(i);
end
else
begin
Dec(i, FIncrement);
if i < 0 then i := 0;
SetIntensity(i);
end;
end;
function THexaColorPicker.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
MousePos: TPoint): Boolean;
begin
Result := inherited DoMouseWheel(Shift, WheelDelta, MousePos);
if not Result then
begin
Result := True;
ChangeIntensity(WheelDelta > 0);
end;
end;
procedure THexaColorPicker.DrawComb(ACanvas: TCanvas; X, Y: Integer; Size: Integer);
var
I: Integer;
P: array[0..5] of TPoint;
begin
for I := 0 to 5 do
begin
P[I].X := Round(FCombCorners[I].X * Size + X);
P[I].Y := Round(FCombCorners[I].Y * Size + Y);
end;
ACanvas.Polygon(P);
end;
procedure THexaColorPicker.DrawCombControls(ACanvas: TCanvas);
var
I, Index: Integer;
XOffs, YOffs, Count: Integer;
OffScreen: TBitmap;
R: TRect;
begin
OffScreen := TBitmap.Create;
try
OffScreen.Width := Width;
OffScreen.Height := HeightOfRect(FColorCombRect) + HeightOfRect(FBWCombRect);
//Parent background
if Color = clDefault then
begin
Offscreen.Transparent := true;
Offscreen.TransparentColor := clForm;
Offscreen.Canvas.Brush.Color := clForm
end else
OffScreen.Canvas.Brush.Color := Color;
OffScreen.Canvas.FillRect(OffScreen.Canvas.ClipRect);
with OffScreen.Canvas do
begin
Pen.Style := psClear;
// draw color combs from FColorCombs array
XOffs := FRadius + FColorCombRect.Left;
YOffs := FRadius + FColorCombRect.Top;
// draw the combs
for I := 0 to High(FColorCombs) do
begin
Brush.Color := FColorCombs[I].Color;
Pen.Mode := pmCopy; // the pen is set here so there are no gaps between the combs
Pen.Style := psSolid;
Pen.Color := FColorCombs[I].Color;
DrawComb(OffScreen.Canvas, FColorCombs[I].Position.X + XOffs, FColorCombs[I].Position.Y + YOffs, FCombSize);
end;
// mark selected comb
if FCustomIndex > 0 then
begin
Index := FCustomIndex - 1;
FSelectedCombIndex := index;
Pen.Style := psSolid;
{
Pen.Mode := pmXOR;
Pen.Color := clWhite;
}
Pen.Color := HighContrastColor(FColorCombs[Index].Color);
Pen.Width := 2;
Brush.Style := bsClear;
DrawComb(OffScreen.Canvas, FColorCombs[Index].Position.X + XOffs, FColorCombs[Index].Position.Y + YOffs, FCombSize);
Pen.Style := psClear;
Pen.Mode := pmCopy;
Pen.Width := 1;
end;
// draw white-to-black combs
XOffs := FColorCombRect.Left;
YOffs := FColorCombRect.Bottom - 4;
// brush is automatically reset to bsSolid
for I := 0 to High(FBWCombs) do
begin
Pen.Mode := pmCopy; // the pen is set here so there are no gaps between the combs
Pen.Style := psSolid;
Pen.Color := FBWCombs[I].Color;
Brush.Color := FBWCombs[I].Color;
if I in [0, High(FBWCombs)] then
begin
if Pen.Color = clWhite then // "white" needs a border if background is white as well
Pen.Color := clGray;
DrawComb(OffScreen.Canvas, FBWCombs[I].Position.X + XOffs, FBWCombs[I].Position.Y + YOffs, 2 * FCombSize)
end else
DrawComb(OffScreen.Canvas, FBWCombs[I].Position.X + XOffs, FBWCombs[I].Position.Y + YOffs, FCombSize);
end;
// mark selected comb
if FCustomIndex < 0 then
begin
Index := -(FCustomIndex + 1);
if index < 0 then
FSelectedCombIndex := Index
else
FSelectedCombIndex := -index;
Pen.Style := psSolid;
{
Pen.Mode := pmXOR;
Pen.Color := clWhite;
}
Pen.Mode := pmCopy;
Pen.Color := HighContrastColor(FBWCombs[Index].Color);
Pen.Width := 2;
Brush.Style := bsClear;
if Index in [0, High(FBWCombs)] then
begin
if Index = High(FBWCombs) then begin
Pen.Color := rgb(254, 254, 254); //clWhite;
Pen.Mode := pmXOR;
end;
if ((FColorCombs[0].Color = Cardinal(clWhite)) and (Index = 0)) or
((FColorCombs[0].Color = Cardinal(clBlack)) and (Index = High(FBWCombs)))
then
DrawComb(OffScreen.Canvas, FRadius + FColorCombRect.Left, FRadius + FColorCombRect.Top, FCombSize); // mark white or black center
DrawComb(OffScreen.Canvas, FBWCombs[Index].Position.X + XOffs, FBWCombs[Index].Position.Y + YOffs, 2 * FCombSize);
end
else
DrawComb(OffScreen.Canvas, FBWCombs[Index].Position.X + XOffs, FBWCombs[Index].Position.Y + YOffs, FCombSize);
Pen.Style := psClear;
Pen.Mode := pmCopy;
Pen.Width := 1;
end;
// Slider
if FSliderVisible then
begin
// center-color trackbar
R := FSliderRect;
R.Right := R.Left + FSliderWidth;
Pen.Style := psSolid;
GradientFill(R, clWhite, clBlack, gdVertical);
// draw marker
Count := FSliderRect.Bottom - FSliderRect.Top - 1;
XOffs := FSliderRect.Left + FSliderWidth + 1;
YOffs := FSliderRect.Top + Round(Count * (1 - FCenterIntensity));;
Pen.Color := clBlack;
case FMarker of
smArrow:
begin
if not FNewArrowStyle then
begin
Brush.Color := clBlack;
Polygon([
Point(XOffs, YOffs),
Point(XOffs + 6, YOffs - 4),
Point(XOffs + 6, YOffs + 4)
])
end
else
begin
Brush.Color := clWhite;
Pen.Color := clBtnShadow;
Polygon([
Point(XOffs, YOffs),
Point(XOffs + 4, YOffs - 4),
Point(XOffs + 6, YOffs - 4),
Point(XOffs + 7, YOffs - 3),
Point(XOffs + 7, YOffs + 3),
Point(XOffs + 6, YOffs + 4),
Point(XOffs + 4, YOffs + 4)]);
end;
end;
smRect:
begin
Brush.Style := bsClear;
Pen.Mode := pmNot;
Rectangle(XOffs - FSliderWidth - 4, YOffs - 3, XOffs + 2, YOffs + 3);
Pen.Mode := pmCopy;
Brush.Style := bsSolid;
end;
end; // case
Pen.Style := psClear;
end;
end;
ACanvas.Draw(0, 0, OffScreen);
finally
Offscreen.Free;
end;
EnumerateCombs;
end;
// Looks for a comb at position (X, Y) in the black&white area.
// Result is -1 if nothing could be found else the index of the particular comb
// into FBWCombs.
function THexaColorPicker.FindBWArea(X, Y: Integer): Integer;
var
I, Scale: Integer;
Pt: TPoint;
begin
Result := -1;
Pt := Point(X - FBWCombRect.Left, Y - FBWCombRect.Top);
for I := 0 to High(FBWCombs) do
begin
if I in [0, High(FBWCombs)] then
Scale := FCombSize
else
Scale := FCombSize div 2;
if PtInComb(FBWCombs[I], Pt, Scale) then
begin
Result := I;
Break;
end;
end;
end;
// Looks for a comb at position (X, Y) in the custom color area.
// Result is -1 if nothing could be found else the index of the particular comb
// into FColorCombs.
function THexaColorPicker.FindColorArea(X, Y: Integer): Integer;
var
I: Integer;
Pt: TPoint;
begin
Result := -1;
Pt := Point(X - (FRadius + FColorCombRect.Left), Y - (FRadius + FColorCombRect.Top));
for I := 0 to High(FColorCombs) do
begin
if PtInComb(FColorCombs[I], Pt, FCombSize div 2) then
begin
Result := I;
Break;
end;
end;
end;
function THexaColorPicker.GetIntensity: integer;
begin
Result := round(FCenterIntensity * 100);
end;
function THexaColorPicker.GetNextCombIndex(i: integer): integer;
begin
if i = 127 then
Result := -1
else
if i = -15 then
Result := 1
else
if i > 0 then
Result := i + 1
else
Result := i - 1;
end;
function THexaColorPicker.GetPreviousCombIndex(i: integer): integer;
begin
if i = 1 then
Result := -15
else
if i = -1 then
Result := 127
else
if i > 0 then
Result := i - 1
else
Result := i + 1;
end;
function THexaColorPicker.GetSelectedCombIndex: integer;
begin
if FSelectedCombIndex < 0 then
Result := FBWCombs[-FSelectedCombIndex].TabIndex
else
Result := FColorCombs[FSelectedCombIndex].TabIndex;
end;
// determines whether the mouse position is within the B&W comb area and acts accordingly
function THexaColorPicker.HandleBWArea(const Message: TLMMouse): Boolean;
var
Index: Integer;
Shift: TShiftState;
begin
Result := PtInRect(FBWCombRect, Point(Message.XPos, Message.YPos)) and (FSelectionMode in [smNone, smBW]);
if Result then
begin
Shift := KeysToShiftState(Message.Keys);
if ssLeft in Shift then
begin
FSelectionMode := smBW;
Index := FindBWArea(Message.XPos, Message.YPos);
if Index > -1 then
begin
// remove selection comb if it was previously in color comb
if FCustomIndex > 0 then InvalidateRect(Handle, @FColorCombRect, False);
if FCustomIndex <> -(Index + 1) then
begin
FCustomIndex := -(Index + 1);
InvalidateRect(Handle, @FBWCombRect, False);
InvalidateRect(Handle, @FCustomColorRect, False);
EndSelection;
end;
end
else
Result := False;
end;
end;
end;
// determines whether the mouse position is within the color comb area and acts accordingly
function THexaColorPicker.HandleColorComb(const Message: TLMMouse): Boolean;
var
Index: Integer;
Shift: TShiftState;
begin
Result := PtInRect(FColorCombRect, Point(Message.XPos, Message.YPos)) and (FSelectionMode in [smNone, smColor]);
if Result then
begin
Shift := KeysToShiftState(Message.Keys);
if ssLeft in Shift then
begin
FSelectionMode := smColor;
Index := FindColorArea(Message.XPos, Message.YPos);
if Index > -1 then
begin
// remove selection comb if it was previously in b&w comb
if FCustomIndex < 0 then InvalidateRect(Handle, @FBWCombRect, False);
if FCustomIndex <> (Index + 1) then
begin
FCustomIndex := Index + 1;
InvalidateRect(Handle, @FColorCombRect, False);
InvalidateRect(Handle, @FCustomColorRect, False);
EndSelection;
end;
end
else
Result := False;
end;
end;
end;
procedure THexaColorPicker.HandleCustomColors(
var Message: {$IFDEF FPC}TLMMouse{$ELSE}TWMMouse{$ENDIF});
begin
if not HandleSlider(Message) then
if not HandleBWArea(Message) then
HandleColorComb(Message);
end;
// determines whether the mouse position is within the slider area and acts accordingly
function THexaColorPicker.HandleSlider(const Message: TLMMouse): Boolean;
var
Shift: TShiftState;
dY: Integer;
R: TRect;
begin
if not FSliderVisible then
begin
Result := false;
Exit;
end;
Result :=
(PtInRect(FSliderRect, Point(Message.XPos, Message.YPos))
and (FSelectionMode = smNone))
or
((Message.XPos >= FSliderRect.Left) and (Message.XPos <= FSliderRect.Right)
and (FSelectionMode = smRamp));
if Result then
begin
Shift := KeysToShiftState(Message.Keys);
if ssLeft in Shift then
begin
FSelectionMode := smRamp;
dY := FSliderRect.Bottom - FSliderRect.Top;
FCenterIntensity := 1 - (Message.YPos - FSliderRect.Top) / dY;
if FCenterIntensity < 0 then FCenterIntensity := 0;
if FCenterIntensity > 1 then FCenterIntensity := 1;
FCenterColor.Red := DefCenterColor.Red * FCenterIntensity;
FCenterColor.Green := DefCenterColor.Green * FCenterIntensity;
FCenterColor.Blue := DefCenterColor.Blue * FCenterIntensity;
R := FSliderRect;
Dec(R.Top, 3);
Inc(R.Bottom, 3);
Inc(R.Left, 10);
InvalidateRect(Handle, @R, False);
FColorCombs := nil;
InvalidateRect(Handle, @FColorCombRect, False);
InvalidateRect(Handle, @FCustomColorRect, False);
CalculateCombLayout;
EndSelection;
if Assigned(FOnIntensityChange) then
FOnIntensityChange(Self);
end;
end;
end;
procedure THexaColorPicker.Initialize;
var
I: Integer;
begin
FSelectedIndex := NoCell;
for I := 0 to 5 do
begin
FCombCorners[I].X := 0.5 * cos(Pi * (90 - I * 60) / 180);
FCombCorners[I].Y := 0.5 * sin(Pi * (90 - I * 60) / 180);
end;
FLevels := 7;
FCombSize := Round(FRadius / (FLevels - 1));
FCenterColor := DefCenterColor;
FIncrement := 1;
FCenterIntensity := 1;
end;
procedure THexaColorPicker.KeyDown(var Key: Word; Shift: TShiftState);
var
eraseKey: Boolean;
begin
eraseKey := true;
if ssCtrl in Shift then
case Key of
VK_LEFT: SetSelectedColor(clWhite);
VK_RIGHT: SetSelectedColor(clBlack);
VK_UP: if FSliderVisible then SetIntensity(100);
VK_DOWN: if FSliderVisible then SetIntensity(0);
else
eraseKey := false;
end
else
case Key of
VK_LEFT: SelectCombIndex(GetPreviousCombIndex(GetSelectedCombIndex));
VK_RIGHT: SelectCombIndex(GetNextCombIndex(GetSelectedCombIndex));
VK_UP: if FSliderVisible then ChangeIntensity(true);
VK_DOWN: if FSliderVisible then ChangeIntensity(false);
else
eraseKey := false;
end;
if eraseKey then
Key := 0;
inherited;
end;
procedure THexaColorPicker.Paint;
begin
PaintParentBack(Canvas);
if FColorCombs = nil then
CalculateCombLayout;
DrawCombControls(Canvas);
end;
function THexaColorPicker.PtInComb(Comb: TCombEntry; P: TPoint; Scale: Integer): Boolean;
begin
Result := (Sqr(Comb.Position.X - P.X) + Sqr(Comb.Position.Y - P.Y)) <= Scale * Scale;
end;
procedure THexaColorPicker.DrawAll;
var
WinTop: integer;
begin
WinTop := - FRadius div 8; // use 10 instead of 8 if the top has been cut
FCombSize := Round(1 + FRadius / (FLevels - 1));
FColorCombRect := Rect(0, WinTop, 2 * FRadius, 2 * FRadius + WinTop);
FBWCombRect := Rect(
FColorCombRect.Left,
FColorCombRect.Bottom - 4,
Round(17 * FCombSize * cos(Pi / 6) / 2) {%H-}+ 6 * FCombSize,
FColorCombRect.Bottom + 2 * FCombSize
);
if FSliderVisible then
FSliderRect := Rect(FColorCombRect.Right, FCombSize, FColorCombRect.Right + 10 + FSliderWidth, FColorCombRect.Bottom - FCombSize)
// FSliderRect := Rect(FColorCombRect.Right, FColorCombRect.Top, FColorCombRect.Right + 10 + FSliderWidth, FColorCombRect.Bottom)
else
FSliderRect := Rect(-1, -1, -1, -1);
end;
// fills arrays with centers and colors for the custom color and black & white combs,
// these arrays are used to quickly draw the combx and do hit tests
function RGBFromFloat(Color: TRGBrec): COLORREF;
begin
Result := RGB(Round(255 * Color.Red), Round(255 * Color.Green), Round(255 * Color.Blue));
end;
{function TRGBrecFromTColor(Color: TColor): TRGBrec;
begin
Result.Red := GetRValue(Color)/255;
Result.Green := GetGValue(Color)/255;
Result.Blue := GetBValue(Color)/255;
end;}
procedure THexaColorPicker.CalculateCombLayout;
function GrayFromIntensity(Intensity: Byte): COLORREF;
begin
Result := RGB(Intensity, Intensity, Intensity);
end;
var
I, J, Level, CurrentIndex, CombCount: Cardinal;
CurrentColor: TRGBrec;
CurrentPos: TFloatPoint;
Scale: Extended;
// triangle vars
Pos1, Pos2, dPos1, dPos2, dPos: TFloatPoint;
Color1, Color2, dColor1, dColor2, dColor: TRGBrec;
begin
// this ensures the radius and comb size is set correctly
// HandleNeeded;
if FLevels < 1 then FLevels := 1;
// To draw perfectly aligned combs we split the final comb into six triangles (sextants)
// and calculate each separately. The center comb is stored as first entry in the array
// and will not considered twice (as with the other shared combs too).
//
// The way used here for calculation of the layout seems a bit complicated, but works
// correctly for all cases (even if the comb corners are rotated).
// initialization
CurrentIndex := 0;
CurrentColor := FCenterColor;
// number of combs can be calculated by:
// 1 level: 1 comb (the center)
// 2 levels: 1 comb + 6 combs
// 3 levels: 1 comb + 1 * 6 combs + 2 * 6 combs
// n levels: 1 combs + 1 * 6 combs + 2 * 6 combs + .. + (n-1) * 6 combs
// this equals to 1 + 6 * (1 + 2 + 3 + .. + (n-1)), by using Gauss' famous formula we get:
// Count = 1 + 6 * (((n-1) * n) / 2)
// Because there's always an even number involved (either n or n-1) we can use an integer div
// instead of a float div here...
CombCount := 1 + 6 * (((FLevels - 1) * FLevels) div 2);
SetLength(FColorCombs, CombCount);
// store center values
FColorCombs[CurrentIndex].Position := Point(0, 0);
FColorCombs[CurrentIndex].Color := RGBFromFloat(CurrentColor);
Inc(CurrentIndex);
// go out off here if there are not further levels to draw
if FLevels < 2 then Exit;
// now go for each sextant, the generic corners have been calculated already at creation
// time for a comb with diameter 1
// ------
// /\ 1 /\
// / \ / \
// / 2 \/ 0 \
// -----------
// \ 3 /\ 5 /
// \ / \ /
// \/ 4 \/
// ------
for I := 0 to 5 do
begin
// initialize triangle corner values
//
// center (always at 0,0)
// /\
// dPos1 / \ dPos2
// dColor1 / \ dColor2
// / dPos \
// /--------\ (span)
// / dColor \
// /____________\
// comb corner 1 comb corner 2
//
// Pos1, Pos2, Color1, Color2 are running terms for both sides of the triangle
// incremented by dPos1/2 and dColor1/2.
// dPos and dColor are used to interpolate a span between the values just mentioned.
//
// The small combs are actually oriented with corner 0 at top (i.e. mirrored at y = x,
// compared with the values in FCombCorners), we can achieve that by simply exchanging
// X and Y values.
Scale := 2 * FRadius * cos(Pi / 6);
Pos1.X := FCombCorners[I].Y * Scale;
Pos1.Y := FCombCorners[I].X * Scale;
Color1 := DefColors[I];
if I = 5 then
begin
Pos2.X := FCombCorners[0].Y * Scale;
Pos2.Y := FCombCorners[0].X * Scale;
Color2 := DefColors[0];
end
else
begin
Pos2.X := FCombCorners[I + 1].Y * Scale;
Pos2.Y := FCombCorners[I + 1].X * Scale;
Color2 := DefColors[I + 1];
end;
dPos1.X := Pos1.X / (FLevels - 1);
dPos1.Y := Pos1.Y / (FLevels - 1);
dPos2.X := Pos2.X / (FLevels - 1);
dPos2.Y := Pos2.Y / (FLevels - 1);
dColor1.Red := (Color1.Red - FCenterColor.Red) / (FLevels - 1);
dColor1.Green := (Color1.Green - FCenterColor.Green) / (FLevels - 1);
dColor1.Blue := (Color1.Blue - FCenterColor.Blue) / (FLevels - 1);
dColor2.Red := (Color2.Red - FCenterColor.Red) / (FLevels - 1);
dColor2.Green := (Color2.Green - FCenterColor.Green) / (FLevels - 1);
dColor2.Blue := (Color2.Blue - FCenterColor.Blue) / (FLevels - 1);
Pos1 := DefCenter;
Pos2 := DefCenter;
Color1 := FCenterColor;
Color2 := FCenterColor;
// Now that we have finished the initialization for this step we'll go
// through a loop for each level to calculate the spans.
// We can ignore level 0 (as this is the center we already have determined) as well
// as the last step of each span (as this is the start value in the next triangle and will
// be calculated there). We have, though, take them into the calculation of the running terms.
for Level := 0 to FLevels - 1 do
begin
if Level > 0 then
begin
// initialize span values
dPos.X := (Pos2.X - Pos1.X) / Level;
dPos.Y := (Pos2.Y - Pos1.Y) / Level;
dColor.Red := (Color2.Red - Color1.Red) / Level;
dColor.Green := (Color2.Green - Color1.Green) / Level;
dColor.Blue := (Color2.Blue - Color1.Blue) / Level;
CurrentPos := Pos1;
CurrentColor := Color1;
for J := 0 to Level - 1 do
begin
// store current values in the array
FColorCombs[CurrentIndex].Position.X := Round(CurrentPos.X);
FColorCombs[CurrentIndex].Position.Y := Round(CurrentPos.Y);
FColorCombs[CurrentIndex].Color := RGBFromFloat(CurrentColor);
Inc(CurrentIndex);
// advance in span
CurrentPos.X := CurrentPos.X + dPos.X;
CurrentPos.Y := CurrentPos.Y + dPos.Y;
CurrentColor.Red := CurrentColor.Red + dColor.Red;
CurrentColor.Green := CurrentColor.Green + dColor.Green;
CurrentColor.Blue := CurrentColor.Blue + dColor.Blue;
end;
end;
// advance running terms
Pos1.X := Pos1.X + dPos1.X;
Pos1.Y := Pos1.Y + dPos1.Y;
Pos2.X := Pos2.X + dPos2.X;
Pos2.Y := Pos2.Y + dPos2.Y;
Color1.Red := Color1.Red + dColor1.Red;
Color1.Green := Color1.Green + dColor1.Green;
Color1.Blue := Color1.Blue + dColor1.Blue;
Color2.Red := Color2.Red + dColor2.Red;
Color2.Green := Color2.Green + dColor2.Green;
Color2.Blue := Color2.Blue + dColor2.Blue;
end;
end;
// second step is to build a list for the black & white area
// 17 entries from pure white to pure black
// the first and last are implicitely of double comb size
SetLength(FBWCombs, 17);
CurrentIndex := 0;
FBWCombs[CurrentIndex].Color := GrayFromIntensity(255);
FBWCombs[CurrentIndex].Position := Point(FCombSize, FCombSize);
Inc(CurrentIndex);
CurrentPos.X := 3 * FCombSize;
CurrentPos.Y := 3 * (FCombSize div 4);
dPos.X := Round(FCombSize * cos(Pi / 6) / 2);
dPos.Y := Round(FCombSize * (1 + sin(Pi / 6)) / 2);
for I := 0 to 14 do
begin
FBWCombs[CurrentIndex].Color := GrayFromIntensity((16 - CurrentIndex) * 15);
if Odd(I) then
FBWCombs[CurrentIndex].Position := Point(Round(CurrentPos.X + I * dPos.X), Round(CurrentPos.Y + dPos.Y))
else
FBWCombs[CurrentIndex].Position := Point(Round(CurrentPos.X + I * dPos.X), Round(CurrentPos.Y));
Inc(CurrentIndex);
end;
FBWCombs[CurrentIndex].Color := 0;
FBWCombs[CurrentIndex].Position := Point(Round(CurrentPos.X + 16 * dPos.X + FCombSize), FCombSize);
EnumerateCombs;
end;
// determine hint message and out-of-hint rect
procedure THexaColorPicker.CMHintShow(
var Message: {$IFDEF FPC}TLMessage{$ELSE}TMessage{$ENDIF} );
var
Index: Integer;
Colors: TCombArray;
cp: TPoint;
begin
Colors := nil;
if (GetColorUnderCursor <> clNone) or PtInRect(FSliderRect, Point(mX, mY)) then
with TCMHintShow(Message) do
begin
if not ShowHint then
Message.Result := 1
else
begin
with HintInfo^ do
begin
// show that we want a hint
Result := 0;
cp := CursorPos;
ReshowTimeout := 0; //1;
HideTimeout := 5000;
HintInfo^.CursorRect := Rect(cp.X, cp.Y, cp.X+1, cp.Y+1);
if PtInRect(FSliderRect, cp) and FSliderVisible then
begin
// in case of the intensity slider we show the current intensity
HintStr := FIntensityText + Format(': %d%%', [Round(100 * FCenterIntensity)]);
HintPos := ClientToScreen(Point(FSliderRect.Right, CursorPos.Y - 8));
end
else
begin
Index := -1;
if PtInRect(FBWCombRect, Point(CursorPos.X, CursorPos.Y)) then
begin
// considering black&white area...
if csLButtonDown in ControlState then
Index := -(FCustomIndex + 1)
else
Index := FindBWArea(CursorPos.X, CursorPos.Y);
Colors := FBWCombs;
end
else
if PtInRect(FColorCombRect, Point(CursorPos.X, CursorPos.Y)) then
begin
// considering color comb area...
if csLButtonDown in ControlState then
Index := FCustomIndex - 1
else
Index := FindColorArea(CursorPos.X, CursorPos.Y);
Colors := FColorCombs;
end;
if (Index > -1) and (Colors <> nil) then
HintStr := FormatHint(FHintFormat, Colors[Index].Color);
end;
end;
end;
end;
end;
procedure THexaColorPicker.SetSelectedColor(Value: TColor);
begin
FCurrentColor := Value;
SelectColor(Value);
Invalidate;
end;
procedure THexaColorPicker.EndSelection;
begin
if FCustomIndex < 0 then
SetSelectedColor(FBWCombs[-(FCustomIndex + 1)].Color)
else
if FCustomIndex > 0 then
SetSelectedColor(FColorCombs[FCustomIndex - 1].Color)
else
SetSelectedColor(clNone);
end;
function THexaColorPicker.GetColorUnderCursor: TColor;
begin
Result := FUnderCursor;
end;
function THexaColorPicker.GetColorAtPoint(X, Y: integer): TColor;
var
Index: Integer;
Colors: TCombArray;
begin
Colors := nil;
Index := -1;
if PtInRect(FBWCombRect, Point(X, Y)) then
begin
Index := FindBWArea(X, Y);
Colors := FBWCombs;
end
else
if PtInRect(FColorCombRect, Point(X, Y)) then
begin
Index := FindColorArea(X, Y);
Colors := FColorCombs;
end;
if (Index > -1) and (Colors <> nil) then
Result := Colors[Index].Color
else
Result := clNone;
end;
function THexaColorPicker.GetHexColorUnderCursor: string;
begin
Result := ColorToHex(GetColorUnderCursor);
end;
function THexaColorPicker.GetHexColorAtPoint(X, Y: integer): string;
begin
Result := ColorToHex(GetColorAtPoint(X, Y));
end;
procedure THexaColorPicker.EnumerateCombs;
var
i, k: integer;
begin
k := 0;
if FBWCombs <> nil then
for i := 1 to High(FBWCombs) do
begin
case i of
// b & w comb indices
1: k := -1;
2: k := -9;
3: k := -2;
4: k := -10;
5: k := -3;
6: k := -11;
7: k := -4;
8: k := -12;
9: k := -5;
10: k := -13;
11: k := -6;
12: k := -14;
13: k := -7;
14: k := -15;
15: k := -8;
// big black comb index (match center comb)
16: K := 64;
end;
FBWCombs[i].TabIndex := k;
end;
if FColorCombs <> nil then
for i := 0 to High(FColorCombs) do
begin
case i of
// center comb index
0: k := 64;
// color comb indices
1: k := 65;
2: k := 66;
3: k := 78;
4: k := 67;
5: k := 79;
6: k := 90;
7: k := 68;
8: k := 80;
9: k := 91;
10: k := 101;
11: k := 69;
12: k := 81;
13: k := 92;
14: k := 102;
15: k := 111;
16: k := 70;
17: k := 82;
18: k := 93;
19: k := 103;
20: k := 112;
21: k := 120;
22: k := 77;
23: k := 89;
24: k := 88;
25: k := 100;
26: k := 99;
27: k := 98;
28: k := 110;
29: k := 109;
30: k := 108;
31: k := 107;
32: k := 119;
33: k := 118;
34: k := 117;
35: k := 116;
36: k := 115;
37: k := 127;
38: k := 126;
39: k := 125;
40: k := 124;
41: k := 123;
42: k := 122;
43: k := 76;
44: k := 87;
45: k := 75;
46: k := 97;
47: k := 86;
48: k := 74;
49: k := 106;
50: k := 96;
51: k := 85;
52: k := 73;
53: k := 114;
54: k := 105;
55: k := 95;
56: k := 84;
57: k := 72;
58: k := 121;
59: k := 113;
60: k := 104;
61: k := 94;
62: k := 83;
63: k := 71;
64: k := 63;
65: k := 62;
66: k := 50;
67: k := 61;
68: k := 49;
69: k := 38;
70: k := 60;
71: k := 48;
72: k := 37;
73: k := 27;
74: k := 59;
75: k := 47;
76: k := 36;
77: k := 26;
78: k := 17;
79: k := 58;
80: k := 46;
81: k := 35;
82: k := 25;
83: k := 16;
84: k := 8;
85: k := 51;
86: k := 39;
87: k := 40;
88: k := 28;
89: k := 29;
90: k := 30;
91: k := 18;
92: k := 19;
93: k := 20;
94: k := 21;
95: k := 9;
96: k := 10;
97: k := 11;
98: k := 12;
99: k := 13;
100: k := 1;
101: k := 2;
102: k := 3;
103: k := 4;
104: k := 5;
105: k := 6;
106: k := 52;
107: k := 41;
108: k := 53;
109: k := 31;
110: k := 42;
111: k := 54;
112: k := 22;
113: k := 32;
114: k := 43;
115: k := 55;
116: k := 14;
117: k := 23;
118: k := 33;
119: k := 44;
120: k := 56;
121: k := 7;
122: k := 15;
123: k := 24;
124: k := 34;
125: k := 45;
126: k := 57;
end;
FColorCombs[i].TabIndex := k;
end;
end;
procedure THexaColorPicker.SelectCombIndex(i: integer);
var
j: integer;
begin
if i > 0 then
begin
if FColorCombs <> nil then
for j := 0 to High(FColorCombs) do
begin
if FColorCombs[j].TabIndex = i then
begin
SetSelectedColor(FColorCombs[j].Color);
Break;
end;
end;
end
else
if FBWCombs <> nil then
for j := 1 to High(FBWCombs) - 1 do
begin
if FBWCombs[j].TabIndex = i then
begin
SetSelectedColor(FBWCombs[j].Color);
Break;
end;
end;
end;
procedure THexaColorPicker.Resize;
var
rw, rh: integer;
begin
if (Width >= 93) and (Height >= 85) then
begin
if FSliderVisible then
rw := Round((Width - 10 - FSliderWidth)/2)
else
rw := Round(Width/2 - 5);
rh := Round((24/53)*(Height - 6));
SetRadius(Min(rw, rh));
end;
inherited;
end;
function THexaColorPicker.SelectAvailableColor(Color: TColor): boolean;
var
I: integer;
C: COLORREF;
found: Boolean;
begin
found := False;
Result := false;
C := ColorToRGB(Color);
if FColorCombs = nil then CalculateCombLayout;
FCustomIndex := 0;
FSelectedIndex := NoCell;
for I := 0 to High(FBWCombs) do
if FBWCombs[I].Color = C then
begin
FSelectedIndex := CustomCell;
FCustomIndex := -(I + 1);
found := True;
Result := true;
Break;
end;
if not found then
for I := 0 to High(FColorCombs) do
if FColorCombs[I].Color = C then
begin
FSelectedIndex := CustomCell;
FCustomIndex := I + 1;
Result := true;
Break;
end;
end;
procedure THexaColorPicker.SelectColor(Color: TColor);
begin
SelectAvailableColor(Color);
Invalidate;
if Assigned(OnChange) then OnChange(Self);
end;
procedure THexaColorPicker.SetIntensity(v: integer);
var
R: TRect;
begin
FCenterIntensity := EnsureRange(v/100, 0, 1);
FCenterColor.Red := DefCenterColor.Red * FCenterIntensity;
FCenterColor.Green := DefCenterColor.Green * FCenterIntensity;
FCenterColor.Blue := DefCenterColor.Blue * FCenterIntensity;
R := FSliderRect;
Dec(R.Top, 3);
Inc(R.Bottom, 3);
Inc(R.Left, 10);
InvalidateRect(Handle, @R, False);
FColorCombs := nil;
InvalidateRect(Handle, @FColorCombRect, False);
InvalidateRect(Handle, @FCustomColorRect, False);
CalculateCombLayout;
EndSelection;
if Assigned(FOnIntensityChange) then
FOnIntensityChange(Self);
end;
procedure THexaColorPicker.SetMarker(Value: TMarker);
begin
if FMarker <> Value then
begin
FMarker := Value;
DrawAll;
CalculateCombLayout;
Invalidate;
end;
end;
procedure THexaColorPicker.SetNewArrowStyle(Value: boolean);
begin
if FNewArrowStyle <> Value then
begin
FNewArrowStyle := Value;
DrawAll;
CalculateCombLayout;
Invalidate;
end;
end;
procedure THexaColorPicker.SetRadius(r: integer);
begin
if Parent = nil then
exit;
FRadius := r;
DrawAll;
CalculateCombLayout;
Invalidate;
end;
procedure THexaColorPicker.SetSliderVisible(Value: boolean);
begin
if FSliderVisible <> Value then
begin
FSliderVisible := Value;
DrawAll;
CalculateCombLayout;
Invalidate;
end;
end;
procedure THexaColorPicker.SetSliderWidth(w: integer);
begin
if (FSliderWidth <> w) and FSliderVisible then
begin
FSliderWidth := w;
DrawAll;
Width := FSliderRect.Right + 2;
CalculateCombLayout;
Invalidate;
end;
end;
procedure THexaColorPicker.WMLButtonDown(
var Message: {$IFDEF FPC}TLMLButtonDown{$ELSE}TWMLButtonDown{$ENDIF} );
begin
inherited;
SetFocus; // needed so the key events work
if PtInRect(ClientRect, Point(Message.XPos, Message.YPos)) then
HandleCustomColors(Message);
end;
procedure THexaColorPicker.WMLButtonUp(
var Message: {$IFDEF FPC}TLMLButtonUp{$ELSE}TWMLButtonUp{$ENDIF} );
var
LastMode: TSelectionMode;
begin
inherited;
LastMode := FSelectionMode;
FSelectionMode := smNone;
if (FSelectedIndex = CustomCell) and (FCustomIndex <> 0) then
begin
if ((FSelectedIndex = CustomCell) and (LastMode in [smColor, smBW])) or
(FSelectedIndex <> NoCell) and (FSelectedIndex <> CustomCell)
then
EndSelection
end;
end;
procedure THexaColorPicker.WMMouseMove(
var Message: {$IFDEF FPC}TLMMouseMove{$ELSE}TWMMouseMove{$ENDIF} );
var
Shift: TShiftState;
Index: Integer;
Colors: TCombArray;
begin
inherited;
mX := Message.XPos;
mY := Message.YPos;
//get color under cursor
Colors := nil;
FUnderCursor := clNone;
if PtInRect(FBWCombRect, Point(Message.XPos, Message.YPos)) then
begin
Index := FindBWArea(Message.XPos, Message.YPos);
Colors := FBWCombs;
if (Index > -1) and (Colors <> nil) then
FUnderCursor := Colors[Index].Color;
end
else
if PtInRect(FColorCombRect, Point(Message.XPos, Message.YPos)) then
begin
Index := FindColorArea(Message.XPos, Message.YPos);
Colors := FColorCombs;
if (Index > -1) and (Colors <> nil) then
FUnderCursor := Colors[Index].Color;
end
else
FUnderCursor := clNone;
// further process message
Shift := KeysToShiftState(Message.Keys);
if ssLeft in Shift then
HandleCustomColors(Message);
end;
end.