Преглед изворни кода

renderer: draw default scrollbars

mattias пре 5 месеци
родитељ
комит
b8fd767ce1

+ 3 - 0
demo/ScrollBox/MainUnit.lfm

@@ -21,6 +21,9 @@ object MainForm: TMainForm
         Style = 'color: red;'
         Caption = 'Label1'
       end
+      object Div2: TDiv
+        Style = 'width: 50px; height: 50px;'
+      end
     end
   end
 end

+ 1 - 0
demo/ScrollBox/MainUnit.pas

@@ -15,6 +15,7 @@ type
   TMainForm = class(TFresnelForm)
     Body1: TBody;
     Div1: TDiv;
+    Div2: TDiv;
     Label1: TLabel;
     procedure MainFormCreate(Sender: TObject);
   private

+ 12 - 0
src/base/fresnel.classes.pas

@@ -184,6 +184,7 @@ function RectFre(const Left, Top, Right, Bottom: TFresnelLength): TFresnelRect;
 function BoundsRectFre(const Left, Top, Width, Height: TFresnelLength): TFresnelRect; overload;
 
 function FPColor(c: TCSSAlphaColor): TFPColor; overload;
+function FPMixColors(const AColor1, AColor2: TFPColor; Part1: Double): TFPColor;
 
 Procedure FLLog(aType: TEventType; Const Msg : string); overload;
 Procedure FLLog(aType: TEventType; Const Fmt : string; Const Args : Array of const); overload;
@@ -299,6 +300,17 @@ begin
   Result.Alpha:=Result.Alpha or (Result.Alpha shl 8);
 end;
 
+function FPMixColors(const AColor1, AColor2: TFPColor; Part1: Double): TFPColor;
+var
+  Part2: Double;
+begin
+  Part2 := 1-Part1;
+  Result.Alpha := Round(AColor1.Alpha * Part1 + AColor2.Alpha * Part2);
+  Result.Red := Round(AColor1.Red * Part1 + AColor2.Red * Part2);
+  Result.Green := Round(AColor1.Green * Part1 + AColor2.Green * Part2);
+  Result.Blue := Round(AColor1.Blue * Part1 + AColor2.Blue * Part2);
+end;
+
 procedure FLLog(aType: TEventType; const Msg: string);
 begin
   TFresnelComponent.DoLog(aType,Msg);

+ 51 - 7
src/base/fresnel.dom.pas

@@ -1064,6 +1064,12 @@ type
     );
   TFresnelLayoutModes = set of TFresnelLayoutMode;
 
+  TFresnelLayoutFlag = (
+    flfHorizontalScrollbar,
+    flfVerticalScrollbar
+    );
+  TFresnelLayoutFlags = set of TFresnelLayoutFlag;
+
   { TFresnelLayoutNode }
 
   TFresnelLayoutNode = class(TComponent)
@@ -1097,6 +1103,7 @@ type
     Container: TFresnelElement;
     SkipLayout: boolean; // e.g. element or ancestor display:none or visibility=collapse
     SkipRendering: boolean; // e.g. element or ancestor display:none or visibility<>visible
+    Flags: TFresnelLayoutFlags;
 
     // used values
     ZIndex: TFresnelLength; // position=static has 0, non static have z-index+0.5
@@ -1125,7 +1132,7 @@ type
     // scroll values below are only valid if CanScrollX or CanScrollY
     ScrollGutterBothEdges: boolean; // when scrollbar needs gutter space, add space on both edges
     ScrollGutterStable: boolean; // true: classic(not overlay) scrollbars need gutter if overflow is auto, scroll, or hidden, even if the box is not overflowing
-    ScrollGutterWidth: TCSSNumericalID; // 0, none, thin, auto, see attribute 'scrollbar-width'
+    ScrollbarWidth: TCSSNumericalID; // 0, none, thin, auto, see attribute 'scrollbar-width'
 
     // Left, Top, Right, Bottom are initialized with the CSS values, then applied depending on position
     // the result is the MarginBox
@@ -1308,6 +1315,8 @@ type
     FEventDispatcher : TFresnelEventDispatcher;
     FUsedBorderBox: TFresnelRect;
     FUsedContentBox: TFresnelRect;
+    function GetClientHeight: TFresnelLength;
+    function GetClientWidth: TFresnelLength;
     function GetNodeCount: integer;
     function GetNodes(Index: integer): TFresnelElement;
     function GetPeudoNodeCount: integer;
@@ -1490,11 +1499,19 @@ type
     function GetContainerContentHeight(UseNaNOnFail: boolean): TFresnelLength; virtual; // used value
     function GetContainerOffset: TFresnelPoint; virtual;
     function IsBlockFormattingContext: boolean; virtual;
+    property ClientWidth: TFresnelLength read GetClientWidth;
+    property ClientHeight: TFresnelLength read GetClientHeight;
     property LayoutNode: TFresnelLayoutNode read FLayoutNode write FLayoutNode;
     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
+    // Scrolling
+    property ScrollLeft: TFresnelLength read FScrollLeft write SetScrollLeft;
+    property ScrollTop: TFresnelLength read FScrollTop write SetScrollTop;
+    property ScrollWidth: TFresnelLength read GetScrollWidth;
+    property ScrollHeight: TFresnelLength read GetScrollHeight;
     // Renderer
     function GetBorderBoxOnViewport: TFresnelRect; virtual;
+    function GetRenderedPaddingBox: TFresnelRect;
     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
@@ -1509,11 +1526,6 @@ type
     property Resolver: TCSSResolver read FResolver;
     property ViewportConnected: boolean read GetViewportConnected write SetViewportConnected; // true for example if using a Font of Viewport
     property Viewport: TFresnelViewport read FViewPort;
-    // Scroll
-    property ScrollLeft: TFresnelLength read FScrollLeft write SetScrollLeft;
-    property ScrollTop: TFresnelLength read FScrollTop write SetScrollTop;
-    property ScrollWidth: TFresnelLength read GetScrollWidth;
-    property ScrollHeight: TFresnelLength read GetScrollHeight;
   published
     property CSSClasses: TStrings read FCSSClasses write SetCSSClasses;
     property Style: string read FStyle write SetStyle;
@@ -6464,6 +6476,8 @@ end;
 
 procedure TFresnelLayoutNode.ResetUsedLengths;
 begin
+  Flags:=[];
+
   MarginLeft:=0;
   MarginRight:=0;
   MarginTop:=0;
@@ -6488,7 +6502,7 @@ begin
   ScrollGutterBottom:=0;
   ScrollGutterBothEdges:=false;
   ScrollGutterStable:=false;
-  ScrollGutterWidth:=0;
+  ScrollbarWidth:=0;
 
   Left:=NaN;
   Top:=NaN;
@@ -7407,6 +7421,24 @@ begin
   Result:=FChildren.Count;
 end;
 
+function TFresnelElement.GetClientWidth: TFresnelLength;
+var
+  n: TFresnelLayoutNode;
+begin
+  n:=LayoutNode;
+  Result:=n.Width-n.ScrollGutterLeft-n.ScrollGutterRight;
+  if Result<0 then Result:=0;
+end;
+
+function TFresnelElement.GetClientHeight: TFresnelLength;
+var
+  n: TFresnelLayoutNode;
+begin
+  n:=LayoutNode;
+  Result:=n.Height-n.ScrollGutterTop-n.ScrollGutterBottom;
+  if Result<0 then Result:=0;
+end;
+
 function TFresnelElement.GetViewportConnected: boolean;
 begin
   Result:=fesViewportConnected in FStates;
@@ -8287,6 +8319,18 @@ begin
   //debugln(['TFresnelElement.GetBoundingClientRect ',Name,' Result=',Result.Left,',',Result.Top,',',Result.Right,',',Result.Bottom]);
 end;
 
+function TFresnelElement.GetRenderedPaddingBox: TFresnelRect;
+var
+  N: TFresnelLayoutNode;
+begin
+  Result:=RenderedContentBox;
+  N:=LayoutNode;
+  Result.Left:=Result.Left-N.PaddingLeft;
+  Result.Top:=Result.Top-N.PaddingTop;
+  Result.Right:=Result.Right+N.PaddingRight;
+  Result.Bottom:=Result.Bottom+N.PaddingBottom;
+end;
+
 function TFresnelElement.GetComputedBorderRadius(Corner: TFresnelCSSCorner
   ): TFresnelPoint;
 var

+ 16 - 6
src/base/fresnel.layouter.pas

@@ -257,6 +257,7 @@ var
   ContSize: TFresnelLayoutNode.TContentSize;
   Size: TFresnelPoint;
   HasMaxWidth, HasMaxHeight: Boolean;
+  El: TFresnelElement;
 begin
   MaxWidth:=Node.Width;
   if IsNan(MaxWidth) then
@@ -315,6 +316,15 @@ begin
     writeln('TFLNodeLayouter.Apply "',Node.Element.Name,'" ScrollSize: ',FloatToCSSStr(Node.ScrollWidth),'x',FloatToCSSStr(Node.ScrollHeight),' Width=',FloatToCSSStr(Node.Width),' Height=',FloatToCSSStr(Node.Height));
     {$ENDIF}
 
+    El:=Node.Element;
+    if Node.ScrollbarWidth<>CSSRegistry.kwNone then
+    begin
+      if (Node.ScrollWidth>aClientWidth) and (El.ComputedOverflowX<>CSSRegistry.kwHidden) then
+        Include(Node.Flags,flfHorizontalScrollbar);
+      if (Node.ScrollHeight>aClientHeight) and (El.ComputedOverflowY<>CSSRegistry.kwHidden) then
+        Include(Node.Flags,flfVerticalScrollbar);
+    end;
+
     // todo: ScrollLeft, ScrollTop
     // todo: scroll anchor
   end else begin
@@ -780,18 +790,18 @@ begin
   if CanScrollX or CanScrollY then
   begin
     ComputeScrollbarGutter;
-    if ScrollGutterWidth=CSSIDNone then
-      ScrollGutterWidth:=Element.GetComputedKeyword(fcaScrollbarWidth,CSSRegistry.Chk_ScrollbarWidth_KeywordIDs);
-    case ScrollGutterWidth of
+    if ScrollbarWidth=CSSIDNone then
+      ScrollbarWidth:=Element.GetComputedKeyword(fcaScrollbarWidth,CSSRegistry.Chk_ScrollbarWidth_KeywordIDs);
+    case ScrollbarWidth of
     CSSIDNone:
-      ScrollGutterWidth:=CSSRegistry.kwAuto;
+      ScrollbarWidth:=CSSRegistry.kwAuto;
     CSSRegistry.kwNone:
       exit;
     end;
   end else
     exit;
   {$IFDEF VerboseFresnelScrolling}
-  writeln('TUsedLayoutNode.ComputeScrollbarsNoChildren ',Element.Name,' ComputedOverflowX=',CSSRegistry.Keywords[Element.ComputedOverflowX],' ComputedOverflowY=',CSSRegistry.Keywords[Element.ComputedOverflowY],' BothEdges=',ScrollGutterBothEdges,' Stable=',ScrollGutterStable,' ScrollGutterWidth=',CSSRegistry.Keywords[ScrollGutterWidth]);
+  writeln('TUsedLayoutNode.ComputeScrollbarsNoChildren ',Element.Name,' ComputedOverflowX=',CSSRegistry.Keywords[Element.ComputedOverflowX],' ComputedOverflowY=',CSSRegistry.Keywords[Element.ComputedOverflowY],' BothEdges=',ScrollGutterBothEdges,' Stable=',ScrollGutterStable,' ScrollbarWidth=',CSSRegistry.Keywords[ScrollbarWidth]);
   {$ENDIF}
 
   CalcGutter(Element.ComputedOverflowX,true);
@@ -807,7 +817,7 @@ begin
   RB:=0;
   VP:=Element.Viewport;
   if VP.ScrollbarsOverlay then exit;
-  case ScrollGutterWidth of
+  case ScrollbarWidth of
   CSSRegistry.kwNone: exit;
   CSSRegistry.kwThin: W:=VP.ScrollbarsThinWidth[Horizontal];
   else

+ 210 - 2
src/base/fresnel.renderer.pas

@@ -30,6 +30,8 @@ const
 
 type
 
+  { TFresnelRenderer }
+
   TFresnelRenderer = class(TComponent,IFresnelRenderer)
   private
     FSubPixel: boolean;
@@ -40,7 +42,7 @@ type
       { TBorderAndBackground }
 
       TBorderAndBackground = class
-      Private
+      private
         FHasBorder : TCalcBoolean;
         FHasRadius : TCalcBoolean;
         FSameBorderWidth : TCalcBoolean;
@@ -60,6 +62,22 @@ type
         function HasRadius : Boolean;
         property Renderer : TFresnelRenderer read FRenderer;
       end;
+
+      { TScrollBar }
+
+      TScrollBar = class
+      private
+        FRenderer : TFresnelRenderer;
+      public
+        Box: TFresnelRect;
+        Horizontal: boolean;
+        Position: TFresnelLength; // ScrollLeft/Top
+        Size: TFresnelLength; // ScrollWidth/Height
+        Page: TFresnelLength; // ClientWidth/Height
+        Color: TFPColor;
+        constructor Create(aRenderer: TFresnelRenderer);
+        property Renderer: TFresnelRenderer read FRenderer;
+      end;
   protected
     // Not in IFresnelRenderer
     // Create backend-specific TBorderAndBackground if needed
@@ -70,6 +88,11 @@ type
     procedure DrawElBackground(El: TFresnelElement; Params: TBorderAndBackground); virtual;
     // Draw the border of the element. This is called after drawing the background. Not called if PrepareBackgroundBorder returned False.
     procedure DrawElBorder(El: TFresnelElement; Params: TBorderAndBackground); virtual;
+    function CreateScrollBar : TScrollBar; virtual;
+    // Draw the scrollbars of the element. Called after drawing borders.
+    procedure DrawScrollBar(El: TFresnelElement; aBar: TScrollBar); virtual;
+    procedure DrawScrollBarCorner(El: TFresnelElement; const r: TFresnelRect); virtual;
+    procedure DrawScrollBars(El: TFresnelElement); virtual;
     // Draw an element
     procedure DrawElement(El: TFresnelElement); virtual;
     // Draw the children of the element
@@ -485,6 +508,184 @@ begin
   end;
 end;
 
+function TFresnelRenderer.CreateScrollBar: TScrollBar;
+begin
+  Result:=TScrollBar.Create(Self);
+end;
+
+procedure TFresnelRenderer.DrawScrollBar(El: TFresnelElement; aBar: TScrollBar);
+var
+  TrackColor, ThumbColor: TFPColor;
+  FromPos, ToPos, l: TFresnelLength;
+  r: TFresnelRect;
+  Border, Radius: integer;
+  ThumbR: TFresnelRoundRect;
+  c: TFresnelCSSCorner;
+begin
+  if El=nil then ;
+  r:=aBar.Box;
+
+  // draw track
+  TrackColor:=colLtGray;
+  FillRect(TrackColor,r);
+  if aBar.Size<1 then
+    exit;
+
+  // draw thumb
+  ThumbColor:=colDkGray;
+  FromPos:=aBar.Position/aBar.Size;
+  if FromPos<0 then FromPos:=0;
+  if FromPos>1 then FromPos:=1;
+  ToPos:=(aBar.Position+aBar.Page)/aBar.Size;
+  if ToPos<0 then ToPos:=0;
+  if ToPos>1 then ToPos:=1;
+  if aBar.Horizontal then
+  begin
+    if r.Width<r.Height then exit;
+    Border:=round(r.Height/6);
+  end else begin
+    if r.Height<r.Width then exit;
+    Border:=round(r.Width/6);
+  end;
+  Radius:=Border*2;
+  for c in TFresnelCSSCorner do begin
+    ThumbR.Radii[c].X:=Radius;
+    ThumbR.Radii[c].Y:=Radius;
+  end;
+  ThumbR.Box.Left:=r.Left+Border;
+  ThumbR.Box.Top:=r.Top+Border;
+  ThumbR.Box.Right:=r.Right-Border;
+  ThumbR.Box.Bottom:=r.Bottom-Border;
+  if aBar.Horizontal then
+  begin
+    l:=ThumbR.Box.Width-2*Radius;
+    ThumbR.Box.Left:=ThumbR.Box.Left+FromPos*l;
+    ThumbR.Box.Right:=ThumbR.Box.Left+2*Radius+ToPos*l;
+  end else begin
+    l:=ThumbR.Box.Height-2*Radius;
+    ThumbR.Box.Top:=ThumbR.Box.Top+FromPos*l;
+    ThumbR.Box.Bottom:=ThumbR.Box.Top+2*Radius+ToPos*l;
+  end;
+
+  RoundRect(ThumbColor,ThumbR,true);
+end;
+
+procedure TFresnelRenderer.DrawScrollBarCorner(El: TFresnelElement; const r: TFresnelRect);
+var
+  TrackColor: TFPColor;
+begin
+  if El=nil then ;
+  TrackColor:=colLtGray;
+  FillRect(TrackColor,r);
+end;
+
+procedure TFresnelRenderer.DrawScrollBars(El: TFresnelElement);
+var
+  LNode: TUsedLayoutNode;
+  BarWidth, BarHeight: TFresnelLength;
+  r, aPaddingBox: TFresnelRect;
+  aScrollbar: TScrollBar;
+  IsLeft, HasVertBar, HasHorzBar: Boolean;
+begin
+  LNode:=TUsedLayoutNode(El.LayoutNode);
+  IsLeft:=false;
+  HasVertBar:=flfVerticalScrollbar in LNode.Flags;
+  HasHorzBar:=flfHorizontalScrollbar in LNode.Flags;
+  if HasVertBar then
+  begin
+    case LNode.ScrollbarWidth of
+    CSSRegistry.kwThin: BarWidth:=El.Viewport.ScrollbarsThinWidth[false];
+    else BarWidth:=El.Viewport.ScrollbarsWidth[false];
+    end;
+    IsLeft:=El.ComputedDirection=CSSRegistry.kwRTL;
+  end;
+  if HasHorzBar then
+    case LNode.ScrollbarWidth of
+    CSSRegistry.kwThin: BarHeight:=El.Viewport.ScrollbarsThinWidth[true];
+    else BarHeight:=El.Viewport.ScrollbarsWidth[true];
+    end;
+  aPaddingBox:=El.GetRenderedPaddingBox;
+
+  if HasVertBar then
+  begin
+    r:=aPaddingBox;
+    if IsLeft then
+      r.Right:=Min(r.Right,r.Left+BarWidth)
+    else
+      r.Left:=Max(r.Left,r.Right-BarWidth);
+    if HasHorzBar then
+      r.Bottom:=Max(r.Top,r.Bottom-BarHeight);
+
+    if not r.IsEmpty then
+    begin
+      aScrollbar:=CreateScrollBar;
+      try
+        aScrollbar.Horizontal:=false;
+        aScrollbar.Size:=LNode.ScrollHeight;
+        aScrollbar.Position:=El.ScrollTop;
+        aScrollbar.Page:=El.ClientHeight;
+        aScrollbar.Box:=r;
+        aScrollbar.Color:=El.GetComputedColor(fcaScrollbarColor,colGray);
+
+        {$IFDEF VerboseFresnelScrolling}
+        writeln('TFresnelRenderer.DrawScrollBars ',El.Name,' ScrollHeight=',FloatToCSSStr(El.ScrollHeight),' ClientHeight',FloatToCSSStr(El.ClientHeight),' ScrollTop=',FloatToCSSStr(El.ScrollTop)+' BarWidth='+FloatToCSSStr(BarWidth));
+        {$ENDIF}
+        DrawScrollBar(El,aScrollbar);
+      finally
+        aScrollbar.Free;
+      end;
+    end;
+  end;
+  if HasHorzBar then
+  begin
+    r:=aPaddingBox;
+    r.Top:=Max(r.Top,r.Bottom-BarHeight);
+    if HasVertBar then
+    begin
+      if IsLeft then
+        r.Left:=r.Left+BarWidth
+      else
+        r.Right:=r.Right-BarWidth;
+    end;
+
+    if not r.IsEmpty then
+    begin
+      aScrollbar:=CreateScrollBar;
+      try
+        aScrollbar.Horizontal:=true;
+        aScrollbar.Size:=LNode.ScrollWidth;
+        aScrollbar.Position:=El.ScrollLeft;
+        aScrollbar.Page:=El.ClientWidth;
+        aScrollbar.Box:=r;
+        aScrollbar.Color:=El.GetComputedColor(fcaScrollbarColor,colGray);
+
+        {$IFDEF VerboseFresnelScrolling}
+        writeln('TFresnelRenderer.DrawScrollBars ',El.Name,' ScrollWidth=',FloatToCSSStr(El.ScrollWidth),' ClientWidth=',FloatToCSSStr(El.ClientWidth),' ScrollLeft=',FloatToCSSStr(El.ScrollLeft)+' BarHeight='+FloatToCSSStr(BarHeight));
+        {$ENDIF}
+        DrawScrollBar(El,aScrollbar);
+      finally
+        aScrollbar.Free;
+      end;
+    end;
+  end;
+
+  if HasVertBar and HasHorzBar then
+  begin
+    r.Bottom:=aPaddingBox.Bottom;
+    r.Top:=Max(aPaddingBox.Top,r.Bottom-BarHeight);
+    if IsLeft then
+    begin
+      r.Left:=aPaddingBox.Left;
+      r.Right:=Min(aPaddingBox.Right,r.Left+BarWidth);
+    end else begin
+      r.Right:=aPaddingBox.Right;
+      r.Left:=Max(aPaddingBox.Left,r.Right-BarWidth);
+    end;
+    if not r.IsEmpty then
+      DrawScrollbarCorner(El,r);
+  end;
+end;
+
 procedure TFresnelRenderer.DrawElement(El: TFresnelElement);
 var
   LNode: TUsedLayoutNode;
@@ -550,7 +751,7 @@ begin
   end;
 
   // draw scrollbars
-
+  DrawScrollBars(El);
 
   // Give element a chance to draw itself (on top of background and border)
   aRenderable.Render(Self as IFresnelRenderer);
@@ -713,5 +914,12 @@ begin
   Result:=FHasRadius;
 end;
 
+{ TFresnelRenderer.TScrollBar }
+
+constructor TFresnelRenderer.TScrollBar.Create(aRenderer: TFresnelRenderer);
+begin
+  FRenderer:=aRenderer;
+end;
+
 end.
 

+ 1 - 0
src/skia/fresnel.skiarenderer.pas

@@ -818,6 +818,7 @@ begin
   for c in TFresnelCSSCorner do
     Radii[CSSToSkRoundRectCorner[c]]:=aRect.Radii[c].GetPointF;
   RR:=aRect.Box.GetRectF;
+  RR.Offset(Origin.X,Origin.Y);
 
   RoundR:=TSkRoundRect.Create;
   RoundR.SetRect(RR,Radii);