unit pixel_classes;

interface

uses
  System.SysUtils, System.Variants, Rtti, TypInfo;

{$TYPEINFO ON}

type
  TZString = type string;

  TNullValue = record
  end;

  TNullable<T> = record
  private
    class function DoAdd(const A, B: T): T; static;
    class function DoSubtract(const A, B: T): T; static;
    class function DoMultiply(const A, B: T): T; static;
    class function DoDivide(const A, B: T): T; static;
  public
    Value: T;
    HasValue: Boolean;

    class operator Implicit(const AValue: T): TNullable<T>;
    class operator Implicit(const AValue: TNullValue): TNullable<T>;
    class operator Explicit(const AValue: T): TNullable<T>;
    class operator Equal(const A: TNullable<T>; const B: TNullValue): Boolean;
    class operator NotEqual(const A: TNullable<T>; const B: TNullValue): Boolean;
    class operator Implicit(const A: TNullable<T>): T;
    class operator Add(const A: TNullable<T>; const B: T): TNullable<T>;
    class operator Subtract(const A: TNullable<T>; const B: T): TNullable<T>;
    class operator Multiply(const A: TNullable<T>; const B: T): TNullable<T>;
    class operator Divide(const A: TNullable<T>; const B: T): TNullable<T>;
  end;

var NullValue: TNullValue;

implementation

class function TNullable<T>.DoAdd(const A, B: T): T;
var
  VA, VB: Variant;
begin
  VA := TValue.From<T>(A).AsVariant;
  VB := TValue.From<T>(B).AsVariant;
  Result := TValue.FromVariant(VA + VB).AsType<T>;
end;

class function TNullable<T>.DoSubtract(const A, B: T): T;
var
  VA, VB: Variant;
begin
  VA := TValue.From<T>(A).AsVariant;
  VB := TValue.From<T>(B).AsVariant;
  Result := TValue.FromVariant(VA - VB).AsType<T>;
end;

class function TNullable<T>.DoMultiply(const A, B: T): T;
var
  VA, VB: Variant;
begin
  VA := TValue.From<T>(A).AsVariant;
  VB := TValue.From<T>(B).AsVariant;
  Result := TValue.FromVariant(VA * VB).AsType<T>;
end;

class function TNullable<T>.DoDivide(const A, B: T): T;
var
  VA, VB: Variant;
begin
  VA := TValue.From<T>(A).AsVariant;
  VB := TValue.From<T>(B).AsVariant;
  Result := TValue.FromVariant(VA / VB).AsType<T>;
end;

class operator TNullable<T>.Implicit(const AValue: T): TNullable<T>;
begin
  Result.Value := AValue;
  Result.HasValue := True;
end;

class operator TNullable<T>.Implicit(const AValue: TNullValue): TNullable<T>;
begin
  Result.Value := Default(T);
  Result.HasValue := False;
end;

class operator TNullable<T>.Explicit(const AValue: T): TNullable<T>;
begin
  Result.Value := AValue;
  Result.HasValue := True;
end;

class operator TNullable<T>.Equal(const A: TNullable<T>; const B: TNullValue): Boolean;
begin
  Result := not A.HasValue;
end;

class operator TNullable<T>.NotEqual(const A: TNullable<T>; const B: TNullValue): Boolean;
begin
  Result := A.HasValue;
end;

class operator TNullable<T>.Implicit(const A: TNullable<T>): T;
begin
  if A.HasValue then
    Result := A.Value
  else
    Result := Default(T);
end;

class operator TNullable<T>.Add(const A: TNullable<T>; const B: T): TNullable<T>;
begin
  if not A.HasValue then
    Exit(NullValue);
  Result.HasValue := True;
  Result.Value := DoAdd(A.Value, B);
end;

class operator TNullable<T>.Subtract(const A: TNullable<T>; const B: T): TNullable<T>;
begin
  if not A.HasValue then
    Exit(NullValue);
  Result.HasValue := True;
  Result.Value := DoSubtract(A.Value, B);
end;

class operator TNullable<T>.Multiply(const A: TNullable<T>; const B: T): TNullable<T>;
begin
  if not A.HasValue then
    Exit(NullValue);
  Result.HasValue := True;
  Result.Value := DoMultiply(A.Value, B);
end;

class operator TNullable<T>.Divide(const A: TNullable<T>; const B: T): TNullable<T>;
begin
  if not A.HasValue then
    Exit(NullValue);
  Result.HasValue := True;
  Result.Value := DoDivide(A.Value, B);
end;

end.

----------------

function TORMBinding.GetNullableBooleanState(const Value: TValue): TCheckBoxState;
begin
  if Value.IsEmpty then
    Result := cbGrayed
  else
  begin
    var NullableBool := Value.AsType<TNullable<Boolean>>;
    if not NullableBool.HasValue then
      Result := cbGrayed
    else if NullableBool.Value then
      Result := cbChecked
    else
      Result := cbUnchecked;
  end;
end;

--------------

fix the GetNullableBooleanState function above