Przeglądaj źródła

design: IDE options frame, by default position absolute, move an absolute element by dragging

mattias 9 miesięcy temu
rodzic
commit
9bcb13f2a7

+ 1 - 1
demo/democomps/DemoCompsReg.pas

@@ -5,7 +5,7 @@ unit DemoCompsReg;
 interface
 
 uses
-  Classes, SysUtils, fresnel.democheckbox, fresnel.demoslider, fresnel.democombobox;
+  Classes, SysUtils, Fresnel.DemoCheckBox, Fresnel.DemoSlider, Fresnel.DemoCombobox;
 
 procedure Register;
 

+ 140 - 0
design/fresnel.dsgnoptions.pas

@@ -0,0 +1,140 @@
+unit Fresnel.DsgnOptions;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, LazFileCache, LazConfigStorage, BaseIDEIntf;
+
+const
+  FresnelDsgnOptsFile = 'fresneldsgnoptions.xml';
+
+type
+
+  { TFresnelDsgnOptions }
+
+  TFresnelDsgnOptions = class(TComponent)
+  private
+    FChangeStamp: int64;
+    FPositionAbsolute: boolean;
+    FSavedStamp: int64;
+    function GetModified: boolean;
+    procedure SetModified(const AValue: boolean);
+    procedure SetPositionAbsolute(const AValue: boolean);
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    procedure IncreaseChangeStamp; inline;
+    procedure Load;
+    procedure Save;
+    procedure LoadFromConfig(Cfg: TConfigStorage);
+    procedure SaveToConfig(Cfg: TConfigStorage);
+  public
+    property ChangeStamp: int64 read FChangeStamp;
+    property Modified: boolean read GetModified write SetModified;
+    property PositionAbsolute: boolean read FPositionAbsolute write SetPositionAbsolute; // true = when putting a new element onto a form use position absolute
+  end;
+
+var
+  FresnelOptions: TFresnelDsgnOptions;
+
+implementation
+
+{ TFresnelDsgnOptions }
+
+procedure TFresnelDsgnOptions.SetPositionAbsolute(const AValue: boolean);
+begin
+  if FPositionAbsolute=AValue then Exit;
+  FPositionAbsolute:=AValue;
+end;
+
+constructor TFresnelDsgnOptions.Create(AOwner: TComponent);
+begin
+  inherited;
+  FChangeStamp:=LUInvalidChangeStamp64;
+  FPositionAbsolute:=true;
+end;
+
+destructor TFresnelDsgnOptions.Destroy;
+begin
+  inherited Destroy;
+end;
+
+procedure TFresnelDsgnOptions.IncreaseChangeStamp;
+begin
+  LUIncreaseChangeStamp64(FChangeStamp);
+end;
+
+procedure TFresnelDsgnOptions.Load;
+var
+  Cfg: TConfigStorage;
+begin
+  Cfg:=GetIDEConfigStorage(FresnelDsgnOptsFile,true);
+  try
+    LoadFromConfig(Cfg);
+  finally
+    Cfg.Free;
+  end;
+end;
+
+procedure TFresnelDsgnOptions.Save;
+var
+  Cfg: TConfigStorage;
+begin
+  Cfg:=GetIDEConfigStorage(FresnelDsgnOptsFile,false);
+  try
+    SaveToConfig(Cfg);
+  finally
+    Cfg.Free;
+  end;
+end;
+
+Const
+  KeyPositionAbsolute = 'positionabsolute/value';
+
+procedure TFresnelDsgnOptions.LoadFromConfig(Cfg: TConfigStorage);
+begin
+  PositionAbsolute:=Cfg.GetValue(KeyPositionAbsolute,true);
+
+  Modified:=false;
+end;
+
+procedure TFresnelDsgnOptions.SaveToConfig(Cfg: TConfigStorage);
+begin
+  Cfg.SetDeleteValue(KeyPositionAbsolute,PositionAbsolute,true);
+
+  Modified:=false;
+end;
+
+function TFresnelDsgnOptions.GetModified: boolean;
+begin
+  Result:=FSavedStamp<>FChangeStamp;
+end;
+
+procedure TFresnelDsgnOptions.SetModified(const AValue: boolean);
+begin
+  if AValue then
+    IncreaseChangeStamp
+  else
+    FSavedStamp:=FChangeStamp;
+end;
+
+procedure DoneFresnelOptions;
+begin
+  if FresnelOptions<>nil then
+  begin
+    try
+      if FresnelOptions.Modified then
+        FresnelOptions.Save;
+    except
+    end;
+    FreeAndNil(FresnelOptions);
+  end;
+end;
+
+finalization
+  DoneFresnelOptions;
+
+end.
+

+ 25 - 0
design/fresnel.dsgnoptsframe.lfm

@@ -0,0 +1,25 @@
+object FresnelOptionsFrame: TFresnelOptionsFrame
+  Left = 0
+  Height = 240
+  Top = 0
+  Width = 320
+  ClientHeight = 240
+  ClientWidth = 320
+  TabOrder = 0
+  DesignLeft = 263
+  DesignTop = 291
+  object PositionAbsoluteCheckBox: TCheckBox
+    AnchorSideLeft.Control = Owner
+    AnchorSideTop.Control = Owner
+    Left = 6
+    Height = 23
+    Top = 6
+    Width = 131
+    BorderSpacing.Left = 6
+    BorderSpacing.Top = 6
+    Caption = 'Position absolute'
+    ParentShowHint = False
+    ShowHint = True
+    TabOrder = 0
+  end
+end

+ 68 - 0
design/fresnel.dsgnoptsframe.pas

@@ -0,0 +1,68 @@
+{ IDE options frame for Fresnel options
+
+  Author: Mattias Gaertner
+}
+unit Fresnel.DsgnOptsFrame;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils,
+  // LCL
+  Forms, StdCtrls, Dialogs,
+  // IdeIntf
+  IDEOptionsIntf, IDEOptEditorIntf,
+  // Fresnel
+  Fresnel.DsgnStrConsts, Fresnel.DsgnOptions;
+
+type
+
+  { TFresnelOptionsFrame }
+
+  TFresnelOptionsFrame = class(TAbstractIDEOptionsEditor)
+    PositionAbsoluteCheckBox: TCheckBox;
+  private
+  public
+    function GetTitle: String; override;
+    procedure Setup({%H-}ADialog: TAbstractOptionsEditorDialog); override;
+    procedure ReadSettings({%H-}AOptions: TAbstractIDEOptions); override;
+    procedure WriteSettings({%H-}AOptions: TAbstractIDEOptions); override;
+    class function SupportedOptionsClass: TAbstractIDEOptionsClass; override;
+  end;
+
+implementation
+
+{$R *.lfm}
+
+{ TFresnelOptionsFrame }
+
+function TFresnelOptionsFrame.GetTitle: String;
+begin
+  Result:='Fresnel';
+end;
+
+procedure TFresnelOptionsFrame.Setup(ADialog: TAbstractOptionsEditorDialog);
+begin
+  PositionAbsoluteCheckBox.Caption:='Position absolute'; // CSS keywords dont need to be translated
+  PositionAbsoluteCheckBox.Hint:=frsWhenPuttingANewElementOntoAFormPositionItAbsoluteU;
+end;
+
+procedure TFresnelOptionsFrame.ReadSettings(AOptions: TAbstractIDEOptions);
+begin
+  PositionAbsoluteCheckBox.Checked:=FresnelOptions.PositionAbsolute;
+end;
+
+procedure TFresnelOptionsFrame.WriteSettings(AOptions: TAbstractIDEOptions);
+begin
+  FresnelOptions.PositionAbsolute:=PositionAbsoluteCheckBox.Checked;
+end;
+
+class function TFresnelOptionsFrame.SupportedOptionsClass: TAbstractIDEOptionsClass;
+begin
+  Result:=IDEEditorGroups.GetByIndex(GroupEnvironment)^.GroupClass;
+end;
+
+end.
+

+ 2 - 0
design/fresnel.dsgnstrconsts.pas

@@ -8,6 +8,8 @@ resourcestring
   frsFresnelApplication = 'Fresnel Application';
   frsFresnelApplicationDesc = 'A graphical Free Pascal application using'
     +' the cross-platform Fresnel library for its GUI.';
+  frsWhenPuttingANewElementOntoAFormPositionItAbsoluteU = 'When putting a new element onto a form, '
+    +'position it absolute using the mouse coordinates';
 
 implementation
 

+ 116 - 10
design/fresnel.register.pas

@@ -16,11 +16,11 @@ interface
 
 uses
   LCLProc, LCLType, Classes, SysUtils, FormEditingIntf, PropEdits, LazIDEIntf,
-  ComponentEditors, LCLIntf, Graphics, Controls, Forms, ProjectIntf,
-  PackageIntf, LazLoggerBase, CodeToolManager, CodeCache,
+  ComponentEditors, IDEOptEditorIntf, LCLIntf, Graphics, Controls, Forms, ProjectIntf,
+  PackageIntf, IDEOptionsIntf, LazLoggerBase, CodeToolManager, CodeCache,
   StdCodeTools, Fresnel.DOM, Fresnel.Controls, Fresnel.Forms,
   Fresnel.Renderer, Fresnel.Classes, Fresnel.LCLApp, Fresnel.LCL,
-  Fresnel.DsgnStrConsts, Fresnel.StylePropEdit;
+  Fresnel.DsgnStrConsts, Fresnel.StylePropEdit, Fresnel.DsgnOptsFrame, Fresnel.DsgnOptions;
 
 const
   ProjDescNameFresnelApplication = 'Fresnel Application';
@@ -57,6 +57,7 @@ type
     procedure GetBounds(AComponent: TComponent; out CurBounds: TRect); override;
     procedure GetClientArea(AComponent: TComponent; out
                       CurClientArea: TRect; out ScrollOffset: TPoint); override;
+    procedure InitComponent(AComponent, NewParent: TComponent; NewBounds: TRect); override;
     procedure Paint; override;
     procedure SetBounds(AComponent: TComponent; NewBounds: TRect); override;
   public
@@ -121,6 +122,9 @@ var
   FileDescFresnelForm: TFileDescFresnelForm;
   ProjDescFresnelApplication: TProjDescFresnelApplication;
 
+var
+  FresnelOptionsFrameID: integer = 1000;
+
 procedure Register;
 
 implementation
@@ -129,21 +133,32 @@ implementation
 
 procedure Register;
 begin
+  FresnelOptions:=TFresnelDsgnOptions.Create(nil);
+
+  // register mediator for designer forms
   FormEditingHook.RegisterDesignerMediator(TFresnelFormMediator);
   FormEditingHook.SetDesignerBaseClassCanAppCreateForm(TFresnelCustomForm,true);
 
+  // register elements
   RegisterComponents('Fresnel',[TDiv,TSpan,TLabel,TButton,TImage,TBody]);
   RegisterComponentRequirements([TDiv,TSpan,TLabel,TButton,TImage,TBody],TFresnelComponentRequirements);
 
+  // register fresnel form as new file type
   FileDescFresnelForm:=TFileDescFresnelForm.Create;
   RegisterProjectFileDescriptor(FileDescFresnelForm,FileDescGroupName);
 
+  // register fresnel application as new project type
   ProjDescFresnelApplication:=TProjDescFresnelApplication.Create;
   RegisterProjectDescriptor(ProjDescFresnelApplication);
 
+  // register property editors
   RegisterPropertyEditor(TypeInfo(String), TFresnelElement, 'Style', TFresnelStylePropertyEditor);
   RegisterPropertyEditor(TypeInfo(String), TFresnelCustomForm, 'Style', THiddenPropertyEditor);
   RegisterPropertyEditor(TypeInfo(TStrings), TFresnelCustomForm, 'Stylesheet', TFresnelStyleSheetPropertyEditor);
+
+  // register IDE options frame
+  FresnelOptionsFrameID:=RegisterIDEOptionsEditor(GroupEnvironment,TFresnelOptionsFrame,
+                                                  FresnelOptionsFrameID)^.Index;
 end;
 
 { TFresnelFormMediator }
@@ -215,8 +230,9 @@ begin
     CurBounds:=FDsgnForm.FormBounds.GetRect;
   end else if AComponent is TFresnelElement then
   begin
+    // return borderbox
     El:=TFresnelElement(AComponent);
-    aBox:=El.GetBoundingClientRect;
+    aBox:=El.RenderedBorderBox;
     FresnelRectToRect(aBox,CurBounds);
   end else
     inherited GetBounds(AComponent,CurBounds);
@@ -225,6 +241,11 @@ end;
 
 procedure TFresnelFormMediator.SetBounds(AComponent: TComponent;
   NewBounds: TRect);
+var
+  El: TFresnelElement;
+  OldStyle: String;
+  NewBorderBox: TFresnelRect;
+  NewLeft, NewTop, NewWidth, NewHeight: TFresnelLength;
 begin
   //debugln(['TFresnelFormMediator.SetBounds ',DbgSName(AComponent),' ',dbgs(NewBounds)]);
   if AComponent=FDsgnForm then
@@ -232,8 +253,50 @@ begin
     FDsgnForm.WSResize(TFresnelRect.Create(NewBounds),NewBounds.Width,NewBounds.Height);
   end else if AComponent is TFresnelElement then
   begin
-    // bounds are controlled by CSS
-    // todo: absolute, fixed
+    // an element (bounds are controlled by CSS)
+    El:=TFresnelElement(AComponent);
+    if El.ComputedPosition in [CSSRegistry.kwAbsolute,CSSRegistry.kwFixed] then
+    begin
+      // NewBounds is borderbox
+      with El.LayoutNode do begin
+        NewBorderBox.SetRect(NewBounds);
+        NewLeft:=NewBorderBox.Left-MarginLeft;
+        NewTop:=NewBorderBox.Top-MarginTop;
+        NewWidth:=NewBorderBox.Width;
+        NewHeight:=NewBorderBox.Height;
+
+        // todo: if parent position is static, use the nearest parent with non static
+        // todo: right and bottom aligned
+
+        OldStyle:=El.Style;
+        case El.ComputedBoxSizing of
+        CSSRegistry.kwBorderBox:
+          begin
+
+          end;
+        CSSRegistry.kwPaddingBox:
+          begin
+            NewWidth:=NewWidth-BorderLeft-BorderRight;
+            NewHeight:=NewHeight-BorderTop-BorderBottom;
+          end;
+        CSSRegistry.kwContentBox:
+          begin
+            NewWidth:=NewWidth-BorderLeft-BorderRight-PaddingLeft-PaddingRight;
+            NewHeight:=NewHeight-BorderTop-BorderBottom-PaddingTop-PaddingBottom;
+          end;
+        end;
+      end;
+
+      if El.GetStyleAttr('left')<>'' then
+        El.SetStyleAttr('left',FloatToCSSPx(NewLeft));
+      if El.GetStyleAttr('top')<>'' then
+        El.SetStyleAttr('top',FloatToCSSPx(NewTop));
+      if El.GetStyleAttr('width')<>'' then
+        El.SetStyleAttr('width',FloatToCSSPx(NewWidth));
+      if El.GetStyleAttr('height')<>'' then
+        El.SetStyleAttr('height',FloatToCSSPx(NewHeight));
+      debugln(['TFresnelFormMediator.SetBounds AComponent=',DbgSName(AComponent),' OldStyle=[',OldStyle,'] OldBorderBox=',FloatToCSSStr(El.RenderedBorderBox.Left),',',FloatToCSSStr(El.RenderedBorderBox.Top),' w=',FloatToCSSStr(El.RenderedBorderBox.Width),',h=',FloatToCSSStr(El.RenderedBorderBox.Height),' box-sizing=',CSSRegistry.Keywords[El.ComputedBoxSizing],' NewLeft,Top=',FloatToCSSStr(NewLeft),',',FloatToCSSStr(NewTop),' NewWH=',FloatToCSSStr(NewWidth),'x',FloatToCSSStr(NewHeight)]);
+    end;
   end else begin
     inherited SetBounds(AComponent, NewBounds);
   end;
@@ -241,14 +304,57 @@ end;
 
 procedure TFresnelFormMediator.GetClientArea(AComponent: TComponent; out
   CurClientArea: TRect; out ScrollOffset: TPoint);
+var
+  El: TFresnelElement;
+  Box, BorderBox: TFresnelRect;
 begin
   if AComponent=FDsgnForm then
   begin
     CurClientArea:=Rect(0,0,round(FDsgnForm.Width),round(FDsgnForm.Height));
     ScrollOffset:=Point(0,0);
-  end else begin
-    inherited GetClientArea(AComponent, CurClientArea, ScrollOffset);
-  end;
+  end else if AComponent is TFresnelElement then begin
+    // return contentbox inside the borderbox
+    El:=TFresnelElement(AComponent);
+    BorderBox:=El.RenderedBorderBox;
+    Box:=El.RenderedContentBox;
+    Box.Offset(-BorderBox.Left,-BorderBox.Top);
+    FresnelRectToRect(Box,CurClientArea);
+  end else
+    inherited;
+end;
+
+procedure TFresnelFormMediator.InitComponent(AComponent, NewParent: TComponent; NewBounds: TRect);
+var
+  El: TFresnelElement;
+  BorderBox: TFresnelRect;
+begin
+  if AComponent is TFresnelElement then
+  begin
+    // set parentcomponent, needed for streaming
+    TFresnelFormMediator(AComponent).SetParentComponent(NewParent);
+    El:=TFresnelElement(AComponent);
+    debugln(['TFresnelFormMediator.InitComponent AComponent=',DbgSName(AComponent),' NewParent=',DbgSName(NewParent),' Bounds=',dbgs(NewBounds)]);
+    if FresnelOptions.PositionAbsolute then
+    begin
+      // todo: if parent position is static, use the nearest parent with non static
+      BorderBox.SetRect(NewBounds);
+      // todo: compute margins via resolver
+      El.SetStyleAttr('position','absolute');
+      El.SetStyleAttr('box-sizing','border-box');
+      El.SetStyleAttr('left',FloatToCSSPx(BorderBox.Left));
+      El.SetStyleAttr('top',FloatToCSSPx(BorderBox.Top));
+      if not (El is TReplacedElement) then
+      begin
+        if NewBounds.Width<=0 then
+          NewBounds.Width:=50;
+        if NewBounds.Height<=0 then
+          NewBounds.Height:=50;
+        El.SetStyleAttr('width',FloatToCSSPx(BorderBox.Width));
+        El.SetStyleAttr('height',FloatToCSSPx(BorderBox.Height));
+      end;
+    end;
+  end else
+    inherited;
 end;
 
 function TFresnelFormMediator.GetComponentOriginOnForm(AComponent: TComponent
@@ -265,7 +371,7 @@ begin
     El:=TFresnelElement(AComponent);
     if not El.Rendered then
       exit(Point(0,0));
-    BorderBox:=El.GetBoundingClientRect;
+    BorderBox:=El.GetBorderBoxOnViewport;
     Result.X:=round(BorderBox.Left);
     Result.Y:=round(BorderBox.Top);
   end else

+ 8 - 0
design/fresneldsgn.lpk

@@ -27,6 +27,14 @@
         <Filename Value="fresnel.dsgnstrconsts.pas"/>
         <UnitName Value="Fresnel.DsgnStrConsts"/>
       </Item>
+      <Item>
+        <Filename Value="fresnel.dsgnoptsframe.pas"/>
+        <UnitName Value="fresnel.dsgnoptsframe"/>
+      </Item>
+      <Item>
+        <Filename Value="fresnel.dsgnoptions.pas"/>
+        <UnitName Value="fresnel.dsgnoptions"/>
+      </Item>
     </Files>
     <RequiredPkgs>
       <Item>

+ 2 - 2
design/fresneldsgn.pas

@@ -8,8 +8,8 @@ unit FresnelDsgn;
 interface
 
 uses
-  Fresnel.Register, Fresnel.StylePropEdit, Fresnel.DsgnStrConsts, 
-  LazarusPackageIntf;
+  Fresnel.Register, Fresnel.StylePropEdit, Fresnel.DsgnStrConsts, Fresnel.DsgnOptsFrame, 
+  Fresnel.DsgnOptions, LazarusPackageIntf;
 
 implementation
 

+ 2 - 2
src/base/fresnel.dom.pas

@@ -1198,7 +1198,7 @@ type
     property UsedBorderBox: TFresnelRect read FUsedBorderBox write FUsedBorderBox; // relative to layout parent's used contentbox
     property UsedContentBox: TFresnelRect read FUsedContentBox write FUsedContentBox; // relative to layout parent's used contentbox
     // renderer
-    function GetBoundingClientRect: TFresnelRect; virtual;
+    function GetBorderBoxOnViewport: TFresnelRect; virtual;
     property Rendered: boolean read FRendered write FRendered;
     property RenderedBorderBox: TFresnelRect read FRenderedBorderBox write FRenderedBorderBox; // relative to layout parent's rendered contentbox
     property RenderedContentBox: TFresnelRect read FRenderedContentBox write FRenderedContentBox; // relative to layout parent's rendered contentbox
@@ -6941,7 +6941,7 @@ begin
   Result:=false;
 end;
 
-function TFresnelElement.GetBoundingClientRect: TFresnelRect;
+function TFresnelElement.GetBorderBoxOnViewport: TFresnelRect;
 var
   Node: TFresnelLayoutNode;
 begin

+ 1 - 1
src/lcl/fresnel.lclevents.pas

@@ -89,7 +89,7 @@ begin
   aEl:=Viewport.GetElementAt(aInit.PagePos.X,aInit.PagePos.Y);
   if aEl=Nil then
     aEl:=FViewport;
-  R:=aEL.GetBoundingClientRect;
+  R:=aEL.GetBorderBoxOnViewport;
   aInit.ControlPos.X:=aInit.PagePos.X-R.Left;
   aInit.ControlPos.Y:=aInit.PagePos.Y-R.Top;
   evt:=aEl.EventDispatcher.CreateEvent(aEl,evtClick) as TFresnelMouseEvent;