From 92ed3fca9a9c20c5e3a2671ffbda97ba316bb636 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Fri, 16 Jul 2021 12:11:54 +0900 Subject: [PATCH 01/76] Fix GraphicsView scaling issue --- .../GraphicsView/ActivityIndicatorDrawable.cs | 2 +- .../GraphicsView/ButtonDrawable.cs | 8 ++------ .../GraphicsView/CheckBoxDrawable.cs | 5 ++--- .../GraphicsView/EditorDrawable.cs | 2 +- .../GraphicsView/EntryDrawable.cs | 4 ++-- .../GraphicsView/StepperDrawable.cs | 4 ++-- .../GraphicsView/SkiaGraphicsView.cs | 5 +++-- 7 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/ActivityIndicatorDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/ActivityIndicatorDrawable.cs index 2c8464d..16a87ee 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/ActivityIndicatorDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/ActivityIndicatorDrawable.cs @@ -97,7 +97,7 @@ void IAnimatable.BatchCommit() { } public override TSize Measure(double availableWidth, double availableHeight) { - return new TSize(45 * DeviceInfo.ScalingFactor, 45 * DeviceInfo.ScalingFactor); + return new TSize(46 * DeviceInfo.ScalingFactor, 46 * DeviceInfo.ScalingFactor); } protected override void Dispose(bool disposing) diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/ButtonDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/ButtonDrawable.cs index a361e02..a55ad29 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/ButtonDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/ButtonDrawable.cs @@ -70,7 +70,7 @@ void DrawMaterialButtonBackground(ICanvas canvas, RectangleF dirtyRect) var height = MaterialBackgroundHeight - MaterialShadowOffset; canvas.SetShadow(new SizeF(0, 1), 3, GColor.FromArgb(Material.Color.Gray2)); - canvas.FillRoundedRectangle(x, y, width, height, (float)(View.CornerRadius * DeviceInfo.ScalingFactor)); + canvas.FillRoundedRectangle(x, y, width, height, (float)View.CornerRadius); canvas.RestoreState(); @@ -83,17 +83,13 @@ void DrawMaterialButtonText(ICanvas canvas, RectangleF dirtyRect) canvas.FontName = "Roboto"; canvas.FontColor = View.TextColor.ToGraphicsColor(Material.Color.White); - var fontSize = canvas.FontSize = (float)(Material.Font.Button * DeviceInfo.ScalingFactor); var x = dirtyRect.X; var y = dirtyRect.Y; var width = dirtyRect.Width - MaterialShadowOffset; - var height = dirtyRect.Height - MaterialShadowOffset; - //canvas.DrawString(View.Text.ToUpper(), x, y, width, MaterialBackgroundHeight, HorizontalAlignment.Center, VerticalAlignment.Center); - - canvas.DrawString(View.Text, x, height / 2.0f - fontSize / 3.0f, width, height, HorizontalAlignment.Center, VerticalAlignment.Top); + canvas.DrawString(View.Text, x, y, width, MaterialBackgroundHeight, HorizontalAlignment.Center, VerticalAlignment.Center); canvas.RestoreState(); } diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/CheckBoxDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/CheckBoxDrawable.cs index 5f024d5..f60975c 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/CheckBoxDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/CheckBoxDrawable.cs @@ -90,7 +90,7 @@ void DrawMaterialCheckBoxMark(ICanvas canvas, RectangleF dirtyRect) y += (dirtyRect.Height - size) / 2; } - canvas.Translate((float)(x * DeviceInfo.ScalingFactor), (float)(y * DeviceInfo.ScalingFactor)); + canvas.Translate(x, y); var vBuilder = new PathBuilder(); var path = vBuilder.BuildPath(MaterialCheckBoxMark); @@ -107,8 +107,7 @@ void DrawMaterialCheckBoxText(ICanvas canvas, RectangleF dirtyRect) canvas.SaveState(); canvas.FontColor = View.TextColor.ToGraphicsColor(Cupertino.Color.Label.Light.Primary); - var fontSize = (float)(14 * DeviceInfo.ScalingFactor); - canvas.FontSize = fontSize; + canvas.FontSize = 14; float size = 20f; float margin = 8f; diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/EditorDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/EditorDrawable.cs index 78696d3..c4ce75a 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/EditorDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/EditorDrawable.cs @@ -119,7 +119,7 @@ void DrawMaterialEditorPlaceholder(ICanvas canvas, RectangleF dirtyRect) canvas.SaveState(); canvas.FontColor = View.PlaceholderColor.ToGraphicsColor(Material.Color.Dark); - canvas.FontSize = (float)(PlaceholderFontSize * DeviceInfo.ScalingFactor); + canvas.FontSize = PlaceholderFontSize; float margin = 12f; diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/EntryDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/EntryDrawable.cs index 7b982b7..15d4c94 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/EntryDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/EntryDrawable.cs @@ -129,7 +129,7 @@ void DrawMaterialEntryPlaceholder(ICanvas canvas, RectangleF dirtyRect) canvas.SaveState(); canvas.FontColor = View.PlaceholderColor.ToGraphicsColor(Material.Color.Dark); - canvas.FontSize = (float)(PlaceholderFontSize * DeviceInfo.ScalingFactor); + canvas.FontSize = PlaceholderFontSize; float margin = 12f; @@ -191,7 +191,7 @@ void DrawMaterialEntryIndicators(ICanvas canvas, RectangleF dirtyRect) // tX = iconMarginX; //} - canvas.Translate((float)(tX * DeviceInfo.ScalingFactor), (float)(tY * DeviceInfo.ScalingFactor)); + canvas.Translate(tX, tY); var vBuilder = new PathBuilder(); var path = vBuilder.BuildPath(MaterialEntryIndicatorIcon); diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/StepperDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/StepperDrawable.cs index de05a3c..a828632 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/StepperDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/StepperDrawable.cs @@ -90,7 +90,7 @@ void DrawMaterialStepperMinus(ICanvas canvas, RectangleF dirtyRect) canvas.DrawRoundedRectangle(x, y, width, height, 6); - canvas.Translate((float)(20 * DeviceInfo.ScalingFactor), (float)(20 * DeviceInfo.ScalingFactor)); + canvas.Translate(20, 20); var vBuilder = new PathBuilder(); var path = vBuilder.BuildPath(MaterialStepperMinusIcon); @@ -118,7 +118,7 @@ void DrawMaterialStepperPlus(ICanvas canvas, RectangleF dirtyRect) canvas.DrawRoundedRectangle(x, y, width, height, 6); - canvas.Translate((float)(80 * DeviceInfo.ScalingFactor), (float)(14 * DeviceInfo.ScalingFactor)); + canvas.Translate(80, 14); var vBuilder = new PathBuilder(); var path = vBuilder.BuildPath(MaterialStepperPlusIcon); diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/SkiaGraphicsView.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/SkiaGraphicsView.cs index 0f34c75..68566f4 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/SkiaGraphicsView.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/SkiaGraphicsView.cs @@ -15,8 +15,6 @@ public SkiaGraphicsView(IDrawable? drawable = null) { _canvas = new SkiaCanvas(); _scalingCanvas = new ScalingCanvas(_canvas); - - _scalingCanvas.Scale((float)DeviceInfo.ScalingFactor, (float)DeviceInfo.ScalingFactor); Drawable = drawable; PaintSurface += OnPaintSurface; } @@ -42,7 +40,10 @@ protected virtual void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs e) var height = (float)(e.Info.Height / DeviceInfo.ScalingFactor); _canvas.Canvas = skiaCanvas; + _scalingCanvas.SaveState(); + _scalingCanvas.Scale((float)DeviceInfo.ScalingFactor, (float)DeviceInfo.ScalingFactor); _drawable.Draw(_scalingCanvas, new RectangleF(0, 0, width, height)); + _scalingCanvas.RestoreState(); } } } From da4ec4bd28472cab6420d115081c9e75d2990f0e Mon Sep 17 00:00:00 2001 From: Sunghyun Min Date: Thu, 22 Jul 2021 14:28:49 +0900 Subject: [PATCH 02/76] [ElmSharp] fix color type for Shell --- src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs b/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs index 97a6928..f7a20df 100644 --- a/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs +++ b/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs @@ -588,9 +588,9 @@ public class TV public class ColorClass { - public static readonly Color DefaultBackgroundColor = Color.FromRgb(33, 150, 243); - public static readonly Color DefaultForegroundColor = Color.White; - public static readonly Color DefaultTitleColor = Color.White; + public static readonly EColor DefaultBackgroundColor = EColor.FromRgb(33, 150, 243); + public static readonly EColor DefaultForegroundColor = EColor.White; + public static readonly EColor DefaultTitleColor = EColor.White; public static readonly EColor DefaultNavigationViewBackgroundColor = EColor.White; public static readonly EColor DefaultDrawerDimBackgroundColor = new EColor(0, 0, 0, 82); From cf420a6498e47d497f6a250a59adbfabd372092d Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Thu, 22 Jul 2021 13:11:51 +0900 Subject: [PATCH 03/76] Add NavigationStack --- src/Tizen.UIExtensions.NUI/NavigationStack.cs | 205 ++++++++++++++++++ test/NUIExGallery/TC/NavigationTest.cs | 186 ++++++++++++++++ 2 files changed, 391 insertions(+) create mode 100644 src/Tizen.UIExtensions.NUI/NavigationStack.cs create mode 100644 test/NUIExGallery/TC/NavigationTest.cs diff --git a/src/Tizen.UIExtensions.NUI/NavigationStack.cs b/src/Tizen.UIExtensions.NUI/NavigationStack.cs new file mode 100644 index 0000000..e97a4ec --- /dev/null +++ b/src/Tizen.UIExtensions.NUI/NavigationStack.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Tizen.NUI; +using Tizen.NUI.BaseComponents; +using Tizen.UIExtensions.Common.Internal; +using Animation = Tizen.UIExtensions.Common.Internal.Animation; + +namespace Tizen.UIExtensions.NUI +{ + + /// + /// A View that provide view stacking with push/pop animation + /// + public class NavigationStack : View, IAnimatable + { + View? _lastTop; + + /// + /// /// Initializes a new instance of the class. + /// + public NavigationStack() + { + Layout = new AbsoluteLayout(); + WidthSpecification = LayoutParamPolicies.MatchParent; + HeightSpecification = LayoutParamPolicies.MatchParent; + + InternalStack = new List(); + } + + /// + /// A stack of views + /// + public IReadOnlyList Stack => InternalStack; + + /// + /// A view at the top + /// + public View? Top => _lastTop; + + List InternalStack { get; set; } + + View? BelowTop + { + get + { + if (InternalStack.Count < 2) + { + return null; + } + return InternalStack[InternalStack.Count - 2]; + } + } + + /// + /// User customized push animation function + /// + /// + /// View is a target that is pushed and `double` is progess of push + /// + public Action? PushAnimation { get; set; } + + /// + /// User customized pop animation function + /// + /// + /// View is a target that is popped and `double` is progess of push + /// + public Action? PopAnimation { get; set; } + + /// + /// Push a view on stack + /// + /// A view to push + /// Flags for animation + public async Task Push(View view, bool animated) + { + view.WidthResizePolicy = ResizePolicyType.FillToParent; + view.HeightResizePolicy = ResizePolicyType.FillToParent; + + InternalStack.Add(view); + Add(view); + + if (animated && PushAnimation != null) + { + if (Top != null) + { + Top.Sensitive = false; + } + + view.Sensitive = false; + var tcs = new TaskCompletionSource(); + var pushAni = new Animation((d) => PushAnimation(view, d), easing: Easing.SinOut); + pushAni.Commit(this, "PushAnimation", finished: (d, b) => + { + tcs.SetResult(true); + }); + await tcs.Task; + view.Sensitive = true; + + if (Top != null) + { + Top.Sensitive = true; + } + } + UpdateTopView(); + } + + /// + /// Remove top view on stack + /// + /// Flags for animation + public async Task Pop(bool animated) + { + if (_lastTop != null) + { + var tobeRemoved = _lastTop; + + if (animated && PopAnimation != null) + { + tobeRemoved.Sensitive = false; + if (BelowTop != null) + { + BelowTop.Show(); + BelowTop.Sensitive = false; + } + + var tcs = new TaskCompletionSource(); + var pushAni = new Animation((d) => PopAnimation(tobeRemoved, d), easing: Easing.SinOut); + pushAni.Commit(this, "PopAnimation", finished: (d, b) => + { + tcs.SetResult(true); + }); + await tcs.Task; + tobeRemoved.Sensitive = true; + if (BelowTop != null) + { + BelowTop.Sensitive = true; + } + } + + InternalStack.Remove(tobeRemoved); + Remove(tobeRemoved); + UpdateTopView(); + // if Pop was called by removed page, + // Unrealize cause deletation of NativeCallback, it could be a cause of crash + tobeRemoved.Dispose(); + } + } + + /// + /// Pops all but the root view off the navigation stack. + /// + public void PopToRoot() + { + while (InternalStack.Count > 1) + { + _ = Pop(false); + } + } + + /// + /// Clear all children + /// + public void Clear() + { + foreach (var child in InternalStack) + { + Remove(child); + child.Dispose(); + } + InternalStack.Clear(); + _lastTop = null; + } + + /// + /// Inserts a view in the navigation stack before an existing view in the stack. + /// + /// The existing view, before which view will be inserted. + /// The view to insert + public void Insert(View before, View view) + { + view.Hide(); + var idx = InternalStack.IndexOf(before); + InternalStack.Insert(idx, view); + Add(view); + UpdateTopView(); + } + + void UpdateTopView() + { + if (_lastTop != InternalStack.LastOrDefault()) + { + _lastTop?.Hide(); + _lastTop = InternalStack.LastOrDefault(); + _lastTop?.Show(); + } + } + + void IAnimatable.BatchBegin() {} + + void IAnimatable.BatchCommit() {} + } +} diff --git a/test/NUIExGallery/TC/NavigationTest.cs b/test/NUIExGallery/TC/NavigationTest.cs new file mode 100644 index 0000000..49d5a38 --- /dev/null +++ b/test/NUIExGallery/TC/NavigationTest.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Tizen.NUI.BaseComponents; +using Tizen.NUI; +using Tizen.UIExtensions.NUI; +using Tizen.UIExtensions.Common; +using TColor = Tizen.UIExtensions.Common.Color; +using Tizen.Applications; + +namespace NUIExGallery.TC +{ + public class NavigationTest : TestCaseBase + { + public override string TestName => "Navigation Animation Test"; + + public override string TestDescription => "Navigation Animation test"; + + public override View Run() + { + var navi = new NavigationStack + { + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.MatchParent, + }; + + navi.PushAnimation = (view, progress) => + { + view.Opacity = 0.5f + 0.5f * (float)progress; + }; + + navi.PopAnimation = (view, progress) => + { + view.Opacity = 0.5f + 0.5f * (float)(1 - progress); + }; + + _ = navi.Push(new PushPopPage(navi, 1), true); + + return navi; + } + + class PushPopPage : View + { + NavigationStack _stack; + public PushPopPage(NavigationStack stack, int depth) + { + _stack = stack; + WidthSpecification = LayoutParamPolicies.MatchParent; + HeightSpecification = LayoutParamPolicies.MatchParent; + var rnd = new Random(); + BackgroundColor = TColor.FromRgba(rnd.Next(100, 200), rnd.Next(100, 200), rnd.Next(100, 200), 255).ToNative(); + + + Layout = new LinearLayout + { + LinearOrientation = LinearLayout.Orientation.Vertical + }; + + Add(new Label + { + Text = $"Text {depth}" + }); + + var push = new Button + { + Text = "Push (fade)" + }; + Add(push); + push.Clicked += (s, e) => + { + stack.PushAnimation = (view, progress) => + { + view.Opacity = 0.5f + 0.5f * (float)progress; + }; + _ = _stack.Push(new PushPopPage(_stack, depth+1), true); + }; + + var push2 = new Button + { + Text = "Push (fade + Left)" + }; + Add(push2); + push2.Clicked += (s, e) => + { + + float? originalX = null; + stack.PushAnimation = (view, progress) => + { + if (originalX == null) + { + originalX = view.PositionX; + } + view.PositionX = originalX.Value + (float)(view.SizeWidth / 4.0f * (1 - progress)); + view.Opacity = 0.5f + 0.5f * (float)progress; + }; + + _ = _stack.Push(new PushPopPage(_stack, depth + 1), true); + }; + + var push3 = new Button + { + Text = "Push (fade + Up)" + }; + Add(push3); + push3.Clicked += (s, e) => + { + + float? originY = null; + stack.PushAnimation = (view, progress) => + { + if (originY == null) + { + originY = view.PositionY; + } + view.PositionY = originY.Value + (float)(view.SizeHeight / 4.0f * (1 - progress)); + view.Opacity = 0.5f + 0.5f * (float)progress; + }; + + _ = _stack.Push(new PushPopPage(_stack, depth + 1), true); + }; + + + var pop = new Button + { + Text = "Pop (fade)" + }; + Add(pop); + pop.Clicked += (s, e) => + { + stack.PopAnimation = (view, progress) => + { + view.Opacity = 0.5f + 0.5f * (float)(1 - progress); + }; + _ = _stack.Pop(true); + }; + + var pop2 = new Button + { + Text = "Pop (fade + Right)" + }; + Add(pop2); + pop2.Clicked += (s, e) => + { + float? originX = null; + stack.PopAnimation = (view, progress) => + { + if (originX == null) + { + originX = view.PositionX; + } + view.PositionX = originX.Value + (float)(view.SizeWidth / 4.0f * progress); + view.Opacity = 0.5f + 0.5f * (float)(1 - progress); + }; + _ = _stack.Pop(true); + }; + + var pop3 = new Button + { + Text = "Pop (fade + down)" + }; + Add(pop3); + pop3.Clicked += (s, e) => + { + float? originY = null; + stack.PopAnimation = (view, progress) => + { + if (originY == null) + { + originY = view.PositionY; + } + view.PositionY = originY.Value + (float)(view.SizeHeight / 4.0f * progress); + view.Opacity = 0.5f + 0.5f * (float)(1 - progress); + }; + _ = _stack.Pop(true); + }; + + + + Add(new Image + { + ResourceUrl = Application.Current.DirectoryInfo.Resource + (depth % 2 == 0 ? "image.png" : "image2.jpg") + }); + } + } + } +} From d60fc50132bc897f275da03ca4450caf93bf5e1e Mon Sep 17 00:00:00 2001 From: Sunghyun Min Date: Thu, 22 Jul 2021 19:41:02 +0900 Subject: [PATCH 04/76] [Elmsharp] Add Tabs --- src/Tizen.UIExtensions.ElmSharp/ITabs.cs | 29 +++++++++++++++++++++ src/Tizen.UIExtensions.ElmSharp/Tabs.cs | 33 ++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 src/Tizen.UIExtensions.ElmSharp/ITabs.cs create mode 100644 src/Tizen.UIExtensions.ElmSharp/Tabs.cs diff --git a/src/Tizen.UIExtensions.ElmSharp/ITabs.cs b/src/Tizen.UIExtensions.ElmSharp/ITabs.cs new file mode 100644 index 0000000..025b0c4 --- /dev/null +++ b/src/Tizen.UIExtensions.ElmSharp/ITabs.cs @@ -0,0 +1,29 @@ +using System; +using ElmSharp; +using EColor = ElmSharp.Color; + +namespace Tizen.UIExtensions.ElmSharp +{ + public interface ITabs + { + TabsScrollType ScrollType { get; set; } + + EColor BackgroundColor { get; set; } + + ToolbarItem SelectedItem { get; } + + event EventHandler Selected; + + ToolbarItem Append(string label, string icon); + + ToolbarItem Append(string label); + + ToolbarItem InsertBefore(ToolbarItem before, string label, string icon); + } + + public enum TabsScrollType + { + Fixed, + Scrollable + } +} diff --git a/src/Tizen.UIExtensions.ElmSharp/Tabs.cs b/src/Tizen.UIExtensions.ElmSharp/Tabs.cs new file mode 100644 index 0000000..164f77f --- /dev/null +++ b/src/Tizen.UIExtensions.ElmSharp/Tabs.cs @@ -0,0 +1,33 @@ +using ElmSharp; + +namespace Tizen.UIExtensions.ElmSharp +{ + public class Tabs : Toolbar, ITabs + { + TabsScrollType _type; + + public Tabs(EvasObject parent) :base(parent) + { + Style = ThemeConstants.Toolbar.Styles.Material; + SelectionMode = ToolbarSelectionMode.Always; + } + + public TabsScrollType ScrollType + { + get => _type; + set + { + switch (value) + { + case TabsScrollType.Fixed: + this.ShrinkMode = ToolbarShrinkMode.Expand; + break; + case TabsScrollType.Scrollable: + this.ShrinkMode = ToolbarShrinkMode.Scroll; + break; + } + _type = value; + } + } + } +} From 50747f9e17ac7019534f3cba204fb33fc07e3465 Mon Sep 17 00:00:00 2001 From: Sunghyun Min Date: Thu, 22 Jul 2021 19:49:27 +0900 Subject: [PATCH 05/76] [Elmsharp] Fix tabstype name --- src/Tizen.UIExtensions.ElmSharp/ITabs.cs | 4 ++-- src/Tizen.UIExtensions.ElmSharp/Tabs.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Tizen.UIExtensions.ElmSharp/ITabs.cs b/src/Tizen.UIExtensions.ElmSharp/ITabs.cs index 025b0c4..2b12d45 100644 --- a/src/Tizen.UIExtensions.ElmSharp/ITabs.cs +++ b/src/Tizen.UIExtensions.ElmSharp/ITabs.cs @@ -6,7 +6,7 @@ namespace Tizen.UIExtensions.ElmSharp { public interface ITabs { - TabsScrollType ScrollType { get; set; } + TabsType Scrollable { get; set; } EColor BackgroundColor { get; set; } @@ -21,7 +21,7 @@ public interface ITabs ToolbarItem InsertBefore(ToolbarItem before, string label, string icon); } - public enum TabsScrollType + public enum TabsType { Fixed, Scrollable diff --git a/src/Tizen.UIExtensions.ElmSharp/Tabs.cs b/src/Tizen.UIExtensions.ElmSharp/Tabs.cs index 164f77f..495f7c5 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Tabs.cs +++ b/src/Tizen.UIExtensions.ElmSharp/Tabs.cs @@ -4,7 +4,7 @@ namespace Tizen.UIExtensions.ElmSharp { public class Tabs : Toolbar, ITabs { - TabsScrollType _type; + TabsType _type; public Tabs(EvasObject parent) :base(parent) { @@ -12,17 +12,17 @@ public Tabs(EvasObject parent) :base(parent) SelectionMode = ToolbarSelectionMode.Always; } - public TabsScrollType ScrollType + public TabsType Scrollable { get => _type; set { switch (value) { - case TabsScrollType.Fixed: + case TabsType.Fixed: this.ShrinkMode = ToolbarShrinkMode.Expand; break; - case TabsScrollType.Scrollable: + case TabsType.Scrollable: this.ShrinkMode = ToolbarShrinkMode.Scroll; break; } From 91b32095fd1ceda4641d47c0bd99045d4b041fa0 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Mon, 2 Aug 2021 14:42:03 +0900 Subject: [PATCH 06/76] Fix typo error --- src/Tizen.UIExtensions.NUI/NavigationStack.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tizen.UIExtensions.NUI/NavigationStack.cs b/src/Tizen.UIExtensions.NUI/NavigationStack.cs index e97a4ec..f5e70bc 100644 --- a/src/Tizen.UIExtensions.NUI/NavigationStack.cs +++ b/src/Tizen.UIExtensions.NUI/NavigationStack.cs @@ -57,7 +57,7 @@ public NavigationStack() /// User customized push animation function /// /// - /// View is a target that is pushed and `double` is progess of push + /// View is a target that is pushed and `double` is progress of push /// public Action? PushAnimation { get; set; } @@ -65,7 +65,7 @@ public NavigationStack() /// User customized pop animation function /// /// - /// View is a target that is popped and `double` is progess of push + /// View is a target that is popped and `double` is progress of pop /// public Action? PopAnimation { get; set; } From 655c19f7a5dbf04e7c2cc73df087cb9079ce49fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=EA=B7=BC/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/Staff=20Engineer/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Tue, 3 Aug 2021 09:01:18 +0900 Subject: [PATCH 07/76] Update Stream Image source (#81) --- .../Extensions/ImageExtensions.cs | 4 +- src/Tizen.UIExtensions.NUI/Image.cs | 26 +++++++- .../StreamImageSourceService.cs | 65 ------------------- 3 files changed, 27 insertions(+), 68 deletions(-) delete mode 100644 src/Tizen.UIExtensions.NUI/StreamImageSourceService.cs diff --git a/src/Tizen.UIExtensions.NUI/Extensions/ImageExtensions.cs b/src/Tizen.UIExtensions.NUI/Extensions/ImageExtensions.cs index 24b719e..093c6d6 100644 --- a/src/Tizen.UIExtensions.NUI/Extensions/ImageExtensions.cs +++ b/src/Tizen.UIExtensions.NUI/Extensions/ImageExtensions.cs @@ -132,7 +132,7 @@ void completed(object? sender, EventArgs args) } } - public static async Task LoadAsync(this ImageView view, Stream stream) + public static async Task LoadAsync(this Image view, Stream stream) { if (stream == null) throw new ArgumentNullException(nameof(stream)); @@ -146,7 +146,7 @@ void completed(object? sender, EventArgs args) try { - view.ResourceUrl = await StreamImageSourceService.Instance.AddStreamAsync(view, stream); + view.Load(stream); return await tcs.Task; } finally diff --git a/src/Tizen.UIExtensions.NUI/Image.cs b/src/Tizen.UIExtensions.NUI/Image.cs index 373743e..1cb69e1 100644 --- a/src/Tizen.UIExtensions.NUI/Image.cs +++ b/src/Tizen.UIExtensions.NUI/Image.cs @@ -1,4 +1,6 @@ -using Tizen.UIExtensions.Common; +using System.IO; +using Tizen.NUI; +using Tizen.UIExtensions.Common; using CSize = Tizen.UIExtensions.Common.Size; using ImageView = Tizen.NUI.BaseComponents.ImageView; @@ -9,6 +11,7 @@ namespace Tizen.UIExtensions.NUI /// public class Image : ImageView, IMeasurable { + ImageUrl? _cachedImageUrl; /// /// Gets or sets the scaling mode for the image. /// @@ -18,6 +21,18 @@ public Aspect Aspect set => this.SetAspect(value); } + /// + /// Load Image from stream + /// + /// The stream containing images + public void Load(Stream stream) + { + using var imageBuffer = new EncodedImageBuffer(stream); + _cachedImageUrl?.Dispose(); + _cachedImageUrl = imageBuffer.GenerateUrl(); + ResourceUrl = _cachedImageUrl.ToString(); + } + /// /// Measures the size of the control in order to fit it into the available area. /// @@ -28,5 +43,14 @@ CSize IMeasurable.Measure(double availableWidth, double availableHeight) { return this.Measure(availableWidth, availableHeight); } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _cachedImageUrl?.Dispose(); + } + base.Dispose(disposing); + } } } diff --git a/src/Tizen.UIExtensions.NUI/StreamImageSourceService.cs b/src/Tizen.UIExtensions.NUI/StreamImageSourceService.cs deleted file mode 100644 index bf98986..0000000 --- a/src/Tizen.UIExtensions.NUI/StreamImageSourceService.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Tizen.Applications; -using NImage = Tizen.NUI.BaseComponents.ImageView; - -namespace Tizen.UIExtensions.NUI -{ - internal class StreamImageSourceService - { - Dictionary, string> _imageCache = new Dictionary, string>(); - - public static StreamImageSourceService Instance { get; } = new StreamImageSourceService(); - - StreamImageSourceService() { } - - /// - /// Stream to file - /// - /// A view that owns the stream - /// A stream to convert file - /// a temporary file path - public async Task AddStreamAsync(NImage view, Stream stream) - { - // Remove a cache that duplicated or released object - foreach (var weakRef in _imageCache.Keys.ToList()) - { - if (!weakRef.TryGetTarget(out var target) || target == view) - { - RemoveTemporaryFile(_imageCache[weakRef]); - _imageCache.Remove(weakRef); - } - } - - var tempfile = Path.Combine(Application.Current.DirectoryInfo.Cache, Path.GetRandomFileName()); - using (var fs = new FileStream(tempfile, FileMode.OpenOrCreate)) - { - await stream.CopyToAsync(fs); - } - _imageCache[new WeakReference(view)] = tempfile; - - return tempfile; - } - - void RemoveTemporaryFile(string path) - { - try - { - if (!string.IsNullOrEmpty(path)) - { - if (File.Exists(path)) - { - File.Delete(path); - } - } - } - catch (Exception) - { - Common.Log.Error("Fail to remove temporary file"); - } - } - } -} From 4d55b4d203e3889e9c56e97a6d9f2a8d048bb0e8 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Tue, 3 Aug 2021 09:14:55 +0900 Subject: [PATCH 08/76] Fix to use Top instead _lastTop --- src/Tizen.UIExtensions.NUI/NavigationStack.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Tizen.UIExtensions.NUI/NavigationStack.cs b/src/Tizen.UIExtensions.NUI/NavigationStack.cs index f5e70bc..9c623ba 100644 --- a/src/Tizen.UIExtensions.NUI/NavigationStack.cs +++ b/src/Tizen.UIExtensions.NUI/NavigationStack.cs @@ -113,9 +113,9 @@ public async Task Push(View view, bool animated) /// Flags for animation public async Task Pop(bool animated) { - if (_lastTop != null) + if (Top != null) { - var tobeRemoved = _lastTop; + var tobeRemoved = Top; if (animated && PopAnimation != null) { @@ -143,8 +143,6 @@ public async Task Pop(bool animated) InternalStack.Remove(tobeRemoved); Remove(tobeRemoved); UpdateTopView(); - // if Pop was called by removed page, - // Unrealize cause deletation of NativeCallback, it could be a cause of crash tobeRemoved.Dispose(); } } From c9131f9b759792609dfa668db68187320159210a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=EA=B7=BC/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/Staff=20Engineer/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Tue, 3 Aug 2021 16:57:47 +0900 Subject: [PATCH 09/76] [NUI] Update SKCanvasView/SKGLSurfaceView to use NativeImageQueue (#83) * Update SKCanvasView to use NativeImageQueue * Fix nullable --- .../Skia/SKCanvasView.cs | 43 +++++++++++-------- .../Skia/SKClipperView.cs | 39 +++++++++-------- .../Skia/SKGLSurfaceView.cs | 33 +++++++------- 3 files changed, 63 insertions(+), 52 deletions(-) diff --git a/src/Tizen.UIExtensions.NUI/Skia/SKCanvasView.cs b/src/Tizen.UIExtensions.NUI/Skia/SKCanvasView.cs index 98b37e9..9a78652 100644 --- a/src/Tizen.UIExtensions.NUI/Skia/SKCanvasView.cs +++ b/src/Tizen.UIExtensions.NUI/Skia/SKCanvasView.cs @@ -9,7 +9,7 @@ namespace Tizen.UIExtensions.NUI public class SKCanvasView : CustomRenderingView { - NativeImageSource? _nativeImageSource; + NativeImageQueue? _nativeImageSource; ImageUrl? _imageUrl; int _bufferWidth = 0; @@ -26,30 +26,31 @@ protected override void OnDrawFrame() if (Size.Width == 0 || Size.Height == 0) return; - UpdateSurface(); - - var buffer = _nativeImageSource!.AcquireBuffer(ref _bufferWidth, ref _bufferHeight, ref _bufferStride); - Debug.Assert(buffer != IntPtr.Zero, "AcquireBuffer is faild"); - var info = new SKImageInfo(_bufferWidth, _bufferHeight); - - using (var surface = SKSurface.Create(info, buffer, _bufferStride)) + if (_nativeImageSource?.CanDequeueBuffer() ?? false) { - // draw using SkiaSharp - SendPaintSurface(new SKPaintSurfaceEventArgs(surface, info)); - surface.Canvas.Flush(); + var buffer = _nativeImageSource!.DequeueBuffer(ref _bufferWidth, ref _bufferHeight, ref _bufferStride); + Debug.Assert(buffer != IntPtr.Zero, "AcquireBuffer is faild"); + var info = new SKImageInfo(_bufferWidth, _bufferHeight); + + using (var surface = SKSurface.Create(info, buffer, _bufferStride)) + { + // draw using SkiaSharp + SendPaintSurface(new SKPaintSurfaceEventArgs(surface, info)); + surface.Canvas.Flush(); + } + _nativeImageSource.EnqueueBuffer(buffer); + Window.Instance.KeepRendering(0); } - _nativeImageSource.ReleaseBuffer(); - _imageUrl?.Dispose(); - _imageUrl = _nativeImageSource.GenerateUrl(); - var url = _imageUrl?.ToString(); - SetImage(url); } protected override void OnResized() { if (Size.Width == 0 || Size.Height == 0) return; + + UpdateSurface(); OnDrawFrame(); + UpdateImageUrl(); } protected override void Dispose(bool disposing) @@ -67,8 +68,14 @@ protected override void Dispose(bool disposing) void UpdateSurface() { _nativeImageSource?.Dispose(); - _nativeImageSource = new NativeImageSource((uint)Size.Width, (uint)Size.Height, NativeImageSource.ColorDepth.Default); - _imageUrl = _nativeImageSource.GenerateUrl(); + _nativeImageSource = new NativeImageQueue((uint)Size.Width, (uint)Size.Height, NativeImageSource.ColorDepth.Default); + } + + void UpdateImageUrl() + { + _imageUrl?.Dispose(); + _imageUrl = _nativeImageSource!.GenerateUrl(); + SetImage(_imageUrl.ToString()); } } } diff --git a/src/Tizen.UIExtensions.NUI/Skia/SKClipperView.cs b/src/Tizen.UIExtensions.NUI/Skia/SKClipperView.cs index f03fd1c..036c9a1 100644 --- a/src/Tizen.UIExtensions.NUI/Skia/SKClipperView.cs +++ b/src/Tizen.UIExtensions.NUI/Skia/SKClipperView.cs @@ -43,7 +43,7 @@ public class SKClipperView : NView Geometry _geometry; Shader _shader; - NativeImageSource? _buffer; + NativeImageQueue? _bufferQueue; Texture? _texture; TextureSet? _textureSet; @@ -69,8 +69,8 @@ public SKClipperView() RemoveRenderer(0); - _buffer = new NativeImageSource(1, 1, NativeImageSource.ColorDepth.Default); - _texture = new Texture(_buffer); + _bufferQueue = new NativeImageQueue(1, 1, NativeImageSource.ColorDepth.Default); + _texture = new Texture(_bufferQueue); _textureSet = new TextureSet(); _textureSet.SetTexture(0u, _texture); _renderer = new Renderer(_geometry, _shader); @@ -96,7 +96,7 @@ public void Invalidate() MainloopContext.Post((s)=> { _redrawRequest = false; - if (!Disposed && _buffer != null) + if (!Disposed && _bufferQueue != null) { OnDrawFrame(); } @@ -109,26 +109,26 @@ protected void OnDrawFrame() if (Size.Width == 0 || Size.Height == 0) return; - UpdateSurface(); - - var buffer = _buffer!.AcquireBuffer(ref _bufferWidth, ref _bufferHeight, ref _bufferStride); - var info = new SKImageInfo(_bufferWidth, _bufferHeight); - using (var surface = SKSurface.Create(info, buffer, _bufferStride)) + if (_bufferQueue?.CanDequeueBuffer() ?? false) { - // draw using SkiaSharp - OnDrawFrame(new SKPaintSurfaceEventArgs(surface, info)); - surface.Canvas.Flush(); + var buffer = _bufferQueue!.DequeueBuffer(ref _bufferWidth, ref _bufferHeight, ref _bufferStride); + var info = new SKImageInfo(_bufferWidth, _bufferHeight); + using (var surface = SKSurface.Create(info, buffer, _bufferStride)) + { + // draw using SkiaSharp + OnDrawFrame(new SKPaintSurfaceEventArgs(surface, info)); + surface.Canvas.Flush(); + } + _bufferQueue.EnqueueBuffer(buffer); + Window.Instance.KeepRendering(0); } - _buffer.ReleaseBuffer(); - - UpdateBuffer(); } void UpdateBuffer() { _texture?.Dispose(); _textureSet?.Dispose(); - _texture = new Texture(_buffer); + _texture = new Texture(_bufferQueue); _textureSet = new TextureSet(); _textureSet.SetTexture(0u, _texture); _renderer.SetTextures(_textureSet); @@ -146,13 +146,14 @@ protected virtual void OnResized() UpdateSurface(); OnDrawFrame(); + UpdateBuffer(); } protected override void Dispose(bool disposing) { if (disposing) { - _buffer?.Dispose(); + _bufferQueue?.Dispose(); _texture?.Dispose(); _textureSet?.Dispose(); _renderer?.Dispose(); @@ -162,8 +163,8 @@ protected override void Dispose(bool disposing) void UpdateSurface() { - _buffer?.Dispose(); - _buffer = new NativeImageSource((uint)Size.Width, (uint)Size.Height, NativeImageSource.ColorDepth.Default); + _bufferQueue?.Dispose(); + _bufferQueue = new NativeImageQueue((uint)Size.Width, (uint)Size.Height, NativeImageSource.ColorDepth.Default); } void OnResized(object source, PropertyNotification.NotifyEventArgs e) diff --git a/src/Tizen.UIExtensions.NUI/Skia/SKGLSurfaceView.cs b/src/Tizen.UIExtensions.NUI/Skia/SKGLSurfaceView.cs index 5b014a6..49b8715 100644 --- a/src/Tizen.UIExtensions.NUI/Skia/SKGLSurfaceView.cs +++ b/src/Tizen.UIExtensions.NUI/Skia/SKGLSurfaceView.cs @@ -33,7 +33,7 @@ public class SKGLSurfaceView : CustomRenderingView " gl_FragColor = texture2D(sTexture, vTexCoord) * uColor;\n" + "}\n"; - NativeImageSource? _nativeImageSource; + NativeImageQueue? _nativeImageSource; int _bufferWidth = 0; int _bufferHeight = 0; @@ -57,28 +57,31 @@ protected override void OnDrawFrame() if (Size.Width == 0 || Size.Height == 0) return; - UpdateSurface(); - - var buffer = _nativeImageSource!.AcquireBuffer(ref _bufferWidth, ref _bufferHeight, ref _bufferStride); - Debug.Assert(buffer != IntPtr.Zero, "AcquireBuffer is faild"); - - var info = new SKImageInfo(_bufferWidth, _bufferHeight); - using (var surface = SKSurface.Create(info, buffer, _bufferStride)) + if (_nativeImageSource?.CanDequeueBuffer() ?? false) { - // draw using SkiaSharp - SendPaintSurface(new SKPaintSurfaceEventArgs(surface, info)); - surface.Canvas.Flush(); + var buffer = _nativeImageSource!.DequeueBuffer(ref _bufferWidth, ref _bufferHeight, ref _bufferStride); + Debug.Assert(buffer != IntPtr.Zero, "AcquireBuffer is faild"); + + var info = new SKImageInfo(_bufferWidth, _bufferHeight); + using (var surface = SKSurface.Create(info, buffer, _bufferStride)) + { + // draw using SkiaSharp + SendPaintSurface(new SKPaintSurfaceEventArgs(surface, info)); + surface.Canvas.Flush(); + } + _nativeImageSource.EnqueueBuffer(buffer); + Window.Instance.KeepRendering(0); } - _nativeImageSource.ReleaseBuffer(); - - UpdateTexture(); } protected override void OnResized() { if (Size.Width == 0 || Size.Height == 0) return; + + UpdateSurface(); OnDrawFrame(); + UpdateTexture(); } protected override void Dispose(bool disposing) @@ -94,7 +97,7 @@ protected override void Dispose(bool disposing) void UpdateSurface() { _nativeImageSource?.Dispose(); - _nativeImageSource = new NativeImageSource((uint)Size.Width, (uint)Size.Height, NativeImageSource.ColorDepth.Default); + _nativeImageSource = new NativeImageQueue((uint)Size.Width, (uint)Size.Height, NativeImageSource.ColorDepth.Default); } void UpdateTexture() From 2fbef2848695e70b6dca55fecde5fe13a1eec9bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=97=88=EA=B0=95=ED=98=B8/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/Principal=20Engineer/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Fri, 6 Aug 2021 07:26:42 +0900 Subject: [PATCH 10/76] [ElmSharp] Fix Image.Measure() (#84) --- src/Tizen.UIExtensions.ElmSharp/Image.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tizen.UIExtensions.ElmSharp/Image.cs b/src/Tizen.UIExtensions.ElmSharp/Image.cs index 0f32185..c89a4b9 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Image.cs +++ b/src/Tizen.UIExtensions.ElmSharp/Image.cs @@ -55,7 +55,7 @@ public CSize Measure(double availableWidth, double availableHeight) size.Height = availableRatio > imageRatio ? availableHeight : imageSize.Height * availableWidth / imageSize.Width; } - return new CSize(); + return size; } } } \ No newline at end of file From 4b1c2ab1d1f87b6b0ba6898ea8f68427dbe72321 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Wed, 18 Aug 2021 16:01:20 +0900 Subject: [PATCH 11/76] Fix build error --- src/Tizen.UIExtensions.NUI/Skia/SKCanvasView.cs | 2 +- src/Tizen.UIExtensions.NUI/Skia/SKClipperView.cs | 4 ++-- src/Tizen.UIExtensions.NUI/Skia/SKGLSurfaceView.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Tizen.UIExtensions.NUI/Skia/SKCanvasView.cs b/src/Tizen.UIExtensions.NUI/Skia/SKCanvasView.cs index 9a78652..bbc8f13 100644 --- a/src/Tizen.UIExtensions.NUI/Skia/SKCanvasView.cs +++ b/src/Tizen.UIExtensions.NUI/Skia/SKCanvasView.cs @@ -68,7 +68,7 @@ protected override void Dispose(bool disposing) void UpdateSurface() { _nativeImageSource?.Dispose(); - _nativeImageSource = new NativeImageQueue((uint)Size.Width, (uint)Size.Height, NativeImageSource.ColorDepth.Default); + _nativeImageSource = new NativeImageQueue((uint)Size.Width, (uint)Size.Height, NativeImageQueue.ColorFormat.RGBA8888); } void UpdateImageUrl() diff --git a/src/Tizen.UIExtensions.NUI/Skia/SKClipperView.cs b/src/Tizen.UIExtensions.NUI/Skia/SKClipperView.cs index 036c9a1..b04b1da 100644 --- a/src/Tizen.UIExtensions.NUI/Skia/SKClipperView.cs +++ b/src/Tizen.UIExtensions.NUI/Skia/SKClipperView.cs @@ -69,7 +69,7 @@ public SKClipperView() RemoveRenderer(0); - _bufferQueue = new NativeImageQueue(1, 1, NativeImageSource.ColorDepth.Default); + _bufferQueue = new NativeImageQueue(1, 1, NativeImageQueue.ColorFormat.RGBA8888); _texture = new Texture(_bufferQueue); _textureSet = new TextureSet(); _textureSet.SetTexture(0u, _texture); @@ -164,7 +164,7 @@ protected override void Dispose(bool disposing) void UpdateSurface() { _bufferQueue?.Dispose(); - _bufferQueue = new NativeImageQueue((uint)Size.Width, (uint)Size.Height, NativeImageSource.ColorDepth.Default); + _bufferQueue = new NativeImageQueue((uint)Size.Width, (uint)Size.Height, NativeImageQueue.ColorFormat.RGBA8888); } void OnResized(object source, PropertyNotification.NotifyEventArgs e) diff --git a/src/Tizen.UIExtensions.NUI/Skia/SKGLSurfaceView.cs b/src/Tizen.UIExtensions.NUI/Skia/SKGLSurfaceView.cs index 49b8715..578c88e 100644 --- a/src/Tizen.UIExtensions.NUI/Skia/SKGLSurfaceView.cs +++ b/src/Tizen.UIExtensions.NUI/Skia/SKGLSurfaceView.cs @@ -97,7 +97,7 @@ protected override void Dispose(bool disposing) void UpdateSurface() { _nativeImageSource?.Dispose(); - _nativeImageSource = new NativeImageQueue((uint)Size.Width, (uint)Size.Height, NativeImageSource.ColorDepth.Default); + _nativeImageSource = new NativeImageQueue((uint)Size.Width, (uint)Size.Height, NativeImageQueue.ColorFormat.RGBA8888); } void UpdateTexture() From 8189b91e9bec1a087de7e67194f63d800b193c80 Mon Sep 17 00:00:00 2001 From: Sunghyun Min Date: Mon, 23 Aug 2021 00:43:28 +0900 Subject: [PATCH 12/76] [Elmsharp] Modify NavigationView --- .../CollectionView/CollectionView.cs | 15 +- .../CollectionView/ItemAdaptor.cs | 8 +- .../Shell/INavigationView.cs | 11 +- .../Shell/NavigationView.cs | 289 +++++------------- .../ThemeConstants.cs | 6 +- .../TC/NavigationDrawerTest.cs | 277 ++++++++++++++--- 6 files changed, 321 insertions(+), 285 deletions(-) diff --git a/src/Tizen.UIExtensions.ElmSharp/CollectionView/CollectionView.cs b/src/Tizen.UIExtensions.ElmSharp/CollectionView/CollectionView.cs index c330366..cfa30b4 100644 --- a/src/Tizen.UIExtensions.ElmSharp/CollectionView/CollectionView.cs +++ b/src/Tizen.UIExtensions.ElmSharp/CollectionView/CollectionView.cs @@ -809,13 +809,16 @@ void UpdateSnapPointsType(SnapPointsType snapPoints) void CreateEmptyView() { _emptyView = Adaptor!.CreateNativeView(this); - _emptyView.Show(); - Adaptor!.SetBinding(_emptyView, 0); - _emptyView.Geometry = Geometry; - _emptyView.MinimumHeight = Geometry.Height; - _emptyView.MinimumWidth = Geometry.Width; + _emptyView?.Show(); + if (_emptyView != null) + { + Adaptor!.SetBinding(_emptyView, 0); - Scroller.SetContent(_emptyView, true); + _emptyView.Geometry = Geometry; + _emptyView.MinimumHeight = Geometry.Height; + _emptyView.MinimumWidth = Geometry.Width; + Scroller.SetContent(_emptyView, true); + } _innerLayout.Hide(); } diff --git a/src/Tizen.UIExtensions.ElmSharp/CollectionView/ItemAdaptor.cs b/src/Tizen.UIExtensions.ElmSharp/CollectionView/ItemAdaptor.cs index 871c715..f5aca8c 100644 --- a/src/Tizen.UIExtensions.ElmSharp/CollectionView/ItemAdaptor.cs +++ b/src/Tizen.UIExtensions.ElmSharp/CollectionView/ItemAdaptor.cs @@ -142,26 +142,26 @@ public virtual object GetViewCategory(int index) /// Create a new view /// /// Created view - public abstract EvasObject CreateNativeView(EvasObject parent); + public abstract EvasObject? CreateNativeView(EvasObject parent); /// /// Create a new view /// /// To used item when create a view /// Created view - public abstract EvasObject CreateNativeView(int index, EvasObject parent); + public abstract EvasObject? CreateNativeView(int index, EvasObject parent); /// /// Create a header view, if header is not existed, null will be returned /// /// A created view - public abstract EvasObject GetHeaderView(EvasObject parent); + public abstract EvasObject? GetHeaderView(EvasObject parent); /// /// Create a footer view, if footer is not existed, null will be returned /// /// A created view - public abstract EvasObject GetFooterView(EvasObject parent); + public abstract EvasObject? GetFooterView(EvasObject parent); /// /// Remove view, a created view by Adaptor, should be removed by Adaptor diff --git a/src/Tizen.UIExtensions.ElmSharp/Shell/INavigationView.cs b/src/Tizen.UIExtensions.ElmSharp/Shell/INavigationView.cs index f446450..2c69535 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Shell/INavigationView.cs +++ b/src/Tizen.UIExtensions.ElmSharp/Shell/INavigationView.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using ElmSharp; using Tizen.UIExtensions.Common; using EColor = ElmSharp.Color; @@ -12,16 +11,14 @@ public interface INavigationView EvasObject? Header { get; set; } - DrawerHeaderBehavior HeaderBehavior { get; set; } + EvasObject? Footer { get; set; } + + EvasObject? Content { get; set; } EColor BackgroundColor { get; set; } EvasObject? BackgroundImage { get; set; } - void BuildMenu(IEnumerable items); - - void UpdateHeaderLayout(); - - event EventHandler ItemSelected; + event EventHandler? LayoutUpdated; } } \ No newline at end of file diff --git a/src/Tizen.UIExtensions.ElmSharp/Shell/NavigationView.cs b/src/Tizen.UIExtensions.ElmSharp/Shell/NavigationView.cs index 8bb87eb..5d8c88f 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Shell/NavigationView.cs +++ b/src/Tizen.UIExtensions.ElmSharp/Shell/NavigationView.cs @@ -1,6 +1,5 @@ -using System; -using System.Collections.Generic; -using ElmSharp; +using ElmSharp; +using System; using Tizen.UIExtensions.Common; using EColor = ElmSharp.Color; @@ -14,21 +13,14 @@ public class NavigationView : Background, INavigationView static readonly EColor s_defaultBackgroundColor = ThemeConstants.Shell.ColorClass.DefaultNavigationViewBackgroundColor; Box _mainLayout; - Box? _headerBox; - - GenList _menu; - GenItemClass _templateClass; - GenItemClass _headerClass; EvasObject? _header; + EvasObject? _footer; + EvasObject? _content; + EvasObject? _backgroundImage; EColor _backgroundColor; - DrawerHeaderBehavior _headerBehavior = DrawerHeaderBehavior.Fixed; - - bool HeaderOnMenu => HeaderBehavior == DrawerHeaderBehavior.Scroll || - HeaderBehavior == DrawerHeaderBehavior.CollapseOnScroll; - /// /// Initializes a new instance of the class. /// @@ -40,22 +32,6 @@ public NavigationView(EvasObject parent) : base(parent) InitializeComponent(parent); } - /// - /// Gets or sets the header behavior. - /// - public DrawerHeaderBehavior HeaderBehavior - { - get => _headerBehavior; - set - { - if (_headerBehavior == value) - return; - - _headerBehavior = value; - UpdateHeaderBehavior(); - } - } - /// /// Gets or sets the background color of the NavigtiaonView. /// @@ -93,38 +69,29 @@ public EvasObject? Header } /// - /// Gets or sets the target view of the NavigtiaonView. + /// Gets or sets the footer view of the NavigtiaonView. /// - public EvasObject TargetView => this; + public EvasObject? Footer + { + get => _footer; + set => UpdateFooter(value); + } + + public EvasObject? Content + { + get => _content; + set => UpdateContent(value); + } /// - /// Occurs when an item is selected in the NavigationView. + /// Gets or sets the target view of the NavigtiaonView. /// - public event EventHandler? ItemSelected; + public EvasObject TargetView => this; /// - /// Create the list of items to be displayed on the NavigationView. + /// Notifies that the layout has been updated. /// - /// - public void BuildMenu(IEnumerable items) - { - _menu.Clear(); - - foreach(var item in items) - { - var genItem = _menu.Append(_templateClass, item); - genItem.SetBackgroundColor(EColor.Transparent); - - if (item is NavigationViewItem naviItem) - { - if (!naviItem.IsFirst) - { - genItem.SetBottomlineColor(EColor.Transparent); - } - } - - } - } + public event EventHandler? LayoutUpdated; void InitializeComponent(EvasObject parent) { @@ -139,216 +106,100 @@ void InitializeComponent(EvasObject parent) }; _mainLayout.SetLayoutCallback(OnLayout); _mainLayout.Show(); - SetContent(_mainLayout); - - _menu = new GenList(parent) - { - Homogeneous = false, - SelectionMode = GenItemSelectionMode.Always, - BackgroundColor = EColor.Transparent, - Style = ThemeConstants.GenList.Styles.Solid, - ListMode = GenListMode.Scroll, - }; - _menu.Show(); - _mainLayout.PackEnd(_menu); - - _menu.ItemSelected += (s, e) => - { - ItemSelected?.Invoke(this, new ItemSelectedEventArgs(e.Item.Data, e.Item.Index)); - }; - - _templateClass = new GenItemClass(ThemeConstants.GenItemClass.Styles.Full) - { - GetContentHandler = GetTemplatedContent, - }; - - _headerClass = new GenItemClass(ThemeConstants.GenItemClass.Styles.Full) - { - GetContentHandler = GetHeaderContent - }; + SetContent(_mainLayout); } - EvasObject GetHeaderContent(object data, string part) + void OnLayout() { - if (_headerBox == null) - { - _headerBox = new Box(this) - { - AlignmentX = -1, - AlignmentY = -1, - WeightX = 1, - WeightY = 1, - }; - } + if (Geometry.Width == 0 || Geometry.Height == 0) + return; - var header = (EvasObject)data; - _headerBox.PackEnd(header); - _headerBox.SetLayoutCallback(OnHeaderBoxLayout); - _headerBox.MinimumHeight = header!.MinimumHeight; + var bound = Geometry; + int headerHeight = 0; + int footerHeight = 0; - return _headerBox; - } - EvasObject GetTemplatedContent(object data, string part) - { - if (data is NavigationViewItem item) + if (_header != null) { - if (item.GetContent != null) - { - return item.GetContent(item.Data); - } + var headerBound = bound; + headerHeight = _header.MinimumHeight; + headerBound.Height = headerHeight; + _header.Geometry = headerBound; } - return new Label(this) + if (_footer != null) { - Text = data.ToString() - }; - } + var footerbound = bound; + footerHeight = _footer.MinimumHeight; + footerbound.Y = bound.Y + bound.Height - footerHeight; + footerbound.Height = footerHeight; + _footer.Geometry = footerbound; + } - void OnHeaderBoxLayout() - { - if (_header != null && _headerBox != null) + if (_content != null) { - _header.Geometry = _headerBox.Geometry; + bound.Y += headerHeight; + bound.Height = bound.Height - headerHeight - footerHeight; + _content.Geometry = bound; } + + NotifyOnLayout(); } - void OnLayout() + void NotifyOnLayout() { - if (Geometry.Width == 0 || Geometry.Height == 0) - return; - - var bound = Geometry; - int headerHeight = 0; - - if (!HeaderOnMenu && _header != null) - { - var headerbound = bound; - headerHeight = _header.MinimumHeight; - headerbound.Height = headerHeight; - _header.Geometry = headerbound; - } - - bound.Y += headerHeight; - bound.Height -= headerHeight; - _menu.Geometry = bound; + LayoutUpdated?.Invoke(this, new LayoutEventArgs() { Geometry = Geometry.ToCommon() }); } void UpdateHeader(EvasObject? header) { if (_header != null) { - if (HeaderOnMenu) - { - ResetHeaderOnMenu(); - } - else if (_header != null) - { - _mainLayout.UnPack(_header); - _header.Unrealize(); - _header = null; - } + _mainLayout.UnPack(_header); + _header.Unrealize(); + _header = null; } if (header != null) { - if (HeaderOnMenu) - { - UpdateHeaderOnMenu(header); - } - else - { - _mainLayout.PackEnd(header); - } + _mainLayout.PackStart(header); } _header = header; _header?.Show(); } - void UpdateHeaderBehavior() + void UpdateFooter(EvasObject? footer) { - if (_header == null) - return; - - if (HeaderOnMenu) + if (_footer != null) { - _mainLayout.UnPack(_header); - UpdateHeaderOnMenu(_header); - } - else - { - ResetHeaderOnMenu(); - if (_header != null) - { - _mainLayout.PackEnd(_header); - } + _mainLayout.UnPack(_footer); + _footer.Unrealize(); + _footer = null; } - OnLayout(); - } - void ResetHeaderOnMenu() - { - if (_menu.FirstItem != null && _headerBox != null) + if(footer != null) { - _headerBox.UnPackAll(); - _menu.FirstItem.Delete(); - _headerBox = null; + _mainLayout.PackEnd(footer); } + _footer = footer; + _footer?.Show(); } - void UpdateHeaderOnMenu(EvasObject header) + void UpdateContent(EvasObject? content) { - if (_menu.FirstItem != null && _menu.FirstItem.Data == header) - return; - - GenListItem item; - if (_menu.Count > 0) - { - item = _menu.InsertBefore(_headerClass, header, _menu.FirstItem); - } - else + if (_content != null) { - item = _menu.Append(_headerClass, header); + _mainLayout.UnPack(_content); + _content.Unrealize(); + _content = null; } - item.SelectionMode = GenItemSelectionMode.None; - } - - void INavigationView.UpdateHeaderLayout() - { - if (_header == null) - return; - ResetHeaderOnMenu(); - if (HeaderOnMenu) - { - UpdateHeaderOnMenu(_header); - } - else + if(content != null) { - if (_header != null) - { - _mainLayout.PackEnd(_header); - } + _mainLayout.PackEnd(content); } - OnLayout(); - } - } - - public class NavigationViewItem - { - public NavigationViewItem() - { - Data = null; - GetContent = null; - IsFirst = false; + _content = content; + _content?.Show(); } - - public bool IsFirst { get; set; } - - public object? Data { get; set; } - - public GetContentDelegate? GetContent { get; set; } - - public delegate EvasObject GetContentDelegate(object? data); } } diff --git a/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs b/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs index f7a20df..9a51ec1 100644 --- a/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs +++ b/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs @@ -565,9 +565,9 @@ public class Shell public class Resources { // The source of icon resources is https://materialdesignicons.com/ - public const string MenuIcon = "Resource.menu.png"; - public const string BackIcon = "Resource.arrow_left.png"; - public const string DotsIcon = "Resource.dots_horizontal.png"; + public const string MenuIcon = "Platform.Tizen.Resources.menu.png"; + public const string BackIcon = "Platform.Tizen.Resources.arrow_left.png"; + public const string DotsIcon = "Platform.Tizen.Resources.dots_horizontal.png"; public const int DefaultDrawerDimOpacity = 30; public class Watch diff --git a/test/ElmSharpExGallery/TC/NavigationDrawerTest.cs b/test/ElmSharpExGallery/TC/NavigationDrawerTest.cs index 43e1a3f..04ba8ba 100644 --- a/test/ElmSharpExGallery/TC/NavigationDrawerTest.cs +++ b/test/ElmSharpExGallery/TC/NavigationDrawerTest.cs @@ -1,15 +1,11 @@ -using ElmSharp; -using System; +using System; +using System.Collections; using System.Collections.Generic; using Tizen.Applications; -using Tizen.UIExtensions.Common; -using Tizen.UIExtensions.ElmSharp; -using EBox = ElmSharp.Box; -using EButton = ElmSharp.Button; -using EColor = ElmSharp.Color; -using ELabel = ElmSharp.Label; -using ExLabel = Tizen.UIExtensions.ElmSharp.Label; -using ExImage = Tizen.UIExtensions.ElmSharp.Image; +using ElmSharp; +using Common = Tizen.UIExtensions.Common; +using Ext = Tizen.UIExtensions.ElmSharp; + namespace ElmSharpExGallery.TC { @@ -19,6 +15,8 @@ public class NavigationDrawerTest1 : TestCaseBase public override string TestDescription => "Shell NavigationDrawer Test 1"; + Box _header; + public override void Run(ElmSharp.Box parent) { Console.WriteLine($" run !!!"); @@ -28,7 +26,7 @@ public override void Run(ElmSharp.Box parent) data.Add($"item {i}"); } - var drawer = new NavigationDrawer(parent) + var drawer = new Ext.NavigationDrawer(parent) { AlignmentX = -1, AlignmentY = -1, @@ -36,14 +34,14 @@ public override void Run(ElmSharp.Box parent) WeightY = 1, }; - var naviView = new NavigationView(parent); + var naviView = new Ext.NavigationView(parent); - var content = new EBox(parent) + var content = new Box(parent) { - BackgroundColor = EColor.White + BackgroundColor = Color.White }; - var label = new ELabel(parent) + var label = new Label(parent) { AlignmentX = 0.5, WeightX = 1, @@ -52,7 +50,7 @@ public override void Run(ElmSharp.Box parent) label.Show(); content.PackEnd(label); - var bLabel = new ELabel(parent) + var bLabel = new Label(parent) { AlignmentX = 0.5, WeightX = 1, @@ -61,22 +59,58 @@ public override void Run(ElmSharp.Box parent) bLabel.Show(); content.PackEnd(bLabel); - var openButton = new EButton(parent) + var openButton = new Button(parent) { - MinimumWidth = 300, + MinimumWidth = 500, Text = "open / close" }; openButton.Show(); content.PackEnd(openButton); - var headerButton = new EButton(parent) + var headerButton = new Button(parent) { - MinimumWidth = 300, - Text = "Scroll / Fixed" + MinimumWidth = 500, + Text = "add / remove header" }; headerButton.Show(); content.PackEnd(headerButton); + var footerButton = new Button(parent) + { + MinimumWidth = 500, + Text = "add / remove footer" + }; + footerButton.Show(); + content.PackEnd(footerButton); + + + var contentButton = new Button(parent) + { + MinimumWidth = 500, + Text = "add / remove content" + }; + contentButton.Show(); + content.PackEnd(contentButton); + + + + var headerButton2 = new Button(parent) + { + MinimumHeight = 100, + MinimumWidth = 300, + Text = "header" + }; + headerButton2.Show(); + content.PackEnd(headerButton2); + + headerButton2.Clicked += (s, e) => + { + if (_header.MinimumHeight < 200) + _header.MinimumHeight = 300; + else + _header.MinimumHeight = 150; + }; + openButton.Clicked += (s, e) => { drawer.IsOpen = !drawer.IsOpen; @@ -84,28 +118,48 @@ public override void Run(ElmSharp.Box parent) headerButton.Clicked += (s, e) => { - if (naviView.HeaderBehavior == DrawerHeaderBehavior.Scroll) - naviView.HeaderBehavior = DrawerHeaderBehavior.Fixed; + if (naviView.Header != null) + { + naviView.Header = null; + } else - naviView.HeaderBehavior = DrawerHeaderBehavior.Scroll; - - bLabel.Text = $"behavior: {naviView.HeaderBehavior}"; + { + naviView.Header = CreateHeader(parent); + } }; - drawer.DimArea.BackgroundColor = EColor.Black; - drawer.DimArea.Opacity = 30; + footerButton.Clicked += (s, e) => + { + if (naviView.Footer != null) + { + naviView.Footer = null; + } + else + { + naviView.Footer = CreateFooter(parent); + } + }; - naviView.BuildMenu(data); - naviView.ItemSelected += (s, e) => + contentButton.Clicked += (s, e) => { - label.Text = $"selected item index: {e.SelectedItemIndex} / data: {e.SelectedItem.ToString()}"; - drawer.IsOpen = false; + if (naviView.Content != null) + { + naviView.Content = null; + } + else + { + naviView.Content = CreateContent(parent); + } }; + drawer.DimArea.BackgroundColor = Color.Black; + drawer.DimArea.Opacity = 30; + naviView.Header = CreateHeader(parent); + naviView.Footer = CreateFooter(parent); + naviView.Content = CreateContent(parent); naviView.BackgroundImage = CreateBackgroundImage(parent); - naviView.BackgroundColor = EColor.Yellow; - naviView.HeaderBehavior = DrawerHeaderBehavior.Fixed; + //naviView.BackgroundColor = Color.Yellow; drawer.Content = content; drawer.Drawer = naviView; @@ -116,7 +170,7 @@ public override void Run(ElmSharp.Box parent) EvasObject CreateBackgroundImage(EvasObject parent) { - var img = new ExImage(parent) + var img = new Ext.Image(parent) { AlignmentX = -1, AlignmentY = -1, @@ -124,7 +178,7 @@ EvasObject CreateBackgroundImage(EvasObject parent) WeightY = 1, }; img.Show(); - img.Aspect = Aspect.Fill; + img.Aspect = Common.Aspect.Fill; img.LoadAsync(Application.Current.DirectoryInfo.Resource + "animated2.gif"); return img; @@ -132,30 +186,161 @@ EvasObject CreateBackgroundImage(EvasObject parent) EvasObject CreateHeader(EvasObject parent) { - var header = new EBox(parent); - header.BackgroundColor = EColor.Red; - header.MinimumHeight = 200; + _header = new Box(parent); + _header.BackgroundColor = Color.Red; + _header.MinimumHeight = 150; - var headerLabel = new ExLabel(parent) + var headerLabel = new Ext.Label(parent) { - FormattedText = new FormattedString + FormattedText = new Common.FormattedString { Spans = { - new Span + new Common.Span { Text = "Header", - FontAttributes = FontAttributes.Bold, - HorizontalTextAlignment= TextAlignment.Center, + FontAttributes = Common.FontAttributes.Bold, + HorizontalTextAlignment= Common.TextAlignment.Center, FontSize = 60, } } } }; headerLabel.Show(); - header.PackEnd(headerLabel); + _header.PackEnd(headerLabel); + + return _header; + } - return header; + EvasObject CreateFooter(EvasObject parent) + { + var footer = new Box(parent); + footer.BackgroundColor = Color.Blue; + footer.MinimumHeight = 200; + + var footerLabel = new Ext.Label(parent) + { + FormattedText = new Common.FormattedString + { + Spans = + { + new Common.Span + { + Text = "Footer", + FontAttributes = Common.FontAttributes.Bold, + HorizontalTextAlignment= Common.TextAlignment.Center, + FontSize = 60, + } + } + } + }; + footerLabel.Show(); + footer.PackEnd(footerLabel); + + return footer; + } + + EvasObject CreateContent(EvasObject parent) + { + var collectionView = new Ext.CollectionView(parent) + { + WeightX = 1, + WeightY = 1, + AlignmentY = -1, + AlignmentX = -1, + }; + collectionView.Show(); + + var items = new List(); + for (int i = 0; i < 100; i++) + { + items.Add($"Items {i}"); + + } + var adaptor = new MyAdaptor(items); + collectionView.Adaptor = adaptor; + collectionView.LayoutManager = new Ext.LinearLayoutManager(false, Ext.ItemSizingStrategy.MeasureAllItems, 10); + + return collectionView; + } + + public class MyAdaptor : Ext.ItemAdaptor + { + + public MyAdaptor(IEnumerable items) : base(items) + { + + } + + public override EvasObject CreateNativeView(EvasObject parent) + { + var label = new Label(parent) + { + Text = "Default label", + BackgroundColor = Color.Gray, + }; + label.Show(); + return label; + } + + public override EvasObject CreateNativeView(int index, EvasObject parent) + { + var label = new Label(parent) + { + Text = $"Created [{index}] label", + BackgroundColor = Color.Yellow, + }; + label.Show(); + return label; + } + + public override EvasObject GetFooterView(EvasObject parent) + { + return null; + } + + public override EvasObject GetHeaderView(EvasObject parent) + { + return null; + } + + public override Size MeasureFooter(int widthConstraint, int heightConstraint) + { + return new Size(0, 0); + } + + public override Size MeasureHeader(int widthConstraint, int heightConstraint) + { + return new Size(0, 0); + } + + public override Size MeasureItem(int widthConstraint, int heightConstraint) + { + return new Size(150, 150); + } + + public override Size MeasureItem(int index, int widthConstraint, int heightConstraint) + { + if (index % 2 == 0) + return new Size(150, 150); + else + return new Size(200, 200); + } + + public override void RemoveNativeView(EvasObject native) + { + native.Unrealize(); + } + + public override void SetBinding(EvasObject view, int index) + { + (view as Label).Text = $"Binding {index}"; + } + + public override void UnBinding(EvasObject view) + { + (view as Label).Text = $"UnBinding"; + } } } } From c8be4fa22dcb43a0b20a8523912d4f7a260a5199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=97=88=EA=B0=95=ED=98=B8/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/Principal=20Engineer/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Wed, 1 Sep 2021 10:24:13 +0900 Subject: [PATCH 13/76] Adds net6.0-tizen TFM --- src/Tizen.UIExtensions.ElmSharp/Canvas.cs | 4 +-- .../CarouselPage.cs | 5 ++-- .../CollectionView/CollectionView.cs | 29 ++++++++++--------- .../CollectionView/IndicatorView.cs | 7 +++-- .../CollectionView/ItemAdaptor.cs | 4 +-- .../SelectedItemChangedEventArgs.cs | 4 +-- .../CollectionView/ViewHolder.cs | 8 ++--- .../DateTimePickerDialog.cs | 4 +-- src/Tizen.UIExtensions.ElmSharp/Dialog.cs | 4 ++- src/Tizen.UIExtensions.ElmSharp/DrawerBox.cs | 2 +- .../EditfieldEntry.cs | 2 +- src/Tizen.UIExtensions.ElmSharp/FlyoutPage.cs | 2 +- .../Shapes/ShapeView.cs | 4 +-- .../Tizen.UIExtensions.ElmSharp.csproj | 5 +++- .../CollectionView/CollectionView.cs | 10 +++---- .../CollectionView/ItemAdaptor.cs | 2 +- .../Tizen.UIExtensions.NUI.csproj | 5 +++- src/Tizen.UIExtensions.NUI/ViewGroup.cs | 6 ++-- 18 files changed, 61 insertions(+), 46 deletions(-) diff --git a/src/Tizen.UIExtensions.ElmSharp/Canvas.cs b/src/Tizen.UIExtensions.ElmSharp/Canvas.cs index ab150eb..ebec546 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Canvas.cs +++ b/src/Tizen.UIExtensions.ElmSharp/Canvas.cs @@ -57,7 +57,7 @@ void Initilize() { _children.CollectionChanged += (o, e) => { - if (e.Action == NotifyCollectionChangedAction.Add) + if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null) { foreach (var v in e.NewItems) { @@ -68,7 +68,7 @@ void Initilize() } } } - else if (e.Action == NotifyCollectionChangedAction.Remove) + else if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null) { foreach (var v in e.OldItems) { diff --git a/src/Tizen.UIExtensions.ElmSharp/CarouselPage.cs b/src/Tizen.UIExtensions.ElmSharp/CarouselPage.cs index e9af5d4..7704b9c 100644 --- a/src/Tizen.UIExtensions.ElmSharp/CarouselPage.cs +++ b/src/Tizen.UIExtensions.ElmSharp/CarouselPage.cs @@ -1,6 +1,7 @@ using ElmSharp; using System; using System.Collections.Generic; +using Index = ElmSharp.Index; using EBox = Tizen.UIExtensions.ElmSharp.Box; namespace Tizen.UIExtensions.ElmSharp @@ -145,7 +146,7 @@ void OnSelect(int selectIndex) _items[selectIndex].Select(true); } - void OnIndexChanged(object sender, EventArgs e) + void OnIndexChanged(object? sender, EventArgs e) { var changedIndex = _items.IndexOf(_index.SelectedItem); if (changedIndex != CurrentPageIndex) @@ -186,7 +187,7 @@ void OnOutterLayoutUpdate() _index.Geometry = newGeometry; } - void OnPageScrolled(object sender, EventArgs e) + void OnPageScrolled(object? sender, EventArgs e) { int previousPageIndex = CurrentPageIndex; CurrentPageIndex = _scroller.HorizontalPageIndex; diff --git a/src/Tizen.UIExtensions.ElmSharp/CollectionView/CollectionView.cs b/src/Tizen.UIExtensions.ElmSharp/CollectionView/CollectionView.cs index cfa30b4..eb99c5a 100644 --- a/src/Tizen.UIExtensions.ElmSharp/CollectionView/CollectionView.cs +++ b/src/Tizen.UIExtensions.ElmSharp/CollectionView/CollectionView.cs @@ -376,9 +376,12 @@ ViewHolder ICollectionViewController.RealizeView(int index) return holder; } - void OnItemStateChanged(object sender, EventArgs e) + void OnItemStateChanged(object? sender, EventArgs e) { - ViewHolder holder = (ViewHolder)sender; + var holder = (ViewHolder?)sender; + if (holder == null) + return; + if (holder.Content != null) { Adaptor?.UpdateViewState(holder.Content, holder.State); @@ -398,7 +401,7 @@ void OnItemStateChanged(object sender, EventArgs e) } } - void OnRequestItemSelection(object sender, EventArgs e) + void OnRequestItemSelection(object? sender, EventArgs e) { if (SelectionMode == CollectionViewSelectionMode.None) return; @@ -568,9 +571,9 @@ void OnAdaptorChanged() } } - void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { - if (sender != Adaptor) + if (sender != Adaptor || Adaptor == null) { return; } @@ -580,7 +583,7 @@ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) return; } - if (e.Action == NotifyCollectionChangedAction.Add) + if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null) { int idx = e.NewStartingIndex; if (idx == -1) @@ -599,7 +602,7 @@ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) LayoutManager?.ItemInserted(idx++); } } - else if (e.Action == NotifyCollectionChangedAction.Remove) + else if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null) { int idx = e.OldStartingIndex; @@ -628,7 +631,7 @@ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) LayoutManager?.ItemRemoved(e.OldStartingIndex); LayoutManager?.ItemInserted(e.NewStartingIndex); } - else if (e.Action == NotifyCollectionChangedAction.Replace) + else if (e.Action == NotifyCollectionChangedAction.Replace && e.OldItems != null) { // Can't tracking if there is no information old data if (e.OldItems.Count > 1 || e.NewStartingIndex == -1) @@ -721,18 +724,18 @@ void OnInnerLayout() int _previousHorizontalOffset = 0; int _previousVerticalOffset = 0; - void OnScrollStarted(object sender, EventArgs e) + void OnScrollStarted(object? sender, EventArgs e) { _isScrollAnimationStarted = true; } - void OnScrollStopped(object sender, EventArgs e) + void OnScrollStopped(object? sender, EventArgs e) { SendScrolledEvent(); _isScrollAnimationStarted = false; } - void OnScrolled(object sender, EventArgs e) + void OnScrolled(object? sender, EventArgs e) { LayoutManager?.LayoutItems(ViewPort); if (!_isScrollAnimationStarted) @@ -741,13 +744,13 @@ void OnScrolled(object sender, EventArgs e) } } - void OnKeyDown(object sender, EvasKeyEventArgs e) + void OnKeyDown(object? sender, EvasKeyEventArgs e) { _allowFocusOnItem = true; UpdateAllowFocusOnItem(_allowFocusOnItem); } - void OnDragStart(object sender, EventArgs e) + void OnDragStart(object? sender, EventArgs e) { _allowFocusOnItem = false; UpdateAllowFocusOnItem(_allowFocusOnItem); diff --git a/src/Tizen.UIExtensions.ElmSharp/CollectionView/IndicatorView.cs b/src/Tizen.UIExtensions.ElmSharp/CollectionView/IndicatorView.cs index cca5a67..0c71912 100644 --- a/src/Tizen.UIExtensions.ElmSharp/CollectionView/IndicatorView.cs +++ b/src/Tizen.UIExtensions.ElmSharp/CollectionView/IndicatorView.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using ElmSharp; +using Index = ElmSharp.Index; namespace Tizen.UIExtensions.ElmSharp { @@ -61,9 +62,11 @@ public void ClearIndex() Clear(); } - void OnSelected(object sender, EventArgs e) + void OnSelected(object? sender, EventArgs e) { - var index = _list.IndexOf((IndexItem)sender); + if (sender == null) + return; + int index = _list.IndexOf((IndexItem)sender); SelectedPosition?.Invoke(this, new SelectedPositionChangedEventArgs(index)); UpdateSelectedIndex(index); } diff --git a/src/Tizen.UIExtensions.ElmSharp/CollectionView/ItemAdaptor.cs b/src/Tizen.UIExtensions.ElmSharp/CollectionView/ItemAdaptor.cs index f5aca8c..65b05f0 100644 --- a/src/Tizen.UIExtensions.ElmSharp/CollectionView/ItemAdaptor.cs +++ b/src/Tizen.UIExtensions.ElmSharp/CollectionView/ItemAdaptor.cs @@ -86,7 +86,7 @@ protected void SetItemsSource(IEnumerable items) } } - public object this[int index] + public object? this[int index] { get { @@ -100,7 +100,7 @@ public object this[int index] public int Count => _itemsSource.Count; INotifyCollectionChanged? _observableCollection; - event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged + event NotifyCollectionChangedEventHandler? INotifyCollectionChanged.CollectionChanged { add { diff --git a/src/Tizen.UIExtensions.ElmSharp/CollectionView/SelectedItemChangedEventArgs.cs b/src/Tizen.UIExtensions.ElmSharp/CollectionView/SelectedItemChangedEventArgs.cs index 5e949ef..a128542 100644 --- a/src/Tizen.UIExtensions.ElmSharp/CollectionView/SelectedItemChangedEventArgs.cs +++ b/src/Tizen.UIExtensions.ElmSharp/CollectionView/SelectedItemChangedEventArgs.cs @@ -10,7 +10,7 @@ public class SelectedItemChangedEventArgs : EventArgs /// /// Gets the selected item. /// - public object SelectedItem { get; } + public object? SelectedItem { get; } /// /// Gets the selected item index @@ -20,7 +20,7 @@ public class SelectedItemChangedEventArgs : EventArgs /// /// Initializes a new instance of the SelectedItemChangedEventArgs class. /// - public SelectedItemChangedEventArgs(object selectedItem, int selectedItemIndex) + public SelectedItemChangedEventArgs(object? selectedItem, int selectedItemIndex) { SelectedItem = selectedItem; SelectedItemIndex = selectedItemIndex; diff --git a/src/Tizen.UIExtensions.ElmSharp/CollectionView/ViewHolder.cs b/src/Tizen.UIExtensions.ElmSharp/CollectionView/ViewHolder.cs index 626f888..808c140 100644 --- a/src/Tizen.UIExtensions.ElmSharp/CollectionView/ViewHolder.cs +++ b/src/Tizen.UIExtensions.ElmSharp/CollectionView/ViewHolder.cs @@ -102,17 +102,17 @@ protected void Initialize(EvasObject parent) Show(); } - protected virtual void OnFocused(object sender, EventArgs e) + protected virtual void OnFocused(object? sender, EventArgs e) { UpdateFocusState(); } - protected virtual void OnUnfocused(object sender, EventArgs e) + protected virtual void OnUnfocused(object? sender, EventArgs e) { UpdateFocusState(); } - protected virtual void OnClicked(object sender, EventArgs e) + protected virtual void OnClicked(object? sender, EventArgs e) { RequestSelected?.Invoke(this, EventArgs.Empty); } @@ -152,7 +152,7 @@ void UpdateFocusState() } } - void OnKeyUp(object sender, EvasKeyEventArgs e) + void OnKeyUp(object? sender, EvasKeyEventArgs e) { if (e.KeyName == "Enter" && _focusArea.IsFocused) { diff --git a/src/Tizen.UIExtensions.ElmSharp/DateTimePickerDialog.cs b/src/Tizen.UIExtensions.ElmSharp/DateTimePickerDialog.cs index c236d2d..e31297b 100644 --- a/src/Tizen.UIExtensions.ElmSharp/DateTimePickerDialog.cs +++ b/src/Tizen.UIExtensions.ElmSharp/DateTimePickerDialog.cs @@ -93,13 +93,13 @@ void Initialize() Hide(); PickerClosed?.Invoke(this, EventArgs.Empty); }; - BackButtonPressed += (object s, EventArgs e) => + BackButtonPressed += (object? s, EventArgs e) => { Hide(); PickerClosed?.Invoke(this, EventArgs.Empty); }; - ShowAnimationFinished += (object s, EventArgs e) => + ShowAnimationFinished += (object? s, EventArgs e) => { PickerOpened?.Invoke(this, EventArgs.Empty); }; diff --git a/src/Tizen.UIExtensions.ElmSharp/Dialog.cs b/src/Tizen.UIExtensions.ElmSharp/Dialog.cs index 9772eb8..4700c69 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Dialog.cs +++ b/src/Tizen.UIExtensions.ElmSharp/Dialog.cs @@ -49,7 +49,9 @@ public Dialog(EvasObject parent) : base(parent) /// /// Occurs whenever the dialog is first displayed. /// - public event EventHandler? Shown; +#pragma warning disable CS0109 + public new event EventHandler? Shown; +#pragma warning restore CS0109 /// /// Gets or sets the title of the dialog diff --git a/src/Tizen.UIExtensions.ElmSharp/DrawerBox.cs b/src/Tizen.UIExtensions.ElmSharp/DrawerBox.cs index e87eac5..d660710 100644 --- a/src/Tizen.UIExtensions.ElmSharp/DrawerBox.cs +++ b/src/Tizen.UIExtensions.ElmSharp/DrawerBox.cs @@ -117,7 +117,7 @@ public DrawerBox(EvasObject parent) : base(parent) _panel.SetScrollable(_isGestureEnabled); _panel.SetScrollableArea(1.0); _panel.Direction = PanelDirection.Left; - _panel.Toggled += (object sender, EventArgs e) => + _panel.Toggled += (object? sender, EventArgs e) => { if (_panel.IsOpen) _dimArea.Show(); diff --git a/src/Tizen.UIExtensions.ElmSharp/EditfieldEntry.cs b/src/Tizen.UIExtensions.ElmSharp/EditfieldEntry.cs index 6b8813f..8480717 100644 --- a/src/Tizen.UIExtensions.ElmSharp/EditfieldEntry.cs +++ b/src/Tizen.UIExtensions.ElmSharp/EditfieldEntry.cs @@ -187,7 +187,7 @@ protected virtual void UpdateEnableClearButton() } } - void OnClearButtonClicked(object sender, EventArgs e) + void OnClearButtonClicked(object? sender, EventArgs e) { Text = string.Empty; } diff --git a/src/Tizen.UIExtensions.ElmSharp/FlyoutPage.cs b/src/Tizen.UIExtensions.ElmSharp/FlyoutPage.cs index bddcb02..020d12b 100644 --- a/src/Tizen.UIExtensions.ElmSharp/FlyoutPage.cs +++ b/src/Tizen.UIExtensions.ElmSharp/FlyoutPage.cs @@ -26,7 +26,7 @@ public class FlyoutPage : DrawerBox public FlyoutPage(EvasObject parent) : base(parent) { - Toggled += (object sender, EventArgs e) => + Toggled += (object? sender, EventArgs e) => { IsPresentedChanged?.Invoke(this, new IsPresentedChangedEventArgs(IsPresented)); }; diff --git a/src/Tizen.UIExtensions.ElmSharp/Shapes/ShapeView.cs b/src/Tizen.UIExtensions.ElmSharp/Shapes/ShapeView.cs index 1421b1c..4a2c307 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Shapes/ShapeView.cs +++ b/src/Tizen.UIExtensions.ElmSharp/Shapes/ShapeView.cs @@ -170,7 +170,7 @@ public Stretch Aspect } } - void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e) + void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs e) { var canvas = e.Surface.Canvas; _drawableBounds = e.Info.Rect; @@ -224,7 +224,7 @@ void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e) } } - void OnLayoutUpdated(object sender, LayoutEventArgs e) + void OnLayoutUpdated(object? sender, LayoutEventArgs e) { _skCanvasView.Geometry = Geometry; } diff --git a/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj b/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj index c2da7aa..9889bf4 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj +++ b/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj @@ -1,7 +1,7 @@  - tizen40 + tizen40;net6.0-tizen6.5 Tizen true 8.0 @@ -27,6 +27,9 @@ + + + diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs b/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs index 16803fd..346c94e 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs @@ -533,14 +533,14 @@ void OnAdaptorChanged() } - void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (sender != Adaptor) { return; } - if (e.Action == NotifyCollectionChangedAction.Add) + if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null) { int idx = e.NewStartingIndex; if (idx == -1) @@ -568,7 +568,7 @@ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) LayoutManager?.ItemInserted(idx++); } } - else if (e.Action == NotifyCollectionChangedAction.Remove) + else if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null) { int idx = e.OldStartingIndex; @@ -611,7 +611,7 @@ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) LayoutManager?.ItemRemoved(e.OldStartingIndex); LayoutManager?.ItemInserted(e.NewStartingIndex); } - else if (e.Action == NotifyCollectionChangedAction.Replace) + else if (e.Action == NotifyCollectionChangedAction.Replace && e.OldItems != null) { // Can't tracking if there is no information old data if (e.OldItems.Count > 1 || e.NewStartingIndex == -1) @@ -751,7 +751,7 @@ void OnItemStateUpdated(object? sender, EventArgs e) } } - ViewHolder FindViewHolder(int index) + ViewHolder? FindViewHolder(int index) { return _viewHolderIndexTable.Where(d => d.Value == index).Select(d => d.Key).FirstOrDefault(); } diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/ItemAdaptor.cs b/src/Tizen.UIExtensions.NUI/CollectionView/ItemAdaptor.cs index 9fdbe71..16f2f0a 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/ItemAdaptor.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/ItemAdaptor.cs @@ -219,7 +219,7 @@ protected virtual void Dispose(bool disposing) } } - void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke(this, e); } diff --git a/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj b/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj index e446b54..36d25c4 100644 --- a/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj +++ b/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj @@ -1,7 +1,7 @@  - tizen90 + tizen90;net6.0-tizen6.5 Tizen true 8.0 @@ -25,6 +25,9 @@ + + + diff --git a/src/Tizen.UIExtensions.NUI/ViewGroup.cs b/src/Tizen.UIExtensions.NUI/ViewGroup.cs index 67671db..552c4dc 100644 --- a/src/Tizen.UIExtensions.NUI/ViewGroup.cs +++ b/src/Tizen.UIExtensions.NUI/ViewGroup.cs @@ -93,9 +93,9 @@ void SendLayoutUpdated() }); } - void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { - if (e.Action == NotifyCollectionChangedAction.Add) + if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null) { foreach (var v in e.NewItems) { @@ -105,7 +105,7 @@ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) } } } - else if (e.Action == NotifyCollectionChangedAction.Remove) + else if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null) { foreach (var v in e.OldItems) { From e41f8cd0c4c04b053798cd2c76b67c75ed01f04a Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Fri, 3 Sep 2021 14:52:53 +0900 Subject: [PATCH 14/76] [NUI] Fix ViewGroup layout event issue --- src/Tizen.UIExtensions.NUI/ViewGroup.cs | 19 ++++++-- test/NUIExGallery/TC/ViewGroupTest.cs | 61 +++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 test/NUIExGallery/TC/ViewGroupTest.cs diff --git a/src/Tizen.UIExtensions.NUI/ViewGroup.cs b/src/Tizen.UIExtensions.NUI/ViewGroup.cs index 552c4dc..f399b13 100644 --- a/src/Tizen.UIExtensions.NUI/ViewGroup.cs +++ b/src/Tizen.UIExtensions.NUI/ViewGroup.cs @@ -54,12 +54,22 @@ public ViewGroup() public event EventHandler? LayoutUpdated; public override void Add(View child) + { + Children.Add(child); + } + + public override void Remove(View child) + { + Children.Remove(child); + } + + void AddInternal(View child) { base.Add(child); LayoutRequest(); } - public override void Remove(View child) + void RemoveInternal(View child) { base.Remove(child); LayoutRequest(); @@ -87,6 +97,7 @@ void SendLayoutUpdated() if (this == null) return; + _layoutRequested = false; LayoutUpdated?.Invoke(this, new LayoutEventArgs { Geometry = new Rect(Position.X, Position.Y, Size.Width, Size.Height) @@ -101,7 +112,7 @@ void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (v is View view) { - Add(view); + AddInternal(view); } } } @@ -111,7 +122,7 @@ void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (v is View view) { - Remove(view); + RemoveInternal(view); } } } @@ -119,7 +130,7 @@ void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { foreach (var child in base.Children.ToList()) { - Remove(child); + RemoveInternal(child); } } } diff --git a/test/NUIExGallery/TC/ViewGroupTest.cs b/test/NUIExGallery/TC/ViewGroupTest.cs new file mode 100644 index 0000000..27a1577 --- /dev/null +++ b/test/NUIExGallery/TC/ViewGroupTest.cs @@ -0,0 +1,61 @@ +using System; +using System.Linq; +using Tizen.NUI.BaseComponents; +using Tizen.UIExtensions.NUI; + +namespace NUIExGallery.TC +{ + public class ViewGroupTest : TestCaseBase + { + public override string TestName => "ViewGroup test"; + + public override string TestDescription => "ViewGroup test"; + + public override View Run() + { + var viewgroup = new ViewGroup(); + viewgroup.LayoutUpdated += (s, e) => + { + var blockSize = viewgroup.Size.Height / viewgroup.Children.Count; + var currentTop = 0f; + foreach (var child in viewgroup.Children) + { + child.UpdateBounds(new Tizen.UIExtensions.Common.Rect(0, currentTop, viewgroup.Size.Width, blockSize)); + currentTop += blockSize; + } + }; + + var button = new Button + { + Text = "Add" + }; + var removeButton = new Button + { + Text = "Remove" + }; + viewgroup.Children.Add(button); + viewgroup.Children.Add(removeButton); + viewgroup.Children.Add(new View + { + BackgroundColor = Tizen.NUI.Color.Yellow + }); + + button.Clicked += (s, e) => + { + var rnd = new Random(); + var view = new View(); + view.UpdateBackgroundColor(Tizen.UIExtensions.Common.Color.FromRgb(rnd.Next(10, 255), rnd.Next(10, 255), rnd.Next(10, 255))); + viewgroup.Add(view); + }; + + removeButton.Clicked += (s, e) => + { + var last = viewgroup.Children.Last(); + viewgroup.Remove(last); + last.Dispose(); + }; + + return viewgroup; + } + } +} From cb6b697a31902927ff44d8865cb485d46a451a64 Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Fri, 24 Sep 2021 15:10:50 +0900 Subject: [PATCH 15/76] Fix ElmSharp Ticker --- .../Animation/Ticker.cs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Tizen.UIExtensions.Common/Animation/Ticker.cs b/src/Tizen.UIExtensions.Common/Animation/Ticker.cs index 92173e3..078a35f 100644 --- a/src/Tizen.UIExtensions.Common/Animation/Ticker.cs +++ b/src/Tizen.UIExtensions.Common/Animation/Ticker.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using Tizen.NUI; +using System.Threading; +using Tizen.Applications; namespace Tizen.UIExtensions.Common.Internal { @@ -10,6 +11,7 @@ public class Ticker static Ticker? s_ticker; readonly Stopwatch _stopwatch; readonly List>> _timeouts; + readonly SynchronizationContext? _context; Timer _timer; int _count; @@ -20,14 +22,13 @@ protected Ticker() _count = 0; _timeouts = new List>>(); _stopwatch = new Stopwatch(); - _timer = new Timer(16); - _timer.Tick += OnTick; - } - bool OnTick(object source, Timer.TickEventArgs e) - { - SendSignals(-1); - return true; + if (SynchronizationContext.Current == null) + { + TizenSynchronizationContext.Initialize(); + } + _context = SynchronizationContext.Current; + _timer = new Timer((object? o) => HandleElapsed(o), this, Timeout.Infinite, Timeout.Infinite); } public static Ticker Default @@ -75,12 +76,12 @@ void RemoveTimeout(int handle) protected void DisableTimer() { - _timer.Stop(); + _timer.Change(-1, -1); } protected void EnableTimer() { - _timer.Start(); + _timer.Change(16, 16); } protected void SendFinish() @@ -117,6 +118,11 @@ protected void SendSignals(long step) } } + void HandleElapsed(object? state) + { + _context?.Post((o) => SendSignals(-1), null); + } + void Disable() { _stopwatch.Reset(); From 90c7c7375a3be915d9460b2d058e3a6f6b59d2fd Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Mon, 27 Sep 2021 13:26:50 +0900 Subject: [PATCH 16/76] [ElmSharp] Add RefreshIcon drawable --- .../GraphicsView/IRefreshIcon.cs | 9 ++ .../GraphicsView/RefreshIconDrawable.cs | 122 ++++++++++++++++++ .../Tizen.UIExtensions.Common.projitems | 2 + .../GraphicsView/RefreshIcon.cs | 62 +++++++++ .../Tizen.UIExtensions.ElmSharp.csproj | 1 + test/ElmSharpExGallery/TC/DrawableTest.cs | 72 +++++++++++ 6 files changed, 268 insertions(+) create mode 100644 src/Tizen.UIExtensions.Common/GraphicsView/IRefreshIcon.cs create mode 100644 src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs create mode 100644 src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs create mode 100644 test/ElmSharpExGallery/TC/DrawableTest.cs diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/IRefreshIcon.cs b/src/Tizen.UIExtensions.Common/GraphicsView/IRefreshIcon.cs new file mode 100644 index 0000000..97bd8ed --- /dev/null +++ b/src/Tizen.UIExtensions.Common/GraphicsView/IRefreshIcon.cs @@ -0,0 +1,9 @@ +namespace Tizen.UIExtensions.Common.GraphicsView +{ + public interface IRefreshIcon + { + bool IsRunning { get; } + + Color Color { get; } + } +} diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs new file mode 100644 index 0000000..bf731ce --- /dev/null +++ b/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs @@ -0,0 +1,122 @@ +using Microsoft.Maui.Graphics; +using Tizen.UIExtensions.Common.Internal; +using GColor = Microsoft.Maui.Graphics.Color; +using TSize = Tizen.UIExtensions.Common.Size; + +namespace Tizen.UIExtensions.Common.GraphicsView +{ + public class RefreshIconDrawable : GraphicsViewDrawable, IAnimatable + { + public const float IconSize = 40f; + public const float StrokeWidth = 4f; + public const int RotationAngle = 360; + + public RefreshIconDrawable(IRefreshIcon view) + { + View = view; + } + + IRefreshIcon View { get; } + + float MaterialRefreshViewIconRotate { get; set; } + + float MaterialRefreshViewIconStartAngle { get; set; } + + float MaterialRefreshViewIconEndAngle { get; set; } + + + public override void Draw(ICanvas canvas, RectangleF dirtyRect) + { + DrawRefreshIcon(canvas, dirtyRect); + } + + public void UpdateAnimation(bool animate) + { + if (animate) + { + StartAnimation(); + return; + } + AbortAnimation(); + } + + void StartAnimation() + { + var startAngle = 90; + var endAngle = 360; + uint animationLength = 1400; + + var materialRefreshViewIconAngleAnimation = new Animation(); + var rotateAnimation = new Animation(v => + { + MaterialRefreshViewIconRotate = (int)v; + SendInvalidated(); + }, 0, RotationAngle, easing: Easing.Linear); + var startAngleAnimation = new Animation(v => MaterialRefreshViewIconStartAngle = (int)v, startAngle, startAngle - RotationAngle, easing: Easing.Linear); + var endAngleAnimation = new Animation(v => MaterialRefreshViewIconEndAngle = (int)v, endAngle, endAngle - RotationAngle, easing: Easing.Linear); + + materialRefreshViewIconAngleAnimation.Add(0, 1, rotateAnimation); + materialRefreshViewIconAngleAnimation.Add(0, 1, startAngleAnimation); + materialRefreshViewIconAngleAnimation.Add(0, 1, endAngleAnimation); + + materialRefreshViewIconAngleAnimation.Commit(this, "MaterialRefreshIcon", length: animationLength, repeat: () => true, finished: (l, c) => materialRefreshViewIconAngleAnimation = null); + } + + void AbortAnimation() + { + this.AbortAnimation("MaterialRefreshIcon"); + SendInvalidated(); + } + + void DrawRefreshIcon(ICanvas canvas, RectangleF dirtyRect) + { + canvas.SaveState(); + canvas.StrokeSize = StrokeWidth; + + var x = dirtyRect.X + StrokeWidth; + var y = dirtyRect.Y + StrokeWidth; + if (View.IsRunning) + { + DrawRunningIcon(canvas, x, y); + } + else + { + DrawIdleIcon(canvas, x, y); + } + canvas.RestoreState(); + } + + void DrawRunningIcon(ICanvas canvas, float x, float y) + { + canvas.Rotate(MaterialRefreshViewIconRotate, x + IconSize / 2, y + IconSize / 2); + canvas.StrokeColor = View.Color.ToGraphicsColor(Material.Color.Blue); + canvas.DrawArc(x, y, IconSize, IconSize, MaterialRefreshViewIconStartAngle, MaterialRefreshViewIconEndAngle, false, false); + } + + void DrawIdleIcon(ICanvas canvas, float x, float y) + { + canvas.Rotate(0, x + IconSize / 2, y + IconSize / 2); + canvas.StrokeColor = View.Color.IsDefault ? GColor.FromArgb(Material.Color.LightBlue) : View.Color.MultiplyAlpha(0.5).ToGraphicsColor(Material.Color.LightBlue); + canvas.DrawArc(x, y, IconSize, IconSize, 0, RotationAngle, false, false); + } + + public override TSize Measure(double availableWidth, double availableHeight) + { + var size = (IconSize + (StrokeWidth * 2)) * DeviceInfo.ScalingFactor; + return new TSize(size, size); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + this.AbortAnimation("MaterialRefreshIcon"); + } + base.Dispose(disposing); + } + + void IAnimatable.BatchBegin() { } + + void IAnimatable.BatchCommit() { } + } +} diff --git a/src/Tizen.UIExtensions.Common/Tizen.UIExtensions.Common.projitems b/src/Tizen.UIExtensions.Common/Tizen.UIExtensions.Common.projitems index ce1bc79..c68112e 100644 --- a/src/Tizen.UIExtensions.Common/Tizen.UIExtensions.Common.projitems +++ b/src/Tizen.UIExtensions.Common/Tizen.UIExtensions.Common.projitems @@ -19,6 +19,8 @@ + + diff --git a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs new file mode 100644 index 0000000..2802241 --- /dev/null +++ b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs @@ -0,0 +1,62 @@ +using System; +using Microsoft.Maui.Graphics.Skia.Views; +using Tizen.UIExtensions.Common.GraphicsView; +using ElmSharp; +using DeviceInfo = Tizen.UIExtensions.Common.DeviceInfo; + +namespace Tizen.UIExtensions.ElmSharp +{ + /// + /// A visual control used to indicate that refreshing is ongoing. + /// + public class RefreshIcon : SkiaGraphicsView, IRefreshIcon + { + RefreshIconDrawable _drawable; + bool _isRunning; + Common.Color _color; + + /// + /// Initializes a new instance of the RefreshIcon. + /// + public RefreshIcon(EvasObject parent) : base(parent) + { + _drawable = new RefreshIconDrawable(this); + _drawable.Invalidated += OnRefreshIconInvalidated; + Drawable = _drawable; + + var size = (RefreshIconDrawable.IconSize + (RefreshIconDrawable.StrokeWidth * 2)) * DeviceInfo.ScalingFactor; + Resize((int)size, (int)size); + } + + /// + /// Gets or sets the value indicating if the RefreshIcon is running. + /// + public bool IsRunning + { + get => _isRunning; + set + { + _isRunning = value; + _drawable.UpdateAnimation(value); + } + } + + /// + /// Gets or sets the Color of the RefreshIcon. + /// + public new Common.Color Color + { + get => _color; + set + { + _color = value; + Invalidate(); + } + } + + void OnRefreshIconInvalidated(object? sender, EventArgs e) + { + Invalidate(); + } + } +} diff --git a/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj b/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj index 9889bf4..49bf9bb 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj +++ b/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj @@ -26,6 +26,7 @@ + diff --git a/test/ElmSharpExGallery/TC/DrawableTest.cs b/test/ElmSharpExGallery/TC/DrawableTest.cs new file mode 100644 index 0000000..759b825 --- /dev/null +++ b/test/ElmSharpExGallery/TC/DrawableTest.cs @@ -0,0 +1,72 @@ +using Tizen.UIExtensions.Common; +using Tizen.UIExtensions.ElmSharp; +using Button = Tizen.UIExtensions.ElmSharp.Button; +using Label = Tizen.UIExtensions.ElmSharp.Label; + +namespace ElmSharpExGallery.TC +{ + public class DrawableTest : TestCaseBase + { + public override string TestName => "Drawable Test"; + + public override string TestDescription => "Test Drawable"; + + public override void Run(ElmSharp.Box parent) + { + var layout = new ElmSharp.Box(parent) + { + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1, + WeightY = 1, + }; + layout.Show(); + parent.PackEnd(layout); + + var label = new Label(parent) + { + Text = "refreshIcon", + FontSize = 20 + }; + label.Show(); + layout.PackEnd(label); + var refreshIcon = new RefreshIcon(parent) + { + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1, + WeightY = 1 + }; + refreshIcon.Show(); + layout.PackEnd(refreshIcon); + + var isRunningButton = new Button(parent) + { + Text = "IsRefreshing", + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1 + }; + isRunningButton.Clicked += (s, e) => + { + refreshIcon.IsRunning = refreshIcon.IsRunning ? false : true; + }; + isRunningButton.Show(); + layout.PackEnd(isRunningButton); + + var colorChangeButton = new Button(parent) + { + Text = "Change Color", + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1 + }; + colorChangeButton.Clicked += (s, e) => + { + refreshIcon.Color = refreshIcon.Color == Color.Default ? Color.Red : Color.Default; + }; + colorChangeButton.Show(); + layout.PackEnd(colorChangeButton); + } + } +} From 5e8403873d3121b77c426fa47e459fc3351732c6 Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Tue, 28 Sep 2021 11:08:35 +0900 Subject: [PATCH 17/76] [ElmSharp] Update IRefreshIcon --- .../GraphicsView/IRefreshIcon.cs | 6 ++ .../GraphicsView/RefreshIconDrawable.cs | 27 +++++--- .../GraphicsView/RefreshIcon.cs | 50 ++++++++++++++- test/ElmSharpExGallery/TC/DrawableTest.cs | 62 ++++++++++++++++++- 4 files changed, 134 insertions(+), 11 deletions(-) diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/IRefreshIcon.cs b/src/Tizen.UIExtensions.Common/GraphicsView/IRefreshIcon.cs index 97bd8ed..a3f9a96 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/IRefreshIcon.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/IRefreshIcon.cs @@ -4,6 +4,12 @@ public interface IRefreshIcon { bool IsRunning { get; } + bool IsPulling { get; } + Color Color { get; } + + int MaximumPullDistance { get; } + + float PullDistance { get; } } } diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs index bf731ce..ecc4021 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs @@ -24,23 +24,25 @@ public RefreshIconDrawable(IRefreshIcon view) float MaterialRefreshViewIconEndAngle { get; set; } + float MaterialRefreshViewIconDistance { get; set; } + public override void Draw(ICanvas canvas, RectangleF dirtyRect) { DrawRefreshIcon(canvas, dirtyRect); } - public void UpdateAnimation(bool animate) + public void UpdateRunningAnimation(bool animate) { if (animate) { - StartAnimation(); + StartRunningAnimation(); return; } - AbortAnimation(); + AbortRunningAnimation(); } - void StartAnimation() + void StartRunningAnimation() { var startAngle = 90; var endAngle = 360; @@ -62,12 +64,18 @@ void StartAnimation() materialRefreshViewIconAngleAnimation.Commit(this, "MaterialRefreshIcon", length: animationLength, repeat: () => true, finished: (l, c) => materialRefreshViewIconAngleAnimation = null); } - void AbortAnimation() + void AbortRunningAnimation() { this.AbortAnimation("MaterialRefreshIcon"); SendInvalidated(); } + public void UpdateIconDistance(float distanceRate) + { + MaterialRefreshViewIconDistance = View.MaximumPullDistance * distanceRate; + SendInvalidated(); + } + void DrawRefreshIcon(ICanvas canvas, RectangleF dirtyRect) { canvas.SaveState(); @@ -75,6 +83,11 @@ void DrawRefreshIcon(ICanvas canvas, RectangleF dirtyRect) var x = dirtyRect.X + StrokeWidth; var y = dirtyRect.Y + StrokeWidth; + if (View.IsPulling) + { + y += MaterialRefreshViewIconDistance; + } + if (View.IsRunning) { DrawRunningIcon(canvas, x, y); @@ -102,8 +115,8 @@ void DrawIdleIcon(ICanvas canvas, float x, float y) public override TSize Measure(double availableWidth, double availableHeight) { - var size = (IconSize + (StrokeWidth * 2)) * DeviceInfo.ScalingFactor; - return new TSize(size, size); + var iconSize = (IconSize + (StrokeWidth * 2)) * DeviceInfo.ScalingFactor; + return new TSize(iconSize, iconSize); } protected override void Dispose(bool disposing) diff --git a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs index 2802241..f6c11de 100644 --- a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs +++ b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs @@ -14,6 +14,9 @@ public class RefreshIcon : SkiaGraphicsView, IRefreshIcon RefreshIconDrawable _drawable; bool _isRunning; Common.Color _color; + bool _isPulling; + float _pullDistance; + int _maximumPullDistance; /// /// Initializes a new instance of the RefreshIcon. @@ -24,8 +27,10 @@ public RefreshIcon(EvasObject parent) : base(parent) _drawable.Invalidated += OnRefreshIconInvalidated; Drawable = _drawable; - var size = (RefreshIconDrawable.IconSize + (RefreshIconDrawable.StrokeWidth * 2)) * DeviceInfo.ScalingFactor; - Resize((int)size, (int)size); + var iconSize = (RefreshIconDrawable.IconSize + (RefreshIconDrawable.StrokeWidth * 2)) * DeviceInfo.ScalingFactor; + _maximumPullDistance = (int)iconSize; + var iconHeight = iconSize + _maximumPullDistance; + Resize((int)iconSize, (int)iconHeight); } /// @@ -37,7 +42,7 @@ public bool IsRunning set { _isRunning = value; - _drawable.UpdateAnimation(value); + _drawable.UpdateRunningAnimation(value); } } @@ -54,6 +59,45 @@ public bool IsRunning } } + /// + /// Gets or sets the value indicating if the RefreshIcon is pulling. + /// + public bool IsPulling + { + get => _isPulling; + set + { + _isPulling = value; + Invalidate(); + } + } + + /// + /// Gets or sets the value(0.0 ~ 1.0) indicating the icon pulling rate. + /// + public float PullDistance + { + get => _pullDistance; + set + { + _pullDistance = value; + _drawable.UpdateIconDistance(value); + } + } + + /// + /// Represents a maximum pull distance. + /// + public int MaximumPullDistance + { + get => _maximumPullDistance; + set + { + _maximumPullDistance = value; + _drawable.UpdateIconDistance(_pullDistance); + } + } + void OnRefreshIconInvalidated(object? sender, EventArgs e) { Invalidate(); diff --git a/test/ElmSharpExGallery/TC/DrawableTest.cs b/test/ElmSharpExGallery/TC/DrawableTest.cs index 759b825..47bc5e6 100644 --- a/test/ElmSharpExGallery/TC/DrawableTest.cs +++ b/test/ElmSharpExGallery/TC/DrawableTest.cs @@ -13,6 +13,8 @@ public class DrawableTest : TestCaseBase public override void Run(ElmSharp.Box parent) { + int _maximumPullDistance = 100; + var layout = new ElmSharp.Box(parent) { AlignmentX = -1, @@ -35,11 +37,69 @@ public override void Run(ElmSharp.Box parent) AlignmentX = -1, AlignmentY = -1, WeightX = 1, - WeightY = 1 + WeightY = 1, + MaximumPullDistance = _maximumPullDistance }; refreshIcon.Show(); layout.PackEnd(refreshIcon); + var distanceLabel = new Label(parent) + { + Text = "Pull Distance", + FontSize = 20 + }; + distanceLabel.Show(); + layout.PackEnd(distanceLabel); + var distanceSlider = new ElmSharp.Slider(parent) + { + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1 + }; + distanceSlider.ValueChanged += (s, e) => + { + refreshIcon.PullDistance = (float)distanceSlider.Value; + }; + distanceSlider.Show(); + layout.PackEnd(distanceSlider); + + var isPullingButton = new Button(parent) + { + Text = $"IsPulling : {refreshIcon.IsPulling}", + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1 + }; + isPullingButton.Clicked += (s, e) => + { + refreshIcon.IsPulling = refreshIcon.IsPulling ? false : true; + isPullingButton.Text = $"IsPulling : {refreshIcon.IsPulling}"; + }; + isPullingButton.Show(); + layout.PackEnd(isPullingButton); + + var MaxpullDistanceButton = new Button(parent) + { + Text = $"Max PullDistance: {refreshIcon.MaximumPullDistance}", + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1 + }; + MaxpullDistanceButton.Clicked += (s, e) => + { + if (refreshIcon.MaximumPullDistance == _maximumPullDistance) + { + refreshIcon.MaximumPullDistance = _maximumPullDistance * 2; + MaxpullDistanceButton.Text = $"Max PullDistance: {refreshIcon.MaximumPullDistance}"; + } + else + { + refreshIcon.MaximumPullDistance = _maximumPullDistance; + MaxpullDistanceButton.Text = $"Max PullDistance: {refreshIcon.MaximumPullDistance}"; + } + }; + MaxpullDistanceButton.Show(); + layout.PackEnd(MaxpullDistanceButton); var isRunningButton = new Button(parent) { Text = "IsRefreshing", From 0d200fc2f021a572422051c0ea217a970363285b Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Tue, 28 Sep 2021 16:13:49 +0900 Subject: [PATCH 18/76] [ElmSharp] Remove moving distance on RefreshIcon --- .../GraphicsView/IRefreshIcon.cs | 2 - .../GraphicsView/RefreshIconDrawable.cs | 14 ---- .../GraphicsView/RefreshIcon.cs | 41 +++------- test/ElmSharpExGallery/TC/DrawableTest.cs | 81 ++++++++----------- 4 files changed, 47 insertions(+), 91 deletions(-) diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/IRefreshIcon.cs b/src/Tizen.UIExtensions.Common/GraphicsView/IRefreshIcon.cs index a3f9a96..4b29d5e 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/IRefreshIcon.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/IRefreshIcon.cs @@ -8,8 +8,6 @@ public interface IRefreshIcon Color Color { get; } - int MaximumPullDistance { get; } - float PullDistance { get; } } } diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs index ecc4021..8df6d32 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs @@ -24,9 +24,6 @@ public RefreshIconDrawable(IRefreshIcon view) float MaterialRefreshViewIconEndAngle { get; set; } - float MaterialRefreshViewIconDistance { get; set; } - - public override void Draw(ICanvas canvas, RectangleF dirtyRect) { DrawRefreshIcon(canvas, dirtyRect); @@ -70,12 +67,6 @@ void AbortRunningAnimation() SendInvalidated(); } - public void UpdateIconDistance(float distanceRate) - { - MaterialRefreshViewIconDistance = View.MaximumPullDistance * distanceRate; - SendInvalidated(); - } - void DrawRefreshIcon(ICanvas canvas, RectangleF dirtyRect) { canvas.SaveState(); @@ -83,11 +74,6 @@ void DrawRefreshIcon(ICanvas canvas, RectangleF dirtyRect) var x = dirtyRect.X + StrokeWidth; var y = dirtyRect.Y + StrokeWidth; - if (View.IsPulling) - { - y += MaterialRefreshViewIconDistance; - } - if (View.IsRunning) { DrawRunningIcon(canvas, x, y); diff --git a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs index f6c11de..453000f 100644 --- a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs +++ b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs @@ -2,7 +2,6 @@ using Microsoft.Maui.Graphics.Skia.Views; using Tizen.UIExtensions.Common.GraphicsView; using ElmSharp; -using DeviceInfo = Tizen.UIExtensions.Common.DeviceInfo; namespace Tizen.UIExtensions.ElmSharp { @@ -16,7 +15,6 @@ public class RefreshIcon : SkiaGraphicsView, IRefreshIcon Common.Color _color; bool _isPulling; float _pullDistance; - int _maximumPullDistance; /// /// Initializes a new instance of the RefreshIcon. @@ -27,10 +25,7 @@ public RefreshIcon(EvasObject parent) : base(parent) _drawable.Invalidated += OnRefreshIconInvalidated; Drawable = _drawable; - var iconSize = (RefreshIconDrawable.IconSize + (RefreshIconDrawable.StrokeWidth * 2)) * DeviceInfo.ScalingFactor; - _maximumPullDistance = (int)iconSize; - var iconHeight = iconSize + _maximumPullDistance; - Resize((int)iconSize, (int)iconHeight); + Resize((int)_drawable.MeasuredWidth, (int)_drawable.MeasuredHeight); } /// @@ -46,19 +41,6 @@ public bool IsRunning } } - /// - /// Gets or sets the Color of the RefreshIcon. - /// - public new Common.Color Color - { - get => _color; - set - { - _color = value; - Invalidate(); - } - } - /// /// Gets or sets the value indicating if the RefreshIcon is pulling. /// @@ -73,27 +55,30 @@ public bool IsPulling } /// - /// Gets or sets the value(0.0 ~ 1.0) indicating the icon pulling rate. + /// Gets or sets the Color of the RefreshIcon. /// - public float PullDistance + public new Common.Color Color { - get => _pullDistance; + get => _color; set { - _pullDistance = value; - _drawable.UpdateIconDistance(value); + _color = value; + Invalidate(); } } /// - /// Represents a maximum pull distance. + /// Gets or sets the value(0.0 ~ 1.0) indicating the icon pulling rate. /// - public int MaximumPullDistance + public float PullDistance { - get => _maximumPullDistance; + get => _pullDistance; set { - _maximumPullDistance = value; + if (value > 1.0) + _pullDistance = 1.0f; + else + _pullDistance = value; _drawable.UpdateIconDistance(_pullDistance); } } diff --git a/test/ElmSharpExGallery/TC/DrawableTest.cs b/test/ElmSharpExGallery/TC/DrawableTest.cs index 47bc5e6..768e16c 100644 --- a/test/ElmSharpExGallery/TC/DrawableTest.cs +++ b/test/ElmSharpExGallery/TC/DrawableTest.cs @@ -1,11 +1,13 @@ +using System.Threading.Tasks; using Tizen.UIExtensions.Common; +using Tizen.UIExtensions.Common.Internal; using Tizen.UIExtensions.ElmSharp; using Button = Tizen.UIExtensions.ElmSharp.Button; using Label = Tizen.UIExtensions.ElmSharp.Label; namespace ElmSharpExGallery.TC { - public class DrawableTest : TestCaseBase + public class DrawableTest : TestCaseBase, IAnimatable { public override string TestName => "Drawable Test"; @@ -13,8 +15,6 @@ public class DrawableTest : TestCaseBase public override void Run(ElmSharp.Box parent) { - int _maximumPullDistance = 100; - var layout = new ElmSharp.Box(parent) { AlignmentX = -1, @@ -37,32 +37,11 @@ public override void Run(ElmSharp.Box parent) AlignmentX = -1, AlignmentY = -1, WeightX = 1, - WeightY = 1, - MaximumPullDistance = _maximumPullDistance + WeightY = 1 }; refreshIcon.Show(); layout.PackEnd(refreshIcon); - var distanceLabel = new Label(parent) - { - Text = "Pull Distance", - FontSize = 20 - }; - distanceLabel.Show(); - layout.PackEnd(distanceLabel); - var distanceSlider = new ElmSharp.Slider(parent) - { - AlignmentX = -1, - AlignmentY = -1, - WeightX = 1 - }; - distanceSlider.ValueChanged += (s, e) => - { - refreshIcon.PullDistance = (float)distanceSlider.Value; - }; - distanceSlider.Show(); - layout.PackEnd(distanceSlider); - var isPullingButton = new Button(parent) { Text = $"IsPulling : {refreshIcon.IsPulling}", @@ -78,28 +57,6 @@ public override void Run(ElmSharp.Box parent) isPullingButton.Show(); layout.PackEnd(isPullingButton); - var MaxpullDistanceButton = new Button(parent) - { - Text = $"Max PullDistance: {refreshIcon.MaximumPullDistance}", - AlignmentX = -1, - AlignmentY = -1, - WeightX = 1 - }; - MaxpullDistanceButton.Clicked += (s, e) => - { - if (refreshIcon.MaximumPullDistance == _maximumPullDistance) - { - refreshIcon.MaximumPullDistance = _maximumPullDistance * 2; - MaxpullDistanceButton.Text = $"Max PullDistance: {refreshIcon.MaximumPullDistance}"; - } - else - { - refreshIcon.MaximumPullDistance = _maximumPullDistance; - MaxpullDistanceButton.Text = $"Max PullDistance: {refreshIcon.MaximumPullDistance}"; - } - }; - MaxpullDistanceButton.Show(); - layout.PackEnd(MaxpullDistanceButton); var isRunningButton = new Button(parent) { Text = "IsRefreshing", @@ -127,6 +84,36 @@ public override void Run(ElmSharp.Box parent) }; colorChangeButton.Show(); layout.PackEnd(colorChangeButton); + + var simulateButton = new Button(parent) + { + Text = "Simulate Animation", + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1 + }; + simulateButton.Clicked += async (s, e) => + { + if (refreshIcon.IsPulling) + { + var iconMoveLength = 150; + var iconGeometry = refreshIcon.Geometry; + var _refreshIconStartAnimation = new Animation(v => { + refreshIcon.PullDistance = (float)(v / iconMoveLength); + refreshIcon.Move(iconGeometry.X, iconGeometry.Y + (int)v); + }, 0, iconMoveLength, Easing.Linear); + _refreshIconStartAnimation.Commit(this, "RefreshStart"); + await Task.Delay(2000); + var _refreshIconEndAnimation = new Animation(v => refreshIcon.Move(iconGeometry.X, iconGeometry.Y + iconMoveLength - (int)v), 0, iconMoveLength, Easing.Linear); + _refreshIconEndAnimation.Commit(this, "RefreshEnd"); + } + }; + simulateButton.Show(); + layout.PackEnd(simulateButton); } + + public void BatchBegin() { } + + public void BatchCommit() { } } } From 7838c95bee60423ec0744b418f10d1c59065948a Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Fri, 1 Oct 2021 09:11:07 +0900 Subject: [PATCH 19/76] [ElmSharp] Use ThemeConstants in RefreshIcon --- .../GraphicsView/IRefreshIcon.cs | 3 ++ .../GraphicsView/RefreshIconDrawable.cs | 47 +++++++++++++------ .../GraphicsView/RefreshIcon.cs | 8 +++- .../ThemeConstants.cs | 11 +++-- 4 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/IRefreshIcon.cs b/src/Tizen.UIExtensions.Common/GraphicsView/IRefreshIcon.cs index 4b29d5e..44f414b 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/IRefreshIcon.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/IRefreshIcon.cs @@ -1,5 +1,8 @@ namespace Tizen.UIExtensions.Common.GraphicsView { + /// + /// An interface defining properties for RefreshIcon. + /// public interface IRefreshIcon { bool IsRunning { get; } diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs index 8df6d32..65a37d7 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs @@ -1,16 +1,24 @@ using Microsoft.Maui.Graphics; using Tizen.UIExtensions.Common.Internal; +using Tizen.UIExtensions.ElmSharp; using GColor = Microsoft.Maui.Graphics.Color; using TSize = Tizen.UIExtensions.Common.Size; namespace Tizen.UIExtensions.Common.GraphicsView { + /// + /// A Drawable class that is used to draw a refresh icon. + /// This drawable draws a round circle stroke that can run in circle. + /// public class RefreshIconDrawable : GraphicsViewDrawable, IAnimatable { - public const float IconSize = 40f; - public const float StrokeWidth = 4f; - public const int RotationAngle = 360; + float _iconSize = ThemeConstants.RefreshLayout.Resources.IconSize; + float _strokeWidth = ThemeConstants.RefreshLayout.Resources.IconStrokeWidth; + int _rotationAngle = ThemeConstants.RefreshLayout.Resources.IconRotationAngle; + /// + /// Initializes a new instance of the RefreshIconDrawable. + /// public RefreshIconDrawable(IRefreshIcon view) { View = view; @@ -24,11 +32,19 @@ public RefreshIconDrawable(IRefreshIcon view) float MaterialRefreshViewIconEndAngle { get; set; } + /// + /// Implementation of the IDrawable.Draw() method. + /// This method defines how to draw a refresh icon. + /// public override void Draw(ICanvas canvas, RectangleF dirtyRect) { DrawRefreshIcon(canvas, dirtyRect); } + /// + /// Updates to start or stop running animation. + /// + /// public void UpdateRunningAnimation(bool animate) { if (animate) @@ -50,9 +66,9 @@ void StartRunningAnimation() { MaterialRefreshViewIconRotate = (int)v; SendInvalidated(); - }, 0, RotationAngle, easing: Easing.Linear); - var startAngleAnimation = new Animation(v => MaterialRefreshViewIconStartAngle = (int)v, startAngle, startAngle - RotationAngle, easing: Easing.Linear); - var endAngleAnimation = new Animation(v => MaterialRefreshViewIconEndAngle = (int)v, endAngle, endAngle - RotationAngle, easing: Easing.Linear); + }, 0, _rotationAngle, easing: Easing.Linear); + var startAngleAnimation = new Animation(v => MaterialRefreshViewIconStartAngle = (int)v, startAngle, startAngle - _rotationAngle, easing: Easing.Linear); + var endAngleAnimation = new Animation(v => MaterialRefreshViewIconEndAngle = (int)v, endAngle, endAngle - _rotationAngle, easing: Easing.Linear); materialRefreshViewIconAngleAnimation.Add(0, 1, rotateAnimation); materialRefreshViewIconAngleAnimation.Add(0, 1, startAngleAnimation); @@ -70,10 +86,10 @@ void AbortRunningAnimation() void DrawRefreshIcon(ICanvas canvas, RectangleF dirtyRect) { canvas.SaveState(); - canvas.StrokeSize = StrokeWidth; + canvas.StrokeSize = _strokeWidth; - var x = dirtyRect.X + StrokeWidth; - var y = dirtyRect.Y + StrokeWidth; + var x = dirtyRect.X + _strokeWidth; + var y = dirtyRect.Y + _strokeWidth; if (View.IsRunning) { DrawRunningIcon(canvas, x, y); @@ -87,21 +103,24 @@ void DrawRefreshIcon(ICanvas canvas, RectangleF dirtyRect) void DrawRunningIcon(ICanvas canvas, float x, float y) { - canvas.Rotate(MaterialRefreshViewIconRotate, x + IconSize / 2, y + IconSize / 2); + canvas.Rotate(MaterialRefreshViewIconRotate, x + _iconSize / 2, y + _iconSize / 2); canvas.StrokeColor = View.Color.ToGraphicsColor(Material.Color.Blue); - canvas.DrawArc(x, y, IconSize, IconSize, MaterialRefreshViewIconStartAngle, MaterialRefreshViewIconEndAngle, false, false); + canvas.DrawArc(x, y, _iconSize, _iconSize, MaterialRefreshViewIconStartAngle, MaterialRefreshViewIconEndAngle, false, false); } void DrawIdleIcon(ICanvas canvas, float x, float y) { - canvas.Rotate(0, x + IconSize / 2, y + IconSize / 2); + canvas.Rotate(0, x + _iconSize / 2, y + _iconSize / 2); canvas.StrokeColor = View.Color.IsDefault ? GColor.FromArgb(Material.Color.LightBlue) : View.Color.MultiplyAlpha(0.5).ToGraphicsColor(Material.Color.LightBlue); - canvas.DrawArc(x, y, IconSize, IconSize, 0, RotationAngle, false, false); + canvas.DrawArc(x, y, _iconSize, _iconSize, 0, _rotationAngle, false, false); } + /// + /// Implementation of the IMeasurable.Measure() method. It returns the size of a drawn icon. + /// public override TSize Measure(double availableWidth, double availableHeight) { - var iconSize = (IconSize + (StrokeWidth * 2)) * DeviceInfo.ScalingFactor; + var iconSize = (_iconSize + (_strokeWidth * 2)) * DeviceInfo.ScalingFactor; return new TSize(iconSize, iconSize); } diff --git a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs index 453000f..e9472e8 100644 --- a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs +++ b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs @@ -2,6 +2,7 @@ using Microsoft.Maui.Graphics.Skia.Views; using Tizen.UIExtensions.Common.GraphicsView; using ElmSharp; +using DeviceInfo = Tizen.UIExtensions.Common.DeviceInfo; namespace Tizen.UIExtensions.ElmSharp { @@ -15,6 +16,8 @@ public class RefreshIcon : SkiaGraphicsView, IRefreshIcon Common.Color _color; bool _isPulling; float _pullDistance; + float _iconSize = ThemeConstants.RefreshLayout.Resources.IconSize; + float _strokeWidth = ThemeConstants.RefreshLayout.Resources.IconStrokeWidth; /// /// Initializes a new instance of the RefreshIcon. @@ -25,7 +28,8 @@ public RefreshIcon(EvasObject parent) : base(parent) _drawable.Invalidated += OnRefreshIconInvalidated; Drawable = _drawable; - Resize((int)_drawable.MeasuredWidth, (int)_drawable.MeasuredHeight); + var iconSize = (_iconSize + (_strokeWidth * 2)) * DeviceInfo.ScalingFactor; + Resize((int)iconSize, (int)iconSize); } /// @@ -79,7 +83,7 @@ public float PullDistance _pullDistance = 1.0f; else _pullDistance = value; - _drawable.UpdateIconDistance(_pullDistance); + Invalidate(); } } diff --git a/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs b/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs index 9a51ec1..07a7531 100644 --- a/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs +++ b/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs @@ -510,13 +510,14 @@ public class ColorClass } #endregion - #region RefreshView - public class RefreshView + #region RefreshLayout + public class RefreshLayout { public class Resources { - public const int IconSize = 48; - public const string IconPath = "Xamarin.Forms.Platform.Tizen.Resource.refresh_48dp.png"; + public const float IconSize = 40f; + public const float IconStrokeWidth = 4f; + public const int IconRotationAngle = 360; } public class ColorClass @@ -614,6 +615,7 @@ public class ColorClass } #endregion + #region FontWeight public static class FontWeight { public class Styles @@ -632,5 +634,6 @@ public class Styles public const string ExtraBlack = "ExtraBlack"; } } + #endregion } } From be9e8a898bf8dfb2fce8d4c497b065d721db0150 Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Wed, 6 Oct 2021 12:26:20 +0900 Subject: [PATCH 20/76] [ElmSharp] Add RefreshLayout --- .../RefreshLayout.cs | 247 ++++++++++++++++++ .../ThemeConstants.cs | 3 + .../TC/RefreshLayoutTest1.cs | 103 ++++++++ 3 files changed, 353 insertions(+) create mode 100644 src/Tizen.UIExtensions.ElmSharp/RefreshLayout.cs create mode 100644 test/ElmSharpExGallery/TC/RefreshLayoutTest1.cs diff --git a/src/Tizen.UIExtensions.ElmSharp/RefreshLayout.cs b/src/Tizen.UIExtensions.ElmSharp/RefreshLayout.cs new file mode 100644 index 0000000..a6c0a5d --- /dev/null +++ b/src/Tizen.UIExtensions.ElmSharp/RefreshLayout.cs @@ -0,0 +1,247 @@ +using System; +using ElmSharp; +using Tizen.UIExtensions.Common.Internal; + +namespace Tizen.UIExtensions.ElmSharp +{ + enum RefreshState + { + Idle, + Drag, + Loading, + } + + /// + /// RefreshLayout is a container that holds a single content and allows you to pull down to refresh content with showing refresh animation. + /// + public class RefreshLayout : Box, IAnimatable + { + RefreshIcon _refreshIcon; + Box _contentLayout; + EvasObject? _content; + GestureLayer _gestureLayer; + RefreshState _refreshState; + bool _shouldRefresh; + bool _isRefreshing; + bool _isRefreshEnabled; + int _initialIconGeometryY; + + int _maximumDistance = ThemeConstants.RefreshLayout.Resources.RefreshDistance; + int _minimumSize = ThemeConstants.RefreshLayout.Resources.MinimumLayoutSize; + float _iconSize = ThemeConstants.RefreshLayout.Resources.IconSize; + uint _animationLength = ThemeConstants.RefreshLayout.Resources.RefreshAnimationLength; + + /// + /// Initializes a new instance of the RefreshLayout class. + /// + /// + public RefreshLayout(EvasObject parent) : base(parent) + { + MinimumHeight = _minimumSize; + _refreshState = RefreshState.Idle; + _refreshIcon = new RefreshIcon(parent); + _contentLayout = new Box(parent); + _refreshIcon.Show(); + _contentLayout.Show(); + + _gestureLayer = new GestureLayer(parent); + _gestureLayer.Attach(_contentLayout); + _gestureLayer.SetMomentumCallback(GestureLayer.GestureState.Move, OnMoved); + _gestureLayer.SetMomentumCallback(GestureLayer.GestureState.End, OnEnded); + _gestureLayer.SetMomentumCallback(GestureLayer.GestureState.Abort, OnEnded); + + this.PackEnd(_refreshIcon); + this.PackEnd(_contentLayout); + + SetLayoutCallback(OnLayoutUpdate); + } + + /// + /// Invokes when refreshing starts by pulling down the content. + /// + public event EventHandler? Refreshing; + + public bool IsRefreshEnabled + { + get + { + return _isRefreshEnabled; + } + set + { + _isRefreshEnabled = value; + } + } + + /// + /// Gets or sets the refreshing status of RefreshLayout. + /// Refresh animation starts when it is set to true. + /// + public bool IsRefreshing + { + get + { + return _isRefreshing; + } + set + { + if (_isRefreshing != value) + { + _isRefreshing = value; + if (_isRefreshing) + { + BeginRefreshing(false); + } + else + { + ResetRefreshing(); + } + } + } + } + + /// + /// Gets or sets the color of the refresh icon. + /// + public Common.Color RefreshIconColor + { + get + { + return _refreshIcon.Color; + } + set + { + _refreshIcon.Color = value; + } + } + + /// + /// Gets or sets the content of the RefreshLayout. + /// + public EvasObject? Content + { + get + { + return _content; + } + set + { + if (_content != null) + { + _contentLayout.UnPackAll(); + } + _content = value; + _contentLayout.PackEnd(_content); + } + } + + void OnLayoutUpdate() + { + var iconGeometryX = Geometry.X + (Geometry.Width - _refreshIcon.Geometry.Width) / 2; + var iconBottomPadding = (int)_iconSize / 2; + _initialIconGeometryY = Geometry.Y - (_refreshIcon.Geometry.Height+ iconBottomPadding); + var iconHeight = _refreshIcon.Geometry.Height + iconBottomPadding; + _refreshIcon.Geometry = new Rect(iconGeometryX, _initialIconGeometryY, _refreshIcon.Geometry.Width, iconHeight); + _contentLayout.Geometry = Geometry; + if (_content != null) + { + _content.Geometry = Geometry; + } + } + + void OnMoved(GestureLayer.MomentumData moment) + { + if (_refreshState == RefreshState.Idle && _isRefreshEnabled) + { + _refreshState = RefreshState.Drag; + } + + if (_refreshState == RefreshState.Drag) + { + var dy = moment.Y2 - moment.Y1; + if (dy < 0) + return; + MoveLayout(dy); + } + } + + void OnEnded(GestureLayer.MomentumData moment) + { + if (_refreshState != RefreshState.Drag) + { + return; + } + + if (_shouldRefresh) + { + BeginRefreshing(true); + _shouldRefresh = false; + } + else + { + ResetRefreshing(); + } + } + + void BeginRefreshing(bool isPulledRefresh) + { + var movedDistance = GetMovedDistance(); + var refreshDistance = _refreshIcon.Geometry.Height + _maximumDistance; + var contentDistanceDiff = refreshDistance - movedDistance; + var _refreshStartAnimation = new Animation(v => _contentLayout.Move(Geometry.X, Geometry.Y + movedDistance + (int)v), 0, contentDistanceDiff, Easing.Linear); + _refreshStartAnimation.Commit(this, "RefreshBegin", length: _animationLength); + + if (!isPulledRefresh) + { + var currentIconGeometryY = Geometry.Y + _refreshIcon.Geometry.Y; + var iconDistanceDiff = _maximumDistance - currentIconGeometryY; + var _refreshIconBeginAnimation = new Animation(v => _refreshIcon.Move(_refreshIcon.Geometry.X, currentIconGeometryY + (int)v), 0, iconDistanceDiff, Easing.Linear); + _refreshIconBeginAnimation.Commit(this, "RefreshIconBegin", length: _animationLength); + } + + _refreshState = RefreshState.Loading; + _isRefreshing = true; + _refreshIcon.IsRunning = true; + Refreshing?.Invoke(this, EventArgs.Empty); + } + + void ResetRefreshing() + { + var movedDistance = GetMovedDistance(); + var _refreshResetAnimation = new Animation(v => _contentLayout.Move(Geometry.X, Geometry.Y + movedDistance - (int)v), 0, movedDistance, Easing.Linear); + _refreshResetAnimation.Commit(this, "RefreshReset", length: _animationLength); + + var currentIconGeometryY = _refreshIcon.Geometry.Y; + var _refreshIconResetAnimation = new Animation(v => _refreshIcon.Move(_refreshIcon.Geometry.X, currentIconGeometryY - (int)v), 0, movedDistance, Easing.Linear); + _refreshIconResetAnimation.Commit(this, "RefreshIconReset", length: _animationLength); + + _refreshState = RefreshState.Idle; + _refreshIcon.IsRunning = false; + } + + int GetMovedDistance() + { + return _contentLayout.Geometry.Y - Geometry.Y; + } + + void MoveLayout(int distance) + { + var iconDistance = _initialIconGeometryY + distance; + if (iconDistance > _maximumDistance) + { + iconDistance = _maximumDistance; + _shouldRefresh = true; + } + else + { + _shouldRefresh = false; + } + _refreshIcon.Move(_refreshIcon.Geometry.X, iconDistance); + _contentLayout.Move(_contentLayout.Geometry.X, Geometry.Y + distance); + } + + void IAnimatable.BatchBegin() {} + + void IAnimatable.BatchCommit() {} + } +} diff --git a/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs b/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs index 07a7531..5e7b1b2 100644 --- a/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs +++ b/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs @@ -518,6 +518,9 @@ public class Resources public const float IconSize = 40f; public const float IconStrokeWidth = 4f; public const int IconRotationAngle = 360; + public const int MinimumLayoutSize = 200; + public const int RefreshDistance = 100; + public const uint RefreshAnimationLength = 100; } public class ColorClass diff --git a/test/ElmSharpExGallery/TC/RefreshLayoutTest1.cs b/test/ElmSharpExGallery/TC/RefreshLayoutTest1.cs new file mode 100644 index 0000000..992baee --- /dev/null +++ b/test/ElmSharpExGallery/TC/RefreshLayoutTest1.cs @@ -0,0 +1,103 @@ +using System.Threading.Tasks; +using Tizen.UIExtensions.ElmSharp; +using Tizen.UIExtensions.Common; +using Color = Tizen.UIExtensions.Common.Color; + +namespace ElmSharpExGallery.TC +{ + public class RefreshLayoutTest1 : TestCaseBase + { + public override string TestName => "RefreshLayoutTest1"; + + public override string TestDescription => "Test RefreshLayout"; + + public override void Run(ElmSharp.Box parent) + { + var refreshLayout = new RefreshLayout(parent) + { + WeightX = 1, + WeightY = 1, + AlignmentY = -1, + AlignmentX = -1, + }; + var box = new Tizen.UIExtensions.ElmSharp.Box(parent) + { + WeightX = 1, + WeightY = 1, + AlignmentY = -1, + AlignmentX = -1, + BackgroundColor = ElmSharp.Color.Gray + }; + var changeIconButton = new Tizen.UIExtensions.ElmSharp.Button(parent) + { + Text = "Change Icon Color", + WeightX = 1, + WeightY = 1, + AlignmentX = -1, + AlignmentY = -1, + }; + changeIconButton.Move(100, 300); + changeIconButton.Resize(400, 100); + changeIconButton.Clicked += (s, e) => + { + if (refreshLayout.RefreshIconColor == Color.Default) + refreshLayout.RefreshIconColor = Color.Red; + else + refreshLayout.RefreshIconColor = Color.Default; + }; + changeIconButton.Show(); + var statusLabel = new Tizen.UIExtensions.ElmSharp.Label(parent) + { + Text = "Idle", + WeightX = 1, + WeightY = 1, + AlignmentX = -1, + AlignmentY = -1, + TextColor = Color.Beige, + HorizontalTextAlignment = TextAlignment.Center, + VerticalTextAlignment = TextAlignment.Center, + FontAttributes = FontAttributes.Bold, + FontSize = 30 + }; + statusLabel.Move(100, 200); + statusLabel.Resize(300, 100); + statusLabel.Show(); + + var changeRefreshingButton = new Tizen.UIExtensions.ElmSharp.Button(parent) + { + Text = $"Start Refreshing", + WeightX = 1, + WeightY = 1, + AlignmentX = -1, + AlignmentY = -1, + }; + changeRefreshingButton.Move(100, 400); + changeRefreshingButton.Resize(400, 100); + changeRefreshingButton.Clicked += (s, e) => + { + refreshLayout.IsRefreshing = true; + }; + changeRefreshingButton.Show(); + + box.PackEnd(statusLabel); + box.PackEnd(changeIconButton); + box.PackEnd(changeRefreshingButton); + box.Show(); + + refreshLayout.Content = box; + refreshLayout.IsRefreshEnabled = true; + refreshLayout.Refreshing += async (s, e) => + { + statusLabel.Text = "Refreshing"; + await Task.Delay(2000); + refreshLayout.IsRefreshing = false; + statusLabel.Text = "Refreshed"; + + }; + + refreshLayout.Resize(300, 900); + refreshLayout.Show(); + parent.PackEnd(refreshLayout); + } + } +} From 0a76213215eff84154a8ad9fc0336ddfa7834ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=EA=B7=BC/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/Staff=20Engineer/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Wed, 6 Oct 2021 16:12:51 +0900 Subject: [PATCH 21/76] Remove theme constant on common (#90) --- .../GraphicsView/RefreshIconDrawable.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs index 65a37d7..c8531d9 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs @@ -1,6 +1,5 @@ using Microsoft.Maui.Graphics; using Tizen.UIExtensions.Common.Internal; -using Tizen.UIExtensions.ElmSharp; using GColor = Microsoft.Maui.Graphics.Color; using TSize = Tizen.UIExtensions.Common.Size; @@ -12,9 +11,10 @@ namespace Tizen.UIExtensions.Common.GraphicsView /// public class RefreshIconDrawable : GraphicsViewDrawable, IAnimatable { - float _iconSize = ThemeConstants.RefreshLayout.Resources.IconSize; - float _strokeWidth = ThemeConstants.RefreshLayout.Resources.IconStrokeWidth; - int _rotationAngle = ThemeConstants.RefreshLayout.Resources.IconRotationAngle; + + public const float IconSize = 40f; + public const float StrokeWidth = 4f; + public const int RotationAngle = 360; /// /// Initializes a new instance of the RefreshIconDrawable. @@ -66,9 +66,9 @@ void StartRunningAnimation() { MaterialRefreshViewIconRotate = (int)v; SendInvalidated(); - }, 0, _rotationAngle, easing: Easing.Linear); - var startAngleAnimation = new Animation(v => MaterialRefreshViewIconStartAngle = (int)v, startAngle, startAngle - _rotationAngle, easing: Easing.Linear); - var endAngleAnimation = new Animation(v => MaterialRefreshViewIconEndAngle = (int)v, endAngle, endAngle - _rotationAngle, easing: Easing.Linear); + }, 0, RotationAngle, easing: Easing.Linear); + var startAngleAnimation = new Animation(v => MaterialRefreshViewIconStartAngle = (int)v, startAngle, startAngle - RotationAngle, easing: Easing.Linear); + var endAngleAnimation = new Animation(v => MaterialRefreshViewIconEndAngle = (int)v, endAngle, endAngle - RotationAngle, easing: Easing.Linear); materialRefreshViewIconAngleAnimation.Add(0, 1, rotateAnimation); materialRefreshViewIconAngleAnimation.Add(0, 1, startAngleAnimation); @@ -86,10 +86,10 @@ void AbortRunningAnimation() void DrawRefreshIcon(ICanvas canvas, RectangleF dirtyRect) { canvas.SaveState(); - canvas.StrokeSize = _strokeWidth; + canvas.StrokeSize = StrokeWidth; - var x = dirtyRect.X + _strokeWidth; - var y = dirtyRect.Y + _strokeWidth; + var x = dirtyRect.X + StrokeWidth; + var y = dirtyRect.Y + StrokeWidth; if (View.IsRunning) { DrawRunningIcon(canvas, x, y); @@ -103,16 +103,16 @@ void DrawRefreshIcon(ICanvas canvas, RectangleF dirtyRect) void DrawRunningIcon(ICanvas canvas, float x, float y) { - canvas.Rotate(MaterialRefreshViewIconRotate, x + _iconSize / 2, y + _iconSize / 2); + canvas.Rotate(MaterialRefreshViewIconRotate, x + IconSize / 2, y + IconSize / 2); canvas.StrokeColor = View.Color.ToGraphicsColor(Material.Color.Blue); - canvas.DrawArc(x, y, _iconSize, _iconSize, MaterialRefreshViewIconStartAngle, MaterialRefreshViewIconEndAngle, false, false); + canvas.DrawArc(x, y, IconSize, IconSize, MaterialRefreshViewIconStartAngle, MaterialRefreshViewIconEndAngle, false, false); } void DrawIdleIcon(ICanvas canvas, float x, float y) { - canvas.Rotate(0, x + _iconSize / 2, y + _iconSize / 2); + canvas.Rotate(0, x + IconSize / 2, y + IconSize / 2); canvas.StrokeColor = View.Color.IsDefault ? GColor.FromArgb(Material.Color.LightBlue) : View.Color.MultiplyAlpha(0.5).ToGraphicsColor(Material.Color.LightBlue); - canvas.DrawArc(x, y, _iconSize, _iconSize, 0, _rotationAngle, false, false); + canvas.DrawArc(x, y, IconSize, IconSize, 0, RotationAngle, false, false); } /// @@ -120,7 +120,7 @@ void DrawIdleIcon(ICanvas canvas, float x, float y) /// public override TSize Measure(double availableWidth, double availableHeight) { - var iconSize = (_iconSize + (_strokeWidth * 2)) * DeviceInfo.ScalingFactor; + var iconSize = (IconSize + (StrokeWidth * 2)) * DeviceInfo.ScalingFactor; return new TSize(iconSize, iconSize); } From 848b2fe0a5127ffb9ac880465db6c9f6e8e31500 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Wed, 6 Oct 2021 16:24:55 +0900 Subject: [PATCH 22/76] Fix Image Load from stream --- .../Extensions/ImageExtensions.cs | 10 +++---- src/Tizen.UIExtensions.NUI/Image.cs | 26 +------------------ test/NUIExGallery/TC/EntryTest1.cs | 2 +- 3 files changed, 7 insertions(+), 31 deletions(-) diff --git a/src/Tizen.UIExtensions.NUI/Extensions/ImageExtensions.cs b/src/Tizen.UIExtensions.NUI/Extensions/ImageExtensions.cs index 093c6d6..398aeb1 100644 --- a/src/Tizen.UIExtensions.NUI/Extensions/ImageExtensions.cs +++ b/src/Tizen.UIExtensions.NUI/Extensions/ImageExtensions.cs @@ -4,9 +4,7 @@ using CSize = Tizen.UIExtensions.Common.Size; using System.Threading.Tasks; using System.IO; -using System.Threading; -using Tizen.Applications; -using System.Diagnostics; +using Tizen.NUI; namespace Tizen.UIExtensions.NUI { @@ -132,7 +130,7 @@ void completed(object? sender, EventArgs args) } } - public static async Task LoadAsync(this Image view, Stream stream) + public static async Task LoadAsync(this ImageView view, Stream stream) { if (stream == null) throw new ArgumentNullException(nameof(stream)); @@ -146,7 +144,9 @@ void completed(object? sender, EventArgs args) try { - view.Load(stream); + using var imageBuffer = new EncodedImageBuffer(stream); + using var imageUrl = imageBuffer.GenerateUrl(); + view.ResourceUrl = imageUrl.ToString(); return await tcs.Task; } finally diff --git a/src/Tizen.UIExtensions.NUI/Image.cs b/src/Tizen.UIExtensions.NUI/Image.cs index 1cb69e1..373743e 100644 --- a/src/Tizen.UIExtensions.NUI/Image.cs +++ b/src/Tizen.UIExtensions.NUI/Image.cs @@ -1,6 +1,4 @@ -using System.IO; -using Tizen.NUI; -using Tizen.UIExtensions.Common; +using Tizen.UIExtensions.Common; using CSize = Tizen.UIExtensions.Common.Size; using ImageView = Tizen.NUI.BaseComponents.ImageView; @@ -11,7 +9,6 @@ namespace Tizen.UIExtensions.NUI /// public class Image : ImageView, IMeasurable { - ImageUrl? _cachedImageUrl; /// /// Gets or sets the scaling mode for the image. /// @@ -21,18 +18,6 @@ public Aspect Aspect set => this.SetAspect(value); } - /// - /// Load Image from stream - /// - /// The stream containing images - public void Load(Stream stream) - { - using var imageBuffer = new EncodedImageBuffer(stream); - _cachedImageUrl?.Dispose(); - _cachedImageUrl = imageBuffer.GenerateUrl(); - ResourceUrl = _cachedImageUrl.ToString(); - } - /// /// Measures the size of the control in order to fit it into the available area. /// @@ -43,14 +28,5 @@ CSize IMeasurable.Measure(double availableWidth, double availableHeight) { return this.Measure(availableWidth, availableHeight); } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - _cachedImageUrl?.Dispose(); - } - base.Dispose(disposing); - } } } diff --git a/test/NUIExGallery/TC/EntryTest1.cs b/test/NUIExGallery/TC/EntryTest1.cs index ab2b7dd..4758e9a 100644 --- a/test/NUIExGallery/TC/EntryTest1.cs +++ b/test/NUIExGallery/TC/EntryTest1.cs @@ -124,7 +124,7 @@ public override View Run() }; selectionChange.Clicked += (s, e) => { - searchkey.SelectedTextEnd = (searchkey.SelectedTextStart + searchkey.SelectedTextEnd) / 2; + searchkey.SelectText(searchkey.SelectedTextStart, (searchkey.SelectedTextStart + searchkey.SelectedTextEnd) / 2); }; view.Add(selectionChange); From 70cd5805a6f2119951a7c790635110fb0006bbac Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Thu, 7 Oct 2021 15:04:21 +0900 Subject: [PATCH 23/76] [ElmSharp] Fix refresh status when containing scrollview --- .../RefreshLayout.cs | 34 +++++++++++++++++-- .../TC/RefreshLayoutTest1.cs | 34 +++++++++++++------ 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/src/Tizen.UIExtensions.ElmSharp/RefreshLayout.cs b/src/Tizen.UIExtensions.ElmSharp/RefreshLayout.cs index a6c0a5d..507b0c8 100644 --- a/src/Tizen.UIExtensions.ElmSharp/RefreshLayout.cs +++ b/src/Tizen.UIExtensions.ElmSharp/RefreshLayout.cs @@ -61,6 +61,18 @@ public RefreshLayout(EvasObject parent) : base(parent) /// public event EventHandler? Refreshing; + public RefreshIcon RefreshIcon + { + get + { + return _refreshIcon; + } + set + { + _refreshIcon = value; + } + } + public bool IsRefreshEnabled { get @@ -137,6 +149,8 @@ public EvasObject? Content void OnLayoutUpdate() { + _maximumDistance = _maximumDistance + Geometry.Y; + var iconGeometryX = Geometry.X + (Geometry.Width - _refreshIcon.Geometry.Width) / 2; var iconBottomPadding = (int)_iconSize / 2; _initialIconGeometryY = Geometry.Y - (_refreshIcon.Geometry.Height+ iconBottomPadding); @@ -151,9 +165,13 @@ void OnLayoutUpdate() void OnMoved(GestureLayer.MomentumData moment) { + if (_refreshState == RefreshState.Idle && _isRefreshEnabled) { - _refreshState = RefreshState.Drag; + if (IsEdgeScrolling()) + { + _refreshState = RefreshState.Drag; + } } if (_refreshState == RefreshState.Drag) @@ -165,6 +183,18 @@ void OnMoved(GestureLayer.MomentumData moment) } } + bool IsEdgeScrolling() + { + if (_content is ScrollView scrollView) + { + if (scrollView.ScrollBound.Y != 0) + { + return false; + } + } + return true; + } + void OnEnded(GestureLayer.MomentumData moment) { if (_refreshState != RefreshState.Drag) @@ -186,7 +216,7 @@ void OnEnded(GestureLayer.MomentumData moment) void BeginRefreshing(bool isPulledRefresh) { var movedDistance = GetMovedDistance(); - var refreshDistance = _refreshIcon.Geometry.Height + _maximumDistance; + var refreshDistance = _maximumDistance - Geometry.Y + _refreshIcon.Geometry.Height; var contentDistanceDiff = refreshDistance - movedDistance; var _refreshStartAnimation = new Animation(v => _contentLayout.Move(Geometry.X, Geometry.Y + movedDistance + (int)v), 0, contentDistanceDiff, Easing.Linear); _refreshStartAnimation.Commit(this, "RefreshBegin", length: _animationLength); diff --git a/test/ElmSharpExGallery/TC/RefreshLayoutTest1.cs b/test/ElmSharpExGallery/TC/RefreshLayoutTest1.cs index 992baee..5fa5853 100644 --- a/test/ElmSharpExGallery/TC/RefreshLayoutTest1.cs +++ b/test/ElmSharpExGallery/TC/RefreshLayoutTest1.cs @@ -20,7 +20,8 @@ public override void Run(ElmSharp.Box parent) AlignmentY = -1, AlignmentX = -1, }; - var box = new Tizen.UIExtensions.ElmSharp.Box(parent) + + var contentBox = new Box(parent) { WeightX = 1, WeightY = 1, @@ -28,7 +29,7 @@ public override void Run(ElmSharp.Box parent) AlignmentX = -1, BackgroundColor = ElmSharp.Color.Gray }; - var changeIconButton = new Tizen.UIExtensions.ElmSharp.Button(parent) + var changeIconButton = new Button(parent) { Text = "Change Icon Color", WeightX = 1, @@ -46,7 +47,7 @@ public override void Run(ElmSharp.Box parent) refreshLayout.RefreshIconColor = Color.Default; }; changeIconButton.Show(); - var statusLabel = new Tizen.UIExtensions.ElmSharp.Label(parent) + var statusLabel = new Label(parent) { Text = "Idle", WeightX = 1, @@ -63,7 +64,7 @@ public override void Run(ElmSharp.Box parent) statusLabel.Resize(300, 100); statusLabel.Show(); - var changeRefreshingButton = new Tizen.UIExtensions.ElmSharp.Button(parent) + var changeRefreshingButton = new Button(parent) { Text = $"Start Refreshing", WeightX = 1, @@ -79,12 +80,25 @@ public override void Run(ElmSharp.Box parent) }; changeRefreshingButton.Show(); - box.PackEnd(statusLabel); - box.PackEnd(changeIconButton); - box.PackEnd(changeRefreshingButton); - box.Show(); + contentBox.PackEnd(statusLabel); + contentBox.PackEnd(changeIconButton); + contentBox.PackEnd(changeRefreshingButton); + contentBox.Show(); + + var scrollView = new ScrollView(parent) + { + WeightX = 1, + WeightY = 1, + AlignmentY = -1, + AlignmentX = -1, + HorizontalScrollBarVisibility = ScrollBarVisibility.Default, + }; + + scrollView.SetScrollCanvas(contentBox); + scrollView.SetContentSize(720, 2000); + scrollView.Show(); - refreshLayout.Content = box; + refreshLayout.Content = scrollView; refreshLayout.IsRefreshEnabled = true; refreshLayout.Refreshing += async (s, e) => { @@ -94,8 +108,6 @@ public override void Run(ElmSharp.Box parent) statusLabel.Text = "Refreshed"; }; - - refreshLayout.Resize(300, 900); refreshLayout.Show(); parent.PackEnd(refreshLayout); } From 0ab63698156350f09ad1761c63030852a9a25de4 Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Fri, 8 Oct 2021 13:07:29 +0900 Subject: [PATCH 24/76] [ElmSharp] Fix Refreshlayout and refresh logic --- .../GraphicsView/RefreshIconDrawable.cs | 38 ++++++- .../GraphicsView/RefreshIcon.cs | 12 +++ .../RefreshLayout.cs | 99 ++++++++----------- .../ThemeConstants.cs | 2 +- test/ElmSharpExGallery/TC/DrawableTest.cs | 14 +++ .../TC/RefreshLayoutTest1.cs | 70 +++++++++---- 6 files changed, 152 insertions(+), 83 deletions(-) diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs index c8531d9..1868647 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs @@ -11,8 +11,11 @@ namespace Tizen.UIExtensions.Common.GraphicsView /// public class RefreshIconDrawable : GraphicsViewDrawable, IAnimatable { + Color _backgroundColor; - public const float IconSize = 40f; + public const float IconPaddingSize = 10f; + public const float IconArcSize = 40f; + public const float IconSize = IconArcSize + IconPaddingSize * 2; public const float StrokeWidth = 4f; public const int RotationAngle = 360; @@ -32,6 +35,19 @@ public RefreshIconDrawable(IRefreshIcon view) float MaterialRefreshViewIconEndAngle { get; set; } + public Color BackgroundColor + { + get + { + return _backgroundColor; + } + set + { + _backgroundColor = value; + SendInvalidated(); + } + } + /// /// Implementation of the IDrawable.Draw() method. /// This method defines how to draw a refresh icon. @@ -90,6 +106,8 @@ void DrawRefreshIcon(ICanvas canvas, RectangleF dirtyRect) var x = dirtyRect.X + StrokeWidth; var y = dirtyRect.Y + StrokeWidth; + + DrawBackground(canvas, x, y); if (View.IsRunning) { DrawRunningIcon(canvas, x, y); @@ -101,18 +119,28 @@ void DrawRefreshIcon(ICanvas canvas, RectangleF dirtyRect) canvas.RestoreState(); } + void DrawBackground(ICanvas canvas, float x, float y) + { + canvas.FillColor = _backgroundColor.ToGraphicsColor(Material.Color.White); + canvas.FillEllipse(x, y, IconSize, IconSize); + } + void DrawRunningIcon(ICanvas canvas, float x, float y) { - canvas.Rotate(MaterialRefreshViewIconRotate, x + IconSize / 2, y + IconSize / 2); + var arcX = x + IconPaddingSize; + var arcY = y + IconPaddingSize; + canvas.Rotate(MaterialRefreshViewIconRotate, arcX + IconArcSize / 2, arcY + IconArcSize / 2); canvas.StrokeColor = View.Color.ToGraphicsColor(Material.Color.Blue); - canvas.DrawArc(x, y, IconSize, IconSize, MaterialRefreshViewIconStartAngle, MaterialRefreshViewIconEndAngle, false, false); + canvas.DrawArc(arcX , arcY, IconArcSize, IconArcSize, MaterialRefreshViewIconStartAngle, MaterialRefreshViewIconEndAngle, false, false); } void DrawIdleIcon(ICanvas canvas, float x, float y) { - canvas.Rotate(0, x + IconSize / 2, y + IconSize / 2); + var arcX = x + IconPaddingSize; + var arcY = y + IconPaddingSize; + canvas.Rotate(0, arcX + IconArcSize / 2, arcY + IconArcSize / 2); canvas.StrokeColor = View.Color.IsDefault ? GColor.FromArgb(Material.Color.LightBlue) : View.Color.MultiplyAlpha(0.5).ToGraphicsColor(Material.Color.LightBlue); - canvas.DrawArc(x, y, IconSize, IconSize, 0, RotationAngle, false, false); + canvas.DrawArc(arcX, arcY, IconArcSize, IconArcSize, 0, RotationAngle, false, false); } /// diff --git a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs index e9472e8..dca0d91 100644 --- a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs +++ b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs @@ -32,6 +32,18 @@ public RefreshIcon(EvasObject parent) : base(parent) Resize((int)iconSize, (int)iconSize); } + /// + /// Gets or sets the background of RefreshIcon. + /// + public new Common.Color BackgroundColor + { + get => _drawable.BackgroundColor; + set + { + _drawable.BackgroundColor = value; + } + } + /// /// Gets or sets the value indicating if the RefreshIcon is running. /// diff --git a/src/Tizen.UIExtensions.ElmSharp/RefreshLayout.cs b/src/Tizen.UIExtensions.ElmSharp/RefreshLayout.cs index 507b0c8..e02d19c 100644 --- a/src/Tizen.UIExtensions.ElmSharp/RefreshLayout.cs +++ b/src/Tizen.UIExtensions.ElmSharp/RefreshLayout.cs @@ -25,10 +25,10 @@ public class RefreshLayout : Box, IAnimatable bool _isRefreshing; bool _isRefreshEnabled; int _initialIconGeometryY; + int _maximumDistance; + float _iconSize; - int _maximumDistance = ThemeConstants.RefreshLayout.Resources.RefreshDistance; int _minimumSize = ThemeConstants.RefreshLayout.Resources.MinimumLayoutSize; - float _iconSize = ThemeConstants.RefreshLayout.Resources.IconSize; uint _animationLength = ThemeConstants.RefreshLayout.Resources.RefreshAnimationLength; /// @@ -50,8 +50,8 @@ public RefreshLayout(EvasObject parent) : base(parent) _gestureLayer.SetMomentumCallback(GestureLayer.GestureState.End, OnEnded); _gestureLayer.SetMomentumCallback(GestureLayer.GestureState.Abort, OnEnded); - this.PackEnd(_refreshIcon); this.PackEnd(_contentLayout); + this.PackEnd(_refreshIcon); SetLayoutCallback(OnLayoutUpdate); } @@ -61,18 +61,14 @@ public RefreshLayout(EvasObject parent) : base(parent) /// public event EventHandler? Refreshing; - public RefreshIcon RefreshIcon - { - get - { - return _refreshIcon; - } - set - { - _refreshIcon = value; - } - } + /// + /// Gets or sets the delegate to decide if the RefreshLayout is at the edge of scrolling when it has scrolling contents. + /// + public Func? IsEdgeScrolling { get; set; } + /// + /// Gets or sets if the RefreshLayout is in its refresh state. + /// public bool IsRefreshEnabled { get @@ -115,7 +111,7 @@ public bool IsRefreshing /// /// Gets or sets the color of the refresh icon. /// - public Common.Color RefreshIconColor + public Common.Color IconColor { get { @@ -127,6 +123,21 @@ public Common.Color RefreshIconColor } } + /// + /// Gets or sets the background color of the refresh icon. + /// + public Common.Color IconBackgroundColor + { + get + { + return _refreshIcon.BackgroundColor; + } + set + { + _refreshIcon.BackgroundColor = value; + } + } + /// /// Gets or sets the content of the RefreshLayout. /// @@ -149,13 +160,12 @@ public EvasObject? Content void OnLayoutUpdate() { - _maximumDistance = _maximumDistance + Geometry.Y; + _iconSize = _refreshIcon.Geometry.Height; + _maximumDistance = (int)_iconSize + Geometry.Y; var iconGeometryX = Geometry.X + (Geometry.Width - _refreshIcon.Geometry.Width) / 2; - var iconBottomPadding = (int)_iconSize / 2; - _initialIconGeometryY = Geometry.Y - (_refreshIcon.Geometry.Height+ iconBottomPadding); - var iconHeight = _refreshIcon.Geometry.Height + iconBottomPadding; - _refreshIcon.Geometry = new Rect(iconGeometryX, _initialIconGeometryY, _refreshIcon.Geometry.Width, iconHeight); + _initialIconGeometryY = Geometry.Y - (int)_iconSize; + _refreshIcon.Geometry = new Rect(iconGeometryX, _initialIconGeometryY, _refreshIcon.Geometry.Width, _refreshIcon.Geometry.Height); _contentLayout.Geometry = Geometry; if (_content != null) { @@ -168,9 +178,11 @@ void OnMoved(GestureLayer.MomentumData moment) if (_refreshState == RefreshState.Idle && _isRefreshEnabled) { - if (IsEdgeScrolling()) + var isEdge = IsEdgeScrolling?.Invoke() ?? true; + if (isEdge) { _refreshState = RefreshState.Drag; + _refreshIcon.IsPulling = true; } } @@ -183,18 +195,6 @@ void OnMoved(GestureLayer.MomentumData moment) } } - bool IsEdgeScrolling() - { - if (_content is ScrollView scrollView) - { - if (scrollView.ScrollBound.Y != 0) - { - return false; - } - } - return true; - } - void OnEnded(GestureLayer.MomentumData moment) { if (_refreshState != RefreshState.Drag) @@ -211,23 +211,15 @@ void OnEnded(GestureLayer.MomentumData moment) { ResetRefreshing(); } + _refreshIcon.IsPulling = false; } void BeginRefreshing(bool isPulledRefresh) { - var movedDistance = GetMovedDistance(); - var refreshDistance = _maximumDistance - Geometry.Y + _refreshIcon.Geometry.Height; - var contentDistanceDiff = refreshDistance - movedDistance; - var _refreshStartAnimation = new Animation(v => _contentLayout.Move(Geometry.X, Geometry.Y + movedDistance + (int)v), 0, contentDistanceDiff, Easing.Linear); - _refreshStartAnimation.Commit(this, "RefreshBegin", length: _animationLength); - - if (!isPulledRefresh) - { - var currentIconGeometryY = Geometry.Y + _refreshIcon.Geometry.Y; - var iconDistanceDiff = _maximumDistance - currentIconGeometryY; - var _refreshIconBeginAnimation = new Animation(v => _refreshIcon.Move(_refreshIcon.Geometry.X, currentIconGeometryY + (int)v), 0, iconDistanceDiff, Easing.Linear); - _refreshIconBeginAnimation.Commit(this, "RefreshIconBegin", length: _animationLength); - } + var currentIconGeometryY = _refreshIcon.Geometry.Y; + var contentDistanceDiff = _maximumDistance - currentIconGeometryY; + var _refreshIconBeginAnimation = new Animation(v => _refreshIcon.Move(_refreshIcon.Geometry.X, currentIconGeometryY + (int)v), 0, contentDistanceDiff, Easing.Linear); + _refreshIconBeginAnimation.Commit(this, "RefreshIconBegin", length: _animationLength); _refreshState = RefreshState.Loading; _isRefreshing = true; @@ -237,10 +229,7 @@ void BeginRefreshing(bool isPulledRefresh) void ResetRefreshing() { - var movedDistance = GetMovedDistance(); - var _refreshResetAnimation = new Animation(v => _contentLayout.Move(Geometry.X, Geometry.Y + movedDistance - (int)v), 0, movedDistance, Easing.Linear); - _refreshResetAnimation.Commit(this, "RefreshReset", length: _animationLength); - + var movedDistance = _refreshIcon.Geometry.Y - _initialIconGeometryY; var currentIconGeometryY = _refreshIcon.Geometry.Y; var _refreshIconResetAnimation = new Animation(v => _refreshIcon.Move(_refreshIcon.Geometry.X, currentIconGeometryY - (int)v), 0, movedDistance, Easing.Linear); _refreshIconResetAnimation.Commit(this, "RefreshIconReset", length: _animationLength); @@ -249,17 +238,11 @@ void ResetRefreshing() _refreshIcon.IsRunning = false; } - int GetMovedDistance() - { - return _contentLayout.Geometry.Y - Geometry.Y; - } - void MoveLayout(int distance) { var iconDistance = _initialIconGeometryY + distance; if (iconDistance > _maximumDistance) { - iconDistance = _maximumDistance; _shouldRefresh = true; } else @@ -267,7 +250,9 @@ void MoveLayout(int distance) _shouldRefresh = false; } _refreshIcon.Move(_refreshIcon.Geometry.X, iconDistance); - _contentLayout.Move(_contentLayout.Geometry.X, Geometry.Y + distance); + var totalDistance = Math.Abs(_initialIconGeometryY) + _maximumDistance - Geometry.Y; + var pullDistance = (float)distance / totalDistance; + _refreshIcon.PullDistance = pullDistance >= 1f ? 1f : pullDistance; } void IAnimatable.BatchBegin() {} diff --git a/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs b/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs index 5e7b1b2..7f92565 100644 --- a/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs +++ b/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs @@ -515,7 +515,7 @@ public class RefreshLayout { public class Resources { - public const float IconSize = 40f; + public const float IconSize = 60f; public const float IconStrokeWidth = 4f; public const int IconRotationAngle = 360; public const int MinimumLayoutSize = 200; diff --git a/test/ElmSharpExGallery/TC/DrawableTest.cs b/test/ElmSharpExGallery/TC/DrawableTest.cs index 768e16c..461081b 100644 --- a/test/ElmSharpExGallery/TC/DrawableTest.cs +++ b/test/ElmSharpExGallery/TC/DrawableTest.cs @@ -85,6 +85,20 @@ public override void Run(ElmSharp.Box parent) colorChangeButton.Show(); layout.PackEnd(colorChangeButton); + var backgroundColorChangeButton = new Button(parent) + { + Text = "Change Background", + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1 + }; + backgroundColorChangeButton.Clicked += (s, e) => + { + refreshIcon.BackgroundColor = refreshIcon.BackgroundColor == Color.Yellow ? Color.White : Color.Yellow; + }; + backgroundColorChangeButton.Show(); + layout.PackEnd(backgroundColorChangeButton); + var simulateButton = new Button(parent) { Text = "Simulate Animation", diff --git a/test/ElmSharpExGallery/TC/RefreshLayoutTest1.cs b/test/ElmSharpExGallery/TC/RefreshLayoutTest1.cs index 5fa5853..963763c 100644 --- a/test/ElmSharpExGallery/TC/RefreshLayoutTest1.cs +++ b/test/ElmSharpExGallery/TC/RefreshLayoutTest1.cs @@ -29,24 +29,7 @@ public override void Run(ElmSharp.Box parent) AlignmentX = -1, BackgroundColor = ElmSharp.Color.Gray }; - var changeIconButton = new Button(parent) - { - Text = "Change Icon Color", - WeightX = 1, - WeightY = 1, - AlignmentX = -1, - AlignmentY = -1, - }; - changeIconButton.Move(100, 300); - changeIconButton.Resize(400, 100); - changeIconButton.Clicked += (s, e) => - { - if (refreshLayout.RefreshIconColor == Color.Default) - refreshLayout.RefreshIconColor = Color.Red; - else - refreshLayout.RefreshIconColor = Color.Default; - }; - changeIconButton.Show(); + var statusLabel = new Label(parent) { Text = "Idle", @@ -64,6 +47,44 @@ public override void Run(ElmSharp.Box parent) statusLabel.Resize(300, 100); statusLabel.Show(); + var changeIconColorButton = new Button(parent) + { + Text = "Change Icon Color", + WeightX = 1, + WeightY = 1, + AlignmentX = -1, + AlignmentY = -1, + }; + changeIconColorButton.Move(100, 300); + changeIconColorButton.Resize(400, 100); + changeIconColorButton.Clicked += (s, e) => + { + if (refreshLayout.IconColor == Color.Default) + refreshLayout.IconColor = Color.Red; + else + refreshLayout.IconColor = Color.Default; + }; + changeIconColorButton.Show(); + + var changeIconBackgroundColorButton = new Button(parent) + { + Text = "Change BackgroundColor", + WeightX = 1, + WeightY = 1, + AlignmentX = -1, + AlignmentY = -1, + }; + changeIconBackgroundColorButton.Move(100, 400); + changeIconBackgroundColorButton.Resize(400, 100); + changeIconBackgroundColorButton.Clicked += (s, e) => + { + if (refreshLayout.IconBackgroundColor == Color.Yellow) + refreshLayout.IconBackgroundColor = Color.White; + else + refreshLayout.IconBackgroundColor = Color.Yellow; + }; + changeIconBackgroundColorButton.Show(); + var changeRefreshingButton = new Button(parent) { Text = $"Start Refreshing", @@ -72,7 +93,7 @@ public override void Run(ElmSharp.Box parent) AlignmentX = -1, AlignmentY = -1, }; - changeRefreshingButton.Move(100, 400); + changeRefreshingButton.Move(100, 500); changeRefreshingButton.Resize(400, 100); changeRefreshingButton.Clicked += (s, e) => { @@ -81,7 +102,8 @@ public override void Run(ElmSharp.Box parent) changeRefreshingButton.Show(); contentBox.PackEnd(statusLabel); - contentBox.PackEnd(changeIconButton); + contentBox.PackEnd(changeIconColorButton); + contentBox.PackEnd(changeIconBackgroundColorButton); contentBox.PackEnd(changeRefreshingButton); contentBox.Show(); @@ -100,6 +122,14 @@ public override void Run(ElmSharp.Box parent) refreshLayout.Content = scrollView; refreshLayout.IsRefreshEnabled = true; + refreshLayout.IsEdgeScrolling = () => + { + if (scrollView.ScrollBound.Y == 0) + { + return true; + } + return false; + }; refreshLayout.Refreshing += async (s, e) => { statusLabel.Text = "Refreshing"; From 2e5cc0fe14f9797445c03edb78f0f0d3ac4d0c5a Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Tue, 12 Oct 2021 13:31:15 +0900 Subject: [PATCH 25/76] [ElmSharp] Add base GraphicsView --- .../GraphicsView/RefreshIconDrawable.cs | 4 +- .../GraphicsView/GraphicsView.cs | 129 ++++++++++++++++++ .../GraphicsView/RefreshIcon.cs | 43 ++---- .../RefreshLayout.cs | 1 + test/ElmSharpExGallery/TC/DrawableTest.cs | 123 ++++++++++++++++- 5 files changed, 265 insertions(+), 35 deletions(-) create mode 100644 src/Tizen.UIExtensions.ElmSharp/GraphicsView/GraphicsView.cs diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs index 1868647..51c722e 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs @@ -11,14 +11,14 @@ namespace Tizen.UIExtensions.Common.GraphicsView /// public class RefreshIconDrawable : GraphicsViewDrawable, IAnimatable { - Color _backgroundColor; - public const float IconPaddingSize = 10f; public const float IconArcSize = 40f; public const float IconSize = IconArcSize + IconPaddingSize * 2; public const float StrokeWidth = 4f; public const int RotationAngle = 360; + Color _backgroundColor; + /// /// Initializes a new instance of the RefreshIconDrawable. /// diff --git a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/GraphicsView.cs b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/GraphicsView.cs new file mode 100644 index 0000000..df84d56 --- /dev/null +++ b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/GraphicsView.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using Microsoft.Maui.Graphics.Skia.Views; +using Tizen.UIExtensions.Common; +using Tizen.UIExtensions.Common.GraphicsView; +using GestureLayer = ElmSharp.GestureLayer; +using GPoint = Microsoft.Maui.Graphics.Point; + +namespace Tizen.UIExtensions.ElmSharp.GraphicsView +{ + public abstract class GraphicsView : SkiaGraphicsView, IMeasurable where TDrawable : GraphicsViewDrawable + { + Dictionary _propertyBag = new Dictionary(); + TDrawable? _drawable; + bool _isEnabled = true; + GestureLayer _gestureLayer; + + protected GraphicsView(global::ElmSharp.EvasObject parent) : base(parent) + { + _gestureLayer = new GestureLayer(parent); + _gestureLayer.Attach(this); + _gestureLayer.SetTapCallback(GestureLayer.GestureType.Tap, GestureLayer.GestureState.Start, OnTapStart); + _gestureLayer.SetMomentumCallback(GestureLayer.GestureState.Move, OnTapMove); + _gestureLayer.SetMomentumCallback(GestureLayer.GestureState.End, OnTapEnd); + _gestureLayer.SetMomentumCallback(GestureLayer.GestureState.Abort, OnTapEnd); + + Focused += OnFocused; + Unfocused += OnUnfocused; + } + + public override bool IsEnabled + { + get => _isEnabled; + set + { + IsEnabled = _isEnabled = value; + Invalidate(); + } + } + + public virtual Size Measure(double availableWidth, double availableHeight) + { + return Drawable?.Measure(availableWidth, availableHeight) ?? new Size(availableWidth, availableHeight); + } + + protected void SetProperty(string name, T value) + { + _propertyBag[name] = value!; + Invalidate(); + } + +#nullable disable + protected T GetProperty(string name) + { + if (_propertyBag.TryGetValue(name, out object value)) + { + return (T)value; + } + return default; + } +#nullable enable + + protected new TDrawable? Drawable + { + get => _drawable; + set + { + if (_drawable != value) + { + if (_drawable != null) + { + _drawable.Invalidated -= OnInvalidated; + } + + base.Drawable = _drawable = value; + + if (value != null) + { + value.Invalidated += OnInvalidated; + } + } + } + } + + protected virtual void OnUnfocused(object? sender, EventArgs e) + { + Drawable?.OnUnfocused(); + } + + protected virtual void OnFocused(object? sender, EventArgs e) + { + Drawable?.OnFocused(); + } + + protected virtual void OnTapStart(GestureLayer.TapData e) + { + if (!IsEnabled) + return; + + Drawable?.OnTouchDown(GetScaledGraphicsPoint(e.X, e.Y)); + } + + protected virtual void OnTapMove(GestureLayer.MomentumData e) + { + if (!IsEnabled) + return; + + Drawable?.OnTouchMove(GetScaledGraphicsPoint(e.X2, e.Y2)); + } + + protected virtual void OnTapEnd(GestureLayer.MomentumData e) + { + if (!IsEnabled) + return; + + Drawable?.OnTouchUp(GetScaledGraphicsPoint(e.X2, e.Y2)); + } + + GPoint GetScaledGraphicsPoint(int x, int y) + { + return new GPoint(x / DeviceInfo.ScalingFactor, y / DeviceInfo.ScalingFactor); + } + + void OnInvalidated(object? sender, EventArgs e) + { + Invalidate(); + } + } +} diff --git a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs index dca0d91..7a7a67e 100644 --- a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs +++ b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs @@ -1,21 +1,16 @@ using System; -using Microsoft.Maui.Graphics.Skia.Views; using Tizen.UIExtensions.Common.GraphicsView; using ElmSharp; using DeviceInfo = Tizen.UIExtensions.Common.DeviceInfo; -namespace Tizen.UIExtensions.ElmSharp +namespace Tizen.UIExtensions.ElmSharp.GraphicsView { /// /// A visual control used to indicate that refreshing is ongoing. /// - public class RefreshIcon : SkiaGraphicsView, IRefreshIcon + public class RefreshIcon : GraphicsView, IRefreshIcon { RefreshIconDrawable _drawable; - bool _isRunning; - Common.Color _color; - bool _isPulling; - float _pullDistance; float _iconSize = ThemeConstants.RefreshLayout.Resources.IconSize; float _strokeWidth = ThemeConstants.RefreshLayout.Resources.IconStrokeWidth; @@ -25,7 +20,6 @@ public class RefreshIcon : SkiaGraphicsView, IRefreshIcon public RefreshIcon(EvasObject parent) : base(parent) { _drawable = new RefreshIconDrawable(this); - _drawable.Invalidated += OnRefreshIconInvalidated; Drawable = _drawable; var iconSize = (_iconSize + (_strokeWidth * 2)) * DeviceInfo.ScalingFactor; @@ -41,6 +35,7 @@ public RefreshIcon(EvasObject parent) : base(parent) set { _drawable.BackgroundColor = value; + Invalidate(); } } @@ -49,10 +44,10 @@ public RefreshIcon(EvasObject parent) : base(parent) /// public bool IsRunning { - get => _isRunning; + get => GetProperty(nameof(IsRunning)); set { - _isRunning = value; + SetProperty(nameof(IsRunning), value); _drawable.UpdateRunningAnimation(value); } } @@ -62,12 +57,8 @@ public bool IsRunning /// public bool IsPulling { - get => _isPulling; - set - { - _isPulling = value; - Invalidate(); - } + get => GetProperty(nameof(IsPulling)); + set => SetProperty(nameof(IsPulling), value); } /// @@ -75,12 +66,8 @@ public bool IsPulling /// public new Common.Color Color { - get => _color; - set - { - _color = value; - Invalidate(); - } + get => GetProperty(nameof(Color)); + set => SetProperty(nameof(Color), value); } /// @@ -88,20 +75,14 @@ public bool IsPulling /// public float PullDistance { - get => _pullDistance; + get => GetProperty(nameof(PullDistance)); set { if (value > 1.0) - _pullDistance = 1.0f; + SetProperty(nameof(PullDistance), 1.0f); else - _pullDistance = value; - Invalidate(); + SetProperty(nameof(PullDistance), value); } } - - void OnRefreshIconInvalidated(object? sender, EventArgs e) - { - Invalidate(); - } } } diff --git a/src/Tizen.UIExtensions.ElmSharp/RefreshLayout.cs b/src/Tizen.UIExtensions.ElmSharp/RefreshLayout.cs index e02d19c..67f112b 100644 --- a/src/Tizen.UIExtensions.ElmSharp/RefreshLayout.cs +++ b/src/Tizen.UIExtensions.ElmSharp/RefreshLayout.cs @@ -1,6 +1,7 @@ using System; using ElmSharp; using Tizen.UIExtensions.Common.Internal; +using Tizen.UIExtensions.ElmSharp.GraphicsView; namespace Tizen.UIExtensions.ElmSharp { diff --git a/test/ElmSharpExGallery/TC/DrawableTest.cs b/test/ElmSharpExGallery/TC/DrawableTest.cs index 461081b..5f09768 100644 --- a/test/ElmSharpExGallery/TC/DrawableTest.cs +++ b/test/ElmSharpExGallery/TC/DrawableTest.cs @@ -1,14 +1,111 @@ +using System; +using System.ComponentModel; using System.Threading.Tasks; using Tizen.UIExtensions.Common; +using Tizen.UIExtensions.Common.GraphicsView; using Tizen.UIExtensions.Common.Internal; -using Tizen.UIExtensions.ElmSharp; +using Tizen.UIExtensions.ElmSharp.GraphicsView; using Button = Tizen.UIExtensions.ElmSharp.Button; using Label = Tizen.UIExtensions.ElmSharp.Label; +using GPoint = Microsoft.Maui.Graphics.Point; namespace ElmSharpExGallery.TC { public class DrawableTest : TestCaseBase, IAnimatable { + public class SampleRefreshIcon : RefreshIcon + { + public SampleRefreshIcon(ElmSharp.EvasObject parent) : base(parent) + { + Drawable = new SampleDrawable(this); + (Drawable as SampleDrawable).PropertyChanged += SampleRefreshIcon_PropertyChanged; + // Set `AllowFocus` to true to allow the widget to have focus. + AllowFocus(true); + } + + public event EventHandler PointChanged; + + public event EventHandler FocusChanged; + + public GPoint TouchPoint { get; set; } + + public bool IsDrawableFocused{ get; set; } + + private void SampleRefreshIcon_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "TouchPoint") + { + TouchPoint = (Drawable as SampleDrawable).TouchPoint; + PointChanged?.Invoke(this, EventArgs.Empty); + } + else if(e.PropertyName == "IsFocused") + { + IsDrawableFocused = (Drawable as SampleDrawable).IsFocused; + FocusChanged?.Invoke(this, EventArgs.Empty); + } + } + } + + public class SampleDrawable : RefreshIconDrawable, INotifyPropertyChanged + { + public SampleDrawable(IRefreshIcon view) : base(view) { } + + GPoint _touchPoint; + bool _isFocused; + + public GPoint TouchPoint + { + get => _touchPoint; + set + { + _touchPoint = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TouchPoint")); + } + } + + public bool IsFocused + { + get => _isFocused; + set + { + _isFocused = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsFocused")); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + public override void OnTouchDown(GPoint point) + { + base.OnTouchDown(point); + TouchPoint = new GPoint(point.X, point.Y); + } + + public override void OnTouchUp(GPoint point) + { + base.OnTouchUp(point); + TouchPoint = new GPoint(point.X, point.Y); + } + + public override void OnTouchMove(GPoint point) + { + base.OnTouchMove(point); + TouchPoint = new GPoint(point.X, point.Y); + } + + public override void OnFocused() + { + base.OnFocused(); + IsFocused = true; + } + + public override void OnUnfocused() + { + base.OnUnfocused(); + IsFocused = false; + } + } + public override string TestName => "Drawable Test"; public override string TestDescription => "Test Drawable"; @@ -32,13 +129,35 @@ public override void Run(ElmSharp.Box parent) }; label.Show(); layout.PackEnd(label); - var refreshIcon = new RefreshIcon(parent) + var refreshIcon = new SampleRefreshIcon(parent) { AlignmentX = -1, AlignmentY = -1, WeightX = 1, WeightY = 1 }; + var isFocusedLabel = new Label(parent) + { + Text = $"Is View Focused: {refreshIcon.IsDrawableFocused}", + FontSize = 20 + }; + refreshIcon.FocusChanged += (s, e) => + { + isFocusedLabel.Text = $"Is View Focused: {refreshIcon.IsDrawableFocused}"; + }; + isFocusedLabel.Show(); + layout.PackEnd(isFocusedLabel); + var pointLabel = new Label(parent) + { + Text = $"Touch Point: (X={(int)refreshIcon.TouchPoint.X}, Y={(int)refreshIcon.TouchPoint.Y})", + FontSize = 20 + }; + refreshIcon.PointChanged += (s, e) => + { + pointLabel.Text = $"Touch Point: (X={(int)refreshIcon.TouchPoint.X}, Y={(int)refreshIcon.TouchPoint.Y})"; + }; + pointLabel.Show(); + layout.PackEnd(pointLabel); refreshIcon.Show(); layout.PackEnd(refreshIcon); From e74203917a07cf3bada5d4c8129caa362776dd55 Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Tue, 12 Oct 2021 17:38:31 +0900 Subject: [PATCH 26/76] [ElmSharp] Update review comments and add doxygen --- .../GraphicsView/GraphicsView.cs | 58 ++++++++++++++----- .../GraphicsView/RefreshIcon.cs | 24 +++++--- 2 files changed, 58 insertions(+), 24 deletions(-) diff --git a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/GraphicsView.cs b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/GraphicsView.cs index df84d56..6a81542 100644 --- a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/GraphicsView.cs +++ b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/GraphicsView.cs @@ -3,11 +3,16 @@ using Microsoft.Maui.Graphics.Skia.Views; using Tizen.UIExtensions.Common; using Tizen.UIExtensions.Common.GraphicsView; +using EvasObject = ElmSharp.EvasObject; using GestureLayer = ElmSharp.GestureLayer; using GPoint = Microsoft.Maui.Graphics.Point; namespace Tizen.UIExtensions.ElmSharp.GraphicsView { + /// + /// A base class for Views that inherits `SkiaGraphicsView`. + /// It helps Views covering `GraphicsViewDrawable` methods. + /// public abstract class GraphicsView : SkiaGraphicsView, IMeasurable where TDrawable : GraphicsViewDrawable { Dictionary _propertyBag = new Dictionary(); @@ -15,29 +20,38 @@ public abstract class GraphicsView : SkiaGraphicsView, IMeasurable wh bool _isEnabled = true; GestureLayer _gestureLayer; - protected GraphicsView(global::ElmSharp.EvasObject parent) : base(parent) + protected GraphicsView(EvasObject parent) : base(parent) { _gestureLayer = new GestureLayer(parent); _gestureLayer.Attach(this); - _gestureLayer.SetTapCallback(GestureLayer.GestureType.Tap, GestureLayer.GestureState.Start, OnTapStart); - _gestureLayer.SetMomentumCallback(GestureLayer.GestureState.Move, OnTapMove); - _gestureLayer.SetMomentumCallback(GestureLayer.GestureState.End, OnTapEnd); - _gestureLayer.SetMomentumCallback(GestureLayer.GestureState.Abort, OnTapEnd); + _gestureLayer.SetTapCallback(GestureLayer.GestureType.Tap, GestureLayer.GestureState.Start, OnTapStartCallback); + _gestureLayer.SetMomentumCallback(GestureLayer.GestureState.Move, OnTapMoveCallback); + _gestureLayer.SetMomentumCallback(GestureLayer.GestureState.End, OnTapEndCallback); + _gestureLayer.SetMomentumCallback(GestureLayer.GestureState.Abort, OnTapEndCallback); Focused += OnFocused; Unfocused += OnUnfocused; } + /// + /// Gets or sets the state of the view, which might be enabled or disabled. + /// public override bool IsEnabled { get => _isEnabled; set { - IsEnabled = _isEnabled = value; - Invalidate(); + if (value != _isEnabled) + { + IsEnabled = _isEnabled = value; + Invalidate(); + } } } + /// + /// Measures the size of the view based on a drawable. + /// public virtual Size Measure(double availableWidth, double availableHeight) { return Drawable?.Measure(availableWidth, availableHeight) ?? new Size(availableWidth, availableHeight); @@ -60,7 +74,7 @@ protected T GetProperty(string name) } #nullable enable - protected new TDrawable? Drawable + public new TDrawable? Drawable { get => _drawable; set @@ -92,28 +106,40 @@ protected virtual void OnFocused(object? sender, EventArgs e) Drawable?.OnFocused(); } - protected virtual void OnTapStart(GestureLayer.TapData e) + void OnTapStartCallback(GestureLayer.TapData e) { if (!IsEnabled) return; - - Drawable?.OnTouchDown(GetScaledGraphicsPoint(e.X, e.Y)); + OnTapStart(GetScaledGraphicsPoint(e.X, e.Y)); } - protected virtual void OnTapMove(GestureLayer.MomentumData e) + void OnTapMoveCallback(GestureLayer.MomentumData e) { if (!IsEnabled) return; - - Drawable?.OnTouchMove(GetScaledGraphicsPoint(e.X2, e.Y2)); + OnTapMove(GetScaledGraphicsPoint(e.X2, e.Y2)); } - protected virtual void OnTapEnd(GestureLayer.MomentumData e) + void OnTapEndCallback(GestureLayer.MomentumData e) { if (!IsEnabled) return; + OnTapEnd(GetScaledGraphicsPoint(e.X2, e.Y2)); + } + + protected virtual void OnTapStart(GPoint touchPoint) + { + Drawable?.OnTouchDown(touchPoint); + } + + protected virtual void OnTapMove(GPoint touchPoint) + { + Drawable?.OnTouchMove(touchPoint); + } - Drawable?.OnTouchUp(GetScaledGraphicsPoint(e.X2, e.Y2)); + protected virtual void OnTapEnd(GPoint touchPoint) + { + Drawable?.OnTouchUp(touchPoint); } GPoint GetScaledGraphicsPoint(int x, int y) diff --git a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs index 7a7a67e..59067d0 100644 --- a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs +++ b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/RefreshIcon.cs @@ -1,5 +1,4 @@ -using System; -using Tizen.UIExtensions.Common.GraphicsView; +using Tizen.UIExtensions.Common.GraphicsView; using ElmSharp; using DeviceInfo = Tizen.UIExtensions.Common.DeviceInfo; @@ -10,7 +9,6 @@ namespace Tizen.UIExtensions.ElmSharp.GraphicsView /// public class RefreshIcon : GraphicsView, IRefreshIcon { - RefreshIconDrawable _drawable; float _iconSize = ThemeConstants.RefreshLayout.Resources.IconSize; float _strokeWidth = ThemeConstants.RefreshLayout.Resources.IconStrokeWidth; @@ -19,8 +17,7 @@ public class RefreshIcon : GraphicsView, IRefreshIcon /// public RefreshIcon(EvasObject parent) : base(parent) { - _drawable = new RefreshIconDrawable(this); - Drawable = _drawable; + Drawable = new RefreshIconDrawable(this); var iconSize = (_iconSize + (_strokeWidth * 2)) * DeviceInfo.ScalingFactor; Resize((int)iconSize, (int)iconSize); @@ -31,10 +28,19 @@ public RefreshIcon(EvasObject parent) : base(parent) /// public new Common.Color BackgroundColor { - get => _drawable.BackgroundColor; + get + { + if (Drawable == null) + return Common.Color.Transparent; + else + return Drawable.BackgroundColor; + } set { - _drawable.BackgroundColor = value; + if (Drawable == null) + return; + + Drawable.BackgroundColor = value; Invalidate(); } } @@ -48,7 +54,7 @@ public bool IsRunning set { SetProperty(nameof(IsRunning), value); - _drawable.UpdateRunningAnimation(value); + Drawable?.UpdateRunningAnimation(value); } } @@ -80,6 +86,8 @@ public float PullDistance { if (value > 1.0) SetProperty(nameof(PullDistance), 1.0f); + else if (value < 0) + SetProperty(nameof(PullDistance), 0f); else SetProperty(nameof(PullDistance), value); } From 813850efde93564e50545b6c70b3d04e2515d835 Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Wed, 13 Oct 2021 10:31:34 +0900 Subject: [PATCH 27/76] [ElmSharp] Remove unnecessary return statements --- .../GraphicsView/GraphicsView.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/GraphicsView.cs b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/GraphicsView.cs index 6a81542..9cdc4d5 100644 --- a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/GraphicsView.cs +++ b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/GraphicsView.cs @@ -108,23 +108,26 @@ protected virtual void OnFocused(object? sender, EventArgs e) void OnTapStartCallback(GestureLayer.TapData e) { - if (!IsEnabled) - return; - OnTapStart(GetScaledGraphicsPoint(e.X, e.Y)); + if (IsEnabled) + { + OnTapStart(GetScaledGraphicsPoint(e.X, e.Y)); + } } void OnTapMoveCallback(GestureLayer.MomentumData e) { - if (!IsEnabled) - return; - OnTapMove(GetScaledGraphicsPoint(e.X2, e.Y2)); + if (IsEnabled) + { + OnTapMove(GetScaledGraphicsPoint(e.X2, e.Y2)); + } } void OnTapEndCallback(GestureLayer.MomentumData e) { - if (!IsEnabled) - return; - OnTapEnd(GetScaledGraphicsPoint(e.X2, e.Y2)); + if (IsEnabled) + { + OnTapEnd(GetScaledGraphicsPoint(e.X2, e.Y2)); + } } protected virtual void OnTapStart(GPoint touchPoint) From b47af927c05fa4b34a037964409f8ebf11673f8d Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Wed, 13 Oct 2021 16:28:04 +0900 Subject: [PATCH 28/76] Fix CollectionView Measure constraint --- src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs | 4 ++-- .../CollectionView/GridLayoutManager.cs | 4 ++-- .../CollectionView/LinearLayoutManager.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs b/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs index 346c94e..0cf98e9 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs @@ -316,8 +316,8 @@ void ContentSizeUpdated() Size ICollectionViewController.GetItemSize() { - var widthConstraint = LayoutManager!.IsHorizontal ? AllocatedSize.Width * 100 : AllocatedSize.Width; - var heightConstraint = LayoutManager!.IsHorizontal ? AllocatedSize.Height : AllocatedSize.Height * 100; + var widthConstraint = LayoutManager!.IsHorizontal ? double.PositiveInfinity : AllocatedSize.Width; + var heightConstraint = LayoutManager!.IsHorizontal ? AllocatedSize.Height : double.PositiveInfinity; return GetItemSize(widthConstraint,heightConstraint); } diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/GridLayoutManager.cs b/src/Tizen.UIExtensions.NUI/CollectionView/GridLayoutManager.cs index 3b27775..5e1780d 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/GridLayoutManager.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/GridLayoutManager.cs @@ -104,8 +104,8 @@ double BaseItemSize double ItemSpacing => IsHorizontal ? HorizontalItemSpacing : VerticalItemSpacing; - double ItemWidthConstraint => IsHorizontal ? _allocatedSize.Width * 100 : ColumnSize; - double ItemHeightConstraint => IsHorizontal ? ColumnSize : _allocatedSize.Height * 100; + double ItemWidthConstraint => IsHorizontal ? double.PositiveInfinity : ColumnSize; + double ItemHeightConstraint => IsHorizontal ? ColumnSize : double.PositiveInfinity; double ColumnSize { diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/LinearLayoutManager.cs b/src/Tizen.UIExtensions.NUI/CollectionView/LinearLayoutManager.cs index c261bff..c06e27b 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/LinearLayoutManager.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/LinearLayoutManager.cs @@ -86,8 +86,8 @@ double BaseItemSize } } - double ItemWidthConstraint => IsHorizontal ? _allocatedSize.Width * 100 : _allocatedSize.Width; - double ItemHeightConstraint => IsHorizontal ? _allocatedSize.Height : _allocatedSize.Height * 100; + double ItemWidthConstraint => IsHorizontal ? double.PositiveInfinity : _allocatedSize.Width; + double ItemHeightConstraint => IsHorizontal ? _allocatedSize.Height : double.PositiveInfinity; double FooterSize => IsHorizontal ? _footerSize.Width : _footerSize.Height; double HeaderSize => IsHorizontal ? _headerSize.Width : _headerSize.Height; From c2cfc1a6fd99738ea4897e2b8fbec2a613260114 Mon Sep 17 00:00:00 2001 From: Sunghyun Min Date: Wed, 13 Oct 2021 17:11:30 +0900 Subject: [PATCH 29/76] [ElmSharp] Add events and property for cursor position --- src/Tizen.UIExtensions.ElmSharp/Entry.cs | 40 ++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/Tizen.UIExtensions.ElmSharp/Entry.cs b/src/Tizen.UIExtensions.ElmSharp/Entry.cs index 0d87e1c..76aa58a 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Entry.cs +++ b/src/Tizen.UIExtensions.ElmSharp/Entry.cs @@ -29,6 +29,7 @@ public Entry(EvasObject parent) : base(parent) readonly Span _span = new Span(); readonly Span _placeholderSpan = new Span(); int _changedByUserCallbackDepth; + int _cursorPositionUpdating = 0; Keyboard _keyboard; /// @@ -56,6 +57,16 @@ public Entry(EvasObject parent) : base(parent) /// public event EventHandler TextChanged; + /// + /// Occurs when the current cursor position is changed. + /// + public event EventHandler CursorPositionChanged; + + /// + /// Occurs when the selection is cleared. + /// + public event EventHandler SelectionCleared; + /// /// Gets or sets the text. /// @@ -264,6 +275,15 @@ public Color PlaceholderColor } } + /// + /// Gets the value to check whether an event which affects the cursor position is finished + /// + /// true if the event is not finished + public bool IsUpdatingCursorPosition + { + get => (_cursorPositionUpdating > 0); + } + /// /// Implementation of the IMeasurable.Measure() method. /// @@ -355,6 +375,26 @@ void Initialize() _changedByUserCallbackDepth--; }; + CursorChanged += (s, e) => + { + _cursorPositionUpdating++; + + CursorPositionChanged?.Invoke(this, EventArgs.Empty); + + _cursorPositionUpdating--; + }; + + SmartEvent selectionCleared = new SmartEvent(this, RealHandle, ThemeConstants.Entry.Signals.SelectionCleared); + selectionCleared.On += (s, e) => + { + _cursorPositionUpdating++; + + SelectionCleared?.Invoke(this, EventArgs.Empty); + + _cursorPositionUpdating--; + }; + + ApplyKeyboard(Keyboard.Normal); } From a662ed3f10a21740b6ff00b651e844c1c19e64e6 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Fri, 15 Oct 2021 12:13:34 +0900 Subject: [PATCH 30/76] Add OnBackButtonPressed on Popup --- .../ActionSheetPopup.cs | 9 ++-- src/Tizen.UIExtensions.NUI/MessagePopup.cs | 9 ++-- src/Tizen.UIExtensions.NUI/Popup.cs | 24 +++++++-- src/Tizen.UIExtensions.NUI/PromptPopup.cs | 8 +-- test/NUIExGallery/TC/PopupTest.cs | 49 +++++++++++++++++++ 5 files changed, 85 insertions(+), 14 deletions(-) diff --git a/src/Tizen.UIExtensions.NUI/ActionSheetPopup.cs b/src/Tizen.UIExtensions.NUI/ActionSheetPopup.cs index 758eeac..b24a641 100644 --- a/src/Tizen.UIExtensions.NUI/ActionSheetPopup.cs +++ b/src/Tizen.UIExtensions.NUI/ActionSheetPopup.cs @@ -37,7 +37,8 @@ protected override View CreateContent() { Layout = new LinearLayout { - LinearAlignment = LinearLayout.Alignment.Center + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, }; BackgroundColor = new TColor(0.1f, 0.1f, 0.1f, 0.5f).ToNative(); @@ -45,7 +46,8 @@ protected override View CreateContent() { Layout = new LinearLayout { - LinearAlignment = LinearLayout.Alignment.Center, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, LinearOrientation = LinearLayout.Orientation.Vertical, }, SizeWidth = Window.Instance.WindowSize.Width * 0.8f, @@ -115,7 +117,8 @@ protected override View CreateContent() { Layout = new LinearLayout { - LinearAlignment = LinearLayout.Alignment.Center, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, LinearOrientation = LinearLayout.Orientation.Horizontal, }, WidthSpecification = LayoutParamPolicies.MatchParent, diff --git a/src/Tizen.UIExtensions.NUI/MessagePopup.cs b/src/Tizen.UIExtensions.NUI/MessagePopup.cs index 8e24a48..653d977 100644 --- a/src/Tizen.UIExtensions.NUI/MessagePopup.cs +++ b/src/Tizen.UIExtensions.NUI/MessagePopup.cs @@ -47,7 +47,8 @@ protected override View CreateContent() { Layout = new LinearLayout { - LinearAlignment = LinearLayout.Alignment.Center + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, }; BackgroundColor = new TColor(0.1f, 0.1f, 0.1f, 0.5f).ToNative(); @@ -56,7 +57,8 @@ protected override View CreateContent() { Layout = new LinearLayout { - LinearAlignment = LinearLayout.Alignment.Center, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, LinearOrientation = LinearLayout.Orientation.Vertical, }, SizeWidth = Window.Instance.WindowSize.Width * 0.8f, @@ -87,7 +89,8 @@ protected override View CreateContent() { Layout = new LinearLayout { - LinearAlignment = LinearLayout.Alignment.Center, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, LinearOrientation = LinearLayout.Orientation.Horizontal, }, WidthSpecification = LayoutParamPolicies.MatchParent, diff --git a/src/Tizen.UIExtensions.NUI/Popup.cs b/src/Tizen.UIExtensions.NUI/Popup.cs index f4b86a5..be1c46b 100644 --- a/src/Tizen.UIExtensions.NUI/Popup.cs +++ b/src/Tizen.UIExtensions.NUI/Popup.cs @@ -193,6 +193,17 @@ public void Close() Closed?.Invoke(this, EventArgs.Empty); } + /// + /// Actions when back button was pressed + /// A default behavior is closing popup + /// + /// if you consume back button pressed event returing true, otherwise false + protected virtual bool OnBackButtonPressed() + { + Close(); + return true; + } + public static void CloseAll() { foreach (var popup in s_openedPopup.ToList()) @@ -201,18 +212,21 @@ public static void CloseAll() } } - public static void CloseLast() + public static bool CloseLast() { - s_openedPopup.LastOrDefault()?.Close(); + return s_openedPopup.LastOrDefault()?.BackButtonPressed() ?? false; + } + + bool BackButtonPressed() + { + return OnBackButtonPressed(); } bool OnKeyEvent(object source, KeyEventArgs e) { if (IsOpen && e.Key.State == Key.StateType.Down && (e.Key.KeyPressedName == "XF86Back" || e.Key.KeyPressedName == "Escape")) { - Console.WriteLine($"Popup - OnKeyEvent - {e.Key.KeyPressedName}"); - Close(); - return true; + return OnBackButtonPressed(); } return false; } diff --git a/src/Tizen.UIExtensions.NUI/PromptPopup.cs b/src/Tizen.UIExtensions.NUI/PromptPopup.cs index 21dd833..3a59a57 100644 --- a/src/Tizen.UIExtensions.NUI/PromptPopup.cs +++ b/src/Tizen.UIExtensions.NUI/PromptPopup.cs @@ -46,7 +46,8 @@ protected override View CreateContent() { Layout = new LinearLayout { - LinearAlignment = LinearLayout.Alignment.Center + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center }; BackgroundColor = new TColor(0.1f, 0.1f, 0.1f, 0.5f).ToNative(); @@ -54,7 +55,8 @@ protected override View CreateContent() { Layout = new LinearLayout { - LinearAlignment = LinearLayout.Alignment.Center, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, LinearOrientation = LinearLayout.Orientation.Vertical, }, SizeWidth = Window.Instance.WindowSize.Width * 0.8f, @@ -104,7 +106,7 @@ protected override View CreateContent() { Layout = new LinearLayout { - LinearAlignment = LinearLayout.Alignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, LinearOrientation = LinearLayout.Orientation.Horizontal, }, WidthSpecification = LayoutParamPolicies.MatchParent, diff --git a/test/NUIExGallery/TC/PopupTest.cs b/test/NUIExGallery/TC/PopupTest.cs index 2cf3db2..788e1b7 100644 --- a/test/NUIExGallery/TC/PopupTest.cs +++ b/test/NUIExGallery/TC/PopupTest.cs @@ -305,6 +305,19 @@ public override View Run() Console.WriteLine($"Popup outside clicked"); }; }; + + + var btn5 = new Button + { + Text = "Unclosed pupup" + }; + view.Add(btn5); + btn5.Clicked += (s, e) => + { + var popup = new NotClosedPopup(); + + popup.Open(); + }; return view; } @@ -354,4 +367,40 @@ Popup MakeSimplePopup() } } + + class NotClosedPopup : Popup + { + public NotClosedPopup() + { + BackgroundColor = new Color(0.1f, 0.1f, 0.1f, 0.7f).ToNative(); + Layout = new LinearLayout + { + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, + }; + + Content = new Label + { + TextColor = Tizen.UIExtensions.Common.Color.White, + Text = "This Popup is not closed until 5 seconds" + }; + } + + public new void Open() + { + base.Open(); + var timer = new Timer(5000); + timer.Start(); + timer.Tick += (s, e) => + { + Close(); + return false; + }; + } + + protected override bool OnBackButtonPressed() + { + return true; + } + } } From 1fc1f5995c3b404285b9f16067650b2ea54230ea Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Mon, 18 Oct 2021 15:39:23 +0900 Subject: [PATCH 31/76] Refactoring ViewGroup class --- src/Tizen.UIExtensions.NUI/Button.cs | 27 +---------- src/Tizen.UIExtensions.NUI/ViewGroup.cs | 60 ++++++++++++++----------- test/NUIExGallery/TC/ViewGroupTest.cs | 7 ++- 3 files changed, 42 insertions(+), 52 deletions(-) diff --git a/src/Tizen.UIExtensions.NUI/Button.cs b/src/Tizen.UIExtensions.NUI/Button.cs index 121f8b8..1fd42e8 100644 --- a/src/Tizen.UIExtensions.NUI/Button.cs +++ b/src/Tizen.UIExtensions.NUI/Button.cs @@ -1,5 +1,4 @@ -using System; -using Tizen.UIExtensions.Common; +using Tizen.UIExtensions.Common; using NButton = Tizen.NUI.Components.Button; using NColor = Tizen.NUI.Color; @@ -58,31 +57,9 @@ public Size Measure(double availableWidth, double availableHeight) { // Issue : NaturalSize of Button is fixed when SizeWidth and SizeHight is set // so, Button's measured size never smaller than before - var buttonNaturalSize = NaturalSize; var textNaturalSize = TextLabel.NaturalSize; float buttonPadding = 46; - -#pragma warning disable CS0618 - // select bigger size between button and label - var requiredWidth = Math.Max(buttonNaturalSize.Width, textNaturalSize.Width + buttonPadding); - - if (availableWidth < requiredWidth) - { - // If label on button could be a multiline - //return new Size(availableWidth, Math.Max(GetHeightForWidth((float)availableWidth), TextLabel.GetHeightForWidth((float)availableWidth))); - - // Do not allow multiline button - return new Size(availableWidth, buttonNaturalSize.Height); - } - else - { - // If label on button could be a multiline - //return new Size(requiredWidth, Math.Max(GetHeightForWidth(requiredWidth), TextLabel.GetHeightForWidth(requiredWidth))); - - // Do not allow multiline button - return new Size(requiredWidth, buttonNaturalSize.Height); - } -#pragma warning restore CS0618 + return new Size(textNaturalSize.Width + buttonPadding, textNaturalSize.Height + buttonPadding); } } } diff --git a/src/Tizen.UIExtensions.NUI/ViewGroup.cs b/src/Tizen.UIExtensions.NUI/ViewGroup.cs index f399b13..041851e 100644 --- a/src/Tizen.UIExtensions.NUI/ViewGroup.cs +++ b/src/Tizen.UIExtensions.NUI/ViewGroup.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; -using System.Diagnostics; using System.Linq; -using System.Threading; using Tizen.NUI; using Tizen.NUI.BaseComponents; using Tizen.UIExtensions.Common; @@ -21,9 +19,11 @@ namespace Tizen.UIExtensions.NUI public class ViewGroup : View, IContainable { readonly ObservableCollection _children = new ObservableCollection(); - bool _layoutRequested; bool _disposed; - SynchronizationContext _mainloopContext; + + float _cachedWidth; + float _cachedHeight; + bool _markChanged; /// /// Initializes a new instance of the class. @@ -31,13 +31,12 @@ public class ViewGroup : View, IContainable /// ViewGroup doesn't support replacing its children, this will be ignored. public ViewGroup() { - Debug.Assert(SynchronizationContext.Current != null, "It must be used on main thread"); - _mainloopContext = SynchronizationContext.Current; - - Layout = new AbsoluteLayout(); + Layout = new ViewGroupLayout + { + LayoutRequest = () => SendLayoutUpdated() + }; WidthSpecification = LayoutParamPolicies.MatchParent; HeightSpecification = LayoutParamPolicies.MatchParent; - Relayout += OnRelayout; _children.CollectionChanged += OnCollectionChanged; } @@ -53,6 +52,8 @@ public ViewGroup() /// public event EventHandler? LayoutUpdated; + public void MarkChanged() => _markChanged = true; + public override void Add(View child) { Children.Add(child); @@ -65,28 +66,14 @@ public override void Remove(View child) void AddInternal(View child) { + _markChanged = true; base.Add(child); - LayoutRequest(); } void RemoveInternal(View child) { + _markChanged = true; base.Remove(child); - LayoutRequest(); - } - - void OnRelayout(object? sender, EventArgs e) - { - SendLayoutUpdated(); - } - - void LayoutRequest() - { - if (!_layoutRequested) - { - _layoutRequested = true; - _mainloopContext.Post((s) => SendLayoutUpdated(), null); - } } void SendLayoutUpdated() @@ -97,7 +84,16 @@ void SendLayoutUpdated() if (this == null) return; - _layoutRequested = false; + var currentSize = Size2D; + var needUpdate = _cachedWidth != currentSize.Width || _cachedHeight != currentSize.Height || _markChanged; + + _cachedWidth = currentSize.Width; + _cachedHeight = currentSize.Height; + _markChanged = false; + + if (!needUpdate) + return; + LayoutUpdated?.Invoke(this, new LayoutEventArgs { Geometry = new Rect(Position.X, Position.Y, Size.Width, Size.Height) @@ -153,5 +149,17 @@ protected override void Dispose(bool disposing) } base.Dispose(disposing); } + + class ViewGroupLayout : AbsoluteLayout + { + public Action? LayoutRequest { get; set; } + + protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom) + { + LayoutRequest?.Invoke(); + base.OnLayout(changed, left, top, right, bottom); + } + } + } } diff --git a/test/NUIExGallery/TC/ViewGroupTest.cs b/test/NUIExGallery/TC/ViewGroupTest.cs index 27a1577..f38766b 100644 --- a/test/NUIExGallery/TC/ViewGroupTest.cs +++ b/test/NUIExGallery/TC/ViewGroupTest.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading; using Tizen.NUI.BaseComponents; using Tizen.UIExtensions.NUI; @@ -13,7 +14,10 @@ public class ViewGroupTest : TestCaseBase public override View Run() { - var viewgroup = new ViewGroup(); + var viewgroup = new ViewGroup() + { + BackgroundColor = Tizen.NUI.Color.Red + }; viewgroup.LayoutUpdated += (s, e) => { var blockSize = viewgroup.Size.Height / viewgroup.Children.Count; @@ -21,6 +25,7 @@ public override View Run() foreach (var child in viewgroup.Children) { child.UpdateBounds(new Tizen.UIExtensions.Common.Rect(0, currentTop, viewgroup.Size.Width, blockSize)); + Thread.Sleep(100); currentTop += blockSize; } }; From 452ac8949d3377fc66a173296ae3b81a132d4eb6 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Mon, 18 Oct 2021 16:54:47 +0900 Subject: [PATCH 32/76] Fix CustomRenderingView - change nui callback method that notify size changed --- .../Skia/CustomRenderingView.cs | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Tizen.UIExtensions.NUI/Skia/CustomRenderingView.cs b/src/Tizen.UIExtensions.NUI/Skia/CustomRenderingView.cs index 3b4354d..2a7cc23 100644 --- a/src/Tizen.UIExtensions.NUI/Skia/CustomRenderingView.cs +++ b/src/Tizen.UIExtensions.NUI/Skia/CustomRenderingView.cs @@ -9,15 +9,16 @@ namespace Tizen.UIExtensions.NUI public abstract class CustomRenderingView : NImageView { bool _redrawRequest; - PropertyNotification _resized; protected SynchronizationContext MainloopContext { get; } protected CustomRenderingView() { + Layout = new CustomLayout + { + SizeUpdated = OnResized + }; MainloopContext = SynchronizationContext.Current ?? throw new InvalidOperationException("Must create on main thread"); - _resized = AddPropertyNotification("Size", PropertyCondition.Step(0.1f)); - _resized.Notified += OnResized; } public event EventHandler? PaintSurface; @@ -47,9 +48,24 @@ protected void SendPaintSurface(SKPaintSurfaceEventArgs e) PaintSurface?.Invoke(this, e); } - void OnResized(object source, PropertyNotification.NotifyEventArgs e) + class CustomLayout : AbsoluteLayout { - OnResized(); + float _width; + float _height; + + public Action? SizeUpdated { get; set; } + + protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom) + { + var sizeChanged = _width != Owner.SizeWidth || _height != Owner.SizeHeight; + _width = Owner.SizeWidth; + _height = Owner.SizeHeight; + if (sizeChanged) + { + SizeUpdated?.Invoke(); + } + base.OnLayout(changed, left, top, right, bottom); + } } } } From c63ab9148dff74b8067b13b90e21a76d67861514 Mon Sep 17 00:00:00 2001 From: Sunghyun Min Date: Tue, 19 Oct 2021 20:25:24 +0900 Subject: [PATCH 33/76] [ElmSharp] Add TVNavigationDrawer and TVNavigationView --- .../DrawerBehavior.cs | 23 ++ .../DrawerHeaderBehavior.cs | 2 +- src/Tizen.UIExtensions.ElmSharp/DrawerBox.cs | 21 +- .../Shell/INavigationDrawer.cs | 3 + .../Shell/NavigationDrawer.cs | 9 + .../Shell/TVNavigationDrawer.cs | 328 +++++++++++++++++ .../Shell/TVNavigationView.cs | 35 ++ .../ThemeConstants.cs | 10 + .../ThemeManager.cs | 28 ++ .../TC/NavigationDrawerTest.cs | 4 +- .../TC/TVNavigationDrawerTest.cs | 342 ++++++++++++++++++ 11 files changed, 801 insertions(+), 4 deletions(-) create mode 100644 src/Tizen.UIExtensions.Common/DrawerBehavior.cs create mode 100644 src/Tizen.UIExtensions.ElmSharp/Shell/TVNavigationDrawer.cs create mode 100644 src/Tizen.UIExtensions.ElmSharp/Shell/TVNavigationView.cs create mode 100644 test/ElmSharpExGallery/TC/TVNavigationDrawerTest.cs diff --git a/src/Tizen.UIExtensions.Common/DrawerBehavior.cs b/src/Tizen.UIExtensions.Common/DrawerBehavior.cs new file mode 100644 index 0000000..10b1f0f --- /dev/null +++ b/src/Tizen.UIExtensions.Common/DrawerBehavior.cs @@ -0,0 +1,23 @@ +namespace Tizen.UIExtensions.Common +{ + /// + /// Enumerates values that describe behaviors of the drawer in NavigationDrawer. + /// + public enum DrawerBehavior + { + /// + /// Disable the drawer. + /// + Disabled, + + /// + /// Show the drawer when it is opened. + /// + Drawer, + + /// + /// Hold the drawer open. + /// + Locked + } +} diff --git a/src/Tizen.UIExtensions.Common/DrawerHeaderBehavior.cs b/src/Tizen.UIExtensions.Common/DrawerHeaderBehavior.cs index c101377..49d163a 100644 --- a/src/Tizen.UIExtensions.Common/DrawerHeaderBehavior.cs +++ b/src/Tizen.UIExtensions.Common/DrawerHeaderBehavior.cs @@ -1,7 +1,7 @@ namespace Tizen.UIExtensions.Common { /// - /// Enumerates values that describe behaviors of the drawer in NavigationDrawer. + /// Enumerates values that describe behaviors of the header in NavigationDrawer. /// public enum DrawerHeaderBehavior { diff --git a/src/Tizen.UIExtensions.ElmSharp/DrawerBox.cs b/src/Tizen.UIExtensions.ElmSharp/DrawerBox.cs index d660710..3c93203 100644 --- a/src/Tizen.UIExtensions.ElmSharp/DrawerBox.cs +++ b/src/Tizen.UIExtensions.ElmSharp/DrawerBox.cs @@ -1,5 +1,6 @@ using System; using ElmSharp; +using Tizen.UIExtensions.Common; using static ElmSharp.GestureLayer; using EBox = ElmSharp.Box; @@ -70,6 +71,8 @@ public class DrawerBox : EBox /// bool _isSplit = false; + DrawerBehavior _drawerBehavior = Common.DrawerBehavior.Drawer; + /// /// The property value. /// @@ -200,7 +203,7 @@ public EvasObject? Content /// /// Gets or sets the DrawerBox is splited /// - public bool IsSplit + public bool IsSplit { get { @@ -216,6 +219,22 @@ public bool IsSplit } } + /// + /// Gets or sets the behavior of the Drawer + /// + public DrawerBehavior DrawerBehavior + { + get + { + return _drawerBehavior; + } + set + { + _drawerBehavior = value; + IsSplit = (_drawerBehavior == DrawerBehavior.Locked) ? true : false; + } + } + /// /// Gets or sets a value indicating whether the Drawer is shown. /// diff --git a/src/Tizen.UIExtensions.ElmSharp/Shell/INavigationDrawer.cs b/src/Tizen.UIExtensions.ElmSharp/Shell/INavigationDrawer.cs index d093ddf..2a42f5b 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Shell/INavigationDrawer.cs +++ b/src/Tizen.UIExtensions.ElmSharp/Shell/INavigationDrawer.cs @@ -1,5 +1,6 @@ using System; using ElmSharp; +using Tizen.UIExtensions.Common; namespace Tizen.UIExtensions.ElmSharp { @@ -15,6 +16,8 @@ public interface INavigationDrawer bool IsSplit { get; set; } + DrawerBehavior DrawerBehavior { get; set; } + event EventHandler Toggled; } } \ No newline at end of file diff --git a/src/Tizen.UIExtensions.ElmSharp/Shell/NavigationDrawer.cs b/src/Tizen.UIExtensions.ElmSharp/Shell/NavigationDrawer.cs index b6b6a79..684affc 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Shell/NavigationDrawer.cs +++ b/src/Tizen.UIExtensions.ElmSharp/Shell/NavigationDrawer.cs @@ -15,6 +15,9 @@ public NavigationDrawer(EvasObject parent) : base(parent) { } + /// + /// Gets or sets the navigation view to be shown on the drawer. + /// public EvasObject? NavigationView { get @@ -27,6 +30,9 @@ public EvasObject? NavigationView } } + /// + /// Gets of sets the main content of the NavigationDrawer. + /// public EvasObject? Main { get @@ -39,6 +45,9 @@ public EvasObject? Main } } + /// + /// Gets the target view of the NaviagtionDrawer + /// public EvasObject TargetView => this; } } diff --git a/src/Tizen.UIExtensions.ElmSharp/Shell/TVNavigationDrawer.cs b/src/Tizen.UIExtensions.ElmSharp/Shell/TVNavigationDrawer.cs new file mode 100644 index 0000000..da9d100 --- /dev/null +++ b/src/Tizen.UIExtensions.ElmSharp/Shell/TVNavigationDrawer.cs @@ -0,0 +1,328 @@ +using ElmSharp; +using System; +using Tizen.UIExtensions.Common; +using Tizen.UIExtensions.Common.Internal; +using EBox = ElmSharp.Box; +using EColor = ElmSharp.Color; + +namespace Tizen.UIExtensions.ElmSharp +{ + /// + /// The native widget to be used in Xamarin.Forms Shell for TV. + /// + public class TVNavigationDrawer : EBox, INavigationDrawer, IAnimatable + { + EBox _drawerBox; + EBox _mainBox; + EvasObject? _main; + EvasObject? _drawer; + Button _focusControlArea; + + DrawerBehavior _behavior; + bool _isOpen; + bool _isSplit; + double _openRatio; + + double _drawerRatioMax = -1; + double _drawerRatioMin = -1; + + + /// + /// Initializes a new instance of the class. + /// + /// + public TVNavigationDrawer(EvasObject parent) : base(parent) + { + SetLayoutCallback(OnLayout); + + _drawerBox = new EBox(parent); + _drawerBox.Show(); + PackEnd(_drawerBox); + + _mainBox = new EBox(parent); + _mainBox.SetLayoutCallback(OnMainBoxLayout); + _mainBox.Show(); + PackEnd(_mainBox); + + _focusControlArea = new Button(parent) + { + Color = EColor.Transparent, + BackgroundColor = EColor.Transparent + }; + _focusControlArea.SetEffectColor(EColor.Transparent); + _focusControlArea.Show(); + _mainBox.PackEnd(_focusControlArea); + + _behavior = Common.DrawerBehavior.Drawer; + + _drawerBox.KeyUp += (s, e) => + { + if (e.KeyName == "Return" || e.KeyName == "Right") + { + IsOpen = false; + } + }; + + _mainBox.KeyUp += (s, e) => + { + if (e.KeyName == "Left") + { + if (_focusControlArea.IsFocused) + IsOpen = true; + } + else + { + // Workaround to prevent unexpected movement of the focus to drawer during page pushing. + if (_behavior == DrawerBehavior.Locked) + _drawerBox.AllowTreeFocus = true; + } + }; + + _mainBox.KeyDown += (s, e) => + { + if (e.KeyName != "Left") + { + // Workaround to prevent unexpected movement of the focus to drawer during page pushing. + if (_behavior == DrawerBehavior.Locked) + _drawerBox.AllowTreeFocus = false; + } + }; + + UpdateFocusPolicy(); + } + + /// + /// Occurs when the drawer is shown or hidden. + /// + public event EventHandler? Toggled; + + /// + /// Gets the target view of TVNavigationDrawer. + /// + public EvasObject TargetView => this; + + /// + /// Gets or sets the navigation view to be shown on the drawer. + /// + public EvasObject? NavigationView + { + get => _drawer; + set => UpdateNavigationView(value); + } + + /// + /// Gets or set the main content of TVNavigationDrawer. + /// + public EvasObject? Main + { + get => _main; + set => UpdateMain(value); + } + + /// + /// Gets or sets a value indicating whether the drawer is shown. + /// + /// true if the Drawer is opened. + public bool IsOpen + { + get => _isOpen; + set => UpdateOpenState(value); + } + + /// + /// Gets or sets the TVNavigationDrawer is splited. + /// + public bool IsSplit + { + get => _isSplit; + set => UpdateBehavior(value ? DrawerBehavior.Locked : _behavior); + } + + /// + /// Gets or sets the behavior of the drawer. + /// + public DrawerBehavior DrawerBehavior + { + get => _behavior; + set => UpdateBehavior(value); + } + + /// + /// Gets or Sets the portion of the screen then the drawer is opened. + /// + public double DrawerRatioMax + { + get => _drawerRatioMax; + set + { + if (_drawerRatioMax != value) + { + _drawerRatioMax = value; + OnLayout(); + } + } + } + + /// + /// Gets or Sets the portion of the screen then the drawer is closed. + /// + public double DrawerRatioMin + { + get => _drawerRatioMin; + set + { + if (_drawerRatioMin != value) + { + _drawerRatioMin = value; + OnLayout(); + } + } + } + + void UpdateBehavior(DrawerBehavior behavior) + { + _behavior = behavior; + _isSplit = (behavior == DrawerBehavior.Locked) ? true : false; + _focusControlArea.IsEnabled = _behavior == Common.DrawerBehavior.Drawer; + + var open = false; + + if (_behavior == DrawerBehavior.Locked) + open = true; + else if (_behavior == DrawerBehavior.Disabled) + open = false; + else + open = _drawerBox.IsFocused; + + UpdateOpenState(open); + } + + void UpdateNavigationView(EvasObject? navigationView) + { + if (_drawer != null) + { + _drawerBox.UnPack(_drawer); + _drawer.Hide(); + } + + _drawer = navigationView; + + if (_drawer != null) + { + _drawer.SetAlignment(-1, -1); + _drawer.SetWeight(1, 1); + _drawer.Show(); + _drawerBox.PackEnd(_drawer); + } + } + + void UpdateMain(EvasObject? main) + { + if (_main != null) + { + _mainBox.UnPack(_main); + _main.Hide(); + } + _main = main; + + if (_main != null) + { + _main.SetAlignment(-1, -1); + _main.SetWeight(1, 1); + _main.Show(); + _mainBox.PackStart(_main); + } + } + + void OnMainBoxLayout() + { + if (_main != null) + { + _main.Geometry = _mainBox.Geometry; + } + + var focusedButtonGeometry = _mainBox.Geometry; + focusedButtonGeometry.X = focusedButtonGeometry.X - 100; + focusedButtonGeometry.Width = 0; + focusedButtonGeometry.Height = this.GetTvFocusedButtonHeight(); + _focusControlArea.Geometry = focusedButtonGeometry; + } + + void OnLayout() + { + if (Geometry.Width == 0 || Geometry.Height == 0) + return; + + var bound = Geometry; + + var ratioMax = (_drawerRatioMax < 0) ? this.GetTvDrawerRatio(Geometry.Width, Geometry.Height) : _drawerRatioMax; + var ratioMin = (_behavior == DrawerBehavior.Disabled) ? 0 : ((_drawerRatioMin < 0) ? this.GetTvDrawerRatioMin() : _drawerRatioMin); + var drawerWidthMax = (int)(bound.Width * ratioMax); + var drawerWidthMin = (int)(bound.Width * ratioMin); + + var drawerWidthOutBound = (int)((drawerWidthMax - drawerWidthMin) * (1 - _openRatio)); + var drawerWidthInBound = drawerWidthMax - drawerWidthOutBound; + + var drawerGeometry = bound; + drawerGeometry.Width = drawerWidthInBound; + _drawerBox.Geometry = drawerGeometry; + + var containerGeometry = bound; + containerGeometry.X = drawerWidthInBound; + containerGeometry.Width = (_behavior == DrawerBehavior.Locked) ? (bound.Width - drawerWidthInBound) : (bound.Width - drawerWidthMin); + _mainBox.Geometry = containerGeometry; + } + + void UpdateOpenState(bool isOpen) + { + if (_behavior == DrawerBehavior.Locked && !isOpen) + return; + + double endState = ((_behavior != DrawerBehavior.Disabled) && isOpen) ? 1 : 0; + new Animation((r) => + { + _openRatio = r; + OnLayout(); + }, _openRatio, endState, Easing.SinOut).Commit(this, "DrawerMove", finished: (f, aborted) => + { + if (!aborted) + { + if (_isOpen != isOpen) + { + _isOpen = isOpen; + UpdateFocusPolicy(); + Toggled?.Invoke(this, EventArgs.Empty); + } + } + }); + } + + void UpdateFocusPolicy() + { + if (_isOpen) + { + if (_behavior == DrawerBehavior.Locked) + { + _drawerBox.AllowTreeFocus = true; + _mainBox.AllowTreeFocus = true; + } + else + { + _mainBox.AllowTreeFocus = false; + _drawerBox.AllowTreeFocus = true; + _drawerBox.SetFocus(true); + } + } + else + { + _mainBox.AllowTreeFocus = true; + _drawerBox.AllowTreeFocus = false; + _mainBox.SetFocus(true); + } + } + + void IAnimatable.BatchBegin() { } + + void IAnimatable.BatchCommit() { } + } +} diff --git a/src/Tizen.UIExtensions.ElmSharp/Shell/TVNavigationView.cs b/src/Tizen.UIExtensions.ElmSharp/Shell/TVNavigationView.cs new file mode 100644 index 0000000..6fb930d --- /dev/null +++ b/src/Tizen.UIExtensions.ElmSharp/Shell/TVNavigationView.cs @@ -0,0 +1,35 @@ +using ElmSharp; +using EColor = ElmSharp.Color; + +namespace Tizen.UIExtensions.ElmSharp +{ + /// + /// The native widget that is configured with an header and an list of items to be used in TVNavigationDrawer. + /// + public class TVNavigationView : NavigationView + { + EColor _backgroundColor; + + /// + /// Initializes a new instance of the class. + /// + /// Parent evas object. + public TVNavigationView(EvasObject parent) : base(parent) + { + BackgroundColor = this.GetTvDefaultBackgroundColor(); + } + + /// + /// Gets or sets the background color. + /// + public override EColor BackgroundColor + { + get => _backgroundColor; + set + { + _backgroundColor = value; + base.BackgroundColor = _backgroundColor.IsDefault ? this.GetTvDefaultBackgroundColor(): _backgroundColor; + } + } + } +} diff --git a/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs b/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs index 7f92565..7b8b4e1 100644 --- a/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs +++ b/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs @@ -573,6 +573,8 @@ public class Resources public const string BackIcon = "Platform.Tizen.Resources.arrow_left.png"; public const string DotsIcon = "Platform.Tizen.Resources.dots_horizontal.png"; public const int DefaultDrawerDimOpacity = 30; + public const int DefaultDrawerItemHeight = 10; + public const double DefaultDrawerRatio = 0.83; public class Watch { @@ -587,6 +589,9 @@ public class TV public const string MenuIconCode = "\u2630"; public const string BackIconCode = "\u2190"; public const string DotsIconCode = "\u2026"; + + public const double DefaultDrawerRatio = 0.3; + public const double DefaultDrawerRatioMin = 0.05; } } @@ -603,6 +608,11 @@ public class Watch public static readonly EColor DefaultNavigationViewForegroundColor = EColor.Default; public static readonly EColor DefaultNavigationViewBackgroundColor = EColor.Black; } + + public class TV + { + public static readonly EColor DefaultBackgroundColor = EColor.Black; + } } } #endregion diff --git a/src/Tizen.UIExtensions.ElmSharp/ThemeManager.cs b/src/Tizen.UIExtensions.ElmSharp/ThemeManager.cs index 2a1e59a..8f9033e 100644 --- a/src/Tizen.UIExtensions.ElmSharp/ThemeManager.cs +++ b/src/Tizen.UIExtensions.ElmSharp/ThemeManager.cs @@ -766,5 +766,33 @@ public static double GetSplitRatio(this DrawerBox drawerlayoutBox) } #endregion + + #region TVNavigationView + public static EColor GetTvDefaultBackgroundColor(this INavigationView nav) + { + return ThemeConstants.Shell.ColorClass.TV.DefaultBackgroundColor; + } + #endregion + + #region TVNavigationDrawer + static int s_defaultDrawerItemHeight = -1; + public static int GetTvFocusedButtonHeight(this INavigationDrawer drawer) + { + if (s_defaultDrawerItemHeight > 0) + return s_defaultDrawerItemHeight; + return s_defaultDrawerItemHeight = ThemeConstants.Shell.Resources.DefaultDrawerItemHeight; + } + + static double s_navigationDrawerRatio = -1; + public static double GetTvDrawerRatio(this INavigationDrawer drawer, int width, int height) + { + return s_navigationDrawerRatio = (width > height) ? ThemeConstants.Shell.Resources.TV.DefaultDrawerRatio : ThemeConstants.Shell.Resources.DefaultDrawerRatio; + } + + public static double GetTvDrawerRatioMin(this INavigationDrawer drawer) + { + return ThemeConstants.Shell.Resources.TV.DefaultDrawerRatioMin; + } + #endregion } } \ No newline at end of file diff --git a/test/ElmSharpExGallery/TC/NavigationDrawerTest.cs b/test/ElmSharpExGallery/TC/NavigationDrawerTest.cs index 04ba8ba..5e22d41 100644 --- a/test/ElmSharpExGallery/TC/NavigationDrawerTest.cs +++ b/test/ElmSharpExGallery/TC/NavigationDrawerTest.cs @@ -161,8 +161,8 @@ public override void Run(ElmSharp.Box parent) naviView.BackgroundImage = CreateBackgroundImage(parent); //naviView.BackgroundColor = Color.Yellow; - drawer.Content = content; - drawer.Drawer = naviView; + drawer.Main = content; + drawer.NavigationView = naviView; drawer.Show(); parent.PackEnd(drawer); diff --git a/test/ElmSharpExGallery/TC/TVNavigationDrawerTest.cs b/test/ElmSharpExGallery/TC/TVNavigationDrawerTest.cs new file mode 100644 index 0000000..ddd549c --- /dev/null +++ b/test/ElmSharpExGallery/TC/TVNavigationDrawerTest.cs @@ -0,0 +1,342 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Tizen.Applications; +using ElmSharp; +using Common = Tizen.UIExtensions.Common; +using Ext = Tizen.UIExtensions.ElmSharp; + +namespace ElmSharpExGallery.TC +{ + public class TVNavigationDrawerTest : TestCaseBase + { + public override string TestName => "Shell TVNavigationDrawer Test 1"; + + public override string TestDescription => "Shell TVNavigationDrawer Test 1"; + + Box _header; + + public override void Run(ElmSharp.Box parent) + { + Console.WriteLine($" run !!!"); + var data = new List(); + for (int i = 0; i < 50; i++) + { + data.Add($"item {i}"); + } + + var drawer = new Ext.TVNavigationDrawer(parent) + { + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1, + WeightY = 1, + }; + + var naviView = new Ext.TVNavigationView(parent); + + var content = new Box(parent) + { + BackgroundColor = Color.White + }; + + var label = new Label(parent) + { + AlignmentX = 0.5, + WeightX = 1, + Text = "selected item index: / data: ", + }; + label.Show(); + content.PackEnd(label); + + var bLabel = new Label(parent) + { + AlignmentX = 0.5, + WeightX = 1, + Text = "behavior: ", + }; + bLabel.Show(); + content.PackEnd(bLabel); + + var openButton = new Button(parent) + { + MinimumWidth = 500, + Text = "open / close" + }; + openButton.Show(); + content.PackEnd(openButton); + + var headerButton = new Button(parent) + { + MinimumWidth = 500, + Text = "add / remove header" + }; + headerButton.Show(); + content.PackEnd(headerButton); + + var footerButton = new Button(parent) + { + MinimumWidth = 500, + Text = "add / remove footer" + }; + footerButton.Show(); + content.PackEnd(footerButton); + + + var contentButton = new Button(parent) + { + MinimumWidth = 500, + Text = "add / remove content" + }; + contentButton.Show(); + content.PackEnd(contentButton); + + + + var headerButton2 = new Button(parent) + { + MinimumHeight = 100, + MinimumWidth = 300, + Text = "header" + }; + headerButton2.Show(); + content.PackEnd(headerButton2); + + headerButton2.Clicked += (s, e) => + { + if (_header.MinimumHeight < 200) + _header.MinimumHeight = 300; + else + _header.MinimumHeight = 150; + }; + + openButton.Clicked += (s, e) => + { + drawer.IsOpen = !drawer.IsOpen; + }; + + headerButton.Clicked += (s, e) => + { + if (naviView.Header != null) + { + naviView.Header = null; + } + else + { + naviView.Header = CreateHeader(parent); + } + }; + + footerButton.Clicked += (s, e) => + { + if (naviView.Footer != null) + { + naviView.Footer = null; + } + else + { + naviView.Footer = CreateFooter(parent); + } + }; + + contentButton.Clicked += (s, e) => + { + if (naviView.Content != null) + { + naviView.Content = null; + } + else + { + naviView.Content = CreateContent(parent); + } + }; + + naviView.Header = CreateHeader(parent); + naviView.Footer = CreateFooter(parent); + naviView.Content = CreateContent(parent); + naviView.BackgroundImage = CreateBackgroundImage(parent); + //naviView.BackgroundColor = Color.Yellow; + + drawer.Main = content; + drawer.NavigationView = naviView; + + drawer.Show(); + parent.PackEnd(drawer); + } + + EvasObject CreateBackgroundImage(EvasObject parent) + { + var img = new Ext.Image(parent) + { + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1, + WeightY = 1, + }; + img.Show(); + img.Aspect = Common.Aspect.Fill; + img.LoadAsync(Application.Current.DirectoryInfo.Resource + "animated2.gif"); + + return img; + } + + EvasObject CreateHeader(EvasObject parent) + { + _header = new Box(parent); + _header.BackgroundColor = Color.Red; + _header.MinimumHeight = 150; + + var headerLabel = new Ext.Label(parent) + { + FormattedText = new Common.FormattedString + { + Spans = + { + new Common.Span + { + Text = "Header", + FontAttributes = Common.FontAttributes.Bold, + HorizontalTextAlignment= Common.TextAlignment.Center, + FontSize = 60, + } + } + } + }; + headerLabel.Show(); + _header.PackEnd(headerLabel); + + return _header; + } + + EvasObject CreateFooter(EvasObject parent) + { + var footer = new Box(parent); + footer.BackgroundColor = Color.Blue; + footer.MinimumHeight = 200; + + var footerLabel = new Ext.Label(parent) + { + FormattedText = new Common.FormattedString + { + Spans = + { + new Common.Span + { + Text = "Footer", + FontAttributes = Common.FontAttributes.Bold, + HorizontalTextAlignment= Common.TextAlignment.Center, + FontSize = 60, + } + } + } + }; + footerLabel.Show(); + footer.PackEnd(footerLabel); + + return footer; + } + + EvasObject CreateContent(EvasObject parent) + { + var collectionView = new Ext.CollectionView(parent) + { + WeightX = 1, + WeightY = 1, + AlignmentY = -1, + AlignmentX = -1, + }; + collectionView.Show(); + + var items = new List(); + for (int i = 0; i < 100; i++) + { + items.Add($"Items {i}"); + + } + var adaptor = new MyAdaptor(items); + collectionView.Adaptor = adaptor; + collectionView.LayoutManager = new Ext.LinearLayoutManager(false, Ext.ItemSizingStrategy.MeasureAllItems, 10); + + return collectionView; + } + + public class MyAdaptor : Ext.ItemAdaptor + { + + public MyAdaptor(IEnumerable items) : base(items) + { + + } + + public override EvasObject CreateNativeView(EvasObject parent) + { + var label = new Label(parent) + { + Text = "Default label", + BackgroundColor = Color.Gray, + }; + label.Show(); + return label; + } + + public override EvasObject CreateNativeView(int index, EvasObject parent) + { + var label = new Label(parent) + { + Text = $"Created [{index}] label", + BackgroundColor = Color.Yellow, + }; + label.Show(); + return label; + } + + public override EvasObject GetFooterView(EvasObject parent) + { + return null; + } + + public override EvasObject GetHeaderView(EvasObject parent) + { + return null; + } + + public override Size MeasureFooter(int widthConstraint, int heightConstraint) + { + return new Size(0, 0); + } + + public override Size MeasureHeader(int widthConstraint, int heightConstraint) + { + return new Size(0, 0); + } + + public override Size MeasureItem(int widthConstraint, int heightConstraint) + { + return new Size(150, 150); + } + + public override Size MeasureItem(int index, int widthConstraint, int heightConstraint) + { + if (index % 2 == 0) + return new Size(150, 150); + else + return new Size(200, 200); + } + + public override void RemoveNativeView(EvasObject native) + { + native.Unrealize(); + } + + public override void SetBinding(EvasObject view, int index) + { + (view as Label).Text = $"Binding {index}"; + } + + public override void UnBinding(EvasObject view) + { + (view as Label).Text = $"UnBinding"; + } + } + } +} From 4d795b1bcc945efb44b546e0ae3e590b76c220f4 Mon Sep 17 00:00:00 2001 From: Sunghyun Min Date: Wed, 20 Oct 2021 12:19:56 +0900 Subject: [PATCH 34/76] [ElmSharp] Update proprety names in TVNavigationDrawer --- .../Shell/TVNavigationDrawer.cs | 36 +++++++++---------- .../ThemeConstants.cs | 2 +- .../ThemeManager.cs | 4 +-- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Tizen.UIExtensions.ElmSharp/Shell/TVNavigationDrawer.cs b/src/Tizen.UIExtensions.ElmSharp/Shell/TVNavigationDrawer.cs index da9d100..18c849a 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Shell/TVNavigationDrawer.cs +++ b/src/Tizen.UIExtensions.ElmSharp/Shell/TVNavigationDrawer.cs @@ -21,10 +21,10 @@ public class TVNavigationDrawer : EBox, INavigationDrawer, IAnimatable DrawerBehavior _behavior; bool _isOpen; bool _isSplit; - double _openRatio; + double _drawerRatio; - double _drawerRatioMax = -1; - double _drawerRatioMin = -1; + double _OpenRatio = -1; + double _closeRatio = -1; /// @@ -150,14 +150,14 @@ public DrawerBehavior DrawerBehavior /// /// Gets or Sets the portion of the screen then the drawer is opened. /// - public double DrawerRatioMax + public double OpenRatio { - get => _drawerRatioMax; + get => _OpenRatio; set { - if (_drawerRatioMax != value) + if (_OpenRatio != value) { - _drawerRatioMax = value; + _OpenRatio = value; OnLayout(); } } @@ -166,14 +166,14 @@ public double DrawerRatioMax /// /// Gets or Sets the portion of the screen then the drawer is closed. /// - public double DrawerRatioMin + public double CloseRatio { - get => _drawerRatioMin; + get => _closeRatio; set { - if (_drawerRatioMin != value) + if (_closeRatio != value) { - _drawerRatioMin = value; + _closeRatio = value; OnLayout(); } } @@ -255,12 +255,12 @@ void OnLayout() var bound = Geometry; - var ratioMax = (_drawerRatioMax < 0) ? this.GetTvDrawerRatio(Geometry.Width, Geometry.Height) : _drawerRatioMax; - var ratioMin = (_behavior == DrawerBehavior.Disabled) ? 0 : ((_drawerRatioMin < 0) ? this.GetTvDrawerRatioMin() : _drawerRatioMin); - var drawerWidthMax = (int)(bound.Width * ratioMax); - var drawerWidthMin = (int)(bound.Width * ratioMin); + var openRatio = (_OpenRatio < 0) ? this.GetTvDrawerRatio(Geometry.Width, Geometry.Height) : _OpenRatio; + var closeRatio = (_behavior == DrawerBehavior.Disabled) ? 0 : ((_closeRatio < 0) ? this.GetTvDrawerCloseRatio() : _closeRatio); + var drawerWidthMax = (int)(bound.Width * openRatio); + var drawerWidthMin = (int)(bound.Width * closeRatio); - var drawerWidthOutBound = (int)((drawerWidthMax - drawerWidthMin) * (1 - _openRatio)); + var drawerWidthOutBound = (int)((drawerWidthMax - drawerWidthMin) * (1 - _drawerRatio)); var drawerWidthInBound = drawerWidthMax - drawerWidthOutBound; var drawerGeometry = bound; @@ -281,9 +281,9 @@ void UpdateOpenState(bool isOpen) double endState = ((_behavior != DrawerBehavior.Disabled) && isOpen) ? 1 : 0; new Animation((r) => { - _openRatio = r; + _drawerRatio = r; OnLayout(); - }, _openRatio, endState, Easing.SinOut).Commit(this, "DrawerMove", finished: (f, aborted) => + }, _drawerRatio, endState, Easing.SinOut).Commit(this, "DrawerMove", finished: (f, aborted) => { if (!aborted) { diff --git a/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs b/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs index 7b8b4e1..e9d359e 100644 --- a/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs +++ b/src/Tizen.UIExtensions.ElmSharp/ThemeConstants.cs @@ -591,7 +591,7 @@ public class TV public const string DotsIconCode = "\u2026"; public const double DefaultDrawerRatio = 0.3; - public const double DefaultDrawerRatioMin = 0.05; + public const double DefaultDrawerCloseRatio = 0.05; } } diff --git a/src/Tizen.UIExtensions.ElmSharp/ThemeManager.cs b/src/Tizen.UIExtensions.ElmSharp/ThemeManager.cs index 8f9033e..0646482 100644 --- a/src/Tizen.UIExtensions.ElmSharp/ThemeManager.cs +++ b/src/Tizen.UIExtensions.ElmSharp/ThemeManager.cs @@ -789,9 +789,9 @@ public static double GetTvDrawerRatio(this INavigationDrawer drawer, int width, return s_navigationDrawerRatio = (width > height) ? ThemeConstants.Shell.Resources.TV.DefaultDrawerRatio : ThemeConstants.Shell.Resources.DefaultDrawerRatio; } - public static double GetTvDrawerRatioMin(this INavigationDrawer drawer) + public static double GetTvDrawerCloseRatio(this INavigationDrawer drawer) { - return ThemeConstants.Shell.Resources.TV.DefaultDrawerRatioMin; + return ThemeConstants.Shell.Resources.TV.DefaultDrawerCloseRatio; } #endregion } From c45f215af62710b86b69d4b63aae92668304174d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=EA=B7=BC/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/Staff=20Engineer/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Wed, 20 Oct 2021 15:36:18 +0900 Subject: [PATCH 35/76] [NUI] Add MaterialIconDrawable and MaterialIconButton (#99) * Add MaterialIconDrawable and MaterialIconButton * Update review --- .../GraphicsView/MaterialIconDrawable.cs | 111 +++++++++++++++ .../GraphicsView/RippleEffectDrawable.cs | 4 +- .../Tizen.UIExtensions.Common.projitems | 1 + .../GraphicsView/MaterialIconButton.cs | 94 +++++++++++++ .../Skia/SKCanvasView.cs | 5 + .../Skia/SKGLSurfaceView.cs | 4 + .../NUIExGallery/TC/MaterialIconButtonTest.cs | 129 ++++++++++++++++++ 7 files changed, 346 insertions(+), 2 deletions(-) create mode 100644 src/Tizen.UIExtensions.Common/GraphicsView/MaterialIconDrawable.cs create mode 100644 src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs create mode 100644 test/NUIExGallery/TC/MaterialIconButtonTest.cs diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/MaterialIconDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/MaterialIconDrawable.cs new file mode 100644 index 0000000..0a717d7 --- /dev/null +++ b/src/Tizen.UIExtensions.Common/GraphicsView/MaterialIconDrawable.cs @@ -0,0 +1,111 @@ +using Microsoft.Maui.Graphics; +using GPoint = Microsoft.Maui.Graphics.Point; +using TSize = Tizen.UIExtensions.Common.Size; + +namespace Tizen.UIExtensions.Common.GraphicsView +{ + public enum MaterialIcons + { + Add, + App, + ArrowBack, + ArrowDownward, + ArrowForward, + ArrowUpward, + Check, + Close, + ExpandLess, + ExpandMore, + Link, + Menu, + MoreHoriz, + MoreVert, + Remove, + RemoveCircle + } + + public class MaterialIconDrawable : GraphicsViewDrawable + { + + const float MaterialIconHeight = 24; + const float MaterialIconWidth = 24; + + const string PathAdd = "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"; + const string PathApp = "M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z"; + const string PathArrowBack = "M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"; + const string PathArrowDownward = "M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"; + const string PathArrowForward = "M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"; + const string PathArrowUpward = "M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"; + const string PathCheck = "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"; + const string PathClose = "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"; + const string PathExpandLess = "M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z"; + const string PathExpandMore = "M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"; + const string PathLink = "M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"; + const string PathMenu = "M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"; + const string PathMoreHoriz = "M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"; + const string PathMoreVert = "M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"; + const string PathRemove = "M19 13H5v-2h14v2z"; + const string PathRemoveCircle = "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11H7v-2h10v2z"; + + static readonly string[] s_Paths = + { + PathAdd, + PathApp, + PathArrowBack, + PathArrowDownward, + PathArrowForward, + PathArrowUpward, + PathCheck, + PathClose, + PathExpandLess, + PathExpandMore, + PathLink, + PathMenu, + PathMoreHoriz, + PathMoreVert, + PathRemove, + PathRemoveCircle, + }; + + public MaterialIconDrawable() + { + } + + public MaterialIcons Icon { get; set; } + public Color Color { get; set; } + + public override void Draw(ICanvas canvas, RectangleF dirtyRect) + { + DrawIcon(canvas, dirtyRect); + } + + public override TSize Measure(double availableWidth, double availableHeight) + { + return new TSize(DeviceInfo.ScalingFactor * MaterialIconWidth, DeviceInfo.ScalingFactor * MaterialIconHeight); + } + + + void DrawIcon(ICanvas canvas, RectangleF dirtyRect) + { + canvas.SaveState(); + + // Note. SkiaGraphicsView use DP unit + var width = dirtyRect.Width; + var height = dirtyRect.Height; + var imgWidth = MaterialIconWidth; + var imgHeight = MaterialIconHeight; + + var transX = (width - imgWidth) / 2.0f; + var transY = (height - imgHeight) / 2.0f; + canvas.Translate(transX, transY); + + var vBuilder = new PathBuilder(); + var path = vBuilder.BuildPath(s_Paths[(int)Icon]); + + canvas.FillColor = Color.ToGraphicsColor(Material.Color.Black); + canvas.FillPath(path); + canvas.RestoreState(); + } + + } +} diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/RippleEffectDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/RippleEffectDrawable.cs index c7c2d82..3bed554 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/RippleEffectDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/RippleEffectDrawable.cs @@ -17,7 +17,7 @@ public RippleEffectDrawable() public override void Draw(ICanvas canvas, RectangleF dirtyRect) { - if (ClipRectangle == RectangleF.Zero || ClipRectangle.Contains(TouchPoint)) + if ((ClipRectangle == RectangleF.Zero || ClipRectangle.Contains(TouchPoint)) && RippleEffectSize > 0) { canvas.SaveState(); @@ -73,7 +73,7 @@ void AnimateDrawRipple() var to = ClipRectangle != RectangleF.Zero ? ClipRectangle.Width : 1000; var thumbSizeAnimation = new Animation(v => RippleEffectSize = (int)v, from, to, easing: Easing.SinInOut); - thumbSizeAnimation.Commit(this, "RippleEffectAnimation", length: 350, finished: (l, c) => + thumbSizeAnimation.Commit(this, "RippleEffectAnimation", rate:32, length: 350, finished: (l, c) => { _rippleEffectSize = 0; thumbSizeAnimation = null; diff --git a/src/Tizen.UIExtensions.Common/Tizen.UIExtensions.Common.projitems b/src/Tizen.UIExtensions.Common/Tizen.UIExtensions.Common.projitems index c68112e..18dcc0a 100644 --- a/src/Tizen.UIExtensions.Common/Tizen.UIExtensions.Common.projitems +++ b/src/Tizen.UIExtensions.Common/Tizen.UIExtensions.Common.projitems @@ -20,6 +20,7 @@ + diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs new file mode 100644 index 0000000..cb4fef1 --- /dev/null +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs @@ -0,0 +1,94 @@ +using System; +using Tizen.UIExtensions.Common; +using Tizen.UIExtensions.Common.GraphicsView; + +namespace Tizen.UIExtensions.NUI.GraphicsView +{ + /// + /// A button View that reacts to touch events. + /// + public class MaterialIconButton : GraphicsView + { + /// + /// Initializes a new instance of the Button class. + /// + public MaterialIconButton() + { + Drawable = new MaterialIconDrawable(); + } + + /// + /// Occurs when the Button is clicked. + /// + public event EventHandler? Clicked; + + /// + /// Occurs when the Button is pressed. + /// + public event EventHandler? Pressed; + + /// + /// Occurs when the Button is released. + /// + public event EventHandler? Released; + + public bool IsPressed + { + get => GetProperty(nameof(IsPressed)); + set => SetProperty(nameof(IsPressed), value); + } + + /// + /// Icon type + /// + public MaterialIcons Icon + { + get => Drawable?.Icon ?? MaterialIcons.Add; + set + { + if (Drawable != null) + { + Drawable.Icon = value; + Invalidate(); + } + } + } + + /// + /// Color of Icons + /// + public new Color Color + { + get => Drawable?.Color ?? Color.Default; + set + { + if (Drawable != null) + { + Drawable.Color = value; + Invalidate(); + } + } + } + + protected override bool OnTouch(object source, TouchEventArgs e) + { + if (!IsEnabled) + return false; + + var state = e.Touch.GetState(0); + + if (state == Tizen.NUI.PointStateType.Down) + { + IsPressed = true; + Pressed?.Invoke(this, EventArgs.Empty); + Clicked?.Invoke(this, EventArgs.Empty); + } + else if (state == Tizen.NUI.PointStateType.Up) + { + IsPressed = false; + Released?.Invoke(this, EventArgs.Empty); + } + return base.OnTouch(source, e); + } + } +} diff --git a/src/Tizen.UIExtensions.NUI/Skia/SKCanvasView.cs b/src/Tizen.UIExtensions.NUI/Skia/SKCanvasView.cs index bbc8f13..7d7dc7e 100644 --- a/src/Tizen.UIExtensions.NUI/Skia/SKCanvasView.cs +++ b/src/Tizen.UIExtensions.NUI/Skia/SKCanvasView.cs @@ -41,6 +41,11 @@ protected override void OnDrawFrame() _nativeImageSource.EnqueueBuffer(buffer); Window.Instance.KeepRendering(0); } + else + { + Invalidate(); + } + } protected override void OnResized() diff --git a/src/Tizen.UIExtensions.NUI/Skia/SKGLSurfaceView.cs b/src/Tizen.UIExtensions.NUI/Skia/SKGLSurfaceView.cs index 578c88e..657cf3d 100644 --- a/src/Tizen.UIExtensions.NUI/Skia/SKGLSurfaceView.cs +++ b/src/Tizen.UIExtensions.NUI/Skia/SKGLSurfaceView.cs @@ -72,6 +72,10 @@ protected override void OnDrawFrame() _nativeImageSource.EnqueueBuffer(buffer); Window.Instance.KeepRendering(0); } + else + { + Invalidate(); + } } protected override void OnResized() diff --git a/test/NUIExGallery/TC/MaterialIconButtonTest.cs b/test/NUIExGallery/TC/MaterialIconButtonTest.cs new file mode 100644 index 0000000..7a21ce8 --- /dev/null +++ b/test/NUIExGallery/TC/MaterialIconButtonTest.cs @@ -0,0 +1,129 @@ +using System; +using System.Linq; +using Tizen.NUI; +using Tizen.NUI.BaseComponents; +using Tizen.UIExtensions.Common; +using Tizen.UIExtensions.Common.GraphicsView; +using Tizen.UIExtensions.NUI; +using Tizen.UIExtensions.NUI.GraphicsView; +using Color = Tizen.UIExtensions.Common.Color; + +namespace NUIExGallery.TC +{ + public class MaterialIconButtonTest : TestCaseBase + { + public override string TestName => "MaterialIconButton Test"; + + public override string TestDescription => "MaterialIconButton test1"; + + public override View Run() + { + var scrollview = new Tizen.UIExtensions.NUI.ScrollView(); + + scrollview.ContentContainer.Layout = new LinearLayout + { + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, + LinearOrientation = LinearLayout.Orientation.Vertical, + }; + + var view = scrollview.ContentContainer; + + view.Add(new Label + { + Text = "MaterialIconButton", + TextColor = Color.White, + FontSize = 9, + FontAttributes = FontAttributes.Bold, + VerticalTextAlignment = TextAlignment.Center, + WidthSpecification = LayoutParamPolicies.MatchParent, + SizeHeight = 100, + Padding = new Extents(20, 10, 10, 10), + BackgroundColor = Color.FromHex("#2196f3").ToNative(), + BoxShadow = new Shadow(5, Color.FromHex("#bbbbbb").ToNative(), new Vector2(0, 5)) + }); + + view.Add(new View + { + SizeHeight = 20, + }); + + foreach (var icon in Enum.GetValues(typeof(MaterialIcons))) + { + view.Add(new Label + { + Padding = new Extents(10, 0, 0, 0), + Text = icon.ToString(), + FontSize = 7, + HorizontalTextAlignment = TextAlignment.Start, + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.WrapContent, + }); + + { + var button = new MaterialIconButton() + { + Icon = (MaterialIcons)icon, + }; + + button.SizeHeight = (float)button.Measure(300, 300).Height; + button.SizeWidth = (float)button.Measure(300, 300).Width; + view.Add(button); + } + } + + + foreach (var icon in Enum.GetValues(typeof(MaterialIcons)).Cast().Take(3)) + { + view.Add(new Label + { + Padding = new Extents(10, 0, 0, 0), + Text = icon.ToString(), + FontSize = 7, + HorizontalTextAlignment = TextAlignment.Start, + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.WrapContent, + }); + + { + var button = new MaterialIconButton() + { + Icon = icon, + }; + + button.SizeHeight = 100; + button.SizeWidth = 100; + button.UpdateBackgroundColor(Color.Yellow); + view.Add(button); + } + } + + foreach (var icon in Enum.GetValues(typeof(MaterialIcons)).Cast().Take(3)) + { + view.Add(new Label + { + Padding = new Extents(10, 0, 0, 0), + Text = icon.ToString(), + FontSize = 7, + HorizontalTextAlignment = TextAlignment.Start, + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.WrapContent, + }); + + { + var button = new MaterialIconButton() + { + Icon = icon, + }; + + button.SizeHeight = (float)DeviceInfo.ScalingFactor * 10; + button.SizeWidth = (float)DeviceInfo.ScalingFactor * 10; + button.UpdateBackgroundColor(Color.Yellow); + view.Add(button); + } + } + + return scrollview; + } + } +} From 00a4dd40e7ccf30f1dabdca8b7f1a9b22720497d Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Wed, 20 Oct 2021 15:29:27 +0900 Subject: [PATCH 36/76] Fix GraphicsView Editor/Entry --- .../GraphicsView/Editor.cs | 110 +++++++++++++---- .../GraphicsView/Entry.cs | 111 ++++++++++++++---- 2 files changed, 179 insertions(+), 42 deletions(-) diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/Editor.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/Editor.cs index e42cf1c..cf5f2e8 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/Editor.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/Editor.cs @@ -8,26 +8,38 @@ namespace Tizen.UIExtensions.NUI.GraphicsView /// /// A control that can edit multiple lines of text. /// - public class Editor : GraphicsView, IEditor + public class Editor : View, IEditor, IMeasurable { + GrapchisEditor _editor; + /// /// Initializes a new instance of the Editor class. /// public Editor() { - Layout = new Tizen.NUI.LinearLayout(); + Layout = new Tizen.NUI.AbsoluteLayout(); EmbedEditor = new TEditor { WidthSpecification = LayoutParamPolicies.MatchParent, HeightSpecification = LayoutParamPolicies.MatchParent, Margin = new Tizen.NUI.Extents((ushort)(12 * DeviceInfo.ScalingFactor), (ushort)(12 * DeviceInfo.ScalingFactor), (ushort)(20 * DeviceInfo.ScalingFactor), (ushort)(12 * DeviceInfo.ScalingFactor)) }; - EmbedEditor.FocusGained += OnFocused; - EmbedEditor.FocusLost += OnUnfocused; - EmbedEditor.TextChanged += OnTextChanged; - Add(EmbedEditor); + _editor = new GrapchisEditor(EmbedEditor) + { + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.MatchParent, + }; + Add(_editor); - Drawable = new EditorDrawable(this); + // NUI AbsoluteLayout not support Margin, so add a internal view as LinearLayout + var marginView = new View + { + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.MatchParent, + Layout = new Tizen.NUI.LinearLayout(), + }; + Add(marginView); + marginView.Add(EmbedEditor); } public TEditor EmbedEditor { get; } @@ -37,8 +49,8 @@ public Editor() /// public string Text { - get => EmbedEditor.Text; - set => EmbedEditor.Text = value; + get => _editor.Text; + set => _editor.Text = value; } /// @@ -46,8 +58,8 @@ public string Text /// public Color TextColor { - get => EmbedEditor.TextColor; - set => EmbedEditor.TextColor = value; + get => _editor.TextColor; + set => _editor.TextColor = value; } /// @@ -55,8 +67,8 @@ public Color TextColor /// public string Placeholder { - get => GetProperty(nameof(Placeholder)); - set => SetProperty(nameof(Placeholder), value); + get => _editor.Placeholder; + set => _editor.Placeholder = value; } /// @@ -64,8 +76,13 @@ public string Placeholder /// public Color PlaceholderColor { - get => GetProperty(nameof(PlaceholderColor)); - set => SetProperty(nameof(PlaceholderColor), value); + get => _editor.PlaceholderColor; + set => _editor.PlaceholderColor = value; + } + + public bool IsFocused + { + get => _editor.IsFocused; } /// @@ -73,16 +90,69 @@ public Color PlaceholderColor /// public new Color BackgroundColor { - get => GetProperty(nameof(BackgroundColor)); - set => SetProperty(nameof(BackgroundColor), value); + get => _editor.BackgroundColor; + set => _editor.BackgroundColor = value; } Color IEditor.BackgroundColor => BackgroundColor; - public bool IsFocused => EmbedEditor.HasFocus(); - void OnTextChanged(object? sender, TextEditor.TextChangedEventArgs e) + public Size Measure(double availableWidth, double availableHeight) { - Invalidate(); + return _editor.Measure(availableWidth, availableHeight); + } + + class GrapchisEditor : GraphicsView, IEditor + { + public GrapchisEditor(TEditor editor) + { + EmbedEditor = editor; + EmbedEditor.FocusGained += OnFocused; + EmbedEditor.FocusLost += OnUnfocused; + EmbedEditor.TextChanged += OnTextChanged; + + Drawable = new EditorDrawable(this); + } + + public TEditor EmbedEditor { get; } + + public string Text + { + get => EmbedEditor.Text; + set => EmbedEditor.Text = value; + } + + public Color TextColor + { + get => EmbedEditor.TextColor; + set => EmbedEditor.TextColor = value; + } + + public string Placeholder + { + get => GetProperty(nameof(Placeholder)); + set => SetProperty(nameof(Placeholder), value); + } + + public Color PlaceholderColor + { + get => GetProperty(nameof(PlaceholderColor)); + set => SetProperty(nameof(PlaceholderColor), value); + } + + public new Color BackgroundColor + { + get => GetProperty(nameof(BackgroundColor)); + set => SetProperty(nameof(BackgroundColor), value); + } + Color IEditor.BackgroundColor => BackgroundColor; + + public bool IsFocused => EmbedEditor.HasFocus(); + + void OnTextChanged(object? sender, TextEditor.TextChangedEventArgs e) + { + Invalidate(); + } } } + } diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/Entry.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/Entry.cs index b14ff2d..66fb4f8 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/Entry.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/Entry.cs @@ -8,14 +8,16 @@ namespace Tizen.UIExtensions.NUI.GraphicsView /// /// A control that can edit a single line of text. /// - public class Entry : GraphicsView, IEntry + public class Entry : View, IEntry, IMeasurable { + GraphcisEntry _entry; + /// /// Initializes a new instance of the Entry class. /// public Entry() { - Layout = new Tizen.NUI.LinearLayout(); + Layout = new Tizen.NUI.AbsoluteLayout(); EmbedEntry = new TEntry { WidthSpecification = LayoutParamPolicies.MatchParent, @@ -23,12 +25,22 @@ public Entry() VerticalTextAlignment = TextAlignment.Center, Margin = new Tizen.NUI.Extents((ushort)(12 * DeviceInfo.ScalingFactor), (ushort)(40 * DeviceInfo.ScalingFactor), (ushort)(12 * DeviceInfo.ScalingFactor), 0) }; - EmbedEntry.FocusGained += OnFocused; - EmbedEntry.FocusLost += OnUnfocused; - EmbedEntry.TextChanged += OnTextChanged; - Add(EmbedEntry); + _entry = new GraphcisEntry(EmbedEntry) + { + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.MatchParent, + }; + Add(_entry); - Drawable = new EntryDrawable(this); + // NUI AbsoluteLayout not support Margin, so add a internal view as LinearLayout + var marginView = new View + { + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.MatchParent, + Layout = new Tizen.NUI.LinearLayout(), + }; + Add(marginView); + marginView.Add(EmbedEntry); } public TEntry EmbedEntry { get; } @@ -36,10 +48,10 @@ public Entry() /// /// Gets or sets the text of the entry /// - public string Text + public string Text { - get => EmbedEntry.Text; - set => EmbedEntry.Text = value; + get => _entry.Text; + set => _entry.Text = value; } /// @@ -47,8 +59,8 @@ public string Text /// public Color TextColor { - get => EmbedEntry.TextColor; - set => EmbedEntry.TextColor = value; + get => _entry.TextColor; + set => _entry.TextColor = value; } /// @@ -56,8 +68,8 @@ public Color TextColor /// public string Placeholder { - get => GetProperty(nameof(Placeholder)); - set => SetProperty(nameof(Placeholder), value); + get => _entry.Placeholder; + set => _entry.Placeholder = value; } /// @@ -65,25 +77,80 @@ public string Placeholder /// public Color PlaceholderColor { - get => GetProperty(nameof(PlaceholderColor)); - set => SetProperty(nameof(PlaceholderColor), value); + get => _entry.PlaceholderColor; + set => _entry.PlaceholderColor = value; } + public bool IsFocused => _entry.IsFocused; + /// /// Gets or sets the color which will fill the background /// public new Color BackgroundColor { - get => GetProperty(nameof(BackgroundColor)); - set => SetProperty(nameof(BackgroundColor), value); + get => _entry.BackgroundColor; + set => _entry.BackgroundColor = value; } - Color IEntry.BackgroundColor => BackgroundColor; + Color IEntry.BackgroundColor => _entry.BackgroundColor; - public bool IsFocused => EmbedEntry.HasFocus(); + public Size Measure(double availableWidth, double availableHeight) + { + return _entry.Measure(availableWidth, availableHeight); + } - void OnTextChanged(object? sender, TextField.TextChangedEventArgs e) + class GraphcisEntry : GraphicsView, IEntry { - Invalidate(); + public GraphcisEntry(TEntry entry) + { + EmbedEntry = entry; + EmbedEntry.FocusGained += OnFocused; + EmbedEntry.FocusLost += OnUnfocused; + EmbedEntry.TextChanged += OnTextChanged; + + Drawable = new EntryDrawable(this); + } + + public TEntry EmbedEntry { get; } + + public string Text + { + get => EmbedEntry.Text; + set => EmbedEntry.Text = value; + } + + public Color TextColor + { + get => EmbedEntry.TextColor; + set => EmbedEntry.TextColor = value; + } + + public string Placeholder + { + get => GetProperty(nameof(Placeholder)); + set => SetProperty(nameof(Placeholder), value); + } + + + public Color PlaceholderColor + { + get => GetProperty(nameof(PlaceholderColor)); + set => SetProperty(nameof(PlaceholderColor), value); + } + + public new Color BackgroundColor + { + get => GetProperty(nameof(BackgroundColor)); + set => SetProperty(nameof(BackgroundColor), value); + } + Color IEntry.BackgroundColor => BackgroundColor; + + public bool IsFocused => EmbedEntry.HasFocus(); + + void OnTextChanged(object? sender, TextField.TextChangedEventArgs e) + { + Invalidate(); + } } } + } From cb08899ecdfb52140d52d8a540eb49146008760e Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Thu, 21 Oct 2021 09:39:47 +0900 Subject: [PATCH 37/76] Set default size of MaterialIconButton --- src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs index cb4fef1..b6a8788 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs @@ -15,6 +15,9 @@ public class MaterialIconButton : GraphicsView public MaterialIconButton() { Drawable = new MaterialIconDrawable(); + var measured = Drawable.Measure(double.PositiveInfinity, double.PositiveInfinity); + SizeWidth = (float)measured.Width; + SizeHeight = (float)measured.Height; } /// From 312cb7d05d7e61fe020b7d9d01dab5863c99efc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=EA=B7=BC/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/Staff=20Engineer/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Thu, 21 Oct 2021 13:40:27 +0900 Subject: [PATCH 38/76] [NUI] Add TitleView (#101) * [NUI] Add TitleView * Update description and CollectionChanged handler --- src/Tizen.UIExtensions.NUI/TitleView.cs | 220 ++++++++++++++++++++++++ test/NUIExGallery/TC/TitleViewTest.cs | 165 ++++++++++++++++++ 2 files changed, 385 insertions(+) create mode 100644 src/Tizen.UIExtensions.NUI/TitleView.cs create mode 100644 test/NUIExGallery/TC/TitleViewTest.cs diff --git a/src/Tizen.UIExtensions.NUI/TitleView.cs b/src/Tizen.UIExtensions.NUI/TitleView.cs new file mode 100644 index 0000000..b45defb --- /dev/null +++ b/src/Tizen.UIExtensions.NUI/TitleView.cs @@ -0,0 +1,220 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using Tizen.NUI; +using Tizen.NUI.BaseComponents; +using Tizen.UIExtensions.Common; +using TColor = Tizen.UIExtensions.Common.Color; + +namespace Tizen.UIExtensions.NUI +{ + /// + /// A View to present the title area + /// + public class TitleView : View + { + Label _label; + View _contentContainer; + View _actionsContainer; + View? _icon; + View? _content; + ObservableCollection _actions = new ObservableCollection(); + + /// + /// Initializes a new instance of the TitleView class. + /// +#pragma warning disable CS8618 + public TitleView() +#pragma warning restore CS8618 + { + Initialize(); + } + + /// + /// Title text + /// + public string Title + { + get => _label.Text; + set => _label.Text = value; + } + + /// + /// Gets a Label for title + /// + public Label Label => _label; + + /// + /// Views for placing at right side of title + /// Ownership of view is moved to TitleView + /// Only support Add/Remove/Reset method + /// + public IList Actions => _actions; + + /// + /// A view placed at left size of title + /// Ownership of view is moved to TitleView + /// + public View? Icon + { + get => _icon; + set + { + if (_icon != null) + { + Remove(Icon); + _icon.Dispose(); + } + _icon = value; + + if (_icon != null) + { + Add(_icon); + if (!(_icon.Layout is LayoutGroup)) + { + _icon.Layout = new AbsoluteLayout(); + } + (_icon.Layout as LayoutGroup)?.ChangeLayoutSiblingOrder(0); + _icon.Margin = new Extents(0, 20, 0, 0); + } + } + } + + /// + /// A View placed at beside of title + /// Ownership of view is moved to TitleView + /// + public View? Content + { + get => _content; + set + { + if (_content != null) + { + _contentContainer.Remove(_content); + _content.Dispose(); + } + _content = value; + + if (_content != null) + { + _contentContainer.Add(_content); + } + } + } + + void Initialize() + { + Layout = new LinearLayout + { + LinearOrientation = LinearLayout.Orientation.Horizontal, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Begin, + }; + + SizeHeight = (float)(DeviceInfo.ScalingFactor * 50); + WidthSpecification = LayoutParamPolicies.MatchParent; + + Padding = new Extents(20, 20, 10, 10); + BackgroundColor = TColor.FromHex("#2196f3").ToNative(); + BoxShadow = new Shadow(5, TColor.FromHex("#bbbbbb").ToNative(), new Vector2(0, 5)); + + _contentContainer = new View + { + Layout = new LinearLayout + { + LinearOrientation = LinearLayout.Orientation.Horizontal, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Begin, + }, + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.MatchParent, + }; + Add(_contentContainer); + + _label = new Label + { + FontSize = 9, + TextColor = TColor.White, + FontAttributes = FontAttributes.Bold, + VerticalTextAlignment = TextAlignment.Center, + }; + _contentContainer.Add(_label); + + _actionsContainer = new View + { + Layout = new LinearLayout + { + LinearOrientation = LinearLayout.Orientation.Horizontal, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.End, + }, + HeightSpecification = LayoutParamPolicies.MatchParent, + WidthSpecification = LayoutParamPolicies.WrapContent, + }; + Add(_actionsContainer); + + _actions.CollectionChanged += OnActionCollectionChanged; + } + + void OnActionCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null) + { + foreach (var item in e.NewItems) + { + if (item is View view) + { + view.Margin = new Extents(10, 10, 0, 0); + _actionsContainer.Add(view); + } + } + } + else if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null) + { + foreach (var item in e.OldItems) + { + if (item is View view) + { + _actionsContainer.Remove(view); + view.Dispose(); + } + } + } + else if (e.Action == NotifyCollectionChangedAction.Reset) + { + var toBeRemoved = _actionsContainer.Children.ToList(); + foreach (var child in toBeRemoved) + { + _actionsContainer.Remove(child); + child.Dispose(); + } + + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (_icon != null) + { + _icon.Unparent(); + _icon.Dispose(); + _icon = null; + } + if (_content != null) + { + _content.Unparent(); + _content.Dispose(); + _content = null; + } + if (_actions.Count > 0) + { + _actions.Clear(); + } + } + base.Dispose(disposing); + } + } +} diff --git a/test/NUIExGallery/TC/TitleViewTest.cs b/test/NUIExGallery/TC/TitleViewTest.cs new file mode 100644 index 0000000..ddab466 --- /dev/null +++ b/test/NUIExGallery/TC/TitleViewTest.cs @@ -0,0 +1,165 @@ +using Tizen.NUI; +using Tizen.NUI.BaseComponents; +using Tizen.UIExtensions.Common; +using Tizen.UIExtensions.Common.GraphicsView; +using Tizen.UIExtensions.NUI; +using Tizen.UIExtensions.NUI.GraphicsView; +using TColor = Tizen.UIExtensions.Common.Color; + +namespace NUIExGallery.TC +{ + public class TitleViewTest : TestCaseBase + { + public override string TestName => "TitleView Test"; + + public override string TestDescription => "TitleView test1"; + + public override View Run() + { + var scrollview = new Tizen.UIExtensions.NUI.ScrollView(); + + scrollview.ContentContainer.Layout = new LinearLayout + { + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, + LinearOrientation = LinearLayout.Orientation.Vertical, + }; + + var view = scrollview.ContentContainer; + + view.UpdateBackgroundColor(TColor.FromHex("#eeeeee")); + + view.Add(new TitleView + { + Title = "Title view 1", + Icon = new MaterialIconButton + { + Icon = MaterialIcons.ArrowBack, + SizeWidth = (float)(25 * DeviceInfo.ScalingFactor), + SizeHeight = (float)(25 * DeviceInfo.ScalingFactor), + Color = TColor.White + }, + Actions = + { + new MaterialIconButton + { + Icon = MaterialIcons.MoreVert, + SizeWidth = (float)(25 * DeviceInfo.ScalingFactor), + SizeHeight = (float)(25 * DeviceInfo.ScalingFactor), + Color = TColor.White + }, + } + }); + + view.Add(new View + { + SizeHeight = 20, + }); + + var title2 = new TitleView + { + Title = "Title view 2" + }; + title2.UpdateBackgroundColor(TColor.White); + title2.Label.TextColor = TColor.Black; + title2.Icon = new MaterialIconButton + { + Icon = MaterialIcons.Menu, + SizeWidth = (float)(25 * DeviceInfo.ScalingFactor), + SizeHeight = (float)(25 * DeviceInfo.ScalingFactor), + }; + title2.Actions.Add(new MaterialIconButton + { + Icon = MaterialIcons.Close, + SizeWidth = (float)(25 * DeviceInfo.ScalingFactor), + SizeHeight = (float)(25 * DeviceInfo.ScalingFactor), + }); + + + view.Add(title2); + + view.Add(new View + { + SizeHeight = 20, + }); + + var title3 = new TitleView(); + title3.UpdateBackgroundColor(TColor.FromHex("6200EE")); + title3.Icon = new MaterialIconButton + { + Icon = MaterialIcons.ArrowBack, + Color = TColor.White, + SizeWidth = (float)(25 * DeviceInfo.ScalingFactor), + SizeHeight = (float)(25 * DeviceInfo.ScalingFactor), + }; + title3.Content = new Tizen.UIExtensions.NUI.Entry + { + PlaceholderText = "Search", + PlaceholderColor = TColor.FromHex("#222222"), + VerticalTextAlignment = TextAlignment.Center, + HeightSpecification = LayoutParamPolicies.MatchParent, + WidthSpecification = LayoutParamPolicies.MatchParent, + Margin = new Extents(0, 10, 5, 5), + }; + title3.Content.UpdateBackgroundColor(TColor.FromHex("#eeeeee")); + title3.Icon = new MaterialIconButton + { + Icon = MaterialIcons.Check, + SizeWidth = (float)(25 * DeviceInfo.ScalingFactor), + SizeHeight = (float)(25 * DeviceInfo.ScalingFactor), + Color = TColor.White, + }; + view.Add(title3); + + + view.Add(new View + { + SizeHeight = 20, + }); + + var title4 = new TitleView(); + title4.UpdateBackgroundColor(TColor.FromHex("6200EE")); + title4.Icon = new MaterialIconButton + { + Icon = MaterialIcons.ArrowBack, + Color = TColor.White, + SizeWidth = (float)(25 * DeviceInfo.ScalingFactor), + SizeHeight = (float)(25 * DeviceInfo.ScalingFactor), + }; + title4.Content = new Tizen.UIExtensions.NUI.Entry + { + PlaceholderText = "Search", + PlaceholderColor = TColor.FromHex("#222222"), + VerticalTextAlignment = TextAlignment.Center, + HeightSpecification = LayoutParamPolicies.MatchParent, + WidthSpecification = LayoutParamPolicies.MatchParent, + Margin = new Extents(0, 10, 5, 5), + }; + title4.Content.UpdateBackgroundColor(TColor.FromHex("#eeeeee")); + title4.Icon = new MaterialIconButton + { + Icon = MaterialIcons.Menu, + SizeWidth = (float)(25 * DeviceInfo.ScalingFactor), + SizeHeight = (float)(25 * DeviceInfo.ScalingFactor), + Color = TColor.White, + }; + title4.Actions.Add(new MaterialIconButton + { + Icon = MaterialIcons.Check, + SizeWidth = (float)(25 * DeviceInfo.ScalingFactor), + SizeHeight = (float)(25 * DeviceInfo.ScalingFactor), + Color = TColor.White, + }); + title4.Actions.Add(new MaterialIconButton + { + Icon = MaterialIcons.Close, + SizeWidth = (float)(25 * DeviceInfo.ScalingFactor), + SizeHeight = (float)(25 * DeviceInfo.ScalingFactor), + Color = TColor.White, + }); + view.Add(title4); + + return scrollview; + } + } +} From b2110295d6428a2b985446193e51908f64c4b1d5 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Thu, 21 Oct 2021 16:07:50 +0900 Subject: [PATCH 39/76] Fix SKClipperView - change nui callback method that notify size changed --- .../Skia/SKClipperView.cs | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/Tizen.UIExtensions.NUI/Skia/SKClipperView.cs b/src/Tizen.UIExtensions.NUI/Skia/SKClipperView.cs index b04b1da..2afc808 100644 --- a/src/Tizen.UIExtensions.NUI/Skia/SKClipperView.cs +++ b/src/Tizen.UIExtensions.NUI/Skia/SKClipperView.cs @@ -38,7 +38,6 @@ public class SKClipperView : NView "}\n" + ""; - PropertyNotification _resized; Renderer _renderer; Geometry _geometry; Shader _shader; @@ -60,12 +59,14 @@ public class SKClipperView : NView /// public SKClipperView() { + Layout = new CustomLayout + { + SizeUpdated = OnResized + }; ClippingMode = ClippingModeType.ClipChildren; MainloopContext = SynchronizationContext.Current ?? throw new InvalidOperationException("Must create on main thread"); _geometry = CreateQuadGeometry(); _shader = new Shader(VERTEX_SHADER, FRAGMENT_SHADER); - _resized = AddPropertyNotification("Size", PropertyCondition.Step(0.1f)); - _resized.Notified += OnResized; RemoveRenderer(0); @@ -167,11 +168,6 @@ void UpdateSurface() _bufferQueue = new NativeImageQueue((uint)Size.Width, (uint)Size.Height, NativeImageQueue.ColorFormat.RGBA8888); } - void OnResized(object source, PropertyNotification.NotifyEventArgs e) - { - OnResized(); - } - static Geometry CreateQuadGeometry() { PropertyBuffer vertexData = CreateVertextBuffer(); @@ -225,5 +221,25 @@ public Vec2(float xIn, float yIn) y = yIn; } } + + class CustomLayout : AbsoluteLayout + { + float _width; + float _height; + + public Action? SizeUpdated { get; set; } + + protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom) + { + var sizeChanged = _width != Owner.SizeWidth || _height != Owner.SizeHeight; + _width = Owner.SizeWidth; + _height = Owner.SizeHeight; + if (sizeChanged) + { + SizeUpdated?.Invoke(); + } + base.OnLayout(changed, left, top, right, bottom); + } + } } } From 3b71f12c4105fddb77c83e9f19c3ac51315fc165 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Fri, 22 Oct 2021 12:04:51 +0900 Subject: [PATCH 40/76] Fix Common project projitems --- .../Tizen.UIExtensions.Common.projitems | 70 +------------------ 1 file changed, 2 insertions(+), 68 deletions(-) diff --git a/src/Tizen.UIExtensions.Common/Tizen.UIExtensions.Common.projitems b/src/Tizen.UIExtensions.Common/Tizen.UIExtensions.Common.projitems index 18dcc0a..72d9567 100644 --- a/src/Tizen.UIExtensions.Common/Tizen.UIExtensions.Common.projitems +++ b/src/Tizen.UIExtensions.Common/Tizen.UIExtensions.Common.projitems @@ -9,72 +9,6 @@ Tizen.UIExtensions.Common - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - \ No newline at end of file + From a2532af3efb52db121dab0170dbcbd60a997ec97 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Fri, 22 Oct 2021 12:43:09 +0900 Subject: [PATCH 41/76] Update SkiaSharp.View.Tizen.ScalingInfo ScalingFactor --- src/Tizen.UIExtensions.ElmSharp/DeviceInfo.cs | 1 + .../Tizen.UIExtensions.ElmSharp.csproj | 2 +- src/Tizen.UIExtensions.NUI/DeviceInfo.cs | 1 + src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Tizen.UIExtensions.ElmSharp/DeviceInfo.cs b/src/Tizen.UIExtensions.ElmSharp/DeviceInfo.cs index 892d651..1c034b4 100644 --- a/src/Tizen.UIExtensions.ElmSharp/DeviceInfo.cs +++ b/src/Tizen.UIExtensions.ElmSharp/DeviceInfo.cs @@ -258,6 +258,7 @@ static void UpdateScalingFactor() } } s_scalingFactor = scalingFactor; + SkiaSharp.Views.Tizen.ScalingInfo.SetScalingFactor(scalingFactor); } } diff --git a/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj b/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj index 49bf9bb..ad7a238 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj +++ b/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/Tizen.UIExtensions.NUI/DeviceInfo.cs b/src/Tizen.UIExtensions.NUI/DeviceInfo.cs index ad2b349..3839555 100644 --- a/src/Tizen.UIExtensions.NUI/DeviceInfo.cs +++ b/src/Tizen.UIExtensions.NUI/DeviceInfo.cs @@ -141,6 +141,7 @@ static void UpdateScalingFactor() } } s_scalingFactor = scalingFactor; + SkiaSharp.Views.Tizen.ScalingInfo.SetScalingFactor(scalingFactor); } } diff --git a/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj b/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj index 36d25c4..d8917ed 100644 --- a/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj +++ b/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj @@ -24,7 +24,7 @@ - + From a3e30674c09eea2e65ae28de91e6ca2bfda5d007 Mon Sep 17 00:00:00 2001 From: Kangho Hur Date: Fri, 29 Oct 2021 18:35:26 +0900 Subject: [PATCH 42/76] Update MicrosoftGraphics package version --- .../Tizen.UIExtensions.ElmSharp.csproj | 4 ++-- src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj b/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj index ad7a238..ead768a 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj +++ b/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj @@ -25,8 +25,8 @@ - - + + diff --git a/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj b/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj index d8917ed..5d76682 100644 --- a/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj +++ b/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj @@ -22,8 +22,8 @@ - - + + From 2fcf0f9dd44997b6ba4188af73a2a45a5871f194 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Tue, 2 Nov 2021 16:01:53 +0900 Subject: [PATCH 43/76] Fix NavigationStack.Remove --- src/Tizen.UIExtensions.NUI/NavigationStack.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Tizen.UIExtensions.NUI/NavigationStack.cs b/src/Tizen.UIExtensions.NUI/NavigationStack.cs index 9c623ba..0ceb312 100644 --- a/src/Tizen.UIExtensions.NUI/NavigationStack.cs +++ b/src/Tizen.UIExtensions.NUI/NavigationStack.cs @@ -186,6 +186,16 @@ public void Insert(View before, View view) UpdateTopView(); } + /// + /// Removes a view in the navigation stack + /// + /// The view to remove + public void Pop(View view) + { + InternalStack.Remove(view); + Remove(view); + } + void UpdateTopView() { if (_lastTop != InternalStack.LastOrDefault()) From 474bc107ff2f6d4f80a6cb2a23babd94094af126 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Wed, 17 Nov 2021 16:57:51 +0900 Subject: [PATCH 44/76] Update looks of GraphicsViewDrawable to react IsEnabled property --- .../GraphicsView/ButtonDrawable.cs | 2 +- .../GraphicsView/CheckBoxDrawable.cs | 23 ++++++++++++---- .../GraphicsView/GraphicsViewDrawable.cs | 3 +++ .../GraphicsView/SliderDrawable.cs | 7 ++--- .../GraphicsView/StepperDrawable.cs | 12 +++++++++ .../GraphicsView/SwitchDrawable.cs | 9 ++++--- .../GraphicsView/GraphicsView.cs | 2 ++ .../Extensions/ViewExtensions.cs | 4 +++ .../GraphicsView/GraphicsView.cs | 17 +++++++++--- test/NUIExGallery/TC/GraphicsViewTest.cs | 26 +++++++++++++++++++ 10 files changed, 88 insertions(+), 17 deletions(-) diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/ButtonDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/ButtonDrawable.cs index a55ad29..84ca2ad 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/ButtonDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/ButtonDrawable.cs @@ -61,7 +61,7 @@ void DrawMaterialButtonBackground(ICanvas canvas, RectangleF dirtyRect) { canvas.SaveState(); - canvas.FillColor = View.BackgroundColor.ToGraphicsColor(Material.Color.Blue); + canvas.FillColor = IsEnabled ? View.BackgroundColor.ToGraphicsColor(Material.Color.Blue) : GColor.FromArgb(Material.Color.Gray1); var x = dirtyRect.X; var y = dirtyRect.Y; diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/CheckBoxDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/CheckBoxDrawable.cs index f60975c..84f2c7b 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/CheckBoxDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/CheckBoxDrawable.cs @@ -1,4 +1,5 @@ using Microsoft.Maui.Graphics; +using GColor = Microsoft.Maui.Graphics.Color; using GPoint = Microsoft.Maui.Graphics.Point; using TSize = Tizen.UIExtensions.Common.Size; @@ -57,17 +58,29 @@ void DrawMaterialCheckBoxBackground(ICanvas canvas, RectangleF dirtyRect) y += (dirtyRect.Height - size) / 2; } - if (View.IsChecked) + if (IsEnabled) { - canvas.FillColor = View.Color.ToGraphicsColor(Material.Color.Blue); - canvas.FillRoundedRectangle(x, y, size, size, 2); + if (View.IsChecked) + { + canvas.FillColor = View.Color.ToGraphicsColor(Material.Color.Blue); + canvas.FillRoundedRectangle(x, y, size, size, 2); + } + else + { + var strokeWidth = 2; + + canvas.StrokeSize = strokeWidth; + canvas.StrokeColor = View.Color.ToGraphicsColor(Material.Color.Gray1); + canvas.DrawRoundedRectangle(x + strokeWidth / 2, y + strokeWidth / 2, size - strokeWidth, size - strokeWidth, 2); + } } else { var strokeWidth = 2; - + canvas.FillColor = GColor.FromArgb(Material.Color.Gray2); + canvas.FillRoundedRectangle(x, y, size, size, 2); canvas.StrokeSize = strokeWidth; - canvas.StrokeColor = View.Color.ToGraphicsColor(Material.Color.Gray1); + canvas.StrokeColor = GColor.FromArgb(Material.Color.Gray1); canvas.DrawRoundedRectangle(x + strokeWidth / 2, y + strokeWidth / 2, size - strokeWidth, size - strokeWidth, 2); } diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/GraphicsViewDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/GraphicsViewDrawable.cs index bd8588c..52c57ad 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/GraphicsViewDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/GraphicsViewDrawable.cs @@ -8,6 +8,9 @@ namespace Tizen.UIExtensions.Common.GraphicsView public abstract class GraphicsViewDrawable : IDrawable, IMeasurable, IDisposable { public event EventHandler? Invalidated; + + public bool IsEnabled { get; set; } = true; + public abstract void Draw(ICanvas canvas, RectangleF dirtyRect); public virtual void OnTouchDown(GPoint point) { } diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/SliderDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/SliderDrawable.cs index 2cf68b4..391c4a0 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/SliderDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/SliderDrawable.cs @@ -1,5 +1,6 @@ using Microsoft.Maui.Graphics; using Tizen.UIExtensions.Common.Internal; +using GColor = Microsoft.Maui.Graphics.Color; using GPoint = Microsoft.Maui.Graphics.Point; using TSize = Tizen.UIExtensions.Common.Size; @@ -84,7 +85,7 @@ void DrawMaterialSliderTrackBackground(ICanvas canvas, RectangleF dirtyRect) { canvas.SaveState(); - canvas.FillColor = View.MaximumTrackColor.ToGraphicsColor(Material.Color.LightBlue); + canvas.FillColor = IsEnabled ? View.MaximumTrackColor.ToGraphicsColor(Material.Color.LightBlue) : GColor.FromArgb(Material.Color.Gray1); var x = dirtyRect.X; @@ -104,7 +105,7 @@ protected virtual void DrawMaterialSliderTrackProgress(ICanvas canvas, Rectangle { canvas.SaveState(); - canvas.FillColor = View.MinimumTrackColor.ToGraphicsColor(Material.Color.Blue); + canvas.FillColor = IsEnabled ? View.MinimumTrackColor.ToGraphicsColor(Material.Color.Blue) : GColor.FromArgb(Material.Color.Gray1); var x = dirtyRect.X; @@ -135,7 +136,7 @@ protected virtual void DrawMaterialSliderThumb(ICanvas canvas, RectangleF dirtyR var y = (float)((dirtyRect.Height - MaterialFloatThumb) / 2); - canvas.FillColor = View.ThumbColor.ToGraphicsColor(Material.Color.Blue); + canvas.FillColor = IsEnabled ? View.ThumbColor.ToGraphicsColor(Material.Color.Blue) : GColor.FromArgb(Material.Color.Gray1); canvas.FillEllipse(x, y, MaterialFloatThumb, MaterialFloatThumb); diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/StepperDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/StepperDrawable.cs index a828632..e00f8de 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/StepperDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/StepperDrawable.cs @@ -90,6 +90,12 @@ void DrawMaterialStepperMinus(ICanvas canvas, RectangleF dirtyRect) canvas.DrawRoundedRectangle(x, y, width, height, 6); + if (!IsEnabled) + { + canvas.FillColor = GColor.FromArgb(Material.Color.Gray1); + canvas.FillRoundedRectangle(x, y, width, height, 6); + } + canvas.Translate(20, 20); var vBuilder = new PathBuilder(); @@ -118,6 +124,12 @@ void DrawMaterialStepperPlus(ICanvas canvas, RectangleF dirtyRect) canvas.DrawRoundedRectangle(x, y, width, height, 6); + if (!IsEnabled) + { + canvas.FillColor = GColor.FromArgb(Material.Color.Gray1); + canvas.FillRoundedRectangle(x, y, width, height, 6); + } + canvas.Translate(80, 14); var vBuilder = new PathBuilder(); diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/SwitchDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/SwitchDrawable.cs index d47ce15..5c5938e 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/SwitchDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/SwitchDrawable.cs @@ -1,5 +1,6 @@ using Microsoft.Maui.Graphics; using Tizen.UIExtensions.Common.Internal; +using GColor = Microsoft.Maui.Graphics.Color; using GPoint = Microsoft.Maui.Graphics.Point; using TSize = Tizen.UIExtensions.Common.Size; @@ -55,12 +56,12 @@ void DrawMaterialSwitchBackground(ICanvas canvas, RectangleF dirtyRect) if (View.IsToggled) { - canvas.FillColor = View.OnColor.ToGraphicsColor(Material.Color.LightBlue); + canvas.FillColor = IsEnabled ? View.OnColor.ToGraphicsColor(Material.Color.LightBlue) : GColor.FromArgb(Material.Color.Gray1); canvas.Alpha = 0.5f; } else { - canvas.FillColor = View.BackgroundColor.ToGraphicsColor(Material.Color.Gray2); + canvas.FillColor = IsEnabled ? View.BackgroundColor.ToGraphicsColor(Material.Color.Gray2) : GColor.FromArgb(Material.Color.Gray1); canvas.Alpha = 1.0f; } @@ -82,9 +83,9 @@ void DrawMaterialSwitchThumb(ICanvas canvas, RectangleF dirtyRect) canvas.SaveState(); if (View.IsToggled) - canvas.FillColor = View.ThumbColor.ToGraphicsColor(Material.Color.Blue); + canvas.FillColor = IsEnabled ? View.ThumbColor.ToGraphicsColor(Material.Color.Blue) : GColor.FromArgb(Material.Color.Gray1); else - canvas.FillColor = View.ThumbColor.ToGraphicsColor(Fluent.Color.Foreground.White); + canvas.FillColor = IsEnabled ? View.ThumbColor.ToGraphicsColor(Fluent.Color.Foreground.White) : GColor.FromArgb(Material.Color.Gray1); var margin = 2; var radius = 10; diff --git a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/GraphicsView.cs b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/GraphicsView.cs index 9cdc4d5..4f98a99 100644 --- a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/GraphicsView.cs +++ b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/GraphicsView.cs @@ -44,6 +44,8 @@ public override bool IsEnabled if (value != _isEnabled) { IsEnabled = _isEnabled = value; + if (_drawable != null) + _drawable.IsEnabled = value; Invalidate(); } } diff --git a/src/Tizen.UIExtensions.NUI/Extensions/ViewExtensions.cs b/src/Tizen.UIExtensions.NUI/Extensions/ViewExtensions.cs index 9837c9f..f849f85 100644 --- a/src/Tizen.UIExtensions.NUI/Extensions/ViewExtensions.cs +++ b/src/Tizen.UIExtensions.NUI/Extensions/ViewExtensions.cs @@ -17,6 +17,10 @@ public static void SetEnable(this View view, bool enable) { button.IsEnabled = enable; } + else if (view is GraphicsView.GraphicsView gv) + { + gv.IsEnabled = enable; + } else { view.EnableControlState = enable; diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/GraphicsView.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/GraphicsView.cs index a90866e..e9e35e1 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/GraphicsView.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/GraphicsView.cs @@ -6,27 +6,36 @@ namespace Tizen.UIExtensions.NUI.GraphicsView { - public abstract class GraphicsView : SkiaGraphicsView, IMeasurable where TDrawable : GraphicsViewDrawable + public abstract class GraphicsView : SkiaGraphicsView { - Dictionary _propertyBag = new Dictionary(); - TDrawable? _drawable; - bool _isEnabled = true; + protected virtual GraphicsViewDrawable? GraphicsViewDrawable { get; } + bool _isEnabled = true; public bool IsEnabled { get => _isEnabled; set { EnableControlState = _isEnabled = value; + if (GraphicsViewDrawable != null) + GraphicsViewDrawable.IsEnabled = value; Invalidate(); } } + } + + public abstract class GraphicsView : GraphicsView, IMeasurable where TDrawable : GraphicsViewDrawable + { + Dictionary _propertyBag = new Dictionary(); + TDrawable? _drawable; public virtual Size Measure(double availableWidth, double availableHeight) { return Drawable?.Measure(availableWidth, availableHeight) ?? new Size(availableWidth, availableHeight); } + protected override GraphicsViewDrawable? GraphicsViewDrawable => _drawable; + protected void SetProperty(string name, T value) { _propertyBag[name] = value!; diff --git a/test/NUIExGallery/TC/GraphicsViewTest.cs b/test/NUIExGallery/TC/GraphicsViewTest.cs index 8b3456e..9bf650a 100644 --- a/test/NUIExGallery/TC/GraphicsViewTest.cs +++ b/test/NUIExGallery/TC/GraphicsViewTest.cs @@ -193,6 +193,7 @@ public override View Run() { var slider1 = new Slider { + IsEnabled = false, Margin = 5, Value = 0, Minimum = 0, @@ -236,6 +237,7 @@ public override View Run() { var button = new Tizen.UIExtensions.NUI.GraphicsView.Button { + IsEnabled = false, Margin = 5, Text = "Clicked 0", CornerRadius = 10, @@ -297,6 +299,29 @@ public override View Run() checkbox1.SizeWidth = (float)checkbox1.Measure(300, 300).Width; view.Add(checkbox1); } + { + var checkbox1 = new CheckBox + { + IsEnabled = false, + Margin = 5, + Text = "CheckBox1", + }; + checkbox1.SizeHeight = (float)checkbox1.Measure(300, 300).Height; + checkbox1.SizeWidth = (float)checkbox1.Measure(300, 300).Width; + view.Add(checkbox1); + } + { + var checkbox1 = new CheckBox + { + IsEnabled = false, + IsChecked = true, + Margin = 5, + Text = "CheckBox1", + }; + checkbox1.SizeHeight = (float)checkbox1.Measure(300, 300).Height; + checkbox1.SizeWidth = (float)checkbox1.Measure(300, 300).Width; + view.Add(checkbox1); + } { var checkbox1 = new CheckBox { @@ -351,6 +376,7 @@ public override View Run() { var switch1 = new Switch { + IsEnabled = false, Margin = 5, ThumbColor = Color.Red, OnColor = Color.Yellow From db3a60d03c2e6eb170c636cf736a3c05bc5a0dc1 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Tue, 23 Nov 2021 17:15:32 +0900 Subject: [PATCH 45/76] Implement CollectionView SnapPoint --- .../CollectionView/CollectionView.cs | 211 +++++++++++++++++- .../CollectionView/GridLayoutManager.cs | 10 + .../ICollectionViewLayoutManager.cs | 16 +- .../CollectionView/LinearLayoutManager.cs | 10 + .../CollectionView/SnapPointsAlignment.cs | 9 + .../CollectionView/SnapPointsType.cs | 21 ++ test/NUIExGallery/Main.cs | 34 +++ test/NUIExGallery/TC/CarouselViewSnapTest.cs | 95 ++++++++ .../NUIExGallery/TC/CollectionViewSanpTest.cs | 172 ++++++++++++++ .../TC/CollectionViewSanpTest2.cs | 172 ++++++++++++++ .../TC/CollectionViewSanpTest3.cs | 172 ++++++++++++++ 11 files changed, 920 insertions(+), 2 deletions(-) create mode 100644 src/Tizen.UIExtensions.NUI/CollectionView/SnapPointsAlignment.cs create mode 100644 src/Tizen.UIExtensions.NUI/CollectionView/SnapPointsType.cs create mode 100644 test/NUIExGallery/TC/CarouselViewSnapTest.cs create mode 100644 test/NUIExGallery/TC/CollectionViewSanpTest.cs create mode 100644 test/NUIExGallery/TC/CollectionViewSanpTest2.cs create mode 100644 test/NUIExGallery/TC/CollectionViewSanpTest3.cs diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs b/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs index 0cf98e9..6cbe2eb 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs @@ -116,6 +116,10 @@ public CollectionViewSelectionMode SelectionMode } } + public SnapPointsType SnapPointsType { get; set; } + + public SnapPointsAlignment SnapPointsAlignment { get; set; } + /// /// A size of allocated by Layout, it become viewport size on scrolling /// @@ -249,7 +253,7 @@ protected virtual ViewHolder CreateViewHolder() /// A ScrollView instance protected virtual ScrollableBase CreateScrollView() { - return new ScrollableBase(); + return new SnappableScrollView(this); } /// @@ -755,5 +759,210 @@ void OnItemStateUpdated(object? sender, EventArgs e) { return _viewHolderIndexTable.Where(d => d.Value == index).Select(d => d.Key).FirstOrDefault(); } + + + /// + /// A ScrollView that implemented snap points + /// + class SnappableScrollView : ScrollableBase + { + int _currentItemIndex = -1; + + public SnappableScrollView(CollectionView cv) + { + CollectionView = cv; + + ScrollDragStarted += OnDragStart; + + ScrollAnimationEnded += OnAnimationEnd; + } + + CollectionView CollectionView { get; } + ICollectionViewLayoutManager LayoutManager => CollectionView.LayoutManager!; + Rect ViewPort => CollectionView.ViewPort; + double ViewPortStart => IsHorizontal ? ViewPort.X : ViewPort.Y; + double ViewPortEnd => IsHorizontal ? ViewPort.Right : ViewPort.Bottom; + double ViewPortSize => IsHorizontal ? ViewPort.Width : ViewPort.Height; + bool IsHorizontal => ScrollingDirection == Direction.Horizontal; + + protected override void Decelerating(float velocity, Animation animation) + { + + if (CollectionView.SnapPointsType == SnapPointsType.MandatorySingle) + { + if (_currentItemIndex == -1) + return; + + int currentItem = _currentItemIndex; + if (Math.Abs(velocity) > 0.5) + { + if (velocity < 0) + { + currentItem = LayoutManager.NextRowItem(currentItem); + } + else + { + currentItem = LayoutManager.PreviousRowItem(currentItem); + } + } + + var itemBound = LayoutManager.GetItemBound(currentItem); + var target = IsHorizontal ? itemBound.X : itemBound.Y; + var itemSize = IsHorizontal ? itemBound.Width : itemBound.Height; + var scrollingSize = IsHorizontal ? ContentContainer.SizeWidth : ContentContainer.SizeHeight; + + // adjust align + if (CollectionView.SnapPointsAlignment == SnapPointsAlignment.Center) + { + target -= (ViewPortSize - itemSize) / 2; + } + else if (CollectionView.SnapPointsAlignment == SnapPointsAlignment.End) + { + target -= (ViewPortSize - itemSize); + } + + // adjust end of scroll area + if (scrollingSize - target < ViewPortSize) + { + target = scrollingSize - ViewPortSize; + } + + if (target < 0) + { + target = 0; + } + + ScrollTo(target); + } + else + { + if (CollectionView.SnapPointsType == SnapPointsType.None) + { + DecelerationRate = 0.998f; + } + else + { + DecelerationRate = 0.992f; + } + + base.Decelerating(velocity, animation); + } + } + + void OnDragStart(object? sender, ScrollEventArgs e) + { + if (CollectionView.SnapPointsType == SnapPointsType.MandatorySingle) + { + if (CollectionView.SnapPointsAlignment == SnapPointsAlignment.Start) + { + _currentItemIndex = CollectionView.LayoutManager!.GetVisibleItemIndex(CollectionView.ViewPort.X, CollectionView.ViewPort.Y); + var bound = CollectionView.LayoutManager!.GetItemBound(_currentItemIndex); + var padding = IsHorizontal ? bound.Width / 2 : bound.Height / 2; + _currentItemIndex = CollectionView.LayoutManager!.GetVisibleItemIndex( + (IsHorizontal ? padding : 0) + CollectionView.ViewPort.X, + (IsHorizontal ? 0 : padding) + CollectionView.ViewPort.Y); + + } + else if (CollectionView.SnapPointsAlignment == SnapPointsAlignment.Center) + { + _currentItemIndex = CollectionView.LayoutManager!.GetVisibleItemIndex(CollectionView.ViewPort.X + (CollectionView.ViewPort.Width / 2), CollectionView.ViewPort.Y + (CollectionView.ViewPort.Height / 2)); + } + else + { + _currentItemIndex = CollectionView.LayoutManager!.GetVisibleItemIndex(CollectionView.ViewPort.X + CollectionView.ViewPort.Width, CollectionView.ViewPort.Y + CollectionView.ViewPort.Height); + var bound = CollectionView.LayoutManager!.GetItemBound(_currentItemIndex); + var padding = IsHorizontal ? bound.Width / 2 : bound.Height / 2; + _currentItemIndex = CollectionView.LayoutManager!.GetVisibleItemIndex( + (IsHorizontal ? -padding : 0) + CollectionView.ViewPort.X + CollectionView.ViewPort.Width, + (IsHorizontal ? 0 : -padding) + CollectionView.ViewPort.Y + CollectionView.ViewPort.Height); + } + } + } + + void OnAnimationEnd(object? sender, ScrollEventArgs e) + { + OnSnapRequest(); + } + + void OnSnapRequest() + { + if (CollectionView.SnapPointsType == SnapPointsType.None) + return; + + double target; + if (CollectionView.SnapPointsAlignment == SnapPointsAlignment.Start) + { + var index = LayoutManager.GetVisibleItemIndex(ViewPort.X, ViewPort.Y); + var bound = LayoutManager.GetItemBound(index); + var itemSize = IsHorizontal ? bound.Width : bound.Height; + var itemStart = IsHorizontal ? bound.X : bound.Y; + + if (ViewPortStart - itemStart > itemSize / 2) + { + index = LayoutManager.NextRowItem(index); + } + + bound = LayoutManager.GetItemBound(index); + target = IsHorizontal ? bound.X : bound.Y; + } + else if (CollectionView.SnapPointsAlignment == SnapPointsAlignment.Center) + { + var index = LayoutManager.GetVisibleItemIndex(ViewPort.X + (ViewPort.Width / 2), ViewPort.Y + (ViewPort.Height / 2)); + var bound = LayoutManager.GetItemBound(index); + var itemSize = IsHorizontal ? bound.Width : bound.Height; + var itemStart = IsHorizontal ? bound.X : bound.Y; + + if (ViewPortStart + (ViewPortSize / 2) - (itemStart + itemSize / 2) > (itemSize / 2)) + { + index = LayoutManager.NextRowItem(index); + } + + bound = LayoutManager.GetItemBound(index); + itemSize = IsHorizontal ? bound.Width : bound.Height; + target = IsHorizontal ? bound.X : bound.Y; + target -= (ViewPortSize - itemSize) / 2; + } + else + { + var index = LayoutManager.GetVisibleItemIndex(ViewPort.Right, ViewPort.Bottom); + var bound = LayoutManager.GetItemBound(index); + var itemSize = IsHorizontal ? bound.Width : bound.Height; + var itemEnd = IsHorizontal ? bound.Right : bound.Bottom; + + if (itemEnd - ViewPortEnd > itemSize / 2) + { + index = LayoutManager.PreviousRowItem(index); + } + + bound = LayoutManager.GetItemBound(index); + itemSize = IsHorizontal ? bound.Width : bound.Height; + + target = IsHorizontal ? bound.X : bound.Y; + target -= (ViewPortSize - itemSize); + } + + ScrollTo(target); + } + + void ScrollTo(double target) + { + var scrollingSize = IsHorizontal ? ContentContainer.SizeWidth : ContentContainer.SizeHeight; + + if (scrollingSize - target < ViewPortSize) + { + target = scrollingSize - ViewPortSize; + } + + if (target < 0) + { + target = 0; + } + + var animation = new Animation(); + animation.Duration = 200; + animation.AnimateTo(ContentContainer, IsHorizontal ? "PositionX" : "PositionY", -(float)target); + animation.Play(); + } + } } } diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/GridLayoutManager.cs b/src/Tizen.UIExtensions.NUI/CollectionView/GridLayoutManager.cs index 5e1780d..0bbefaf 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/GridLayoutManager.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/GridLayoutManager.cs @@ -520,6 +520,16 @@ public void SetFooter(View? footer, Size size) UpdateFooterPosition(); } + public int NextRowItem(int index) + { + return Math.Min(index + Span, CollectionView!.Count - 1); + } + + public int PreviousRowItem(int index) + { + return Math.Max(index - Span, 0); + } + void UpdateFooterPosition() { if (_footer == null) diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/ICollectionViewLayoutManager.cs b/src/Tizen.UIExtensions.NUI/CollectionView/ICollectionViewLayoutManager.cs index eb86de9..3a32940 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/ICollectionViewLayoutManager.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/ICollectionViewLayoutManager.cs @@ -106,5 +106,19 @@ public interface ICollectionViewLayoutManager /// Fotter view /// Size of footer void SetFooter(View? footer, Size size); - } + + /// + /// Gets index of next row item + /// + /// Current item index + /// + int NextRowItem(int index); + + /// + /// Gets index of previous row item + /// + /// Current item index + /// + int PreviousRowItem(int index); + } } diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/LinearLayoutManager.cs b/src/Tizen.UIExtensions.NUI/CollectionView/LinearLayoutManager.cs index c06e27b..8eba23d 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/LinearLayoutManager.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/LinearLayoutManager.cs @@ -448,6 +448,16 @@ public void SetFooter(View? footer, Size size) UpdateFooterPosition(); } + public int NextRowItem(int index) + { + return Math.Min(index + 1, CollectionView!.Count - 1); + } + + public int PreviousRowItem(int index) + { + return Math.Max(index - 1, 0); + } + void UpdateFooterPosition() { if (_footer == null) diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/SnapPointsAlignment.cs b/src/Tizen.UIExtensions.NUI/CollectionView/SnapPointsAlignment.cs new file mode 100644 index 0000000..5cde150 --- /dev/null +++ b/src/Tizen.UIExtensions.NUI/CollectionView/SnapPointsAlignment.cs @@ -0,0 +1,9 @@ +namespace Tizen.UIExtensions.NUI +{ + public enum SnapPointsAlignment + { + Start, + Center, + End + } +} \ No newline at end of file diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/SnapPointsType.cs b/src/Tizen.UIExtensions.NUI/CollectionView/SnapPointsType.cs new file mode 100644 index 0000000..baeba9d --- /dev/null +++ b/src/Tizen.UIExtensions.NUI/CollectionView/SnapPointsType.cs @@ -0,0 +1,21 @@ +namespace Tizen.UIExtensions.NUI +{ + /// + /// Enumerates values that specifies the behavior of snap points when scrolling.. + /// + public enum SnapPointsType + { + /// + /// indicates that scrolling does not snap to items. + /// + None, + /// + /// indicates that content always snaps to the closest snap point to where scrolling would naturally stop, along the direction of inertia. + /// + Mandatory, + /// + /// indicates the same behavior as Mandatory, but only scrolls one item at a time. + /// + MandatorySingle, + } +} \ No newline at end of file diff --git a/test/NUIExGallery/Main.cs b/test/NUIExGallery/Main.cs index 1a0eb0d..e20574f 100644 --- a/test/NUIExGallery/Main.cs +++ b/test/NUIExGallery/Main.cs @@ -70,6 +70,39 @@ View CreateListPage(IEnumerable tests) var testCase = ((sender as View).BindingContext as TestCaseBase); RunTC(testCase); }; + + + var collectionview = new ScrollableBase + { + Layout = new LinearLayout + { + LinearOrientation = LinearLayout.Orientation.Vertical + } + }; + + foreach (var item in tests) + { + var itemView = new DefaultLinearItem(); + + itemView.BindingContext = item; + itemView.Clicked += clicked; + + var label = new TextLabel + { + VerticalAlignment = VerticalAlignment.Center, + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.MatchParent, + }; + + label.PixelSize = 30; + label.SetBinding(TextLabel.TextProperty, new Binding("TestName")); + + itemView.Add(label); + collectionview.Add(itemView); + } + + // NUI bug - on target NUI CollectionView was crashed + /* var collectionview = new CollectionView { SelectionMode = ItemSelectionMode.None, @@ -126,6 +159,7 @@ View CreateListPage(IEnumerable tests) //return itemView; }) }; + */ layout.Add(collectionview); diff --git a/test/NUIExGallery/TC/CarouselViewSnapTest.cs b/test/NUIExGallery/TC/CarouselViewSnapTest.cs new file mode 100644 index 0000000..09c9bac --- /dev/null +++ b/test/NUIExGallery/TC/CarouselViewSnapTest.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Tizen.NUI; +using Tizen.NUI.BaseComponents; +using Tizen.UIExtensions.NUI; +using Color = Tizen.UIExtensions.Common.Color; + +namespace NUIExGallery.TC +{ + + public class CarouselViewSnapTest : TestCaseBase + { + + class CarouselViewAdaptor : MyAdaptor2 + { + public CarouselViewAdaptor(IEnumerable items) : base(items) + { + } + + public override View GetHeaderView() + { + return null; + } + + public override Tizen.UIExtensions.Common.Size MeasureItem(int index, double widthConstraint, double heightConstraint) + { + return new Tizen.UIExtensions.Common.Size(720, 300); + } + } + + class CarouselViewAdaptor2 : CarouselViewAdaptor + { + public CarouselViewAdaptor2(IEnumerable items) : base(items) + { + } + + public override Tizen.UIExtensions.Common.Size MeasureItem(int index, double widthConstraint, double heightConstraint) + { + return new Tizen.UIExtensions.Common.Size(300, 300); + } + } + + public override string TestName => "CarouselView snap test"; + + public override string TestDescription => "CarouselView snap test"; + + public override View Run() + { + var items = new List(); + for (int i = 0; i < 1000; i++) + { + items.Add($"Items {i}"); + } + + var adaptor = new CarouselViewAdaptor(items); + + var collectionView = new CollectionView + { + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.MatchParent, + }; + collectionView.SelectionMode = CollectionViewSelectionMode.Single; + collectionView.Adaptor = adaptor; + collectionView.LayoutManager = new LinearLayoutManager(true); + collectionView.SnapPointsAlignment = SnapPointsAlignment.Center; + collectionView.SnapPointsType = SnapPointsType.MandatorySingle; + + + + var collectionView2 = new CollectionView + { + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.MatchParent, + Adaptor = new CarouselViewAdaptor2(items), + LayoutManager = new LinearLayoutManager(true), + SnapPointsAlignment = SnapPointsAlignment.Center, + SnapPointsType = SnapPointsType.MandatorySingle + }; + + var layout = new View + { + Layout = new LinearLayout + { + LinearOrientation = LinearLayout.Orientation.Vertical, + LinearAlignment = LinearLayout.Alignment.Top + } + }; + + layout.Add(collectionView); + layout.Add(collectionView2); + return layout; + } + } +} diff --git a/test/NUIExGallery/TC/CollectionViewSanpTest.cs b/test/NUIExGallery/TC/CollectionViewSanpTest.cs new file mode 100644 index 0000000..602581d --- /dev/null +++ b/test/NUIExGallery/TC/CollectionViewSanpTest.cs @@ -0,0 +1,172 @@ +using System.Collections; +using System.Collections.Generic; +using Tizen.NUI; +using Tizen.NUI.BaseComponents; +using Tizen.UIExtensions.NUI; + +namespace NUIExGallery.TC +{ + + public class CollectionViewSnapTest : TestCaseBase + { + + class SnapAdaptor : MyAdaptor2 + { + public SnapAdaptor(IEnumerable items) : base(items) + { + } + + public override View GetHeaderView() + { + return null; + } + + public override Tizen.UIExtensions.Common.Size MeasureItem(int index, double widthConstraint, double heightConstraint) + { + return new Tizen.UIExtensions.Common.Size(300, 300); + } + } + + public override string TestName => "CollectionView Snap Test1"; + + public override string TestDescription => "CollectionView Snap Test1"; + + public override View Run() + { + var items = new List(); + for (int i = 0; i < 1000; i++) + { + items.Add($"Items {i}"); + } + + var adaptor = new SnapAdaptor(items); + + var collectionView = new CollectionView() + { + Adaptor = adaptor, + LayoutManager = new LinearLayoutManager(true), + SnapPointsAlignment = SnapPointsAlignment.Center, + SnapPointsType = SnapPointsType.Mandatory + }; + + var layout = new View + { + Layout = new LinearLayout + { + LinearOrientation = LinearLayout.Orientation.Vertical, + } + }; + + { + var label = new Label + { + FontSize = 10, + Text = "SnapPointType: Mandatory" + }; + layout.Add(label); + + var horizontal = new View + { + Layout = new LinearLayout + { + LinearOrientation = LinearLayout.Orientation.Horizontal + } + }; + layout.Add(horizontal); + var typeNone = new Button + { + Text = "None", + SizeWidth = 200, + }; + horizontal.Add(typeNone); + typeNone.Clicked += (s, e) => + { + collectionView.SnapPointsType = SnapPointsType.None; + label.Text = "SnapPointType: None"; + }; + + var typeMandatory = new Button + { + Text = "Mandatory", + SizeWidth = 200, + }; + horizontal.Add(typeMandatory); + typeMandatory.Clicked += (s, e) => + { + collectionView.SnapPointsType = SnapPointsType.Mandatory; + label.Text = "SnapPointType: Mandatory"; + }; + + var typeSingle = new Button + { + Text = "MandatorySingle", + SizeWidth = 200, + }; + horizontal.Add(typeSingle); + typeSingle.Clicked += (s, e) => + { + collectionView.SnapPointsType = SnapPointsType.MandatorySingle; + label.Text = "SnapPointType: MandatorySingle"; + }; + + } + + { + var label = new Label + { + FontSize = 10, + Text = "SnapPointsAlignment: Center" + }; + layout.Add(label); + + var horizontal = new View + { + Layout = new LinearLayout + { + LinearOrientation = LinearLayout.Orientation.Horizontal + } + }; + layout.Add(horizontal); + var start = new Button + { + Text = "Start", + SizeWidth = 200, + }; + horizontal.Add(start); + start.Clicked += (s, e) => + { + collectionView.SnapPointsAlignment = SnapPointsAlignment.Start; + label.Text = "SnapPointsAlignment: Start"; + }; + + var center = new Button + { + Text = "Center", + SizeWidth = 200, + }; + horizontal.Add(center); + center.Clicked += (s, e) => + { + collectionView.SnapPointsAlignment = SnapPointsAlignment.Center; + label.Text = "SnapPointsAlignment: Center"; + }; + + var end = new Button + { + Text = "End", + SizeWidth = 200, + }; + horizontal.Add(end); + end.Clicked += (s, e) => + { + collectionView.SnapPointsAlignment = SnapPointsAlignment.End; + label.Text = "SnapPointsAlignment: End"; + }; + + } + + layout.Add(collectionView); + return layout; + } + } +} diff --git a/test/NUIExGallery/TC/CollectionViewSanpTest2.cs b/test/NUIExGallery/TC/CollectionViewSanpTest2.cs new file mode 100644 index 0000000..e7e98af --- /dev/null +++ b/test/NUIExGallery/TC/CollectionViewSanpTest2.cs @@ -0,0 +1,172 @@ +using System.Collections; +using System.Collections.Generic; +using Tizen.NUI; +using Tizen.NUI.BaseComponents; +using Tizen.UIExtensions.NUI; + +namespace NUIExGallery.TC +{ + + public class CollectionViewSnapTest2 : TestCaseBase + { + + class SnapAdaptor : MyAdaptor2 + { + public SnapAdaptor(IEnumerable items) : base(items) + { + } + + public override View GetHeaderView() + { + return null; + } + + public override Tizen.UIExtensions.Common.Size MeasureItem(int index, double widthConstraint, double heightConstraint) + { + return new Tizen.UIExtensions.Common.Size(300, 300); + } + } + + public override string TestName => "CollectionView Snap Test2"; + + public override string TestDescription => "CollectionView Snap Test2"; + + public override View Run() + { + var items = new List(); + for (int i = 0; i < 1000; i++) + { + items.Add($"Items {i}"); + } + + var adaptor = new SnapAdaptor(items); + + var collectionView = new CollectionView() + { + Adaptor = adaptor, + LayoutManager = new LinearLayoutManager(false), + SnapPointsAlignment = SnapPointsAlignment.Center, + SnapPointsType = SnapPointsType.Mandatory + }; + + var layout = new View + { + Layout = new LinearLayout + { + LinearOrientation = LinearLayout.Orientation.Vertical, + } + }; + + { + var label = new Label + { + FontSize = 10, + Text = "SnapPointType: Mandatory" + }; + layout.Add(label); + + var horizontal = new View + { + Layout = new LinearLayout + { + LinearOrientation = LinearLayout.Orientation.Horizontal + } + }; + layout.Add(horizontal); + var typeNone = new Button + { + Text = "None", + SizeWidth = 200, + }; + horizontal.Add(typeNone); + typeNone.Clicked += (s, e) => + { + collectionView.SnapPointsType = SnapPointsType.None; + label.Text = "SnapPointType: None"; + }; + + var typeMandatory = new Button + { + Text = "Mandatory", + SizeWidth = 200, + }; + horizontal.Add(typeMandatory); + typeMandatory.Clicked += (s, e) => + { + collectionView.SnapPointsType = SnapPointsType.Mandatory; + label.Text = "SnapPointType: Mandatory"; + }; + + var typeSingle = new Button + { + Text = "MandatorySingle", + SizeWidth = 200, + }; + horizontal.Add(typeSingle); + typeSingle.Clicked += (s, e) => + { + collectionView.SnapPointsType = SnapPointsType.MandatorySingle; + label.Text = "SnapPointType: MandatorySingle"; + }; + + } + + { + var label = new Label + { + FontSize = 10, + Text = "SnapPointsAlignment: Center" + }; + layout.Add(label); + + var horizontal = new View + { + Layout = new LinearLayout + { + LinearOrientation = LinearLayout.Orientation.Horizontal + } + }; + layout.Add(horizontal); + var start = new Button + { + Text = "Start", + SizeWidth = 200, + }; + horizontal.Add(start); + start.Clicked += (s, e) => + { + collectionView.SnapPointsAlignment = SnapPointsAlignment.Start; + label.Text = "SnapPointsAlignment: Start"; + }; + + var center = new Button + { + Text = "Center", + SizeWidth = 200, + }; + horizontal.Add(center); + center.Clicked += (s, e) => + { + collectionView.SnapPointsAlignment = SnapPointsAlignment.Center; + label.Text = "SnapPointsAlignment: Center"; + }; + + var end = new Button + { + Text = "End", + SizeWidth = 200, + }; + horizontal.Add(end); + end.Clicked += (s, e) => + { + collectionView.SnapPointsAlignment = SnapPointsAlignment.End; + label.Text = "SnapPointsAlignment: End"; + }; + + } + + layout.Add(collectionView); + return layout; + } + } +} diff --git a/test/NUIExGallery/TC/CollectionViewSanpTest3.cs b/test/NUIExGallery/TC/CollectionViewSanpTest3.cs new file mode 100644 index 0000000..2a17e97 --- /dev/null +++ b/test/NUIExGallery/TC/CollectionViewSanpTest3.cs @@ -0,0 +1,172 @@ +using System.Collections; +using System.Collections.Generic; +using Tizen.NUI; +using Tizen.NUI.BaseComponents; +using Tizen.UIExtensions.NUI; + +namespace NUIExGallery.TC +{ + + public class CollectionViewSnapTest3 : TestCaseBase + { + + class SnapAdaptor : MyAdaptor2 + { + public SnapAdaptor(IEnumerable items) : base(items) + { + } + + public override View GetHeaderView() + { + return null; + } + + public override Tizen.UIExtensions.Common.Size MeasureItem(int index, double widthConstraint, double heightConstraint) + { + return new Tizen.UIExtensions.Common.Size(300, 300); + } + } + + public override string TestName => "CollectionView Snap Test3"; + + public override string TestDescription => "CollectionView Snap Test3"; + + public override View Run() + { + var items = new List(); + for (int i = 0; i < 1000; i++) + { + items.Add($"Items {i}"); + } + + var adaptor = new SnapAdaptor(items); + + var collectionView = new CollectionView() + { + Adaptor = adaptor, + LayoutManager = new GridLayoutManager(false, 2), + SnapPointsAlignment = SnapPointsAlignment.Center, + SnapPointsType = SnapPointsType.Mandatory + }; + + var layout = new View + { + Layout = new LinearLayout + { + LinearOrientation = LinearLayout.Orientation.Vertical, + } + }; + + { + var label = new Label + { + FontSize = 10, + Text = "SnapPointType: Mandatory" + }; + layout.Add(label); + + var horizontal = new View + { + Layout = new LinearLayout + { + LinearOrientation = LinearLayout.Orientation.Horizontal + } + }; + layout.Add(horizontal); + var typeNone = new Button + { + Text = "None", + SizeWidth = 200, + }; + horizontal.Add(typeNone); + typeNone.Clicked += (s, e) => + { + collectionView.SnapPointsType = SnapPointsType.None; + label.Text = "SnapPointType: None"; + }; + + var typeMandatory = new Button + { + Text = "Mandatory", + SizeWidth = 200, + }; + horizontal.Add(typeMandatory); + typeMandatory.Clicked += (s, e) => + { + collectionView.SnapPointsType = SnapPointsType.Mandatory; + label.Text = "SnapPointType: Mandatory"; + }; + + var typeSingle = new Button + { + Text = "MandatorySingle", + SizeWidth = 200, + }; + horizontal.Add(typeSingle); + typeSingle.Clicked += (s, e) => + { + collectionView.SnapPointsType = SnapPointsType.MandatorySingle; + label.Text = "SnapPointType: MandatorySingle"; + }; + + } + + { + var label = new Label + { + FontSize = 10, + Text = "SnapPointsAlignment: Center" + }; + layout.Add(label); + + var horizontal = new View + { + Layout = new LinearLayout + { + LinearOrientation = LinearLayout.Orientation.Horizontal + } + }; + layout.Add(horizontal); + var start = new Button + { + Text = "Start", + SizeWidth = 200, + }; + horizontal.Add(start); + start.Clicked += (s, e) => + { + collectionView.SnapPointsAlignment = SnapPointsAlignment.Start; + label.Text = "SnapPointsAlignment: Start"; + }; + + var center = new Button + { + Text = "Center", + SizeWidth = 200, + }; + horizontal.Add(center); + center.Clicked += (s, e) => + { + collectionView.SnapPointsAlignment = SnapPointsAlignment.Center; + label.Text = "SnapPointsAlignment: Center"; + }; + + var end = new Button + { + Text = "End", + SizeWidth = 200, + }; + horizontal.Add(end); + end.Clicked += (s, e) => + { + collectionView.SnapPointsAlignment = SnapPointsAlignment.End; + label.Text = "SnapPointsAlignment: End"; + }; + + } + + layout.Add(collectionView); + return layout; + } + } +} From 24157414a85e2a041adfc25bfa6e7830f6e66ffc Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Tue, 23 Nov 2021 15:39:53 +0900 Subject: [PATCH 46/76] Apply Build CI --- .github/workflows/build.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..69e5166 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,30 @@ +name: Build Projects + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: code-large + container: + image: mcr.microsoft.com/dotnet/sdk:6.0 + options: --user root + steps: + - uses: CODE-Actions/checkout@v2 + with: + fetch-depth: 0 + - name: Install Tizen workload + run: | + apt-get update && apt-get install -y unzip + curl -sSL https://github.com/raw/Samsung/Tizen.NET/main/workload/scripts/workload-install.sh | bash + - name: Build + env: + PULLREQUEST_ID: ${{ github.event.number }} + run: | + dotnet build Tizen.UIExtensions.sln + working-directory: . \ No newline at end of file From 4285bcefa731dd089ca3a3b77eb1522d3bd9815f Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Wed, 24 Nov 2021 14:39:58 +0900 Subject: [PATCH 47/76] Apply code review --- .../CollectionView/CollectionView.cs | 163 ++++++++++-------- .../CollectionView/GridLayoutManager.cs | 4 +- .../ICollectionViewLayoutManager.cs | 4 +- .../CollectionView/LinearLayoutManager.cs | 4 +- .../CollectionView/SnapPointsAlignment.cs | 14 +- 5 files changed, 112 insertions(+), 77 deletions(-) diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs b/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs index 6cbe2eb..9ebb150 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs @@ -116,8 +116,14 @@ public CollectionViewSelectionMode SelectionMode } } + /// + /// Specifies the behavior of snap points when scrolling. + /// public SnapPointsType SnapPointsType { get; set; } + /// + /// Specifies how snap points are aligned with items. + /// public SnapPointsAlignment SnapPointsAlignment { get; set; } /// @@ -787,95 +793,111 @@ public SnappableScrollView(CollectionView cv) protected override void Decelerating(float velocity, Animation animation) { - if (CollectionView.SnapPointsType == SnapPointsType.MandatorySingle) { - if (_currentItemIndex == -1) - return; + // Only one item should be passed when scrolling by snap + HandleMandatorySingle(velocity); + } + else + { + HandleNonMandatorySingle(velocity, animation); + } + } - int currentItem = _currentItemIndex; - if (Math.Abs(velocity) > 0.5) - { - if (velocity < 0) - { - currentItem = LayoutManager.NextRowItem(currentItem); - } - else - { - currentItem = LayoutManager.PreviousRowItem(currentItem); - } - } + void HandleNonMandatorySingle(float velocity, Animation animation) + { + if (CollectionView.SnapPointsType == SnapPointsType.None) + { + DecelerationRate = 0.998f; + } + else + { + // Adjust DecelerationRate to stop more quickly because it will be moved again by OnSnapRequest + DecelerationRate = 0.992f; + } - var itemBound = LayoutManager.GetItemBound(currentItem); - var target = IsHorizontal ? itemBound.X : itemBound.Y; - var itemSize = IsHorizontal ? itemBound.Width : itemBound.Height; - var scrollingSize = IsHorizontal ? ContentContainer.SizeWidth : ContentContainer.SizeHeight; + base.Decelerating(velocity, animation); + } - // adjust align - if (CollectionView.SnapPointsAlignment == SnapPointsAlignment.Center) - { - target -= (ViewPortSize - itemSize) / 2; - } - else if (CollectionView.SnapPointsAlignment == SnapPointsAlignment.End) - { - target -= (ViewPortSize - itemSize); - } + void HandleMandatorySingle(float velocity) + { + if (_currentItemIndex == -1) + return; - // adjust end of scroll area - if (scrollingSize - target < ViewPortSize) + int currentItem = _currentItemIndex; + if (Math.Abs(velocity) > 0.5) + { + if (velocity < 0) { - target = scrollingSize - ViewPortSize; + currentItem = LayoutManager.NextRowItemIndex(currentItem); } - - if (target < 0) + else { - target = 0; + currentItem = LayoutManager.PreviousRowItemIndex(currentItem); } + } + + var itemBound = LayoutManager.GetItemBound(currentItem); + var target = IsHorizontal ? itemBound.X : itemBound.Y; + var itemSize = IsHorizontal ? itemBound.Width : itemBound.Height; + var scrollingSize = IsHorizontal ? ContentContainer.SizeWidth : ContentContainer.SizeHeight; - ScrollTo(target); + // adjust align + if (CollectionView.SnapPointsAlignment == SnapPointsAlignment.Center) + { + target -= (ViewPortSize - itemSize) / 2; } - else + else if (CollectionView.SnapPointsAlignment == SnapPointsAlignment.End) { - if (CollectionView.SnapPointsType == SnapPointsType.None) - { - DecelerationRate = 0.998f; - } - else - { - DecelerationRate = 0.992f; - } + target -= (ViewPortSize - itemSize); + } - base.Decelerating(velocity, animation); + // adjust end of scroll area + if (scrollingSize - target < ViewPortSize) + { + target = scrollingSize - ViewPortSize; } + + if (target < 0) + { + target = 0; + } + + ScrollTo(target); } void OnDragStart(object? sender, ScrollEventArgs e) { if (CollectionView.SnapPointsType == SnapPointsType.MandatorySingle) { - if (CollectionView.SnapPointsAlignment == SnapPointsAlignment.Start) - { - _currentItemIndex = CollectionView.LayoutManager!.GetVisibleItemIndex(CollectionView.ViewPort.X, CollectionView.ViewPort.Y); - var bound = CollectionView.LayoutManager!.GetItemBound(_currentItemIndex); - var padding = IsHorizontal ? bound.Width / 2 : bound.Height / 2; - _currentItemIndex = CollectionView.LayoutManager!.GetVisibleItemIndex( - (IsHorizontal ? padding : 0) + CollectionView.ViewPort.X, - (IsHorizontal ? 0 : padding) + CollectionView.ViewPort.Y); + MarkCurrentItem(); + } + } - } - else if (CollectionView.SnapPointsAlignment == SnapPointsAlignment.Center) - { - _currentItemIndex = CollectionView.LayoutManager!.GetVisibleItemIndex(CollectionView.ViewPort.X + (CollectionView.ViewPort.Width / 2), CollectionView.ViewPort.Y + (CollectionView.ViewPort.Height / 2)); - } - else - { - _currentItemIndex = CollectionView.LayoutManager!.GetVisibleItemIndex(CollectionView.ViewPort.X + CollectionView.ViewPort.Width, CollectionView.ViewPort.Y + CollectionView.ViewPort.Height); - var bound = CollectionView.LayoutManager!.GetItemBound(_currentItemIndex); - var padding = IsHorizontal ? bound.Width / 2 : bound.Height / 2; - _currentItemIndex = CollectionView.LayoutManager!.GetVisibleItemIndex( - (IsHorizontal ? -padding : 0) + CollectionView.ViewPort.X + CollectionView.ViewPort.Width, - (IsHorizontal ? 0 : -padding) + CollectionView.ViewPort.Y + CollectionView.ViewPort.Height); - } + void MarkCurrentItem() + { + if (CollectionView.SnapPointsAlignment == SnapPointsAlignment.Start) + { + _currentItemIndex = CollectionView.LayoutManager!.GetVisibleItemIndex(CollectionView.ViewPort.X, CollectionView.ViewPort.Y); + var bound = CollectionView.LayoutManager!.GetItemBound(_currentItemIndex); + var padding = IsHorizontal ? bound.Width / 2 : bound.Height / 2; + _currentItemIndex = CollectionView.LayoutManager!.GetVisibleItemIndex( + (IsHorizontal ? padding : 0) + CollectionView.ViewPort.X, + (IsHorizontal ? 0 : padding) + CollectionView.ViewPort.Y); + + } + else if (CollectionView.SnapPointsAlignment == SnapPointsAlignment.Center) + { + _currentItemIndex = CollectionView.LayoutManager!.GetVisibleItemIndex(CollectionView.ViewPort.X + (CollectionView.ViewPort.Width / 2), CollectionView.ViewPort.Y + (CollectionView.ViewPort.Height / 2)); + } + else + { + _currentItemIndex = CollectionView.LayoutManager!.GetVisibleItemIndex(CollectionView.ViewPort.X + CollectionView.ViewPort.Width, CollectionView.ViewPort.Y + CollectionView.ViewPort.Height); + var bound = CollectionView.LayoutManager!.GetItemBound(_currentItemIndex); + var padding = IsHorizontal ? bound.Width / 2 : bound.Height / 2; + _currentItemIndex = CollectionView.LayoutManager!.GetVisibleItemIndex( + (IsHorizontal ? -padding : 0) + CollectionView.ViewPort.X + CollectionView.ViewPort.Width, + (IsHorizontal ? 0 : -padding) + CollectionView.ViewPort.Y + CollectionView.ViewPort.Height); } } @@ -899,7 +921,7 @@ void OnSnapRequest() if (ViewPortStart - itemStart > itemSize / 2) { - index = LayoutManager.NextRowItem(index); + index = LayoutManager.NextRowItemIndex(index); } bound = LayoutManager.GetItemBound(index); @@ -914,7 +936,7 @@ void OnSnapRequest() if (ViewPortStart + (ViewPortSize / 2) - (itemStart + itemSize / 2) > (itemSize / 2)) { - index = LayoutManager.NextRowItem(index); + index = LayoutManager.NextRowItemIndex(index); } bound = LayoutManager.GetItemBound(index); @@ -931,7 +953,7 @@ void OnSnapRequest() if (itemEnd - ViewPortEnd > itemSize / 2) { - index = LayoutManager.PreviousRowItem(index); + index = LayoutManager.PreviousRowItemIndex(index); } bound = LayoutManager.GetItemBound(index); @@ -946,6 +968,7 @@ void OnSnapRequest() void ScrollTo(double target) { + // it is a ScrollTo api that do not raise ScrollAnimationStarted/Ended event var scrollingSize = IsHorizontal ? ContentContainer.SizeWidth : ContentContainer.SizeHeight; if (scrollingSize - target < ViewPortSize) diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/GridLayoutManager.cs b/src/Tizen.UIExtensions.NUI/CollectionView/GridLayoutManager.cs index 0bbefaf..7e433e7 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/GridLayoutManager.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/GridLayoutManager.cs @@ -520,12 +520,12 @@ public void SetFooter(View? footer, Size size) UpdateFooterPosition(); } - public int NextRowItem(int index) + public int NextRowItemIndex(int index) { return Math.Min(index + Span, CollectionView!.Count - 1); } - public int PreviousRowItem(int index) + public int PreviousRowItemIndex(int index) { return Math.Max(index - Span, 0); } diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/ICollectionViewLayoutManager.cs b/src/Tizen.UIExtensions.NUI/CollectionView/ICollectionViewLayoutManager.cs index 3a32940..31fe2d4 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/ICollectionViewLayoutManager.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/ICollectionViewLayoutManager.cs @@ -112,13 +112,13 @@ public interface ICollectionViewLayoutManager /// /// Current item index /// - int NextRowItem(int index); + int NextRowItemIndex(int index); /// /// Gets index of previous row item /// /// Current item index /// - int PreviousRowItem(int index); + int PreviousRowItemIndex(int index); } } diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/LinearLayoutManager.cs b/src/Tizen.UIExtensions.NUI/CollectionView/LinearLayoutManager.cs index 8eba23d..72f6cea 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/LinearLayoutManager.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/LinearLayoutManager.cs @@ -448,12 +448,12 @@ public void SetFooter(View? footer, Size size) UpdateFooterPosition(); } - public int NextRowItem(int index) + public int NextRowItemIndex(int index) { return Math.Min(index + 1, CollectionView!.Count - 1); } - public int PreviousRowItem(int index) + public int PreviousRowItemIndex(int index) { return Math.Max(index - 1, 0); } diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/SnapPointsAlignment.cs b/src/Tizen.UIExtensions.NUI/CollectionView/SnapPointsAlignment.cs index 5cde150..f3a0cef 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/SnapPointsAlignment.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/SnapPointsAlignment.cs @@ -1,9 +1,21 @@ namespace Tizen.UIExtensions.NUI { + /// + /// Enumerates values that specifies how snap points are aligned with items. + /// public enum SnapPointsAlignment { + /// + /// indicates that scrolling item was aligned with start + /// Start, + /// + /// indicates that scrolling item was aligned with center + /// Center, + /// + /// indicates that scrolling item was aligned with end + /// End - } + } } \ No newline at end of file From 385f72d965234386fbdb4816bac1f7e9acafdee2 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Mon, 29 Nov 2021 16:47:18 +0900 Subject: [PATCH 48/76] Support key event focus navigation --- .../CollectionView/CollectionView.cs | 19 ++- .../CollectionView/ViewHolder.cs | 28 +++- test/NUIExGallery/Main.cs | 2 +- .../TC/CollectionViewFocusTest.cs | 134 ++++++++++++++++++ .../TC/CollectionViewSelectedTest.cs | 26 +++- 5 files changed, 198 insertions(+), 11 deletions(-) create mode 100644 test/NUIExGallery/TC/CollectionViewFocusTest.cs diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs b/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs index 9ebb150..b0e6133 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs @@ -201,18 +201,15 @@ public void ScrollTo(int index, ScrollToPosition position = ScrollToPosition.Mak itemPadding = (viewportSize - itemSize); break; } - itemSize = viewportSize; } if (isHorizontal) { itemBound.X -= itemPadding; - itemBound.Width = itemSize; } else { itemBound.Y -= itemPadding; - itemBound.Height = itemSize; } ScrollView.ScrollTo(isHorizontal ? (float)itemBound.X : (float)itemBound.Y, animate); @@ -274,10 +271,9 @@ protected virtual void InitializationComponent() ScrollView = CreateScrollView(); ScrollView.WidthSpecification = LayoutParamPolicies.MatchParent; ScrollView.HeightSpecification = LayoutParamPolicies.MatchParent; -#pragma warning disable CS0618 ScrollView.WidthResizePolicy = ResizePolicyType.FillToParent; ScrollView.HeightResizePolicy = ResizePolicyType.FillToParent; -#pragma warning restore CS0618 + ScrollView.ScrollingEventThreshold = 10; ScrollView.Scrolling += OnScrolling; ScrollView.ScrollAnimationEnded += OnScrollAnimationEnded; @@ -758,6 +754,12 @@ void OnItemStateUpdated(object? sender, EventArgs e) if (holder.Content != null) { Adaptor?.UpdateViewState(holder.Content, holder.State); + + if (_viewHolderIndexTable.ContainsKey(holder) && holder.State == ViewHolderState.Focused) + { + var index = _viewHolderIndexTable[holder]; + ScrollTo(index, ScrollToPosition.MakeVisible, true); + } } } @@ -804,6 +806,13 @@ protected override void Decelerating(float velocity, Animation animation) } } + public override View? GetNextFocusableView(View currentFocusedView, FocusDirection direction, bool loopEnabled) + { + + // workaround code, to disable SetKeyboardNavigationSupport + return null; + } + void HandleNonMandatorySingle(float velocity, Animation animation) { if (CollectionView.SnapPointsType == SnapPointsType.None) diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs b/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs index cab71c7..4cf9a59 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs @@ -35,16 +35,25 @@ public View? Content } set { - _content?.Unparent(); + if (_content != null) + { + _content.FocusGained -= OnContentFocused; + _content.FocusLost -= OnContentUnfocused; + _content.Unparent(); + } + _content = value; + if (_content != null) { _content.WidthSpecification = LayoutParamPolicies.MatchParent; _content.HeightSpecification = LayoutParamPolicies.MatchParent; -#pragma warning disable CS0618 _content.WidthResizePolicy = ResizePolicyType.FillToParent; _content.HeightResizePolicy = ResizePolicyType.FillToParent; -#pragma warning restore CS0618 + + _content.FocusGained += OnContentFocused; + _content.FocusLost += OnContentUnfocused; + Add(_content); } } @@ -83,6 +92,7 @@ public void ResetState() protected void Initialize() { Layout = new AbsoluteLayout(); + TouchEvent += OnTouchEvent; KeyEvent += OnKeyEvent; FocusGained += OnFocused; @@ -101,9 +111,19 @@ void OnFocused(object? sender, EventArgs e) State = ViewHolderState.Focused; } + void OnContentUnfocused(object? sender, EventArgs e) + { + OnUnfocused(this, e); + } + + void OnContentFocused(object? sender, EventArgs e) + { + OnFocused(this, e); + } + bool OnKeyEvent(object? source, KeyEventArgs e) { - if (e.Key.State == Key.StateType.Down && e.Key.KeyPressedName == "Enter") + if (e.Key.State == Key.StateType.Down && (e.Key.KeyPressedName == "Return" || e.Key.KeyPressedName == "Enter")) { RequestSelected?.Invoke(this, EventArgs.Empty); return true; diff --git a/test/NUIExGallery/Main.cs b/test/NUIExGallery/Main.cs index e20574f..4ac5719 100644 --- a/test/NUIExGallery/Main.cs +++ b/test/NUIExGallery/Main.cs @@ -17,7 +17,7 @@ class App : NUIApplication protected override void OnCreate() { base.OnCreate(); - + FocusManager.Instance.EnableDefaultAlgorithm(true); Initialize(); Stack.Push(CreateListPage(GetTestCases())); } diff --git a/test/NUIExGallery/TC/CollectionViewFocusTest.cs b/test/NUIExGallery/TC/CollectionViewFocusTest.cs new file mode 100644 index 0000000..fb7f67a --- /dev/null +++ b/test/NUIExGallery/TC/CollectionViewFocusTest.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Tizen.NUI; +using Tizen.NUI.BaseComponents; +using Tizen.UIExtensions.NUI; +using Color = Tizen.UIExtensions.Common.Color; +using Size = Tizen.UIExtensions.Common.Size; + +namespace NUIExGallery.TC +{ + public class CollectionViewFocusTest : TestCaseBase + { + public override string TestName => "CollectionView Focus Test"; + + public override string TestDescription => "CollectionView Focus Test"; + + public override View Run() + { + var items = new List(); + for (int i = 0; i < 1000; i++) + { + items.Add($"Items {i}"); + } + + var adaptor = new FocusableMyAdaptor(items); + + var collectionView = new CollectionView(); + collectionView.Adaptor = adaptor; + collectionView.LayoutManager = new LinearLayoutManager(false); + + return collectionView; + } + + public class FocusableMyAdaptor : ItemAdaptor + { + + public FocusableMyAdaptor(IEnumerable items) : base(items) + { + + } + + public override View CreateNativeView() + { + Console.WriteLine($"CreateNativeView..."); + var view = new View(); + view.UpdateBackgroundColor(Color.Gray); + + view.Add(new TextLabel + { + Text = "Default text" + }); + return view; + } + + public override View CreateNativeView(int index) + { + Console.WriteLine($"CreateNativeView... for {index}"); + + var wrapper = new MyWrapper + { + Focusable = true, + FocusableInTouch = true, + Layout = new AbsoluteLayout(), + + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.MatchParent, + }; + wrapper.UpdateBackgroundColor(Color.Yellow); + var item = new TextLabel + { + Text = $"Text for{index}", + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.MatchParent, + HeightResizePolicy = ResizePolicyType.FillToParent, + WidthResizePolicy = ResizePolicyType.FillToParent, + }; + wrapper.Add(item); + return wrapper; + } + + public override View GetFooterView() + { + return null; + } + + public override View GetHeaderView() + { + return null; + } + + public override Size MeasureFooter(double widthConstraint, double heightConstraint) + { + return new Size(0, 0); + } + + public override Size MeasureHeader(double widthConstraint, double heightConstraint) + { + return new Size(0, 0); + } + + public override Size MeasureItem(double widthConstraint, double heightConstraint) + { + return new Size(100, 100); + } + + public override Size MeasureItem(int index, double widthConstraint, double heightConstraint) + { + return new Size(100, 100); + } + + public override void RemoveNativeView(View native) + { + native.Unparent(); + native.Dispose(); + } + + public override void SetBinding(View view, int index) + { + (view.Children[0] as TextLabel).Text = $"(U) - View for {index}"; + } + + public override void UnBinding(View view) + { + (view.Children[0] as TextLabel).Text = $"default!!"; + } + } + + class MyWrapper : View + { + + } + } +} diff --git a/test/NUIExGallery/TC/CollectionViewSelectedTest.cs b/test/NUIExGallery/TC/CollectionViewSelectedTest.cs index 5165f07..055fd12 100644 --- a/test/NUIExGallery/TC/CollectionViewSelectedTest.cs +++ b/test/NUIExGallery/TC/CollectionViewSelectedTest.cs @@ -30,7 +30,28 @@ public override void SendItemSelected(IEnumerable selected) public override void UpdateViewState(View view, ViewHolderState state) { - view.UpdateBackgroundColor(state == ViewHolderState.Selected ? Color.Red : Color.Yellow); + if (state == ViewHolderState.Focused) + { + view.UpdateBackgroundColor(Color.Green); + } + else if (state == ViewHolderState.Selected) + { + view.UpdateBackgroundColor(Color.Red); + } + else + { + view.UpdateBackgroundColor(Color.Yellow); + } + } + + public override View CreateNativeView(int index) + { + var item = base.CreateNativeView(index); + + item.Focusable = true; + item.FocusableInTouch = true; + + return item; } } @@ -85,6 +106,7 @@ public override View Run() layout.Add(horizontal); var selectionNone = new Button { + Focusable = true, Text = "None", SizeWidth = 200, }; @@ -93,6 +115,7 @@ public override View Run() var selectionSingle = new Button { + Focusable = true, Text = "Single", SizeWidth = 200, }; @@ -101,6 +124,7 @@ public override View Run() var selectionMulti = new Button { + Focusable = true, Text = "Multiple", SizeWidth = 200, }; From 1f9b008c4db9cd0b808d8c1c96a75633dd8c123a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=B1=EC=88=98/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/Staff=20Engineer/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Thu, 9 Dec 2021 13:43:00 +0900 Subject: [PATCH 49/76] Fix maximum value for Collectionview item (#113) * Fix maximum value for Collectionview item * Check the maximum value at ToScaledDP --- .../CollectionView/GridLayoutManager.cs | 4 ++-- .../CollectionView/LinearLayoutManager.cs | 4 ++-- src/Tizen.UIExtensions.ElmSharp/Extensions/DPExtensions.cs | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Tizen.UIExtensions.ElmSharp/CollectionView/GridLayoutManager.cs b/src/Tizen.UIExtensions.ElmSharp/CollectionView/GridLayoutManager.cs index 3fa73e9..ba58bad 100644 --- a/src/Tizen.UIExtensions.ElmSharp/CollectionView/GridLayoutManager.cs +++ b/src/Tizen.UIExtensions.ElmSharp/CollectionView/GridLayoutManager.cs @@ -148,8 +148,8 @@ int BaseItemSize int ItemSpacing => IsHorizontal ? HorizontalItemSpacing : VerticalItemSpacing; - int ItemWidthConstraint => IsHorizontal ? _allocatedSize.Width * 100 : ColumnSize; - int ItemHeightConstraint => IsHorizontal ? ColumnSize : _allocatedSize.Height * 100; + int ItemWidthConstraint => IsHorizontal ? int.MaxValue : ColumnSize; + int ItemHeightConstraint => IsHorizontal ? ColumnSize : int.MaxValue; int ColumnSize { diff --git a/src/Tizen.UIExtensions.ElmSharp/CollectionView/LinearLayoutManager.cs b/src/Tizen.UIExtensions.ElmSharp/CollectionView/LinearLayoutManager.cs index 822f903..c9923a6 100644 --- a/src/Tizen.UIExtensions.ElmSharp/CollectionView/LinearLayoutManager.cs +++ b/src/Tizen.UIExtensions.ElmSharp/CollectionView/LinearLayoutManager.cs @@ -127,8 +127,8 @@ int BaseItemSize } } - int ItemWidthConstraint => IsHorizontal ? _allocatedSize.Width * 100 : _allocatedSize.Width; - int ItemHeightConstraint => IsHorizontal ? _allocatedSize.Height : _allocatedSize.Height * 100; + int ItemWidthConstraint => IsHorizontal ? int.MaxValue : _allocatedSize.Width; + int ItemHeightConstraint => IsHorizontal ? _allocatedSize.Height : int.MaxValue; int FooterSize => IsHorizontal ? _footerSize.Width : _footerSize.Height; int HeaderSize => IsHorizontal ? _headerSize.Width : _headerSize.Height; diff --git a/src/Tizen.UIExtensions.ElmSharp/Extensions/DPExtensions.cs b/src/Tizen.UIExtensions.ElmSharp/Extensions/DPExtensions.cs index 453b54b..5d0d495 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Extensions/DPExtensions.cs +++ b/src/Tizen.UIExtensions.ElmSharp/Extensions/DPExtensions.cs @@ -17,6 +17,8 @@ public static int ToScaledPixel(this double dp) public static double ToScaledDP(this int pixel) { + if (pixel == int.MaxValue) + return double.PositiveInfinity; return pixel / DeviceInfo.ScalingFactor; } From 22cf5522eb5a39fb9b0a7e552ca707a60eee2c78 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Mon, 13 Dec 2021 16:06:33 +0900 Subject: [PATCH 50/76] Update ActivityIndicatorDrawable animation --- .../GraphicsView/ActivityIndicatorDrawable.cs | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/ActivityIndicatorDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/ActivityIndicatorDrawable.cs index 16a87ee..a974a8a 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/ActivityIndicatorDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/ActivityIndicatorDrawable.cs @@ -1,4 +1,5 @@ using Microsoft.Maui.Graphics; +using System; using Tizen.UIExtensions.Common.Internal; using GColor = Microsoft.Maui.Graphics.Color; using TSize = Tizen.UIExtensions.Common.Size; @@ -18,8 +19,8 @@ public ActivityIndicatorDrawable(IActivityIndicator view) float MaterialActivityIndicatorStartAngle { get; set; } - float MaterialActivityIndicatorEndAngle { get; set; } - + float MaterialActivityIndicatorSweepAngle { get; set; } + float MaterialActivityIndicatorLastStartAngle { get; set; } public override void Draw(ICanvas canvas, RectangleF dirtyRect) { @@ -37,21 +38,29 @@ public void UpdateAnimation(bool animate) var materialActivityIndicatorAngleAnimation = new Animation(); - - var startAngle = 90; - var endAngle = 360; + MaterialActivityIndicatorStartAngle = 0; + MaterialActivityIndicatorLastStartAngle = 0; var rotateAnimation = new Animation(v => { MaterialActivityIndicatorRotate = (int)v; SendInvalidated(); - }, 0, 360, easing: Easing.Linear); - var startAngleAnimation = new Animation(v => MaterialActivityIndicatorStartAngle = (int)v, startAngle, startAngle - 360, easing: Easing.Linear); - var endAngleAnimation = new Animation(v => MaterialActivityIndicatorEndAngle = (int)v, endAngle, endAngle - 360, easing: Easing.Linear); + }, 0, 360*3, easing: Easing.Linear); + var sweepAnimationUp = new Animation(v => + { + MaterialActivityIndicatorSweepAngle = 30 + (int)v; + MaterialActivityIndicatorLastStartAngle = MaterialActivityIndicatorSweepAngle; + }, 0, 270, easing: Easing.Linear); + var sweepAnimationDown = new Animation(v => + { + MaterialActivityIndicatorSweepAngle = 30 + (int)v; + MaterialActivityIndicatorStartAngle += (Math.Abs(MaterialActivityIndicatorLastStartAngle - MaterialActivityIndicatorSweepAngle)); + MaterialActivityIndicatorLastStartAngle = MaterialActivityIndicatorSweepAngle; + }, 270, 0, easing: Easing.Linear); materialActivityIndicatorAngleAnimation.Add(0, 1, rotateAnimation); - materialActivityIndicatorAngleAnimation.Add(0, 1, startAngleAnimation); - materialActivityIndicatorAngleAnimation.Add(0, 1, endAngleAnimation); + materialActivityIndicatorAngleAnimation.Add(0, 0.5, sweepAnimationUp); + materialActivityIndicatorAngleAnimation.Add(0.5, 1, sweepAnimationDown); materialActivityIndicatorAngleAnimation.Commit(this, "MaterialActivityIndicator", length: 1400, repeat: () => true, finished: (l, c) => materialActivityIndicatorAngleAnimation = null); } @@ -72,7 +81,7 @@ void DrawMaterialActivityIndicator(ICanvas canvas, RectangleF dirtyRect) { canvas.Rotate(MaterialActivityIndicatorRotate, x + strokeWidth + size / 2, y + strokeWidth + size / 2); canvas.StrokeColor = View.Color.ToGraphicsColor(Material.Color.Blue); - canvas.DrawArc(x + strokeWidth, y + strokeWidth, size, size, MaterialActivityIndicatorStartAngle, MaterialActivityIndicatorEndAngle, false, false); + canvas.DrawArc(x + strokeWidth, y + strokeWidth, size, size, MaterialActivityIndicatorStartAngle, MaterialActivityIndicatorStartAngle + MaterialActivityIndicatorSweepAngle, false, false); } else { From 942409422f5700ac34e8f71e070a48e4f7b15726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=EA=B7=BC/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/Staff=20Engineer/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Tue, 14 Dec 2021 09:33:47 +0900 Subject: [PATCH 51/76] Fix SliderDrawable OnTouchMove (#114) --- src/Tizen.UIExtensions.Common/GraphicsView/SliderDrawable.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/SliderDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/SliderDrawable.cs index 391c4a0..4dd51c7 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/SliderDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/SliderDrawable.cs @@ -71,7 +71,8 @@ public override void OnTouchUp(GPoint point) public override void OnTouchMove(GPoint point) { - UpdateValue(point); + if (_isThumbSelected) + UpdateValue(point); } From 91c3cbb5cdf0a3a464982dc4d84cf07d46dd0836 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Mon, 10 Jan 2022 19:09:38 +0900 Subject: [PATCH 52/76] Fix ViewHolder Selection issue --- .../CollectionView/CollectionView.cs | 7 ------- .../CollectionView/ViewHolder.cs | 10 +++++++++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs b/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs index b0e6133..15b1395 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs @@ -806,13 +806,6 @@ protected override void Decelerating(float velocity, Animation animation) } } - public override View? GetNextFocusableView(View currentFocusedView, FocusDirection direction, bool loopEnabled) - { - - // workaround code, to disable SetKeyboardNavigationSupport - return null; - } - void HandleNonMandatorySingle(float velocity, Animation animation) { if (CollectionView.SnapPointsType == SnapPointsType.None) diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs b/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs index 4cf9a59..2eff8e6 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs @@ -17,6 +17,7 @@ public class ViewHolder : View ViewHolderState _state; bool _isSelected; bool _isFocused; + bool _isPressed; View? _content; @@ -134,11 +135,18 @@ bool OnKeyEvent(object? source, KeyEventArgs e) bool OnTouchEvent(object? source, TouchEventArgs e) { - if (e.Touch.GetState(0) == PointStateType.Finished) + if (e.Touch.GetState(0) == PointStateType.Started) { + _isPressed = true; + return true; + } + else if (e.Touch.GetState(0) == PointStateType.Finished && _isPressed) + { + _isPressed = false; RequestSelected?.Invoke(this, EventArgs.Empty); return true; } + _isPressed = false; return false; } From ab66407fcc524ca1fcab6f30fa9e1b2ca50789c4 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Thu, 13 Jan 2022 08:24:58 +0900 Subject: [PATCH 53/76] Fix Text measure --- src/Tizen.UIExtensions.NUI/Editor.cs | 6 +++--- src/Tizen.UIExtensions.NUI/Entry.cs | 6 +++--- src/Tizen.UIExtensions.NUI/Label.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Tizen.UIExtensions.NUI/Editor.cs b/src/Tizen.UIExtensions.NUI/Editor.cs index df7f389..ffffbe4 100644 --- a/src/Tizen.UIExtensions.NUI/Editor.cs +++ b/src/Tizen.UIExtensions.NUI/Editor.cs @@ -1,9 +1,9 @@ +using System; using Tizen.NUI; using Tizen.NUI.BaseComponents; using Tizen.UIExtensions.Common; using AutoCapitalType = Tizen.NUI.InputMethod.AutoCapitalType; using NColor = Tizen.NUI.Color; -using NPanelLayoutType = Tizen.NUI.InputMethod.PanelLayoutType; using Size = Tizen.UIExtensions.Common.Size; using TColor = Tizen.UIExtensions.Common.Color; @@ -207,12 +207,12 @@ public Size Measure(double availableWidth, double availableHeight) else { // even though text but natural size is zero. it is abnormal state - return new Size(Text.Length * PixelSize + 10, PixelSize + 10); + return new Size(Math.Max(Text.Length * PixelSize + 10, availableWidth), PixelSize + 10); } } else { - return new Size(PixelSize, PixelSize); + return new Size(Math.Max(PixelSize + 10, availableWidth), PixelSize + 10); } #pragma warning restore CS0618 } diff --git a/src/Tizen.UIExtensions.NUI/Entry.cs b/src/Tizen.UIExtensions.NUI/Entry.cs index fd40d4c..62298cc 100644 --- a/src/Tizen.UIExtensions.NUI/Entry.cs +++ b/src/Tizen.UIExtensions.NUI/Entry.cs @@ -1,12 +1,12 @@ +using System; using Tizen.NUI; using Tizen.NUI.BaseComponents; using Tizen.UIExtensions.Common; +using AutoCapitalType = Tizen.NUI.InputMethod.AutoCapitalType; using NColor = Tizen.NUI.Color; using NPanelLayoutType = Tizen.NUI.InputMethod.PanelLayoutType; using Size = Tizen.UIExtensions.Common.Size; using TColor = Tizen.UIExtensions.Common.Color; -using AutoCapitalType = Tizen.NUI.InputMethod.AutoCapitalType; -using System; namespace Tizen.UIExtensions.NUI { @@ -232,7 +232,7 @@ public Size Measure(double availableWidth, double availableHeight) } else { - return new Size(PixelSize, PixelSize); + return new Size(Math.Max(PixelSize + 10, availableWidth), PixelSize + 10); } #pragma warning restore CS0618 } diff --git a/src/Tizen.UIExtensions.NUI/Label.cs b/src/Tizen.UIExtensions.NUI/Label.cs index f949171..ed33564 100644 --- a/src/Tizen.UIExtensions.NUI/Label.cs +++ b/src/Tizen.UIExtensions.NUI/Label.cs @@ -172,7 +172,7 @@ public Size Measure(double availableWidth, double availableHeight) } else { - return new Size(PixelSize, PixelSize); + return new Size(PixelSize + 10, PixelSize + 10); } #pragma warning restore CS0618 } From 32a86fc8dc9f92e0c32fbb5f5b1c9e6463766fae Mon Sep 17 00:00:00 2001 From: Kangho Hur Date: Thu, 13 Jan 2022 19:08:05 +0900 Subject: [PATCH 54/76] [ElmSharp] Add RadioButton --- .../RadioButton.cs | 198 ++++++++++++++++++ .../ThemeManager.cs | 2 +- test/ElmSharpExGallery/TC/RadioButtonTest.cs | 104 +++++++++ 3 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 src/Tizen.UIExtensions.ElmSharp/RadioButton.cs create mode 100644 test/ElmSharpExGallery/TC/RadioButtonTest.cs diff --git a/src/Tizen.UIExtensions.ElmSharp/RadioButton.cs b/src/Tizen.UIExtensions.ElmSharp/RadioButton.cs new file mode 100644 index 0000000..f4d9558 --- /dev/null +++ b/src/Tizen.UIExtensions.ElmSharp/RadioButton.cs @@ -0,0 +1,198 @@ +using System; +using Tizen.UIExtensions.Common; +using EvasObject = ElmSharp.EvasObject; +using Radio = ElmSharp.Radio; + +namespace Tizen.UIExtensions.ElmSharp +{ + /// + /// Extends the ElmSharp.Radio control, providing basic formatting features, + /// i.e. font color, size, text color. + /// + public class RadioButton : Radio, IMeasurable, IBatchable + { + /// + /// Initializes a new instance of the class. + /// + /// Parent evas object. + public RadioButton(EvasObject parent) : base(parent) + { + } + + /// + /// Holds the formatted text of the radio button. + /// + readonly Span _span = new Span(); + + /// + /// Gets or sets the radio button's text. + /// + /// The text. + public override string Text + { + get + { + return _span.Text; + } + + set + { + if (value != _span.Text) + { + _span.Text = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the color of the text. + /// + /// The color of the text. + public Color TextColor + { + get + { + return _span.ForegroundColor; + } + + set + { + if (!_span.ForegroundColor.Equals(value)) + { + _span.ForegroundColor = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the color of the text background. + /// + /// The color of the text background. + public Color TextBackgroundColor + { + get + { + return _span.BackgroundColor; + } + + set + { + if (!_span.BackgroundColor.Equals(value)) + { + _span.BackgroundColor = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the font family. + /// + /// The font family. + public string FontFamily + { + get + { + return _span.FontFamily; + } + + set + { + if (value != _span.FontFamily) + { + _span.FontFamily = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the font attributes. + /// + /// The font attributes. + public FontAttributes FontAttributes + { + get + { + return _span.FontAttributes; + } + + set + { + if (value != _span.FontAttributes) + { + _span.FontAttributes = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the size of the font. + /// + /// The size of the font. + public double FontSize + { + get + { + return _span.FontSize; + } + + set + { + if (value != _span.FontSize) + { + _span.FontSize = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Implementation of the IMeasurable.Measure() method. + /// + public virtual Size Measure(double availableWidth, double availableHeight) + { + Resize(availableWidth.ToScaledPixel(), Geometry.Height); + var formattedSize = this.GetTextBlockFormattedSize(); + Resize(Geometry.Width, Geometry.Height); + return new Size + { + Width = MinimumWidth + formattedSize.Width, + Height = Math.Max(MinimumHeight, formattedSize.Height) + }; + } + + void IBatchable.OnBatchCommitted() + { + ApplyTextAndStyle(); + } + + void ApplyTextAndStyle() + { + if (!this.IsBatched()) + { + SetInternalTextAndStyle(_span.GetDecoratedText(), _span.GetStyle()); + } + } + + void SetInternalTextAndStyle(string formattedText, string textStyle) + { + bool isVisible = true; + if (string.IsNullOrEmpty(formattedText)) + { + base.Text = null; + this.SetTextBlockStyle(null); + this.SendTextVisibleSignal(false); + } + else + { + base.Text = formattedText; + this.SetTextBlockStyle(textStyle); + this.SendTextVisibleSignal(isVisible); + } + } + } +} \ No newline at end of file diff --git a/src/Tizen.UIExtensions.ElmSharp/ThemeManager.cs b/src/Tizen.UIExtensions.ElmSharp/ThemeManager.cs index 0646482..8f80721 100644 --- a/src/Tizen.UIExtensions.ElmSharp/ThemeManager.cs +++ b/src/Tizen.UIExtensions.ElmSharp/ThemeManager.cs @@ -670,7 +670,7 @@ public static ESize GetTextBlockFormattedSize(this Radio radio) return radio.EdjeObject[ThemeConstants.Common.Parts.Text].TextBlockFormattedSize; } - public static void SetTextBlockStyle(this Radio radio, string style) + public static void SetTextBlockStyle(this Radio radio, string? style) { var textBlock = radio.EdjeObject[ThemeConstants.Common.Parts.Text]; if (textBlock != null) diff --git a/test/ElmSharpExGallery/TC/RadioButtonTest.cs b/test/ElmSharpExGallery/TC/RadioButtonTest.cs new file mode 100644 index 0000000..6d0d052 --- /dev/null +++ b/test/ElmSharpExGallery/TC/RadioButtonTest.cs @@ -0,0 +1,104 @@ +using ElmSharp; +using System; +using Tizen.UIExtensions.Common; +using Tizen.UIExtensions.ElmSharp; +using Color = Tizen.UIExtensions.Common.Color; +using Label = Tizen.UIExtensions.ElmSharp.Label; + +namespace ElmSharpExGallery.TC +{ + public class RadioButtonTest : TestCaseBase + { + public override string TestName => "RadioButton Test"; + + public override string TestDescription => "Test RadioButton"; + + public override void Run(ElmSharp.Box parent) + { + var scrollview = new ScrollView(parent) + { + WeightX = 1, + WeightY = 1, + AlignmentY = -1, + AlignmentX = -1, + }; + scrollview.Show(); + scrollview.ScrollOrientation = ScrollOrientation.Vertical; + parent.PackEnd(scrollview); + + var scrollCanvas = new ElmSharp.Box(parent) + { + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1, + WeightY = 1, + }; + scrollCanvas.Show(); + scrollview.SetScrollCanvas(scrollCanvas); + + RadioButton rd1 = new RadioButton(parent) + { + StateValue = 1, + Text = "Value #1", + TextColor = Color.Violet, + TextBackgroundColor = Color.Yellow, + AlignmentX = -1, + AlignmentY = 0, + WeightX = 1, + WeightY = 1 + }; + rd1.Show(); + + Radio rd2 = new Radio(parent) + { + StateValue = 2, + Text = "Value #2", + AlignmentX = -1, + AlignmentY = 0, + WeightX = 1, + WeightY = 1 + }; + rd2.Show(); + + Radio rd3 = new Radio(parent) + { + StateValue = 3, + Text = "Value #3", + AlignmentX = -1, + AlignmentY = 0, + WeightX = 1, + WeightY = 1 + }; + rd3.Show(); + + rd2.SetGroup(rd1); + rd3.SetGroup(rd2); + + var label = new Label(parent) + { + AlignmentX = -1, + AlignmentY = 0, + WeightX = 1, + WeightY = 1 + }; + label.Show(); + + rd1.ValueChanged += OnRadioValueChanged; + rd2.ValueChanged += OnRadioValueChanged; + rd3.ValueChanged += OnRadioValueChanged; + + void OnRadioValueChanged(object sender, EventArgs e) + { + label.Text = string.Format("Value Changed: {0}", ((Radio)sender).GroupValue); + } + + scrollCanvas.PackEnd(label); + scrollCanvas.PackEnd(rd1); + scrollCanvas.PackEnd(rd2); + scrollCanvas.PackEnd(rd3); + + scrollview.SetContentSize(720, 3000); + + } + } +} From 5aff8b647e24a3710a97832ebd8d813ea3d7f5d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=EA=B7=BC/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/Staff=20Engineer/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Tue, 25 Jan 2022 13:24:57 +0900 Subject: [PATCH 55/76] [NUI] Fix collection view (#119) * Fix size calculation code when item was added in GridlayoutManager * Fix CollectionView nullable issue * Update Header/Footer mesasure changed --- .../CollectionView/CollectionView.cs | 31 ++++++++++++++----- .../CollectionView/GridLayoutManager.cs | 4 +-- .../CollectionView/ItemAdaptor.cs | 4 +-- .../CollectionView/LinearLayoutManager.cs | 2 +- 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs b/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs index 15b1395..610209b 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs @@ -284,6 +284,12 @@ protected virtual void InitializationComponent() void ICollectionViewController.ItemMeasureInvalidated(int index) { + if (index == -1) + { + UpdateHeaderFooter(); + RequestLayoutItems(); + return; + } // If a first item size was updated, need to reset _itemSize if (index == 0) { @@ -553,6 +559,7 @@ void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { idx = Adaptor!.Count - e.NewItems.Count; } + foreach (var item in e.NewItems) { foreach (var viewHolder in _viewHolderIndexTable.Keys.ToList()) @@ -666,10 +673,13 @@ void OnScrolling(object? sender, ScrollEventArgs e) void SendScrolledEvent() { + if (LayoutManager == null) + return; + var args = new CollectionViewScrolledEventArgs(); - args.FirstVisibleItemIndex = LayoutManager!.GetVisibleItemIndex(ViewPort.X, ViewPort.Y); - args.CenterItemIndex = LayoutManager!.GetVisibleItemIndex(ViewPort.X + (ViewPort.Width / 2), ViewPort.Y + (ViewPort.Height / 2)); - args.LastVisibleItemIndex = LayoutManager!.GetVisibleItemIndex(ViewPort.X + ViewPort.Width, ViewPort.Y + ViewPort.Height); + args.FirstVisibleItemIndex = LayoutManager.GetVisibleItemIndex(ViewPort.X, ViewPort.Y); + args.CenterItemIndex = LayoutManager.GetVisibleItemIndex(ViewPort.X + (ViewPort.Width / 2), ViewPort.Y + (ViewPort.Height / 2)); + args.LastVisibleItemIndex = LayoutManager.GetVisibleItemIndex(ViewPort.X + ViewPort.Width, ViewPort.Y + ViewPort.Height); args.HorizontalOffset = ViewPort.X; args.HorizontalDelta = ViewPort.X - _previousHorizontalOffset; args.VerticalOffset = ViewPort.Y; @@ -682,11 +692,18 @@ void SendScrolledEvent() void UpdateHeaderFooter() { - LayoutManager?.SetHeader(_headerView, - _headerView != null ? Adaptor!.MeasureHeader(AllocatedSize.Width, AllocatedSize.Height) : new Size(0, 0)); + if (LayoutManager != null) + { + double widthConstraint = LayoutManager.IsHorizontal ? double.PositiveInfinity : AllocatedSize.Width; + double heightConstraint = LayoutManager.IsHorizontal ? AllocatedSize.Height : double.PositiveInfinity; + + LayoutManager.SetHeader(_headerView, + _headerView != null ? Adaptor!.MeasureHeader(widthConstraint, heightConstraint) : new Size(0, 0)); + + LayoutManager.SetFooter(_footerView, + _footerView != null ? Adaptor!.MeasureFooter(widthConstraint, heightConstraint) : new Size(0, 0)); + } - LayoutManager?.SetFooter(_footerView, - _footerView != null ? Adaptor!.MeasureFooter(AllocatedSize.Width, AllocatedSize.Height) : new Size(0, 0)); } void UpdateSelectionMode() diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/GridLayoutManager.cs b/src/Tizen.UIExtensions.NUI/CollectionView/GridLayoutManager.cs index 7e433e7..5771d40 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/GridLayoutManager.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/GridLayoutManager.cs @@ -409,7 +409,7 @@ public void ItemMeasureInvalidated(int index) { if (_hasUnevenRows) { - if (_cached.Count > index) + if (index >= 0 && _cached.Count > index) _cached[index] = false; if (_realizedItem.ContainsKey(index)) @@ -597,7 +597,7 @@ void BuildAccumulatedSize() for (int i = 0; i < n; i++) { int accIndex = i / Span; - double prevSize = accIndex > 0 ? (_accumulatedItemSizes[accIndex - 1] + ItemSpacing) : 0; + double prevSize = accIndex > 0 ? (_accumulatedItemSizes[accIndex - 1] + ItemSpacing) : ItemStartPoint; if (i % Span == 0) { _accumulatedItemSizes.Add(prevSize); diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/ItemAdaptor.cs b/src/Tizen.UIExtensions.NUI/CollectionView/ItemAdaptor.cs index 16f2f0a..09e149f 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/ItemAdaptor.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/ItemAdaptor.cs @@ -137,13 +137,13 @@ public virtual object GetViewCategory(int index) /// Create a header view, if header is not existed, null will be returned /// /// A created view - public abstract View GetHeaderView(); + public abstract View? GetHeaderView(); /// /// Create a footer view, if footer is not existed, null will be returned /// /// A created view - public abstract View GetFooterView(); + public abstract View? GetFooterView(); /// /// Remove view, a created view by Adaptor, should be removed by Adaptor diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/LinearLayoutManager.cs b/src/Tizen.UIExtensions.NUI/CollectionView/LinearLayoutManager.cs index 72f6cea..606f098 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/LinearLayoutManager.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/LinearLayoutManager.cs @@ -340,7 +340,7 @@ public void ItemMeasureInvalidated(int index) { if (_hasUnevenRows) { - if (_cached.Count > index) + if (index >= 0 && _cached.Count > index) _cached[index] = false; if (_realizedItem.ContainsKey(index)) From 44c81c0d78705e734e5d4584ad321fddfed48fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=EA=B7=BC/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/Staff=20Engineer/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Thu, 27 Jan 2022 14:52:43 +0900 Subject: [PATCH 56/76] Fix GraphicsView touch issue and add more MaterialIconDrawable (#120) --- .../GraphicsView/MaterialIconDrawable.cs | 20 +++++++++++++- .../GraphicsView/StepperDrawable.cs | 26 +++++++++++++------ .../GraphicsView/Button.cs | 7 ++++- .../GraphicsView/MaterialIconButton.cs | 9 ++++++- 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/MaterialIconDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/MaterialIconDrawable.cs index 0a717d7..b942f78 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/MaterialIconDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/MaterialIconDrawable.cs @@ -21,7 +21,13 @@ public enum MaterialIcons MoreHoriz, MoreVert, Remove, - RemoveCircle + RemoveCircle, + Search, + Replay, + Account, + Settings, + ArrowLeft, + ArrowRight, } public class MaterialIconDrawable : GraphicsViewDrawable @@ -46,6 +52,12 @@ public class MaterialIconDrawable : GraphicsViewDrawable const string PathMoreVert = "M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"; const string PathRemove = "M19 13H5v-2h14v2z"; const string PathRemoveCircle = "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11H7v-2h10v2z"; + const string PathSearch = "M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"; + const string PathReplay = "M12,5V1L7,6L12,11V7A6,6 0 0,1 18,13A6,6 0 0,1 12,19A6,6 0 0,1 6,13H4A8,8 0 0,0 12,21A8,8 0 0,0 20,13A8,8 0 0,0 12,5Z"; + const string PathAccount = "M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z"; + const string PathSettings = "M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"; + const string PathArrowLeft = "M15.41,16.58L10.83,12L15.41,7.41L14,6L8,12L14,18L15.41,16.58Z"; + const string PathArrowRight = "M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z"; static readonly string[] s_Paths = { @@ -65,6 +77,12 @@ public class MaterialIconDrawable : GraphicsViewDrawable PathMoreVert, PathRemove, PathRemoveCircle, + PathSearch, + PathReplay, + PathAccount, + PathSettings, + PathArrowLeft, + PathArrowRight, }; public MaterialIconDrawable() diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/StepperDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/StepperDrawable.cs index e00f8de..508bb95 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/StepperDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/StepperDrawable.cs @@ -16,6 +16,7 @@ public class StepperDrawable : GraphicsViewDrawable RectangleF _minusRect; RectangleF _plusRect; + bool _pressed; readonly RippleEffectDrawable _minusRippleEffect; readonly RippleEffectDrawable _plusRippleEffect; @@ -54,27 +55,36 @@ public override TSize Measure(double availableWidth, double availableHeight) public override void OnTouchDown(GPoint point) { - var touchDownPoint = new PointF((float)point.X, (float)point.Y); - - if (_minusRect.Contains(touchDownPoint)) - View.Value -= View.Increment; - - if (_plusRect.Contains(touchDownPoint)) - View.Value += View.Increment; - _minusRippleEffect.ClipRectangle = _minusRect; _plusRippleEffect.ClipRectangle = _plusRect; _plusRippleEffect.OnTouchDown(point); _minusRippleEffect.OnTouchDown(point); + + _pressed = true; } public override void OnTouchUp(GPoint point) { + var touchDownPoint = new PointF((float)point.X, (float)point.Y); + + if (_minusRect.Contains(touchDownPoint) && _pressed) + View.Value -= View.Increment; + + if (_plusRect.Contains(touchDownPoint) && _pressed) + View.Value += View.Increment; + _minusRippleEffect.OnTouchUp(point); _plusRippleEffect.OnTouchUp(point); + _pressed = false; } + public override void OnTouchMove(GPoint point) + { + _pressed = false; + } + + void DrawMaterialStepperMinus(ICanvas canvas, RectangleF dirtyRect) { canvas.SaveState(); diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/Button.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/Button.cs index 5f49bff..b2ec023 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/Button.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/Button.cs @@ -9,6 +9,7 @@ namespace Tizen.UIExtensions.NUI.GraphicsView /// public class Button : GraphicsView, IButton { + Tizen.NUI.PointStateType _lastPointState; /// /// Initializes a new instance of the Button class. /// @@ -87,13 +88,17 @@ protected override bool OnTouch(object source, TouchEventArgs e) { IsPressed = true; Pressed?.Invoke(this, EventArgs.Empty); - Clicked?.Invoke(this, EventArgs.Empty); } else if (state == Tizen.NUI.PointStateType.Up) { IsPressed = false; Released?.Invoke(this, EventArgs.Empty); + if (_lastPointState == Tizen.NUI.PointStateType.Down) + { + Clicked?.Invoke(this, EventArgs.Empty); + } } + _lastPointState = state; return base.OnTouch(source, e); } } diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs index b6a8788..64e4151 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs @@ -9,6 +9,9 @@ namespace Tizen.UIExtensions.NUI.GraphicsView /// public class MaterialIconButton : GraphicsView { + + Tizen.NUI.PointStateType _lastPointState; + /// /// Initializes a new instance of the Button class. /// @@ -84,13 +87,17 @@ protected override bool OnTouch(object source, TouchEventArgs e) { IsPressed = true; Pressed?.Invoke(this, EventArgs.Empty); - Clicked?.Invoke(this, EventArgs.Empty); } else if (state == Tizen.NUI.PointStateType.Up) { IsPressed = false; Released?.Invoke(this, EventArgs.Empty); + if (_lastPointState == Tizen.NUI.PointStateType.Down) + { + Clicked?.Invoke(this, EventArgs.Empty); + } } + _lastPointState = state; return base.OnTouch(source, e); } } From 59097adc7d0f1026db90a789307a8897a20fa1ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=97=88=EA=B0=95=ED=98=B8/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Fri, 4 Feb 2022 16:52:12 +0900 Subject: [PATCH 57/76] Update README.md --- README.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/README.md b/README.md index 293e883..09d3706 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,2 @@ # Tizen.UIExtensions The Tizen.UIExtensions provides a set of advanced views and containers based on Tizen.NET UI Framework. - -### How to use Tizen90 -Use myget package repository -https://tizen.myget.org/feed/dotnet/package/nuget/Tizen.NET - -#### Nuget (PM Console) -``` bash -PM> Install-Package Tizen.NET -Version 9.0.0.16047 -Source https://tizen.myget.org/F/dotnet/api/v3/index.json -``` From e99c77c5c4176b4c064ccb74507dd1a00ee68ffc Mon Sep 17 00:00:00 2001 From: Kangho Hur Date: Fri, 4 Feb 2022 17:01:00 +0900 Subject: [PATCH 58/76] Use net6.0 and update the Microsoft.Maui.Graphics version --- Versions.prop | 5 +++ .../GraphicsView/ButtonDrawable.cs | 2 +- .../Tizen.UIExtensions.ElmSharp.csproj | 6 +-- .../Tizen.UIExtensions.NUI.csproj | 6 +-- .../ElmSharpExGallery.csproj | 19 ++++----- test/NUIExGallery/NUIExGallery.csproj | 39 ++++++++----------- 6 files changed, 36 insertions(+), 41 deletions(-) create mode 100644 Versions.prop diff --git a/Versions.prop b/Versions.prop new file mode 100644 index 0000000..700911b --- /dev/null +++ b/Versions.prop @@ -0,0 +1,5 @@ + + + 6.0.200-preview.13.935 + + diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/ButtonDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/ButtonDrawable.cs index 84ca2ad..b694c6f 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/ButtonDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/ButtonDrawable.cs @@ -81,7 +81,7 @@ void DrawMaterialButtonText(ICanvas canvas, RectangleF dirtyRect) { canvas.SaveState(); - canvas.FontName = "Roboto"; + canvas.Font = new Font("Roboto"); canvas.FontColor = View.TextColor.ToGraphicsColor(Material.Color.White); var x = dirtyRect.X; diff --git a/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj b/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj index ead768a..cad6cbf 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj +++ b/src/Tizen.UIExtensions.ElmSharp/Tizen.UIExtensions.ElmSharp.csproj @@ -22,12 +22,12 @@ 8839360a-88a7-448e-9b20-4fb785592264 + - - - + + diff --git a/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj b/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj index 5d76682..cd1c622 100644 --- a/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj +++ b/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj @@ -22,15 +22,15 @@ - - - + + + diff --git a/test/ElmSharpExGallery/ElmSharpExGallery.csproj b/test/ElmSharpExGallery/ElmSharpExGallery.csproj index 13668e0..7d8ae1f 100644 --- a/test/ElmSharpExGallery/ElmSharpExGallery.csproj +++ b/test/ElmSharpExGallery/ElmSharpExGallery.csproj @@ -1,13 +1,10 @@ - - - - Exe - tizen40 - ElmSharpExGallery - - - - - + + + net6.0-tizen + Exe + + + + diff --git a/test/NUIExGallery/NUIExGallery.csproj b/test/NUIExGallery/NUIExGallery.csproj index c1c098d..20680d3 100644 --- a/test/NUIExGallery/NUIExGallery.csproj +++ b/test/NUIExGallery/NUIExGallery.csproj @@ -1,27 +1,20 @@ - + + + net6.0-tizen + Exe + - - Exe - tizen90 - True - NUIExGallery - + + + + - - - - - - - - - - - - - - - - + + + + + + + From 3a71bf9a56a72a881e93af057afd504312589691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=A4=91=ED=98=84/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Mon, 7 Feb 2022 10:26:18 +0900 Subject: [PATCH 59/76] update build action --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 69e5166..c49a6cf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: build: runs-on: code-large container: - image: mcr.microsoft.com/dotnet/sdk:6.0 + image: mcr.microsoft.com/dotnet/sdk:6.0.101 options: --user root steps: - uses: CODE-Actions/checkout@v2 @@ -21,10 +21,10 @@ jobs: - name: Install Tizen workload run: | apt-get update && apt-get install -y unzip - curl -sSL https://github.com/raw/Samsung/Tizen.NET/main/workload/scripts/workload-install.sh | bash + curl -sSL https://github.com/raw/Samsung/Tizen.NET/release/6.0.1xx-rc1/workload/scripts/workload-install.sh | bash - name: Build env: PULLREQUEST_ID: ${{ github.event.number }} run: | dotnet build Tizen.UIExtensions.sln - working-directory: . \ No newline at end of file + working-directory: . From b078d13011b7fe8128c7abf0768e666d23d59dd4 Mon Sep 17 00:00:00 2001 From: Kangho Hur Date: Tue, 22 Feb 2022 17:56:58 +0900 Subject: [PATCH 60/76] Update build.yml --- .github/workflows/build.yml | 4 ++-- test/ElmSharpExGallery/tizen-manifest.xml | 2 +- test/NUIExGallery/tizen-manifest.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c49a6cf..8d15ae1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: build: runs-on: code-large container: - image: mcr.microsoft.com/dotnet/sdk:6.0.101 + image: mcr.microsoft.com/dotnet/sdk:6.0.200 options: --user root steps: - uses: CODE-Actions/checkout@v2 @@ -21,7 +21,7 @@ jobs: - name: Install Tizen workload run: | apt-get update && apt-get install -y unzip - curl -sSL https://github.com/raw/Samsung/Tizen.NET/release/6.0.1xx-rc1/workload/scripts/workload-install.sh | bash + curl -sSL https://github.com/raw/Samsung/Tizen.NET/release/7.0.100-preview.6/workload/scripts/workload-install.sh | bash - name: Build env: PULLREQUEST_ID: ${{ github.event.number }} diff --git a/test/ElmSharpExGallery/tizen-manifest.xml b/test/ElmSharpExGallery/tizen-manifest.xml index 66ed3ab..adea39f 100644 --- a/test/ElmSharpExGallery/tizen-manifest.xml +++ b/test/ElmSharpExGallery/tizen-manifest.xml @@ -1,5 +1,5 @@  - + diff --git a/test/NUIExGallery/tizen-manifest.xml b/test/NUIExGallery/tizen-manifest.xml index 38217bf..5847ec6 100644 --- a/test/NUIExGallery/tizen-manifest.xml +++ b/test/NUIExGallery/tizen-manifest.xml @@ -1,5 +1,5 @@  - + From 604ac52aed8eb9dab94f1e7f91804ce4e0fbd944 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Mon, 7 Mar 2022 11:41:47 +0900 Subject: [PATCH 61/76] Fix SKClipperView --- .../Skia/SKClipperView.cs | 2 +- test/NUIExGallery/TC/ClippingTest2.cs | 159 ++++++++++++++++++ test/NUIExGallery/res/dotnet_bot.png | Bin 0 -> 114419 bytes 3 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 test/NUIExGallery/TC/ClippingTest2.cs create mode 100644 test/NUIExGallery/res/dotnet_bot.png diff --git a/src/Tizen.UIExtensions.NUI/Skia/SKClipperView.cs b/src/Tizen.UIExtensions.NUI/Skia/SKClipperView.cs index 2afc808..ebc08a5 100644 --- a/src/Tizen.UIExtensions.NUI/Skia/SKClipperView.cs +++ b/src/Tizen.UIExtensions.NUI/Skia/SKClipperView.cs @@ -34,7 +34,7 @@ public class SKClipperView : NView "void main(){\n" + " mediump vec4 texColor = texture2D(sTexture, vTexCoord) * uColor;\n" + " if (texColor.r < 1 || texColor.g < 1 || texColor.b < 1) discard;\n" + - " gl_FragColor = texColor;\n" + + " gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);\n" + "}\n" + ""; diff --git a/test/NUIExGallery/TC/ClippingTest2.cs b/test/NUIExGallery/TC/ClippingTest2.cs new file mode 100644 index 0000000..04144f2 --- /dev/null +++ b/test/NUIExGallery/TC/ClippingTest2.cs @@ -0,0 +1,159 @@ +using System; +using Tizen.NUI.BaseComponents; +using Tizen.NUI; +using Tizen.UIExtensions.NUI; +using Tizen.UIExtensions.Common; +using SkiaSharp; +using Tizen.Applications; +using Color = Tizen.NUI.Color; + +namespace NUIExGallery.TC +{ + public class ClippingTest2 : TestCaseBase + { + public override string TestName => "Clipping Test2"; + + public override string TestDescription => "Clipping test1"; + + bool isCircle = true; + + public override View Run() + { + var scrollView = new Tizen.UIExtensions.NUI.ScrollView + { + BackgroundColor = Color.Blue, + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.MatchParent + }; + + scrollView.ContentContainer.Layout = new LinearLayout + { + LinearAlignment = LinearLayout.Alignment.Center, + LinearOrientation = LinearLayout.Orientation.Vertical, + }; + + scrollView.ContentContainer.WidthSpecification = LayoutParamPolicies.MatchParent; + scrollView.ContentContainer.HeightSpecification = LayoutParamPolicies.WrapContent; + + { + var clipper = new SKClipperView + { + WidthSpecification = LayoutParamPolicies.MatchParent, + SizeHeight = 300, + }; + clipper.DrawClippingArea += OnDrawCircle; + clipper.Add(new Image + { + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.MatchParent, + ResourceUrl = Application.Current.DirectoryInfo.Resource + "dotnet_bot.png", + }); + + scrollView.ContentContainer.Add(clipper); + + var btn = new Button + { + Text = "Change" + }; + scrollView.ContentContainer.Add(btn); + + btn.Clicked += (s, e) => + { + if (isCircle) + { + clipper.DrawClippingArea -= OnDrawCircle; + clipper.DrawClippingArea += OnDrawRoundRect; + } + else + { + clipper.DrawClippingArea -= OnDrawRoundRect; + clipper.DrawClippingArea += OnDrawCircle; + } + isCircle = !isCircle; + clipper.Invalidate(); + }; + } + + for (int i = 0; i < 10; i++) + { + var clipper = new SKClipperView + { + WidthSpecification = LayoutParamPolicies.MatchParent, + SizeHeight = 300, + }; + clipper.DrawClippingArea += (s, e) => + { + var canvas = e.Surface.Canvas; + var width = e.Info.Width; + var height = e.Info.Height; + + using (var paint = new SKPaint + { + IsAntialias = true, + Color = SKColors.White, + Style = SKPaintStyle.Fill, + }) + { + canvas.Clear(); + canvas.DrawCircle(width / 2.0f, height / 2.0f, Math.Min(width, height) / 2.0f, paint); + } + }; + + clipper.Add(new Image + { + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.MatchParent, + ResourceUrl = Application.Current.DirectoryInfo.Resource + "dotnet_bot.png", + }); + + clipper.Add(new Label + { + WidthSpecification = LayoutParamPolicies.MatchParent, + HeightSpecification = LayoutParamPolicies.MatchParent, + Text = "Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text", + LineBreakMode = LineBreakMode.CharacterWrap, + + }); + scrollView.ContentContainer.Add(clipper); + } + + return scrollView; + } + + private void OnDrawRoundRect(object sender, SkiaSharp.Views.Tizen.SKPaintSurfaceEventArgs e) + { + var canvas = e.Surface.Canvas; + var width = e.Info.Width; + var height = e.Info.Height; + + using (var paint = new SKPaint + { + IsAntialias = true, + Color = SKColors.White, + Style = SKPaintStyle.Fill, + }) + { + canvas.Clear(); + canvas.DrawRoundRect(100, 10, width - 200, height - 20, 40, 40, paint); + } + } + + private void OnDrawCircle(object sender, SkiaSharp.Views.Tizen.SKPaintSurfaceEventArgs e) + { + var canvas = e.Surface.Canvas; + var width = e.Info.Width; + var height = e.Info.Height; + + using (var paint = new SKPaint + { + IsAntialias = true, + Color = SKColors.White, + Style = SKPaintStyle.Fill, + }) + { + canvas.Clear(); + canvas.DrawCircle(width / 2.0f, height / 2.0f, Math.Min(width, height) / 2.0f, paint); + } + } + } +} diff --git a/test/NUIExGallery/res/dotnet_bot.png b/test/NUIExGallery/res/dotnet_bot.png new file mode 100644 index 0000000000000000000000000000000000000000..1096e673e93e3463e393c80d20d2d6dda98fc80d GIT binary patch literal 114419 zcmXtA1yq#J*Iq&d$prxc=@di}DM^u~OS((CI|ONgr9>K~q&uWL1qA8tR$@u%{$~CC zKM&}U^X@w{ckaD&@AEuE=vxIz+y~?jAP@+yv=mGk0zpMUAZSvU7~m&97#Y3bA9Uw8 z(yEx?&j(BsI0Qlik%oz?dZg?uymxuL@t)~2&(mNe()&I+Hi{i2IlE3e0aK^O^gU53 z>{F_|su?G5Z?m)b+tqrxy;SH2GpDs5nc9Q%5kp_Tz?^s#R~Zswr^#N`h%?qOzS3?J zCv-<;fO~Iw?-yR(Z@Y(%sp_h661?G#NB><(?S?hRKg6JQ=?aU6E;EL!?&u=$XB(T; zs2_SeE`(q?nBje*!IKnpi0Eep83KuZ{*~(I`1qgJn+@scl;v1lP+%~IA;YDkD69Z3 z8qr6ZWIu@sIp`qI5v~nozNQ`ynmL4_a4Az91Pms=lQgw40AD2{ba+HK|FtR95BmC% z)7D3Od|doX?)Ug~bbSvx`JqbDWPdN`MU_5SJ^AMK_p}@J+Rx#K`{m};(Pc(T zN|#CJC@Ce9Im6oUIDo@u7)!B2C>Wl9@Mtm={$QtWYD3!WhdJZ2Lxy#IEREsj#h9XR z!LTa<)Yb$!je9OtcRwm)K4!d`a@@>E7-*CV&xZsz3A=O9x3HjuJka~q zfNnIMLnPearT~lcMSk|ZTR-9`SA450rRVt{A~XT0k|;+p5%l-SySJnU?3);)HZRJ- ze<_j6NVA|EKf$2HVM2>wLUZw?B>c`mIFj99-+_FaM(`Wb*RU#R{=aADtBD=dXN}zF zmW!)8EwpcRJUP?e2*$krN!{L|3c*%s-&xX(X=;yHM>{Y%#iymCtFNn2fMtKDEgPaF z=qj>8Kk2)CFkYjYm0M5f+5D`?oZ+4Hd>vO1MPhD6saW2W_uh|#>=#9-O%HSGjY zOO_<40s3dPx9)Ye7JH?Vde+r_YV1zxv)|V;29oOdBV{|M;ufB^QxfEmzBRf33JQ&< zCu2uB4fhQc>K8mG9v24owyndxUmOKsygFz~+ zJs9XRxhc;s1qly|N3Y~a=r5_a2dv-T_q(hiw-FjHId74ZP`)0Lg2Dd2Kn_4dE3>5B zkTI@q6y$D*onqIhZdy{%&>lJ9#ZSv}*{?>59Efkk^^6qnKJ;hs1X@P%L>U4DPkb@447GA2z z>}~DX2Nlg0s7Y%Tht$p^sd8xnT7mOTG^WT72VEh=hKMj+tT<#OrTFMHA$*kCV zsL+w%o^r6~I+P(s%At-pU6-Yl4?c&x^vrA7LZ&b$N|&<}V%i==D_YA=<+CRki* z%X#Y!YoQVZxN$T_*j?T|5R|fJu$kDm{X9fgUuBKgnC1-O@CQ-KIoXxZl-mEZMLloo-=_T=^L|gWFh0Zko7tKN zY+xHE84H+FW2k{Or6`cLcqN6RUq97g9k!o6T8)e|Vk%@{;V~%1QBJ0+xv|SC=`jre ztEgK13(j}nld<&z=t3>toK_Fv;-`(5KcvgV4M$E=vDi&EL-sAhP83N4I{4wgnct5= zpE0Jum1s`L$!Cp)LUFr(>-)(3(uTu%I6;=7)21blcv7EYOc4s_>Hx#Q*kEFRI~!;W zH9M8TGhE0_G=Gk*=SPBCNjo$3fFBw6(Go~&JAE@zdqv0+iJKh5VssjOEfV;sJs!NW ziixP?C3&dJ-c+nb3xoXt zqsL)4>-)5xcBsDorK}n)m(Ahrt$%vLpW4>>84l}JumYD~!0mK<4>3|Nd_@RTyTSnC!5sr_5~5L_VezviBl{c?Ey^ zfHg)UH~9A^TW4S=0SD{ZCCEZmPbI&*a7N1JnIz&v2pDiIRq>$E1%I$JQpA5H9}Pwf z(33n#d+~{MkxY~`A;ve*Z)9UgaDe0yU8bNRGJO1F)E`0k?#eW(S;eFAeLTsWiT*0G z-K#4Ec2Rtjfem7SG|-z_0hN#C?DFd$8Jn5893$~FCNo%yFM`#qC;qV?`s=p*{o^qz z+d?|J%tl93FgBxSv%*N&(N5&Orn^6k;zXL`pCj@dE)53R@V6ny{0m07GT>RSr9wS>LmsA;?!p(3wj64V_Gwu0FTG^2R95-ZIm%;`~d?%VcHtG_g>F zf!|~pu_)E-@%h#z65{d31J+|(I02Qk=o|Fl9DpX_{{A~zi`f>NQUKzN4;g3VuV&p> z%h8+iyDM=dgmkMFruAxcmg>ZnkVh8RLoo&dl{MQ0Bjiwm$S}e!Ae%l46$Pg&AJTg) zcPz{HKeL2AHy!INRo%aJ+wrKQP!GW}xGV8hy@jBlh;~3RKxD19A)WqyPm^%jsC-@& z<$HqL0uG5mxp_}^4x->LyMP%pR7f3y(qZb=$l?@4(O|6vAu*(2LNuvJc04a915=el zM&e&sZ?1}>0h-V6yBm|qePVp(gDZn;XQaz(=?8j_{WeWE#B-FHkk{isQ!Kh zUiF`4Rc2AwN}VmVKy<_tAZ-`<#L^$duk|{Wdb!%ZDcpnMD&FzVg%9Bs3eWfWXH7Y2 z?0)&Oo36%ld&TAm6_L7pzN)LdPLJQMtp&&I;4w_cLEyf@pS--&U=p_0 zyz8zemv~*&*S^!^qd=I-`i<868NeRXP$|4S-sywvmAZdx$=X(LjOa4zPQO0=_}~Q0 znfBiSv;LodFF2J}^U0m1lr0WxMFtOA?fBXx7oI?&N{4G)kAQvQnS3;7vy0x_k3WLy z<984;C4p*X#>o5VmWN%ImM)X93;1C+F^a&tzGoT8NOZH)$ZfBp6IE|*$SV^?$iU(G zuzzDNCrvS5(PW>tNc{K7ZsaFXpa(yjPxoGJgZJ|yA1koWQ!Z1&4+ zosIu|rS@y^7qU0<7Lqm6C&)H}_((rbSuYfXMhpHsuYYpqqqPyWUP&`#N?$KfUt^BE zew@~V@!|BR7>Lq`BG!Mm1(7~8*!n`7jzS|S7Bw3xO%R;XQ!Y!WfD5_+Y&~MA(s!iK z#TYqk9Nmu&)*)bc&fvj-NrmZPK9=so94H-ihj{Kw<{60BtNGyWG8O+|t@Tx;2Ok!Q zanG~btc=PByK@?!+>aGQphLTC^QXQWAZt2_+V~7-!Cnf0 zxGYVDd8aP~?_aM(edi!kuAJ}C!Z~1O6n(_A`EhC*TM+F9SgwHeQ3 zP@6ul4~c7xN&1-b3Ll?;QfmbVlr9-nPfl$QhodD4QR2pD1+xS_V~*{Sd7bddk`s#s z<&cC$e$7-fNSp6PwS9u3ra`=eGC_8%t~Yb06Vkyy`*%jGZkqO6C0)}OWb%teESMeTkgaS6EM`5= z74BenI@bIDT9fQtofX{}3|cA^j>=r;Ph- z@=r(#YJ9whF&%l{nz}2Kcyb+c->~xNHL%AFVxQPh{1Bg zfJe&Aj*7zEgPnQH<9e|l_V25YZFfHxyn0cGNKK;Bd(9e+0pW5}6C3y9ywlGk z8m_Fx74n)p=&oJrv|jSia8&$Y8I8e!g|-#J$2INS(K>!~TcO3=)crjgrpgNTH9s5Y zz4zX034WH>{0vxpanCv&eDX?D2hQu4E;0Mn=^-?N)XgW=*8B3S*U3}Ejb-w&XCyAa zeC>!Yy9S0cEo~rD`ffbL z4<&BSnM~+GmnQ%0Cl*vH`3!mF9Lh$$wjW>e-2E$He12r!>W(<`%))EEPNx>1oAjJm zx*JS$HLu1EeC};DfygamU#4z7<(C+GCi1%1aNKw)D?Hi2I2xUBgdygTf$;l7<=%(N zpY3i%X-8YG1Fc)TUX0XbDBhy~uVftqpoOS+RO$6lp zMGog@!uMjk>UK^jNX-r;74L2;ruyB8@b31Qm6nuptZ;n~>|3$!#@}h$1BYYwy&JRE zzpL0!cf1TqOlY30oD00H`RckP+Yg6}6T&4)#coPkB?#fcs{}7s8-`dOpk4=jOeC8R z&DSi)4Tgtx@A`cb+scW|5TBW=_!LGifV4z0<~HQ*T#XZfJYCF>^xKfTAcLD5#jL`D zH`V-mI6nuP5sE1VXT{f@9rGUz)HSEJTtS&-R90SkdE&w4Z^7}&f+bj!ujQD{-=yiM z`a0>HDIVJBN{+BQ25?#yE*l!#P@1`SJ2Y+>qw>hjePMZ*152$(Ow*YCG@MSOr8{r+YfhY zS%y@OeiCRrCHl_r<_LOj9*Ef=On}?X{oZ#bA;61?o{DNm3MJY!x-+Z`z*1l@JoXT&LQ(=~wT%dmO6y!1ua;$@tIg?vdZ#-Hh;uuThA_p0M%?nm{rKC_IR9J_HsJjJ0pJhvhJfpsmyOD^K_V<5Pb|1PF z(T$>l)M>dr$;zKiD|PU&fy(fZgD!6lFJulwGKW&37FXhE7UfFeiwlPRhqLi8`B)KW z8^|2>cHBusZ_&~Dnz3(`%j)7Tm8}lquy?76+I2U+BJC9~Rhz-hAb!F<*UABS-tFd5 zIbT0wkvfXu@XFx4lDN!%W-jQf*?aDFS-J$6Ay3sYTZCp^8{0kveVMUY|4i*u00SL2 zIKZQskhN4ci!dWf2(8iY(muf#UvU9`v*Iw9ILjV)?`T5KAieXVHleny>UeThBmF8O zb3^w7g8z2I_fnYQa5359UaDUz^?v*5eSwjdW|RQC-?h6iu26UVdOwZ39oy{KU!8Xw zzE?&z!o$xRbC)i@Bz~4=zZ3hJo4RXpu)d!?VUrzWd7c+|Fogbsccsv9u2Oz|7JYEHIFXPoJ`!Y_K;DyGt|n-w#-!?m{d z?>1R)_waoftQG3JjNa_GxPhvgV2lp+CA5D5)9`w9r_ldy`T5R|_UC)5szc7L=cH_x zosG@A7CS6^h5FWz|&ZRk@ER0~4F zOP)vFUZNcP*6AEJ8{AB^!VH-zcB+*%wG4)?RUt>QKK+X5pW%$yZ~YwzQT6|(3SXX} zHobzzb0C9Jn)XdY(PU^}j)T5Z{8D68Y*uC6`QTsi(dV~4QAT5FlPT#!BG4Z?OBayD z#d`l4#Eh$=jmUIp)Xla&pT?IRx*qD=?j=Jzn~OY*v40y2kW7#E9HN#%5;D3NB#`)!0^mmNb2ppsv9{t*o{e+WD~;)51%7u>F(0}< zN4*|v*J&-YDsH4iAP<55D?h=0-~uTz)q$ zqbX)jC9VDbE$=&o{q1g;2#ZatFA*!kIsHaI0-O(K_D>Dn=F4W~-fN+s-&+oPkh)id zvkR*t83fJcj9u`t9}Crp=UBc%nV6~fJ*%&*N)yY$?fFxGcTJ@8Mlt)J;=6bWSBl4; zIZeG^jn-z*``}&SgE=*AdEnXOINP0cc zjK1XDiZ@z#H}>as_B&%vijirh@a|9A*(#R(i<)i6C+}@F<`eOBWK4c^g5uTTm>yb! zYzeK&cYJR{{aCDKWA_RjzqK@DoY-<}HwUqrb^l4c@wpO9MNs*vT6Nq`S1ctz?GC7+ zBo1r0cpV5}&NOWRj3R^cxcU`LY<9po(XVqW!o#C{{~{Knnd9fM0t|_HEp!4*?zKeR zhtCXUBtyJkar5nqM7_OmZ4^#=*b3R>rD*wTuYV}j-=~QWgK^cberB@9x^5i#I%D<5 zT9IDciyVptdm+RVwPo!r)jdUB`-ReyCN+9d2(-y#cy9W4(4NT9PIJfM1X{i8f$>#Kk>T= zaHm_>d>Q^;LZ)--)~=_c^pHr;DPs%^WT6+XwsNTKlB#y`pMpaYwbm6B=iFZSeuD0fvL{dHb zng6rjG2;HV2-MejJD#`N1C5TAO7LN*1)4T_*Lg9+3jFJktNP5y>==AoLSC|*G{}0R z`^9DZdC5|(T}jVc>j{)^K4*&a@2o*nIB28yDy9G$UbD9^VX7zYU&xBz>p@7QVgBM3 zXVyTls_3Ih#K-oH;QNY&cMf!{7$A#M^pEK;(JDRkw)*t0xY#|Tx)!B$dtPDaDL4K8 zQrpQ9QLT@?v(`an4~C4{nk8pi2XE^&>$6s&njP0brKnx}ZXj-4T2{nw_Y^D?=1|{#3gIB(bqAoCJRWz{LCr zTu|>DN_EK>yY21kurf7OfpS-0-UueD)@XZYBJ4-+?=Uh&U3s%AxP$Wrfs4l%C<&U_tUt5)qwV?Lo2B+rR5gt2^D@OXBx>WrS-EO`vOBRk zqc%Hc)7RcVEhw?r3KD$AqS(E26r-Pj-O`xwVuAi9>f~4YqU1FyE3d*RIHzM+ojl>8 zI}?s>>~VJz4wsB&Yf)cV?GQ6K_`_Z$8R(eebHeHKx7M5>&7gTAGpp<#`@-WPO*y7g zIpgDbK4mO!^J;A70nZg_fD^=10rPV@{l{O4OU3X`nSUu^vYDOu)W@Glo@2`bvcbku zLjb4^3Ju&RW=HO#?RKs;@#jyr6^F@$1pIkhZWhDFr1ni*lSUfzoW<<%o|$dAU>I*O zJFNXZ4pX$MU}}2~c`oRCx%Ir@=rvv6edK_S|B-F9+-Vb|V}74(*{uYv#3))hBY{&* z;^?q9S8pyXeMSnbu+v_>H%jAsRKA^;~6rAdcGJmV7 zCybw+ zPdE2A6P`jfa?>ZNGR7bdXDF~Wlkm){m8~isfx6@kIOXGAiH9kU$yH^#Soui1yH=E7 zf#2t;y9e@M;vpb9VS;NXxF#f5W0m?3rYKxCrx(Mz@lJC|i!}K@Y4Z7l_D+>9(uFk= zG#M`ZXn{)jS-p^~8A$j5PEjXnQ6~=l-LSZ(O(fZkp>ApjRtJadgwD~fAt)L1^P+;)po(p}Bkwx|IT-C^MvxKRx{)m7V?ME72|8=p$C;VuXsBq4KCZOa4 z@)dEYuH?&EJ#`y_axAkV>D^Iye#E(X0N>rD8pB$_4mDky{qUA>|N9--EP(r1QR_dM z6UR?Tr!PYG z%6v_ZPqy|QgHol?00YAMSvR1eJ-g@h$_vooMS3Jq+t=V!H23`gjW&FWFvo0@9ZG+H zDNX!~j?Td}HJ=8&o}`fsbd@+>L2A1daEriEisO9iGEvZ}KTSFDklepHiSp1-zg?`I zD31I`{`OUZF+g}z;-7Swto$IQQ%+O>QIYe&?_-+{-&_wzIZ;7`EeGsF3xcmmrwl+qC8~A8afEi!uorb8KlHZ&;M~4Y>%A6?`MHOJvjckt-ZMJ=qHHL6 z5Wa8bY!3=ePq+8u&j1Tr130snBK7ctHw=yU!Q6g-G)k2cL&7`S4-JCQYe&5(1grsH zq!(TRhd+eAq+9eo;v~sMoe}Z_+hq-s6nQUWeyyz?>?*;Gy$=X1!Wg6h1I3anVGia8 zyhPmvfXw-ik>{pOpyyQr69&Lh_ycIZnJ3;)E79`tjbX6opf{^_53!(MOL3)(M?UGt z()<7`EO|i3i+nh37yk_)I~*qXjB3bVh4t2{9LEa0g7Mu6ej-dtip!eg!q z$#th2%92F^IJsETCLAqpNXl_wxRzCRZSpr>`h<<1)msh=_DXa9Evvp7f9A+aO~zfu zz%ZiT_9MKnXMOYOvzC{c*Agf`i3(HgWIHgn7f%RGw?085!vY$5t$x&8@xI6>Qkt=} zC==~DCHc)Oqj~Kk17qW0Cop}I-22=!i^?1SxWk0UN{s{vOR8=9(r~oD1Ms2x~Qj4cTT^xlt_ff0G{O&iwxgU))652h?k0{5W?5UB!=5Z$NI`9 zkXG3cG!f>oLmioY>s?!ux6GNi;(0pPTqEj6Y99c&*y`GkQ2EgI_#e@)O!GOqCC5d~ z(zF>ntz3}G2x%}eTVrOBW~W< zHR=0%`#g0;!Uq{=_YAdw<&+Q7KbwaZ*689R<*^lhJDF4G^2fxqC%o zBN@3cnCUM)4gb>t`LKN=@x!h){dCE|7`P0;n@0F6wp8rr=06sCCL(7OpItK`pKtZ{ zyjdwgfL11q_mn6e8&ovhCS!T}6U%*8Cvdn0{pP|LR#9j14wLj>yi<|803Ko}F)!%2 z8$v)w!kY?RQgey|TfY}l+iu8Zf!x>L@ZeRCvxzfWGnn|9<1N1}4CFrTuFq2&3T!(n zq%Np6dk7>=s~s|6DZ1}X8MglmzFNyS7|lZtMpr|JSz;1Cw6(BW8pScs(JKNw*(U?4 zMQu;=xCMrG%6@nllQT4)|C1d6cyA%)@p=MC9T5ZB0payIP7V` zddBH*cTo6lTd+rK4|<+KUOzy&H1%-Y=6 zpSQtfyqo2w2BhfzlEehif_9S^8I`T_GxCIjC`#m`x&N`n6pLWN5QeQMjAt&V)gA9t zJW#0t|5cFDi~%qhQ(oy&74T9dX+ohdVRR)6N)`YKl=J}&&oB@s0l!OrnhIr12l5}{ z$DRDP7Lr$rEd19$Oj}izM0pCgaES*v*D-0Iy_zSCM7?#Y|IPJ5Aq5*S)A~}|ge$8N4nnntFq12L{qmFTfM7u! zV2oJ|^hix=VT8m)!LF>bG8z+pNE@)|sg+q4er$d-0_XuKarAbsjOLCE zUPWx$BS1$-9W$~5u@ftsjQYeqlmi=;$$cQc>k=nGHw~GQcDOGdZTz^v1ZnNJ4Tbu1 z+PFnx?N24%i8*Ef>_UjoZJM+Qee= zKO&c?xg!!s73<~g?Zy3+hf(Mgct`)&hV5^>o2CXs;2~AyLzuvwr9M|1NfG#|2aTZt z_PuTk*OSX-{Aa6w2!m3nAsFUc_FXwB>(m9jCV(PA-dRHkJ(cFllPU(519FPYB9hns zU0R5ys|Hjm8o)oR2jXSyR%oSFkAXebrBT!3y=>*38Eq5>|2-i?YT_Y9NGt%XV5#Wk z9YP~|S}0|WuYr8~mjO%h$jk}X3wqyBBn%<*H7VNUtgz84#i7ntI{`$m`0JHNlDs{9 z$}eJl10YJ#uYe$tkWA@R;r5T+y`?5l=_Py?3D9Kuqpczk{g1ei#ybq-MD7b|$Wyx3xom&!FUs);2j2$JozZ_K5s$kE-XtxipDsWXR)H^S zwNeQ3`sDRX+J42$7tY31y_zy3&EMZXMz)GOs|*dzyZM&NTqTUwuC^0(my3@$EtMuu zq?H75UXMsm!!7_s%lIS-ijVTQo0~ksbG%yJS{EEB??WYt9yRiXenA{b+nm6`GA zS<}k^U+>>IK-ZK{IKI#IcM%uhphcJcUIq3VPMJ>x1xMn-pc-Hgk-CYkh|PE}Z)3@l z1dwp!g8zbtU^TS$9;NV}n5ugU8+YKf;`HQjb|R+Q=xtcF~c z6k@D}kISd!01a#U6FzDLVdiEJlcELYdS!XYP%R*Tkv4R*d-G=^ zW~ccr>>{Ssb_T`@Vs0Kw`jQ-?h1KNjg{}CMkxsh{l$VQMH*|lxZvb#=4TPLum-EXr zDhg4nPvRZarwyyWf`pJ^kT>ua)M=iAH6L7s$P_cBdj*f5YVDXs7^Ee5U!VcV*3PTJK~R>!88w|#LGKTUmi^OwfmZb zK%eIeBYfR)CU?!Q|9_idVhU@a+bghbX~vsmnV%rt+N=fR0S3%qj*K*cQZ4PR=|K z>k6jl%r|)qRKR`=UU@By2V7SpMqn%3tJntGw|Lx9VE?6uK7oxL_$rqU=X0^08YnqpsCh?K*T9|J3i zO<+dMXMMr^Hwg`sR+#)>mOh8VWmGL_147xxk{Wnw_p12=@ft;{?@0dr- zK76PI0;mvxM}d@;)Wj2V@W$kZ0;0;JwoI?M*S^?O(_!(V)ECW9{%V$H6J2Vx@8wfM z#}nQ!sD1->S|pRd%iK1)OrK#)jm4!#+w$j&T%@ zVO*}3-x${VwGbn5r~H}pBS2dbKLX#}jO-k4iE2k$%`lj@)Q^}X^3<;NpHB&2T%f!k zYvB>RwmEorLewv-Syt@6GFy*T zpXmIN9$5?%S4Xh)QKhQQ^{WAdfW2}J8@yVLt7UkV!)R&fc*TF{EGt*Oy*I_$Vrk?f z@9pUhusf4-VTdcFTvHw{Za)-Ykc5>{P^5oyR<9!-kA@LTwY7fPdUHK1@gx#k>B+AsAbe(qaXQ29gn6zp7V$&?C`0wS&@9o)z z4?|^)MAFk-=i{UAxSwMqGV05>_Z^?dyFLRa!> zkP?L3?uOn6#TK13AxUmvQWJk5k{3UXI+;KFQsS|^;r^|;RjKmQYL<1S_&YsUqZ7UL zo4{B(|E_YMJXOv}O+LILu`9Q3P<8>eV1a#Vz99%}iy)di3KLg)khC*bor_4lT}VRn zul0|&Nl2;AFDR$IEGTTA?OxTPd~?=#so=fe5jIu*0AY62r+6RLd8O&2tGixM%rAUA z0JCJQh_&T0L#cN+ z;f~gao2Ir!blO0_PW)o)TS^hQ?(kR;6I_aUT1C)4zWEdkX-cW<5}VZ*d?PX!awW3l zJES6UlwoW>Ce)e$XEv&~9Io}z(8Ry`JrJuA2)fN!4RaGcwlG5QMjY^+0}2jiT4$52 zj;NPF8%-3&1PDkfm8m}j*geDxbIrBH_kBvK1w=-QlwA9&?5zEItzL5`=pRwsw<$^l%BiXwnw9?s{@MY${gT_Ht6b=(uD9aaCU=(Vq7x zhT+PbFnShjBn!LuI*TkI(hhp@himGgyE=P9J`r#hop%yHcFs}F?4BC>ytxn-8~P;i z*rSPhG3u<%Ldkt6^Q)i$xduLh+?JD@9GAcamErKzB}IznYht-w#UtXA66ZyJVq{KZ z>!N<8FTm>`R6qmnjI2MBq*Lyi|AA`OO~S#u5|RWZUa+nq`+ha1TTY_-r4+`&kxRR{vmy|`rWhm*!25;~8otNo11 zGU7)7A@k(JlC>flNT-UTW4yopRu^b&0;jFVkH@I6`EC*3rp9&%{76QmoamcFp2@( zeZU)=+91IGWgS$fbCkfpb30D5bN6MpoUbqIW-luJl4r2R^L-bB*iN9(dR%0Tj!M>W zwR@*xYVXQ}y7=_^`tFGN?%)|Gr=%_UaYL#B8P(&-I)+SBaIZ>y3t1RLngVZzS|QS& ztaBULDrCAI=ICR_N1HRh{b#hbsildc8)6Q}S0zSX{EpSNb5fmgSTe79H)LM>%~9o| zdJef!+{M<_y)MTi?)ZniC@Lgg@+53kcBZBa z623;cbeUIau{8TJVQ+%>EhwAAf>)E0HQVOvs563g@D^Sq;Z9#j;k$WTVSD0N73mr| zS}v%!ZXrrc zhm7kE9DuR}ti&TwnPs`V-`^otCoW>hd`GAaH?;eJ0usDdZ5<}+aCIK>nYSnQrN|^& zxVP~K&Z)^-&!OfdXtaX4A;+eguVU5+T<-F;H7~ydE!Qc-LXKghL%*Yf-eX z-{|`*@pq&3M6c;4)tODdthm&#QMtW0o%0@=QUAcao%jfaOWobeMpfT*uZu0Vyl6Pn zR4aj>RvAK58?c<%rNt_}@9jJe)&Ycv`?RJ@FyU7vrzJEC(DFaxEvxJ;-vs7e^Mpg= zW2HEPfn?|pGaBZgd9CO%Q}VBX+bFv?capqDV~gsWk=tdD5(^V-aY6Q0Rc?%x+mDkD zm+DsG_`Ox75i-XlP@D&O4ld~Q0lWq@Y@LBVkdU;~OT6C=7Y{X42pkU=l#rMHfbKzc za^skfPPupBG~wcj$RDO_sct9i@Imsc62HajMlyb%%$mXkcGseo(g`M&EKAeJq2jCW zaS(1m+GDE67Me-g_G?xh=0efQA72Ho4n4;|P1l4tVIz3O#T-*^dd2yQb`ESTgB$JE zPKpSLwE>~gVFEc)K>^$n!AoU@A?GQ9^0hUg&AUw|k0yBnbT_}!JXeGeQ-=jt%ZQ;S zndE9Ty6(hw?R;_fN=0vrdJM-mqbC3e2TtRKgb&SNQS%6q!{qxM*UjNcOWQ<@uC0%2oE@h^OCOS!m2jTCcb;+eIaasyIpX zY?&mJqfu3o-)_N4yXpvn69UagHAt_;?0z|FMNReP$EHL+_ zxTMMkJ6@Z6i+!BUu8h}JM2lIOai(f zSf+>l{P+p*B5)KVi#oTn>ntt~gZHL1d_Or5Tzn|INB zTA4FCfI<5G9D$89>v`2{LpGe>_`utN)8ASw7FHcA09|jAwwI@x-gjG8egT_b{+qp1baH zFw(S4I_9GbfAe--m+t&RB_5<#^R}a*BHqKlk!BID} z!XI{~PFI+h1S6?$4;5ed8tYle1+>n4Y}7A+XZ^iN&l|S=y5!R=S^REsXANy zW1s~Cl=qgZ*-bcOZ;`h+EF2Ue>nxW3FXkeqFAYOj_r31cZl>>E&N&|DO&t!p53L-Z zJ*R_eUEPgE%Dul~&)skMtdQ&YCnWK3EOBVHgJOy%)SCD2B00+YOMW2B9p_TZ%pjX25+PlB)muD8bWZ*G^(%kHlrqW_3M*(7jAiMA-=szc1i+ zFO=iGzE&;EX4J;HL?0=z$`j=Mmp6ZSr6l8tg|23+o?*4Ub6C0v)!(^>J-=3=O}Whr z=evuAemKZFA=Lwy7tzh$)P2|{-+9JXNIsu$=K|#ILt?*GKY^Q+z>Pa@SpuAm)bgt4 zOF;j`@6MfNt;ojj&f{n$`vrJIzLlHNZTH_;ITfeNhDwJsSdwa{M<;&|uW}3WaFGUg znAa+5gvSd^pPa3ix`SnZk;z6njpOANspEG15h5*)9P0-G)(?l9YxD>+G**OOzNJ&A z+S*6az_sRrDn_D$oIA5jIJz|*b>avA)S<(@o0A4``BW57iFygm@drmEQ*J7Uu|d0(LXq#f_WiY#@H z7xVMA=Ga8XLxUa;_l4F!%>^)WWX0}niw7|T=uGBpXqb!uIEBK@P*t|D?`PJ&dCx!~ zHcc(cUof!!NB$ElFh+G-Wfn9ob>QHb*Xq(oHD#N>@ZeU&K<_rQYP!}=^*vP7ewh8L znZ)bKWTu5FrGvHF{gG4R@vFKS6m?smNW)twUR9)+Vvl@@#X2H0>|x|Iak*Hd4BT!_(T$Z%mY%{iCj>w$ zhBd_Q`!=S5ME^SvQ6QvCO~Ge(7Z08_IpP9TWU5&tRnFM1BdR`hj_nw9vw%YcUvA*< zL|ijA-V%~zIsU-^NsEBMhy`ejz!j3x-tHlxcz=fXTrQLhYHsPvTa@jya zWVFX^EtJ0011_UCJ>eqMn0z7v3}a#fySUj-{#*53RX+K&dZAE&Bw}s*fKMX0=Y^Bi z<1BNg=&-W8z!y+iI;iY3>;s8PBgfwhrg{$!Pn%LP>!NdFEq`Q5|HumMGFbWXjt0Lc zI^uUN{*(=G#(QT>h`<*D4lB26yu zCaK1Ya@BX?yWyg~k-Esk#(I}-??CASF z9bT>9zO28r5OMgRtl>xB@r!8A0e3rhP3x?t8>*>FbP$dpOGEsCt{qI3!_$m#YEW*u zyWX;Y3A4{r9bB?8@hvKJ@d9l^obrd^O~xFsgWDRQ2>$%^=rbI8}x|UXDIq z^Ey5v?{mcSpudei#M-Xm*shdn7Mq0<#)|Anb6!y8$rJuAoKy+=d2n7aHEL;l*?WSq zP@(TyV%D&Pt#iL%8kr4{FgwbZqlr0pE=xTHrSfU{&Bd{>J)v3=aX7|WotqgU@1XqJ zrB`fHLxW$zFXyl{ua^;}u3_nm-*j;H&FI0e^*ELg!m0Hkv%6_t%nwm0)49$W6U`&t zyDVxNer^VXXJSgn9o$vAFBv(dsDWRqpr)7Vthm`Ln)Nh_L9*8goq~bl;OoH=A!$=M zsivVQ#xvdhg|3|r)Yh(_<>k6=C=?WEJT;DOqIh8m#U_^7BU1G8lRwhF48BrZ5b;zl z@-pLB3;A;No=Rv;PfzbgX1hT6X7c^eO5QD|pvZ{|g!(rvrgRPS%>U8!4ZwMI-`BBi z+qN1fjqS9tZQC{*J85h?X=5}_W7~GV+xPeVXYOS3%p}jfM|-ce_B!V>qeYUTMUfC> ze&9yFPu!mAR?y+a{*t%k z%>q3`x912^s#A~&fi7Qa)SYT?qssxawzn8q!Qbg!T9(lH(Pc9HD*$}gR|7%+$1jIN zo}i71Z=RCm{BOA=Z@C6cVcloA6-_J)Cx#U>lNqes1zwnwDakuZGe7w41<)d?IM~KC zY!Z*p7Ah@r=?6w;tsmn=c-$q!Uq< zzTa=g&3*iG2qs#tyEqC;(i{cHSd6MN8@`BMql$-Do!xh?Wl5c@<@c*)AK-FWeY;Xv zfB5(D9d~*volmy}YK0AR-V4$N)5=25%3?gFK97o$!?Zrni=FX;qQt(3In`*dTEB-% zA||v7UYhzhL(Twnh)GnCs|ucqj*eB;qG*X`lu)i|e#2yvRSVZFhI_2b~-9XUn>$zD7O0SxfVyG|9VTF-GAm_w=vY%s6e7c7^`d`4A~?=^9d zM^IkziTfp8h94Y1bUM2hy*A7j_kXV!1v3a?R3*=nkd*u`S*WBPZbd;bV?@xNAc5xZ z*Sg32RHIBQ4-iDl?h*Q4KuHimf)Np5nz1~fkzsN`O>Jt9)hSaPxoR9QzkXn5U#40S zo3JlclO$@aGGKf=Y>REY_;_>9ue}CQo0~f^-)eV zO2@OClvNAc(Cqyt`(E^pVXHW4-s}#NbRR=rl&zT~nlhR4tCVrG@BJ7XYq%&3>3CW> zmm?Z4G4JV&g`fFipjMrNRDHfd4o0^NU4P27s$L9=sFYh0)TvQ%e_d#vcDmA692Ie3 zu6$JlNml9M0~C7YIt3U~BNh$n8d+2bl1!saqYAV+0}Hbp2791CcBo-!Ay4s=(VQ@w zBdJ1ASaB+nn8~JHStc78`X&VY44oEL?_(!BY3y?%{teojijJ**+8!DsX-LA_kJ=qo z@sRAB_Hq`VIP(kDM1{KCsSY|(Hj#clT0h#Fma+r(5EL?^VC`g=P8>@**m4$6q-E#z z)YJqUU>_jsX55>Kk8s07oOxfSp-Q zDut-x6=-rohVftPhtC+_v<`3XDa*qpeNq(LVZcZ;{&h>UixX(`zSaNwE?00AbHb;% zfA5P{Z`a~$(2sdO8}uYvxW&<=0#+J00SHkT?b)Qss^~;TKTo`vgv0E67qdlJRQzsy zX=T~No5Gc1G-sL+l!i0QakS~ioxLjkT&UzrgB)!iYSEhzpf}cL+q!-(&Q0) z*5!t0+#3OYCvls3#dHQ-xXD@jh`pvZ$Lk}?lr6`LNzbbf^dnn_U$Rh zn8&{c#Li;hONW}>tC{Z-B49X5e>IB)kpvuuhWx#r!uFXGUNiRgb4FP0br9gP z*H_J-L1nd&ADwRGCx#q9U@E3@;lyG2Hwl38(nh1ObjhOU6u3m#j{JOwBQU-wj(-L2 z^!nb7V?jJf{qY{hA0Nk{Nv$s!zv+952~tl4y?XH%Ebx%k$q@~EgD9Y6DQvjs8jbJa zehYoGx3RB?vb~Ut3VE?enMenTJW6!BiXFHandrZp6-SjkYY5)ReaU^90!{-HOT}8+ zXeJV|hry258_;UywA*z5a3Xqe*>!;TqJQ)K5Smnzl53u{#I%S0M|(eu#9{nU_1Yg;Bq%@ZVq<;DEG0O1?_Rk(@Qhx|G%J5#lD*;-8!|uG*C~Ltw zB#=KXuVhF9p5G=&llYY=X_M*Q@DOhJfwpD%h+;LkkP(QcMFcBm2U)LyD@<+gZm!lJ zeaPci)}QCfJFa%bAx3fXH^}Mod%+$eKY)VReV$<*O|GQmy(WG-e;LC!!XC|i-pHzp zZ4<{OAB{ILbqXbzuA(mO2l_3I8CbZpbs-8>bCwL3?U#Hh3yj&26fj^zUR6Lg-EDa! zC^N{}TRiRM2P5IC^cpX#k$Lu1h{hk*KZ=lD@;d+Q$+^8wd{h339_%VOe7Gg5X*~Lx z4lar%3@fE0<}_%sqd9%`=& zMp7P_-q$19OHw7=&Q${(%_>IK^Vdd^LR{{cJx>763yia?{l#NtaDH zncCF9fhgEOAwHNWicD(Vf{NYcCWN5=jl;G=+QX;KnV|RM#u?^$kd|3(&B%Z8vJI@dL6A;V+^FhrjR<1jBO@bjR3hlMw>6y8ASnqISOFSXlv=i59P zghwWnI$eG@r_o~mie~8-bl={1{H$l6dD?LuViF8Iw@ESj2p0LPMOC)${Yg~F@PnWi z*sgC&Q(XE90zWA{?rAOr3Va$}R*dxHwkPPckH7s&V2su!AbeB1?!#SE#89rJwrdpi;t6L*dy%kV`;MmQJjunk0D(~<@FwyKa4 zga4)N`B`3Aud<+Z6-=a|n~T(Mo@);+ntbehMClZ@DG4rL4?Oi1@cGQ8zkId+)`w2_ zeAm2Bqo2HX&3pQlzfmD#bJb?266M|;x7<76J=7Pxob#GSm&Z) zO>evM6uP}gD|b~hW@eK)^1g={@b$PDPa*e`jQyJ{nPiEDcUuR#P}tM{lw-Gf()YZ2 zS&9>+kqTt7zU~LbjhhAD3J9jzlZ8q^u4}+X9QMMYpkIOp0g}Sl$y1`zv8K~EyQGqc z2vk{Y7c+qd4lnnFl0YC4tCa@3P*3|~f*~C9FL54YT5EZds{ZmLJY+&Rx5giR@}L`h z;~feqrt9cKFz!SDb@hB*6{kNEMNGjyx`A%=CNxx1)%YwA5fP$_yvQk>6(L6YFTeA< z*}YwBX7hgvt6EtpK&~ z^kaI*|L_WcmJCW&^m+(mZ}caBUlXLEAun3rgMd61WB;&y4o||!&&#CsQnkNx87I>P%_$?8C*0ZO$u z-9=v6{x_wPG||+~PG(_?7k+Vbp5IvrZ>k21v$~o_Ac%xQK5~b`2m3cKxF%t%S~C0oWg^p^*fV(GZ3>-t_=+jPq%Z)G6ui;8K0Y&5E11#6wMUiB^6j?a zLT#o2dV3bLd!HnKJxLPFOK6Qf4jk55n*($t6-`|g9RQ0*Tzo8s3!=x~#P98BF^mhB zD2o;;i}=Jw7Dan1D^8TI8xr_=rjLo|cw3YfEZDe~*)-Q88=P(~4VGPbLbnKoT_ zDeKHKtFW{EsxeLdoX!9Vg;I_;v9^22s)Y=xk_QlzGjvCq5D=gNWDT$1iM@c+C6VSS z0k?;f0A6OLva)41>^!J2Q{MS}hNbR}(M(%C9*DzJ`P|mQF-|*TI$4FwkxaC&LKLSNp6r-1kS%E z?(|P~B24|fG6x-~{!0*Uh>Ii7cXW5+nPoJxr}ETQPcT-0F>0e%vz5XJ`K_j041hM- ze&UGx zOV6rL2oeUxkRZcLqvk}D>O_-rbEc|UI`#ZqQ%?j}n%Kycuiur3f+og(N^w|EUKsO6GqrtAHx39Ro1{V>pedkf&_3yhe_OPD#6K> zTlzP`*M8o>1T3c}R%ANO&#UKGi6Xa)X4d)B;DpU>2b%th3fw@AL6YyYb0*zFF2(6$ zSFKI$#lQi9DPwJ%QaYAMf=+=Fb2|AqGs$6COkR9Sz6D0ffKynefF?^(Z1KR7sYc65 zc2X5^r`tg*>V`taR;vS4LH4!X-On-$hMa7Cea(Onq*?tYd6KmbqqSHsQQZ zipaHRAzad}2Zp32>}N*{n2IGs$e&|Q;DUJycHk3$ff9|-Tl^!B5+FoT&y!x)Dd+yn~ff?gB}u)c;EN0Fvpzn z8T~RiRUL6b_bM~4cW8d>m_HjCqAONzGK77ZLRFBQ)p7AME0mWk!pe1^OX!|EpaQYC z9w800TntSd2*7=daEKfG_wT0WdGi#4x0AZkegtXXwULM;06f;|z&gu)eNytdsWZtb z8{*)kp>6Hfg*L^>L!h1xQ|yodm$)n;8;d*FV++%Tm{{i26E7Jq?_=imkCS}26gfFW z+jUXaOpCYMOyd%2-`wVE@=Sdu*Me}(*xO^y-ZT)y{Ozxc5Zi;Qpp zAa&|Wd7J)NP!d=gg<}xXX`Eh%QyV%~#JUF4JNL7S$R&h7^*x8b`8)->ivS`H2c4*q zhK_DHrsh(?W6s~lBcD_etj3p@Asxi%h!MWMU!QM-jWEz;z(^isI*sffr-z#2Kt+|q zvCetqN^%U41Hv2z9613TIYtx-2AtRyq&Og&B`&|xuX=qPUwN~6E25K80ner2<9l24 zy+~QG`h*!O8B0Rdj?Y%6gn*|UBGCZ@DH^&47TE=}WMPc|NcMg9)YaX-PoB6Jt=XC$ zdov?}p#AhC>*|X7BjMh0QK?SvZs_&=sdoAUmMBXI>!)V=Z^naZtnmf|Q-)$TCQ@}O zyx0LYD=~)(x@fhuUM0$eAx|6xu~Y*oJwTpt0t^p%(ysbxrb>2PqSaI9Hgz&30prJz zAPCvGUr%i-oxFVNbw9T?p0Xnw`2eO4)d;nZC^27KE57c57%=UZ%+U=J>| zf8Kg#N4ozDRtg{Q9v$x9Nx2XmE=K0EVu?arTBTLNyr~{ul8IfDHCk`mrKN{WPX-XQ zCb~-fItrEjsFa}V430N+;9rusePzM|h5PwN;a`4HYrifY=*sugd>*SkE?KDaBF_hE zQDz()ywCovPl8`u+E|Wh=H|lo!U4J6*f z=+roHm0MfDbcoDVN2_Gdi=Orm^FlWXAqLriMHVfqKMGy8&hDI*TK7{wx!0-5auV48 zAdy7v!bPDCe=`t#d{#SSrkCYON|y?TFer=yahHRKy3YxteNpU$3mQarE|3#Bn*>}Ikk5X)3K?2q&(a(^;zzR%EOz)-vB>4Ge1 zQG1FJ%1LjG?9CSIUOG)0#7rLK_(xL-z>rel)(*z!`5ue{6OAgpu%)MgB#;kexr#}g zOc|v9YYF|gTcMd9=Q4HU9IW)Dt^xoUaT`rmK!X)%ecsZlltUnl8ysar3YO*JL7|F{ z5Da2dU7a$&<08pcokV@B6fgO!R(A~nM%qTuh-rYgwC}Eb+~k5s#)@F!NIQxTtaFXh zOwy93+Hd3ve+ZC5S$T7Pl8TJ|iEFy8yJ>1=VMt33_QnsdJMFvKoHw;fv)b{rX_B#C zCu~zS0z1^zA#o-7o=*_egGfEa|E&ffbjP8WnKo&Zg^_?PM*a$TXYNGGce91G($I{! zSqVW=q04>U)+6xP!3bCuorOcqoGTgUI~M05h>VQ{UDQ!+yGQm@sSH|)s!Rmoj{ zEPlTW|2bZuspX)uasTf7^xUmzs)n2yLB9oC_?Sa_XELJ`j?%#MuN&82=jHOJ9MOP0 zjKQyj3=!mb%zSTcmCyPUo{PvIkNs50YPDSvNMUH~WmRn8SA*9@dw7>Fcs)bfouR_V_P_omb3KaNG zTv&n!z&~wpRsN3d-xXHrFh6MJYtSp}evhkHribuzo3R8AVrLfmWDtA~{^GchWBrvF zI%C1}Coyy$PrHR{E}(%NEiZuwYX^V-IIyzmG)FIX;`@r4R6eMfGc>p9>wM6&ksnBl ze7LP97qf~2J6zGr!VuiN)UwxI`l55ZS1FcnCPmSEl$@{^x_TVCk&nt~|1a^zt#rs} z4OPK}lUb)!BmAEiz%l;);yd#o-`Owki&e50p0vh%<`{Hlx2-ExbCwm(j74WcEacqJ z`1j@85m=kgHx1x{lBJ3$Of2C%f}RG2E+t;G)&oat&g?0*K! zd|#ew`EVKIVnbkLqV%Nn+Y?V~sy-le2Vc${>j9ec zIuk$b)I2a{7>``q9+pKW{&m@N&j!eofx`!~%aQ}q`Sf%7T|+QohA4N`y>|pRp^g{A z#4C?3MITXgNAx$VB%ui^^RH7Ve%FuFTPm+O0a|`u>bATobl0k=s;Eek2Dr^e9jDDT za>44FSb_qMPRmG##a~K%aSd#|CkZTS-y~F(5-P&H&C3}_*xJLd{kyME%(Q%=kigr)Is`3jpV;q$u9$Yc_VDw77n6{pc)9Co7aaK4$c$IIW-v@M@@25Y zGR0j_Q1aki_62x zRg76xkl+Ms<`6(2RJll z5f?%8Yi1BEbIq3mwbHw;S?LQCpQserN-*-?wsYEc7Sp=o%Tow_TL^34mR22Nz0rtN zfA0VMZW-2IPf$hiuH7x0LyptM1Lc<+_TALg%jYEAy|AF!VSyZmw#k7cCf&~q2HMZv z$t+8URLnG$miaVzcDt_Au(iYNLUS>z?jRErl0dnuHP@fIhDM|V2MYP~X=2Ikz(Fcx z0;qeKm>v#=pP{H!!m!JiX!PmmQY|K6VZ`15P?RizLh&Vzc~cl(QT0zx89wvp9ix_) zEo^*w6Wn}lxrC|j!)LFEKTNkd6mZ_sXUXxGo+d;Sii7PZ&8x^{!gX`QhXlg;hX(F!_zPO7mv z$n@b4{9D)s)QZ&nrSl^*Z8aJxB)&Bk6j2{>hB0?>)V?}3Nju>Bosg>^y|IOH!3m;X z`{vhXuWoeXhBgKwsS-#2r;aj zGDL{<=9<GaT^e3>QXQ#%PIriM@7F8y0~l1b1J8!fgIK`Y(VHb zkrK*4lW9oJ`!BaHFL7UXaN_Mhn)!f0{j9EKh(g9M8wBrMfE0-k8kfZ+xOkdHY& zf}C0B0_ptS{4}Mi1q+`ejo7RJ8>@_1o*lf2F5ZV9GJPICe(===*p|wLv(--A>VF=r zO&r1C{rfdbAxdwtpAV#6Co1!0Ob3yczCZ~F9qWr||1a4x#A@}YWq0?!wXP;veyA?e zu9w?E#|6sL)FqsHBV%9Xd2uL^+VOaJYOifqD(2RQj~wpp^+&y|LuJ$?{8xW>wAzm| zl%Sr!w~3;(VxA@SBnwTm+isq$ynGiM4$sf2ll-S;f#) zvI%Rpcm#9C4?+)zz#G4#-|CIZ!VbQfEM>g*WuMFV%IXyfb zs&GLdzz~#?J6_X*{HqpqJvz%azKmRB^Ih%4Blj6cBm1d;gVeCI3+d^4!di#x3h0R= zMR?Ro(tGLI(PeykdUO*mC>YW#=Ez=O3dSa0iH{8YYoJMHXh^ptR>WA-aXk9u3mP+d ztVFl1Wz%CysMfK@$$sk%e#E~BA5UH{cB7LWBla~1RXrB-4-VUBYZgKM?(B2o#7P}^ zg4D9RGND@%B7pOTE;gV3XhMQ^s{C!W6k~k(f>s#;QV&JcjCfF30Z(uR&B|&gNikZY zbPILxhD(&@s6w7u+gRW%|9*68IDA^)Ru>JkN7u&rD(1{ z$JHu_eT91Q4=_V%v1O#_hB~OPCwc*!Wn@9yIbSf8L~@HoM*=ph zce>Y;@n)sdR0ryEYkC-9gJ_V3CVw;-FD-PA9-+>eb3S4^ZR|zhGCZv2 z8*$;S<>p-M=f?m$P7e!eYj?v&$o+TC0gFX!Ux0JSG!vxWn_ltx!cr2wUy#HJEP(>@ zD`zD=9EYfA((SU3$A{$W^I&Udk>qjhB!y#%`}y)Mozzaw$ei>fC{PS zuZGu!Y{Uo#dLTQ;tgASOlheV_?(aCNz%J^nBdq=aFAz3T@Zdm$1Q#w~$aR4XGt5_^ z%~zpEK?p6Ke6 z;If;V6p*vk*cM_U1IO|5hJHzUH1+;PY+VSy=f+lC5^wN%UKct0_`T}!mc?wmHE}fI z20L8XH4xAM2TqYR`$QNcPKHC68v$=%t(l?R%g3Fn54J%v)#ZvhCG9>tyx>I%3(_ucSMWtA!@E_r7sif zxCqm))4T1vz^T1F(_5p5bF1D@-9p4D2JMp1ayht@m$CCNARLS>`W$FqC!^p-QrSip z*t8^*$}XOLMJ!x@yZ=oG1rHU4s#XZ4nWk2Y$>`ea3*5{JA%U@ID}+lTL+}XhK>kQ@ibOH71RaR|RK%DRODSZ=_=4_W#6aa&s?jn#IeY%BZrw%{dc zP==0!bEIpBDw^P$Hrn{iExC@9Xp5Cj)qmhLl-xE^W$IFA(DXzErKH%ZuV#U)0XRGi zjxYrea-I)!pxboCKYU3iN{c*4Qst{Vru>NfKvGd z77gsr1r(JPq_+zSqQB2zD|+2HOX{lSNrO7NKo9Do!Al)xBF!I8d5ovB*>rf?D%4DU z9YD<%bRgXx=K@Jt!mBr2Jqb!rRJXz~(1wbO+&ktSg#pWqi@E%$ju<49RdN(ZD>kvM z>=`718IV-{t68o-mGa51j(aevj~q@0sM#ZXq4sGU#Bs^Om8xP?$z(VPfqBVz=t$y! zTy0RoKV6`}0#-OvA!Qk1p~*ci!o9E09v?|q4wE|7rVP(r>6Kb5D)xdxTv!mmRZ~IP zLNOj3HFZ3P6NF-nr(K21Rr4KOD?O0vIlB=Z@{LyHZV1r|B?SSn~<4e`i!o- z_QDwp(fp}Vie-;oJQ}rjY3489ZS>o+T!SnYJ%vb&`1ny&>QeeOp}uRlkCZm39GwcY z8eZnGDr$D+E2vVgiREfz1%-AGNhLE?!KQtg9MJq*$fDI681K*-a#X@jApo1W;rxTvl1am-0>3uo_UK{fuyym9b3|hUaA!1CEd-BC-4sU@) z?Zg1zm4X58lz$!>aKTl#Rx|uL8d2GChrV7UR@k1&Vcxm3)`gjboh@OrGrP8^=jO^( zv|+XMIf186HkgyQcGi&XZuMoc^cw9r)yS_Xf1=|%rgy zCD50bmR@bYF7Sh~(6JE$6eY$lT)%Cza9Rp(wd`l#c@=^(Uu2|YDB?2@J$cUT+6ag! zDj7;Em3II6C~O~XWPDhL^Aan#rw zSkBLCF$Eo-`S`_EO_5Rn-WHdhH%EH_C*578>q4`-`0-ziAyTLIsntk^{#Bc|^eu;h z8pOfR?P5@hFX{Pt-~Nn-b_M15;Gii=c(1O>HK4PXjDXh-H@Kdr=B{bOZcpst{npII zIxL=s1fYw>7IjYVQf1TTVQ8X4K^^$<58;7L5dc@ z0JN?q(@ztmOusYYVmJ7wwmOn-C>3>W=jXLMpk4_fKbrWUd;@Zpb*(OFc^3zB=ONx8 z3rps`(rHRv9N*5(vTm|o*X^ulRa*zr%h=jmZ$ioNCDP8hP)@&n?*m!f$1{V?U`^X_ z2^$kuu_hjAI<`LTiVNhMh?WG^?&5*eJ^|Ny6PG!lv}FUE411~lM1X_DVC7FwogVOv zwu{l8*8HB<0@d;c2yp$+3K~)VgS%tM5M!g_~94~fg-vcbb_g83Zo}J|9G3pULkozaAC|j)ug7rUlojqO^ z&mC7w*pRGiZ6)NR+l$Bg_oHPhftLhAuWI=adH=y4N;F?l*09fyE2Eg&qx_=xF)ik? z6Q~J@8O&fCPkbM2?HHz*P$`-?B+Sm+NfRM^jQ#$9Q7&8%jG~R|q-X-%K`_FH(xU8+>Ne@JBPDESyW*YK;dtFO7i& z^eY2ANBTDMTm##LjaKbsmY($|5LVR2fXJ~`*S>u_l$XU!K-3pH2oA{CL)HGyoHbp> zGckp@l6khnw?Zq#n%smSE~a=@-ELp|WwS89=w?7=*=dIZ3vavgY;5Y1*aH&O9faHO zYz!lVJUW*I+G6R-Y|7m@-Ne$+QWIsM5Y*6OZ{xn@b^Cw}_Vb&~rith2 zoK~8MXEo}gvbWA)!oLoKOGO~iH>^^*l_~xSO|lC zYbsC*X>XeI#<$zo_B}D(@wpM7twsSaoC{jcuFlfFBbZ5QLj?^z2zFW6BKmLXxwrQB z!G-~8)+g{Bd@3|G(#%Lgu6T+7R`EEEFJtBXu{b|AVmJFKSZ8Z zy4gV1@uvDF?^i1(OKGJn;IOjV$gT|iJ-ilE2csm!CQFn8@Ib*#Mc*x$==M{-z!q!) z;7{6k8Km_%_fzA*E6AL)Z1h#+_IqwOXK^C!xdEZxt+SJbugIdOIj5Ab3ixrQnD_U>AnGJOwYOsWa}@C-j9G4LQlIcQ`sXWVw#PLAP!)%L zbx=VXVk4xhlv66lkZs7&N;zcZG78;+vo`L?|xw&Lx0@eXbfKgv)2%HuRk;e z$DL3lEiZKZE0B6-hkEAuG0OCKzjh@Wgz*T>#PJHvWBR7$I#)^x zGF=g#m$BnE0(;ouosM(-eoE<72F&wtrU6#L&zGup3bY5^|D^Lh(nK`LJRS<_*~#zB zwUNyM`Vn%Zq4g@^k%r!)>8e1D_7)15Xi=r-E-hMVp(?cDQ~7~vL!@~7=wpIZ=vQGV za3Lhy6uj2=-D}5V97JZS#eq}mPHg+vW(AVfwuBeC1^hAazl&H(^ z)6{ZK-p0(LTtOv0AW19mytnK(lNTaIr0@gjYRaZ^MK2WNw0c~f?P$Cy`hr?@B%gW7 z$Z0tZrS6s=xmO4#R-?VxsR=hMP>u-@pW_zNu^~bR6WhGDoMD4N;@6?aDe2F!sI}rk zMXR;g^o`)YR%?pV*Ody9Ydy&; zQZ#)b_(2RC-OMjwIo!t8)>E39PZweO7AS)`@OHzhn_Rb_mBS71jNHvUpEHsP!HOnoghHWD&PCPgYzgzn_5h4HDxUeqiS z${ZyO7!`w?hd`tzR_5YTp=E&}p@`&3!uodZA zNUuIY{U~cp#9em!I*_5yKsuj@1#I6Z`RtZ5rTtQ@k>Z6sk8a%A$ro+sR>5HUK6|yw z3o4n>+i6Ml7t-15*zkXto8zZC`{E5jN@PG|6(Cb(>KV+YnXx0q4RJHXX)t<~a#HJn zMaDh`kXbNbABTg}bbE+OdF-jxpSl)_`K77!V1L2Hzv#F;IH1#`6ld;!e_E2wR$)Pa z*%fzg<9oyl;-5O!V_jnjh`(MUaZCG>uivrJDhfdAPw3P+_Y2h+Xe5G2`cAJ0&u>V# z6wluH@*1qb=}zQW)Q`V@=V(#W<(WxWN|P^F-Ma04LpkAQ=+Ft?TMli7cK9$L2JGK` zd`%A4T%jt0jan|W8cx!wo7lhFDC=j_bZ7@LS2gyV=#PD-h%SQGlb$|Z7i{tKrv^{W z>9*hpc#i|8_a*-ji}K(oj3}f?Y0=C(#oQ}Wb)#hynGxggcbrbV_fFmj7yknMf&br` z0uo=`U|*^(2i8Nff~+UJ)u4?_q5=L_M-}NeTW2w0r1+NO^OoCdlE!pKe7TI!{jk!F zXW{egs$j&no$MM{yG#`G#{toNH8-|}+IaBb&d_tPFR(iV ztCg9KIoj2D3GJ4vYC_&zs-7vzQoVU_Bwh=mulUe!8F9lR z7eqCf&sD5%VIhL9+TO1`-z-xD)Q&M&Qcewi&Wq~TFph>%JhT1b4U#>S+-ae0a`M^s z7Yy=(TXwDhD$jh#$oaR`f(Y&k3?02Hl;J8R0pE&roDiege&6y_4V_SZzLs7=a2`fz6d>sp=0?aFs2@e#C8uO(=a60Sba(KsP65|Hs7Ttb}X}dEgYW@9>&> zUQsV*@n=|%4!mzW6kl>_@hMR>sWziqs6^QG$Kg6)snQLUP+tLDy-+K||Gd=#!_YHJ zTtQM6+mDRnY)TcFO68ccE!xUlZyzL743UHdxM3pWrBG_zntP@Mz`dzA&!oJ*Vt`nE zqnl}{CuKI)mZHEA(OduwPd^HvOz-KM$ z90y)B^RRsx+(o@sFh11}D)uSDV}&C%zNM2&VCxyp^r}ZvWC;1cOgl^8qQV>Ayu@vE zejoeOf<;unK%x(QU@rkY%@3c6pgkn8(HIhkvkxS`0qvo6UdQsd5GHJIZHeo$BP;&K zU%^yALo|xfond3rg?02M!dy>m2oMMr(tjPogp*%+f?s*Q!$64ME;{H(gcuiaTISDG z0tM-O)`|i(=H9+h!HZwdMFNiO>1gE;{*TF>bq_-aU~yGXfG6!Pk4R*5)bK8sV5p?g zIC?5uF&WTj%Q2NIQ^(CvJxfLVS%w%}iuK3Ftz)%ZP0`!<-L?tO2_+woW#KOxpBG*K z;4OIKDLVmT7IXTOHcb0uN|J+|LwEb{VodLH3`(qY?=!R*a>o6c<0+_JTY zlF%SmvM3iB9=k42c^SkcEcO-dxZJs5l%#nZV@d`9_i(_ci&uqtg9zpk`{}b-3R+eG zt{}W%IICbdfFaW5py74Xg%>d@Fl7N@TUSns^XY6_DB_-*aWi0@b6e!H9QCe;J*1}v z6(LViAi|K60Rs*fLxz{*+c%O-y6|;HKTCUOq%kOVo2m0kbWrU+ zbiOhEJoq6o4T@yoo%$i$FN3 zy$e@siULW!T2-_}RkT`_sr`igmh<8z>(hhyzkNQ+`-*?C2dfOqA7rdsz8;fAhnA^WyF|G&!NJFvKrP?+h&%|($@RQ!lz`uF?AqA4)QcV5{Q2M zWgSgXc^hOF43X@8W+O=ZdoQtMXCEr6-Zq+oUfVi&nZX$7z3;8L`>x+XdX6zBMH7Wy z>#{Q`8Bguw$^C;Ev&;cud#ZaK zv6i0o5+y=C`X@^kNx^||G#UgclLImoyKIKE1W+|!q%WAI%by+cqic=|@-r|WXWf=K ztj}$`TedEb{y#4Oc$n$cDDhe(y*oorfG-~lF~3k;K^!YWF8;81ukD6}_$YvnWlU`j zbP7@!qr=RM07o2B7Cut=m*Y#b$tam7o3YPW8XvcsX_mD#a6?~MG5}rb#904 zBO9xU#V_0<-}3LMS_h+Bx)04S?12fp{PCC3l8_uNYG+yY(BbHq{i<{4RnMlwma=87 z8C0F#>@^C4U!%x1Tc6>m*l%3APpw%1O%zJgV;+=p{z*Ty8~TD5xwG@-{Re@x zU?cUmmf8v}zDX!o`MD2p$pm`Se?MtUlq^#Ln5u;3DLn<0 zUD=_TDCSK*?!+#KOCPJ4{`_R)-mniZF-I#}*8mE>I6zTVtJU?IT2F#0U9=`aq$WkA zAYl$Ki2!h1{=ji3K~tEJPaoq2$^x*ZbYdIB0AiXV>VO#*_bI8#66(Nh^&``eeUFt? z*t%*oHzQhgTT+M25(c>7zp^Jt{d8EZzy~YMtd#HZ7l9z5&nFOmF8#HK?}BcWTB>8NHg+t%?6;vF?Xlq-ESlaeou=&j%E@} zw5D@D*H6D|{e*uzV97E?)k39*cJH>=673)k%P;9QM*wzOn9)N32RR56HUJ|JeHHkN zf5qqDkkb_>j8UkHyxABqr#b`t3ov&9rOn6?$nkn!Z{iRz!=_$WP{BtVay*TJt;SDJ zcae%;;-aWp;ktE5d@Q_2A+95Vqq0a^(1dkJyy!R9a|KF6&<9O8YWzg;$Su;?h0-rn z4l1xfIZ*s-PB{p9^zkXqz@OhwXknB1mDiE}>qu@>6P}yL%KRVxdKvBh_vf0zC5#j5 zjqK3O6L+U=2UD(XxA05MF9f-V*KdQ+Gd->5W?7=-gSz_du-2`m^JXR@w)QUD6S+)x zt%vml6v2KlazcYJwn2SobFx3))dstH0OOu`ssf$dX>6t8&p(lDaZ)@{(bTU7gwlQ+ zhT2eoQ@yEUAPwpJn30G#Oo0-_wh~T(0Z!Kl`&C8~&?EZ;<#-pUG;8Sog~%v$_#`$4Pd?(q z%|p`tYw>IIEI)3(QCejLGE!=^>u2{_z?`muJ`eq&8yOB&?|gb zK$(9V_9620hi;|D4~J>+))IlNo*(c2JfZKsLaB4crJ;r^I54ji46m!77OQ_X+5#Eu zn+n4tVmy(g7%_*~SZKf1O-WN~^_o&jcrhFad`U?t?Y{*BC>p?@ezM?OnkhjzwIeur z`tXT!;C-dR$wk}}Lk$t#DVt%S!iyvWt}S^Y98?$+u_`Unz zW^ehIErQ2BOUGfb8&qtaWIY~6I}gIh>OFSKRL2CF_P=?mt;dgKpAaGe>jyz|xjv|c zA<6ls#sU+up|&(8gs%CoYCicjjH=too9l^p@F6v#kdY~0IVPt0dzJe6#;2yfkEutO z*_?a6<+KU=0+IG@LDNo-e%hB6NEibjLfK=}J9D8h17@Jh1d%uT>+RR1Cz;L@5fT3JN7XjVJ-U zSTce*GPF1nf;bX3-!EvRbh<@a5Z$D2o3FV;>z}6@KM~>DZ2j7(yRZ1(*hQZ2Y-UcPTnBm%gg@&Q+M>~#D-pF`n9^Yv$nG#1myGQp&UvecNRrmEmp8fsAj6yinv z9SRSIMiw7@f9lcVImY#DAVh?DFhnTOzH;EjzQ@SD$K2FwJEGFfr{JZT8qxCFFmQ$^ zJ0-|aZt>Wefzr?_0M>>@*x@c;Bn&#-Z(Q61CQNTT9;ZLav3^ds;|6#8;!pY_#3Y3L8ekcirF)Nq|gf+>h z1{de+`T+Xq4avRQkNS_uE{Ib!*l55UD6B6Sxbe^*BEi*n$MDJ{x6hYp%1nkZII$1DDhRn zBin{KL6#9KUY>lQ1e3bzG}1NhuUOxs#(w|%zJP{3-Mbsf+m?eLo9cUaEAz~!jVQAcP%U7(|jO~CqzfhSEo>?l<>9Y+GQ z=(6jfF5CV2Z=SfS*O0cZ;L#RX9UKz{Rbj zaM|^mqK;qp|7f}jpsJd;e<|ti?vn2A?v#-3kS+n~?(QxX5NYW~x}~K-8l)TaTi^eD zGiR=s8OGu4Is5Fdo?Vb!nc+qyCeD&xtoYL+tBjQ}F!X0v1hm~aDh`$F8@-Cieo4P< zg{h4fxHKjSpT_o}NPqccNtSam;NbX8l$vDr|2=-x6%P1foAm64;b9?=%V`XMabGZH2Q> zWr~NtO~_C|l}WzjbZm4cS#!KV^#jR$>NsDB&Skc(Y` zB2G`&JV~Roxft7}!awhBHaAm0n-NcRZEM|T`THVXNhrpSuqY1F`qWKykV3r5>T5Qz z#ZMKvvV=_r7Z2XB^JP#UAP>wB7BCSY^zPgS@7Pk3QPOY;v-KRFo0^aFIvckElSJkn zsgB9KVeRA22ngg%yZf>`5z}XG?x1(!Pvrhkn~IL`HWU^mzK8FS(4Hf_YMV|oBWNKt zzW+S=1p1eAolS6?r`9(*Lq~e`o_)rKK)*rG_$<%Rql5QJaOPIslUdadkQoaA?KoYp zuvj}4ih8f`gfC7PU5>uRo_}yNX?-oFB4Ij|a73#1n|LL^9qY{)i3)m3$8_CEX~l2i z_;a9!cjx=Cg3hg~_G&m_$>cQ~yK?tg_>D)@B`bubHa^gWC0TkK2lf9R-0UGZ(b4@O z3Ilkg3bDQG+HHKfQ}y^$*?{D$$;6^NW%Hsx6@e>twgI4}$2gxKdt-wm&r;1b{SC2= z6M7!0)RcBQ5yisi(&MF*jSsW!y4KHvFg9`*OE7HIz%U3fq7}WHZO3-I!{70Ib-r2+ z?L3r|4Y$y2Fkd+O5{z78>IEW#cvh72L)=rFV;dSpHT*k--jEB?LTno;2 z!F8G@+7fLP2nT{ZLCIuFt6snjR{L}8HCHp{r%`Um(yzV`dt%YRND`n1Y2)NcJ9;+K zP%qzg&DZ^)ie|CXZa|@>$l>CJ;^_Px&x!Y&&eoC8g4H#Vg+EX_dKaysZ`Azn(ByFs zKXzU>CvMll8~3X3dyh^fkMa{Rd6qjcNU6Ak1iN< z|AxE2K&bI;!ms!+E3)`cEz@)RNIe|CLy9%?=oBhI2bYecQ&?*k3clJcZTC3CcYDeY zBBHjQ_5{jrU*E_hKw&Up9=myOy7}UrEEvpdCk%RigF6)!ER^RDNoa20svlf@$@o%s z|LgtxBCj_x3pI712pkCT;52_d{spP-@`s@@44C{3(v-m2O6{+6>k`$41O^;HL?3Ea zI6TaF*`3DEkwPq1xwuN5*x#Mc1p1)!>-z>a_fIuhOgXDD|9sH@s@tbt5jt5OJXrqL zMWg+2R78K#n2d`1JWvq6v0@K%^@lON4j)oNx5wxAxJE% zBM1WqWI$@2-XV{p?)o4r&ThHGyX=Y6cU0dWQGu?G!aslV%k{H!t1G}pJnKF;(2~LD z<4r*SHl>KWB-(+V8fS@?4v8*a{t=HBOWdUKuQQ&Gb>MQodHoyIuIHi6d*R!jF&91I zO-n4ly~wl3F5ynMp1{XHWF#LhRyzjDEfpDw78XoTc_zvY-oROWBzv9Dc}f=wj8(x@ zaDb0hxrx@lIOEi*9CwFHLphwbk;%7y?|+#sWpr`CE`2f_1bv*N!H#F;IOhfT#YQK}4w12XSy+0XYF{N3Jk6mYSTzn{3-z#=<(1@-xR<6#x!zWDC&~&OA#+j6nRq}c9kzQZ@sP@*P*rV>ILYEx5o_Uq? zY0J&mv{^tRrFSwHc4a*%+)889t&O9auivt^Z$JJFg;pa*qoECD_eKUI>i&vf%32*K zUg`4(b`LA=Bc4;1dGF1UT#*fN;} zYxzMDyb9`0VlZjO(*@Y2XLUxZsX@5M%vY12o>x9F=KQgydisHbx-N}oBuMopN<%#v zK80qpmVXEDk2((O=YG`o2}HEWyaqO5JiLt7A!|=_hdG2V%TV270jxEgY6-6$5nt=z zNYSW}*Ies?EKlB38Md{*CB03Glinzu-V-VTSyp0Gk_3m}J6HE+bJ@vS zMLC(P0c3^9{Vpd3qRQXf^qq%#3kzW(+^gE*Jk5)=!jX$zcVrQ)@iGpn4AQns+Q?SF z`v}<}L3(r{RC2HSYooJ2pg^6TKD>WUi?8O2{O_h**Cf_tZx)^)wphy>X70tcn)yw3 zG_VTd$<$H-GGr~nXeW61<>DvuPi3~v`Xoh|%zFxpf%wSCV>rR$5S>_QXAbYmAU(d5*Vni`UZ}xWs2uJ3$RDA#zp66tKW|Q{nK#mB^|}4sF`&?(xi#iL zPsMI=siHSG$dC=jU7RmdDUt(IHkrELc7wmv%)vuMK(B7Hp2G0UawSwPZL&X101{P& zi}B^9HH<=vy5*R<9w9T4yPnbPr=b?30#i#?3`pWoekQFkcv|0!?TA!A6Vrv_?pRp2 zgHf=T-;i#wt7+d+#vFp*LrL(}%L;o-F3yhTK_ zCV1lxCu$AyXT4tIDZ(;Tj>w(6PKS^wb6eSajb>q=-cjM<_=T4b13&a zGiaI^L&R}G1BTb6XezVpD7j{QLPf*LAFt0;;1~`b2WNaLi3c^_t-iEPq7}QTV3t+h zKkWPO%ep+~tAbq|B6XyUL>oRNm@S2D_}z6zy~%yPG}Nwnt+axxSEmfyy-0pFNQc(< zZr^Pi|Z2~uj#xa4>ETH;~+fCY?srVzRTmZK)I585#w06bU|Kc=>~a_ zjB~i%c+3T*mSJR#%c>WfvHuaI>yiAuB(_}GT@!qJaSBcF&l_4UveZv@Co5MP-yhO_ z+_ZTog}sUyzp{mis{T}Gwlx(B%G~$TiH*JPcw?d8C|vBwdIeivkbM^?Tfic-v-cg@ z;|HeE2LB@v*j)$lPq*aDsD^Q|5M-_lZl?O`afW9#774cK%l!`d!n_)?4<-n-e zp~*fC&1`T$H&d&+e?+7UhfSOc*QT>BJ!NKY2&)i>rvnc|eUI~9M5?MDs+Zw&6;p}Q z0nOs-HSjS|}2DZY4^54SX5F6P*m=^T+;-kwS z7p>jndX)s3H`LQB8CeL9Qb@{`+}K4e`{d6E#yO~rw(dIuvQTug;&h^&7Ipy1&XrpE4`jnoV}>&6kT0FxYm4v+&Z^u`9m^rn)-OSIE}F26MkitTHYb@$N zFhI6iT`3R z%*s~Xc->nb+ZBO96Rd*HYkrma&CUi60WxUD6h}QJ%Q70c&@y2vv|*#QVWF5-_3=1x zWcp9t`VX&r4qdoL2l2XG@!wnxfoUtq}TAK8q z43)6oZDm3J``bHkzKP8E?=JsHLe;gh;85*$jvwVX?PQONP{}?;;lh@zgM1uAQg5q* z>~Ro-`N|<`8M7kbni3=iiCsn;3vq0@nid@SNPvrZfkTtS#gWxxs%gc;bnNWoEp1$&;I=? z_V;QaYX8!~PS&F%A1&H2tyb@I?|7dNV}FcihEMRTVigaSw_1X@H<$`x4CUkf)9Eu& z{>(dGS+XqZ75MAc4x;LNqebKC#g%oiJN3yAa z)~(4;W@hne_?6Skp54oy)9aoo3SU)>AYr+P6F=Jet8|PQ{7iB9kT3TB_z~=@9~1TY z4Hl9TKXiGDXy;tNuRpXS9lY<^cD+Kr--g#g>BsXhO3$rfRNBBzl*Syv>Rv8lutyG} zcIV3-fcaNfVh5$YsN|sq!js_yz@Qe9vrFR|-x?FVn;|Uv8p$f>IL4UQI%e0_Z==PwA2#5FYoQ>9#Y~r6L9^*X-5DTYi1u0suk=d$ z*Jb(I$@lvtR}I6>6bS~rq848Afo~7BxugWd+!A7@QHReMaH!s^#^B0|=S`x4DwfAk z&csCG{8^bm+kgNI?b6=SJKQ=3Q<#~}4<9nqQ6(&(4MNdA*=O6F*q=0zeJ4@-%sdDD z{C6`H9bO)Nqjs}XHH*)Uh?owk)ix=xg z^25GSmh@uBPDkq6IGKP<#`;+?6-LaZ>(SO=fYRO3QmL?#xdUj*6%Aq<$c>|hXT6q1 z2!1b=)+&m85n#nb2npiUMYF0V;rTLVI-PAs_N|ZIJYtBavs9Ts7{Zgz8M+DQt=SSQ z4SkLu+(|mNij4rfS)kXOjdKvqO%oF?{$0*17)xJ?#+NK%@l@vQf${(zg#+3Cfr_m9 z@0V8&OI^^00lprTRv|uf5y(RX_!+-9R+^aRFrPRaukd0@8o8tDYts#BKb36BNl_z~ zghSSpQY>R}Y!w<${oPk4Nri}S8>hK*vX!r2OadZuJO?nMLK&&{EuIbV6WjIO3Y{|! z%?1R=^7^H#)NG3B{-ag+^B%?B76d-D%MM4fDD^ywmhaNa9hie>_d@LEkgl?D_rR|7 z)tp<(yjj_C9e~D3Qi|dD<0C8y4}4Um5bxc;>D{>q|1OYfKCQ>#W(g+I@DBzlG!3Qb zAhDoeB|#J;!uSUtS_SAYPG5R6x{YC?soXVKn5i(-Bajn zCm7wdc7>ZvK6u;mDVf|cMv+=hqvdI94zIxcYdJOHRC;#AIL=f}! z3#9)Chj#XqOOIPcwYUtYaK={-X?q#gD@e|V1eq*KPIGeoG+gw9rk_G!Y=lzIHtS?K&M zD)x#k(Y(xn{LVRFx3Z6PV>RdpZgyrSS!{El2a0ywD^;#qv6;E_*6rntPwSr|I&0WR z%*3voY?R-PdN7W1A2Ab<^Y&WT_uJ(!n=z9FXC*qsV^viwttOG3Vc=yTVlk?YFse&@ z%;@L#eWPSgG<`=5Afu#f{HyT!92pHGF>)6lLrXiH;UCH}hvODHdZ@QWL41M)Y|gKq zp<2L|R}gh7EQ&8o_Wp=35t*!T=#R++S3sbtWRRTPs8+llYlt=@+8>7RNtJk!S2C67 zK7JC(>!pdyJ~$A|Wpf7~Tb{;dZMK{}Yf}2h=@s5S@6~xmcI5CN8m|uwzCyM%9UU`) z0o?X0l8cZ-cvR3_YQ{X>f5G1gYQid5EXO3mK7|R{$$Ii8Z~0HT(%3y#w74cj*qca_%{dE-JkM&HXdu#UwsB z8qd9Vi+MsjY8eYb9h7|p*m57tU#aK!hCr7}vQeOXqmbbtIHnBr78FCG=*oV?NPrDy zL*tif{bhbin`#A5?F_Yx{Y`rihsG0Vbzx-R5?=W+*WO?@h0J~4xlCg7>3XU)1WEls zcs}&POl$5PEEEol7B!Oznok13s4(F#0rPudsHF*3y=yOzd_n+{vaTQOpeNeDO+4ZG zrqf{S2^ach$Vil^Vgk}6^Tr;<@W;T~(DCKgk3(aW_lFuES>oRzkE9T2%CNt#EI43S z(&8r3!9THvcd({Ep@RqCcO>f+$|1PviZIGC-elgwXf495Kow~*MB^txlTR_^#C#i5 zLIul@8|6oKuGQ=R+`I3NDuID90-H2Eki;OJ1fJ%w%q(7X1MEdh2Y4jCwtxA7pfZK5 zHazfC=*Z<1pNJEj??6W0J%ekSw)X^iDBJgiYO zrGB4l&0*3VvTu#Q*GZk)ky?=>by@zbiSuvKYJEq@yWSi!^Q4+hKOnNvM)t+&Yp=nF z#6E7Uc6f2W1D5v2i4}{7l?LTHeyk0>A36D`AG~Hg%*VpwbB_u1!7*Aa{DH#tj$9_+ zL&e8KCoopFXp~CD4=*K~iX02-4Dwu-S6!5&yqnih_nD=Im5wkJVUv`9r7Pi3A<+KA zaW1pt#Z+1xhb#oYfAsU)N57L|Njk6l{o87(#UCyVyo0|E=Ra>Kyw)X!eau<$l4*uf zf$_4-ZPtQ1`f542hig*|WT48=IGGJ{tbJq|gL)mz=;*?pTxFxn%cZ_Xpoey-aC3FW z$r=Xn`n9~<8^NzxWyB}xSyq#j3YYQ};gV{2N^pj}sKSFSe*g<}B%ju<&@1rCmIdRjx z#hSyG`0p7Fvh(+}Ncr5eUWAKZeiK z*P6qLcqd+UMMXZ5uRupjMx_R}CEbe?Svd%fUjxyc_Um~|cC;r1cvTU0b6BpuPn+{v z-Y0zMWa62)aQ? z)A!G8_=IsK%_j~^4MH7EEMqthL?~lqpRCsFE`h|jgP?{h?^=tCLY@5N@i7$ z=X+yjFl!Cz(d$N(GZBkL9@-X?vD#7z&O) zU8iwE%kJ3)H92L|MWA?NwRVVv44x(uHghO?iLHgj;Y3vgS+d|#m?7Mt%gP2SnYp)L4P!0rz!jhRTVk;~pd&X%;hzkKsQaJq1N%Bw8KvqHh z;44w;@##fc6KY|-jkChN<-{q^_pjl1-Z#N08;^%D8c#%#U-=(b8u$Yng`9FdWU5i% zaD9d+by;)hXqbTrHe}x@wc*^eROI?o8=o~X^l<`rskW2ssJ_Z4&&>B?g#4$=eY#dM zG2(ZYrg({W?oX_a_BK3sU{Kv*dr+fK+P5YL1!~A2eyQ0=PHKX=9hBSE=Cv@yo%nOw zr=C1hjJ(HmpP-`}r%DG)IyQvH$r4%hv!38P7iMIoTCL?c^EIoxAS87KmWS9f$+WrM z(ryd;4*$V48#*iO4(GLvE>cOP&*0EO_+>9e_0h)Yx#K zeF-m+hK5@y070(km`i*^OtdsMnb{!j+xt1Q)BG`t6c#6&%xr>_>77uM{Dh@J>~n%_ z#4>lYLlhcjUig~0l8!FhNjh_$C2D5VBa@3qFgB9 zyvI-*x~falY;uGWIPQG)uwFAy+O?i?4}D4?9ATP#B7c6w(Q&E>lO{YR{h~Qmdpe`C zbUFPRtc*I1WL+e?;@hr1Gk^~IllBj@Xcrf0PVnlPo9ROX-=;qbvDE)n*3AN8ZWHeO zo;i4#>Yg9nng$_3X2T(ie_vjr(xg}u`>Tpl#5dNPjO>i`h|ojCkyR`VsGiV_e@Gam zz|=W7b=;6z2AOJ{_kU+S|J`h!;B@fp8OR1F#1+iopuPlQK0;BKDRKZC zF3BM68_kuPz8)SIZz@db%T6zMCShKLG$#}owC|NG_8A_PWAjTWs{cGq7kv@Tm2`W7 zLHN&XeW;NTPR(){NF>eO19l_Js}NO|u|;~_1|ICK{9>Nn@r}=9ndoQFzhHkR8bYbS zSLTMVKw$hJk2`)ZpXqM37Sv7uIQov^P3CJ@Jv=e&^oxn; z-&4D;Nm@mUkABdYp(;qx0cO`^tgXJ@^;uK%BX`W0vzOh6XyX!{EW5 zqH!~N1SXek+s!$D=jLT=l@%GX(kqK*e!%{R(lY{3jDPexxygPURFO=Aef=%G$U+cz^I9C7fb1m@U48t7T1<72;>(las4R>syXe?c^LN+ix+$^fV(+!z#bK^1 zKrSWatZZAaj0I#ATk?%jJQFzbeb+qU-ZvIZlb&SxEDj(W@CgNX{#Z4Q&9f z#tI#Xr{GM!BR2_TB2vClwC>1)gT_(q4n!-cwUyKfCJFqz^VGY=85@S~m9W*&Jhih; z-#N>Bh^YVv_*Fz3cO>p_W@f^mwQV5aAnr&FMlBp)s8?D|uWO>7ew|OUX{d0i1D>t- z?g$J)moQnKh*D{gG;3cEtLNzTX< z!`6LTBKadprCDz103^{Ga z*M{vxhshTNyvt6@s0AABi0_T*G4*lX&GE5D6Q|g&iX|Shc2M8D4WX#u*vF{T7(+r} z*q2^;s2Pr}>LKv|%1k^&vu+d@Dh{l%Dz!fDhonWHke_S0tcVP=C^{;p2lOChinNF) zo1}*xz58@#V@~#MYjHy)YV7kX*9{8;m#f8U1^yUO(r?E~E`RjUR};QpEkZGf1Rc!I zgq}_MLK7kZp-^+u$+5xh&H4g1#7S-70>?-5m#$^-*nZFHdB`WInlbpDD8d~nS-_z% z5K6m+Ww4-xSwW_l@$b%`tI~2K6KFd;cXZFLq{#a82fg=dB5pCbwS;-Kge6**ke;3E zK7R8-^-MB=;YnB#i=}(sir3dNLzLkJAvoHAec=<=oz6_?`AE@f6A?Z=|?>;Q#6Xw>CO@8w+n1D3Vw{_3ro&3!Es35;V zA7=I)WnY>|83YVGiFa}vZt`ch(&<5DWNYWk)M{`^TSwYN=-7UVNBFDH8>g0n9{t~r zzWUQ&pRp+X^}HSJPthb>m)5aPb*Q3BH{ z{T5;iP<()I9>%FQDZ8q#w-+TI6yh_EG+PcQn-523W3D0WsgIXoViExs-r>>AsS9R& z{3nxp0uYvvbHE;xv0XJpjw!i#=)FZa?#ae*8NReJ$k}ern`F=Ou2$33F$voyQc;`? zJXB_reF~{iutI&fKjM1-eYNJZCqehiozyUE+P{HFPOZ z@z8!|mf++`DM=dKFt?i^Q^B~|J&6er$c(1YK+tHd#326t6`X#?1A&&!$JLHo^?EH1 zyfbr$>8P!0jU^VQcuER}RKvuqaDcH&1Um`66W(l6i89|NcX%ZHxjW`Txy`rs|B zHK6>rpj?|$E9%;2bnppc+r|v8hzUk4NF_@eQEW?nkRtW=BlDd^@3Z!x37D3_c~fYq z)l15PqY{`pLU7fB3ymq})JBi(Kqn_1d3JNRAb0A1;msECw~(CTvpmOVNm2+m?lSxF zDZwcRCm~&#_(s5oKcqF5_A{>a|5m*PP&RCaUfnD-VB?pRjV-l8qmljW#6A99p;q4V z$vc~wCcI>Ha%;s5Q?HvkQKII_&ZW|LZyBA@$6bBsPTA~vKSOx@XKbMx1p_FhdI^ggrI1jiqlNo(| z0b6$k1G%xeANz_6Kj9M<@;~0)P6y{J!Ze-Q3tq$bV*FvqLsT%E!2Sp15iq1JFW+gO z%E%d4IFidQ<*7H_IXi8kZSgrPZy?syCn){==C2Z`f|->BuO??V8(YnYz_#fjLAR)?L^gNgKdZNt*oJSA@*W*{^^JmwnPZ*|)>1 z7mp@uhnq9%z}#Jp!o;gvc0%XRxZ~x0@$>f``x2(U@@tHXIyWs;>6FcHUv{pX;!ruG z3b6j85H>IQDGC3w3oKAa2tl%+EZmXalpDW6Vr6T+<)`a}+ZWA#`|>Z+Q|nL?dzd~O zdcVdM2@Lv+c>Jc1*H>l>D`qn*(<{}XD_aA$&6L}o&Hczme5D71kRXcl7XXzAgeI(B z1Gh^dX6>i-M;`G`X)cDG?1WPxzS1q(y$K5NYTpLjG#Bru$I~*DM;W1%0%u0X8|z!G zBHSkp(EE7Y`!V%};DkhJ7*_(MNwVo9IdxwFIbB$EWhlTx!O7qA5yS zD;c+~lujOc{#|UXxjP)FrLWLJZST;VI&thjvH0fP z>K}HWl`^8(qHflUK9Pj0N8VM>vhL8PQ;y-YmRzco?k=>vH(}vThRLrhTPYuhITV z_;*)8AjAeOKhZXW2IJ~H9=}=Yqkh&$*TSJhrqA#*4|Za#g+xJPyzcIJ6yBlKT8c&E z$g*oz`YDa9+NZ5^=+Vk3Zqtpi3Oj0j{_DOja$BA}xA?`zLCjL?P7_|uUn(a7Y z_z-TM6Mrb~m;7ZidVtv!zXmZYL1%M%m9jSX!$b~5ug6Ncy!~w7EbRsbHr50_ao5Wg ze9EYnvxpxdkPwJxzGl#GmQ+!aH+^u=8-CcC5{hVI z{15#!fZ@`NJCs5}DAHc}8}Q)Hgco(5Qe(g0*M&e@X%&Ufe@zTC#&o|usBNj+uQ5Ru zVJjb(vg*IqaLXMD1|*;Ahx(=B8*j_APR2U-JX9Q+;=ij5f@-faFzof*fYkiE377xn7 z*t;#_=M$tl(Z&uR+bOE$#kXR*(pV^xFmqhbTgaIL>HiG4>qSi;<*p?7f+Tn}g}&oky6ZrfeqEid2(}t? zkAg0D>ZQMZLzKyrxGpXe`D5x0)vzC#QW|oF|L6#zy zNL+yPA`57Pm1(1+0w(CU%tbK0{aEjz1Jrx2d^VBqH<7)2$pg;$)Oz3xr{L6jEZ_oi z#K8%w)VZ#bS-B)GZ;iMF8^4=t=4Dn-|AQPcN0)fd5xx~$SNC-_57dUwG=iI78OJ1U zDs4ZnuOmzS+on%l5=|F{fm}{ZowZxLp$L^r!qPTSff<~ixb_Y_h}psWtTSW9uXQ(!lt6XXoJVBS+cIMJ#sz6&-}9;^CISD%aBp3mLV&WBoiI z&P8r4Umhgt(ILayXl-5$_n{=&6J%(dh20r&RM?!Ed-DI*VlKnQy|dckbaBODdpYZ- z;mH`I)MZ(SP^i)~*;dAhb)vR?s}#xrbC4F9LF`;TxFEMcn~rV+2uS>2eiWl-OrGPI zi-Eva+LGP7n!h(|>J~I9Qrm4Gbm{#Z631d^Bd6qeR338o&tx?mkA5;u;d0AZl&xUk zoHlv-j(gc?Jx-J1c*$OtrE5T;8Ia z4EFvNAVk7&e0hA#ici;+sB&sSl<29V7`Ys1k?fDTp&bTP>WAcv3Cw|<&8#E029a~M z?6M-NJztvY2ac%7mEXNz)Vg87=zM8j*!~^Z#@hJu0FK?(rXc7Y8PzQbfZ=)?0!dXt z(c^?AIt`?YJFzZub06!EZ`5(%{A&=UG6&h`1R{!+EWp~v9^(?;zv~*(XT%yJO$U@- z+9P=B1yBTbf;~Vh% z3f94sz%2`i#L!0&@I&;WtrW-w0kU57?mtg7j^xqQ6Q@GX$`56D1Ugl0l>br_Ix&xE zsKSBLG5ve_-dW~`XMq3V)`#SId1P7?h_jSJg%4t1zI-L` zH%g8d0p;MFZ)$DgUhgnsakqc^BL|At5r>BziB!IYOBSJLY;4HbjyT8_GGGEOB2I%T z&VUQPgm9^d3NtOtN>yn=yI1IesE_p6Zvt>#g_Dq0z#ql21BmJZ9CSC4>r@xngGtxL(}uDt)KTRaW@AJD-r zGJfUK?^pQr?ynELqV6v_2W0%Cqf6$K`$GzKEQ^nM%^Qb;y4>3k-hHl5CtAI|L5-7c2jeQh(6@X8{iWsB59s8HVxj(Rb4<`ZmJoSd?9j^1Ns12X=GTHODM&!+JvT zBuAVf;gEPF8MW>p_LYwsq#y`6SsS5P8j`HdPx%WDLn++*?rUk4ULia%BK(~G*%ALU zC{|U~7!(VXE9X4q)2PbFv;owiY?co1;KW6$bkp=j%V^#IwX*LDbYYDJA6SJVYfr+< zn9qE#fJKV>0%=f9i|vH^;jpw^yRSjCBvPp1r6PmZb+N7OlT$tPUxExGp3c_4AsRT< zQ62HJUDwPpCy+6x7t-S-pwQK#r)jQN*EOu*`FZ9rt0HPn*k0;*i^=w7(@nmmW4HWaq4VQthp9}P%-8mB)DT?~(E&C4pEM+zhz?Xe(L^yu zq3_cFI6R96RgH3ZhbHcvmHxE0wN#ok3zE{KDHoVW1=Jk5%je<3|8f-E34wPsAjI}F z(UVi~OtaD6a`e0XeJS>rtt+aZ^sy>yXlw!Z!^AEtKqwTr_r2;I&hMYg)Qh_%y0|(! zLFW;%LOjJyHh?{eL!yqfBjAHB~H4X_gG zyC3I*quB83l%sf>tK{w1w?Ll1k*t&84fox2x1-E%WuHE|*eno_2o5<-8yBNy3O1)K zcPcGS2Yk!)?w+72i%)N02b!jKLWg!^mkN&Ly{RnmN0W7PcraPek*>7`MQh98St40? zHmAhGhX`NDw0_CgIT+D;bIp~jLm_=eFN|DC*5l%XK!Qc#6zuVp=G?0hu}_7>sRLqv z<6Oj(I|1=zGU9{~pVQX!e7|t}S0U^85drLZeV!Dr)#Xz>?s04yP-IVv5V@!1oE|;G z8M$1qzcuhbJ}#R!l>KetqU>FNup!Uxz@hic5AovtBPV)(Yb?tcWqkPW3FW2ivV! za||$ZN?_#66yWGTxG~Jv+vO(JF{6>-@!j$Th5@);YD_H;{m~PSp>jr=-+6#qXJSe@ z2{7sbj8j7dvRPhdMD=U5pmk2&KBJ0^2%T=GMVzhTwdRQdI|!`q>JU5&(8PKN?PT$* zcF^wWbPzk8Kxc5`luH^cMHC*Zf^c5NLIPM?!^%o#8MMH9Vcy@gt1-YBU3AO~NK9ya zJQn7OuG|8YIK_4;2*CLa=x@4UHwSPf>H|vC>*UqPZ6DoUs7>3qnAMw4NmH+|y}=K2qdt78p6o(HHBba96k($^aUNNw@T8LI!xgDRUz?!W zA?0M&TG9alZu_f<+*ZAeTpukG=Fx(VIJ4Txi`jy~%%2h3Pg+YGt6lFVXE`&W)ogRe z{DLw*l^5M`s;GS}!JZD09s7P(_PYnpob1mTswdcl%3=Sbcj)FjEv}vtYVcCvS z3I=;B&4UZwIzzp=e{vm$q(~kJ9=ig>OS_8dm?91LG_yFheelj^hb$7r4lR{{<=caO zNwvPo9dG(%6-jTaVPjM4fDD>$wvH$6Bxx+%JyN#{&mPRCpHf@MDA{Zg2Ga0553Lp{J664;bVM87d~!7;?}c0fpqB z*Tsg$-xUVQh^M@5D);!46;Idlr&o6Mx_e>V7fm}z67W;7?E;L5BZ}b4Bqr?n`TDI9 z8{4&TR!_{0%y)~Qdo00_n=Q#H82JwBxBP8RFafvuFB-Uqsen88GO2Z?Rx;Ab0JSpl zq!r#z2ZLiT+EFZQr8nlr#d3MOXu1GnwVs*dyCP+onTBhHW}3DX2Ob^;Lz-%bCmQ)n zHJs@C##+2FnQ{XIv}kY;+x2tw82iz7jqIR##rxMr2qFGJ*!VQ#iI(`il+7OzOXgDI zRr^RZ@ME^Gg-C02t!)VN>^W6x8^GwZYbZZ%Ik?Y-V?oilVS?5d8YE0uD$y3de*qH! z<)QyT{EUIK3lU~@o~~Iw0>UNUu5@w5a9?MlleOg(KcPVP+mlG$Pi))(b1hw*7;U=j ze28g~Zb%z=Gzx4$TwI?=K#PP#4ne8)7#%RyF+)909-i-$2V&K?phi+4;LO{qztx3M zCA{|)PO%GsUC{FHwY6V*;o~KuHB-GRZBs=vog(dFP2;IYC7 z5=lhzeX*lpZCXhUN3w*j0KBh}HRJY971n+|&|tf?qi+MqBI$8k)6QyrXG$v4G_!hL zU7#3_$JXfUWdS`(^urVMKP~n9iR9>xyT-D5+-pb1d&#{DKQKw2=rakP90iC<2EGo<==2Q=GM(5Pqbi6^s!&+g@@Bu1YuRyN<} zPj_c7uyR!CO}YFl&c%$6^vXksw9woR)lwO6d%Y1o=zi&I+K#%JHS5o9%Y;wZ9kvP%(Ffxm_Cm2rHV2tDYocz;zFEkI0vhmEp~2k zDFo`YL`7K1MW5#Z9Nw3gPVI9kdyV*-_+P6bNeqd8n1MkLx%m4P@$Y})4Kjkql!OnZ z7%(ZifxiufpW$lYmS6$fuz9 zTTT~#nmO8K5QwWKB!xiQN78Fe$;lgwj>jS!)_KAW{U-4VX;EhBN61!0hx*Q3%{hY9^m09bdBM zjtwWiJe!8Iebk_{U=V{-vGN)xqb?+GiQ0JC#PEG9Ck|HvSO2eZ^XOmWX52eCdmjFx zv%wPEd^^&R=`m~k0(JQbFD5+Wds@zcka74?qPl=dIWze zDa`?RK<~pb5{`By_N_{y9EJE6SPVw0Voq%wiDr>gq&Qj2I<6n);y0J?Shu?W9%mUYaN#BP0BtVfAtEL-+3-MNpBm(97%`wfff^{if>8MWpI9 z=Sr1VO~BG%zc_Blz^OY|%@})5cjmabb#Q!|gFu9qdDJ@jR}zDXM%cIJ|D)+F zgR=UfHU84w9TJivos!axQW8pchje#$2-4jhN_PoJcXvyNlyEoyd+&_S_<)aYnS`Vj+84|+#>UfV8>F=^YUN~9YrXnr|UhBGFv+=I?WqbkZ|A zV9g>X4WB+PLb17(wOf`457i|KAlJRFq#@J|r#9vWm(x?2@zgFa6x!T$wK~%HnvBtJ z{%5b(yUr=H{>@H!_S#=ey7yABpA0k)E3AhPBZT4TQJoHmqVnhu3sx3)sSd8lO#0m6 zYXQUdiZej#x^^dsDwqlrDdSb&O5;{3(#bslSzt(hMFa_%f^sT=s%S64IA0=G!$`4TSO^8q?NY5u>4KpLkP|3B5Kd3Z(xmD}-$#9sFp zqDbraeWxVjr-lDoDH^HIYv0Fy*NBk-z_RhRL|v-f?T|yQzYpz@I)0D2OAn`of+r^0 zi-7{W-p-q%id}6W2$SO%m~7U|PI=LTEI^RDnd+(aXvNUH>VC^^b30V3=;-x~LM}FE zZR8DVZ5jb~A$uu-GM31_ kZjsXnVVoib!faZ2TKRoc%?2htTxmRNu@Gen=AkF#c z;~mbzO*P*0JMOX^5FXt3?Rv0VxDdVddj@V*r>dr*Jc`H-ju$bBDs*bkP9h)$lf z0nrJ9jbKp-$s=DW{JD*Ei+hEFf&)CBmn03i%#lkAaV*<8AZ5QZZhF(O3I}PJ3P8!C zomtBL(PT|WHGq{Av;lTiQem*5N#O{t40_lim+`MR@~s?h0`wLP-`*0fJ&RKyLJ!ZX z=j{9n8vh}LWu^zvtIM0gw6vgLyd$Fi7tskj`zKAakA3PwpD_VFC9wVM6(mOa-yiqf zdmK%Gqb`1y=d-r)bdC1&uyggl(Vu-X|p|XuMy`|4SDmQo^-nEl^Vd5U?^T2aVoNHoadhHWL?sj!! zqa=(58+O=AEQQo~P5SA{8Y=yO5BhY-D`$E20Q2teAkA)4(~E4o%ubhJAj6CE_9`-v z55TqIM-~(cxtG?A-Aog_4rU&|H)1u~bC6BnH)7V!Qjr}bq62viwTCzxJG%5zLSVaa z{eY*So4~8VUp*JTUksrVo--bhiw1VUI z-Wo;dd00a0uQ1H)N?ZE1>Bm^`0yVsb*_VHAEru5J;y9~5%kA3_);^9i4BRS$abt@Z z=-D=2ZoP*CU^Bo%-Z|_W^xiy0P|1w=1sTW3F~dZ+O@v2(d@$f^fr!MZYLrNN_buOi z1x>O9AGG|2$*c!?aI7-jcdJ3vuUDM@+nMcy2|(VPVB->n610b})O&hC^rUg(p{X-F zSZ^OVup*sBu=I2^u)I7< zprLJMR9a;`eNC_Pb6;HXae~3W5TEBX&5RiETqkv6x7!%j`{ofoJ#?6tN~#rpg`B23 zm1w|)u8v#+2P0@E8c`WT1!C!4W>8KV=^!PzC(LQca;;mXaaXJiBx;~>*{{+?;9P|B z-;aU%5l|UDq~RTyZuc$&!WXn~Zvf8s@4Z-(%#hYyt0yJw7xaXj9s0{+ z!3D8n_1Nv`yYOAkhU4Xl>1qObIDWir0F~zaiUVLtm_>*c5MGkW$p_APmh890%9J{q zUNhO&PC|jSbvnHkGyU0{eH#1S?YUPImM6-xf7Tg{G{ z%4?JKd`gaZ=K_Gw33|*c8J5ZssShI9uLw=MZmW@2j%D6;Kq|_!+}*L|opKtmINMfw zD7F8m5Cwc+9og@a&BU_`R2wuj@31&W+jwwvETJ z_Z?w-@0WwB1iTPNNe$F?#=6It;9M#f}_g?mCziYZx}$e8&~ZKT8i-=xv_J zM<<_yxm;#Ns53}d7Uc~+$NxKdmO7kpe)>Gnw%!EvT9VOTjzM*rJLQ0}x!(RijE!XA zfVx$Lt|dOaF>I%~#qtAmo;68mf=*SrlooiEKPGgeS&gI()lPix^u3rg@S{Pi*yr~W zYY^fkCOR(n0iQ~k52aQ;(BHV1hEhuWGVE|H-*~8zDjM!K$+#R>mKi{*) zK(J0)n(!h>0I(^M7%7Dnp7?{X0Pv9fkOR$~hXd4&euUpS1v#K|xYdR{siI7NaJxrl zs5X{DXJP5*Wl-WrS|sNN&J-8Rw1(fZ)w=31Aj9b&aRSX{;~x5$+<8$4Y}ardJR0Rt!HFxt zOg`$f9Cd@yGVY_pb9~XP_*&Rof|;{`cl`qYNJ7o-$Fd1Z5D#YbSE7XYo)^FCH0Thacdmo5v}a8H;oIxJEIoFMiqC1* zWK#a$#DwDL^cqZ=AlAd*_CfA$hEUu-G7<6jW(|Z>#b{FAm(L6Qr)NJ$FaN9|kWeG~ zLuBku72^D9xfmmUmhaH;I+OC78}iyu<(nIzwPbC1M$N?0Rxl}jSYok0?01gmD~o1Y zs$t^&oWvLxPje#Jg@hR}PqOFf={z_#iz`WE@!9`D$Lz+W$zkv-2P z3n|+1HNMDY%#0x$I>fu0epmdO{NLQ7Nb_wrEU7F``ZGzQp~kPJ<2SSCb&v!412=50 zBWXm*iU{W3A9hB?@GDje;iTPsqjGWj-1x*h96$F2#&&P0>kcIAwvxY}|9e{hGi5Q6 zmy;?DfDAxUX`!7 zNUg@gwOMt0FR!Y-Cppw1Bmo{PJa^pthwb7K+d1?f{zkN*BhxAox!8e?xSplB^l!c# zw%+Iv!V_gmHAd7UZSG_ln72?=K4BKO!MN2Pt!7WZ391L^`k^4yG6vG|z11nl;Aizc zHQrl?rRMscSc>hpbHln-;(|3WoLONtJ9HA#MGKSwaD_v~_7P|)V>0squb zcN~|hlZf|AIvHMJ&wc#f{JFIa8)z&S>Eijjv=YBRWV~SxAyLS3?lebXNN~$3Jg}?J zafEXFproSUK3J5l^Ht>e1keaSev(+04pW3i{Weu6q(gPJX?RD0%T(Ut;`bVbXS7~H z#XK#lZ>Ul#062_B3BObzPMEmgdg`C73h(#w8IhhI$^Qx`3RiJ|Ju>{pdSu;r?@a>9 zZt;fHoU?`Eq3kWhlaax5UBTMDjxUtT!Itg(;4V|Ibs~D+ybO8R{r-+Je0Jw`kpK); zn?>0eh^h$46o{>#?50S3%MvF@Gj^l(#C#pRg=lk__fl((Df~t|J69 z=-*=I_4^k(2%7QDp{$%eMxEOu)ku%Xi7E%BOI~Wi{@`rPbM>I z=l^l%)X)`1ui$O>YG(^fE;X^x5JniuhVpszNv`>umPVP>H@RpspmH?fTtc3N#4pVf zv>TC8+}Z-mW}oAeLdc!K*c5qx9Ee~2peFiNy(w1dJN6mb$4&_wH~89cl^kUYw#mFq zHYyViUc2jm+*)28Xcwe!7+i4muG`?CY|`F-D_K0!^|oh$=U2V7#ibb%*q4mjZ!8xd zz@w3D;kq1ZJr5F~HkPIwOQNJ@7LbXu`&y??OX#9}3w?c9(AeBG=_;a>uyqm7wTiio z-xId-NBCn8MflvFq|vjza$LusDT8XJMyodvqi=;Gg2gZg1#=xWgfE+)jIxl^7u*%P zntA829aj&xU;e6BFO4|H=Tyh2Dz)Q7^6=hz<$(Iu)^j-=q*0sfiW4>a>lc|{4)%N| zbHweh@FRXO;v#?u0F~WgOo*O_bN5Yi${lH*lzZ}WM|xN;*x3lWXN5rn^(WEaJhJhv zgy;5f4qGj;u4dHoRi)6}D9vHXC;Q`3W$Hp1Fy#7eeM3`8Tf4I?h!XYx4QW6V;9*InY<$v46S6bwf!*@EUoH2g6Kf4$}T z^eqH(TSp>8%5+&Cc2wLBoP{D_O^*20Hfr*nvLZgitZiuY()g1zj&AApNrXHk?*+hLe71L4va*@Dx}Nq3*xOU@_cwbrmLF!W z=@;Sy8lLY+X;n?YE0VDC$NWNGk?zpJ`>h13>)vXIKt4M|lP+5;dblbF{ts;i99Xn$ z152z%&y#dRO)LCuP4-`PMs^w*ifCrpWYb+-awT!z(}BabJX;q_BcTa>*^LY&1z&38 zrcavN|DOeDV}N74*)&D+IXiPi>L>Tv=`bba%_y;8Z76F;{TZV`lJz~)H@Ao39~XRn%cFO7rl7Y!onF+S6t=b=_AE%mRgCd9ejXZAmea(2!>G8b zoZtm%YL75$TLNJ5?EZ_u53@vO*c&q>ZKy)(A)NWMt7@gc5=8x#%7AS@(v8> zRyGJMQezmKI(W3B87!zJS?=I{y}GPeaL;QjEU^q1c*1j8bGStdrbVmRC%@9;hPu)( zeh+%tff{L0=q#-(&n$l)kVt2PxY`j3cwpR{`{f!g_g$RzmfF`lIF!EYr&QJ&DxD;7ps{X z>F-4Jt3o~?UEfy-r&Q>b_st)f=gah3MD;5>sy~vLJw3rI4s@7QgP>0? z(VD7$pl+eu1Anv7*)1~+pD>FO>-ye6mVP8yxVMKYq#@6)=g;CnLr=`c#@LfS#_Dr} zoY`$Y$?3oC*UA?Ku>~Vmvy=*GR_VEAQhX%xsnS40;;Z9mVyJ#r+&SkFR{F@cvT9?W z`=muOREkV!OakV1a7sX%hr`+wksOM=nwC9IM>9c>Kv6LD7W~^_=yG&?CEatOCe@`rF&K0s6*kde~ijARZ0gbJ^*`)(rCYQsS z<#6pO4s-nr=R{brOe^}+(q)ae?fxc4Lqa@V&;5hk`CUl+6XnRh_AuId6>1d`lMF_9dJ!_p9 zx&7+>#kW-pUl*~Xr4$UVU*r`<(?^8N=na6wIl$Ti^+!q0;gsLir2K14UwR>UqzhMS z>IllDuq4$rNg(1z>-&nRojK?Fxo^AaDCM2qT1<Ve*J4>7tU|(%t2V@7%)7LFxVakBFvF1_4A#e)q^Q?T94#sZ}womqGW~9?sfuP zF5b8s|1ceU(~Zzvla4rQ-5AFy1RU7cWZS&T#Ryk+8lF3_Iv8R1Q`%-ia}iD+5jFn? zxiCZbljQ}jGAb0W53&!Zwx-rN*E8Si@>oX>V`*H{8&y9q)ubFpTCB!!k-GEj2QabG z!Z^dwA8HmZDdPrJlEAQ%l+$Zuyt*Pa*fPWk!@eZic=U(P@`m4~R0{5I zP+cromrJ?|)_D9zj4AVgb^NUad>s<*N|Ub+GKq~h#!)}$Ra=va9fKt-V`J-=-;APL z7ma6>6s;vxc6+R4vQ3E?J^DvTJ`L9DujOUR3Hh)epo?AF42qDq_*F-4J z2}D`cq89C_hHdR*$u_@zrT?haA9trS0O=qIQE|sRe7g znU-VnmKMi-Z{0rH#%802c#62si0u@+H~uCMExUI#6PSwXuM@P>&^l{uEB2^Dt4M}F zyZw720nU^Puev5Hfv-mVPek2r>4e{Nc%l)8-h>%_SXu1GD!mPKQFu-H#M}h|O#;^?C4woQ1rW%WXE; zAW|nXF=)A#k&z0KXc~XrR=s6EH4mz#VKF5zKG?MPU|THcQhay^zcfvFLKw30`}eO9 zJuJ*&acOCMDiJaaB>};`-JMVXlOBCLvHP{kl+rsb+qcajfGRuNWaPkI+EI%?xPW3y z3=4Uq;_vhYHa{6h1qTRjFJ1WcEqa7JBJr*7R^eWX97dvPPFwFc>u<#0Y3LGJ`si=+ zN+|r`>1|oHV^@h4!a?&n>@Vsl2@yHw*Q}d|b@s~0jJ{WRy1M7mdJ^M!DJ%#wQ5Z)$ zK(!+Dq*?KNci1B5k36r9PH-q>kt^uUKIPzl?w16WGs_56heX>q;#RPS_pM|sR}55+ zhVc8JBo}{p@N6KzlO9Q+tYEdjphKGEJg>E3Mf+wJaPYe^gRJ=8k??5dPVLs+X&jY! z{hHM_61Vpvj)H)Sl9w#r=)AT{uqdEsSA5u1yRJF@D;#O#tE(BkTPro2{N&41RnO0d zMuJnfuiwb|KXm*^Ns7~_=6x7GYP@QZga~^YkTNvjh5RG*ViF@fxFN!!F_u=UrDXXt zkR8!yCiZWEdvS}`YBp7RC|#1{D1o$6;3nP$Dx+uD@H$%bkGBa`MLd3=R}lR|Vh~D4 zrPPXtwf8Hhkyt2<#~J+314}mrGmZAmI7|fh)jKG7(t`n;z z`>+ACsCBFCw${~48|QO4YvL4}Y{VKwSAAmVJejl z*Sd6JMjT4Zi|?RG2tJAkJ}T@c?6I1cQGhm9_cHC^7)_eUfHrrYrRZ6pqbpf1_CE8DWdS`Zt}$MC8rJC;27 ziKRCSiD)vlh#j|2QXrZYzh8kfFD3j!o~GO2_z$L_@#;acUTeSS#IzLXuICdUt8Ul! z>=vY1VmUGsW{jj<1nU~pAZk`>UJNfQ_pY>93DzZq!{*doFk|WBktfWc1hMA4 z8!vAj8<|=r_vjwN*>g{{HbyVgkK^N!wI-Sg3f|23Um_Cdm?!xmaA9H87zR+*)2Fb} z^cFV>I`PhGZAK0^@yOFqoffS$4|3J7|7!nckO}fuX0rhoD5OY~M3~Gbf2Wn;`64q` zAdzC?-#wS zar($tjSP?`O6aPgK@jOrsXzI@u_+Qsqa)%pna;8nyW55CW!B?(mDUriN`_IgtUkB} zR*h|5FSEK^KtIU18(9k6exZa<4gl|!Uq-A#>|Cro@W+w7c=%~S8a0?~29yR{L7{jL z-uRXW9&fhlTgKfBJk$nfqAs{F_Kex|oO-*$gW&i^cS*LUBVWTBa;_gi`|nN$6AgoLsOYHg@>!FWro&KsTcx8<&Vg>J?-?9n+ zc6-$NC|FJ;F0g{88Q4o%S^=dY!vUG5gN0VA$8V)p2Gt0sU&_v|p96b3(!aDb;PKNC z5vQTDg}ci0-HR&n_##zUnYp-9cwF()<+3YEUH2Q|hE*!K z5+1yV&Kg$G2XJqGF@7kZK{4T2`&_w=)X0%Um%mFx^+qh*?6av7)NsFd>eu-{J!`%REX9Sny_!cO=8&D;6Q#~MwIZQmHw!@}G$RX`6Z5}{r)Zn5 zSffUogVy5`oHSeibSLT4&mFnaKFC9wJK_r=Du#|apoUP3$2{kj15zs zIV|gURx_|0)D5p;n6khY49GB)9*t)jy4x|ynhji(wrnLTmV|XbIr*;l&#td#?hVDh zFsme^_~5;k6*h(Ikl%aUX#eppL@sl|WzTF7 z?i~QffZau}1Q;r#_1x&NYBE*dE$&l2x?8)arND zU7I!(dr-oOgQn!#BPd8d9-AN#k2X-XQNS$}m`BX!L$G;))bwGGq!w3sb+GBMOQ-Q2n4OnNPI;>9^Y+sxz+#5-=+E(NrNCh=bU!lzs z#7=uIJO{qmMF%=|KWV4xSN=4SP);~WO;FF`)AXMhF{Gr|BohNuZrh;G0{ zknV$gLZ#RI2RWi$TAc=hx#wwUKvCy}V#qVF$C6>IC1mI%a|#k$rlLz-dlj8+1&hIe zeLEu=&j%Im&hXt!G4W%ZbQOEHtJrF|^VdjG4E!`mGH8fny$&I`O&~hdzK5XmlWx*_=EzCz(3-077!zB#mP-HqR%|By5l*0t%2}N@4VK zBT0Dcko?PIbN09c4+8E=j0smhT1E*m4fR%4&9j8$Tmi$uuFsOH4!!NDVdf23WMod2qP*dbpLLw?Rc?PrWAkBRVLumGY2 zxWlhb87n+66emm=vv)hD>i71n12im(3K@=Hj3(^7)vPsC6U*7IT^`MwVWRMS*5$lz zy@CC5?1sOOcZ+U)Q2Us45h8=TR*qUqlW8PpxVc^T{y8KCbX84?SFvjt$lk|=WS5~~>1P8KF3`-yEgupYOcek+sPyR7KVYoHB{EAG zxI<%4VS|&?m+{k!R0R$V?Co=D=^G+N8SE{Jq%ex`7(c~r%kAxpcN0Q^#?cFUpwhK5 z3%;srj`O}db31vzuhR_o2~TPtH?W?pq=N{Em@_5Ul`3lQc++USprTlu; zy}P}V21y(z8pg4URcU&cy2(RyG_z5WSM*Otutq*G*QqYeh6y&FnysD+=d4ixNcW(x z3#FO;kF$F=QDGWs0dZCF$GPCk+qVpL-l|Ge_VKEUqxt4{gZc?@;DXq{?>`LX>#7Tp z(~w+HwkUi8*9>g5;be7-h?#0;I#@CvLS<1UJJ0)qiYM4Q+Cj8a8uOj%l&3h>kJ>^E ziFKN0LlhpP(Rp9BYGxRpyM%x9*T4JCp?sHiM3%mh)$1OkeI|de3i>$WRgCEwhx?kD z@aE6Qwdeo+axPy|exc~73x;xSl9iIUoFiaHlKCd=f7q(siGutsb2OZ#)&nsCJM9%HJ}rmBXOTBiUw zfAt&2Iqr}VP;JwDGBtIT&%Kcvz#ZA6Al{O0I>QzNaK0%yo#W| z+?|vW>1cUY)**8iy3x-xs9X5Gg9a1! z5@B6Qh=0+rdec0)kK)vcF*@vJoETCSOr<|fR^ntgd8Wn^hX}6CbK1r-MQ(}N>r!GX zZ?P`3q0c3zafRo;hHN7&W~3lap_~SyRCeO{FISfddux}Jqt)4|wJr0t8WEqzb{{Y8cG@d<=A z!XaQTg_$+_=PFp|D_tP8^5VU5AT~Lp`()mH2)|)3#c0MW133*}D0j)*VeYzoiF*e$ z<~&K29UX~E+^rXx(75Wx8^6=Cw(g}~L0RB}>2^^Bd=!8RXb^x|hD$m^hHLl50DsIk zm*2*rx8Q(n#+(c`+rabu*2TCGz*;+Z?&Sl)*V@=RI3TdLMejn(o$yL9Dxl7yaBcg9 z4!8JeXb~>;6UC0p4*S8UMY6%BWu)qbavUA_J%{6JtpD7I4SVxqwoL*YlvoXPg&RIe(m4!)BK|C#qTWUN0mLn1P!3piF!w$>s$f^e?Sx-23b zgs!#)YKPgcxG2h~@yy*WV$-UUv7sr+q4ib6Xk~BhcvlpEMhRC;j~=K~D&TBIfRs2p z#w;i7BR1t5Ird}z_arZ0e%{{Sb+>|z@jEfR0cPK>s2G!R-{;#Smudiq)ZDN7SC5tK zObK~DX8nNb#^cC2oMSA}%b;7&sb?Suhz>Laeo+cD8IvUbV%4)z8%2>>0AW~2gC~Ak zNf9@X&nYJ~X0r6`U>4eU#A@I+o5k+KV6+wxzwQ#$T2)SOQ7upoS1tQ)=;i{iHDLWZ z_*WgNEuw3LZ*4>ei}s_d<}=6UXPg;`Xa;YGv;GYHz*w?BN#=njUT?KwxlapWo8^yQ z{**FhB%UYUG({h$3+S7XNiX*W#czjtYUtl^LKGN=bFe7*CG5)Z&T&jW5) z^dL8wpG9XWBr&!p=6Y7u;7^X@ZZ+X$jhOt@fFx#Z;qGA9m_s%#l1YPdH^1>(2R{T9 z30GWbP`Kl2a{-^eL{KD2n&A0|^|DFi@GTGM5=%&nT%_j~roJx8VqXXb9|Z8GZz&1M zM#R7H`r%X?W>wM0R}vLiRWs#|dYL;*=+K>5mrQ za)s4vX!<`n{7)OFt8hx9^3ns;8nEo)2{k|>&_c(MZ219o>anb2CPmB2W)nzHi4X3S z|4k)}`w}iJJ+eu-elG~8s>J!T{5$qiWb{3V>eCx&ob*NdhSYB_*BK*Y2!|+)ISdn_ zkbS^t9YAe(e0#7a{&n?j#&D?$Oq$9KHAK&|p^%7kz5n3Dy9(xClUo7Ei(go7wldV2 zvu}*bP8~WSX?=e6tw#L=k<>tYL@$F;OL&DVBf_5>Laj*}XLQi0jB*Kk+>ye*EJy}S z)DxP)xs*scMDbn|FN0;of8Sa7<8)$cv#;AgRT+6zT9iVn4I;+_x($l!@pf3metCf@ z$8m@+TV`MC7ZY09d7!)yIXLQ)AiJ)WH{^nv zDV{z5*Bf1dyhwT_{Dib0wJP75OkPVec4peD>VG2CmWBeZntr|<%%>`J$%InBxc_GX zqVQ;=;3O-!;+dx(bQGtz#qruisWK%w+bV#D| zwe@JP3@~v&3muCc^*w3Qh?&1y`s4e-*q4#!PiY{fQX?$_SQoXtSHL(-8f#W;svxX6 z&!MTZ84wuzD=^8p1Vo0Nnmk;%#dv4mkGX6@Y|Q1C+4RU>Bkg*@!+R8Yie$6@#tdp{;Nw+-C1YXKFaC&vdL&7 zxn3afPprKQ$9Ucvs>dT5sU};DL!vg8jnhmF|K3=P?wJI!|H$0O;valYpV&Ti|i(wsW)oq-J+*m7ZqOiopwnL&l;xW<(;R zOv-S=fc~JR3PbET_fR_n7tWZ!%lyb5H+9lI6O&fh*8ad!s(5YZHtxswpV3K0*UdIS zf5ofnR!+`oD;{JZ6#ck47%JE9CP@7fe}Wngiz+t3s4NOLCMckDW6E@=O@v|_*POe{ z6vJ}@(y?pj`+NSBPqYK0Rkq^pO3Km$T%d#QIUfb+VQe|6+M2<1+oDOdl7Lz9*B}dg zEd*O55K9eLGy;Z+yo?knnnB9z=i6)F4fLhV>>G-rwG%Z}?qkmP^F-&Sy6BszergdiM2K-Y|_AmMl8uJ%H$f~M)Tisu-8|_Ou>>xX3D>KpZ z!l~FX62!W5p`F>oO1U z6?5e2IXO#Tk{pp_`6vczI(IdXJ%$=M4k)(@5ws#s$Nl9*y|0UevlM+`4GgE25n$5z z%li$Upg4eH6}6{6m$mn(K*xZ+eQO9(`L8BJjp=pBSTLtVWMi*}w8~~|EQ6%G{_AZw z<;9U)`$IRac z15&}5uQ*xAY5PrIcJ#x-l0xI7GsmCZFHUDoWm7LPRhGw8j3V3$$K_!M>hVd+)I7$t zPV=ug*Ids4sOWq5Y1aDK*|U^YlY{Hs4?(n8Dh4xhy%#+MC<#6RyJm{2j3WPhp@I0% z?Lt=q>yal<6L1?|ex*Z4>&f#6`#aL@L{o|&5u})&-QK7=>ofQbITdi`mY-kO#@nPP z*5y^mcN5$PUTT(~&0e}Mlhx~-1kZ_y(r@N@I&2p-?gGyT;AMN{@5m>Fy(Z>kTOVK{ zl9^B(aq)CPjDo-)SL(zNxm`Xq0Yf!~1+%42ud$}HPP%vAbBY0vlQlZzmU^N*<(#kv zQg$*brq)E7r%gOBKb3X@EP7O897&IXjoKE9P(0t^82DqQBy{*FBtbM;AZ+$nAXlo* z0>KF2%8~a{?zX5~ppc$$u6dco3%G`(QuHdh+x&{$bl^h zfCbns`VW`RMQ1&=WOJtn87F9F{Vx5>Vb8#{@|_$DCOO*D%X@EJ@sznI2)u+JDkC|^ z)WpRs#0e?ikeas~7?fk%81Mj)1Y#(#tIOg(T_3DhHv_arY^5roSr_Be+7&7SggU=h z|MQ2)xpfg)V*)I`NMNM zZiqyEmsPSv{Y>xveSXDPxg0-Pv8Wzx9aDu~`XoT=d7tKf|KkYqf-`{CdX&Fm$`Hn! zj7CvVlVJc_ERprrb-Y>oJa3+fn~6Dl=401rwtD>P^PJq%+Z7(XGJxOG_5Pm(|(HR*%&fJc!OL| zQq=LVNI)4Eq8^M3flnP62h41(1ef7iySo$=Hz>KMADfb~zc62IPCj$%d+MD$U@&Ka zGv~GP*LaFZFsMK(j8U>;fsU7tkFBm$DRfzNJvAbt!?Lq)jr*SFS{cR2^>)EJb}z8< zGZr#!1|A*yW6~4+#P$bqULXonz>#Xles=bEO&EE-N+F}%wao_$DQie({fhSL4{1QH zwb9}c(92$`d4kE(vf+*XkC}IZR+0!H4rG89WO(^qFT+lh&U185dcmP}r~o{G9O`X^F;4EP_Ho|G6zCvS_67b~sEyYvP$IxQzT z+1l+5$X&a~gM=+U{#?JZREN4nh3UAt2!J}}w`P7pn$+K$!5n0?u+ZdD6D^b}18@?c zD#7oc9*j*AC_{r&3wtcTTXS*@=h{unC`+Y8eq@nG^2k2<+C1T0fRLXI{?C%<_h)h< zA0(>Sg?UUgVo^{bGBW0fZO2FHZTtE9DpJtl0g^$n6^{xtfvqwm z)t|-LR!TW~+VCgOWlXWuW9gc2z#?xdEbNz)^!|ANGpEYiadwLcVB=_cKz`Otec@#7 z%yeLL#i1YxtNutouy8DJa6Hbc$JgFn?s*hzgR39@(E--(y2?B#?1f1Y$!uv{xLPV% zRR8c^qlkhnZd(KrSvjV(&*4Vp2;CV*6`uN!TILv`_#dUo(nsvhmVvbB-^Keq`gQ2+ z=QZ|=+GZV#5ud(<`=_FUYC3`C7*#GC9g=oaA^Kwba;c%ZMCRqGg~cm0oH*RF2`pdV zf|ibJKxd}?Yy)NzL9~^WNs4Ex>}O!zT*^wq3oC_#JX`1es^|7W_OnO=jS*Ev>Yzfo3~z+8T7|pVg5VB(Z-eE)a}g#-!{9Tq7y=MI5bYZukosEXzTYe1*rXqd|D)4} z8X|B2JeF8es&DTV>>`?!(h2Dwd<1ZOM1IzdJwXy}qD&XK3&@~fwv&5RUZs!QaX0XB z-Tr>}$K?@8joWi+U+QY>#~T7da5sIv5~%I30B(O1}xgO(V#3B1j%GHkO!L8 zmjWJ95zPw!NMI$ObgI@6A}#+B7&5JHBp+g&66 z$QIE$Q+|JA7V{0!?z!P-w&U<)1T6XElIc{`H#3T)@K_3h2aDGm3waN9W-j^uX*1eQZU8?GbUxi)ulb022tWyVwYu#xS|{!d=Y$)g z>CJDaDkb_aUo^d6Yd|>q`#ghRzDBYdZxFK-Vd!&%pU$nzc-7g^Kv5JkYFI>jsn{A>odWR(;e2LSQltW(#vFJNso z9H7?V`q@`>p(^(GC)TrT8+Yk@y4f72O}h$n2NYgdLUB(VqiPw zeFro0D{k6TK8PMhMh!F;#zW@HPYPw5%dQP@??YVpg>@5aSyyACjp%!V?K=pAl~7-IbTxA#Ylk! zV4mqptzB4HsZ8jfg^?+qan3^0&xWM@Vq5lw{`@)$Zk^EY?>5Q}J$ItiGh~9&XD!>TpMzXf+gEs=KGo&S72wxX9d# zOn+amy*HB}^LnpmoI)GUbg?q@cB?3Wffw9Vai3qwS0M}!L7v;Cpk?-aK1kIUbU*a^ zMrn{jUw+6Ay!nwVCxrx5x!;L;YhBijrw3@U1#B#G!}K5e1BXzrJAbo#au%;LuBo)q zS&t1~ma1?x$V(xSq;0qOpp7R`F#$&ElZ(_(b^kZuj*XWT(pdr-z^(7fnIj!7Km-l% zz9!M|^F5na&^9nDaaaw335Dbn@E z9Ml{x`Wy%yw0cyZAQZ3YA4@Nn0Q93$rZ+87*(vhPVLHabTK;BgPE{pceD9M&GIW;+ zFaC$7cMh(sZMuhJ+cxjmwkNi2CzFY7YhrU^+t$RHiETR*>)UzW?^iWdshWSz+1I&t z_v*D)ck5Y-l40+x60d=R7;wQxfel60Kdnw6lPT~vj(7of9QN#!2Tf|ZvdQJ9UIK#4 zYNd=pbMi`l$f+Z*U>U4)R4uR6+neiW;FF1X>4gu7*pr)VKmgqHI;8PHAvn3sc)Uz?SS-s4H2R(hbO@X#{|hlk{3p0tVXlnnUab^Wu!i z10&g^4ZH<^%W!7hSMSA!BRiyC{RXXzoR+K*6aaegv?4*E{r>eeUG=F@1D4{-tJXPW z$-`HL3+PMn2NefaEZ;e==_*P|1#4=`KE^#wtlK40xPUBXui0 z<9pk{f4fK*H!Ra@{c8!xMbz%Vq4f;(BLV8zVqiDH_X4K$a!AZNrj`GBUBE%CY}*di z%|zMb$uadO(KUdMN2n9Z{+)VdXH8g$)&+*phO0%o4bWQHy{D&BC2{W>_Y8_Yt@vlSIF6}r_z0y`u7Q2a{i*(?Q0*|$eZ6} zG`i|_SvgjMTku%n!fx%3&%LjAq&|Wf7ZL1?{?~$mI}Vs5(9M8HW~XkWAuqRDjryC% zwU>;RJST95fRo+0O)9phL1&d!WTBAK5TiH4<{VaQmgml2;LH_QH{)|Knz2EO@E8H6 zOi$2K$8m1nsJrbyxO#Z zI@J6!gSpDB*_dHg+~x}^5CIU_WBaY$$^Gql72d0mUh4BTeMATL9tMDIn9L4AI_1Mk zM>zlf1DE4vv!mk6cEj#6K(j-l%M7)Q07fNGod+n}hPjEyCvG|e_O011R)mEB#hvl0 zR$$FSx&n0s4reUJqw>;Y~c1kdF&;s`R;Hbn}Nd~P^stO+O~42%c|7wQl)(dmQ`AyG^W z1G;}ZY7nPvL71U`{ytRweL3IlI!Et)%*+tz`S1CZa!%_Twz4RK#+GgXaIjib=U-g` zGL{l+l&b?=@IezhVGCyAz+pMegEXr|?=WIYdN0hz;u_5>K;0r?cM+l4F?#>TS*E7| zS`m=4;5C8~0qgVX9PIybiN^W%5WyEkO82(;ggL;O2X5?Y{bx?^G6xg%#1KC9vRFSd z$TVM}=*w@ovIQvU3;Eh;KtsznnBXRPLpT@{PawyZ{9c_3Gz0ST-d8-3o~9cgaQXTX zfOrNJQ;N(Ri)9?Zs&cVRvKygZ+^X6&{g|Pcv1-ealYZ=x5abf@(cwI4)3d8OUy0f4|y_wPJ(!Ak7TQr{1^ zZWpl~z_wRn?CaHV%*QAZ9^BV^&c9OL1-A;s+xveP5PXVe} zQA$2pbWrx^>q#8LW-?+wYu7Qnc+YK(q0c9I*~>qGOKV@@{ zF}ch(Vp4y-kLa*oh0x@9?-9l zWP#NL^gdwzE*DQ;1Aj;XtO|i^2N{0}lLk$L2M+)tIbhHnSk@CTc=ukn(!+|6m4$ho zj%o7FFQanZAprE=Tzg4-tmppTGVs^{R)6<+cIM=!UlR2`(~(?nDC zZ@~n*XdRZ`(y!Z6r-38;@pWW0QQGGH)3x?Pw&7dgARgHU+5VM8$bQ~uReQbv-e0eU zm~w8RpXBlbz=87zY{I_3T{Y!(YJ0^j>7%CxMg&qw+wn4gU;%mPmM;veW=iCno5$Jc zLa@#c{oa(Jl@~2aH~wb9DxiO4^kSG=s5i=%IAn7l8%eW zaDzqc1C7^N_g;URp#lRt_uCtA&n~gRfW-^`p11zTj>0IlL4_FUD;Z4Rk~2v2AtLS| zByzADwXeeX1}BDw=)1XL4gC)aoQV9CaWoaSEw_Brlxsorrp9A=O=|U!)QSATG49pM zUZ1D^eOY!Z6#;!w$nPypgy+}CrIh8Yv-XMq$CSUf*)&Dv+DX5zlp-`{9k>gA3`6@Q z&^KXC*9U0u7D6?e_&^KAUhM&+L0&jZ|8`u+GghaD zLo&M(lyMWQbwtUG=`S+qWOIK;4Xo&NsR%G4CQq5hG%f%rowi-YOcO94kU_3g4xEkf z;m0T#KqP1VKs=*k`Ctv=L7^VjF;&lyz zS@TJV6qV>)uV4Pw#ehg1X}!ZF3+Om{epg|i&r9(FA^!@;T!q;BS&awzYV}}F#V|oc z*oPV;ybE2U?gsy{{r}<-5763!H}j+DvKuglvsumti;(OBL_I!bR)bO(t&bpNs|(=l zeQBvb^5XOL<}Pxt_3)Ip^%(et}}Fq;nPf~6oul+N^Ixhn;u}!4P)Nn z*{=t#>)acmd~jL& zIbM||{i5yahro0!tiK`{)sM7AoH%6+@Yo`=ubIe6ml{URJAr>F(aXREhvS4kcMNbpPQpp#xQ;(V%MVzfHZEc* z8obSb_uvz=DarqwpQ`fSzvL{W=Z8qnpA|;oo-wJ%OZ~Pjg~F}a`uj(*3%^N!hud;Q z;2{fua0+{^5!{koK4P&@m}Gj8j6AU~q%x9#!;(823#POMb`pKmpdv*l)oY2}Yc3W( zixSg&<*mJy=PeW(=}QJwp2z{zh`z_`feWZPds9A1hPAnkjQJw6I#F$2m1<7f`5$(( zlwuY^$)#A(vJ}B0XvVxRv45bXNDv(`ec)K(g90RyyP-EhA>^?iQrM0H;>Y!EdqP9Y z9Nxq8|1?I7@5gb9zmuSnJBkk{t{41Q;r$}0^uwIt!`2pXY=}plj<`p>et=#bsTCXD z#|_lAP0;F&TpvCP7ILKr)B|LU5~~~&jm>vGgN979h|}M9((HFVdTp5LTy8w?26Fr22<}3*Z@;9-3A=K_c=HN@a}6Ac2%ng(3K|LI+8~MgB8?QuqQ925Ym)H zBby6YlH*h`3`{yU3_p)d6G|jDH3>3ZGMC}6@uaA%&D znC%*}zZYbl|5%sQ^~1|OC3+Q`^*U(4J)fosO=0cKm76EfW3$FIjD=1u{G2KZV?st3 zx#U_zZ1W2#)*WY~e4@P2jI&7R!NxaZ{+z1jLX3=LP6^M;G$=v?V2+aQOHug{U2s|a;dDJqNLcf0%oP8Q zZn+Y9U{BjZsofCPOrugL9qH`|whj{L?e8A|0qeX?U>9)o?)7|+*6(s9K;?PmJMEHt zbjPxL3N7E}*5Iivr7V$-;JL<{;BLvwPATKC;I{M_UsPR#4a~Snq8p$O3pN0cHc%`{{C0sLXhaPNm^X zn|(MI7s4d%-8i0$ZnpgBdKjQBZBds%?ReMOfGrv9!H6c|B@Cf5liZuwdXxi*)+i<1 zDA`73y-dWvlk|8q?)eYFRP2VABeu|rJNu3`+=M`16Zsa@z9wy8f%zq=C0!P1l}w_&xtjUILiCND&IzE(FZk;#%!J@4}b>oAJ;J`I6Je zy;pk4#V^i-0#=OkG2tD3ZK7sdO=@w`EEL#__x6^ffleWw@Dt|&ckra|dTeS->I9p$ z3k&IxI}j(LZWe!XWBlQ;(HC$sDCqxQ|(%E6qA~uw9nfa zQ?}@o_db>BcK*_WhB&BSvNp6a@MAld$7O9nL9qNclf;&R)W7Lf4x z9FA`D>;Y%`7~~u4WCKTjKj19eBi9hf9eGD_GStXPZO_Ta195ZV&R?*e43MmY!E>68 z*k(tOK;*Vs6qbo?mUdy}LqfIh3US;6dDq$ENxFW6Wh>xOnMfhpHR^uwK;L{nX{T)7 zJvl?KyZcKywzAtT{`cFtM+44LoL(!(dSYKO!4eXIaF-G>G36)z_mq8Cjn8ab>MZ9a z%Savaw7N4ke5W%}>JB_;P!RdF4yTh1Njop@_~R$3-X8D`E@+lzktcGr0!F5qRb72e z=Vhj#`}P!^x#h8Ar*OB;ze$qwdA0-d%j_Vwo>Dsx1R-?YTk9tkbOp7Z$CYa&DKgh{ zbj@m`k$tWIHFpH}=NgksK$H<|nf#rnVVoK`l!8OSJINCEWpTE)@wv>qlzMFjb}wNQ z7v1Rbt=8U~tWm?)zuwV7ld)4WXVbjsU)<_(IxWO(v+3y5fDoji{bj*k}Bid zw0rdYu0Vge)d`u2cJ)M>@O?=Y|9U!pMumVhRj_Cw1CwDOKf@xT>$8)IV%=O&zDA6E2ppAVrYe1zPBJVl;^?I=OGw>;stPVQBAL1;C z$9?pqEF>?!f8vBlbnnfL(1PP_bq?z zw0e=%CHB`49CO}l?<2XpZ3lJfK0B^NqL?=o%Qx?q~Qj$rkFs`qs3M@I^$kQrIpw%3`(dBv^+d5}B{>K*QFR zE$#^23}LIPd*V1{+mURR(-Ad%|FiMHQ5>|3^Xa2zdBd69xU>EB{|?d_b53h8|CL{i zfK*i)H4im3@Im><{9~<$5U*$sN+k98<;d0Ny)VdO=!5JQPgL(yKXrZ#thA=(ku!@rlr0&DyJEsjw(tZ+}7CvXV z`qfyZSB!l;_5io_KxA43#so;+`3paL>@V1iIY0a|CX2s=&toguVM1@C&ObJB`V@OT zAv$~O<`^dwlGLHES9M^_d zpLCzK2n%ph!etTD+E#+nMm}Zq1df(VZD7|7 z?5Ah-d6lv}2Xpl-Lx}fx!S17o15Qg+oaaElp$qo6)dw?eS+}l004LCKO(#J_x9Mat@_!P;;XzqriWgUa{{@dDk`|3#iRZX2@>SP z`I`r1sOXiTFnETx8)vr3Ml5MFw0-+&@%}}-yp-^q8s<-5Y}xK;$&H(#Q;sBQLQ4zh}6$b-oGL|AqMyWz=V z^(%Ek?8tTb45Tc@Zh5WZ=JNZ+P=zMLGcifjjQ!$Xv=W5=jl8W0q4zh_(co%+7*tgHZo0Fzl^X?t=52fo0ZM-mNZE+IG8eBd$=_D`Bf zy1-E{@30IZ|Hj~=7yZS#AQbaIgW)}Ub-vqEnD26jxBS!!ZtW8k;#wJ z)i;?Vq%e~tCuPH1Qbff^T`Q|#`0Ri#88Y%nCcHte5NY$q!7J!{<~p78aOfv}>aTSe z23K@M)FK6o+apnxcD^&^bytmsxo8h8V)C-mK_G5qJ3);g)2`JBqFVj+;;iG98OE}1 zzJYS)NZ3z^ocNouCWK55N!}UFCZ0>APAiSZq6m^}y2W(ka9$nV647#1=4=t&muSNw z-v4@(VlTNqF(F*q`JyNb2fxK3t86cYW-}wY%;U5AYR?mi{2eOY!h(g(I>`A?NcL3E z(zq+6!GNjPWW+Q+cpNnNHn*O@nm@f&*2#EuB@%3*2TH)!yot^v7(#H;aoc8mrqISf z`%*37zr$%{vk<40^s+O?&nl7B5#c|bX<8xDgUIIRayqY*q5=*iVdHoFF+-trQ_|uB zcSK@4o__Ap^}Ns~ay5Ef--o1+BnD&mnzU|V=}OAm*l#vpDQ!0a7*FOK<8EaP$-^8DaBu}kv`&3G%y?P<|-KVE&GzMVk`G)_HVvk*;V;+>^)zw*sznI zrcpc9YRYy%DNpX-Mk4`ko;mj)VgGv4=V$Wwdt43JKB8qb3NFpqN=B;>s4tdYP--K| z{>T`{KJNi2e?^cji@xo_7`}n4goKsK?l+6&hX!#Wt;+$6Wgadh!0x&o>EFHTl%QKEG-OTa*xuh(ec3xR5b1 za~RD~QB3^EbTbY#R+GdAMo?VH-z}diF6fB~sPO9a?V;ag<&if(=CldcU-`P+uEh$& zaPSTZH>I~})i`hvK7)&1UN`6*!HWe~ezxEv9Syvh|hGo5~OHg4LY&#`tS$ri>Nsr#((!p_hw@X*bK#4+$r9=PMr>8u6Mj-d>dbhMnAX66pB z3Gs{Z_jI(pS*J0M89gQB$RLYh49_ISF|~8ymwVpiX8&AD9HU6b$&q}N-<9aV>&v}} zM7~-{`R7DFQ3p-T{!Y*+*{GS4u*+8HbSDZ;C0X^KuSF8%$cgs8gLAhykGm}%m1N1> z8o%AE-J+G`V7uGCg;jU?K#h*Bni;{P{JM!N)Upx(eaM>FgdX<9e6443!jNFc76+01xE52xYpVsB&K7=O7*l5S|{hcNcp=F9T|2i zu&3@RjSwK>1}bPft0M*=QkW8djxg3UDI763$tFT)^k`$Qb?>Z%QvYPa*#uHTX- zSGYWA=xTVu658JcpBC)f?jl8zXU1}uKJPGWKAMa+)7&kg1YhGXW%K>6CuF(bUn|}6 zBt}Av-)rP{-6=q>(LQAI%oib}h3)!#()(*rOG-yrWGfdr_<6f$200ACIX2l%Rp> z$bvG8Gb@Pb?_g=BqC%k)#Pg;;^WM{%HM1(M9Z(mMX*Juse2A5CH(ZR}vVw+j@OF(^ z->Mbtu|hO+5a-DY2b8~S5=FTmc2Fu2pKxuztWfFZ*T5tdd1x{!=lk)oh9X$E%PhY* z!QOIOiV`(6$y9y(!1sG|uSH{+EuoIVv7&BrJ~Z;^DWT8*Z-H|O(fMrAQL1++5&lpc5WOm8#gs!Oiyg?fR9{szX^rt2qJI^{CYfl;3@kszDaQ}OB++DY=Tw38UJSFSR zj|lkaG-5t1ub?GI`X>3BdWGwp3cN^yFJcUSPxnIG*fU*$bfXRf?A_m0JxNHwyeGa@ z)J{@9;DCxzUW{@N(juD58}as42zb96nWG?|jF@Cm#F7NSz}!?g9dITI1YFCJA>|0|91 z^y|)oc(sNJVlXmOYv1li4eSp}4_M@>7mQ-F(V}}Vu9r1ztid3Oq53@En79|eH*E>p z;@e*)Dr!0t1n`TvA`szj5l%Ex-19PRcjA4+?s(Vzp9Vvd5c<}!{NiCiwh0NL%ZEuK zr;r2nj6hJyfIusUP{|l-G0MweEj)ZeoXo_Mi)RllmzItnshUFoP)Ny$B{L6atjr(= z)jx+-9u$dck-nR6)t5wz9(Zj|ea5KE@~kZn9&DGN(bY;JKPLh@g1vj%U;!ut{N_;^ zu;>6qLRK`!QB=GpGZig0dha(1e!;`j`+B}$)ZNu)Q%e)IRVZ-TQ)@Tk*z-uewiM;W z6O#mwTzXmJCl@DQWMqp~+z|Tt3l{hSh^py$S%H2|lRIMKxP+kQb`WayiLy;|)~NJg zHF@0<)u}4}9f(52KHzzu)8H_w9Za(>*wh9ca;@r^eJg)~VpSzt!Xx#e4PaouxgCoB z=e~8Zup|gU7`a5~vKxtXrWN^{XC&VLF^f#sq0<#b;S!t9O zwFZGwkdZoHJPw}c*`q1J}O>$U8D1RXPysSMH>vjg@~p9P07t*H)*rPxK#pn zOoh$rEk#a`7$7{JvT(B2S1kVHAF)Os38Q9S<_XG81)apREQpB&nfa>@uT2kjI>UjQ z(n~-3bd6DyVs3%^f2a8|&kPBeZ;MOwn!aS(BWe6$AE!C7oSvg<~WZS#jquQ~_f%#o9%f z@C0|Td`n;dMD4-J>a&00_M75lM*lVB8anYJSX+{r-QoyF8Tzs89su935B+mSyu+Dz z*_{EN2)M!$ULvgDuVzU|$+5K4kcbk7 z0ilM%#)LGgu=3sfnGcQVPc|X!kVAF~^`g2`W7|Zq zMxhl%Mfc?>V(dEl|D)ZfN2|}vW!@cmw)nwyZ|b$q!S!F`?-=0v>v?_hy3XqR*TBAj zF0sOy1}A-?5a(0(k2!z$XDf08{~h)X5rS1~8=m*buXwV`>uN3s0q4Fiyep|a_q*Pj zogQwUZLySufosnGh+2e*OlodgZ-`>8Ru@>R$NhHEY9}HFFQJjMc%RsR?am!BhYYMI2%&RfVWAhL$j6>JtCg&ub55g0|q0$Qu`>Nk(Z|aOswBiepM%*^7@^ z@ZBt}*mOc{I6~Idz*r38Ar;`LTwWj$^2m00OjL*Zkpy&nW^o8pnr0stmUwp`?o>B} zH7jlD&VOnXF$I6IEEG}ssn%-4p?j+S{>UXG7U!9`vX|PHudM)~^z}Mhaj(U-`ukv^ zlgm!%4XBYy(?v*`Xds}m7NI5;it1(h&a&9D7d7?}13xtWUrZwJ69ljp zlRAoC%Mt>Ue`ZOGK4>ox1n8dwS4_<7JnYUwN}>eFS0I~7Psco_Bg<$g<4L|Ewm^EV z-8&?&ryO0k0j0-ns~I`13jr5XtFgsTgCl*2TmgUq@vJF{Y0rVe5h|nQQ`S(CnEwk3 zQfk-}RTY~{cc&LOb__W<1w`8zRVtHq6SNrBefcdk*j_W^(|V(Bl}gok?GQ1tf{pVF zp9g@!2FRIGfB%INpdlg!0Bpa0PrqH@j{Tw9JD!=|8?Vm-326Jlkz2JKJ5+@ezVwr} zcx*L2s4AAW?QtRt7@9v(!>L;b)8==G#krmne~n3?He~@{lh=Om<7C1HKt{W>ty`j( zi|p(7JPaz|ch~)54~}gK`zwXG0|Cm;%UeNXNgbuO;% z)x}La@t`9{A`2rP4Z9)$`c~If&&Gl5&v*0-uNHoZX-UMI+$If9ys!}v+A&}v$^FF0C6`RQUJAi#j%fb!~XX)}4e zXyj`**1SARK5_M~le0(?F$&RyYCaGkaQo{`vt8009tOTood7?6!zs= zwEs4&St%4b+S@+ykTFc3zau))uxAqxbhp^iAP+kMfS#u92|(i)H_bjS^+Gq zCW*gGiSEo5oM#prYL-dwFW-|KsPCHMNEh^cBQPovW~TrZc5LsqUiSHVTD2I{w#c;4K zLfTEI^?*z{pr!$tGPmg*j>sJcFrJLS9Yc(3tyocvouOyp&SaM>I5Z2MSSl>&bVhOf zc$9jIpLvJ#f9r?U<6DV|R6aUr0N6I2njd@1GeThwSB^JDz0c$}o1F(weVE(g=kW7J zlQa=(OhNzN%*4ymw(J2k7>V0tU|6=I+slBDR=(ubA4#*E8OsbO0qflsaBgbEOdvuo zbvjnLdvGKuyx0C<-bnIGCOuLnbbW=(ReRZ+ z_6F=6Bh>8aGKXhzP&a?$qKmVnUTv$I^_R$dag{LCQzbdvsz8^SYSOB;eVr3wXKPM? z9kdUV*pPpho{$fbBOR2^BSOU1F=9ztp!Eu}rPg)2DCXEBE`bwKGokpiL@iA8ANVNf zNoZt*ygDh_e+XgyuyQd9rY2@^d*rIKkBa$sl0v+Wv*wZ-p?d~ZH4MmOw`t%yIU`o6 z5sjxi6-x2*30!(s^$X>;aH<;g-M1W}?lptI9&6VQzrn?>kq4tnJMkNmqirHjrFAO5 z9J$lKcDwuexJevD0GMjgaY9l$-DKs;I~a&#yMGH=W_iH+$&Bh;AY`;}=8t5g;~__# zt-9L0{#Fq_kO(^eKQ92;yo*dqzUlB%Z z#KEYgAD=q`Wx!&fZK!g409YrwQ;k1Cgq~C^`H@N=&ynyaAvMj+8~ULIkr%THT;pgT z_8p@miw5Jn_zfL&Q^&aQM!$X~zozPXY=f{Bh^*z7O99w4x%rKz3JW9_uDxt-sw-~Hk{2fM#Xq|Np?885tY)VUSbjU0Yi zY^rgf>vtv~dR-%IseN4y(}SN6ECsSJ(tjQX(U=;_pkuZbAl0gd0bLpbf()w{HLZu* zjX5{UfI#Auo%Oi8E@z`qC~uk5uXQ%bV~=(gxYe<2{^$x&+HvnQ&*r04=EKJCIcINV zKmkmIFphJ^=Jba-762fnGb!rX7JS&WO_b3HpThkvYQ3$<{<^^;aCc0W);d3!B*jBJ zF1pNe3*_jWwpjv?`TEp8XQo+#yW7XD(W=BA zrl91_L*pL|71GkbUE+5)mC}et{aW63y|uYky5B}IMrn^}h?BP<0&hGLFep^YyEZ2L6M45!#D|`W0OX>L`6$W~7ry)KZ1j&5T zBhOu^Q(bI6`^#0w_hSSEa9PY@0CkYM=J=-$4pWqqW~@ySxv^+zhK!fzc~iAqBTK9C zYswTMu+5wIRjP}N$|~GkDo(HZULYyr4{k&F-E|8opH$XT^#~JG5zn5o9}KI9Ph#!B zk)*2V`NEQ-e&>HDj$gqg0PPz(#@Kw>k!q>s68IV41lB>ky1Sq$9pVjM;p$!-l*X-` z+SlJF`$Vnzm$0^imalNvlVNnHvu1M6)`?k~;!U8n(+-jh;Lh>-(2MtSs;Bf~H&aD) zi!8kSelD3UuY-;{&sLQ#yP`=jOhm8*rL^W{Ui9sz<%E|b2sttm?ihxyCtYSg#TkePpX zf6|xK_-nNl3}{er8)1gD@10!J*K8L+^E+2o*U1uP&RbH!L(}(Z#>B?1xK=F;pvV)p zxlt^an7aQ4aDBcOQRbB%Qjw7kdY3RyqqUB?7#GKLYj==Ydt!9Lkg;RxpG((3wR$HbjI!VcI>zC0D0C<^+i|i0$ToyYf*_#{-Jj@_@$E@e*EoBCo1}Ja?pmY zX`AkbQ*{Lh#~K?yZoM{BXjXGVYc>4QPM?1DPoJdK7FN57sPBFeaQw-F%S497Tnc3O z($OU$Hd&VyK}XFqOUVpt8=Yt~3(3tVHWQC8`m@soIce95P+q<-7qiKI9%KnbmkVod zAwSujw>>|tNQwgYxG60D%s)pC@!|g8B{q3ptqB8-L0)?`D>ETaJPGWeccN@iR3y(cE7op>Q`Sm#ii6F{PR zx~lCC`rF&vQFy)!xG#YB;|6e96uwOK_V0i@7Ci&XyeZ*M*^S#mx0^fUX#)x^(fip2 zQ2k8<4u!I-QRvUkI-S)gqZ-whV?Z`x#%E}$E&7@KFUeKdLZ%@{B^G>DFEKTf&+Q7d zy5A zn5EcE6tWPXxD}F>D;`z%2#jQIC9N$TT}=m^5%@*@Eeh^!>r=cb3+$-}WtjIr9DL{a zXm>!ZS#?Co9CKQ}NR4Lgn!e*O=QyBL-@=sOmvvbWZFMlp_r*|5-srZwb*BW12R)xo zP~bL%$#m~kJp-x}@!Kt$;F~@o`!Gmfqaguxhc; z1V^cTUYBWFu=%&R)iv{E5#FHn^Miq}>T!d{JTt_whXJQw|8IgC@u6h$bKW6MzA>Gl zrFyT-BSmo?W+N1!w2mn_!o0^g_2sY%Foc~LL2zw>W#`7qA#o3F0NIid4zUPu2>6jZ zL;}FWO=eCe{MJ8fmzh_BI3t_?f~-GPRKq@%^>vlCHT0UHli}9@nD8|7dG)vqNKJ13 z2GJ2^2UFnGh-_aLDF#h%r#qi;)e2TXKmm+Mvp^|yW8_uy^WR6chZHWv9OcE2a=oC} z2ZxcCQFu+l$6FWT)R@R*|xrrggz2B&ZVAdm=w37b5dO zs^ojAM3%{e{!Z&h^G1V%Y#Rkc;KdEX#vR#`1)}$>9X8+vk)UwNN>R5@^^T7?)ow0o zb+kRODMcz!ATLV9nfCLNTP5DTp%zKx!!4@UCq`VWj_xi1=Lc1kOjO+Q1bD`~a+_9NAaskiB9jp=3g0ZCB5*TD0n)Uq+xpWMiK$ z*bEozj5>-mNiYxoVwS`T;R7vH+z@~#dnDoYyk|`=`gB1eT*B5Xx_rN-D&FUt#!HsjvIsMc+*+k6KE;7lwnCs*I}+Zms}_qK z$l-_-_oqAKO7UZ~cu>{9bQ4dV(pux*181*M0EyFV1p(U^4+o()e9wmDC&M>PH$|9HRabQ@?rJ-=M&;f=Yxf{Px=_j8 z5<{oAAN#7}fnI=(mdEy2l?06USi>`Ty z3^H*puvPxI);LfmimfIU6p!O183R=GWU_h%JMY}PwzpwHR3rt_YmCOxOYDr%6BCeE zgdG^61M0?Fs}yn|c;R@iu4Y4^q@l0_0k^~+3Gi*e0AD3gNzU!Yq#e=rGAu|19Y zu+(-zt%KNyTZ6&q7Y%)BxY_7Sm{c)f`FSIlDa$D9e-pjFJF)tU~O zZmI9WniFo3rhuVsA)qz8ka~3xQ0gmy+}iKV1YqkkyL=>ixWy@~#0pz|RH{M4w{7$Y z8LG9OD|?~=rq2_7U4}|}@hB$0s~t>ALThb;sv`$^mkAzC(Gr-qRTHS^K_Dl#U?=ir z%m{}{hPCOV{Yjf$33aJ*$U#k>EGI`c#v|ezv*Pgz|GM(e~45Zt@x*o&^Y__SOBi&CVUp`Om{?=wIJRg5+sZ`$ z-2vTWS(CPU;C)qd0{y(B&M>g5C|uR7n)}=XYQ!^X+s4xXGjG_yoBYV(Bj|j-3sfgHNCIb{QYL8 zS!|Xpzt!#Z5Oiqs=HKigt@XmgVx!iO)B#ou61$R;@_wqGCYsft6(jn}pLE5Xzwn_< zt67d> zga=pLKtN_(08gvi>$BsiU4zFP9>NT3>AWyIXGkGb7KXB()-}VXAQB$;K(#FtISinx zID4j1zZm<3sxb~%&7Cjmb5u0&O3r@81)RPeK(!P=N`?#4vRND6&ChWW>4p<&-LshA^V$OIF!rUjCUV(oqyc{$UnGpCdCARa9;$U^~^$9z)SdT^|N zzk@*|)q(&;1gr5CP3L>m5mS<*9^WM7l+@t|yjL;s!MenTS5A{_LcGz@j$3uF=W*e; z*UMX%AV-0AYYWJkcAuO7oO;$~dI?6XZ;f(}JP&E`wmrzw4U)Bx$BlBHG_dC`=OUSL z1cf0=p^TdpOyBmdCbu3h=`j&Uj>v;W;BWNv6(WQmMpEu4W)e^g^%~;A#Qq^w6pLJbSKfYkR7L7dJA&RSvKA;B2vG z%^*33V`c(W zxevHL(S*xscb24Y7R)rPX9bB9wI8oKVPD>wbe!f^h>syIx7|4CJH4zN@EU?nSdeW> z0!|@~?KxWpiyp~b2*q#D%-P{3n!!h!e%k5s7XO+M@@wrQD)^%??y4YP0$GlsN9^)s z4$#W>{->NJRfVl1xd9L|D}H{@6rvVKIlG8v-Um_<8-dxZEzuHzNsp%1n0n2#;5#|@#aW>qEbR*43fXe%yBRK`*4o;#2~i_{(_ zHEwzyjhhcf%aXHCr;PNCSTPT%&}X@>8zvrvcU8EAh~38yr=@}#WCy5NxKXpxwhS-3 z7FFf-*2m!^#u>jfm2vQM`1=t33XLgmh<WG4icp z3Qzm<_{@wGlad(3+-MFsh=5apAo?6p=(%?3d-*!P?DUl&CG9GX?2{~GExrti zAPL#%oi+m9ow&zFk$ohAPuM(6joj;%qf@fg%;-?_fNoK-EL~%| z6pw~LR~{%nNkZhz;Jx(C%HDxHAH?#BT);Nfu@Js!s}J}K0WpKj>ftN0MMcUc+D8)~O}3{4)#U7aeH8uNBeMMNz@iHa@@n=VyD$ zc6t-7DdOIwLZ%SgC;w0B^)&5P)j1R9MH&^LAOWyRP9rt2GHJ;2^TaPC#Cyj49EfNj zAS|@s_BT!l+ebWKbR@^1qbDD@ajd$QgKkiv;e-iLlFB5Re%*4ke7k7eTd@! z*~H}W81bG+qTs>jnz19>;{c(G;W;6wr#U9Z|sKefBpLl3`5X*Br>2#6R2 zK6loxK+fr?Wfdlm5baUGG-u|9hq^u(VS_w`&Q9g%|Q3*oDt8IrxA;@U7Cpnxe6~m53y09Z_3hj45(Gmp> z@{7Rw>O90i_P<6T2i`VTJ5F?z!I_fl&-O|k-7DnYcQ z(yu8yn0K%3xBW3r7DqWHiWu(-hMNA zR==nLsqm`mVKlqeu4xv4;E}b!sF<@9)z>rr?4UOe01K_3d1f{gt1Kk~vkwNC*{SiQ zqFe_vt}{^+NSFH(x`x2Iqjs4pwP1sV6#pWKPOIi=%@sw7FW4^ zX?B#9I@{Dfgw~L4l*zOzW*O;HOMirK-I4lE$s(EJuftSB#b{E+=u<^05=HrG=jb9R zqCQ+gCMz4iTtx<#krpNaejt(kU_v%2K@its^(35@U5^)_BI*Q6FlPEeX- zQBl$xh7*$Vuy`KdH38yC$vuZr7yXMN(_&4TOTnpgbZJ!#@OeDAA?4`A2ajgV4`&Ow z{mzDK=1fI$Da8bW(KK1fl0LLbDjw2b1UVsoPDFhjUipW;x4`^t6)p;(6Jmw>xN(+j z7paAV(-t6i8QdtwIsK3Ry2$^UR@uEBYXJLEALy~?aObBr2lzh$DN6%plYp?U4+%YT z;A0s_Kr4kt3;;-IurT`lkGCaR%w2J&z@j3%{hQ z57ecr)pb7?u`rafiv~-;7u9UThvgq})j+9X&8+cOg*ujlQRtJi65eqelko*!hz8BKmU^pB&nUFTxgIzN$zNbZ-f24H5P;Y(FDmBp zF$wtN8%UmzCRNQ2AeCixQ^S5&@akJfw#CW+r`HhoVg$M~kX%p-+Db_|631Y7>ZCbN zls6@%#n_|f_R?MN{{kW3?f42VZYfUrq@&JtX_Gq;5O_g=aSQzlg;J{5uJjg&J`^BA zt?j9a%F9tR{Ng&CE-C~t%Fz!Ms7Io{Br&_H%|fsz7Upwa8L+IUWV`!eCK1M2$K)J= z#2ML0DqP>0i^@w;wNo^Zy*)HOkU9=pWpb1ngN-HxM zbA)1^r^BhEtlZ$9OK=6mDLynIt_&7;&voBF|Lb4_2jdn==&HcV;sJ!Wp`Rc58K9rK3RCWU0eUSo0Kvo4XpH{Avb3WuN$r?tb^Cw@fXN*~2 zqN#iMm6(nJ8ItRT9nQC>{qpGteWqyAmlL4pSkzJ7Kf_0@7u?Q!Qw6UMF7#`%Q7?sr zDq3(M%NTko$`JqAY|-rGAj!C5JOgW*6%z_J2~OW=Kb|dg#zT*B({tcm-nrWRuV(PN z5ko9;K&nJ|J{d}(ao%X#l39`90<_BW`9rA96F^-(`u&M%l?m^;)J}l{VMOq^1Oa224*o?|E}m}Z^k ziwZ$h7+YbFRFC%#9o)b14oW(O!a5)R_6C(-0sx3tAL6=)iHM#{VuirFL)%fCudQV) z?XHkOG7MUj>u`SzR4V)SoH!uNB-4DW3^{RGUI8`uFuqVKqTu~yJBFxhlGihW=uCai zv~PMnVjVv!2fCjicGhHi6Wl8D@xxfBb6n+MtNF5Ur%qk^s1zPjO zO_tE+2%jPpe3$mD@P3Kj>@)9N5jjl<&6%K?;z*ucrw0Df4bu>C1j85(F7HjTKeeg4 zFw7_UCIG|-#m~!y3Z6I>9E&)d7eC)(OmxPB{YaW-mbk-;GElY}^|=ek@IQfG%7wF% zN<6T+H?+gxT+`+b(E=nYu9DD?7~9M>|EKGd-Rb03nJZ-GG&m9WGJ$CHGZm_>AA!@A z62S%wX}!feXa4}{g_I8@u5E*UEnn`Sg`@Y7$|O<=ac=plO{^E(-Dx&{^TWH${^tc(CO5t zF#%;zK1$_~4DF1IYbma*o*~BUO=9WfzT+?!J0ML8wY73EvGeFHbsp!+w$T)HM_qfh zgrZ z*z>J9+>n2CPpnXU$>Wp$cz?6@vNhuVpBA7+?9Yq1yt6$l;GR!~BUM99c7;Hx#8sq3 zG8@OzJ2%eVa|^E6jCGSb&PmA{q#<;EKg}zE=*k2PIQP%6wEXrj2&C|3;J>!;<)6|@ zBEPTH8DQYXZc=_qWF{Tvn9D&*=hoQaBY|lwX=w!JFFNo68jBgNC94M`K%JTo=#bRE z?|x}%SNOu`mMEwSOJ=h6EIh;*rlFt3{u2v&tyVWzSVqqQgZJ;76E+!}3+IY= z&Hc|oW_9lOAvO6Bwk9FE4LYxep?ab7W(6M7RIN?*+edpMt!YjZOgq#=ffOeSQ;kJU`^7rPC^R;<1fP26?8;54my;~r;q_XVK_poB7Vn4KSlR|$U zjVO=hfTWft+q#UY{D3aORlLpEBh%AaJ)aY}mES`0PghoTM&*rt!e+#a-zb1J@a|LR zAcM(`(Pm~N!<0goh(ywdX_GF&G6s0~Nx5Y!eaBqLY=$|d#kFO7#GKbON zLy-2>XTC=2hU%K|VH$(DfX0d({(3x-p`WQBieuW^{-idI$)({0?6=^MTQ(lj^aw>{ zVA!JV*rshXu71k=hnfa0@4Mj+D;hkqwrE=$*yvYWc}rbl0ZK5k7@aZ@J&FQAZgZxC zYqz-74(O*sfVJznVjx=`zdbD9cV2u%Vr%TkojJ4In9p7|@?v5xJG0lhSMhS=iuf~H zo5*vwr|dF+hUJRdyNz}7CMr=~V*zpR{CQoxu#oig(0}g{ITO!_P$ClR@9&?uPbXzg z*8Y9tU4LO<9M8%~tlgbc&&I?6NkU_q1uKr2E&sAdDe~a) z&)VsO)k$iz@7%4^PWV)Sc2Zh8z&7JLKg<_U)@$n!3-oQ~RzjCle3}CRWbg-8T^w`| z;CfvzL+)xff0<$p%zz`V-ap^-pQy|Ytt(c}xJT(i5lgCeOMj+fnktaUQyD|2`F6iedC`!60>5$zc~7{a9o6P_cHhBNy@bZmjd zJcWwSMT&Yr(6%SE>rYSo2`XD@j@tniZMp53?&36T5}-H17E8@iACRKRv{(pF&*rQw zsZx#*?-lWEy`Tdf8nZf>XvRdh3Qo0C9Kc5+ zi7D0+JoS77u(BaoMwKwS2yb^P;o!R0p)g!6q>y4K5V0XpWzd5CA%oLH|23%mkP!q! zaSL`}^ta7=ibYe_2^bepy&S?T z)%@dS|M4ySKvPj>D#82cJ;BFytLHt3Hn49|F*m}G68$l_;6e=$ZSh^yV+NlQ=z%O0 z2*jq%Lk&LQutaL|kHzV6P<^S7c6^3fUtA%Sp-#cbk`+|HPw{`fm=d$`6P#fKdW}ln zOOR9l+UI_!F+j1*JSU`Y;BOE^i=E3L#kL!hwyQ;)kg-MEt%oP=(g&^g5FycLFIA@! z2Gy`H9;Q#jpeqv!14W3K2E9Si03eq%YY8B510NBu7qi^xq_K+p~m*VjY4 zb@0pH*~zIi@e1sH86K50Fo3olH)VzU^wE2@1;4rroIV$)+5hAun@*59*ihGG{OkLK zQ+)COt51MRLE494MAc>B86!plA93Ed?^iazX6{FYY(HWe(26)LL6_{zi-(1gy+Mft z15FR#g-e7~KSXWW?`g^}rjW*t^-(ye!B_sX3+pC1@EX137LGO7!0=m!{N(AKCpO5{Peb?w-^9#>8#jHk7W{H zVUmplKydlQEzF=AiQqvy^hzLD`;Q56n6YXN9sXDQ8&Yp8Y{%le0fZ-fSNy`}6H1Vj zPqOqsXt4amdD%${TS3c;D0=1u%STPO99FzZwqi!<7nCT4@cS*hz#Kr~38W>k>Ag~$ z$ZCQ*T3>vBrPHwqMo$H~8c%*Lt;|o_y)-bIvOupY^)MW3?E*T0p`H9scZeRMv=olJ%Dwt11HegtDEBYt6S#xUvm1P zS4@2dgaV~;m+6-lDOo`QfLsM=)w{2T!Po@q%m&Jq|wF*cf&Va+(VNYYVpcC)Yg^v=5V=xDrrxAhHo+S-e$r2`6|QTokLOs^WJBR}TAn41j^%04WfW##MU= zuvW3KXMkExyw?ga)Q1|oR>yCRd7NsPk^kW=)=HRpG)6CQf!&P(_(;WQ;e~`|x*e@D zs?suBG0zBjG44uCF#i<--xhsFooK;#i&TLn(cV*qM?e48ygfY$4x23Kp9X#O7Ep&$ z2nbE>7!6QkP}4*Xf0*>L{$wDXPeis9hC+hj;vAHXVC8ER*4R1z^v7CKO;Sbz0H?gc zXBwQ@l-xqvoNBOFy~jT(5eWnHXcPmhWw6u`PzsZ@CID7U24KYkxniD3&2YqEN7WHZ zzeMz&@iD4CpaDkfBnYcU=3yJ2KX}7o${L6$h87N5*j3ln+L!}6IimuxB&B6?uS^-n zY3k+@${!E`_Tkba?X~R9jSyguGA~SXY9N-H)%5>eNDk~|ray}u2@O)w@wfPB`|MNU#-jPj(wtAmw}p)@vy}L|w^*9#F0eu~83FI2bxe`9HJH=#&O;4&w|W!QlVj zyqO%I0ciI>EU0jt!L6Tn&2KB+yu1BTfVF8UKa#{o zobQ0D@uB?j&wv@Ak?;_uKD)V4v^DrhX?-IA>L#EUN)1OR*wSz(UT%Z^t8ov1pVi05 zEyZ}1L8=5e_<=8fR`^6@;tz0ecXq_#J}|~1!DqD-DFKzU=P`k#fEtcIkAwj zVF~kPCwF1?#o}f^_7n>%)ou!s5EUos{PTA&}yI*yfOY zJpag8@x)M~&CRgJ%ASdV&LF$?s%fF`8H{6-PGGf{Z@wuFtt1YXr^Mv%~UZNt8LQy>e`RUH@FHAw@6iN zv@^2AB{H;SacWy31Y#j`AG~fX(9Tiw1JKg`1ccWiE+*x0$)7j;kuER+$_W5tTZe8W zg7vE5s#0*ZJ4Uk$qdax0Cod5uZAwGS#nrl#jC^7O^SS4cQVgtEwffA-wUr{|%>f;U z=+%3j`{B0z#$nb`msKAM8dbAn{(Ul*ou>*BgBwnh5`hK3T=AMt`d_&V!KtjlgwvMu z`X&?pjwWl&aGS$)fh=L`3Pp#9-zjT~7;E1c7iy@-B1W=|!!UZ=4m!HH=i?=WGqRP@X9_mI8D7-0#rb z9`iz~7=l~t^P;*`b2Ihq{q@EVx_~YW@5IwT)+m8$B7lF4ZTV(dOUEt{eT;&@`lP#0 z)iNutp|nc-VNtExfg7cX_`%l)ch#K}bn~P3R3&WIbM=Q_3!M&a$cXC^dLQfZziCU} za+hI6G=s1IrX@QT;dQf8yj-;#FoA}nK`C`2LO1T+0G0EZ;uPXBLRC~(VsGhGSgIni z9)32-O5n&ZJAY%!=tX8Wtu|=Y6`L%tKbW=rOm`=SS%HG;f^MnE2CW6rXUIxIi~x_C6F; zK~y6rL3acQiwprxe}jB;mZOW5Tu*qesucdRM9dO~3dDBGepVfWga%Qu{DV3n$wBlc zSwl!E6x~3AVz6hWreYy5q%Ae<`0gYa%H++n)%hxLhhLOL0SQ}>?-Cm5c2i)Swa1tv zx}j^K0&}8~Xib2FY(HeKy1`G*pxV$fe^4)8eKdU#n-(z!>h~qx{AvH-el&N$CIy2c zO?~|Gv8sg_QqI8K!96PATh43qZwl6|mqSM`2?Akt6H1Um;yCqe!i2b#DO1Jezx0LZ z30{^e*V|PtU5I_oR6V2$=g!MOg9b^~`9Qd4&P7xXxvMS?2Y-BSU5-GwTfKvS+Y=?T zb#?pwQF+-w>y<<&L>r?p6~&$SZ015Nd&K+15)jjtJD8%Q8$VYuqzGkp3S*<+gyhl_@k#~w^u!l z_&`j?_9q8uzLzL8^#cuLL^B;2=2*W>BstIJ>H>UJzcS2DOCd8`&-Y(c8=ikRC90OF zV**|CM{nLp{529L4>5w_wMYQ_51q){EfiG8Y8TyC2;P(2w=JGCgOwnz9Y(NIW^fZ~ zhxQg}ghYuo$bn7XRtlT_Ct)zunLh=IK?yw8;$3^!P)^)$p6}_3BZx$;B%3I0W0w81 zvH@2R_6RhR3{05{1`8Sx5{m~{El%%=ZPl+m(ba9PUXIE8yZ@Pm1zTZwplfZ>;fv6- zGs-*@5kKiBN~nG@64)pHg&zKb^8^v{WYMt$^`;o#M!A2ccrbT=gipO~cp62j)l!v8 z*qxk|6E<_RPYXkVuPN)xl0l6ybqHuR-miJMB6iZ8Ayna0+vsEq&A}HRO)i=!G#stK zyu%9-^UXwk7OsI^BVC6JrJYi2V|*@1a4{TPATt$2SR9E!aVHU?gJe1QUO7N>r2kn) z9wxa5Edf$jE4kD>&3uBNT#w0W^}%?FQvaHvIAI%G>KM7qjD;AhB9Sq)E#`iYsALV= z#n%pYp(OPa!Xo@gc-crH%IX(+mo3}t9l*Y8JRQCLP$gYYse^z`acaIfimZ*Ya^_n) zsW!eCa_c_-UCFX9ufT(611lZYxCu#{jEq6dnT<>E-o|(O7#2k zOkSngp~aRl^WoYC_v!00ip`YH0k7e!LI_fsKCL8#hT~#^kg%j9a~0$pty}l*m>GQ~ z<8vvg_cyg_<;#yf1XWX#{WGn8FcbK?r3SqPm^PY{IdYaUU7d0inNH&KFuz;F-WwTv zJG`9NuG8VnFAOg#5v&UB=6#Zr7U+||{5-9~jg)*F2}wfP#l~4^Oy>9m{5{hL>ZZRM z&y<$E{9$#Nho)6Rv|$W?M;HH;>G6UVSogd&7EhWyT(zt!szCr;0u1w#k&h|A9y>?_ zMgk5jWF_~26*!$0zNgW#iplFL_pe3BEfn@O6>MA;5C+ccO)fA6wrxj+asaP_>Q3St&Yg_|0}aBa z*^K@THr@IMUH_s0+$vdi4Cb>qeO3yxCrZcQAZ9+qP|fn)FpkTq19+m@q!J8@YH?a` zuKQnvgmqc^tf>a!ER!F99^8$0&$T*Ir4<9^v*FZK6ln26Guc7EFIOA6OaK$<{@x;e z`n^^RnhmStMl0;YNNIQy%KUF+hrTnq3AJJazwXYEfDN6=f;Cu!pQ+F|Ntbp&eeP%= zd#EvBszEK7@fp>fc*0s3Q7ts^4xySeo^vSrcVP0+Us|`^qCEMFLi9uvt~*_;qglGr zuk3ip>8=Qia#tD)tu+^=22R=n&iqC&o$~vvgL!`rEl#2RRb(_0j1rs~y}=XuVt{a; z972o^Mlfc4{n$eJn{l|d%s6XT7f!3^XaTL3CUPRMa;Rx{bR{#581<`wZU_&V+!VwS zxA`~h7 zjsJ*Xu@o(qe#hc)!X>LbZZ)MGWqZ+Lw3<6f|LA|8-TP>abQ8--Hh)LFZHxO??%JY^S)IekBcj$0rtS)EE>y@x1xl zc&0PFrrW$bGf<}Sc&30{K$0F#hs8n(6k`U`;4bha@w}$i{^MSStRxC#Bno>jl@lfb z#@J*!o;r3AjNB&2@~s0j-KI_5=k>k%hhm2^P21P{JoCP?@!MI6d%MGCDTpJ|Jzn_X z^4AJ2Za&{UPM2SgaLQC@L8iK^C*s3iRC23T_d25$hqUo%=qS=?*|B6;QDj(flo%1F zCd^-|xvCiv0m1rrMx7M!K2~EL7{rd$?-%Y>G|e6jSP-A# zKKXB=VRj9x#XoM5@d|7WI-D`b@w($tu&g}zl7bofD?=1Ly+Mbc33)yTxUu7@wAuIe zV~^F!8s2KW_mGpQkl{p9V#H7+lr1eGTP(Q(hXITTU0htzQkBx?OtKvJ*N=UP|4B!I zIHMVh&A5`Oo5J0oHz&R}oPE@hNC@2eQ7#c3pK-B|9aEe;scao@X1bz)8V(}OXaH%C#vyzIl zqKyjeGLKEUJbPysPO|ct29ZtpmMppE;0}N;d{?u?RmCJE7J>&6?Q9fp?bE zKfb)4F$9kzjGcBf@&J_ymR)=~AWJmV`*}aGc=YF2&XAR3W960mA9TcEPU7#_S~ySE zJZwxGFksy_n6(hDU<#QQP|#KOUpM;~3l9-MJL0Wx^p~!y%}n_w6}} zqI@ARgoZRQ09rA|Rv}>w3>w6rYrN5K&T7cCJLBW}q&3EhRL5yOQNV(PRZU~**6xx8v?fHT-hP6@b6%Y2@sOQ94fe@buln8q~LcU z6Y*L&;un;^vDrGfuWq@l(&4N2u9MI*YJr>eeb!r*@~Z^)y-+>vMYY<&l0e@0RGF}1 zamTvT_r#TU+wUn9`t4LvCpyjzkb-=Yyr`T8CApEz^>vTJ@@#CzFdhj+qwb6?DV?;+djNg z!p}*q%|a*|nr)>Fkt6Up;0>;Zicu3K{Duj8kh#V}EVqPamh2i9jvm=T8-^qS)MK^P zItd0~M9LOb?rKR%E04ip!D&dR&LiWHBP6$gm#Etf!0vK%)-iTp+rC`ha$4JVT1f!+ z#kU&Mk@mF|0(SueYQ#9rB>jn}bqzG0zZcr4LOakXchOu@9jywt``-mv1u!-Kb<+D! zUP34HAuK9=)WsrswU*=O6;a^Qk%SgZiwhi@;lXL+{X-ZE>Hh}6L~tW1Gqq1FtJA{W zJx-mP?VY@{oHSN?ZVoz66}Ujb6-sBKzX#yc_VX`%M5Lc-u^(F2QRWxDd%ix#sf_o0 z-tV0cj!<{O|4aFn4JqS|s^%sngH@541m&vh3Rb;Bms#_*2DfS>{FM=KTkrMlU6nTnnmXB%fsKgIF{o0{t4aJOMMV%xj} zY^NZ0JM9j|jsmc;Dfe0mcUOrLBT529Q@y(TKgvhJ9Xkj+i)M#a)jnS5yze4vd{pW{ zz;`er&{(%}*ZHJMEBUc<$)k3YB zZ{QRs%pUJ~d9$TleX-l`NahA;RJbE?YTXWPE^*ZFBG0(_vWNdw5j`LAuj${WEnqRAig8mM5OGKrgjLYB>pKTL z2_p-`Yjgw;9gHk#BQV5o(1T}beAcqKd4bE|zP6p;)bhZ$kgSX~x4Z01^SZL43N+Sp zghYT-&yv^*uDBL0WSNLirf0%o_kzj5nY!H!UMwvdiWGXdBs#nlm;EEa#QucqMMPIr zn(R6I)~kOee?CMZW%XAZL7X0GsK$xc4vvY@N*pHwy#n_IAD#`B$==m7F{KBp=n%U~ z$St(N{J=9<0-bR@6dgedo6sn_SHEQ*Oc0#A7#fEVwR{etTtfEmVRd)HQ}zOLcLqO= zQ34%hEt-UfaKx|=($&$mAuSuZi62eymJ6OK06*Qzq6LinTp&>vOEHJCJVZ~+3N0QD zFSFs{DyL(2?o>xxpbBS)-|yf5Mi+F~(hCdHs8AZQ^2WV7N3E>%K3^Xfq}awI$gnBl zEP|uc;Yl~oW$wyzJCdfz?ml$^8`=3;kINYnFhIfR9%+)sMeC6;+s%qZq)d1aG=_ht znp@q|n)1~^;gy+uUle98&&@asUmp~6ry`D#`tD0oRc<`R6oFw`1et~!^8I8Q7@7xT zFF-im*#@qp?{mqe?N+qB>%4R52U7F7MfAQ}d|=*L7x8aCxSEkg3WD7I`n5v$n7_q z(k?DsdA{0^6*03Vy?(&6K~07X?JH^~ zMv>Oyo5h<)4vBmDMuW6yMGFzeD4FWeCZyb#!-1#MJ+2|DDQDF;NMN_V78NsT{==ar zz3R}LO_Qhfs(d4-_pZ(z7$L(O+1-Cas7@=0n7D>@MFR~f^kO~HO#`s4-O1qu_BTZ}}d z-;G-rI>h!>A4UverV-_cQ4#6i@EX}-le`UVEZ@bW@Cr6^#ZsTx`I|%Xw`N=hz29?J zzKoq=$I4MjDymFkdsrk#QsiQ!t}13Y=f2hW?+y)iD8*45pPqA4+*q^tuvfF<_N?~{ z?!aEo5%~+;q zy{iHdksux3Y*$5AH2U6AdQ4~#HKRR;wQ|7IVU9OMmWrKWYt~rgYOft0VgOuk038mG z(g-`6(w#dpbO9V}KHQWHIttCZB#!Aw*I_|eNUeX5bFysokC;k~Ou!+>9Hy{ez058V zK?SgsClI#vwcc9N&`|YLT=g1U|M@i~8T@rfRI`{lhGz++W^6+ezWjajd#e6>MJwYN z9%+_VeX-Ke72~~d19q&RAF2;iatB|%>b-CabTpyurHKE9OeyaVEC45_Rs>njN^z$` zcU?c{NUlwWv@dPlu5RBRELl@wg^{t7N_dd-wUe`V5lv4`1+#Gw^she(T1p221LyUa z7FUPz8T>_!@n8a}@%#1v;OJC9FlzNA5j)v&?-y0d(&<$jXBF*W)WRe`M~tWF<`85V zJY`rU8kF8r$zcpi7ilqhdmh!bUCyw$^k~|DIY!pi1q{^ZIMyG0^RZw)->0+ zL-GTC%Ks&idv1HRrQP{dns9;+x?I*+!Wb+FHy7=~TMji1W9&}}eCgJJt1}ZDkC`&F zQPuDG#W)rwwF_Jkr%b+PUR`Z2!#WCebYg$DFUK`{N}Spf=(>>@kDkKy=EDYX zA`F)j(DFCk6jU3bDtm26zy0?oC_CnxdlBpkK_~{)CKZEwDqif~QxCK{vl{@~p&*jA zI6E|hgQZ9#&M|WDuVrL{y-Ek_2(3(LoJg=tru1kQY@G63xJ7vK1Ct{5G+8>`ljh;$ zS@+7m`Mob%xs%_Kgda|ard#}P=&j<#Zx-7wLH?K4KA=mHW4#^uMF8~+=L1l`T*fh@ z885>*98$kC+nlmyElr0UNQWG=jf9+$9=`D?7*=3I$G@V@^^H`~UQASa{*@PN2WqPY zn}CIsm(LaK1m&9DJ2(}r(4gS7h;()khgm>j6@Ey-MxK#49Y#1gYi3sIYqZDlu$hEd|1&!te(wE}mpK?`3G8iQDyN zlwH{@Y7n`)NI&0L*-mcl=0h?k;haVQZ|wCIB{9euuazzD^j9JcM$D%ZdcN$S3Cn;9 zho5s6EX&3vx9nM8OMdk5Cs$x5@Ngixcb!l1 z{qs2ge$Q(7x+rj>lJFmR1IDS1j0XHux=ddv*|CiV+SH| zWcZqoW^Ag(7ae#5P0ur0m<)()*<~0hCExD>uu@YLWp* z&}Z_fI~B9NMXK{Jcw+AZ@f9~I92el#M2MktFsWmCpu$}#obn$4pz@UpMmGhK!$9M; z-cg)h{^X>j%xpp(rVb5GLXq)3^VWwr6SZ1`tgYTVOOZS^XaPjdLrC|cxN6E0cJWHD8lOv|Y98jha4do9O ztN1&sC9!g~xc#feOLe+=6+89)t{`T(7RKHFQ{rZk|Mu;-%9wR@ESI0x`S^0VRkWTq zAN!EZGyDCoE~_ra-EzID=UzmW-X%o|@`wo=-0IzU;J9{Hn)GI(v@d^Otbv2mHBmz;BFbIv?75t@EZo!6i8F`B z{GT{BvQv-Bf$%SUUDd{!MG0wicK@))d0L|mR2q^%l2%1$y{Y;%vgrdyoW*I{GQSR- zYX1JOO~kP7-5bcwEa|IH9vakPKtD_MQ%g}o;@Z=xb#gM1n0@W*Ks??Q1e%?O3r$be zuW!)|-15M1M&`**AE~B8nkTQ5bXe83BJz7>2ktX&2dRC)Pf=tf?m0Bfo#UK&nqra& zRS%U=#}j3F9ac>m(_fPg4-Ux7Dr^l@yyTSZl1Kc#3gC-^_ok45c6Tf( z!A3#NRhv1MI+AF{rv3(gMklXRG)BS3jink>p?8%|Uley{B+&hfq*0Fd{kN}c?9?I> zE3S6{Aer#NJmuBoczt;IdlA3+kDI@~l6J%_b^0=c{8G$jYU-fZ9_%&NSrpgST@E~6n-H$yK}oSewhUG zm%b(ch@UO8ix09_4R+;yeK~V+vPs*>va@xAe@fa_s|}%AlDd{Rv|g?+grR%nGwtLD zUKHUr`Cn9;@9#G%E?U}APCZ>12v&VRFafWmhO~emPa8M3CU};mM$Yx30CN!cl1`zM zW8jxH%b*JTLx(ZucIxFXzePcCMhHEpW7!lx3p;7wNI|iyKJ3iWn^7C8TkKOg3;QMZ zXl1!Xj~6QwGwLdXlVx}U_R2l9MXf5rEgW|M5qC(L&!$UOTOk> zg`E%(_=Clt%)=fv+_HeBNRv4ZJ?VZq{qD|CuGHmqcUwBmIMloEYEC@Y*nqAntiAZ! zG>N)>mdbqGh?7qmsnk8>w^H>61>7CLssn@$FIHG=e{D5f9G@-MRxNzoAEq*Dr~mz5 zS2b%nQloCnKuX8ia&tL#IY~nid7H#|ua8dZ-K9@0q-`E|+U%RUxQlVf_>+s5rNPid zVf_9Qy-I`gLOGKDE1|KgxyXe|*9{3wRWzuPBtHk%&jD7@^+Z2G-OYd)VG)s$3r&>E zF^Dkd?I9Mgfhd1w>kj^9LEA`5FhdnUMBtZSFD)MlwEZS(mV+xA%}qMkL8Zn97=@a} z;3wgxuHvIc9^?q0>?zv;K% z4$C`W@Cp$#A@})6XKgzx;o#WN=DdZHq>*>dIzkgmTfH?G`(;$X*e67+3S3zrDl$l{ zT^77{d5L6jKS$|d%xq2mH2LMR9U3N+6wq2X8e@R!s9VG1MB`|GpB+?<5){>3&oiR~ z9_Sje06!xZ{!UT|3&M$H%T(aY{@G`*|L|+I$Y#E2(R{-n@PS`E4CZbX)xnU)wrY${ zH2edjVm^?n!9T+hM)PU2Q4 zOgE-k1`=BKdmk)p47X{qO(Jxh<Ftv9yr9-i4<|@>Q%%OvQ}gDU5Fvtc>ii^>SEq0r2QS*KemY3GZ_GOAkd_AKmQw} zguTzrqkK*v{lv&dzFpO3G;r%A`pw_I4i~W8T5h4z?K1VnE4hSU?i>8Y!xglPyy~QS zfk+`1-pF4N$PYAkYAooQ+rbT&*wgBlO2(J^?v{zPTu$vqTK$inpp@Sy{zh0iNjs#u`yX!D8MhRt>1B5cQnt83Q zzN8+MF?g!g?D+NyjkGzQ?JX8DwBKO%uuHfk;uPM95G*g=)7n#SxsdbrN2cUi0iO3} z*5Zq>ul~23F;I6(iP;y=4ah5W(D@ncUng*^F$%FiyotCRRqdD@8+A5B0kRKz(7|>>xnYQ z&Ms&Hk`-gXv=ExSL$6kwraqn5EkoILRY5XlZdM_US-OkJPI2zrTq&`TF#KW16CWO( z^-fF8xSc4p;h8Rv|64n=uW3}3{Ykg0lbqp-jRT{<+~3n7xlWd?{=z}hpzSGLhF#Ra zL`$@%H9%6%OYN)%F!y7Yxyu66OoHq_k(U`z#4~U9z`ZYF59qcic_mh;Na9DwBY8er zy6ft&)bMEMiuX~{s+qz+2}g627KLQ8BixE&REb%=s_c>T`aECn&x^05FOU3BEHQg` z%aTT@Usu0IOwCBn*#Y}XwE_-P=FWZL`yJ7TX~os!w^M!|-rLE8kr-!E%CY5-Ki)ju z+j?EN)q0arQkXag@av9-_>6@+hC!AU};!6LWPv#mzQ_!Y%QQZAa-RoYYY(+B$B zAWq(&E*vEklg^VP@6D)db9f&O`|T>Tm`$MQ zbZPCw^)n!Gj1I|XmR%#)wrE%yL`85&vp$pZp zBc{{+;?sG#b%prNpw~vu*m*6vbF7VEwo&!u?}qp&LP0!0XCi)Wl``IHn5kf)Yv`5b z+-&D*Kd)j{gTT<}?6dp+48~C)W{@4US9KFUp@&keo`&>sD=g}T4)H(Ah{8`lnJH5x z7TGv(`WgJMVs?bfHv*&E7OWuZw|H@IGQZdAQ)CF6_3#vH$v(D$ zi)O?0I}gE{d3|B$P36uLy)w0pt&!IEocB{DgPhPkhcyZN>g9z8P_a55HNv86N=Ek` zA27f25mCTI-z1p*6OwKU;7$A_YSw5CyK64m5`7-BkjfLyy_4(nDVigeotdZD!_7zO- zrBkb}tSg~%FBeL|)De7%Y>WPaqB|t;eoG$moD{^l+QhZLQEDudE);k(0T_k)a zvO0VCCR#wKj`^TP*HtL#WB&?Q^;mHXQOfR+7I7bDbgeSla)F=5@mN zWha)HIqOZ=@U-!~*-**(7Q}p)>1mBIhbdd*a`1P7Y)5`-grYjh;`79(>fB%Zn0~_$PLb< zPf&aQ-jmZ|add;>+9*^~h$Zb?DBeRM^?8BB5Ly90(3zupXun9s?-92&ioyzX% zm^PBXLoGKG(05-zxlmuQ1K0+pS^{dBP-EprB(gWbhi&wUysk`PHF+X7?JV0ASfups z%@GGOZNbc`pQ9?t&af$VW%Oadq?Yf?qgQ~EbYqJb%0Ya{UQC&7t5`R*U|aJ>zCX5H zO30>Ev<~c@`j*Os*>Dm|eo_J+leyl3(RT~iji807HiW_P?_dY&5Y|J3qa#e!uB?4S zbJW{_r)#Jz;$op7`>oA1AlA_u64k=37?S6#%U*2wxqCZW!2%2VIa~qxW5PsgP0K=^ z5tvcl;F!ZO{C{ft>aeK3?`u#=K|n!DNSni?rkLd95{B6A!4ywB;}bagy4X>RE`xVXqJu;!@a;=o7yyTUbUKlOpq+ zqZrAAguWWxq;A=eD25<(67fq{d}V#2*=_TiD`Hx*&dEnohK(SUCC(_?2iW{w${_)V z#J$VEHn`K@Y~Anmh14s3nl#urXeV?Z>E@5`PfrX&2!6sQ(0kZZ2ivJs zD1HpW4EG(+AsCNWh}+}$)lroL!SivIrCOmL?+#wi;CDQ(gF%fx?O4hwRQ%PXz@I)T zvObXscrAoZ7J*^(IP&Cbz5UuSdr#0s`_{rdY-nsYxlD~XM@^4X2;H|9e>gUdKP?~( z-ywnZt>Mw5lFhxzSVYDjINMW+c(9?)SYO)C1Po00s}1~g=Pe#zVe%lXv?fxcqk8KoqX*bP|IGY! z+%ochy?Kt$wx-6kOwifV9Q;sE4vHj&ZLR?#6podF4i{Np{g$tL?3mJ1j(6)kKiQqz zzdra&rL%|gTzg{~3*H9%Go>SswSz75W2fP5MS{Eql1^JPX1EyE!OSap#E--n9W~9? zIr}?(&ci?&MQSgXTIEz40Wpba9{*RR^(7aFi<#Gm&LMQz-r!cUpfdY_$b!C5XDxze zSk5qB)5d0nQwU9YixMCA%SsP9ts~am^o8cRI#KI=<0W{$ui^YWK78$OdgmV3cnmwW z%dgRbd}py|x?cb7(5U9?#*^BZ8+Cm_36cC9?H$Ks8xYb> zdGYJ8Y^6`VzdH$RUMB2Pi*~A#T$l4VrR^4EwC`xWIkLOsa$D#=Dz_G4xFkxw6|Z46jq)=tbeJx8Vq|dOW0|5A5qcFn z7af!ot05OxYC8?hGR@Ld%!JanB08<>>z>%OxIENbDY+jKUc{_l^GBtmm=cllf>Pb2 zro-9{d5J@^w8Vf8y0UAB{Bo0m49d~!8h;wJGTh@l;0er4oc_Kkg!AtVIGbbPLI zNTrom)qP5Do+b^!(j9~aBl?{;Uy8f{+Iu?}|NO}&U8yfeNV*Ko@{h_&!ZBS*e`48P6qV!p0g%T{=8=nLo%=6mBzF=j$ z9>}Dn`%CV)>cmV10$I&i_LT0i?X7G6o62%!Lh1?~y6m8~zm<#XOyx;wzNj;kxxM@- zH~#t^D3=iP9Og3{(CkSSlgl}!mbbyd8tXEph*{+`UrnoXuOWPHNbmYd63f^KoWe5!mXa! z>-vJJ5Hl2b7m&F0A`S)y?E?i_DQ)Cs3W6eh0D?Q#J5Y<$_=GNz*%kH4NS@19CzI|w z2b=!6kWanC%*y3*&(o@s<4MOKut7AzSX;f7I@6qT07gEV`B9Iw*)hA)ycS>1P~}&u z1tmeZB(%m}dSGhCj|e3|&{49U(>3{O-IUBX0My z>O_X3T}Dgge!h9(C4{C<86m16H|6}N&5P%ql6^WPB{s8`-uY|&SB5C_3|KJvdNm{y zs$qOfFC7&UX`Sf!Y3t>za7=lUL19`7^UL~NK%P2(+uS~>R?eArIi0n;dwI^*Zi?IJ zL#n+U5NKKFL(^!q z3!af2qYbNStbu*0U z%13b7`n{Ut@7TWG5ygrCnRrMk$r zNB?JwnIoIj9JBQ4sK?QvlpV}0lE7HZoBE_aAC)-8T2HMk4)7Qof@vS%JhR;^HeT|W zagRH&%Robw27i&wbR>m9Q_=dKW#b*kGm2KykUzcSp1BSzyQ(Ltw8foL8(P-6xnu$r zrU2zEh?8O|GS+e1eP4w_CbRw;$Q1wnwD@0p3k6IdP6Q0duzRcLMMe!5i@#CoO=Zv# zEuC?$c>XzD9?^!GGj(RclMjo=!-LnXXfNt5Xx0ym6{{$6g!Y%#)LKDo#7NX2tMj~I zPL7=NgJB?V3Dd6IUzVwVHCUl~k1Iy*cgyKCpP$OJE$Qrb7)Vj-wpqn&uow4{+{N1^7ugq_DMSJx)ydJ?j>{p z)>q)R*!n8UL!u+9T`w{zYMYXRSUPAjLTeQ?-0ow2Yq`?>{GKMubCnO zde7oZnQatpw(n`=Xs&y+$WDH?pO2tNU|xCh!Cg+JG^E|odCn#hsL#K$jGZ`7LxTE zrRYl;Hw~ucwg(Zdv__wsb%q=Tv-vVUEqdd=LwpzFVD)%rd8)M$lE8Dfj*|yYTTDdG zUwNs!{=TgB=mHi-G8Vb8sU84JV@O>O0tVP{8aG{!^;lyk0Iue?Fk}FwKe3hjoCR&P z=Jog}W$cT@_FZX-DgR{xc)+P!zxwLldtY+Hsd0Sad78`@ zZvA(YR=d${hrPo(3C}JL{|Z`7>pUpuBnK!C{gb%Iet6dQTfO&eM8 zhs|TaI|BF2klSMhr@0+5u`Z^4yYD2^H}xRA=}OmJ3`}zj=xEl^iE7YxEy%RU!Bt$y?PqLJyQ7rf4ow^X>GQLap_V5lOgpN5*~T! zqw7cHh0)>%rRZ+%6Ot%ZtreB!LuJ|grbT8}_&fJ>GLt-VLk%qfH~q?ySZjAP(`%0C=RzV^;6Jge1}sPMsZSj_Cz8~ zZ&84dby+h`QubPdmo%5cB$~* z{m(PIQJPvahM|a#_3}A#(}V71mS$2nx%IyM;=M*?YUO@>bPlDrS6kRz8fUC1*XHJo zk#w=Xyb}^WLZxpmf8k|pkTTb%LCQ?9MAhA1V=TLJ(cXJ+hhU$-E8UlycKd}_^c|@5 z=~?`;3_K1kkZP_u=k7hQf5$V&lCI{8W>w2lpxZrT^Jsxg(2R&Y!1@ofPyapU{i0Vm^|=T*;v-c zw@U9~dieBNU+R=@NO&ZD(G8&Szam0>3*Jl(6&>dqiik5jwg-7(d7vmmICOVB39X%w z#s(Y(=M@}p@Hlq(4T)_A2&O&uR@-JR;fo=MF5Gl8 zo5|`y9WoPSKFTqO#6e2B3ov0JFI!NEJc~^@P?Bgl-8{_Lu0`=oez(N?ZW&G1$?~73 zfIHD#kzM11>YF5*cF#ZGfsm4>F0RHwL`~It)*vCcrrZ~GxBemW0=z}o@d67eM5xme z;N!Y|=DNbP@oYmw$M)vDQ1e$FBtQ=ANpPAea%SWKJ+HlFUp#(_!JV}#4|03vi&(ie ziZ-DLBGrH>5Fd|yVedGD52f#&NQJf$@eO&yp0w9)jwfA%mq=>*x2lSyT2s>H!o#EU zw{;TD4Xj^$@cYv70^V>H0!`Sg^Q|`Xm5JQ0wTkX`(gBHwBH2fhVu(p7nz>M1oI35e zB5wP~ojoMugX{Hf&~I_Gj~CB_aY{DFX8g$Ex9)_Y!hOVv)j3=|c(GEKJ>57!x)K$` zkH^L!MovUG#0+$41t1TH5$Om|1R*zYo0V>{36B zn~Ry*a^BASfMH6e6PKZwp(Y6KjZx^$lJ4yBq%y-IYtB$NzGtWn!gjDs23pl$-hV2| z-09s=2gn12y@a6TYhWS4H}|*AOo}|^V&b5Ez2d(o1gML@Y6GXD0o^U$*mSN1`u>dw zZuvbW2;iDO!!G3$f8nI9<`Ga_67Q>j#)P#MmSG0|GY(LP-?h4+tlB*LbbvF+U*>Xx z`6lff)9-#~?$(rD1*L4I!Iv)$O%4#BI0l<>YSido(1c`TU;Y`cV6NvL1j^1SnmsYk zWA)9k35?uPchlP*l2fxMQYX`fyYH;-dcA7GY^>T?;51;J;pgVkwY3t6VFO{ZHkeI0 zK7(ml1#7#Hdz_e2dO+nTre8&bNm_7(7oC1C*5goAj>GhNv4@<-oxaq~9zVplZj(J( zSDV(g1vL4wYvh3Qcg28=h!@E?V8)637CD;>WBoL;zWA#H-I)Jfx)MOw=w!O{LDFms!F*=&lR*-6-yp{wRz0k+GhFIzyI~f^El8u%!#54{c{(*TKU-x}k}}<7?~} z^a)oVD6|PM=Z+&<{!0vwahXe1aQl4uMXjO<6#kkL_PHIP+7CEMVKq@kx5RbH&9BWU z17JrJ1VLYWmm=X@<{N1`JER3*e28Ih1j?C*bk>q^!5dfx4*)J&O%=r!RofCt@Y z_)6R}TE(~6_9|+7tW_tHbbHI1cZdU{0HRJvpUy^7Dw1$LUK<-}_Cnysq2?!&F)Sso z-m)ggzu{;*9^=YzNusiBhqm0-BcGgr3w*#@!FrHLxIky$_p?uwy{`A#us7mU1(lwzBVVCP;;{ME zGU>*6uM+jvo$l4JNNY8iw$crnzr&>P&zkaIt=9#a3N>?42-s{hf(A@_pbC3v+WIjS z@c)4FrGSryCO6?;vJ3-I z2aeS4_Jb^-fK!x#jQev?fCeX3-;dEhmpBge(frRMYS*xr|y@&B5Cj)e1ix!_MQ){8W9YQ-a-bmjcf-?(SpiN_!>3IptVbH)U0v>@bh50Q7TsBfM{$2UlzK0-iCH#8%sLZii{-nfrqT#)# z-b)1Ukmje`ttvLop@?6n6_qF!j`~@wo^aH4>BVRUNs)S6v_h{a-8$ih%GdOYW8sLua|7}s)lnH0FiC(hJ=?;L22 z$Cy(9!2yZNeNLmMnfz*aTT~@~XFZRZ#&w53I14qDUvrYxNz#e%_s|+)!A$F!$w(8m z$L&G!e%ZC#9cW^G_l$AYM+t$RLcFUZ^3FXWeB&3NX}3u+!F^xB%bZ#41|#-MVm;I=fC`s{cjUy?~9^1_&ak$ z17laU-z?PvEZjX6fBZ}W%?VOGMp%56?EU6uEa68!GxDsxLVK$H%U=HTw{tTwRYVPB zz9JPUxVIWfnE>Zq7usy4M|dP3fsxZ>NDGo0-G&(#?Tq@QiKtS)!ec1@LBPzH*^6Dm z``(4Bao7~BCURKA*RSD~kSam(Qiq>?gPkKAxy_R6TPFNz!iHHy;lj$vTgj9MKe{kU z^GX7kJ=RX%o#W`CRRtl1$OkYI2@Z)%c<6 z1zk>Cf)koFYgJkhKE^Xx=);JwPmN#sAG*$ry*KF9i-kXKv=W(;vU!KyA#Q|Gk#tn{@C&{gTGW??Uv@%A7*+* zn6I9`Ae~Ug;ZC$bh8Fu_wND!Cl4jWyI1#Jy0IX3n# zIR!S0H7|{P>Jl$OXj_^?!&_(8%iW~*nMl6*&PA7N#@n|0YGNmnBzjh+5?2Q<93|;B zrJ4@Y9KY%8AA_MZS9l8I8TgCBq<&%;>I;V9XhU{$5fBxnHW&C(^AF{Xedrzd_ha$G zlaXIHi3f&_@14B(qiTZ{hE%W_kV{lz29fVWY%?EegRNnE-F9!6U|(8ZqI;FJyAJNW zs(q=`ht^o}DTuP(9idA7;ILaJH(26(DDB2yr^Q&`;^PL{KHKSbvl@{lgpDG59s{odS`jW>!?V=X zUiI##Ca@~ck+%{=6>!^Lqm}lIi3_mS3c2HUX%B)X4&r=|9e=A=fCFLr5BZc*w7z?pX$?Rzi%&!4_NmXj=G*P{l<%1%9eKZHBS}r@Cr6} z4M7`K(To3N|8nKj*h5Stuvdo7f(0Fo@&}QoV~yv|md*}787t(sCt9;db_&|{X(_=R zh3}C6(nrtt;V2?goV)(pv&91$_@IRDFZOMvV=WdvPi=TxDWg)4vy4sd&!uxXDT*k% zJ~i<p|#Ns-mNHyEna+$=sDVRmt?Ms;j#LrmD? zirRbc|F{1AqJN<4^`izz#sS(HsjuL0If09pYi@0tT7fv%uh=RpkIS$QmJDyp$ER)* zMmRKYUACNfFc^#Yd2BZ|Bf?j1rpciJ37X`&iN8T4Z8_ecxxRZUN0z2x27FZb8zi^Z zPlNOO1fh>;)5N5xIDDWD{{^Q+M6J^h`)%rUb7uENVFj@4O|{AMj7M zhqz1XOokg=hNQT zp)Y|Fo_pzL*CmkjJiy(kW00f5?nOfIU}1}g8@O;JpO>xSEkjtPId30EU!uvYag#h- zP6KdyI7cr0&Xi&ie)g$DUx(ax@u>7=aT!vPXh$bcpa*cat=Tz(QkG_wvg?@$J}>Xq zh5kaoQdhfak32OrMG%;N?%F#EZCQF57tCSVV{otAzi)tl$+PFxe-m_^0~5Vz56Su7 zb%GS%9zp_4!c!h=uj3ideA9D>pW#@tjM`N3+=eN8Y*P9`+GHkpxfx;>+ds z$c}St5|gN^z+?^9H)bSY*pWcDzyTVEarFT;KSAha6BY2pK@bh$ zR6m0ddVUr*)3yOT)C>5hhS~>Q+0&oD(_|&CjvEQ5 zIWHJLzAEj!fWp&~>j4`8C=?Q2l1K1)THZmFtJkKdq$mz);54JaT?mf;%fl6E_`Cly z48G^x(m&EVk&S>Ca9Cyshg{ULLciKDAE}*JzIiIF9lU_8Dw3+ z#0$}a0?#Kw)lgQDVKlh7m^KP-Ty0lI$^BMp4n%g+O3J*B&sEZ&_zpJ!tb~W$aJTyw z((JP*6^HzHbBeBQTYPM8{8nr)?#5j;g4d7X@hA{0g?e@MkUuWs6 z>hnA&qer`{9P*beysiPQ0c$d!m%>XsucEj|1jHUnuC$-Ko`mL)_vf*cMt!z0*eIfY zA&1$<_rFE;@K$&z2RIV$a#Qb{Qk%B9AVZ!0rmox;ZS(<*pQ2SHeC(wb1zafJk1oO7 zmnm;X^8}h@?wV{lr!@($;{E?u@d1OdNK*ee!(a9lRXU#kmbhvTY}|JP*hlx_FuESL zI6WhMhONjh6@W?L5o4XHHQ8GA;{X4e-{R5E)8&C*5`jDy1`u=ZkbXab3|jC;aKWIt zBOhfVoEV7|)&)&h)ncyV;QwvFD@HK=>$^R#G7r{faqRzn`5~p-z{o`w>yE3iz$#4_6YqP%GB#nyZygpS<2xSKA`Q7 z{)KF@^r@}hwzR{GF$F4B?dAX7dyIAJ^Yx1$IF_&CPM~T2|Gw=X1MuC*fMj!ZF&{dI zIk1S8BOg^l_^ki$YD`4sx`|64)@$rU`6yG)L(P3De_V48o@s7APDXPl!6JxhSPjK) zZbOYFJ%fXjjxZU!Z*d52DQS861jHcVLqWqvhgF1uvF6W87vi|_5d(Y_ Date: Mon, 7 Mar 2022 14:46:03 +0900 Subject: [PATCH 62/76] Fix GraphicsView Slider --- .../GraphicsView/SliderDrawable.cs | 4 +++- test/NUIExGallery/TC/GraphicsViewTest.cs | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/SliderDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/SliderDrawable.cs index 4dd51c7..bcf2b26 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/SliderDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/SliderDrawable.cs @@ -29,7 +29,9 @@ public double ValueRate { get { - return View.Value / View.Maximum; + double start = View.Minimum; + double diff = View.Maximum - View.Minimum; + return (View.Value - start) / diff; } set { diff --git a/test/NUIExGallery/TC/GraphicsViewTest.cs b/test/NUIExGallery/TC/GraphicsViewTest.cs index 9bf650a..876b4ab 100644 --- a/test/NUIExGallery/TC/GraphicsViewTest.cs +++ b/test/NUIExGallery/TC/GraphicsViewTest.cs @@ -224,6 +224,22 @@ public override View Run() view.Add(slider1); } + { + var slider1 = new Slider + { + Margin = 5, + Value = 0, + Minimum = -20, + Maximum = 10, + SizeHeight = 50, + MaximumTrackColor = Color.Red, + MinimumTrackColor = Color.Green, + ThumbColor = Color.Yellow, + WidthSpecification = LayoutParamPolicies.MatchParent, + }; + view.Add(slider1); + } + view.Add(new Label { Padding = new Extents(10, 0, 10, 0), From a752f013f0c1b57a95b2389a853d7e6fcfe76263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=EA=B7=BC/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Tue, 15 Mar 2022 12:19:57 +0900 Subject: [PATCH 63/76] Updated MauiGraphics version to 6.0.200-preview.14.1092 (#126) --- Versions.prop | 2 +- .../GraphicsView/ActivityIndicatorDrawable.cs | 4 ++-- .../GraphicsView/ButtonDrawable.cs | 10 +++++----- .../GraphicsView/CheckBoxDrawable.cs | 8 ++++---- .../GraphicsView/EditorDrawable.cs | 8 ++++---- .../GraphicsView/EntryDrawable.cs | 14 +++++++------- .../GraphicsView/GraphicsViewDrawable.cs | 2 +- .../GraphicsView/MaterialIconDrawable.cs | 4 ++-- .../GraphicsView/ProgressBarDrawable.cs | 6 +++--- .../GraphicsView/RefreshIconDrawable.cs | 4 ++-- .../GraphicsView/RippleEffectDrawable.cs | 12 ++++++------ .../GraphicsView/SliderDrawable.cs | 16 ++++++++-------- .../GraphicsView/StepperDrawable.cs | 14 +++++++------- .../GraphicsView/SwitchDrawable.cs | 6 +++--- .../GraphicsView/SkiaGraphicsView.cs | 2 +- 15 files changed, 56 insertions(+), 56 deletions(-) diff --git a/Versions.prop b/Versions.prop index 700911b..efefb9d 100644 --- a/Versions.prop +++ b/Versions.prop @@ -1,5 +1,5 @@ - 6.0.200-preview.13.935 + 6.0.200-preview.14.1092 diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/ActivityIndicatorDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/ActivityIndicatorDrawable.cs index a974a8a..6b3f688 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/ActivityIndicatorDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/ActivityIndicatorDrawable.cs @@ -22,7 +22,7 @@ public ActivityIndicatorDrawable(IActivityIndicator view) float MaterialActivityIndicatorSweepAngle { get; set; } float MaterialActivityIndicatorLastStartAngle { get; set; } - public override void Draw(ICanvas canvas, RectangleF dirtyRect) + public override void Draw(ICanvas canvas, RectF dirtyRect) { DrawMaterialActivityIndicator(canvas, dirtyRect); } @@ -65,7 +65,7 @@ public void UpdateAnimation(bool animate) materialActivityIndicatorAngleAnimation.Commit(this, "MaterialActivityIndicator", length: 1400, repeat: () => true, finished: (l, c) => materialActivityIndicatorAngleAnimation = null); } - void DrawMaterialActivityIndicator(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialActivityIndicator(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/ButtonDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/ButtonDrawable.cs index b694c6f..32d23bb 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/ButtonDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/ButtonDrawable.cs @@ -11,7 +11,7 @@ public class ButtonDrawable : GraphicsViewDrawable const float MaterialShadowOffset = 3f; readonly RippleEffectDrawable _rippleEffect; - RectangleF _backgroundRect; + RectF _backgroundRect; public ButtonDrawable(IButton view) { @@ -22,7 +22,7 @@ public ButtonDrawable(IButton view) IButton View { get; } - public override void Draw(ICanvas canvas, RectangleF dirtyRect) + public override void Draw(ICanvas canvas, RectF dirtyRect) { DrawMaterialButtonBackground(canvas, dirtyRect); DrawMaterialButtonText(canvas, dirtyRect); @@ -57,7 +57,7 @@ public override void OnTouchUp(GPoint point) _rippleEffect.OnTouchUp(point); } - void DrawMaterialButtonBackground(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialButtonBackground(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); @@ -74,10 +74,10 @@ void DrawMaterialButtonBackground(ICanvas canvas, RectangleF dirtyRect) canvas.RestoreState(); - _backgroundRect = new RectangleF(x, y, width, height); + _backgroundRect = new RectF(x, y, width, height); } - void DrawMaterialButtonText(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialButtonText(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/CheckBoxDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/CheckBoxDrawable.cs index 84f2c7b..02f3f27 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/CheckBoxDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/CheckBoxDrawable.cs @@ -16,7 +16,7 @@ public CheckBoxDrawable(ICheckBox view) ICheckBox View { get; } - public override void Draw(ICanvas canvas, RectangleF dirtyRect) + public override void Draw(ICanvas canvas, RectF dirtyRect) { DrawMaterialCheckBoxBackground(canvas, dirtyRect); DrawMaterialCheckBoxMark(canvas, dirtyRect); @@ -44,7 +44,7 @@ public override void OnTouchDown(GPoint point) View.IsChecked = !View.IsChecked; } - void DrawMaterialCheckBoxBackground(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialCheckBoxBackground(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); @@ -87,7 +87,7 @@ void DrawMaterialCheckBoxBackground(ICanvas canvas, RectangleF dirtyRect) canvas.RestoreState(); } - void DrawMaterialCheckBoxMark(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialCheckBoxMark(ICanvas canvas, RectF dirtyRect) { if (View.IsChecked) { @@ -115,7 +115,7 @@ void DrawMaterialCheckBoxMark(ICanvas canvas, RectangleF dirtyRect) } } - void DrawMaterialCheckBoxText(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialCheckBoxText(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/EditorDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/EditorDrawable.cs index c4ce75a..4a2b0c7 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/EditorDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/EditorDrawable.cs @@ -45,7 +45,7 @@ float PlaceholderFontSize } } - public override void Draw(ICanvas canvas, RectangleF dirtyRect) + public override void Draw(ICanvas canvas, RectF dirtyRect) { DrawMaterialEditorBackground(canvas, dirtyRect); DrawMaterialEditorBorder(canvas, dirtyRect); @@ -67,7 +67,7 @@ public override void OnUnfocused() AnimateMaterialPlaceholder(false); } - void DrawMaterialEditorBackground(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialEditorBackground(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); @@ -87,7 +87,7 @@ void DrawMaterialEditorBackground(ICanvas canvas, RectangleF dirtyRect) canvas.RestoreState(); } - void DrawMaterialEditorBorder(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialEditorBorder(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); @@ -114,7 +114,7 @@ void DrawMaterialEditorBorder(ICanvas canvas, RectangleF dirtyRect) canvas.RestoreState(); } - void DrawMaterialEditorPlaceholder(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialEditorPlaceholder(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/EntryDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/EntryDrawable.cs index 15d4c94..a63220f 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/EntryDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/EntryDrawable.cs @@ -19,7 +19,7 @@ public class EntryDrawable : GraphicsViewDrawable, IAnimatable float _placeholderY; float _placeholderFontSize; - RectangleF _indicatorRect; + RectF _indicatorRect; public EntryDrawable(IEntry view) { @@ -51,7 +51,7 @@ float PlaceholderFontSize } } - public override void Draw(ICanvas canvas, RectangleF dirtyRect) + public override void Draw(ICanvas canvas, RectF dirtyRect) { DrawMaterialEntryBackground(canvas, dirtyRect); DrawMaterialEntryBorder(canvas, dirtyRect); @@ -82,7 +82,7 @@ public override void OnTouchDown(GPoint point) View.Text = string.Empty; } - void DrawMaterialEntryBackground(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialEntryBackground(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); @@ -100,7 +100,7 @@ void DrawMaterialEntryBackground(ICanvas canvas, RectangleF dirtyRect) canvas.RestoreState(); } - void DrawMaterialEntryBorder(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialEntryBorder(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); @@ -124,7 +124,7 @@ void DrawMaterialEntryBorder(ICanvas canvas, RectangleF dirtyRect) canvas.RestoreState(); } - void DrawMaterialEntryPlaceholder(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialEntryPlaceholder(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); @@ -151,7 +151,7 @@ void DrawMaterialEntryPlaceholder(ICanvas canvas, RectangleF dirtyRect) canvas.RestoreState(); } - void DrawMaterialEntryIndicators(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialEntryIndicators(ICanvas canvas, RectF dirtyRect) { if (!string.IsNullOrEmpty(View.Text)) { @@ -175,7 +175,7 @@ void DrawMaterialEntryIndicators(ICanvas canvas, RectangleF dirtyRect) canvas.RestoreState(); - _indicatorRect = new RectangleF(x - radius, y - radius, radius * 2, radius * 2); + _indicatorRect = new RectF(x - radius, y - radius, radius * 2, radius * 2); canvas.SaveState(); diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/GraphicsViewDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/GraphicsViewDrawable.cs index 52c57ad..14171b6 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/GraphicsViewDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/GraphicsViewDrawable.cs @@ -11,7 +11,7 @@ public abstract class GraphicsViewDrawable : IDrawable, IMeasurable, IDisposable public bool IsEnabled { get; set; } = true; - public abstract void Draw(ICanvas canvas, RectangleF dirtyRect); + public abstract void Draw(ICanvas canvas, RectF dirtyRect); public virtual void OnTouchDown(GPoint point) { } diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/MaterialIconDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/MaterialIconDrawable.cs index b942f78..ba4af91 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/MaterialIconDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/MaterialIconDrawable.cs @@ -92,7 +92,7 @@ public MaterialIconDrawable() public MaterialIcons Icon { get; set; } public Color Color { get; set; } - public override void Draw(ICanvas canvas, RectangleF dirtyRect) + public override void Draw(ICanvas canvas, RectF dirtyRect) { DrawIcon(canvas, dirtyRect); } @@ -103,7 +103,7 @@ public override TSize Measure(double availableWidth, double availableHeight) } - void DrawIcon(ICanvas canvas, RectangleF dirtyRect) + void DrawIcon(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/ProgressBarDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/ProgressBarDrawable.cs index fc5a799..50cd492 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/ProgressBarDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/ProgressBarDrawable.cs @@ -15,7 +15,7 @@ public ProgressBarDrawable(IProgressBar view) IProgressBar View { get; } - public override void Draw(ICanvas canvas, RectangleF dirtyRect) + public override void Draw(ICanvas canvas, RectF dirtyRect) { DrawMaterialProgressTrack(canvas, dirtyRect); DrawMaterialProgressBar(canvas, dirtyRect); @@ -26,7 +26,7 @@ public override TSize Measure(double availableWidth, double availableHeight) return new TSize(availableWidth, MaterialTrackHeight * DeviceInfo.ScalingFactor); } - void DrawMaterialProgressTrack(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialProgressTrack(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); @@ -42,7 +42,7 @@ void DrawMaterialProgressTrack(ICanvas canvas, RectangleF dirtyRect) canvas.RestoreState(); } - void DrawMaterialProgressBar(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialProgressBar(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs index 51c722e..43b1fc6 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/RefreshIconDrawable.cs @@ -52,7 +52,7 @@ public Color BackgroundColor /// Implementation of the IDrawable.Draw() method. /// This method defines how to draw a refresh icon. /// - public override void Draw(ICanvas canvas, RectangleF dirtyRect) + public override void Draw(ICanvas canvas, RectF dirtyRect) { DrawRefreshIcon(canvas, dirtyRect); } @@ -99,7 +99,7 @@ void AbortRunningAnimation() SendInvalidated(); } - void DrawRefreshIcon(ICanvas canvas, RectangleF dirtyRect) + void DrawRefreshIcon(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); canvas.StrokeSize = StrokeWidth; diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/RippleEffectDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/RippleEffectDrawable.cs index 3bed554..169b99b 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/RippleEffectDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/RippleEffectDrawable.cs @@ -15,13 +15,13 @@ public RippleEffectDrawable() RippleColor = Colors.White; } - public override void Draw(ICanvas canvas, RectangleF dirtyRect) + public override void Draw(ICanvas canvas, RectF dirtyRect) { - if ((ClipRectangle == RectangleF.Zero || ClipRectangle.Contains(TouchPoint)) && RippleEffectSize > 0) + if ((ClipRectangle == RectF.Zero || ClipRectangle.Contains(TouchPoint)) && RippleEffectSize > 0) { canvas.SaveState(); - if (ClipRectangle == RectangleF.Zero) + if (ClipRectangle == RectF.Zero) ClipRectangle = dirtyRect; canvas.ClipRectangle(ClipRectangle); @@ -41,7 +41,7 @@ public override TSize Measure(double availableWidth, double availableHeight) public GColor RippleColor { get; set; } - public RectangleF ClipRectangle { get; set; } + public RectF ClipRectangle { get; set; } public PointF TouchPoint { get; set; } @@ -63,14 +63,14 @@ public override void OnTouchDown(GPoint point) public override void OnTouchUp(GPoint point) { - if (ClipRectangle == RectangleF.Zero || ClipRectangle.Contains(TouchPoint)) + if (ClipRectangle == RectF.Zero || ClipRectangle.Contains(TouchPoint)) AnimateDrawRipple(); } void AnimateDrawRipple() { var from = 0; - var to = ClipRectangle != RectangleF.Zero ? ClipRectangle.Width : 1000; + var to = ClipRectangle != RectF.Zero ? ClipRectangle.Width : 1000; var thumbSizeAnimation = new Animation(v => RippleEffectSize = (int)v, from, to, easing: Easing.SinInOut); thumbSizeAnimation.Commit(this, "RippleEffectAnimation", rate:32, length: 350, finished: (l, c) => diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/SliderDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/SliderDrawable.cs index bcf2b26..abbbff6 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/SliderDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/SliderDrawable.cs @@ -22,8 +22,8 @@ public SliderDrawable(ISlider view) float MaterialFloatThumb { get; set; } = NormalMaterialThumbSize; - public RectangleF ThumbRect { get; set; } - public RectangleF TrackRect { get; set; } + public RectF ThumbRect { get; set; } + public RectF TrackRect { get; set; } public double ValueRate { @@ -41,7 +41,7 @@ public double ValueRate } } - public override void Draw(ICanvas canvas, RectangleF dirtyRect) + public override void Draw(ICanvas canvas, RectF dirtyRect) { DrawMaterialSliderTrackBackground(canvas, dirtyRect); DrawMaterialSliderTrackProgress(canvas, dirtyRect); @@ -84,7 +84,7 @@ void UpdateValue(GPoint point) SendInvalidated(); } - void DrawMaterialSliderTrackBackground(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialSliderTrackBackground(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); @@ -101,10 +101,10 @@ void DrawMaterialSliderTrackBackground(ICanvas canvas, RectangleF dirtyRect) canvas.RestoreState(); - TrackRect = new RectangleF(x, y, width, height); + TrackRect = new RectF(x, y, width, height); } - protected virtual void DrawMaterialSliderTrackProgress(ICanvas canvas, RectangleF dirtyRect) + protected virtual void DrawMaterialSliderTrackProgress(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); @@ -124,7 +124,7 @@ protected virtual void DrawMaterialSliderTrackProgress(ICanvas canvas, Rectangle canvas.RestoreState(); } - protected virtual void DrawMaterialSliderThumb(ICanvas canvas, RectangleF dirtyRect) + protected virtual void DrawMaterialSliderThumb(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); @@ -145,7 +145,7 @@ protected virtual void DrawMaterialSliderThumb(ICanvas canvas, RectangleF dirtyR canvas.RestoreState(); - ThumbRect = new RectangleF(x, y, MaterialFloatThumb, MaterialFloatThumb); + ThumbRect = new RectF(x, y, MaterialFloatThumb, MaterialFloatThumb); } public void AnimateMaterialThumbSize(bool increase) diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/StepperDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/StepperDrawable.cs index 508bb95..bf202e1 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/StepperDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/StepperDrawable.cs @@ -14,8 +14,8 @@ public class StepperDrawable : GraphicsViewDrawable const float MaterialStepperWidth = 110.0f; const float MaterialButtonMargin = 6.0f; - RectangleF _minusRect; - RectangleF _plusRect; + RectF _minusRect; + RectF _plusRect; bool _pressed; readonly RippleEffectDrawable _minusRippleEffect; @@ -40,7 +40,7 @@ public StepperDrawable(IStepper view) IStepper View { get; } - public override void Draw(ICanvas canvas, RectangleF dirtyRect) + public override void Draw(ICanvas canvas, RectF dirtyRect) { DrawMaterialStepperMinus(canvas, dirtyRect); DrawMaterialStepperPlus(canvas, dirtyRect); @@ -85,7 +85,7 @@ public override void OnTouchMove(GPoint point) } - void DrawMaterialStepperMinus(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialStepperMinus(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); @@ -116,10 +116,10 @@ void DrawMaterialStepperMinus(ICanvas canvas, RectangleF dirtyRect) canvas.RestoreState(); - _minusRect = new RectangleF(x, y, width, height); + _minusRect = new RectF(x, y, width, height); } - void DrawMaterialStepperPlus(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialStepperPlus(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); @@ -150,7 +150,7 @@ void DrawMaterialStepperPlus(ICanvas canvas, RectangleF dirtyRect) canvas.RestoreState(); - _plusRect = new RectangleF(x, y, width, height); + _plusRect = new RectF(x, y, width, height); } } } diff --git a/src/Tizen.UIExtensions.Common/GraphicsView/SwitchDrawable.cs b/src/Tizen.UIExtensions.Common/GraphicsView/SwitchDrawable.cs index 5c5938e..e8d24cf 100644 --- a/src/Tizen.UIExtensions.Common/GraphicsView/SwitchDrawable.cs +++ b/src/Tizen.UIExtensions.Common/GraphicsView/SwitchDrawable.cs @@ -20,7 +20,7 @@ public SwitchDrawable(ISwitch view) ISwitch View { get; } - public override void Draw(ICanvas canvas, RectangleF dirtyRect) + public override void Draw(ICanvas canvas, RectF dirtyRect) { DrawMaterialSwitchBackground(canvas, dirtyRect); DrawMaterialSwitchThumb(canvas, dirtyRect); @@ -50,7 +50,7 @@ float MaterialSwitchThumbPosition } } - void DrawMaterialSwitchBackground(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialSwitchBackground(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); @@ -78,7 +78,7 @@ void DrawMaterialSwitchBackground(ICanvas canvas, RectangleF dirtyRect) canvas.RestoreState(); } - void DrawMaterialSwitchThumb(ICanvas canvas, RectangleF dirtyRect) + void DrawMaterialSwitchThumb(ICanvas canvas, RectF dirtyRect) { canvas.SaveState(); diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/SkiaGraphicsView.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/SkiaGraphicsView.cs index 68566f4..3773793 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/SkiaGraphicsView.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/SkiaGraphicsView.cs @@ -42,7 +42,7 @@ protected virtual void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs e) _canvas.Canvas = skiaCanvas; _scalingCanvas.SaveState(); _scalingCanvas.Scale((float)DeviceInfo.ScalingFactor, (float)DeviceInfo.ScalingFactor); - _drawable.Draw(_scalingCanvas, new RectangleF(0, 0, width, height)); + _drawable.Draw(_scalingCanvas, new RectF(0, 0, width, height)); _scalingCanvas.RestoreState(); } } From 1cc528401c78687a23b983a506d50030921660e7 Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Tue, 15 Mar 2022 12:30:44 +0900 Subject: [PATCH 64/76] Fix Scrolled event when scrolled by snap --- .../CollectionView/CollectionView.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs b/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs index 610209b..6c4f6c3 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/CollectionView.cs @@ -279,6 +279,11 @@ protected virtual void InitializationComponent() ScrollView.ScrollAnimationEnded += OnScrollAnimationEnded; ScrollView.Relayout += OnLayout; + if (ScrollView is SnappableScrollView snappable) + { + snappable.SnapRequestFinished += OnSnapRequestFinished; + } + Add(ScrollView); } @@ -650,6 +655,11 @@ void OnScrollAnimationEnded(object? sender, ScrollEventArgs e) SendScrolledEvent(); } + void OnSnapRequestFinished(object? sender, EventArgs e) + { + SendScrolledEvent(); + } + void OnLayout(object? sender, EventArgs e) { //called when resized @@ -802,6 +812,8 @@ public SnappableScrollView(CollectionView cv) ScrollAnimationEnded += OnAnimationEnd; } + public event EventHandler? SnapRequestFinished; + CollectionView CollectionView { get; } ICollectionViewLayoutManager LayoutManager => CollectionView.LayoutManager!; Rect ViewPort => CollectionView.ViewPort; @@ -1003,6 +1015,7 @@ void ScrollTo(double target) var animation = new Animation(); animation.Duration = 200; animation.AnimateTo(ContentContainer, IsHorizontal ? "PositionX" : "PositionY", -(float)target); + animation.Finished += (s, e) => SnapRequestFinished?.Invoke(this, EventArgs.Empty); animation.Play(); } } From 1c0e6fd4daa21118df75c78f05963d93b8c79ddc Mon Sep 17 00:00:00 2001 From: Seungkeun Lee Date: Tue, 15 Mar 2022 17:18:09 +0900 Subject: [PATCH 65/76] Fix ViewHolder layout --- .../CollectionView/ViewHolder.cs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs b/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs index 2eff8e6..96f8dae 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs @@ -12,7 +12,7 @@ public enum ViewHolderState Focused, } - public class ViewHolder : View + public class ViewHolder : ViewGroup { ViewHolderState _state; bool _isSelected; @@ -40,7 +40,7 @@ public View? Content { _content.FocusGained -= OnContentFocused; _content.FocusLost -= OnContentUnfocused; - _content.Unparent(); + Children.Remove(_content); } _content = value; @@ -55,7 +55,7 @@ public View? Content _content.FocusGained += OnContentFocused; _content.FocusLost += OnContentUnfocused; - Add(_content); + Children.Add(_content); } } } @@ -92,12 +92,27 @@ public void ResetState() protected void Initialize() { - Layout = new AbsoluteLayout(); + if (Common.DeviceInfo.DeviceType == Common.DeviceType.TV) + { + Focusable = true; + } TouchEvent += OnTouchEvent; KeyEvent += OnKeyEvent; FocusGained += OnFocused; FocusLost += OnUnfocused; + LayoutUpdated += OnLayout; + } + + void OnLayout(object? sender, Common.LayoutEventArgs e) + { + var bounds = this.GetBounds(); + bounds.X = 0; + bounds.Y = 0; + foreach (var child in Children) + { + child.UpdateBounds(bounds); + } } void OnUnfocused(object? sender, EventArgs e) From dedf6cb57ae35a5fb369bd814826bcf018934a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=EA=B7=BC/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Tue, 12 Apr 2022 12:14:53 +0900 Subject: [PATCH 66/76] Fix Button Measure to reflect icon size (#129) --- src/Tizen.UIExtensions.NUI/Button.cs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Tizen.UIExtensions.NUI/Button.cs b/src/Tizen.UIExtensions.NUI/Button.cs index 1fd42e8..5b940ca 100644 --- a/src/Tizen.UIExtensions.NUI/Button.cs +++ b/src/Tizen.UIExtensions.NUI/Button.cs @@ -59,7 +59,27 @@ public Size Measure(double availableWidth, double availableHeight) // so, Button's measured size never smaller than before var textNaturalSize = TextLabel.NaturalSize; float buttonPadding = 46; - return new Size(textNaturalSize.Width + buttonPadding, textNaturalSize.Height + buttonPadding); + float horizontalPadding = buttonPadding; + float verticalPadding = buttonPadding; + + if (Icon != null) + { + if (IconRelativeOrientation == IconOrientation.Bottom || IconRelativeOrientation == IconOrientation.Top) + { + verticalPadding += Icon.NaturalSize.Height; + } + else + { + horizontalPadding += Icon.NaturalSize.Width; + } + if (IconPadding != null) + { + verticalPadding += IconPadding.Top + IconPadding.Bottom; + horizontalPadding += IconPadding.Start + IconPadding.End; + } + } + + return new Size(textNaturalSize.Width + horizontalPadding, textNaturalSize.Height + verticalPadding); } } } From 2b654d1963d4ac6963151b30e81e105af30616ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=EA=B7=BC/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Wed, 13 Apr 2022 14:32:40 +0900 Subject: [PATCH 67/76] Fix focus navigation (#130) --- .../ActionSheetPopup.cs | 10 ++++++ .../CollectionView/ViewHolder.cs | 2 +- .../GraphicsView/Button.cs | 12 +++++++ .../GraphicsView/CheckBox.cs | 12 +++++++ .../GraphicsView/MaterialIconButton.cs | 12 +++++++ .../GraphicsView/Switch.cs | 12 +++++++ src/Tizen.UIExtensions.NUI/MessagePopup.cs | 2 ++ src/Tizen.UIExtensions.NUI/NavigationStack.cs | 31 +++++++++++++++++-- src/Tizen.UIExtensions.NUI/Popup.cs | 20 +++++++++++- test/NUIExGallery/Main.cs | 12 ++++--- test/NUIExGallery/TC/ActionSheetPopupTest.cs | 7 +++++ 11 files changed, 123 insertions(+), 9 deletions(-) diff --git a/src/Tizen.UIExtensions.NUI/ActionSheetPopup.cs b/src/Tizen.UIExtensions.NUI/ActionSheetPopup.cs index b24a641..75ac746 100644 --- a/src/Tizen.UIExtensions.NUI/ActionSheetPopup.cs +++ b/src/Tizen.UIExtensions.NUI/ActionSheetPopup.cs @@ -85,6 +85,7 @@ protected override View CreateContent() var itemLabel = new Label { Text = item, + Focusable = true, HorizontalTextAlignment = TextAlignment.Center, PixelSize = (int)(25 * DeviceInfo.ScalingFactor), WidthSpecification = LayoutParamPolicies.MatchParent, @@ -101,6 +102,15 @@ protected override View CreateContent() } return false; }; + itemLabel.KeyEvent += (s, e) => + { + if (e.Key.State == Key.StateType.Up && (e.Key.KeyPressedName == "Return" || e.Key.KeyPressedName == "Enter")) + { + SendSubmit(item); + return true; + } + return false; + }; scrollview.ContentContainer.Add(itemLabel); scrollview.ContentContainer.Add(new View { diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs b/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs index 96f8dae..bdfec2a 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs @@ -139,7 +139,7 @@ void OnContentFocused(object? sender, EventArgs e) bool OnKeyEvent(object? source, KeyEventArgs e) { - if (e.Key.State == Key.StateType.Down && (e.Key.KeyPressedName == "Return" || e.Key.KeyPressedName == "Enter")) + if (e.Key.State == Key.StateType.Up && (e.Key.KeyPressedName == "Return" || e.Key.KeyPressedName == "Enter")) { RequestSelected?.Invoke(this, EventArgs.Empty); return true; diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/Button.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/Button.cs index b2ec023..8f907c2 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/Button.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/Button.cs @@ -15,7 +15,9 @@ public class Button : GraphicsView, IButton /// public Button() { + Focusable = true; Drawable = new ButtonDrawable(this); + KeyEvent += OnKeyEvent; } /// @@ -101,5 +103,15 @@ protected override bool OnTouch(object source, TouchEventArgs e) _lastPointState = state; return base.OnTouch(source, e); } + + bool OnKeyEvent(object source, KeyEventArgs e) + { + if (e.Key.State == Tizen.NUI.Key.StateType.Up && (e.Key.KeyPressedName == "Return" || e.Key.KeyPressedName == "Enter")) + { + Clicked?.Invoke(this, EventArgs.Empty); + return true; + } + return false; + } } } diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/CheckBox.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/CheckBox.cs index 6137640..9544f8e 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/CheckBox.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/CheckBox.cs @@ -14,8 +14,10 @@ public class CheckBox : GraphicsView, ICheckBox /// public CheckBox() { + Focusable = true; Text = string.Empty; Drawable = new CheckBoxDrawable(this); + KeyEvent += OnKeyEvent; } /// @@ -62,5 +64,15 @@ public string Text get => GetProperty(nameof(Text)); set => SetProperty(nameof(Text), value); } + + bool OnKeyEvent(object source, KeyEventArgs e) + { + if (e.Key.State == Tizen.NUI.Key.StateType.Up && (e.Key.KeyPressedName == "Return" || e.Key.KeyPressedName == "Enter")) + { + IsChecked = !IsChecked; + return true; + } + return false; + } } } diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs index 64e4151..e2a1b63 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs @@ -17,10 +17,12 @@ public class MaterialIconButton : GraphicsView /// public MaterialIconButton() { + Focusable = true; Drawable = new MaterialIconDrawable(); var measured = Drawable.Measure(double.PositiveInfinity, double.PositiveInfinity); SizeWidth = (float)measured.Width; SizeHeight = (float)measured.Height; + KeyEvent += OnKeyEvent; } /// @@ -100,5 +102,15 @@ protected override bool OnTouch(object source, TouchEventArgs e) _lastPointState = state; return base.OnTouch(source, e); } + + bool OnKeyEvent(object source, KeyEventArgs e) + { + if (e.Key.State == Tizen.NUI.Key.StateType.Up && (e.Key.KeyPressedName == "Return" || e.Key.KeyPressedName == "Enter")) + { + Clicked?.Invoke(this, EventArgs.Empty); + return true; + } + return false; + } } } diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/Switch.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/Switch.cs index 31f1255..f9d3c53 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/Switch.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/Switch.cs @@ -14,7 +14,9 @@ public class Switch : GraphicsView, ISwitch /// public Switch() { + Focusable = true; Drawable = new SwitchDrawable(this); + KeyEvent += OnKeyEVent; } /// @@ -61,5 +63,15 @@ public Color ThumbColor get => GetProperty(nameof(BackgroundColor)); set => SetProperty(nameof(BackgroundColor), value); } + + bool OnKeyEVent(object source, KeyEventArgs e) + { + if (e.Key.State == Tizen.NUI.Key.StateType.Up && (e.Key.KeyPressedName == "Return" || e.Key.KeyPressedName == "Enter")) + { + IsToggled = !IsToggled; + return true; + } + return false; + } } } diff --git a/src/Tizen.UIExtensions.NUI/MessagePopup.cs b/src/Tizen.UIExtensions.NUI/MessagePopup.cs index 653d977..a072047 100644 --- a/src/Tizen.UIExtensions.NUI/MessagePopup.cs +++ b/src/Tizen.UIExtensions.NUI/MessagePopup.cs @@ -104,6 +104,7 @@ protected override View CreateContent() Text = _cancel, SizeWidth = content.SizeWidth * 0.4f, HeightSpecification = LayoutParamPolicies.WrapContent, + Focusable = true, }; cancelButton.Clicked += (s, e) => SendSubmit(false); hlayout.Add(cancelButton); @@ -116,6 +117,7 @@ protected override View CreateContent() Text = _accept, SizeWidth = content.SizeWidth * 0.4f, HeightSpecification = LayoutParamPolicies.WrapContent, + Focusable = true, }; acceptButton.Clicked += (s, e) => SendSubmit(true); hlayout.Add(acceptButton); diff --git a/src/Tizen.UIExtensions.NUI/NavigationStack.cs b/src/Tizen.UIExtensions.NUI/NavigationStack.cs index 0ceb312..f7231e2 100644 --- a/src/Tizen.UIExtensions.NUI/NavigationStack.cs +++ b/src/Tizen.UIExtensions.NUI/NavigationStack.cs @@ -29,6 +29,11 @@ public NavigationStack() InternalStack = new List(); } + /// + /// Raised when top page was changed + /// + public event EventHandler? Navigated; + /// /// A stack of views /// @@ -69,6 +74,11 @@ public NavigationStack() /// public Action? PopAnimation { get; set; } + /// + /// Options to show the page that behind of the top page + /// + public bool ShownBehindPage { get; set; } = false; + /// /// Push a view on stack /// @@ -200,12 +210,29 @@ void UpdateTopView() { if (_lastTop != InternalStack.LastOrDefault()) { - _lastTop?.Hide(); + if (_lastTop != null) + { + if (!ShownBehindPage) + _lastTop.Hide(); + _lastTop.FocusableChildren = false; + } + _lastTop = InternalStack.LastOrDefault(); - _lastTop?.Show(); + + if (_lastTop != null) + { + _lastTop.Show(); + _lastTop.FocusableChildren = true; + } + SendNavigated(); } } + void SendNavigated() + { + Navigated?.Invoke(this, EventArgs.Empty); + } + void IAnimatable.BatchBegin() {} void IAnimatable.BatchCommit() {} diff --git a/src/Tizen.UIExtensions.NUI/Popup.cs b/src/Tizen.UIExtensions.NUI/Popup.cs index be1c46b..a036e9e 100644 --- a/src/Tizen.UIExtensions.NUI/Popup.cs +++ b/src/Tizen.UIExtensions.NUI/Popup.cs @@ -174,7 +174,11 @@ public void Open() PopupLayer.Add(this); IsOpen = true; s_openedPopup.Add(this); - FocusManager.Instance.SetCurrentFocusView(this); + var focusable = FindFocusableChild(this); + if (focusable != null) + FocusManager.Instance.SetCurrentFocusView(focusable); + else + FocusManager.Instance.SetCurrentFocusView(this); } /// @@ -244,5 +248,19 @@ bool OnContentTouch(object source, TouchEventArgs e) { return true; } + + View? FindFocusableChild(View view) + { + if (view.Focusable && !(view is Popup)) + return view; + + foreach (var child in view.Children) + { + var focusable = FindFocusableChild(child); + if (focusable != null) + return focusable; + } + return null; + } } } diff --git a/test/NUIExGallery/Main.cs b/test/NUIExGallery/Main.cs index 4ac5719..0ce501d 100644 --- a/test/NUIExGallery/Main.cs +++ b/test/NUIExGallery/Main.cs @@ -7,26 +7,27 @@ using System.Linq; using Tizen.NUI.Components; using Tizen.NUI.Binding; +using Tizen.UIExtensions.NUI; namespace NUIExGallery { class App : NUIApplication { - SimpleViewStack Stack { get; set; } + public static NavigationStack Stack { get; set; } protected override void OnCreate() { base.OnCreate(); FocusManager.Instance.EnableDefaultAlgorithm(true); Initialize(); - Stack.Push(CreateListPage(GetTestCases())); + _ = Stack.Push(CreateListPage(GetTestCases()), true); } void Initialize() { Window.Instance.KeyEvent += OnKeyEvent; - Stack = new SimpleViewStack + Stack = new NavigationStack { BackgroundColor = Color.White }; @@ -50,7 +51,7 @@ void Initialize() void RunTC(TestCaseBase tc) { - Stack.Push(tc.Run()); + _ = Stack.Push(tc.Run(), true); } View CreateListPage(IEnumerable tests) @@ -84,6 +85,7 @@ View CreateListPage(IEnumerable tests) { var itemView = new DefaultLinearItem(); + itemView.Focusable = true; itemView.BindingContext = item; itemView.Clicked += clicked; @@ -193,7 +195,7 @@ void OnKeyEvent(object sender, Window.KeyEventArgs e) if (Stack.Stack.Count > 1) { - Stack.Pop(); + Stack.Pop(true); } else { diff --git a/test/NUIExGallery/TC/ActionSheetPopupTest.cs b/test/NUIExGallery/TC/ActionSheetPopupTest.cs index 98c991f..d339d09 100644 --- a/test/NUIExGallery/TC/ActionSheetPopupTest.cs +++ b/test/NUIExGallery/TC/ActionSheetPopupTest.cs @@ -62,6 +62,8 @@ public override View Run() view.Add(btn3); btn3.Clicked += async (s, e) => { + App.Stack.ShownBehindPage = true; + _ = App.Stack.Push(new View(), false); try { var result = await new ActionSheetPopup("Choose", "Cancel", buttons: new string[]{ @@ -75,6 +77,11 @@ public override View Run() { _ = new MessagePopup("Result", $"Canceled", "OK").Open(); } + finally + { + App.Stack.ShownBehindPage = false; + _ = App.Stack.Pop(false); + } }; } From 96b7488decf24b8b1635e011d00a43d77c64be1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=EA=B7=BC/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Mon, 18 Apr 2022 16:43:45 +0900 Subject: [PATCH 68/76] Implement focus save/restore in NavigationStack (#131) --- src/Tizen.UIExtensions.NUI/NavigationStack.cs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/Tizen.UIExtensions.NUI/NavigationStack.cs b/src/Tizen.UIExtensions.NUI/NavigationStack.cs index f7231e2..471b717 100644 --- a/src/Tizen.UIExtensions.NUI/NavigationStack.cs +++ b/src/Tizen.UIExtensions.NUI/NavigationStack.cs @@ -16,6 +16,7 @@ namespace Tizen.UIExtensions.NUI public class NavigationStack : View, IAnimatable { View? _lastTop; + Dictionary> _focusStack = new Dictionary>(); /// /// /// Initializes a new instance of the class. @@ -86,9 +87,10 @@ public NavigationStack() /// Flags for animation public async Task Push(View view, bool animated) { + DidSaveFocus(); + view.WidthResizePolicy = ResizePolicyType.FillToParent; view.HeightResizePolicy = ResizePolicyType.FillToParent; - InternalStack.Add(view); Add(view); @@ -151,6 +153,7 @@ public async Task Pop(bool animated) } InternalStack.Remove(tobeRemoved); + _focusStack.Remove(tobeRemoved); Remove(tobeRemoved); UpdateTopView(); tobeRemoved.Dispose(); @@ -179,6 +182,7 @@ public void Clear() child.Dispose(); } InternalStack.Clear(); + _focusStack.Clear(); _lastTop = null; } @@ -203,9 +207,33 @@ public void Insert(View before, View view) public void Pop(View view) { InternalStack.Remove(view); + _focusStack.Remove(view); Remove(view); } + protected virtual void DidSaveFocus() + { + if (Top != null) + { + var currentFocused = FocusManager.Instance.GetCurrentFocusView(); + if (currentFocused != null) + { + _focusStack[Top] = new WeakReference(currentFocused); + } + } + } + + protected virtual void DidRestoreFocus() + { + if (Top != null) + { + if (_focusStack.ContainsKey(Top) && _focusStack[Top].TryGetTarget(out var target)) + { + FocusManager.Instance.SetCurrentFocusView(target); + } + } + } + void UpdateTopView() { if (_lastTop != InternalStack.LastOrDefault()) @@ -225,6 +253,7 @@ void UpdateTopView() _lastTop.FocusableChildren = true; } SendNavigated(); + DidRestoreFocus(); } } From 3b088e6b1880956d17d6f46587ec843272646459 Mon Sep 17 00:00:00 2001 From: Sunghyun Min Date: Tue, 19 Apr 2022 20:30:04 +0900 Subject: [PATCH 69/76] [Elmsharp] Update INavigationDrawer for FlyoutView --- src/Tizen.UIExtensions.ElmSharp/DrawerBox.cs | 35 ++++++++++++-- .../Shell/INavigationDrawer.cs | 4 ++ .../Shell/TVNavigationDrawer.cs | 47 ++++++++++++++++--- 3 files changed, 75 insertions(+), 11 deletions(-) diff --git a/src/Tizen.UIExtensions.ElmSharp/DrawerBox.cs b/src/Tizen.UIExtensions.ElmSharp/DrawerBox.cs index 3c93203..520af25 100644 --- a/src/Tizen.UIExtensions.ElmSharp/DrawerBox.cs +++ b/src/Tizen.UIExtensions.ElmSharp/DrawerBox.cs @@ -56,10 +56,15 @@ public class DrawerBox : EBox /// EvasObject _mainWidget; + ///// + ///// The property value. + ///// + double _splitRatio = 0; + /// - /// The property value. + /// The property value. /// - double _splitRatio = 0; + double _drawerWidth = -1; /// /// The property value. @@ -78,6 +83,8 @@ public class DrawerBox : EBox /// bool _isGestureEnabled = true; + double _screenWidth = DeviceInfo.PixelScreenSize.Width; + /// /// Occurs when the Drawer is shown or hidden. /// @@ -281,6 +288,28 @@ public double SplitRatio if (_splitRatio != value) { _splitRatio = value; + _drawerWidth = (_splitRatio > 0) ? _screenWidth * _splitRatio : 0; + ConfigureLayout(); + } + } + } + + /// + /// Gets or Sets the width of the drawer that the DrawerBox takes in split mode. + /// + /// The width of the drawer. + public double DrawerWidth + { + get + { + return _drawerWidth; + } + set + { + if (_drawerWidth != value) + { + _drawerWidth = value; + _splitRatio = (_drawerWidth > 0) ? _drawerWidth / _screenWidth : 0; ConfigureLayout(); } } @@ -348,8 +377,6 @@ void ConfigureLayout() // the structure for split mode and for popover mode looks differently if (IsSplit) { - if (_panel != null) - _splitPane.SetLeftPart(_drawerBox, true); _splitPane.SetRightPart(_contentBox, true); _splitPane.Proportion = (SplitRatio > 0) ? SplitRatio : this.GetSplitRatio(); diff --git a/src/Tizen.UIExtensions.ElmSharp/Shell/INavigationDrawer.cs b/src/Tizen.UIExtensions.ElmSharp/Shell/INavigationDrawer.cs index 2a42f5b..b3b8aca 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Shell/INavigationDrawer.cs +++ b/src/Tizen.UIExtensions.ElmSharp/Shell/INavigationDrawer.cs @@ -16,6 +16,10 @@ public interface INavigationDrawer bool IsSplit { get; set; } + double DrawerWidth { get; set; } + + bool IsGestureEnabled { get; set; } + DrawerBehavior DrawerBehavior { get; set; } event EventHandler Toggled; diff --git a/src/Tizen.UIExtensions.ElmSharp/Shell/TVNavigationDrawer.cs b/src/Tizen.UIExtensions.ElmSharp/Shell/TVNavigationDrawer.cs index 18c849a..cebac93 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Shell/TVNavigationDrawer.cs +++ b/src/Tizen.UIExtensions.ElmSharp/Shell/TVNavigationDrawer.cs @@ -22,10 +22,12 @@ public class TVNavigationDrawer : EBox, INavigationDrawer, IAnimatable bool _isOpen; bool _isSplit; double _drawerRatio; - - double _OpenRatio = -1; + + double _drawerWidth = -1; + double _openRatio = -1; double _closeRatio = -1; + double _screenWidth = DeviceInfo.PixelScreenSize.Width; /// /// Initializes a new instance of the class. @@ -148,16 +150,34 @@ public DrawerBehavior DrawerBehavior } /// - /// Gets or Sets the portion of the screen then the drawer is opened. + /// Gets or Sets the portion of the screen when the drawer is opened. /// public double OpenRatio { - get => _OpenRatio; + get => _openRatio; + set + { + if (_openRatio != value) + { + _openRatio = value; + _drawerWidth = (_openRatio > 0) ? _screenWidth * _openRatio : 0; + OnLayout(); + } + } + } + + /// + /// Gets or Sets the width of the drawer when the drawer is opened. + /// + public double DrawerWidth + { + get => _drawerWidth; set { - if (_OpenRatio != value) + if (_drawerWidth != value) { - _OpenRatio = value; + _drawerWidth = value; + _openRatio = (_drawerWidth > 0) ? _drawerWidth / _screenWidth : 0; OnLayout(); } } @@ -179,6 +199,19 @@ public double CloseRatio } } + /// + /// Gets or sets a value indicating whether the Drawer can be opend by swipe gesture.
+ /// This property is not supported on TV. + ///
+ public bool IsGestureEnabled + { + get => false; + set + { + // Not supported on TV. + } + } + void UpdateBehavior(DrawerBehavior behavior) { _behavior = behavior; @@ -255,7 +288,7 @@ void OnLayout() var bound = Geometry; - var openRatio = (_OpenRatio < 0) ? this.GetTvDrawerRatio(Geometry.Width, Geometry.Height) : _OpenRatio; + var openRatio = (_openRatio < 0) ? this.GetTvDrawerRatio(Geometry.Width, Geometry.Height) : _openRatio; var closeRatio = (_behavior == DrawerBehavior.Disabled) ? 0 : ((_closeRatio < 0) ? this.GetTvDrawerCloseRatio() : _closeRatio); var drawerWidthMax = (int)(bound.Width * openRatio); var drawerWidthMin = (int)(bound.Width * closeRatio); From 0a468281970aa795ebce22088d77a457490f7191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=EA=B7=BC/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Wed, 27 Apr 2022 16:59:21 +0900 Subject: [PATCH 70/76] Update TizenFX version on NUI (#132) --- NuGet.config | 7 +++++++ .../Extensions/ViewExtensions.cs | 20 ++++--------------- .../GraphicsView/Button.cs | 3 ++- .../GraphicsView/GraphicsView.cs | 15 +++++--------- .../GraphicsView/MaterialIconButton.cs | 3 ++- .../Tizen.UIExtensions.NUI.csproj | 8 +++----- test/NUIExGallery/NUIExGallery.csproj | 2 ++ 7 files changed, 25 insertions(+), 33 deletions(-) create mode 100644 NuGet.config diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 0000000..12b2932 --- /dev/null +++ b/NuGet.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Tizen.UIExtensions.NUI/Extensions/ViewExtensions.cs b/src/Tizen.UIExtensions.NUI/Extensions/ViewExtensions.cs index f849f85..0066390 100644 --- a/src/Tizen.UIExtensions.NUI/Extensions/ViewExtensions.cs +++ b/src/Tizen.UIExtensions.NUI/Extensions/ViewExtensions.cs @@ -1,7 +1,6 @@ -using View = Tizen.NUI.BaseComponents.View; -using Color = Tizen.UIExtensions.Common.Color; -using Rect = Tizen.UIExtensions.Common.Rect; -using NButton = Tizen.NUI.Components.Button; +using Color = Tizen.UIExtensions.Common.Color; +using View = Tizen.NUI.BaseComponents.View; + namespace Tizen.UIExtensions.NUI { public static class ViewExtensions @@ -13,18 +12,7 @@ public static void UpdateBackgroundColor(this View view, Color color) public static void SetEnable(this View view, bool enable) { - if (view is NButton button) - { - button.IsEnabled = enable; - } - else if (view is GraphicsView.GraphicsView gv) - { - gv.IsEnabled = enable; - } - else - { - view.EnableControlState = enable; - } + view.IsEnabled = enable; } } } diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/Button.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/Button.cs index 8f907c2..3945bf8 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/Button.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/Button.cs @@ -84,6 +84,7 @@ protected override bool OnTouch(object source, TouchEventArgs e) if (!IsEnabled) return false; + var consume = base.OnTouch(source, e); var state = e.Touch.GetState(0); if (state == Tizen.NUI.PointStateType.Down) @@ -101,7 +102,7 @@ protected override bool OnTouch(object source, TouchEventArgs e) } } _lastPointState = state; - return base.OnTouch(source, e); + return consume; } bool OnKeyEvent(object source, KeyEventArgs e) diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/GraphicsView.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/GraphicsView.cs index e9e35e1..af54013 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/GraphicsView.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/GraphicsView.cs @@ -10,17 +10,12 @@ public abstract class GraphicsView : SkiaGraphicsView { protected virtual GraphicsViewDrawable? GraphicsViewDrawable { get; } - bool _isEnabled = true; - public bool IsEnabled + protected override void OnEnabled(bool enabled) { - get => _isEnabled; - set - { - EnableControlState = _isEnabled = value; - if (GraphicsViewDrawable != null) - GraphicsViewDrawable.IsEnabled = value; - Invalidate(); - } + base.OnEnabled(enabled); + if (GraphicsViewDrawable != null) + GraphicsViewDrawable.IsEnabled = enabled; + Invalidate(); } } diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs index e2a1b63..9f48d2a 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs @@ -83,6 +83,7 @@ protected override bool OnTouch(object source, TouchEventArgs e) if (!IsEnabled) return false; + var consume = base.OnTouch(source, e); var state = e.Touch.GetState(0); if (state == Tizen.NUI.PointStateType.Down) @@ -100,7 +101,7 @@ protected override bool OnTouch(object source, TouchEventArgs e) } } _lastPointState = state; - return base.OnTouch(source, e); + return consume; } bool OnKeyEvent(object source, KeyEventArgs e) diff --git a/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj b/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj index cd1c622..67ad97a 100644 --- a/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj +++ b/src/Tizen.UIExtensions.NUI/Tizen.UIExtensions.NUI.csproj @@ -1,9 +1,10 @@  - tizen90;net6.0-tizen6.5 + tizen10.0;net6.0-tizen Tizen true + true 8.0 enable NU5048, NU5104 @@ -24,10 +25,7 @@ - - - - + diff --git a/test/NUIExGallery/NUIExGallery.csproj b/test/NUIExGallery/NUIExGallery.csproj index 20680d3..9d4e45a 100644 --- a/test/NUIExGallery/NUIExGallery.csproj +++ b/test/NUIExGallery/NUIExGallery.csproj @@ -2,6 +2,7 @@ net6.0-tizen Exe + true @@ -15,6 +16,7 @@ + From efb33f4c5670d43f1bf1dfafbdbd3be334a0b6d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=EA=B7=BC/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Wed, 27 Apr 2022 17:15:46 +0900 Subject: [PATCH 71/76] Add well-known key names class (#134) * Add well-known key names class * Fix typo error --- src/Tizen.UIExtensions.Common/KeyNames.cs | 34 +++++++++++++++++++ .../CollectionView/ViewHolder.cs | 3 +- .../ActionSheetPopup.cs | 2 +- .../CollectionView/ViewHolder.cs | 2 +- .../Extensions/KeyEventExtensions.cs | 29 ++++++++++++++++ .../GraphicsView/Button.cs | 2 +- .../GraphicsView/CheckBox.cs | 2 +- .../GraphicsView/MaterialIconButton.cs | 2 +- .../GraphicsView/Switch.cs | 2 +- src/Tizen.UIExtensions.NUI/Popup.cs | 2 +- test/NUIExGallery/Main.cs | 2 +- 11 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 src/Tizen.UIExtensions.Common/KeyNames.cs create mode 100644 src/Tizen.UIExtensions.NUI/Extensions/KeyEventExtensions.cs diff --git a/src/Tizen.UIExtensions.Common/KeyNames.cs b/src/Tizen.UIExtensions.Common/KeyNames.cs new file mode 100644 index 0000000..7d85dbb --- /dev/null +++ b/src/Tizen.UIExtensions.Common/KeyNames.cs @@ -0,0 +1,34 @@ +namespace Tizen.UIExtensions.Common +{ + + /// + /// A collection of well-known key names + /// + public static class InputKeyNames + { + public static readonly string Return = "Return"; + public static readonly string Enter = "Enter"; + public static readonly string BackButton = "XF86Back"; + public static readonly string Escape = "Escape"; + + /// + /// Check if the key name is related to enter + /// + /// A key name to check + /// true, if related to enter + public static bool IsEnterKey(this string keyName) + { + return keyName == Return || keyName == Enter; + } + + /// + /// Check if the key name is related to back + /// + /// A key name to check + /// true, if related to back + public static bool IsBackKey(this string keyName) + { + return keyName == BackButton || keyName == Escape; + } + } +} diff --git a/src/Tizen.UIExtensions.ElmSharp/CollectionView/ViewHolder.cs b/src/Tizen.UIExtensions.ElmSharp/CollectionView/ViewHolder.cs index 808c140..c6d021c 100644 --- a/src/Tizen.UIExtensions.ElmSharp/CollectionView/ViewHolder.cs +++ b/src/Tizen.UIExtensions.ElmSharp/CollectionView/ViewHolder.cs @@ -1,6 +1,7 @@ using System; using ElmSharp; using EColor = ElmSharp.Color; +using Tizen.UIExtensions.Common; namespace Tizen.UIExtensions.ElmSharp { @@ -154,7 +155,7 @@ void UpdateFocusState() void OnKeyUp(object? sender, EvasKeyEventArgs e) { - if (e.KeyName == "Enter" && _focusArea.IsFocused) + if (e.KeyName.IsEnterKey() && _focusArea.IsFocused) { RequestSelected?.Invoke(this, EventArgs.Empty); } diff --git a/src/Tizen.UIExtensions.NUI/ActionSheetPopup.cs b/src/Tizen.UIExtensions.NUI/ActionSheetPopup.cs index 75ac746..0b6eada 100644 --- a/src/Tizen.UIExtensions.NUI/ActionSheetPopup.cs +++ b/src/Tizen.UIExtensions.NUI/ActionSheetPopup.cs @@ -104,7 +104,7 @@ protected override View CreateContent() }; itemLabel.KeyEvent += (s, e) => { - if (e.Key.State == Key.StateType.Up && (e.Key.KeyPressedName == "Return" || e.Key.KeyPressedName == "Enter")) + if (e.Key.IsAcceptKeyEvent()) { SendSubmit(item); return true; diff --git a/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs b/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs index bdfec2a..30828e8 100644 --- a/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs +++ b/src/Tizen.UIExtensions.NUI/CollectionView/ViewHolder.cs @@ -139,7 +139,7 @@ void OnContentFocused(object? sender, EventArgs e) bool OnKeyEvent(object? source, KeyEventArgs e) { - if (e.Key.State == Key.StateType.Up && (e.Key.KeyPressedName == "Return" || e.Key.KeyPressedName == "Enter")) + if (e.Key.IsAcceptKeyEvent()) { RequestSelected?.Invoke(this, EventArgs.Empty); return true; diff --git a/src/Tizen.UIExtensions.NUI/Extensions/KeyEventExtensions.cs b/src/Tizen.UIExtensions.NUI/Extensions/KeyEventExtensions.cs new file mode 100644 index 0000000..750c293 --- /dev/null +++ b/src/Tizen.UIExtensions.NUI/Extensions/KeyEventExtensions.cs @@ -0,0 +1,29 @@ +using System; +using Tizen.NUI; +using Tizen.UIExtensions.Common; + +namespace Tizen.UIExtensions.NUI +{ + public static class KeyEventExtensions + { + /// + /// Check if the key event means acceptance + /// + /// A key event to check + /// true, if the key event means acceptance + public static bool IsAcceptKeyEvent(this Key key) + { + return Key.StateType.Up == key.State && key.KeyPressedName.IsEnterKey(); + } + + /// + /// Check if the key event means decline + /// + /// A key event to check + /// true, if the key event means declien + public static bool IsDeclineKeyEvent(this Key key) + { + return Key.StateType.Up == key.State && key.KeyPressedName.IsBackKey(); + } + } +} diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/Button.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/Button.cs index 3945bf8..96dd5e2 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/Button.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/Button.cs @@ -107,7 +107,7 @@ protected override bool OnTouch(object source, TouchEventArgs e) bool OnKeyEvent(object source, KeyEventArgs e) { - if (e.Key.State == Tizen.NUI.Key.StateType.Up && (e.Key.KeyPressedName == "Return" || e.Key.KeyPressedName == "Enter")) + if (e.Key.IsAcceptKeyEvent()) { Clicked?.Invoke(this, EventArgs.Empty); return true; diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/CheckBox.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/CheckBox.cs index 9544f8e..8da2d1e 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/CheckBox.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/CheckBox.cs @@ -67,7 +67,7 @@ public string Text bool OnKeyEvent(object source, KeyEventArgs e) { - if (e.Key.State == Tizen.NUI.Key.StateType.Up && (e.Key.KeyPressedName == "Return" || e.Key.KeyPressedName == "Enter")) + if (e.Key.IsAcceptKeyEvent()) { IsChecked = !IsChecked; return true; diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs index 9f48d2a..144fa7e 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/MaterialIconButton.cs @@ -106,7 +106,7 @@ protected override bool OnTouch(object source, TouchEventArgs e) bool OnKeyEvent(object source, KeyEventArgs e) { - if (e.Key.State == Tizen.NUI.Key.StateType.Up && (e.Key.KeyPressedName == "Return" || e.Key.KeyPressedName == "Enter")) + if (e.Key.IsAcceptKeyEvent()) { Clicked?.Invoke(this, EventArgs.Empty); return true; diff --git a/src/Tizen.UIExtensions.NUI/GraphicsView/Switch.cs b/src/Tizen.UIExtensions.NUI/GraphicsView/Switch.cs index f9d3c53..8b9659d 100644 --- a/src/Tizen.UIExtensions.NUI/GraphicsView/Switch.cs +++ b/src/Tizen.UIExtensions.NUI/GraphicsView/Switch.cs @@ -66,7 +66,7 @@ public Color ThumbColor bool OnKeyEVent(object source, KeyEventArgs e) { - if (e.Key.State == Tizen.NUI.Key.StateType.Up && (e.Key.KeyPressedName == "Return" || e.Key.KeyPressedName == "Enter")) + if (e.Key.IsAcceptKeyEvent()) { IsToggled = !IsToggled; return true; diff --git a/src/Tizen.UIExtensions.NUI/Popup.cs b/src/Tizen.UIExtensions.NUI/Popup.cs index a036e9e..ef9179c 100644 --- a/src/Tizen.UIExtensions.NUI/Popup.cs +++ b/src/Tizen.UIExtensions.NUI/Popup.cs @@ -228,7 +228,7 @@ bool BackButtonPressed() bool OnKeyEvent(object source, KeyEventArgs e) { - if (IsOpen && e.Key.State == Key.StateType.Down && (e.Key.KeyPressedName == "XF86Back" || e.Key.KeyPressedName == "Escape")) + if (IsOpen && e.Key.IsAcceptKeyEvent()) { return OnBackButtonPressed(); } diff --git a/test/NUIExGallery/Main.cs b/test/NUIExGallery/Main.cs index 0ce501d..031e35a 100644 --- a/test/NUIExGallery/Main.cs +++ b/test/NUIExGallery/Main.cs @@ -185,7 +185,7 @@ orderby test.TestName void OnKeyEvent(object sender, Window.KeyEventArgs e) { - if (e.Key.State == Key.StateType.Down && (e.Key.KeyPressedName == "XF86Back" || e.Key.KeyPressedName == "Escape")) + if (e.Key.IsDeclienKeyEvent()) { if (Tizen.UIExtensions.NUI.Popup.HasOpenedPopup) { From ce5e428e4cbab1306d6dc80a8579a1a5f13d293d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=EA=B7=BC/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Wed, 27 Apr 2022 17:28:29 +0900 Subject: [PATCH 72/76] Fix test build error (#135) --- test/NUIExGallery/Main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/NUIExGallery/Main.cs b/test/NUIExGallery/Main.cs index 031e35a..1d7e439 100644 --- a/test/NUIExGallery/Main.cs +++ b/test/NUIExGallery/Main.cs @@ -185,7 +185,7 @@ orderby test.TestName void OnKeyEvent(object sender, Window.KeyEventArgs e) { - if (e.Key.IsDeclienKeyEvent()) + if (e.Key.IsDeclineKeyEvent()) { if (Tizen.UIExtensions.NUI.Popup.HasOpenedPopup) { From 4039a7c611085c142df012bdac6930f6deb32253 Mon Sep 17 00:00:00 2001 From: Sunghyun Min Date: Thu, 28 Apr 2022 12:01:19 +0900 Subject: [PATCH 73/76] [Elmshrap] Add MaterialIcon --- .../GraphicsView/MaterialIcon.cs | 56 ++++++++++++++++ test/ElmSharpExGallery/TC/MaterialIconTest.cs | 67 +++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 src/Tizen.UIExtensions.ElmSharp/GraphicsView/MaterialIcon.cs create mode 100644 test/ElmSharpExGallery/TC/MaterialIconTest.cs diff --git a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/MaterialIcon.cs b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/MaterialIcon.cs new file mode 100644 index 0000000..a972ac2 --- /dev/null +++ b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/MaterialIcon.cs @@ -0,0 +1,56 @@ +using ElmSharp; +using Tizen.UIExtensions.Common.GraphicsView; + +namespace Tizen.UIExtensions.ElmSharp.GraphicsView +{ + /// + /// A visual control of material icons + /// + public class MaterialIcon : GraphicsView + { + /// + /// Initializes a new instance of the MaterialIcon + /// + public MaterialIcon(EvasObject parent) : base(parent) + { + AllowFocus(true); + Drawable = new MaterialIconDrawable(); + var measured = Drawable.Measure(double.PositiveInfinity, double.PositiveInfinity); + + MinimumWidth = (int)measured.Width; + MinimumHeight = (int)measured.Height; + } + + /// + /// Gets of sets the type of the MaterialIcon + /// + public MaterialIcons Type + { + get => Drawable?.Icon ?? MaterialIcons.Add; + set + { + if(Drawable != null) + { + Drawable.Icon = value; + Invalidate(); + } + } + } + + /// + /// Gets of sets the color of the MaterialIcon + /// + public new Common.Color Color + { + get => Drawable?.Color ?? Common.Color.Default; + set + { + if (Drawable != null) + { + Drawable.Color = value; + Invalidate(); + } + } + } + } +} diff --git a/test/ElmSharpExGallery/TC/MaterialIconTest.cs b/test/ElmSharpExGallery/TC/MaterialIconTest.cs new file mode 100644 index 0000000..63515de --- /dev/null +++ b/test/ElmSharpExGallery/TC/MaterialIconTest.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq; +using Tizen.UIExtensions.Common.GraphicsView; +using Tizen.UIExtensions.ElmSharp; +using Tizen.UIExtensions.ElmSharp.GraphicsView; + +namespace ElmSharpExGallery.TC +{ + public class MaterialIconTest : TestCaseBase + { + public override string TestName => "MaterialIcon Test"; + + public override string TestDescription => "Test MaterialIcon"; + + public override void Run(ElmSharp.Box parent) + { + var page = new ElmSharp.Box(parent) + { + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1, + WeightY = 1, + }; + page.Show(); + parent.PackEnd(page); + + foreach (var type in Enum.GetValues(typeof(MaterialIcons)).Cast()) + { + var box = new ElmSharp.Box(parent) + { + IsHorizontal = true, + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1, + WeightY = 1, + }; + box.Show(); + + var label = new Label(parent) + { + AlignmentX = 0.5, + AlignmentY = -1, + WeightY = 1, + WeightX =1, + Text = type.ToString(), + }; + label.Show(); + + var icon = new MaterialIcon(parent) + { + AlignmentX = 0, + AlignmentY = -1, + WeightY = 1, + WeightX = 1, + Type = type, + }; + icon.Show(); + + box.PackEnd(label); + box.PackEnd(icon); + + page.PackEnd(box); + } + + } + } +} From b517d6a220bd2872572e9cb893f8e70a52b57a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=97=88=EA=B0=95=ED=98=B8/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Thu, 28 Apr 2022 15:27:16 +0900 Subject: [PATCH 74/76] Update Versions.prop --- Versions.prop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Versions.prop b/Versions.prop index efefb9d..e80f3a6 100644 --- a/Versions.prop +++ b/Versions.prop @@ -1,5 +1,5 @@ - 6.0.200-preview.14.1092 + 6.0.300-rc.2.1310 From 041787adefa1bd361b35bc9dfbbf6b2f7108c859 Mon Sep 17 00:00:00 2001 From: Sunghyun Min Date: Thu, 28 Apr 2022 15:32:28 +0900 Subject: [PATCH 75/76] [ElmSharp] Fix Label.Text issue --- src/Tizen.UIExtensions.ElmSharp/Label.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tizen.UIExtensions.ElmSharp/Label.cs b/src/Tizen.UIExtensions.ElmSharp/Label.cs index 0b469fc..f93652e 100644 --- a/src/Tizen.UIExtensions.ElmSharp/Label.cs +++ b/src/Tizen.UIExtensions.ElmSharp/Label.cs @@ -65,7 +65,7 @@ public override string Text { if (value != _span.Text) { - _span.Text = value; + _span.Text = value ?? string.Empty; ApplyTextAndStyle(); } } From 16aebb5241772a1e274e9ce65cb8da26d1f1ae74 Mon Sep 17 00:00:00 2001 From: Sunghyun Min Date: Thu, 28 Apr 2022 16:07:34 +0900 Subject: [PATCH 76/76] [ElmSharp] Update propery name --- .../GraphicsView/MaterialIcon.cs | 3 +-- test/ElmSharpExGallery/TC/MaterialIconTest.cs | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/MaterialIcon.cs b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/MaterialIcon.cs index a972ac2..80a5b99 100644 --- a/src/Tizen.UIExtensions.ElmSharp/GraphicsView/MaterialIcon.cs +++ b/src/Tizen.UIExtensions.ElmSharp/GraphicsView/MaterialIcon.cs @@ -13,7 +13,6 @@ public class MaterialIcon : GraphicsView ///
public MaterialIcon(EvasObject parent) : base(parent) { - AllowFocus(true); Drawable = new MaterialIconDrawable(); var measured = Drawable.Measure(double.PositiveInfinity, double.PositiveInfinity); @@ -24,7 +23,7 @@ public MaterialIcon(EvasObject parent) : base(parent) /// /// Gets of sets the type of the MaterialIcon /// - public MaterialIcons Type + public MaterialIcons IconType { get => Drawable?.Icon ?? MaterialIcons.Add; set diff --git a/test/ElmSharpExGallery/TC/MaterialIconTest.cs b/test/ElmSharpExGallery/TC/MaterialIconTest.cs index 63515de..64a38a8 100644 --- a/test/ElmSharpExGallery/TC/MaterialIconTest.cs +++ b/test/ElmSharpExGallery/TC/MaterialIconTest.cs @@ -24,7 +24,7 @@ public override void Run(ElmSharp.Box parent) page.Show(); parent.PackEnd(page); - foreach (var type in Enum.GetValues(typeof(MaterialIcons)).Cast()) + foreach (var iconType in Enum.GetValues(typeof(MaterialIcons)).Cast()) { var box = new ElmSharp.Box(parent) { @@ -42,7 +42,7 @@ public override void Run(ElmSharp.Box parent) AlignmentY = -1, WeightY = 1, WeightX =1, - Text = type.ToString(), + Text = iconType.ToString(), }; label.Show(); @@ -52,7 +52,7 @@ public override void Run(ElmSharp.Box parent) AlignmentY = -1, WeightY = 1, WeightX = 1, - Type = type, + IconType = iconType, }; icon.Show();