Browse Source

Dynamic: improve state management

MarcinZiabek 3 years ago
parent
commit
e1101d870d

+ 29 - 22
QuestPDF.Examples/DynamicExamples.cs

@@ -15,25 +15,29 @@ namespace QuestPDF.Examples
         public int Price { get; set; } = Placeholders.Random.Next(1, 11) * 10;
         public int Count { get; set; } = Placeholders.Random.Next(1, 11);
     }
+
+    public struct OrdersTableState
+    {
+        public int ShownItemsCount { get; set; }
+    }
     
-    public class OrdersTable : IDynamicComponent
+    public class OrdersTable : IDynamicComponent<OrdersTableState>
     {
         private ICollection<OrderItem> Items { get; }
-        private ICollection<OrderItem> ItemsLeft { get; set; }
-        
+        public OrdersTableState State { get; set; }
+
         public OrdersTable(ICollection<OrderItem> items)
         {
             Items = items;
+
+            State = new OrdersTableState
+            {
+                ShownItemsCount = 0
+            };
         }
         
         public void Compose(DynamicContext context, IDynamicContainer container)
         {
-            if (context.Operation == DynamicLayoutOperation.Reset)
-            {
-                ItemsLeft = new List<OrderItem>(Items);
-                return;
-            }
-
             var header = ComposeHeader(context);
             var sampleFooter = ComposeFooter(context, Enumerable.Empty<OrderItem>());
             var decorationHeight = header.Size.Height + sampleFooter.Size.Height;
@@ -41,12 +45,9 @@ namespace QuestPDF.Examples
             var rows = GetItemsForPage(context, decorationHeight).ToList();
             var footer = ComposeFooter(context, rows.Select(x => x.Item));
 
-            if (ItemsLeft.Count > rows.Count)
+            if (State.ShownItemsCount + rows.Count < Items.Count)
                 container.HasMoreContent();
             
-            if (context.Operation == DynamicLayoutOperation.Draw)
-                ItemsLeft = ItemsLeft.Skip(rows.Count).ToList();
-
             container.MinimalBox().Decoration(decoration =>
             {
                 decoration.Header().Element(header);
@@ -59,6 +60,11 @@ namespace QuestPDF.Examples
 
                 decoration.Footer().Element(footer);
             });
+
+            State = new OrdersTableState
+            {
+                ShownItemsCount = State.ShownItemsCount + rows.Count
+            };
         }
 
         private IDynamicElement ComposeHeader(DynamicContext context)
@@ -98,10 +104,11 @@ namespace QuestPDF.Examples
         private IEnumerable<(OrderItem Item, IDynamicElement Element)> GetItemsForPage(DynamicContext context, float decorationHeight)
         {
             var totalHeight = decorationHeight;
-            var counter = Items.Count - ItemsLeft.Count + 1;
-            
-            foreach (var orderItem in ItemsLeft)
+
+            foreach (var index in Enumerable.Range(State.ShownItemsCount, Items.Count - State.ShownItemsCount))
             {
+                var item = Items.ElementAt(index);
+                
                 var element = context.CreateElement(content =>
                 {
                     content
@@ -110,11 +117,11 @@ namespace QuestPDF.Examples
                         .Padding(5)
                         .Row(row =>
                         {
-                            row.ConstantItem(30).Text(counter++);
-                            row.RelativeItem().Text(orderItem.ItemName);
-                            row.ConstantItem(50).AlignRight().Text(orderItem.Count);
-                            row.ConstantItem(50).AlignRight().Text($"{orderItem.Price}$");
-                            row.ConstantItem(50).AlignRight().Text($"{orderItem.Count*orderItem.Price}$");
+                            row.ConstantItem(30).Text(index + 1);
+                            row.RelativeItem().Text(item.ItemName);
+                            row.ConstantItem(50).AlignRight().Text(item.Count);
+                            row.ConstantItem(50).AlignRight().Text($"{item.Price}$");
+                            row.ConstantItem(50).AlignRight().Text($"{item.Count*item.Price}$");
                         });
                 });
 
@@ -124,7 +131,7 @@ namespace QuestPDF.Examples
                     break;
                     
                 totalHeight += elementHeight;
-                yield return (orderItem, element);
+                yield return (item, element);
             }
         }
     }

+ 19 - 19
QuestPDF/Elements/Dynamic.cs

@@ -8,22 +8,26 @@ namespace QuestPDF.Elements
 {
     internal class DynamicHost : Element, IStateResettable
     {
-        private IDynamicComponent Child { get; }
+        private DynamicComponentProxy Child { get; }
+        private object InitialComponentState { get; set; }
+
         internal TextStyle TextStyle { get; } = new();
 
-        public DynamicHost(IDynamicComponent child)
+        public DynamicHost(DynamicComponentProxy child)
         {
             Child = child;
+            
+            InitialComponentState = Child.GetState();
         }
 
         public void ResetState()
         {
-            GetContent(Size.Zero, DynamicLayoutOperation.Reset);
+            Child.SetState(InitialComponentState);
         }
         
         internal override SpacePlan Measure(Size availableSpace)
         {
-            var content = GetContent(availableSpace, DynamicLayoutOperation.Measure);
+            var content = GetContent(availableSpace, acceptNewState: false);
             var measurement = content.Measure(availableSpace);
 
             if (measurement.Type != SpacePlanType.FullRender)
@@ -36,11 +40,13 @@ namespace QuestPDF.Elements
 
         internal override void Draw(Size availableSpace)
         {
-            GetContent(availableSpace, DynamicLayoutOperation.Draw).Draw(availableSpace);
+            GetContent(availableSpace, acceptNewState: true).Draw(availableSpace);
         }
 
-        DynamicContainer GetContent(Size availableSize, DynamicLayoutOperation operation)
+        private DynamicContainer GetContent(Size availableSize, bool acceptNewState)
         {
+            var componentState = Child.GetState();
+            
             var context = new DynamicContext
             {
                 PageNumber = PageContext.CurrentPage,
@@ -48,13 +54,15 @@ namespace QuestPDF.Elements
                 Canvas = Canvas,
                 TextStyle = TextStyle,
                 
-                AvailableSize = availableSize,
-                Operation = operation
+                AvailableSize = availableSize
             };
             
             var container = new DynamicContainer();
             Child.Compose(context, container);
-            
+
+            if (!acceptNewState)
+                Child.SetState(componentState);
+
             container.VisitChildren(x => x?.Initialize(PageContext, Canvas));
             container.VisitChildren(x => (x as IStateResettable)?.ResetState());
             
@@ -62,23 +70,15 @@ namespace QuestPDF.Elements
         }
     }
 
-    public enum DynamicLayoutOperation
-    {
-        Reset,
-        Measure,
-        Draw
-    }
-    
     public class DynamicContext
     {
         internal IPageContext PageContext { get; set; }
         internal ICanvas Canvas { get; set; }
         internal TextStyle TextStyle { get; set; }
-        
+    
         public int PageNumber { get; internal set; }
         public Size AvailableSize { get; internal set; }
-        public DynamicLayoutOperation Operation { get; internal set; }
-        
+
         public IDynamicElement CreateElement(Action<IContainer> content)
         {
             var container = new DynamicElement();

+ 3 - 7
QuestPDF/Fluent/DynamicComponentExtensions.cs

@@ -5,14 +5,10 @@ namespace QuestPDF.Fluent
 {
     public static class DynamicComponentExtensions
     {
-        public static void Dynamic<TDynamic>(this IContainer element) where TDynamic : IDynamicComponent, new()
+        public static void Dynamic<TState>(this IContainer element, IDynamicComponent<TState> dynamicElement) where TState : struct
         {
-            element.Dynamic(new TDynamic());
-        }
-
-        public static void Dynamic(this IContainer element, IDynamicComponent dynamicElement)
-        {
-            element.Element(new DynamicHost(dynamicElement));
+            var componentProxy = DynamicComponentProxy.CreateFrom(dynamicElement);
+            element.Element(new DynamicHost(componentProxy));
         }
     }
 }

+ 23 - 3
QuestPDF/Infrastructure/IDynamicComponent.cs

@@ -1,9 +1,29 @@
-using QuestPDF.Elements;
+using System;
+using QuestPDF.Elements;
+using QuestPDF.Helpers;
 
 namespace QuestPDF.Infrastructure
 {
-    public interface IDynamicComponent
+    internal class DynamicComponentProxy
     {
-        void Compose(DynamicContext context, IDynamicContainer container);
+        internal Action<object> SetState { get; private set; }
+        internal Func<object> GetState { get; private set; }
+        internal Action<DynamicContext, IDynamicContainer> Compose { get; private set; }
+        
+        internal static DynamicComponentProxy CreateFrom<TState>(IDynamicComponent<TState> component) where TState : struct
+        {
+            return new DynamicComponentProxy
+            {
+                GetState = () => component.State,
+                SetState = x => component.State = (TState)x,
+                Compose = component.Compose
+            };
+        }
+    }
+    
+    public interface IDynamicComponent<TState> where TState : struct
+    {
+        TState State { get; set; }
+        void Compose(DynamicContext context, IDynamicContainer dynamicContainer);
     }
 }