unit PXT.Widgets;
(*
 * Copyright (c) 2015 - 2025 Dr. Yuriy Kotsarenko. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 *)
{< High-level wrappers around Afterwarp UI widgets. }
interface

{$INCLUDE PXT.Config.inc}

uses
  SysUtils, RTTI, PXT.Types, PXT.Graphics;

type
  // A combination of widget property name, type, behavior and its value.
  TWidgetPropertyValue = record
    Name: Utf8String;
    PropType: TWidgetPropertyType;
    Behavior: TWidgetPropertyBehavior;
    Value: TValue;
  end;

  // Enumerator for retrieving widget's properties.
  TWidgetPropertyEnumerator = record
  private
    FWidget: TUntypedObjectHandle;
    FProperty: TWidgetPropertyValue;
    FLocation: PtrUInt;
    FStarted: Boolean;
    function GetCurrent: TWidgetPropertyValue; inline;
  public
    // Creates a widget's property enumerator.
    class function Create(const AWidget: TUntypedObjectHandle): TWidgetPropertyEnumerator; static; inline;

    // Moves enumerator to the next property.
    function MoveNext: Boolean; inline;

    // Retrieves current property and its value.
    property Current: TWidgetPropertyValue read GetCurrent;
  end;

  // An instantiator proxy for creating enumerator of the widget properties.
  TWidgetPropertiesEnumeratorFactory = record
  private
    FWidget: TUntypedObjectHandle;
  public
    // Creates the factory associated with the given widget.
    class function Create(
      const AWidget: TUntypedObjectHandle): TWidgetPropertiesEnumeratorFactory; static; inline;

    // Returns enumerator for retrieving list of existing properties.
    function GetEnumerator: TWidgetPropertyEnumerator; inline;
  end;

  // Pointer to @link(TWidget).
  PWidget = ^TWidget;

  // Widget class that represents a user interface element.
  TWidget = record
  private
    FHandle: TUntypedObjectHandle;

    function GetPayload: TObjectPayload; inline;
    function GetChildCount: Integer; inline;
    function GetTexture(const ATextureType: TWidgetManagerTextureType): TTexture; inline;
    function GetBatchCount: Integer; inline;
    function GetFormat: TPixelFormat; inline;
    function GetMultisamples: Integer; inline;
    function GetAttributes: Cardinal; inline;
    function GetValue(const AName: Utf8String): TValue; inline;
    procedure SetValue(const AName: Utf8String; const AValue: TValue); inline;
    function GetValueAsString(const AName: Utf8String): Utf8String;
    function GetName: Utf8String; inline;
    procedure SetName(const AName: Utf8String); inline;
    function GetClassName: Utf8String; inline;
    function GetStyle: Utf8String; inline;
    procedure SetStyle(const AStyle: Utf8String); inline;
    function GetRect: TFloatRect; inline;
    procedure SetRect(const ARect: TFloatRect); inline;
    function GetAlignment: TWidgetAlignment;
    procedure SetAlignment(const AAlignment: TWidgetAlignment); inline;
    function GetVisible: Boolean; inline;
    procedure SetVisible(const AVisible: Boolean); inline;
    function GetEnabled: Boolean; inline;
    procedure SetEnabled(const AEnabled: Boolean); inline;
    function GetFocused: Boolean; inline;
    procedure SetFocused(const AFocused: Boolean); inline;
    function GetZoneCount: Integer; inline;
    function GetParentZone: Integer; inline;
    procedure SetParentZone(const AParentZone: Integer); inline;
    procedure SetTransitoryZone(const ATransitoryZone: Integer); inline;
    function GetMargins: TMargins; inline;
    procedure SetMargins(const AMargins: TMargins); inline;
    function GetPadding(const AZoneIndex: Integer): TMargins; inline;
    procedure SetPadding(const AZoneIndex: Integer; const APadding: TMargins); inline;
    function GetClientRect(const AZoneIndex: Integer): TFloatRect; inline;
    function GetProperties: TWidgetPropertiesEnumeratorFactory; inline;
    function GetApplication: TApplication; inline;
    function GetTextRenderer: TTextRenderer; inline;
    function GetScale: Double; inline;
    procedure SetScale(const AScale: Double); inline;
    function GetCursor: TAppCursor; inline;
  private
    class function ValueToPropertyType(const AValue: TValue): TWidgetPropertyType; static;
    class function RetrievePropertyValue(const AHandle: TUntypedObjectHandle;
      const AProperty: TWidgetProperty; const AName: Utf8String): TValue; static;
  public
    // Releases the widget.
    procedure Free; inline;

    // Converts local position to screen coordinates.
    function LocalToScreen(const ALocalPos: TPoint2f): TPoint2f; inline;

    // Converts screen coordinates to local position.
    function ScreenToLocal(const AScreenPos: TPoint2f): TPoint2f; inline;

    // Puts the widget on front of other components.
    procedure BringToFront; inline;

    // Puts the widget behind all other components.
    procedure SendToBack; inline;

    // Schedules widget repaint during next update cycle.
    procedure Invalidate; inline;

    // Schedules widget repositioning during next update cycle.
    procedure Accomodate; inline;

    // Updates widget and its children. Returns @true if repaint is required.
    // Note: this can only be performed on widget manager.
    function Update(const ALatency: Double): Boolean; inline;

    // Widget receives and processes mouse (or touch) input.
    procedure AcceptMouse(const AEvent: TMouseEvent; const AButton: TMouseButton;
      const APosition: TPoint2f); inline;

    // Widget receives and processes keyboard input.
    procedure AcceptKey(const AEvent: TKeyEvent; const AKey: TKey; const ACharCode: TUnicodeChar); inline;

    // Renders widget's area and all controls within.
    // Note: this can only be performed on window manager component.
    function Present: Boolean; inline;

    // Invokes a widget's event.
    procedure InvokeEvent(const AEventName: Utf8String); inline;

    // Returns the value of an existing widget property with the given name.
    function GetProperty(const AName: Utf8String): TValue;

    // Updates an existing widget property with the given name.
    procedure SetProperty(const AName: Utf8String; const AValue: TValue;
      const AInvokeChanged: Boolean = True);

    // Specifies a new external event callback.
    procedure SetExternalEvent(const AEvent: TWidgetExternalEvent; const AUser: Pointer); inline;

    // Returns current external event callback.
    procedure GetExternalEvent(out AEvent: PWidgetExternalEvent; out AUser: Pointer); inline;

    // Wrapped module's handle.
    property Handle: TUntypedObjectHandle read FHandle;

    // Widget's payload.
    property Payload: TObjectPayload read GetPayload;

    // Number of existing children widgets.
    property ChildCount: Integer read GetChildCount;

    // Returns one of textures used for the composition.
    // Note: this can only be performed on widget manager.
    property Texture[const ATextureType: TWidgetManagerTextureType]: TTexture read GetTexture;

    // Total number of canvas batches.
    // Note: this can only be performed on widget manager.
    property BatchCount: Integer read GetBatchCount;

    // Pixel format of composition textures.
    // Note: this can only be performed on widget manager.
    property Format: TPixelFormat read GetFormat;

    // Returns multisamples of rendering composition texture.
    // Note: this can only be performed on widget manager.
    property Multisamples: Integer read GetMultisamples;

    // Returns attributes with which the widget manager was created.
    // Note: this can only be performed on widget manager.
    property Attributes: Cardinal read GetAttributes;

    // Retrieves an existing value, updates an existing value or creates a new value.
    // Raises an exception if the value cannot be obtained or changed.
    property Value[const AName: Utf8String]: TValue read GetValue write SetValue; default;

    /// Widget's name (case-insensitive).
    property Name: Utf8String read GetName write SetName;

    /// Widget's class name.
    property ClassName: Utf8String read GetClassName;

    /// Widget's visual style.
    property Style: Utf8String read GetStyle write SetStyle;

    // Widget's rectangle (in logical units).
    property Rect: TFloatRect read GetRect write SetRect;

    // Widget's alignment.
    property Alignment: TWidgetAlignment read GetAlignment write SetAlignment;

    // Widget's visibility.
    property Visible: Boolean read GetVisible write SetVisible;

    // Indicates whether the widget is able to receive keyboard input.
    property Enabled: Boolean read GetEnabled write SetEnabled;

    // Indicates whether the widget is focused or not.
    property Focused: Boolean read GetFocused write SetFocused;

    // Returns number of supported client zones. If widget does not support placement of child controls,
    // this function returns -1.
    property ZoneCount: Integer read GetZoneCount;

    // Child's identifier that corresponds to certain parent's client area.
    property ParentZone: Integer read GetParentZone write SetParentZone;

    // Widget's margins (spacing outside of the widget).
    property Margins: TMargins read GetMargins write SetMargins;

    // Widget's padding for a particular zone (spacing inside of widget).
    property Padding[const AZoneIndex: Integer]: TMargins read GetPadding write SetPadding;

    // Returns widget client area's rectangle. If widget does not support placement of child controls,
    // then client rectangle should be its entire area.
    property ClientRect[const AZoneIndex: Integer]: TFloatRect read GetClientRect;

    // Returns a factory object that creates enumerator for listing widget's properties.
    property Properties: TWidgetPropertiesEnumeratorFactory read GetProperties;

    // Properties that can be accessed only from widget manager.

    // Returns application associated with the widget manager.
    property Application: TApplication read GetApplication;

    // Returns text renderer associated with the widget manager.
    property TextRenderer: TTextRenderer read GetTextRenderer;

    // General scale of widget manager and all widgets parented to it.
    property Scale: Double read GetScale write SetScale;

    // Currently active application cursor.
    property Cursor: TAppCursor read GetCursor;
  end;

  // Helper module for TWidget.
  TWidgetHelper = record helper for TWidget
  private
    function GetManager: TWidget; inline;
    function GetParent: TWidget; inline;
    procedure SetParent(const AParent: TWidget); inline;
    function GetChild(const AIndex: Integer): TWidget; inline;
    function GetWidget(const AName: Utf8String): TWidget; inline;
    function GetByPayload(const APayload: TObjectPayload): TWidget; inline;
  public
    // Creates a new widget of the given class type (case-insensitive), assigns it requested instance name
    // (case-insensitive) and optional payload. The widget is commonly accessed by its instance name, which
    // must be unique. Possible values for "className" are: Widget, Label, Button, Edit, RadioButton,
    // CheckBox, GaugeEdit, SpinEdit, Placer, Panel, TabSheets,  Window and SketchPad.
    class function Create(const AManager: TWidget; const AClassName: Utf8String; const AName: Utf8String;
      const APayload: TObjectPayload = nil): TWidget; static;

    // Creates a widget manager with the given parameters.
    class function CreateManager(const AApplication: TApplication; const ATextRenderer: TTextRenderer;
      const AName: Utf8String; const AFormat: TPixelFormat = TPixelFormat.Unknown;
      const AMultisamples: Integer = 0; const AAttributes: Cardinal = 0;
      const APayload: TObjectPayload = nil): TWidget; static;

    // Attempts to find a given widget in the list of existing children and returns its index.
    function IndexOf(const AWidget: TWidget): Integer; inline;

    // Searches through children hierarchy and returns widget that is pointed by the given local position.
    // If no child is found, self is returned. This is particularly useful for mouse hit test.
    function FindAt(const APosition: TPoint2f): TWidget; inline;

    // Manager associated with this widget.
    property Manager: TWidget read GetManager;

    // Widget's parent. A parent typically manages the lifetime of the widget, so releasing the parent would
    // also release all of its child hierarchy.
    property Parent: TWidget read GetParent write SetParent;

    // Returns an existing child widget.
    property Children[const AIndex: Integer]: TWidget read GetChild;

    // Returns a widget with the given name.
    // Note: this can only be performed on widget manager.
    property Widget[const AName: Utf8String]: TWidget read GetWidget;

    // Returns a widget with the given payload.
    // Note: this can only be performed on widget manager.
    property ByPayload[const APayload: TObjectPayload]: TWidget read GetByPayload;
  end;

resourcestring
  SInvalidPropertyName = 'Invalid property name (%s)';
  SCouldNotRetrieveProperty = 'Could not retrieve string property (%s)';
  SCouldNotChangeProperty = 'Could not change string property (%s)';
  SCouldNotUpdateWidgetName = 'Could not update widget''s name (%s)';
  SCouldNotUpdateWidgetStyle = 'Could not update widget''s style (%s)';
  SCouldNotRetrieveWidgetRectangle = 'Could not retrieve widget''s rectangle';
  SCouldNotUpdateWidgetRectangle = 'Could not update widget''s rectangle';
  SCouldNotRetrieveWidgetAlignment = 'Could not retrieve widget''s alignment';
  SCouldNotUpdateWidgetAlignment = 'Could not update widget''s alignment';
  SCouldNotRetrieveWidgetZoneCount = 'Could not retrieve widget''s zone count';
  SCouldNotRetrieveWidgetParentZone = 'Could not retrieve widget''s parent zone';
  SCouldNotUpdateWidgetParentZone = 'Could not update widget''s parent zone';
  SCouldNotUpdateWidgetTransitoryZone = 'Could not update widget''s transitory zone';
  SCouldNotRetrieveWidgetMargins = 'Could not retrieve widget''s margins';
  SCouldNotUpdateWidgetMargins = 'Could not update widget''s margins';
  SCouldNotRetrieveWidgetPadding = 'Could not retrieve widget''s padding';
  SCouldNotUpdateWidgetPadding = 'Could not update widget''s padding';
  SCouldNotRetrieveWidgetClientRect = 'Could not retrieve widget''s client rectangle';
  SCouldNotRetrieveTextRendererFromWidgetManager = 'Could not retrieve text renderer from widget''s manager';
  SPropertyWithGivenNameDoesNotExist = 'Property with the given name (%s) does not exist';
  SCouldNotDeterminePropertyType = 'Could not determine property (%s) type for the given value';
  SCouldNotUpdateWidgetProperty = 'Could not update a property (%s) of the widget';
  SCouldNotCreateWidget = 'Could not create a widget of class (%s) and name (%s)';
  SCouldNotCreateWidgetManager = 'Could not create a widget manager';
  SCouldNotSetParentForWidget = 'Could not change widget''s parent';
  SCouldNotRetrieveWidgetChild = 'Could not retrieve widget''s child (%d)';
  SCouldNotRetrieveWidgetWithName = 'Could not retrieve widget with the given name (%s)';
  SCouldNotRetrieveWidgetWithPayload = 'Could not retrieve widget with the given payload';
  SCouldNotIdentifyWidgetProperty = 'Could not identify widget''s property';

implementation

uses
  TypInfo, PXT.Headers;

{$REGION 'TWidgetPropertyEnumerator'}

class function TWidgetPropertyEnumerator.Create(
  const AWidget: TUntypedObjectHandle): TWidgetPropertyEnumerator;
begin
  FillChar(Result, SizeOf(TWidgetPropertyEnumerator), 0);
  Result.FWidget := AWidget;
end;

function TWidgetPropertyEnumerator.GetCurrent: TWidgetPropertyValue;
begin
  if Length(FProperty.Name) < 1 then
    raise EAfterwarpException.Create(SCouldNotIdentifyWidgetProperty);

  Result := FProperty;
end;

function TWidgetPropertyEnumerator.MoveNext: Boolean;
var
  LProperty: TWidgetProperty;
begin
  if FStarted and (FLocation = 0) then
    Exit(False);

  FStarted := True;
  FillChar(LProperty, SizeOf(TWidgetProperty), 0);
  LProperty.Location := FLocation;

  if not WidgetPropertyIdentify(FWidget, @LProperty) then
  begin
    FLocation := 0;
    Exit(False);
  end;

  FProperty.Name := LProperty.Name;
  if Length(FProperty.Name) < 1 then
  begin
    FLocation := 0;
    Exit(False);
  end;

  FLocation := LProperty.Location;
  FProperty.PropType := LProperty.PropType;
  FProperty.Behavior := LProperty.Behavior;
  FProperty.Value := TWidget.RetrievePropertyValue(FWidget, LProperty, FProperty.Name);
  Result := True;
end;

class function TWidgetPropertiesEnumeratorFactory.Create(
  const AWidget: TUntypedObjectHandle): TWidgetPropertiesEnumeratorFactory;
begin
  Result.FWidget := AWidget;
end;

function TWidgetPropertiesEnumeratorFactory.GetEnumerator: TWidgetPropertyEnumerator;
begin
  Result := TWidgetPropertyEnumerator.Create(FWidget);
end;

{$ENDREGION}
{$REGION 'TWidget'}

procedure TWidget.Free;
begin
  if FHandle <> nil then
  begin
    WidgetDestroy(FHandle);
    FHandle := nil;
  end;
end;

function TWidget.GetPayload: TObjectPayload;
begin
  Result := WidgetGetPayload(FHandle);
end;

function TWidget.GetChildCount: Integer;
begin
  Result := WidgetGetChildCount(FHandle);
end;

function TWidget.GetTexture(const ATextureType: TWidgetManagerTextureType): TTexture;
begin
  TWidget(Result).FHandle := WidgetManagerGetTexture(FHandle, ATextureType);
end;

function TWidget.GetBatchCount: Integer;
begin
  Result := WidgetManagerBatchCount(FHandle);
end;

function TWidget.GetFormat: TPixelFormat;
begin
  Result := WidgetManagerGetFormat(FHandle);
end;

function TWidget.GetMultisamples: Integer;
begin
  Result := WidgetManagerGetMultisamples(FHandle);
end;

function TWidget.GetAttributes: Cardinal;
begin
  Result := WidgetManagerGetAttributes(FHandle);
end;

function TWidget.GetValue(const AName: Utf8String): TValue;
begin
  Result := GetProperty(AName);
end;

procedure TWidget.SetValue(const AName: Utf8String; const AValue: TValue);
begin
  SetProperty(AName, AValue);
end;

function TWidget.GetValueAsString(const AName: Utf8String): Utf8String;
var
  LResLength: LongInt;
begin
  Result := '';
  LResLength := 0;

  if not WidgetGetPropertyAsString(FHandle, PAnsiChar(AName), nil, @LResLength) then
    Exit;

  if LResLength > 0 then
  begin
    SetLength(Result, LResLength);
    if not WidgetGetPropertyAsString(FHandle, PAnsiChar(AName), @Result[1], @LResLength) then
      raise EAfterwarpException.CreateFmt(SCouldNotRetrieveProperty, [AName]);
  end;
end;

function TWidget.GetName: Utf8String;
begin
  Result := GetValueAsString('Name');
end;

procedure TWidget.SetName(const AName: Utf8String);
begin
  if not WidgetSetProperty(FHandle, 'Name', TWidgetPropertyType.TextString, PAnsiChar(AName), True) then
    raise EAfterwarpException.CreateFmt(SCouldNotUpdateWidgetName, [AName]);
end;

function TWidget.GetClassName: Utf8String;
begin
  Result := GetValueAsString('Class');
end;

function TWidget.GetStyle: Utf8String;
begin
  Result := GetValueAsString('Style');
end;

procedure TWidget.SetStyle(const AStyle: Utf8String);
begin
  if not WidgetSetProperty(FHandle, 'Style', TWidgetPropertyType.TextString, PAnsiChar(AStyle), True) then
    raise EAfterwarpException.CreateFmt(SCouldNotUpdateWidgetStyle, [AStyle]);
end;

function TWidget.GetRect: TFloatRect;
begin
  if not WidgetGetProperty(FHandle, 'Rect', TWidgetPropertyType.Rect, @Result) then
    raise EAfterwarpException.Create(SCouldNotRetrieveWidgetRectangle);
end;

procedure TWidget.SetRect(const ARect: TFloatRect);
begin
  if not WidgetSetProperty(FHandle, 'Rect', TWidgetPropertyType.Rect, @ARect, True) then
    raise EAfterwarpException.Create(SCouldNotUpdateWidgetRectangle);
end;

function TWidget.GetAlignment: TWidgetAlignment;
begin
  if not WidgetGetProperty(FHandle, 'Alignment', TWidgetPropertyType.Enumeration, @Result) then
    raise EAfterwarpException.Create(SCouldNotRetrieveWidgetAlignment);
end;

procedure TWidget.SetAlignment(const AAlignment: TWidgetAlignment);
begin
  if not WidgetSetProperty(FHandle, 'Alignment', TWidgetPropertyType.Enumeration, @AAlignment, True) then
    raise EAfterwarpException.Create(SCouldNotUpdateWidgetAlignment);
end;

function TWidget.GetVisible: Boolean;
begin
  Result := GetProperty('Visible').AsType<Boolean>;
end;

procedure TWidget.SetVisible(const AVisible: Boolean);
begin
  SetProperty('Visible', TValue.From<Boolean>(AVisible));
end;

function TWidget.GetEnabled: Boolean;
begin
  Result := GetProperty('Enabled').AsType<Boolean>;
end;

procedure TWidget.SetEnabled(const AEnabled: Boolean);
begin
  SetProperty('Enabled', TValue.From<Boolean>(AEnabled));
end;

function TWidget.GetFocused: Boolean;
begin
  Result := GetProperty('Focused').AsType<Boolean>;
end;

procedure TWidget.SetFocused(const AFocused: Boolean);
begin
  SetProperty('Focused', TValue.From<Boolean>(AFocused));
end;

function TWidget.GetZoneCount: Integer;
begin
  if not WidgetGetProperty(FHandle, 'ZoneCount', TWidgetPropertyType.Integer, @Result) then
    raise EAfterwarpException.Create(SCouldNotRetrieveWidgetZoneCount);
end;

function TWidget.GetParentZone: Integer;
begin
  if not WidgetGetProperty(FHandle, 'ParentZone', TWidgetPropertyType.Integer, @Result) then
    raise EAfterwarpException.Create(SCouldNotRetrieveWidgetParentZone);
end;

procedure TWidget.SetParentZone(const AParentZone: Integer);
begin
  if not WidgetSetProperty(FHandle, 'ParentZone', TWidgetPropertyType.Integer, @AParentZone, True) then
    raise EAfterwarpException.Create(SCouldNotUpdateWidgetParentZone);
end;

procedure TWidget.SetTransitoryZone(const ATransitoryZone: Integer);
begin
  if not WidgetSetProperty(FHandle, 'Transitory.Zone', TWidgetPropertyType.Integer, @ATransitoryZone, True) then
    raise EAfterwarpException.Create(SCouldNotUpdateWidgetTransitoryZone);
end;

function TWidget.GetMargins: TMargins;
begin
  if not WidgetGetProperty(FHandle, 'Margins', TWidgetPropertyType.Margins, @Result) then
    raise EAfterwarpException.Create(SCouldNotRetrieveWidgetMargins);
end;

procedure TWidget.SetMargins(const AMargins: TMargins);
begin
  if not WidgetSetProperty(FHandle, 'Margins', TWidgetPropertyType.Margins, @AMargins, True) then
    raise EAfterwarpException.Create(SCouldNotUpdateWidgetMargins);
end;

function TWidget.GetPadding(const AZoneIndex: Integer): TMargins;
begin
  SetTransitoryZone(AZoneIndex);
  if not WidgetGetProperty(FHandle, 'Padding', TWidgetPropertyType.Margins, @Result) then
    raise EAfterwarpException.Create(SCouldNotRetrieveWidgetPadding);
end;

procedure TWidget.SetPadding(const AZoneIndex: Integer; const APadding: TMargins);
begin
  SetTransitoryZone(AZoneIndex);
  if not WidgetSetProperty(FHandle, 'Padding', TWidgetPropertyType.Margins, @APadding, True) then
    raise EAfterwarpException.Create(SCouldNotUpdateWidgetPadding);
end;

function TWidget.GetClientRect(const AZoneIndex: Integer): TFloatRect;
begin
  SetTransitoryZone(AZoneIndex);
  if not WidgetGetProperty(FHandle, 'ClientRect', TWidgetPropertyType.Rect, @Result) then
    raise EAfterwarpException.Create(SCouldNotRetrieveWidgetClientRect);
end;

function TWidget.GetProperties: TWidgetPropertiesEnumeratorFactory;
begin
  Result := TWidgetPropertiesEnumeratorFactory.Create(FHandle);
end;

function TWidget.GetApplication: TApplication;
begin
  Result := TApplication.Create(WidgetManagerGetApplication(FHandle));
end;

function TWidget.GetTextRenderer: TTextRenderer;
begin
  TCustomGraphicsObject(Result).Handle := WidgetManagerGetTextRenderer(FHandle);
  if Result.Handle = nil then
    raise EAfterwarpException.Create(SCouldNotRetrieveTextRendererFromWidgetManager);
end;

function TWidget.GetScale: Double;
begin
  Result := GetProperty('Scale').AsType<Double>;
end;

procedure TWidget.SetScale(const AScale: Double);
begin
  SetProperty('Scale', TValue.From<Double>(AScale));
end;

function TWidget.GetCursor: TAppCursor;
begin
  Result := TAppCursor(GetProperty('Cursor').AsType<Integer>);
end;

class function TWidget.ValueToPropertyType(const AValue: TValue): TWidgetPropertyType;
begin
   if AValue.Kind = tkEnumeration then
    Result := TWidgetPropertyType.Enumeration
{$IFDEF FPC}
  else if AValue.Kind = tkBool then
    Result := TWidgetPropertyType.Boolean
  else if AValue.Kind in [tkSString, tkAString] then
    Result := TWidgetPropertyType.TextString
{$ENDIF}
  else if AValue.IsOrdinal then
    Result := TWidgetPropertyType.Integer
  else if AValue.Kind = tkFloat then
    Result := TWidgetPropertyType.Real
  else if AValue.Kind in [tkUString, tkWString] then
    Result := TWidgetPropertyType.TextString
  else if AValue.IsType<TPoint2f> then
    Result := TWidgetPropertyType.Point
  else if AValue.IsType<TVector3f> then
    Result := TWidgetPropertyType.Vector
  else if AValue.IsType<TFloatRect> then
    Result := TWidgetPropertyType.Rect
  else if AValue.IsType<TVector3f> then
    Result := TWidgetPropertyType.Vector
  else if AValue.IsType<TMargins> then
    Result := TWidgetPropertyType.Margins
  else if AValue.IsType<TIntColor> then
    Result := TWidgetPropertyType.Color
  else if AValue.IsType<TColorPair> then
    Result := TWidgetPropertyType.ColorPair
  else if AValue.IsType<TColorRect> then
    Result := TWidgetPropertyType.ColorRect
  else if AValue.IsType<TFontParameters> then
    Result := TWidgetPropertyType.Font
  else
    Result := TWidgetPropertyType.None;
end;

class function TWidget.RetrievePropertyValue(const AHandle: TUntypedObjectHandle;
  const AProperty: TWidgetProperty; const AName: Utf8String): TValue;
var
  LValueString: Utf8String;
  LValueStringLength: LongInt;
begin
  if AProperty.Behavior = TWidgetPropertyBehavior.Event then
    Exit(TValue.Empty); // Event has no value.

  if AProperty.PropType = TWidgetPropertyType.None then
    raise EAfterwarpException.CreateFmt(SCouldNotDeterminePropertyType, [AName]);

  if AProperty.PropType = TWidgetPropertyType.TextString then
  begin
    LValueString := '';

    if Byte(AProperty.Data.ValueString[0]) > SizeOf(AProperty.Data.ValueString) - 2 then
    begin // String is too long, need to retrieve it using a dedicated function.
      LValueStringLength := 0;

      if WidgetGetPropertyAsString(AHandle, PAnsiChar(AName), nil, @LValueStringLength) and
        (LValueStringLength > 0) then
      begin
        SetLength(LValueString, LValueStringLength);
        if not WidgetGetPropertyAsString(AHandle, PAnsiChar(AName), @LValueString[1],
          @LValueStringLength) then
          raise EAfterwarpException.CreateFmt(SCouldNotRetrieveProperty, [AName]);
      end;
    end
    else
    begin // Direct copy from the returned data.
      SetLength(LValueString, Byte(AProperty.Data.ValueString[0]));
      Move(AProperty.Data.ValueString[1], LValueString[1], Length(LValueString));
    end;
    Exit(TValue.From<Utf8String>(LValueString));
  end;

  case AProperty.PropType of
    TWidgetPropertyType.Integer:
      Result := TValue.From<Integer>(AProperty.Data.ValueInteger);

    TWidgetPropertyType.Int64:
      Result := TValue.From<Int64>(AProperty.Data.ValueInt64);

    TWidgetPropertyType.Float:
      Result := TValue.From<Single>(AProperty.Data.ValueFloat);

    TWidgetPropertyType.Real:
      Result := TValue.From<Double>(AProperty.Data.ValueReal);

    TWidgetPropertyType.Boolean:
      Result := TValue.From<Boolean>(AProperty.Data.ValueBool);

    TWidgetPropertyType.Point:
      Result := TValue.From<TPoint2f>(AProperty.Data.ValuePoint);

    TWidgetPropertyType.Vector:
      Result := TValue.From<TVector3f>(AProperty.Data.ValueVector);

    TWidgetPropertyType.Rect:
      Result := TValue.From<TFloatRect>(AProperty.Data.ValueRect);

    TWidgetPropertyType.Margins:
      Result := TValue.From<TMargins>(AProperty.Data.ValueMargins);

    TWidgetPropertyType.Color:
      Result := TValue.From<TIntColor>(AProperty.Data.ValueColor);

    TWidgetPropertyType.ColorPair:
      Result := TValue.From<TColorPair>(AProperty.Data.ValueColorPair);

    TWidgetPropertyType.ColorRect:
      Result := TValue.From<TColorRect>(AProperty.Data.ValueColorRect);

    TWidgetPropertyType.Enumeration:
      Result := TValue.From<Integer>(AProperty.Data.ValueEnum);

    TWidgetPropertyType.Font:
      Result := TValue.From<TFontParameters>(AProperty.Data.ValueFont);
  else
    raise EAfterwarpException.CreateFmt(SCouldNotDeterminePropertyType, [AName]);
  end;
end;

function TWidget.LocalToScreen(const ALocalPos: TPoint2f): TPoint2f;
begin
  WidgetLocalToScreen(FHandle, @Result, @ALocalPos);
end;

function TWidget.ScreenToLocal(const AScreenPos: TPoint2f): TPoint2f;
begin
  WidgetScreenToLocal(FHandle, @Result, @AScreenPos);
end;

procedure TWidget.BringToFront;
begin
  WidgetBringToFront(FHandle);
end;

procedure TWidget.SendToBack;
begin
  WidgetSendToBack(FHandle);
end;

procedure TWidget.Invalidate;
begin
  WidgetInvalidate(FHandle);
end;

procedure TWidget.Accomodate;
begin
  WidgetAccomodate(FHandle);
end;

function TWidget.Update(const ALatency: Double): Boolean;
begin
  Result := WidgetUpdate(FHandle, ALatency);
end;

procedure TWidget.AcceptMouse(const AEvent: TMouseEvent; const AButton: TMouseButton;
  const APosition: TPoint2f);
begin
  WidgetAcceptMouse(FHandle, AEvent, AButton, @APosition);
end;

procedure TWidget.AcceptKey(const AEvent: TKeyEvent; const AKey: TKey; const ACharCode: TUnicodeChar);
begin
  WidgetAcceptKey(FHandle, AEvent, AKey, ACharCode);
end;

function TWidget.Present: Boolean;
begin
  Result := WidgetManagerPresent(FHandle);
end;

procedure TWidget.InvokeEvent(const AEventName: Utf8String);
begin
  WidgetInvokeEvent(FHandle, PAnsiChar(AEventName));
end;

function TWidget.GetProperty(const AName: Utf8String): TValue;
var
  LProperty: TWidgetProperty;
begin
  if (Length(AName) < 1) or (Length(AName) > 255) then
    raise EAfterwarpException.CreateFmt(SInvalidPropertyName, [AName]);

  FillChar(LProperty, SizeOf(TWidgetProperty), 0);
  Move(AName[1], LProperty.Name[0], Length(AName));

  if not WidgetPropertyIdentify(FHandle, @LProperty) then
    raise EAfterwarpException.CreateFmt(SCouldNotRetrieveProperty, [AName]);

  Result := RetrievePropertyValue(FHandle, LProperty, AName);
end;

procedure TWidget.SetProperty(const AName: Utf8String; const AValue: TValue; const AInvokeChanged: Boolean);
var
  LPropType: TWidgetPropertyType;
  LValueOrdinal: Int64;
  LValueDouble: Double;
  LValueBoolean: LongBool;
  LValueString: Utf8String;
  LSuccess: Boolean;
begin
  LPropType := ValueToPropertyType(AValue);

  case LPropType of
    TWidgetPropertyType.Integer,
    TWidgetPropertyType.Int64,
    TWidgetPropertyType.Enumeration:
      begin
        LValueOrdinal := AValue.AsOrdinal;
        LSuccess := WidgetSetProperty(FHandle, PAnsiChar(AName), TWidgetPropertyType.Int64, @LValueOrdinal,
          AInvokeChanged);
      end;

    TWidgetPropertyType.Float,
    TWidgetPropertyType.Real:
      begin
        LValueDouble := AValue.AsExtended;
        LSuccess := WidgetSetProperty(FHandle, PAnsiChar(AName), TWidgetPropertyType.Real, @LValueDouble,
          AInvokeChanged);
      end;

    TWidgetPropertyType.Boolean:
      begin
        LValueBoolean := AValue.AsBoolean;
        LSuccess := WidgetSetProperty(FHandle, PAnsiChar(AName), LPropType, @LValueBoolean, AInvokeChanged);
      end;

    TWidgetPropertyType.TextString:
      begin
        LValueString := Utf8String(AValue.AsString);
        LSuccess := WidgetSetProperty(FHandle, PAnsiChar(AName), LPropType, PAnsiChar(LValueString),
          AInvokeChanged);
      end;

    TWidgetPropertyType.Point,
    TWidgetPropertyType.Vector,
    TWidgetPropertyType.Rect,
    TWidgetPropertyType.Margins,
    TWidgetPropertyType.Color,
    TWidgetPropertyType.ColorPair,
    TWidgetPropertyType.ColorRect,
    TWidgetPropertyType.Font:
      LSuccess := WidgetSetProperty(FHandle, PAnsiChar(AName), LPropType, AValue.GetReferenceToRawData,
        AInvokeChanged);
  else
    raise EAfterwarpException.CreateFmt(SCouldNotDeterminePropertyType, [AName]);
  end;
  if not LSuccess then
    raise EAfterwarpException.CreateFmt(SCouldNotUpdateWidgetProperty, [AName]);
end;

procedure TWidget.SetExternalEvent(const AEvent: TWidgetExternalEvent; const AUser: Pointer);
begin
  WidgetSetExternalEvent(FHandle, AEvent, AUser);
end;

procedure TWidget.GetExternalEvent(out AEvent: PWidgetExternalEvent; out AUser: Pointer);
begin
  WidgetGetExternalEvent(FHandle, @AEvent, @AUser);
end;

{$ENDREGION}
{$REGION 'TWidgetHelper'}

class function TWidgetHelper.Create(const AManager: TWidget; const AClassName: Utf8String;
  const AName: Utf8String; const APayload: TObjectPayload): TWidget;
begin
  Result.FHandle := WidgetCreate(AManager.Handle, PAnsiChar(AClassName), PAnsiChar(AName), APayload);
  if Result.FHandle = nil then
    raise EAfterwarpException.CreateFmt(SCouldNotCreateWidget, [AClassName, AName]);
end;

class function TWidgetHelper.CreateManager(const AApplication: TApplication;
  const ATextRenderer: TTextRenderer; const AName: Utf8String; const AFormat: TPixelFormat;
  const AMultisamples: Integer; const AAttributes: Cardinal; const APayload: TObjectPayload): TWidget;
var
  LApplicationHandle: TUntypedObjectHandle;
begin
  LApplicationHandle := nil;
  if AApplication <> nil then
    LApplicationHandle := AApplication.Handle;

  Result.FHandle := WidgetManagerCreate(LApplicationHandle, ATextRenderer.Handle, PAnsiChar(AName), APayload,
    AFormat, AMultisamples, AAttributes);
  if Result.FHandle = nil then
    raise EAfterwarpException.Create(SCouldNotCreateWidgetManager);
end;

function TWidgetHelper.IndexOf(const AWidget: TWidget): Integer;
begin
  Result := WidgetGetChildIndex(FHandle, AWidget.Handle);
end;

function TWidgetHelper.FindAt(const APosition: TPoint2f): TWidget;
begin
  Result.FHandle := WidgetFindAt(FHandle, @APosition);
end;

function TWidgetHelper.GetManager: TWidget;
begin
  Result.FHandle := WidgetGetManager(FHandle);
end;

function TWidgetHelper.GetParent: TWidget;
begin
  Result.FHandle := WidgetGetParent(FHandle);
end;

procedure TWidgetHelper.SetParent(const AParent: TWidget);
begin
  if not WidgetSetParent(FHandle, AParent.Handle) then
    raise EAfterwarpException.Create(SCouldNotSetParentForWidget);
end;

function TWidgetHelper.GetChild(const AIndex: Integer): TWidget;
begin
  Result.FHandle := WidgetGetChildByIndex(FHandle, AIndex);
  if Result.FHandle = nil then
    raise EAfterwarpException.CreateFmt(SCouldNotRetrieveWidgetChild, [AIndex]);
end;

function TWidgetHelper.GetWidget(const AName: Utf8String): TWidget;
begin
  Result.FHandle := WidgetManagerGetWidgetByName(FHandle, PAnsiChar(AName));
  if Result.FHandle = nil then
    raise EAfterwarpException.CreateFmt(SCouldNotRetrieveWidgetWithName, [AName]);
end;

function TWidgetHelper.GetByPayload(const APayload: TObjectPayload): TWidget;
begin
  Result.FHandle := WidgetManagerGetWidgetByPayload(FHandle, APayload);
  if Result.FHandle = nil then
    raise EAfterwarpException.Create(SCouldNotRetrieveWidgetWithPayload);
end;

{$ENDREGION}

end.