{ ------------------------------------------------------------- }
{ Purpose : NxInspector 6                                       }
{ By      : Bojan Nikolic                                       }
{ For     : BergSoft                                            }
{ ------------------------------------------------------------- }
{ Copyright (C) BergSoft.net 2019. All Rights Reserved.         }
{ ------------------------------------------------------------- }
{$I '..\NxSuite6.inc'}

unit NxInspectorNodeViewClasses6;

interface

uses
  { Standard }
  Classes, Types, Windows, Graphics,
  {$IFDEF UI_TYPES}UITypes,{$ENDIF}
  { Controls }
  Controls, StdCtrls, ExtCtrls, Menus,
  { Next Standard 6 }
  NxClasses6, NxTypes6, NxConsts6,
  { NextInspector 6 }
  NxInspectorNode6, NxInspectorNodeClasses6;

resourcestring
  stPlaceImage = 'place image';

const
  szCheckBox = 13;
  szRadioButton = 13;

const
  szDividerWidth = 5; { 2 + 1 + 2 }
  szMinButtonWidth = 4;

const
  szFadingSize = 20;

const
  szDropDownButton = 13;

const
  szColorPaletteButtonMinSize = 14;

type
  { TNxFloatInspectorNodeView }

  TNxFloatInspectorNodeView = class(TNxInspectorNodeView)
  private
    FAsFloat: Double;
    { Property Accessors }
    function GetFloatNode: TNxFloatInspectorNode6;
    procedure SetAsFloat(const Value: Double);
  protected
    { Protected Methods }
    procedure SetAsString(const Value: WideString); override;
    { Protected Properties }
    property FloatNode: TNxFloatInspectorNode6 read GetFloatNode;
  public
    procedure AssignValue; override;
    procedure Changed; override;
    function GetValue: WideString; override;
    { Properties }
    property AsFloat: Double read FAsFloat write SetAsFloat;
  end;

  TNxEditableFloatItemView = class(TNxFloatInspectorNodeView)
  public
    procedure PaintValueContent(ContentRect: TRect); override;
  end;

  { TNxIntegerItemView }

  TNxIntegerItemView = class(TNxInspectorNodeView)
  private
    FAsInteger: Integer;
    { Property Accessors }
    function GetIntegerItem: INxIntegerInspectorItem;
    procedure SetAsInteger(const Value: Integer);
  protected
    { Protected Methods }
    procedure SetAsString(const Value: WideString); override;
    { Protected Properties }
    property IntegerItem: INxIntegerInspectorItem read GetIntegerItem;
  public
    procedure AssignValue; override;
    procedure Changed; override;
    function GetValue: WideString; override;
    { Properties }
    property AsInteger: Integer read FAsInteger write SetAsInteger;
  end;

  TNxEditableIntegerItemView = class(TNxIntegerItemView)
  public
    procedure PaintValueContent(ContentRect: TRect); override;
  end;

  { TNxCheckableItemView }

  TNxCheckableItemView = class(TNxInspectorNodeView)
  private
    FHover: Boolean;
    FDown: Boolean;
    function GetCheckableItem: INxCheckable;
    function GetChecked: Boolean;
    procedure SetChecked(const Value: Boolean);
    procedure SetDown(const Value: Boolean);
    procedure SetHover(const Value: Boolean);
  protected
    procedure DrawCheckBox; virtual; abstract;
    procedure DrawText; virtual;
    function GetThemeClass: Integer; virtual;
  public
    function CaptureMouse: Boolean; override;
    function GetCheckBoxRect: TRect; virtual;
    function GetTextRect: TRect; override;
    { Mouse/Keyboard Actions }
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure KeyUp; overload; override;
    procedure KeyUp(var Key: Word; Shift: TShiftState); overload; override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseLeave; override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    { Painting Methods }
    procedure PaintValueContent(ContentRect: TRect); override;
    procedure RefreshCheckBox;
    procedure Toggle;

    property CheckableItem: INxCheckable read GetCheckableItem;
    property Checked: Boolean read GetChecked write SetChecked;
    property Down: Boolean read FDown write SetDown;
    property Hover: Boolean read FHover write SetHover;
  end;

  TNxCheckBoxNodeView = class(TNxCheckableItemView)
  private
    { Property Accessors }
    function GetCheckBoxItem: INxCheckBoxInspectorItem;
  protected
    function FirstChar(State: TCheckBoxState): WideChar;
    { Virtual Methods }
    procedure DrawCheckBox; override;
    { Properties }
    property CheckBoxItem: INxCheckBoxInspectorItem read GetCheckBoxItem;
  public
    { Mouse/Keyboard Actions }
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure KeyUp(var Key: Word; Shift: TShiftState); overload; override;
  end;

  TNxRadioButtonNodeView = class(TNxCheckableItemView)
  private
    function GetRadioButtonNode: TNxRadioInspectorNode6;
  protected
    function FirstChar(Value: Boolean): WideChar;
    { Virtual Methods }
    procedure DrawCheckBox; override;
    { Properties }
    property RadioButtonNode: TNxRadioInspectorNode6 read GetRadioButtonNode;
  public
    { Mouse/Keyboard Actions }
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure KeyUp(var Key: Word; Shift: TShiftState); overload; override;
  end;

  { TNxPictureInspectorNodeView }

  TNxPictureInspectorNodeView = class(TNxInspectorNodeView)
  private
    { Property Fields }
    FCloseTimer: TTimer;
    FDelayTimer: TTimer;
    FFileNameHint: THintWindow;
    FFocused: Boolean;
    FLeaved: Boolean;
    FPopupMenu: TPopupMenu;
    procedure SetFocused(const Value: Boolean);
  protected
    procedure CreatePopup;
    function OpenPictureDialog: WideString;
    procedure ActivateFileNameHint;
    procedure DeactivateFileNameHint;
    procedure UpdateFileNameHint;
  public
    constructor Create(Owner: TNxInspectorNode6); override;
    destructor Destroy; override;
    { Action Methods }
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure KeyUp(var Key: Word; Shift: TShiftState); overload; override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseLeave; override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    { Event Handlers }
    procedure DoBrowseClick(Sender: TObject); dynamic;
    procedure DoClearClick(Sender: TObject); dynamic;
    procedure DoCloseTimerTimer(Sender: TObject); dynamic;
    procedure DoDelayTimerTimer(Sender: TObject); dynamic;
    { Painting Methods }
    procedure PaintValueContent(ContentRect: TRect); override;
    function PreferedSize(Entities: TNxInspectorNodeEntity = [etValue]): TSize; override;
    { Properties }
    property Focused: Boolean read FFocused write SetFocused;
  end;

  TNxMemoItemView = class(TNxInspectorNodeView)
  protected
    function MeasureTextSize: TSize; override;
  public
    { Painting Methods }
    procedure PaintValueContent(ContentRect: TRect); override;
    function PreferedSize(Entities: TNxInspectorNodeEntity = [etValue]): TSize; override;
  end;

  INxToolbarItemView = interface
    ['{71E479B8-B098-466A-982B-DFAD048472A0}']
    procedure RefreshButton(Button: TNxToolbarNodeButton);
    procedure RefreshText;
  end;

  TNxToolbarItemView = class(TNxInspectorNodeView, INxToolbarItemView)
  private
    FButtonHint: THintWindow;
    FDownButton: TNxToolbarNodeButton;
    FHoverButton: TNxToolbarNodeButton;
    function GetButtons(Index: Integer): TNxToolbarNodeButton;
    function GetCount: Integer;
    function GetToolbarNode: TNxToolbarInspectorNode6;
    procedure SetDownButton(const Value: TNxToolbarNodeButton);
    procedure SetHoverButton(const Value: TNxToolbarNodeButton);
  protected
    procedure ActivateButtonHint(Button: TNxToolbarNodeButton);
    procedure ReleaseButtonHint;
    { Virtual Methods }
    procedure DrawButton(ButtonRect: TRect; Button: TNxToolbarNodeButton;
      State: TNxButtonState); virtual;
    function GetButtonsSize: Integer;
    function GetEndingPos: Integer;
    function GetStartingPos: Integer;
    property DownButton: TNxToolbarNodeButton read FDownButton write SetDownButton;
    property HoverButton: TNxToolbarNodeButton read FHoverButton write SetHoverButton;
  public
    constructor Create(Owner: TNxInspectorNode6); override;
    destructor Destroy; override;
    { Methods }
    function CaptureMouse: Boolean; override;
    procedure ClickButton(Button: TNxToolbarNodeButton); virtual;
    function GetButtonAtNumber(const Number: Integer): TNxToolbarNodeButton;
    function GetButtonAtPos(const X, Y: Integer): TNxToolbarNodeButton;
    function GetButtonRect(const Button: TNxToolbarNodeButton): TRect;
    function GetButtonWidth(const Button: TNxToolbarNodeButton): Integer;
    function GetEditRect: TRect; override;
    function GetTextRect: TRect; override;
    function PreferedSize(Entities: TNxInspectorNodeEntity = [etValue]): TSize; override;
    { Mouse/Keyboard Actions }
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure KeyUp; overload; override;
    procedure KeyUp(var Key: Word; Shift: TShiftState); overload; override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseLeave; override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    { Painting Methods }
    procedure PaintValueContent(ContentRect: TRect); override;
    { INxToolbarItemView }
    procedure RefreshButton(Button: TNxToolbarNodeButton);
    procedure RefreshText;

    property Buttons[Index: Integer]: TNxToolbarNodeButton read GetButtons;
    property Count: Integer read GetCount;
    property ToolbarNode: TNxToolbarInspectorNode6 read GetToolbarNode;
  end;

  TNxProgressInspectorNodeView = class(TNxEditableFloatItemView)
  private
    FDown: Boolean;
    { Property Getters }
    function GetProgressNode: TNxProgressInspectorNode6;
  public
    function CaptureMouse: Boolean; override;
    function GetAtPos(X, Y: Integer): Integer;
    { Mouse/Keyboard Actions }
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    { Painting Methods }
    procedure PaintValueContent(ContentRect: TRect); override;
    { Properties }
    property ProgressNode: TNxProgressInspectorNode6 read GetProgressNode;
  end;

  TNxTrackbarInspectorNodeView = class(TNxEditableIntegerItemView)
  private
    FState: TNxButtonState;
    FSliderHint: THintWindow;
    function GetTrackBarNode: TNxTrackBarInspectorNode6;
    function GetTrackRect: TRect;
    procedure SetState(const Value: TNxButtonState);
  protected
    procedure ActivateSliderHint;
    procedure ReleaseSliderHint;
    procedure UpdateSliderHint;
    { Virtual Methods }
    function GetSliderSize: TSize;
    function GetTrackWidth: Integer; virtual;
  public
    constructor Create(Owner: TNxInspectorNode6); override;
    destructor Destroy; override;
    function CaptureMouse: Boolean; override;
    function GetPositionAtPos(const X, Y: Integer): Integer;
    function GetSliderRect: TRect;
    function GetTextRect: TRect; override;
    function HasContent: Boolean; override;
    { Mouse/Keyboard Actions }
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure KeyUp; overload; override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseLeave; override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    { Painting Methods }
    procedure PaintValueContent(ContentRect: TRect); override;
    { Refresh }
    procedure RefreshSlider;
    property State: TNxButtonState read FState write SetState;
    property TrackbarNode: TNxTrackBarInspectorNode6 read GetTrackBarNode;
    property TrackRect: TRect read GetTrackRect;
  end;

  TNxRateInspectorNodeView = class(TNxEditableIntegerItemView)
  private
    function GetRateNode: TNxRateInspectorNode6;
  public
    function GetAtPos(X, Y: Integer): Integer;
    function GetGlyphsRect: TRect;
    { Mouse/Keyboard Actions }
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    { Painting Methods }
    procedure PaintValueContent(ContentRect: TRect); override;
    property RateNode: TNxRateInspectorNode6 read GetRateNode;
  end;

  { TNxPaletteItemView }

  TNxColorLocation = (ctNone, ctSelected, ctFavorites, ctPalette);

  TNxColorPaletteInfo = record
    Color: TColor;
    Index: Integer;
    Location: TNxColorLocation;
  end;

  TNxColorPaletteNodeView = class(TNxInspectorNodeView, INxFullSizeItem)
  private
    { Fields }
    FColorHint: THintWindow;
    FFullSizeButtonState: TNxButtonState;
    FHoverColor: TNxColorPaletteInfo;
    { Property Accessors }
    function GetButtonSize: Integer;
    function GetColorPaletteNode: TNxColorPaletteInspectorNode6;
    function GetFullSize: Boolean;
    function GetFullSizeHeight: Integer;
    procedure SetFullSize(const Value: Boolean);
    procedure SetFullSizeButtonState(const Value: TNxButtonState);
    procedure SetHoverColor(const Value: TNxColorPaletteInfo);
  protected
    procedure ActivateColorHint(const Info: TNxColorPaletteInfo);
    procedure ReleaseColorHint;
    { Virtual Methods }
    function GetContentRect: TRect; override;
    function GetFullSizeButtonRect: TRect;
    procedure PaintPreviewContent(Canvas: TCanvas; Dest: TRect); override;
    { Other }
    procedure PaintColor(Canvas: TCanvas; Color: TColor; ColorRect: TRect;
      Inner: Boolean = False); virtual;
    { Properties }
    property ButtonSize: Integer read GetButtonSize;
    property FullSizeButtonState: TNxButtonState read FFullSizeButtonState write SetFullSizeButtonState;
    property HoverColor: TNxColorPaletteInfo read FHoverColor write SetHoverColor;
  public
    constructor Create(Owner: TNxInspectorNode6); override;
    function GetColorAtPos(const Location: TPoint): TNxColorPaletteInfo;
    function GetColorRect(const Info: TNxColorPaletteInfo): TRect;
    function GetColorHintText(const Info: TNxColorPaletteInfo): string;
    function HasButton: Boolean;
    { Mouse Methods }
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseLeave; override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    { Virtual Methods }
    procedure PaintValue; override;
    procedure PaintValueContent(ContentRect: TRect); override;
    { Properties }
    property ColorPaletteNode: TNxColorPaletteInspectorNode6 read GetColorPaletteNode;
    property FullSize: Boolean read GetFullSize write SetFullSize;
    property FullSizeHeight: Integer read GetFullSizeHeight;
  end;

implementation

uses
  { Standard }
  SysUtils, Dialogs, ExtDlgs, Forms, StrUtils, Math,
  { Next Standard 6 }
  NxGraphics6, NxViews6, NxStyles6,
  { Styles }
{$IFDEF THEMES}
  UxTheme
{$ELSE}
  NxTheme6
{$ENDIF}
;

{ TNxFloatInspectorNodeView }

procedure TNxFloatInspectorNodeView.AssignValue;
begin
  Node.AsFloat := FAsFloat;
end;

procedure TNxFloatInspectorNodeView.Changed;
begin
  FAsFloat := Node.AsFloat;
end;

function TNxFloatInspectorNodeView.GetFloatNode: TNxFloatInspectorNode6;
begin
  Result := TNxFloatInspectorNode6(Node);
end;

function TNxFloatInspectorNodeView.GetValue: WideString;
begin
  Result := FloatToStr(AsFloat);
end;

procedure TNxFloatInspectorNodeView.SetAsFloat(const Value: Double);
var
  NewValue: Double;
begin
  NewValue := Value;

  { Adjust to Range }
  if NewValue > FloatNode.Max then NewValue := Round(FloatNode.Max);
  if NewValue < FloatNode.Min then NewValue := Round(FloatNode.Min);

  if NewValue <> FAsFloat then
  begin
    FAsFloat := NewValue;

    { Assign Value to Node, or update InplaceEdit (trigger event) }
    if Node.AutoAssign then AssignValue else DoChange;

    Refresh;
  end;
end;

procedure TNxFloatInspectorNodeView.SetAsString(const Value: WideString);
begin
  AsFloat := StrToFloatDef(Value, 0);
end;

{ TNxEditableFloatItemView }

procedure TNxEditableFloatItemView.PaintValueContent(ContentRect: TRect);
begin
  inherited PaintValueContent(ContentRect);

  { Draw Divider }
  if Node.EditWidth > 0 then
    with Canvas do
    begin
      Pen.Color := INxInspectorStyle(Style).GetDividerColor;
      Polyline([
        Point(InteractiveRect.Left, ContentRect.Top + pdStandard),
        Point(InteractiveRect.Left, ContentRect.Bottom - pdStandard)
      ]);
    end;
end;

{ TNxIntegerItemView }

procedure TNxIntegerItemView.AssignValue;
begin
  Node.AsInteger := FAsInteger;
end;

procedure TNxIntegerItemView.Changed;
begin
  FAsInteger := Node.AsInteger;
end;

function TNxIntegerItemView.GetIntegerItem: INxIntegerInspectorItem;
begin
  Supports(Node, INxIntegerInspectorItem, Result);
end;

function TNxIntegerItemView.GetValue: WideString;
begin
  Result := IntToStr(AsInteger);
end;

procedure TNxIntegerItemView.SetAsInteger(const Value: Integer);
var
  NewValue: Integer;
begin
  NewValue := Value;

  { Adjust to Range }
  if NewValue > IntegerItem.Max then NewValue := Round(IntegerItem.Max);
  if NewValue < IntegerItem.Min then NewValue := Round(IntegerItem.Min);

  if NewValue <> FAsInteger then
  begin
    FAsInteger := NewValue;

    { Assign Value to Node, or update InplaceEdit (trigger event) }
    if Node.AutoAssign then AssignValue
      else DoChange;

    Refresh;
  end;
end;

procedure TNxIntegerItemView.SetAsString(const Value: WideString);
begin
  AsInteger := StrToIntDef(Value, 0);
end;

{ TNxEditableIntegerView }

procedure TNxEditableIntegerItemView.PaintValueContent(ContentRect: TRect);
begin
  inherited PaintValueContent(ContentRect);

  { Draw Divider }
  if Node.EditWidth > 0 then
    with Canvas do
    begin
      Pen.Color := INxInspectorStyle(Style).GetDividerColor;
      Polyline([
        Point(InteractiveRect.Left, ContentRect.Top + pdStandard),
        Point(InteractiveRect.Left, ContentRect.Bottom - pdStandard)
      ]);
    end;
end;

{ TNxCheckBoxNodeView }

function TNxCheckableItemView.CaptureMouse: Boolean;
begin
  Result := Down;
end;

function TNxCheckableItemView.GetCheckableItem: INxCheckable;
begin
  Supports(Node, INxCheckable, Result);
end;

function TNxCheckableItemView.GetCheckBoxRect: TRect;
var
  ButtonSize: TSize;
begin
  with Result do
  begin
    { Size }
    case GetThemeClass of
      tcCheckBox: ButtonSize.cx := szCheckBox;
      tcRadioButton: ButtonSize.cx := szRadioButton;
      else ButtonSize.cx := 0;
    end;
    ButtonSize.cy := ButtonSize.cx;
    ButtonSize := DotToPixel(ButtonSize);

    { Horizontal }
    case Node.Alignment of
      taLeftJustify: Left := ContentRect.Left;
      taRightJustify: Left := ContentRect.Right - ButtonSize.cx;
      Classes.taCenter: Left := ContentRect.Left + ContentWidth div 2 - ButtonSize.cx div 2;
    end;

    { Vertical }
    case CheckableItem.VerticalAlignment of
      taAlignTop: Top := ContentRect.Top;
      taAlignBottom: Top := ContentRect.Bottom - ButtonSize.cy;
      taVerticalCenter: Top := CenterInRect(ContentRect, ButtonSize.cy).Y;
    end;
    Result := Bounds(Left, Top, ButtonSize.cx, ButtonSize.cy);
  end;
end;

function TNxCheckableItemView.GetChecked: Boolean;
begin
  Result := Node.AsBoolean;
end;

function TNxCheckableItemView.GetTextRect: TRect;
begin
  Result := ContentRect;
  Result.Left := GetCheckBoxRect.Right + DotToPixel(pdStandard);
end;

function TNxCheckableItemView.GetThemeClass: Integer;
begin
  Result := -1;
  if Supports(Node, INxCheckBoxInspectorItem) then Result := tcCheckBox;
  if Supports(Node, INxRadioInspectorItem) then Result := tcRadioButton;
end;

procedure TNxCheckableItemView.KeyDown(var Key: Word; Shift: TShiftState);
begin
  if Key = VK_SPACE then Down := True;
end;

procedure TNxCheckableItemView.KeyUp;
begin
  Down := False;
end;

procedure TNxCheckableItemView.KeyUp(var Key: Word; Shift: TShiftState);
begin
  if Down then if Key = VK_SPACE then Toggle;

  { Call overload KeyUp }
  inherited;
end;

procedure TNxCheckableItemView.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  Down := PtInRect(GetCheckBoxRect, Point(X, Y));
end;

procedure TNxCheckableItemView.MouseLeave;
begin
  Hover := False;
end;

procedure TNxCheckableItemView.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  inherited;
  Hover := PtInRect(GetCheckBoxRect, Point(X, Y));
end;

procedure TNxCheckableItemView.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Down then
  begin
    if PtInRect(ClientRect, Point(X, Y)) then
    begin
      { Toggle State prop. }
      Toggle;
    end;

    { Un-hold }
    Down := False;
  end;
end;

procedure TNxCheckableItemView.DrawText;
begin
  Canvas.Font.Color := clGrayText;
  DrawTextRect(Canvas, GetTextRect, taLeftJustify, CheckableItem.VerticalAlignment, Node.DisplayText, wkEllipsis);
end;

procedure TNxCheckableItemView.PaintValueContent(ContentRect: TRect);
begin
  DrawCheckBox;
  if CheckableItem.ShowText then DrawText;
end;

procedure TNxCheckableItemView.RefreshCheckBox;
begin
  Refresh(GetCheckBoxRect);
end;

procedure TNxCheckableItemView.SetChecked(const Value: Boolean);
begin
  if Supports(Node, INxRadioInspectorItem) and not Value then
  begin
    { Radio Button can't be unchecked }
  end else Node.AsBoolean := Value;
end;

procedure TNxCheckableItemView.SetDown(const Value: Boolean);
begin
  if Value <> FDown then
  begin
    FDown := Value;
    RefreshCheckBox;
  end;
end;

procedure TNxCheckableItemView.SetHover(const Value: Boolean);
begin
  if Value <> FHover then
  begin
    FHover := Value;
    RefreshCheckBox;
  end;
end;

procedure TNxCheckableItemView.Toggle;
begin
  if Supports(Node, INxRadioInspectorItem) and Node.AsBoolean then
  begin
    { Radio Button can't be unchecked }
  end else CheckableItem.Toggle;
end;

{ TNxCheckBoxNodeView }

procedure TNxCheckBoxNodeView.DrawCheckBox;
var
  ButtonState: TNxButtonState;
begin
  ButtonState := [];
  if FHover then Include(ButtonState, bsHover);
  if FDown then Include(ButtonState, bsPressed);
  if not Node.Enabled then Include(ButtonState, bsDisabled);
  Style.PaintCheckBox(GetCheckBoxRect, CheckBoxItem.State, ButtonState);
end;

function TNxCheckBoxNodeView.FirstChar(State: TCheckBoxState): WideChar;
var
  S: WideString;
begin
  S := CheckBoxItem.GetStateText(State);
  if S <> EmptyStr then Result := S[1] else Result := EmptyChr;
end;

function TNxCheckBoxNodeView.GetCheckBoxItem: INxCheckBoxInspectorItem;
begin
  Supports(Node, INxCheckBoxInspectorItem, Result);
end;

procedure TNxCheckBoxNodeView.KeyDown(var Key: Word; Shift: TShiftState);
begin
  inherited; { Space Char }

  { First Char }
  if (WideChar(Chr(Key)) = FirstChar(cbChecked))
    or (WideChar(Char(Key)) = FirstChar(cbUnchecked))
      or (WideChar(Char(Key)) = FirstChar(cbGrayed))
        then Down := True;
end;

procedure TNxCheckBoxNodeView.KeyUp(var Key: Word; Shift: TShiftState);
begin
  if Down then
  begin
    if WideChar(Chr(Key)) = FirstChar(cbChecked) then CheckBoxItem.State := cbChecked;
    if WideChar(Chr(Key)) = FirstChar(cbUnchecked) then CheckBoxItem.State := cbUnchecked;
    if CheckBoxItem.AllowGrayed then
    begin
      if WideChar(Chr(Key)) = FirstChar(cbGrayed) then CheckBoxItem.State := cbGrayed;
    end;
  end;
  inherited;
end;

{ TNxRadioButtonNodeView }

procedure TNxRadioButtonNodeView.DrawCheckBox;
var
  State: Integer;
  Theme: HTHEME;
begin
  Theme := OpenThemeData(Control.Handle, teButton);

  case Node.AsBoolean of
    False:
      begin
        State := tiRadioNormal;
        if FHover then State := tiRadioHover;
        if FDown then State := tiRadioDown;
        if not Node.Enabled then State := tiRadioDisabled;
      end;
    else
      begin
        State := tiRadioChecked;
        if FHover then State := tiRadioCheckedHover;
        if FDown then State := tiRadioCheckedDown;
        if not Node.Enabled then State := tiRadioCheckedDisabled;
      end;
  end;

  { Draw CheckBox }
  DrawThemeBackground(Theme, Canvas.Handle, GetThemeClass, State, GetCheckBoxRect, nil);

  CloseThemeData(Theme);
end;

function TNxRadioButtonNodeView.FirstChar(Value: Boolean): WideChar;
var
  S: WideString;
begin
  S := RadioButtonNode.GetValueText(Value);
  if S <> EmptyStr then Result := S[1] else Result := EmptyChr;
end;

function TNxRadioButtonNodeView.GetRadioButtonNode: TNxRadioInspectorNode6;
begin
  Result := TNxRadioInspectorNode6(Node);
end;

procedure TNxRadioButtonNodeView.KeyDown(var Key: Word; Shift: TShiftState);
begin
  inherited;
  { First Char }
  if WideChar(Chr(Key)) = FirstChar(True) then Down := True;
end;

procedure TNxRadioButtonNodeView.KeyUp(var Key: Word; Shift: TShiftState);
begin
  if Down then if WideChar(Chr(Key)) = FirstChar(True) then Node.AsBoolean := True;
  inherited; { Release Down }
end;

{ TNxPictureInspectorNodeView }

procedure TNxPictureInspectorNodeView.ActivateFileNameHint;
begin
  if not Assigned(FFileNameHint) then
  begin
    { Create Hint Window }
    FFileNameHint := HintWindowClass.Create(Control);
    FFileNameHint.Color := clInfoBk;
  end;

  { Move Hint & Activate }
  UpdateFileNameHint;
end;

constructor TNxPictureInspectorNodeView.Create(Owner: TNxInspectorNode6);
begin
  inherited;
  FCloseTimer := TTimer.Create(nil);
  FCloseTimer.Enabled := False;
  FCloseTimer.OnTimer := DoCloseTimerTimer;
  FCloseTimer.Interval := Application.HintHidePause;
  FDelayTimer := TTimer.Create(nil);
  FDelayTimer.Enabled := False;
  FDelayTimer.OnTimer := DoDelayTimerTimer;
  FDelayTimer.Interval := Application.HintPause;
  FFileNameHint := nil;
  FFocused := False;
  FLeaved := True;
  CreatePopup;
end;

procedure TNxPictureInspectorNodeView.CreatePopup;
var
  OpenItem, ClearItem: TMenuItem;
begin
  FPopupMenu := TPopupMenu.Create(nil);

  OpenItem := TMenuItem.Create(FPopupMenu);

  with OpenItem do
  begin
    Caption := 'Browse...';
    OnClick := DoBrowseClick;
  end;

  FPopupMenu.Items.Add(OpenItem);

  ClearItem := TMenuItem.Create(FPopupMenu);

  with ClearItem do
  begin
    Caption := 'Clear...';
    OnClick := DoClearClick;
  end;

  FPopupMenu.Items.Add(ClearItem);
end;

procedure TNxPictureInspectorNodeView.DeactivateFileNameHint;
begin
  { Stop all }
  FDelayTimer.Enabled := False;
  FCloseTimer.Enabled := False;

  if Assigned(FFileNameHint) then
  begin
    { Release, don't call .Free! }
    FFileNameHint.ReleaseHandle;
    FFileNameHint := nil;
  end;
end;

destructor TNxPictureInspectorNodeView.Destroy;
begin
  FreeAndNil(FCloseTimer);
  FreeAndNil(FDelayTimer);
  FreeAndNil(FPopupMenu);
  inherited;
end;

procedure TNxPictureInspectorNodeView.DoBrowseClick(Sender: TObject);
begin
  Node.Value := OpenPictureDialog;
end;

procedure TNxPictureInspectorNodeView.DoClearClick(Sender: TObject);
begin
  Node.Value := EmptyStr;
end;

procedure TNxPictureInspectorNodeView.DoCloseTimerTimer(Sender: TObject);
begin
  { First Stop }
  FCloseTimer.Enabled := False;
  FDelayTimer.Enabled := False;
  { Hide Hint }
  DeactivateFileNameHint;
end;

procedure TNxPictureInspectorNodeView.DoDelayTimerTimer(Sender: TObject);
begin
  FDelayTimer.Enabled := False;
  ActivateFileNameHint;
end;

procedure TNxPictureInspectorNodeView.KeyDown(var Key: Word; Shift: TShiftState);
begin
  case Key of
    VK_DELETE,
    VK_BACK:
      if Focused then
      begin
        Node.ChangeValue(EmptyStr);
      end;
  end;
end;

procedure TNxPictureInspectorNodeView.KeyUp(var Key: Word; Shift: TShiftState);
begin
  case Key of
    VK_SPACE: Focused := not Focused;
  end;
end;

procedure TNxPictureInspectorNodeView.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbLeft then
    { Double Click }
    if ssDouble in Shift then
    begin
      Node.Value := OpenPictureDialog;
    end; { ssDouble }
end;

procedure TNxPictureInspectorNodeView.MouseLeave;
begin
  DeactivateFileNameHint;
  FLeaved := True;
end;

procedure TNxPictureInspectorNodeView.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  inherited;
  if not Assigned(FFileNameHint)
    and FLeaved then
  begin
    FDelayTimer.Enabled := True;
  end;
  FLeaved := False;
end;

procedure TNxPictureInspectorNodeView.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  PopupPoint: TPoint;
begin
  if PtInRect(ContentRect, Point(X, Y)) then
  begin
    if Button = mbLeft then
    begin
      if not (ssDouble in Shift) then Focused := not Focused;
    end;

    if Button = mbRight then
    begin
      PopupPoint := Control.ClientToScreen(Point(X, Y));
      FPopupMenu.Popup(PopupPoint.X, PopupPoint.Y);
    end;
  end;
end;

function TNxPictureInspectorNodeView.OpenPictureDialog: WideString;
var
  PictureDialog: TOpenPictureDialog;
begin
  Result := Node.Value;
  try
    PictureDialog := TOpenPictureDialog.Create(nil);
    if PictureDialog.Execute then
    begin
      { Assign FileName }
      Result := PictureDialog.FileName;
    end;
  finally
    FreeAndNil(PictureDialog);
  end;
end;

procedure TNxPictureInspectorNodeView.PaintValueContent(ContentRect: TRect);
var
  FocusRect: TRect;
begin
  { Init Node's properties
    into View (!) }
  AssignNode;

  with Node as TNxPictureInspectorNode6 do
  begin
    if Assigned(Picture) and Assigned(Picture.Graphic) then
    begin
      { Resize & Draw Picture }
      ConstrainedRect(Picture.Graphic, ContentRect);

      if not IsRectEmpty(ContentRect) then
      begin
        Canvas.StretchDraw(ContentRect, Picture.Graphic);

        PaintEffects(Canvas, ContentRect, Effects);

        if Focused then
        begin
          FocusRect := ContentRect;
          with Canvas do
          begin
            Pen.Assign(BorderPen);
            Pen.Color := INxInspectorStyle(Style).GetSelectionBorderColor;
            Brush.Style := bsClear;
            Rectangle(FocusRect);
            InflateRect(FocusRect, -1, -1);
            Rectangle(FocusRect);
            Brush.Style := bsSolid;
          end;
        end;
      end;
    end else
      with Canvas do
      begin
        { Image not Assigned }
        Pen.Color := INxInspectorStyle(Style).GetDividerColor;
        Pen.Style := psDot;
        RoundRect(ContentRect.Left, ContentRect.Top, ContentRect.Right, ContentRect.Bottom, 10, 10);
        Pen.Style := psSolid;
        Font.Color := Pen.Color;
        DrawTextRect(Canvas, ContentRect, Classes.taCenter, taVerticalCenter, stPlaceImage, wkEllipsis);
      end;
  end;

end;

function TNxPictureInspectorNodeView.PreferedSize(Entities: TNxInspectorNodeEntity): TSize;
begin
  { Caption }
  Result := inherited PreferedSize(Entities);

  { Value }
  if etValue in Entities then
    with Node as TNxPictureInspectorNode6 do
    begin
      Result.cy := Padding.Top + Padding.Bottom;
      if Assigned(Picture) then
      begin
        Inc(Result.cy, Picture.Graphic.Height);
      end;
    end;
end;

procedure TNxPictureInspectorNodeView.SetFocused(const Value: Boolean);
begin
  if Value <> FFocused then
  begin
    FFocused := Value;
    Refresh;
  end;
end;

procedure TNxPictureInspectorNodeView.UpdateFileNameHint;
var
  HintRect: TRect;
  MousePos: TPoint;
  DisplayText: string;
begin
  if Assigned(FFileNameHint) then
  begin
    DisplayText := Node.AsString;

    GetCursorPos(MousePos);

    { Calculate Rect }
    HintRect := FFileNameHint.CalcHintRect(Screen.Width, DisplayText, nil);

    { Cordinates must be "Screen" }
    HintRect.TopLeft := MousePos;

    HintRect.Bottom := HintRect.Top + HintRect.Bottom;
    HintRect.Right := HintRect.Left + HintRect.Right;

    { Show Hint }
    FFileNameHint.ActivateHint(HintRect, DisplayText);

    { Start ticking }
    FCloseTimer.Enabled := True;
  end;
end;

{ TNxMemoItemView }

function TNxMemoItemView.MeasureTextSize: TSize;
begin
  Result := GetTextSize(Canvas.Handle, TextRect, Node.Value, wkWordWrap)
end;

procedure TNxMemoItemView.PaintValueContent(ContentRect: TRect);
var
  FadingSize: Integer;
  FadingRect: TRect;
begin
  with Canvas do
  begin
    { Init Node's properties
      into View (!) }
    AssignNode;

    { Draw Text }
    DrawTextRect(Canvas, ContentRect, Node.Alignment, taAlignTop, Node.Value, wkWordWrap);

    { Draw Fade-out effect }
    if Node.NodeHeight < PreferedSize.cy + Node.Padding.Top + Node.Padding.Bottom then
    { ToDo: Having new function like RequiredSize or similar, to include padding in it }
    begin
      FadingSize := szFadingSize;
      Max(ContentHeight, FadingSize);

      FadingRect := ContentRect;
      FadingRect.Top := FadingRect.Bottom - FadingSize;

      FadeVert(Canvas, FadingRect);
    end;
  end;
end;

function TNxMemoItemView.PreferedSize(Entities: TNxInspectorNodeEntity): TSize;
begin
  { Caption }
  Result := inherited PreferedSize(Entities); 

  { Value }
  if etValue in Entities then
  begin
    AssignNode;
    Result.cy := GetTextHeight(Canvas, ContentWidth, Node.DisplayText);
    Result.cy := Max(Result.cy, szItemHeight);
  end;
end;

{ TNxToolbarItemView }

procedure TNxToolbarItemView.ActivateButtonHint(Button: TNxToolbarNodeButton);
var
  ButtonRect, HintRect: TRect;
begin
  if Assigned(Button)
    and (Button.Hint <> EmptyStr) then
  begin
    FButtonHint := HintWindowClass.Create(Control);
    FButtonHint.Color := clInfoBk;

    { Calculate Rect }
    ButtonRect := GetButtonRect(Button);
    HintRect := FButtonHint.CalcHintRect(Screen.Width, Button.Hint, nil);

    { Cordinates must be "Screen" }
    HintRect.TopLeft := Control.ClientToScreen(ButtonRect.TopLeft);

    { Move Hint bellow Button }
    Inc(HintRect.Top, ButtonRect.Bottom - ButtonRect.Top);

    HintRect.Bottom := HintRect.Top + HintRect.Bottom;
    HintRect.Right := HintRect.Left + HintRect.Right;

    { Show Hint }
    FButtonHint.ActivateHint(HintRect, Button.Hint);
  end;
end;

function TNxToolbarItemView.CaptureMouse: Boolean;
begin
  Result := FDownButton <> nil;
end;

procedure TNxToolbarItemView.ClickButton(Button: TNxToolbarNodeButton);
begin
  { AutoCheck Buttons }
  if Button.AutoCheck then
  begin
    Button.Checked := not Button.Checked;
  end;

  { Trigger Event }
  ToolbarNode.DoToolButtonClick(Button.Index);
end;

constructor TNxToolbarItemView.Create(Owner: TNxInspectorNode6);
begin
  inherited;
  FDownButton := nil;
  FHoverButton := nil;
end;

destructor TNxToolbarItemView.Destroy;
begin
  ReleaseButtonHint; { In case that is still active }
  inherited;
end;

procedure TNxToolbarItemView.DrawButton(ButtonRect: TRect; Button: TNxToolbarNodeButton;
  State: TNxButtonState);
var
  Middle: Integer;
  DividerDrawingPoint, ImageDrawingPoint: TPoint;
  BtnTextRect: TRect;
begin
  with Canvas do
    if Button.Divider then
    begin
      Middle := ButtonRect.Left + (ButtonRect.Right - ButtonRect.Left) div 2;
      DividerDrawingPoint := Point(Middle, ButtonRect.Top + pdStandard);
      Pen.Color := INxInspectorStyle(Style).GetDividerColor;
      Polyline([
        DividerDrawingPoint,
        Point(Middle, ButtonRect.Bottom - pdStandard)
      ]);
    end else
    begin
      { Draw Button }
      INxInspectorStyle(Style).PaintToolButton(ButtonRect, State);

      { Set Vertical Pos }
      ImageDrawingPoint := CenterInRect(ButtonRect, Button.Glyph.Height);
      ImageDrawingPoint.X := ButtonRect.Left + pdStandard;

      BtnTextRect := Rect(ImageDrawingPoint.X, ButtonRect.Top, ButtonRect.Right - pdStandard, ButtonRect.Bottom);

      if not Button.Glyph.Empty then
      begin
        DrawBitmap(Canvas, ImageDrawingPoint.X, ImageDrawingPoint.Y, Button.Glyph);

        { Move Pos for Text }
        Inc(BtnTextRect.Left, Button.Glyph.Width);
        Inc(BtnTextRect.Left, pdStandard);
      end;

      { Draw Text }
      if Button.ShowText then
      begin
        Font.Color := Node.ValueFont.Color;

        DrawTextRect(Canvas, BtnTextRect, taLeftJustify, taVerticalCenter, Button.Text, wkNone);
      end;

    end;
end;

function TNxToolbarItemView.GetButtonAtNumber(const Number: Integer): TNxToolbarNodeButton;
var
  i, Pos: Integer;
begin
  Result := nil;
  Pos := -1;
  for i := 0 to Pred(Count) do
    if not Buttons[i].Divider then
    begin
      Inc(Pos);
      if Pos = Pred(Number) then
      begin
        Result := Buttons[i];
        Exit;
      end;
    end;
end;

function TNxToolbarItemView.GetButtonAtPos(const X, Y: Integer): TNxToolbarNodeButton;
var
  i: Integer;
  ButtonSize: TSize;
  ButtonRect: TRect;
  MovingPoint: TPoint;
begin
  Result := nil;

  { Starting Pos }
  MovingPoint := Point(GetStartingPos, ContentRect.Top);

  for i := 0 to Pred(Count) do
  begin
    ButtonSize.cx := GetButtonWidth(Buttons[i]);

    ButtonSize.cy := IfThen(ToolbarNode.ButtonHeight = 0, ContentHeight, ToolbarNode.ButtonHeight);

    ButtonRect := Bounds(MovingPoint.X, MovingPoint.Y, ButtonSize.cx, ButtonSize.cy);

    if PtInRect(ButtonRect, Point(X, Y)) then
    begin
      Result := Buttons[i];

      Exit;
    end;

    { Move to draw next Button }
    Inc(MovingPoint.X, ButtonSize.cx);

    { Spacing between Buttons }
    Inc(MovingPoint.X, ToolbarNode.Spacing);
  end;

end;

function TNxToolbarItemView.GetButtonRect(const Button: TNxToolbarNodeButton): TRect;
var
  i: Integer;
  ButtonSize: TSize;
  MovingPoint: TPoint;
begin
  { Starting Pos }
  MovingPoint := Point(GetStartingPos, ContentRect.Top);

  for i := 0 to Pred(Count) do
  begin
    ButtonSize.cx := GetButtonWidth(Buttons[i]);

    ButtonSize.cy := IfThen(ToolbarNode.ButtonHeight = 0, ContentHeight, ToolbarNode.ButtonHeight);

    if Button = Buttons[i] then
    begin
      Result := Bounds(MovingPoint.X, MovingPoint.Y, ButtonSize.cx, ButtonSize.cy);

      { Done }
      Exit;
    end;

    { Move to draw next Button }
    Inc(MovingPoint.X, ButtonSize.cx);

    { Spacing between Buttons }
    Inc(MovingPoint.X, ToolbarNode.Spacing);
  end;

end;

function TNxToolbarItemView.GetButtons(Index: Integer): TNxToolbarNodeButton;
begin
  Result := ToolbarNode.Buttons[Index];
end;

function TNxToolbarItemView.GetButtonsSize: Integer;
var
  i: Integer;
begin
  Result := 0;
  for i := 0 to Pred(Count) do
    Inc(Result, GetButtonWidth(Buttons[i]));
end;

function TNxToolbarItemView.GetButtonWidth(const Button: TNxToolbarNodeButton): Integer;
var
  Size: Integer;
begin
  if Button.Divider
    then Result := szDividerWidth else
  begin
    Result := IfThen(ToolbarNode.ButtonWidth > 0, ToolbarNode.ButtonWidth, szMinButtonWidth);

    Size := 0;

    Inc(Size, pdStandard);

    if not Button.Glyph.Empty then
    begin
      Size := Button.Glyph.Width;
      Inc(Size, pdStandard * 2);
    end;

    if Button.ShowText then
    begin
      Inc(Size, Canvas.TextWidth(Button.Text));
      Inc(Size, pdStandard);
    end;

    if Size > Result then
      Result := Size;
  end;
end;

function TNxToolbarItemView.GetCount: Integer;
begin
  Result := ToolbarNode.Buttons.Count;
end;

function TNxToolbarItemView.GetEditRect: TRect;
begin
  Result := inherited GetEditRect;
  Inc(Result.Top, ToolbarNode.ButtonHeight);
end;

function TNxToolbarItemView.GetEndingPos: Integer;
var
  i: Integer;
begin
  Result := GetStartingPos;
  for i := 0 to Pred(Count) do
  begin
    Inc(Result, GetButtonWidth(Buttons[i]));
    Inc(Result, ToolbarNode.Spacing);
  end;
end;

function TNxToolbarItemView.GetStartingPos: Integer;
var
  Size: Integer;
begin
  Size := GetButtonsSize;
  case Node.Alignment of
    taLeftJustify: Result := ContentRect.Left;
    taRightJustify: Result := ContentRect.Right - Size;
    else Result := ContentRect.Left + ContentWidth div 2 - Size div 2
  end;
end;

function TNxToolbarItemView.GetTextRect: TRect;
begin
  Result := ContentRect;
  Result.Left := GetEndingPos;
end;

function TNxToolbarItemView.GetToolbarNode: TNxToolbarInspectorNode6;
begin
  Result := TNxToolbarInspectorNode6(Node);
end;

procedure TNxToolbarItemView.KeyDown(var Key: Word; Shift: TShiftState);
var
  Number: Integer;
  ButtonAtNumber: TNxToolbarNodeButton;
begin
  Number := NumberKey(Key);
  if Number > -1 then
  begin
    { Skip Divider Buttons }
    ButtonAtNumber := GetButtonAtNumber(Number);

    { Do Click }
    if Assigned(ButtonAtNumber) then
    begin
      { Set Button in "Pressed" state }
      DownButton := ButtonAtNumber;
    end;
  end;
end;

procedure TNxToolbarItemView.KeyUp;
begin
  DownButton := nil;
end;

procedure TNxToolbarItemView.KeyUp(var Key: Word; Shift: TShiftState);
begin
  if Assigned(DownButton) then
  begin
    { Trigger event & Check }
    ClickButton(DownButton);

    inherited;
  end;
end;

procedure TNxToolbarItemView.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  DownButton := GetButtonAtPos(X, Y);
end;

procedure TNxToolbarItemView.MouseLeave;
begin
  HoverButton := nil;
end;

procedure TNxToolbarItemView.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  inherited;
  HoverButton := GetButtonAtPos(X, Y);
end;

procedure TNxToolbarItemView.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  ButtonAtPos: TNxToolbarNodeButton;
begin
  ButtonAtPos := GetButtonAtPos(X, Y);

  { Check if same button }
  if (DownButton = ButtonAtPos)
    and Assigned(ButtonAtPos) then
  begin
    ClickButton(ButtonAtPos);
  end;

  DownButton := nil;
end;

procedure TNxToolbarItemView.PaintValueContent(ContentRect: TRect);
var
  i: Integer;
  ButtonSize: TSize;
  DrawingPoint: TPoint;
  ButtonRect: TRect;
  ButtonState: TNxButtonState;
begin
  { Starting Pos }
  DrawingPoint := Point(GetStartingPos, ContentRect.Top);

  { Draw Buttons }
  for i := 0 to Pred(Count) do
  begin
    ButtonSize.cx := GetButtonWidth(Buttons[i]);
    ButtonSize.cy := ToolbarNode.ButtonHeight;

    if ButtonSize.cy = 0 then ButtonSize.cy := ContentRect.Bottom - ContentRect.Top;

    ButtonRect := Bounds(DrawingPoint.X, DrawingPoint.Y, ButtonSize.cx, ButtonSize.cy);

    { Button State }
    ButtonState := [];
    if Buttons[i] = HoverButton then Include(ButtonState, bsHover);
    if Buttons[i] = DownButton then Include(ButtonState, bsPressed);
    if Buttons[i].Checked then Include(ButtonState, bsChecked);

    { Draw Button }
    DrawButton(ButtonRect, Buttons[i], ButtonState);

    { Move to draw next Button }
    Inc(DrawingPoint.X, ButtonSize.cx);

    { Spacing between Buttons }
    Inc(DrawingPoint.X, ToolbarNode.Spacing);
  end;

  { Draw Node's Value }
  if ToolbarNode.ShowText then
  begin
    Inc(DrawingPoint.X, pdStandard);
    Canvas.Font.Color := clGrayText;
    DrawTextRect(Canvas, Rect(DrawingPoint.X, ContentRect.Top, ContentRect.Right, ContentRect.Bottom), taLeftJustify, taVerticalCenter, Node.DisplayText, wkEllipsis);
  end;

end;

function TNxToolbarItemView.PreferedSize(Entities: TNxInspectorNodeEntity): TSize;
var
  i: Integer;
begin
  { Caption }
  Result := inherited PreferedSize(Entities);

  { Value }
  if etValue in Entities then
  begin
    AssignNode;
    Result.cy := szMinNodeHeight; { Can't be smaller }
    for i := 0 to Pred(Count) do
    begin
      { Add Padding to the Button }
      Result.cy := Max(Buttons[i].ButtonHeight + pdStandard * 2, Result.cy);
    end;
  end;
end;

procedure TNxToolbarItemView.RefreshButton(Button: TNxToolbarNodeButton);
var
  ButtonRect: TRect;
begin
  if Canvas <> nil then
  begin
    ButtonRect := GetButtonRect(Button);
    InvalidateRect(Control.Handle, @ButtonRect, False);
  end;
end;

procedure TNxToolbarItemView.RefreshText;
var
  TextRect: TRect;
begin
  if Canvas <> nil then
  begin
    with TextRect do
    begin
      Left := GetStartingPos + GetButtonsSize + pdStandard;
      Top := ContentRect.Top;
      Right := ContentRect.Right;
      Bottom := ContentRect.Bottom;
    end;
    InvalidateRect(Control.Handle, @TextRect, False);
  end;
end;

procedure TNxToolbarItemView.ReleaseButtonHint;
begin
  if Assigned(FButtonHint) then
  begin
    FButtonHint.ReleaseHandle; { Required }
    { Free }
    FreeAndNil(FButtonHint);
  end;
end;

procedure TNxToolbarItemView.SetDownButton(const Value: TNxToolbarNodeButton);
var
  OldDownButton: TNxToolbarNodeButton;
begin
  if Value <> FDownButton then
  begin
    OldDownButton := FDownButton;
    FDownButton := Value;
    if Assigned(OldDownButton) then RefreshButton(OldDownButton);
    if Assigned(FDownButton) then RefreshButton(FDownButton);
  end;
end;

procedure TNxToolbarItemView.SetHoverButton(const Value: TNxToolbarNodeButton);
var
  OldHoverButton: TNxToolbarNodeButton;
begin
  if Value <> FHoverButton then
  begin
    OldHoverButton := FHoverButton;
    FHoverButton := Value;

    { Since new Button become hover, destroy Hint }
    ReleaseButtonHint;

    if Assigned(OldHoverButton) then RefreshButton(OldHoverButton);
    if Assigned(FHoverButton) then RefreshButton(FHoverButton);

    if Assigned(FHoverButton) then ActivateButtonHint(Value);
  end;
end;

{ TNxProgressInspectorNodeView }

function TNxProgressInspectorNodeView.CaptureMouse: Boolean;
begin
  Result := FDown;
end;

function TNxProgressInspectorNodeView.GetAtPos(X, Y: Integer): Integer;
var
  ClickableRect: TRect;
begin
  ClickableRect := InteractiveRect;

  { Apply Margin }
  InflateRect(ClickableRect, -ProgressNode.OuterMargin, -ProgressNode.OuterMargin);

  { Incl. Divider }
  if Node.EditWidth > 0
    then Inc(ClickableRect.Left, 1);

  { Border Space }
  InflateRect(ClickableRect, -sizProgressBorderSize, -sizProgressBorderSize);

  Result := Round((X - ClickableRect.Left) / (ClickableRect.Right - ClickableRect.Left) * 100) + 1;
end;

function TNxProgressInspectorNodeView.GetProgressNode: TNxProgressInspectorNode6;
begin
  Result := TNxProgressInspectorNode6(Node);
end;

procedure TNxProgressInspectorNodeView.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  ClickableRect: TRect;
begin
  if Button = mbLeft then
  begin
    FDown := True;

    ClickableRect := InteractiveRect;

    { Apply Margin }
    InflateRect(ClickableRect, -ProgressNode.OuterMargin, -ProgressNode.OuterMargin);

    { Incl. Divider }
    if Node.EditWidth > 0
      then Inc(ClickableRect.Left, 1);

    { Border Space }
    InflateRect(ClickableRect, -sizProgressBorderSize, -sizProgressBorderSize);

    if PtInRect(ClickableRect, Point(X, Y))
      then AsFloat := GetAtPos(X, Y);
  end;
end;

procedure TNxProgressInspectorNodeView.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  inherited;
  if Shift = [ssLeft] then AsFloat := GetAtPos(X, Y);
end;

procedure TNxProgressInspectorNodeView.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  FDown := False;
end;

procedure TNxProgressInspectorNodeView.PaintValueContent(ContentRect: TRect);
var
  ProgressView: TNxProgressView;
  ProgressRect: TRect;
begin
  inherited;
  ProgressView := TNxProgressView.Create;

  ProgressRect := InteractiveRect;

  { Incl. Divider }
  if Node.EditWidth > 0
    then Inc(ProgressRect.Left, 1);

  try
    with ProgressView do
    begin
      AsFloat := Self.AsFloat;
      BorderColor := ProgressNode.BorderColor;
      Canvas := Self.Canvas;
      Color := Node.ValueColor;
      Control := Self.Control;
      ClientRect := ProgressRect;
      Max := ProgressNode.Max;
      Min := ProgressNode.Min;
      OuterMargin := ProgressNode.OuterMargin;
      ProgressColor := ProgressNode.ProgressColor;
      ProgressMargin := ProgressNode.ProgressMargin;
      ProgressStyle := ProgressNode.ProgressStyle;
      RoundCorners := ProgressNode.RoundCorners;
      Transparent := ProgressNode.Transparent;

      Style := Self.Style;

      Paint;
    end;
  finally
    FreeAndNil(ProgressView);
  end;
end;

{ TNxTrackbarInspectorNodeView }

procedure TNxTrackbarInspectorNodeView.ActivateSliderHint;
begin
  if not Assigned(FSliderHint) then
  begin
    { Create Hint Window }
    FSliderHint := HintWindowClass.Create(Control);
    FSliderHint.Color := clInfoBk;
  end;

  { Move Hint }
  UpdateSliderHint;
end;

function TNxTrackbarInspectorNodeView.CaptureMouse: Boolean;
begin
  Result := bsPressed in FState;
end;

constructor TNxTrackbarInspectorNodeView.Create(Owner: TNxInspectorNode6);
begin
  inherited;
  FState := [];
end;

destructor TNxTrackbarInspectorNodeView.Destroy;
begin
  ReleaseSliderHint; { If active }
  inherited;
end;

function TNxTrackbarInspectorNodeView.GetPositionAtPos(const X, Y: Integer): Integer;
var
  Spot: Integer;
begin
  with TrackbarNode do
  begin
    Spot := X - TrackRect.Left;
    Result := Round(Spot / GetTrackWidth * (Max - Min) + Min);
  end;
end;

function TNxTrackbarInspectorNodeView.GetTextRect: TRect;
begin
  Result := inherited GetTextRect;
  Result.Right := Result.Left + Node.EditWidth;

  { Apply Padding between Edit & Trackbar }
  Dec(Result.Right, pdStandard);
end;

function TNxTrackbarInspectorNodeView.GetSliderRect: TRect;
var
  Distance: Integer;
  TrackSize, SliderSize: TSize;
begin
  TrackSize.cx := TrackRect.Right - TrackRect.Left;
  TrackSize.cy := TrackRect.Bottom - TrackRect.Top;

  with TrackbarNode do
  begin
    Distance := Round(TrackSize.cx * (FAsInteger - Min) / (Max - Min));
  end;

  SliderSize := GetSliderSize;

  with Result do
  begin
    Left := TrackRect.Left + Distance - SliderSize.cx div 2;
    Top := TrackSize.cy div 2 - SliderSize.cy div 2 + TrackRect.Top;
    Right := Left + SliderSize.cx;
    Bottom := Top + SliderSize.cy;
  end;
end;

function TNxTrackbarInspectorNodeView.GetSliderSize: TSize;
begin
  Result.cy := ContentRect.Bottom - ContentRect.Top + 1;
  Result.cx := Result.cy - 8;
end;

function TNxTrackbarInspectorNodeView.GetTrackBarNode: TNxTrackBarInspectorNode6;
begin
  Result := TNxTrackBarInspectorNode6(Node);
end;

function TNxTrackbarInspectorNodeView.GetTrackRect: TRect;
begin
  Result := InteractiveRect;

  { Add spacing between Edit & Trackbar }
  if Node.EditWidth > 0 then Inc(Result.Left, pdEditToText - 1);

  { Half-size of Slider left }
  Inc(Result.Left, GetSliderSize.cx div 2);

  { Half-size of Slider right }
  Dec(Result.Right, GetSliderSize.cx div 2 + 1);

  { Apply Padding }
  InflateRect(Result, 0, DotToPixel(-szTrackbarPadding));
end;

function TNxTrackbarInspectorNodeView.GetTrackWidth: Integer;
begin
  Result := TrackRect.Right - TrackRect.Left;
end;

function TNxTrackbarInspectorNodeView.HasContent: Boolean;
begin
  Result := TrackbarNode.Editable and True;
end;

procedure TNxTrackbarInspectorNodeView.KeyDown(var Key: Word; Shift: TShiftState);
var
  Change: Integer;
begin
  if ssCtrl in Shift then
    Change := Round((TrackbarNode.Max - TrackbarNode.Min) / 10) { 10% }
  else
    Change := 1;

  case Key of
    VK_LEFT:  AsInteger := AsInteger - Change;
    VK_RIGHT: AsInteger := AsInteger + Change;
    VK_HOME:  AsInteger := Round(TrackbarNode.Min);
    VK_END:   AsInteger := Round(TrackbarNode.Max);
    48:       AsInteger := 0;
    49..57:   begin
                AsInteger := Round((TrackbarNode.Max - TrackbarNode.Min) / 10) * NumberKey(Key);
              end;
  end;
end;

procedure TNxTrackbarInspectorNodeView.KeyUp;
begin
  inherited;
  State := State - [bsPressed];
end;

procedure TNxTrackbarInspectorNodeView.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  Piece, Spot: Double;
begin
  if ssDouble in Shift then
  begin
    Piece := RectWidth(TrackRect) / (TrackbarNode.Max - TrackbarNode.Min);
    Spot := (X - TrackRect.Left) / Piece;
    AsInteger := TrackbarNode.Min + Round(Spot);
  end else
  begin
    AsInteger := GetPositionAtPos(X, Y);

    { Activate Hint too }
    State := State + [bsPressed];
  end;
end;

procedure TNxTrackbarInspectorNodeView.MouseLeave;
begin
  State := State - [bsHover];
end;

procedure TNxTrackbarInspectorNodeView.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  inherited;
  if PtInRect(GetSliderRect, Point(X, Y)) then
  begin
    State := State + [bsHover];
  end else State := State - [bsHover];
  if bsPressed in FState then
  begin
    AsInteger := GetPositionAtPos(X, Y);

    { Repositionate Hint }
    if TrackbarNode.ShowHint then UpdateSliderHint;
  end;
end;

procedure TNxTrackbarInspectorNodeView.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  State := State - [bsPressed];
end;

procedure TNxTrackbarInspectorNodeView.PaintValueContent(ContentRect: TRect);
var
  ButtonState: TNxButtonState;
begin
  inherited;
  ButtonState := State;
  if not Node.Enabled then Include(ButtonState, bsDisabled);
  Style.PaintTrack(TrackRect);
  Style.PaintTrackThumb(GetSliderRect, ButtonState);
end;

procedure TNxTrackbarInspectorNodeView.RefreshSlider;
begin
  Refresh(GetSliderRect);
end;

procedure TNxTrackbarInspectorNodeView.ReleaseSliderHint;
begin
  if Assigned(FSliderHint) then
  begin
    FSliderHint.ReleaseHandle; { Required }
    { Free }
    FreeAndNil(FSliderHint);
  end;
end;

procedure TNxTrackbarInspectorNodeView.SetState(const Value: TNxButtonState);
begin
  if Value <> FState then
  begin
    FState := Value;
    RefreshSlider;
    { Hint }
    if (bsPressed in FState) and TrackbarNode.ShowHint
      then ActivateSliderHint
      else ReleaseSliderHint;
  end;
end;

procedure TNxTrackbarInspectorNodeView.UpdateSliderHint;
var
  HintWidth: Integer;
  SliderRect, HintRect: TRect;
  DisplayText: string;
begin
  if Assigned(FSliderHint) then
  begin
    DisplayText := IntToStr(FAsInteger) + TrackbarNode.TextAfter;

    { Calculate Rect }
    HintRect := FSliderHint.CalcHintRect(Screen.Width, DisplayText, nil);
    SliderRect := GetSliderRect;

    HintWidth := HintRect.Right - HintRect.Left;

    { Cordinates must be "Screen" }
    HintRect.TopLeft := Control.ClientToScreen(SliderRect.TopLeft);

    { Center Hint }
    Dec(HintRect.Left, HintWidth div 2);
    Inc(HintRect.Left, (SliderRect.Right - SliderRect.Left) div 2);

    { Move Hint bellow Slider }
    Inc(HintRect.Top, SliderRect.Bottom - SliderRect.Top);

    HintRect.Bottom := HintRect.Top + HintRect.Bottom;
    HintRect.Right := HintRect.Left + HintRect.Right;

    { Show Hint }
    FSliderHint.ActivateHint(HintRect, DisplayText);
  end;
end;

{ TNxRateInspectorNodeView }

function TNxRateInspectorNodeView.GetAtPos(X, Y: Integer): Integer;
var
  StartPoint: TPoint;
  Star, Pos: Integer;
  Bitmap: TBitmap;
begin
  Result := -1;

  if PtInRect(GetGlyphsRect, Point(X, Y)) then
  begin
    with RateNode do
    begin
      { Calculate Drawing Pos }
      StartPoint := PosInRect(Round(Glyph.Width * Max), Glyph.Height, InteractiveRect, RateNode.Alignment, taVerticalCenter);

      Pos := StartPoint.X;

      for Star := Succ(Round(Min)) to Round(Max) do
      begin
        if Star < Succ(AsInteger)
          then Bitmap := Glyph
          else Bitmap := GlyphPlaceholder;

        if (X >= Pos) and (X <= Pos + Bitmap.Width) then
        begin
          Result := Star;

          Exit;
        end;

        { Move Drawing Position }
        Inc(Pos, Bitmap.Width);
      end;

    end;
  end;

end;

function TNxRateInspectorNodeView.GetGlyphsRect: TRect;
var
  StartPoint: TPoint;
begin
  with RateNode do
  begin
    StartPoint := PosInRect(Round(Glyph.Width * Max), Glyph.Height, InteractiveRect, Alignment, taVerticalCenter);
    Result := Rect(StartPoint.X, StartPoint.Y, StartPoint.X + Round(Glyph.Width * Max), StartPoint.Y + Glyph.Height);
  end;
end;

function TNxRateInspectorNodeView.GetRateNode: TNxRateInspectorNode6;
begin
  Result := TNxRateInspectorNode6(Node);
end;

procedure TNxRateInspectorNodeView.KeyDown(var Key: Word; Shift: TShiftState);
begin
  case Key of
    VK_LEFT,
    VK_BACK:  AsInteger := AsInteger - 1;
    VK_RIGHT: AsInteger := AsInteger + 1;
    48:       AsInteger := 0;
    49..57:   AsInteger := NumberKey(Key);
  end;
end;

procedure TNxRateInspectorNodeView.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbLeft then AsInteger := GetAtPos(X, Y);
end;

procedure TNxRateInspectorNodeView.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  inherited;
  if Shift = [ssLeft] then AsInteger := GetAtPos(X, Y);
end;

procedure TNxRateInspectorNodeView.PaintValueContent(ContentRect: TRect);
var
  Star, X, Y: Integer;
  StartPoint: TPoint;
  Bitmap: TBitmap;
begin
  { Paint Text & Etc. }
  inherited;

  with RateNode do
  begin
    { Calculate Drawing Pos }
    StartPoint := PosInRect(Round(Glyph.Width * Max), Glyph.Height, InteractiveRect, Alignment, taVerticalCenter);

    X := StartPoint.X;
    Y := StartPoint.Y;

    for Star := Succ(Round(Min)) to Round(Max) do
    begin
      if Star < Succ(Self.AsInteger)
        then Bitmap := Glyph
        else Bitmap := GlyphPlaceholder;

      Canvas.Draw(X, Y, Bitmap);

      { Move Drawing Position }
      Inc(X, Bitmap.Width);

      if X > InteractiveRect.Right then Exit;
    end;

  end;

end;

{ TNxColorPaletteNodeView }

procedure TNxColorPaletteNodeView.ActivateColorHint(const Info: TNxColorPaletteInfo);
var
  ColorRect, HintRect: TRect;
  ColorHint: string;
begin
  with ColorPaletteNode do
    case Info.Location of
      ctSelected: if Info.Index <> 0 then Exit;
      ctFavorites: if not InRange(Info.Index, 0, Pred(FavoritesCount)) then Exit;
      ctPalette: if not InRange(Info.Index, 0, Pred(Colors.Count)) then Exit;
      else Exit;
    end;

  FColorHint := HintWindowClass.Create(Control);
  FColorHint.Color := clInfoBk;

  ColorHint := GetColorHintText(Info);

  { Calculate Rect }
  ColorRect := GetColorRect(Info);
  HintRect := FColorHint.CalcHintRect(Screen.Width, ColorHint, nil);

  { Cordinates must be "Screen" }
  HintRect.TopLeft := Control.ClientToScreen(ColorRect.TopLeft);

  { Move Hint bellow Button }
  Inc(HintRect.Top, ColorRect.Bottom - ColorRect.Top);

  HintRect.Bottom := HintRect.Top + HintRect.Bottom;
  HintRect.Right := HintRect.Left + HintRect.Right;

  { Show Hint }
  FColorHint.ActivateHint(HintRect, ColorHint);
end;

constructor TNxColorPaletteNodeView.Create(Owner: TNxInspectorNode6);
begin
  inherited;
  FFullSizeButtonState := [];
  FHoverColor.Location := ctNone;
  FHoverColor.Index := -1;
  FHoverColor.Color := clNone;
end;

function TNxColorPaletteNodeView.GetButtonSize: Integer;
begin
  Result := Node.NodeHeight - (Node.Padding.Top + Node.Padding.Bottom);
  if Result < szColorPaletteButtonMinSize then Result := szColorPaletteButtonMinSize;
end;

function TNxColorPaletteNodeView.GetColorAtPos(
  const Location: TPoint): TNxColorPaletteInfo;
var
  ColorRect, BorderRect: TRect;
  Pos: TPoint;
  i, ColorSize: Integer;
begin
  { Start Pos }
  Pos := ContentRect.TopLeft;

  { Not Found? }
  Result.Location := ctNone;

  { No Border }
  ColorSize := ButtonSize - 2;

  with ColorPaletteNode do
  begin

    { Favorite Color TRect }
    if HasFavorites then
    begin

      { Bound all Favorite Colors }
      BorderRect := Bounds(Pos.X, ContentRect.Top, ColorSize * FavoritesCount + 2, ColorSize + 2);

      if BorderRect.Right <= ContentRect.Right then
      begin
        { Excl. Left Border }
        Inc(Pos.X);

        for i := 0 to Pred(ColorPaletteNode.FavoritesCount) do
        begin
          { Set Rect }
          ColorRect := Bounds(Pos.X, Pos.Y, ColorSize, ColorSize);

          if PtInRect(ColorRect, Location) then
            with Result do
            begin
              Color := Favorite[i];
              Index := i;
              Location := ctFavorites;

              Exit; { Done }
            end;

          Inc(Pos.X, ColorSize);
        end;

        { Excl. Right Border }
        Inc(Pos.X);

        { Right Padding }
        Inc(Pos.X, pdStandard);

      end;

    end; { HasFavorites }

    { Color TRect }

    for i := 0 to Pred(Colors.Count) do
    begin

      if Pos.X + ButtonSize > ContentRect.Right then
      begin
        Inc(Pos.Y, ButtonSize);

        Inc(Pos.Y, pdStandard);

        Pos.X := ContentRect.Left + 1; { Incl. Border }
      end;

      if Pos.Y > ContentRect.Bottom then Exit;

      { Set Rect }
      ColorRect := Bounds(Succ(Pos.X), Pos.Y, ColorSize, ColorSize);

      if PtInRect(ColorRect, Location) then
        with Result do
        begin
          { Convert Required since Colors[] contain
            Values in any color format }
          Color := StrToColor(ColorPaletteNode.Colors[i]);
          Index := i;
          Location := ctPalette;

          Exit; { Done }
        end;

      { Shift X Pos. }
      Inc(Pos.X, ButtonSize);

      { Spacing between Colors }
      Inc(Pos.X);
    end;

  end; { with }

end;

function TNxColorPaletteNodeView.GetColorHintText(const Info: TNxColorPaletteInfo): string;
var
  Color: TColor;
  ColorName: string;
  R, G, B: Byte;
begin
  with ColorPaletteNode do
  begin
    { Actual Color }
    case Info.Location of
      ctSelected: Color := AsColor;
      ctFavorites: Color := Favorite[Info.Index];
      ctPalette: Color := StrToColor(Colors[Info.Index]);
      else Color := clNone;
    end;

    if hdWebName in HintData then
    begin
      ColorName := WebColorName(ColorToWebColor(Color));

      if ColorName <> EmptyStr then
      begin
        Result := ColorName + LnBreak;
      end;
    end;

    if hdWebCode in HintData then
    begin
      Result := Result + HTMLColorToString(Color) + LnBreak;
    end;

    if hdRGB in HintData then
    begin
      GetRGB(Color, R, G, B);

      Result := Result + 'R:' + IntToStr(R) + ' G:' + IntToStr(G) + ' B:' + IntToStr(B);
    end;

  end;

end;

function TNxColorPaletteNodeView.GetColorPaletteNode: TNxColorPaletteInspectorNode6;
begin
  Result := TNxColorPaletteInspectorNode6(Node);
end;

function TNxColorPaletteNodeView.GetColorRect(const Info: TNxColorPaletteInfo): TRect;
var
  ColorRect, BorderRect: TRect;
  Pos: TPoint;
  i, ColorSize: Integer;
begin
  { Start Pos }
  Pos := ContentRect.TopLeft;

  { No Border }
  ColorSize := ButtonSize - 2;

  with ColorPaletteNode do
  begin

    { Favorite Color TRect }
    if HasFavorites then
    begin

      { Bound all Favorite Colors }
      BorderRect := Bounds(Pos.X, ContentRect.Top, ColorSize * FavoritesCount + 2, ColorSize + 2);

      if BorderRect.Right <= ContentRect.Right then
      begin
        { Excl. Left Border }
        Inc(Pos.X);

        for i := 0 to Pred(ColorPaletteNode.FavoritesCount) do
        begin
          { Set Rect }
          ColorRect := Bounds(Pos.X, Pos.Y, ColorSize, ColorSize);

          if (Info.Location = ctFavorites) and (Info.Index = i) then
          begin
            Result := ColorRect;

            Exit; { Done }
          end;

          Inc(Pos.X, ColorSize);
        end;

        { Excl. Right Border }
        Inc(Pos.X);

        { Right Padding }
        Inc(Pos.X, pdStandard);

      end;

    end; { HasFavorites }

    { Color TRect }

    for i := 0 to Pred(Colors.Count) do
    begin

      { Check Fit? }
      if Pos.X + ButtonSize > ContentRect.Right then
      begin
        Inc(Pos.Y, ButtonSize);

        Inc(Pos.Y, pdStandard);

        Pos.X := ContentRect.Left; { Incl. Border }
      end;

      if Pos.Y > ContentRect.Bottom then Exit;

      { Set Rect }
      ColorRect := Bounds(Succ(Pos.X), Pos.Y, ColorSize, ColorSize);

      if Info.Index = i then
      begin
        Result := ColorRect;

        Exit; { Done }
      end;

      { Shift X Pos. }
      Inc(Pos.X, ButtonSize);

      { Spacing between Colors }
      Inc(Pos.X);
    end;

  end; { with }

end;

function TNxColorPaletteNodeView.GetContentRect: TRect;
begin
  Result := inherited GetContentRect;

  { Exclude Button }
  Dec(Result.Right, szDropDownButton);
end;

function TNxColorPaletteNodeView.GetFullSize: Boolean;
begin
  Result := ColorPaletteNode.FullSize;
end;

function TNxColorPaletteNodeView.GetFullSizeButtonRect: TRect;
begin
  Result := Rect(ValueRect.Right - szDropDownButton, ValueRect.Top, ValueRect.Right, ValueRect.Bottom);
end;

function TNxColorPaletteNodeView.GetFullSizeHeight: Integer;
var
  ColorRect, BorderRect: TRect;
  Pos: TPoint;
  i, ColorSize: Integer;
begin
  { Start Pos }
  Pos := ContentRect.TopLeft;

  { No Border }
  ColorSize := ButtonSize - 2;

  with ColorPaletteNode do
  begin
    { AsColor TRect }

    { Excl. Border }
    Inc(Pos.X);
    Inc(Pos.Y);

    ColorRect := Bounds(Pos.X, Pos.Y, ColorSize, ColorSize);

    { Shift X Pos. }
    Pos.X := ColorRect.Right;

    { Excl. Border }
    Inc(Pos.X);

    { Favorite Color TRect }

    if HasFavorites then
    begin
      { Left Spacing }
      Inc(Pos.X, pdStandard);

      { Bound all Favorite Colors }
      BorderRect := Bounds(Pos.X, ContentRect.Top, ColorSize * FavoritesCount + 2, ColorSize + 2);

      if BorderRect.Right <= ContentRect.Right then
      begin
        { Excl. Left Border }
        Inc(Pos.X);

        for i := 0 to Pred(ColorPaletteNode.FavoritesCount) do
        begin
          { Set Rect }
          ColorRect := Bounds(Pos.X, Pos.Y, ColorSize, ColorSize);

          Inc(Pos.X, ColorSize);
        end;

        { Excl. Right Border }
        Inc(Pos.X);

        { Right Padding }
        Inc(Pos.X, pdStandard);

      end;

    end; { HasFavorites }

    { Color TRect }

    for i := 0 to Pred(Colors.Count) do
    begin

      if Pos.X + ButtonSize > ContentRect.Right then
      begin
        Inc(Pos.Y, ButtonSize);

        Inc(Pos.Y, pdStandard);

        Pos.X := ContentRect.Left + 1; { Incl. Border }
      end;

      { Set Rect }
      ColorRect := Bounds(Succ(Pos.X), Pos.Y, ColorSize, ColorSize);

      { Shift X Pos. }
      Inc(Pos.X, ButtonSize);

      { Spacing between Colors }
      Inc(Pos.X);
    end;

    Result := Pos.Y - ContentRect.Top + ButtonSize;

    { Add Padding }
    Inc(Result, Padding.Bottom);

  end; { with }

end;

function TNxColorPaletteNodeView.HasButton: Boolean;
begin
  Result := FullSize or (Height < FullSizeHeight);
end;

procedure TNxColorPaletteNodeView.MouseDown(Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  Color: TNxColorPaletteInfo;
  ColorDialog: TColorDialog;
  Value: TColor;
begin
  inherited;

  { Toggle FullSize }
  if HasButton then
    if PtInRect(GetFullSizeButtonRect, Point(X, Y)) then
    begin
      FullSizeButtonState := FullSizeButtonState + [bsPressed];
      FullSize := not FullSize;

      Exit; { Done }
    end;

  { Set Selected Color }
  Color := GetColorAtPos(Point(X, Y));

  Value := Color.Color;

  if Color.Location <> ctNone then
  begin

    { Double Click on Color }
    if ssDouble in Shift then
    begin
      { Create Dialog }
      ColorDialog := TColorDialog.Create(nil);

      try
        { Select Curr. Color }
        ColorDialog.Color := Color.Color;

        { Open & Pick }
        if ColorDialog.Execute then
          Value := ColorDialog.Color;
      finally
        { Destroy }
        ColorDialog.Free;
      end;

    end;

    { Assign new Value }
    ColorPaletteNode.AsColor := Value;
  end;
end;

procedure TNxColorPaletteNodeView.MouseLeave;
begin
  inherited;
  FHoverColor.Index := -1;
  FHoverColor.Location := ctNone;
  FHoverColor.Color := clNone;

  ReleaseColorHint; { Hide Hint }

  { Un-hover FullSize Button }
  FullSizeButtonState := FullSizeButtonState - [bsHover];
end;

procedure TNxColorPaletteNodeView.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  inherited;
  HoverColor := GetColorAtPos(Point(X, Y));

  if HasButton then
    if PtInRect(GetFullSizeButtonRect, Point(X, Y)) then
    begin
      FullSizeButtonState := FullSizeButtonState + [bsHover];
    end
    else
      FullSizeButtonState := FullSizeButtonState - [bsHover];
end;

procedure TNxColorPaletteNodeView.MouseUp(Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  inherited;
  FullSizeButtonState := FullSizeButtonState - [bsPressed];
end;

procedure TNxColorPaletteNodeView.PaintColor(Canvas: TCanvas; Color: TColor;
  ColorRect: TRect; Inner: Boolean = False);
begin
  with Canvas do
  begin
    if Inner then InflateRect(ColorRect, -1, -1);

    if Color = clNone then
    begin
      Brush.Color := Node.ValueColor;
      FillRect(ColorRect);

      InflateRect(ColorRect, -1, -1);

      { Draw Cross Line }
      Pen.Color := clRed;
      with ColorRect do
      begin
        Polyline([Point(Left, Bottom - 2), Point(Right - 1, Top - 1)]);
        Polyline([Point(Left, Bottom - 1), Point(Right, Top - 1)]);
        Polyline([Point(Left + 1, Bottom - 1), Point(Right, Top)]);
      end;

    end else
    begin
      Brush.Color := Color;
      FillRect(ColorRect);
    end;
  end;
end;

procedure TNxColorPaletteNodeView.PaintPreviewContent(Canvas: TCanvas; Dest: TRect);
begin
  PaintColor(Canvas, ColorPaletteNode.AsColor, Dest, False);
  inherited;
end;

procedure TNxColorPaletteNodeView.PaintValue;
var
  ButtonRect: TRect;
begin
  inherited PaintValue;

  { Draw Button }
  if HasButton then
  begin
    ButtonRect := GetFullSizeButtonRect;

    { Style Button }
    Style.PaintDropDownButton(ButtonRect, FFullSizeButtonState);
  end;
end;

procedure TNxColorPaletteNodeView.PaintValueContent(ContentRect: TRect);
var
  ColorRect, BorderRect: TRect;
  DrawPos: TPoint;
  i, ColorSize: Integer;
  Color: TColor;
begin
  { No Border }
  ColorSize := ButtonSize - 2;

  DrawPos := ContentRect.TopLeft;

  with ColorPaletteNode do
  begin

    { Paint Favorites }
    if HasFavorites then
    begin

      { Bound all Favorite Colors }
      BorderRect := Bounds(DrawPos.X, ContentRect.Top, ColorSize * FavoritesCount + 2, ColorSize + 2);

      if BorderRect.Right <= ContentRect.Right then
      begin
        { Excl. Left Border }
        Inc(DrawPos.X);

        { Draw Frame }
        Style.DrawFrame(BorderRect);

        for i := 0 to Pred(FavoritesCount) do
        begin
          { Set Rect. ColorRect Excl. Border (!) }
          ColorRect := Bounds(DrawPos.X, DrawPos.Y + 1, ColorSize, ColorSize);

          { "Paint" Color }
          PaintColor(Canvas, Favorite[i], ColorRect);

          Inc(DrawPos.X, ColorSize);
        end;

        { Excl. Right Border }
        Inc(DrawPos.X);

        { Right Padding }
        Inc(DrawPos.X, pdStandard);
      end;

    end; { HasFavorites }

    { Paint Colors }

    for i := 0 to Pred(Colors.Count) do
    begin
      { Set Color }
      try
        Color := StrToColor(Colors[i]);
      except
        Color := clNone;
      end;

      if DrawPos.X + ButtonSize > ContentRect.Right then
      begin
        Inc(DrawPos.Y, ButtonSize);

        Inc(DrawPos.Y, pdStandard);

        DrawPos.X := ContentRect.Left;
      end;

      if DrawPos.Y > ContentRect.Bottom then Exit;

      { Set Rect }
      ColorRect := Bounds(DrawPos.X, DrawPos.Y, ButtonSize, ButtonSize);

      { "Paint" Color }
      Style.DrawFrame(ColorRect);
      PaintColor(Canvas, Color, ColorRect, True);

      { Shift X Pos. }
      Inc(DrawPos.X, ButtonSize);

      { Spacing between Colors }
      Inc(DrawPos.X);
    end;

  end; { with }

end;

procedure TNxColorPaletteNodeView.ReleaseColorHint;
begin
  if Assigned(FColorHint) then
  begin
    FColorHint.ReleaseHandle; { Required }
    { Free }
    FreeAndNil(FColorHint);
  end;
end;

procedure TNxColorPaletteNodeView.SetFullSize(const Value: Boolean);
begin
  ColorPaletteNode.FullSize := Value;
end;

procedure TNxColorPaletteNodeView.SetFullSizeButtonState(
  const Value: TNxButtonState);
begin
  if Value <> FFullSizeButtonState then
  begin
    FFullSizeButtonState := Value;

    Refresh(GetFullSizeButtonRect);
  end;
end;

procedure TNxColorPaletteNodeView.SetHoverColor(
  const Value: TNxColorPaletteInfo);
begin
  if (Value.Index <> FHoverColor.Index)
    or (Value.Location <> FHoverColor.Location) then
  begin
    FHoverColor := Value;

    ReleaseColorHint;

    ActivateColorHint(Value);
  end;
end;

end.
