From d7fae9efc8b86af98357da1e22c5a5ff629ba89d Mon Sep 17 00:00:00 2001 From: Prathamesh Narkhede <55591622+prathameshnarkhede@users.noreply.github.com> Date: Mon, 19 Aug 2024 09:32:53 -0700 Subject: [PATCH 1/7] Switch cancelButton image to FontImageSource allowing for more flexible and scalable icon usage (#593) * Switch cancelButton image to FontImageSource allowing for more flexible and scalable icon usage with support for Dark/Light mode fixes. * Update cancelButton icon color to #6E6E6E Removed unused resources. --- src/Toolkit/Toolkit.Maui/Assets/x-small.png | Bin 164 -> 0 bytes src/Toolkit/Toolkit.Maui/Assets/x.png | Bin 390 -> 0 bytes src/Toolkit/Toolkit.Maui/SearchView/SearchView.cs | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 src/Toolkit/Toolkit.Maui/Assets/x-small.png delete mode 100644 src/Toolkit/Toolkit.Maui/Assets/x.png diff --git a/src/Toolkit/Toolkit.Maui/Assets/x-small.png b/src/Toolkit/Toolkit.Maui/Assets/x-small.png deleted file mode 100644 index 3f9dc9b4964fcfea98f18949a30a9defb48d385a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt_Uo-U3d z9-VKeY~($lz{BD{v-{Ag12?;MGZr{cJoxWi_E$w9#NA-DFmo6((qSJwVw3ku{^Qi%N<{M>tvg=Wzkg}aVSKqG*lA%<_# zx=FuyHd}k9d)7;)=@(5Xk+(Dd>9)zjZ2t)laa;Wldi$qd;olsW9xwB3-8E0Emw%mt z*Dv{a>f@h8vyVL?u`-VZSKhKvOI`Bn@eGlPvpSQ%^<0UO+3da~?eUCwB~$t1Ih`VE zeUZYNKtYwD8y0DLK|d_gvL;vl{SdnY$WRqbe-gYz=~2r52FL9mb{8zmJ2CIMr|V-i z+vIoaZH}Kz37ls0yrp-`Gev`#CA$765|#dLs?D&wBv-vUP5JH4k2!ilU9pwM6NBU; zL*!3Bwpo75V_tjgRo7F6LJ^@VarHkRePz{pFZ-iHRrDv*R2A8;KfV^}e5{*Md&WQ- d5(W$^f0>sw@y{3A@JR(E;_2$=vd$@?2>`pkqmTdq diff --git a/src/Toolkit/Toolkit.Maui/SearchView/SearchView.cs b/src/Toolkit/Toolkit.Maui/SearchView/SearchView.cs index 36fd3faae..4504ea1c3 100644 --- a/src/Toolkit/Toolkit.Maui/SearchView/SearchView.cs +++ b/src/Toolkit/Toolkit.Maui/SearchView/SearchView.cs @@ -71,7 +71,7 @@ public SearchView() if (GetTemplateChild(nameof(PART_CancelButton)) is ImageButton cancelButton) { - cancelButton.Source = ImageSource.FromResource($"Esri.ArcGISRuntime.Toolkit.Maui.Assets.x{suffix}.png", Assembly.GetAssembly(typeof(SearchView))); + cancelButton.Source = new FontImageSource { Glyph = "\ue30a", FontFamily = "calcite-ui-icons-24", Color = Color.FromArgb("#6E6E6E") }; } BindingContext = this; From 48eb833212bf4222b13ce10bde743b371187743c Mon Sep 17 00:00:00 2001 From: Prathamesh Narkhede Date: Wed, 18 Sep 2024 09:25:26 -0700 Subject: [PATCH 2/7] Add platform-specific min size for SymbolDisplay on Android to fix issue with Pixel 4 rendering. --- src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs b/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs index c0cb335fb..f181185fb 100644 --- a/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs +++ b/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs @@ -161,8 +161,12 @@ private async Task UpdateSwatchAsync() image.MaximumWidthRequest = imageData.Width / scale; image.MaximumHeightRequest = imageData.Height / scale; image.Source = await imageData.ToImageSourceAsync(); - SourceUpdated?.Invoke(this, EventArgs.Empty); + SourceUpdated?.Invoke(this, EventArgs.Empty); #pragma warning restore ESRI1800 +#if ANDROID + this.MinimumWidthRequest = imageData.Width / scale; + this.MinimumHeightRequest = imageData.Height / scale; +#endif } catch { From 783ca292d65acb9e5e4b741411de7651674775c2 Mon Sep 17 00:00:00 2001 From: Prathamesh Narkhede Date: Wed, 18 Sep 2024 12:10:16 -0700 Subject: [PATCH 3/7] Reverting changing height and width and removing Border to resolve the issue --- .../Toolkit.SampleApp.Maui/Samples/SymbolEditorSample.xaml | 6 +----- src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Samples/Toolkit.SampleApp.Maui/Samples/SymbolEditorSample.xaml b/src/Samples/Toolkit.SampleApp.Maui/Samples/SymbolEditorSample.xaml index 9338eec7c..c60e44fb0 100644 --- a/src/Samples/Toolkit.SampleApp.Maui/Samples/SymbolEditorSample.xaml +++ b/src/Samples/Toolkit.SampleApp.Maui/Samples/SymbolEditorSample.xaml @@ -22,11 +22,7 @@ - - - - - + \ No newline at end of file diff --git a/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs b/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs index f181185fb..3fc0e6e5f 100644 --- a/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs +++ b/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs @@ -16,7 +16,7 @@ using System; using Esri.ArcGISRuntime.Symbology; -using Esri.ArcGISRuntime.Toolkit.Internal; +using Esri.ArcGISRuntime.Toolkit.Internal; namespace Esri.ArcGISRuntime.Toolkit.Maui; @@ -163,10 +163,6 @@ private async Task UpdateSwatchAsync() image.Source = await imageData.ToImageSourceAsync(); SourceUpdated?.Invoke(this, EventArgs.Empty); #pragma warning restore ESRI1800 -#if ANDROID - this.MinimumWidthRequest = imageData.Width / scale; - this.MinimumHeightRequest = imageData.Height / scale; -#endif } catch { From 7c5a644b2736f4ef4e4e3087b7cfb60021cdc6f6 Mon Sep 17 00:00:00 2001 From: Prathamesh Narkhede Date: Wed, 18 Sep 2024 12:12:15 -0700 Subject: [PATCH 4/7] Fixing unnecessary line endings --- .../SymbolDisplay/SymbolDisplay.cs | 420 +++++++++--------- 1 file changed, 210 insertions(+), 210 deletions(-) diff --git a/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs b/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs index 3fc0e6e5f..b923b1f93 100644 --- a/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs +++ b/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs @@ -1,214 +1,214 @@ -// /******************************************************************************* -// * Copyright 2012-2018 Esri -// * -// * Licensed under the Apache License, Version 2.0 (the "License"); -// * you may not use this file except in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, software -// * distributed under the License is distributed on an "AS IS" BASIS, -// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// * See the License for the specific language governing permissions and -// * limitations under the License. -// ******************************************************************************/ - +// /******************************************************************************* +// * Copyright 2012-2018 Esri +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// ******************************************************************************/ + using System; -using Esri.ArcGISRuntime.Symbology; +using Esri.ArcGISRuntime.Symbology; using Esri.ArcGISRuntime.Toolkit.Internal; - -namespace Esri.ArcGISRuntime.Toolkit.Maui; - -/// -/// A control that renders a . -/// -public partial class SymbolDisplay : TemplatedView -{ - private WeakEventListener? _inpcListener; - private WeakEventListener? _displayDensityChangedListener; - private Task? _currentUpdateTask; - private bool _isRefreshRequired; - private static readonly ControlTemplate DefaultControlTemplate; - private Image? image; - private double _lastScaleFactor = double.NaN; - - static SymbolDisplay() - { - string template = @""; - DefaultControlTemplate = new ControlTemplate() - { - LoadTemplate = () => - { - return Microsoft.Maui.Controls.Xaml.Extensions.LoadFromXaml(new Image(), template); - } - }; - } - /// - /// Initializes a new instance of the class. - /// - public SymbolDisplay() - { - ControlTemplate = DefaultControlTemplate; - this.PropertyChanged += SymbolDisplay_PropertyChanged; - } - - private void SymbolDisplay_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(Window) && Window != null) - { - if (_lastScaleFactor != GetScaleFactor()) - { - Refresh(); - } - - _displayDensityChangedListener?.Detach(); - - _displayDensityChangedListener = new WeakEventListener(this, Window) - { - OnEventAction = static (instance, source, eventArgs) => - { - if (instance._lastScaleFactor != instance.GetScaleFactor()) - { - instance.Refresh(); - } - }, - OnDetachAction = static (instance, source, weakEventListener) => source.DisplayDensityChanged -= weakEventListener.OnEvent, - }; - Window.DisplayDensityChanged += _displayDensityChangedListener.OnEvent; - } - } - - /// - /// Gets or sets the symbol to render. - /// - public Symbology.Symbol? Symbol - { - get => (Symbol?)GetValue(SymbolProperty); - set => SetValue(SymbolProperty, value); - } - - /// - /// Identifies the bindable property. - /// - public static readonly BindableProperty SymbolProperty = - BindableProperty.Create(nameof(Symbol), typeof(Symbol), typeof(SymbolDisplay), null, propertyChanged: OnSymbolPropertyChanged); - - private static void OnSymbolPropertyChanged(BindableObject sender, object? oldValue, object? newValue) - { - ((SymbolDisplay)sender).OnSymbolChanged(oldValue as Symbol, newValue as Symbol); - } - - private void OnSymbolChanged(Symbology.Symbol? oldValue, Symbology.Symbol? newValue) - { - if (oldValue != null) - { - _inpcListener?.Detach(); - _inpcListener = null; - } - - if (newValue != null) - { - _inpcListener = new WeakEventListener(this, newValue) - { - OnEventAction = static (instance, source, eventArgs) => - { - instance.Refresh(); - }, - OnDetachAction = static (instance, source, weakEventListener) => source.PropertyChanged -= weakEventListener.OnEvent, - }; - newValue.PropertyChanged += _inpcListener.OnEvent; - } - - Refresh(); - } - - /// - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - image = GetTemplateChild("image") as Image; - Refresh(); - } - - private async Task UpdateSwatchAsync() - { - - if (image is null) - { - return; - } - - if (Symbol == null) - { - image.Source = null; - image.MaximumWidthRequest = 0; - image.MaximumHeightRequest = 0; - } - else - { - try - { - var scale = GetScaleFactor(); - _lastScaleFactor = scale; - - if (double.IsNaN(scale)) - { - return; - } -#pragma warning disable ESRI1800 // Add ConfigureAwait(false) - This is UI Dependent code and must return to UI Thread - var imageData = await Symbol.CreateSwatchAsync(scale * 96); - image.MaximumWidthRequest = imageData.Width / scale; - image.MaximumHeightRequest = imageData.Height / scale; - image.Source = await imageData.ToImageSourceAsync(); + +namespace Esri.ArcGISRuntime.Toolkit.Maui; + +/// +/// A control that renders a . +/// +public partial class SymbolDisplay : TemplatedView +{ + private WeakEventListener? _inpcListener; + private WeakEventListener? _displayDensityChangedListener; + private Task? _currentUpdateTask; + private bool _isRefreshRequired; + private static readonly ControlTemplate DefaultControlTemplate; + private Image? image; + private double _lastScaleFactor = double.NaN; + + static SymbolDisplay() + { + string template = @""; + DefaultControlTemplate = new ControlTemplate() + { + LoadTemplate = () => + { + return Microsoft.Maui.Controls.Xaml.Extensions.LoadFromXaml(new Image(), template); + } + }; + } + /// + /// Initializes a new instance of the class. + /// + public SymbolDisplay() + { + ControlTemplate = DefaultControlTemplate; + this.PropertyChanged += SymbolDisplay_PropertyChanged; + } + + private void SymbolDisplay_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Window) && Window != null) + { + if (_lastScaleFactor != GetScaleFactor()) + { + Refresh(); + } + + _displayDensityChangedListener?.Detach(); + + _displayDensityChangedListener = new WeakEventListener(this, Window) + { + OnEventAction = static (instance, source, eventArgs) => + { + if (instance._lastScaleFactor != instance.GetScaleFactor()) + { + instance.Refresh(); + } + }, + OnDetachAction = static (instance, source, weakEventListener) => source.DisplayDensityChanged -= weakEventListener.OnEvent, + }; + Window.DisplayDensityChanged += _displayDensityChangedListener.OnEvent; + } + } + + /// + /// Gets or sets the symbol to render. + /// + public Symbology.Symbol? Symbol + { + get => (Symbol?)GetValue(SymbolProperty); + set => SetValue(SymbolProperty, value); + } + + /// + /// Identifies the bindable property. + /// + public static readonly BindableProperty SymbolProperty = + BindableProperty.Create(nameof(Symbol), typeof(Symbol), typeof(SymbolDisplay), null, propertyChanged: OnSymbolPropertyChanged); + + private static void OnSymbolPropertyChanged(BindableObject sender, object? oldValue, object? newValue) + { + ((SymbolDisplay)sender).OnSymbolChanged(oldValue as Symbol, newValue as Symbol); + } + + private void OnSymbolChanged(Symbology.Symbol? oldValue, Symbology.Symbol? newValue) + { + if (oldValue != null) + { + _inpcListener?.Detach(); + _inpcListener = null; + } + + if (newValue != null) + { + _inpcListener = new WeakEventListener(this, newValue) + { + OnEventAction = static (instance, source, eventArgs) => + { + instance.Refresh(); + }, + OnDetachAction = static (instance, source, weakEventListener) => source.PropertyChanged -= weakEventListener.OnEvent, + }; + newValue.PropertyChanged += _inpcListener.OnEvent; + } + + Refresh(); + } + + /// + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + image = GetTemplateChild("image") as Image; + Refresh(); + } + + private async Task UpdateSwatchAsync() + { + + if (image is null) + { + return; + } + + if (Symbol == null) + { + image.Source = null; + image.MaximumWidthRequest = 0; + image.MaximumHeightRequest = 0; + } + else + { + try + { + var scale = GetScaleFactor(); + _lastScaleFactor = scale; + + if (double.IsNaN(scale)) + { + return; + } +#pragma warning disable ESRI1800 // Add ConfigureAwait(false) - This is UI Dependent code and must return to UI Thread + var imageData = await Symbol.CreateSwatchAsync(scale * 96); + image.MaximumWidthRequest = imageData.Width / scale; + image.MaximumHeightRequest = imageData.Height / scale; + image.Source = await imageData.ToImageSourceAsync(); SourceUpdated?.Invoke(this, EventArgs.Empty); -#pragma warning restore ESRI1800 - } - catch - { - image.Source = null; - image.MaximumWidthRequest = 0; - image.MaximumHeightRequest = 0; - } - } - } - - private double GetScaleFactor() => Window?.DisplayDensity ?? double.NaN; - - private async void Refresh() - { - try - { - if (_currentUpdateTask != null) - { - // Instead of refreshing immediately when a refresh is already in progress, avoid updating too frequently, but just flag it dirty - // This avoid multiple refreshes where properties change very frequently, but just the latest state gets refreshed. - _isRefreshRequired = true; - return; - } - -#pragma warning disable SA1500 // Braces for multi-line statements should not share line - - do - { - _isRefreshRequired = false; - var task = _currentUpdateTask = UpdateSwatchAsync(); - await task; - } while (_isRefreshRequired); - -#pragma warning restore SA1500 // Braces for multi-line statements should not share line - - _currentUpdateTask = null; - } - catch (Exception) - { - // Ignore - } - } - // Even though this code doesn't apply to .NET standard build, Visual Studio sill warns about it. - /// - /// Triggered when the image source has updated - /// - [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] - public event System.EventHandler? SourceUpdated; +#pragma warning restore ESRI1800 + } + catch + { + image.Source = null; + image.MaximumWidthRequest = 0; + image.MaximumHeightRequest = 0; + } + } + } + + private double GetScaleFactor() => Window?.DisplayDensity ?? double.NaN; + + private async void Refresh() + { + try + { + if (_currentUpdateTask != null) + { + // Instead of refreshing immediately when a refresh is already in progress, avoid updating too frequently, but just flag it dirty + // This avoid multiple refreshes where properties change very frequently, but just the latest state gets refreshed. + _isRefreshRequired = true; + return; + } + +#pragma warning disable SA1500 // Braces for multi-line statements should not share line + + do + { + _isRefreshRequired = false; + var task = _currentUpdateTask = UpdateSwatchAsync(); + await task; + } while (_isRefreshRequired); + +#pragma warning restore SA1500 // Braces for multi-line statements should not share line + + _currentUpdateTask = null; + } + catch (Exception) + { + // Ignore + } + } + // Even though this code doesn't apply to .NET standard build, Visual Studio sill warns about it. + /// + /// Triggered when the image source has updated + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public event System.EventHandler? SourceUpdated; } \ No newline at end of file From bc03bb4737f4bcb2aa1f08224bf01d290bbeedcf Mon Sep 17 00:00:00 2001 From: Prathamesh Narkhede Date: Wed, 18 Sep 2024 12:13:09 -0700 Subject: [PATCH 5/7] . --- .../SymbolDisplay/SymbolDisplay.cs | 422 +++++++++--------- 1 file changed, 211 insertions(+), 211 deletions(-) diff --git a/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs b/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs index b923b1f93..206f6bbcb 100644 --- a/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs +++ b/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs @@ -1,214 +1,214 @@ -// /******************************************************************************* -// * Copyright 2012-2018 Esri -// * -// * Licensed under the Apache License, Version 2.0 (the "License"); -// * you may not use this file except in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, software -// * distributed under the License is distributed on an "AS IS" BASIS, -// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// * See the License for the specific language governing permissions and -// * limitations under the License. -// ******************************************************************************/ - +// /******************************************************************************* +// * Copyright 2012-2018 Esri +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// ******************************************************************************/ + using System; -using Esri.ArcGISRuntime.Symbology; -using Esri.ArcGISRuntime.Toolkit.Internal; - -namespace Esri.ArcGISRuntime.Toolkit.Maui; - -/// -/// A control that renders a . -/// -public partial class SymbolDisplay : TemplatedView -{ - private WeakEventListener? _inpcListener; - private WeakEventListener? _displayDensityChangedListener; - private Task? _currentUpdateTask; - private bool _isRefreshRequired; - private static readonly ControlTemplate DefaultControlTemplate; - private Image? image; - private double _lastScaleFactor = double.NaN; - - static SymbolDisplay() - { - string template = @""; - DefaultControlTemplate = new ControlTemplate() - { - LoadTemplate = () => - { - return Microsoft.Maui.Controls.Xaml.Extensions.LoadFromXaml(new Image(), template); - } - }; - } - /// - /// Initializes a new instance of the class. - /// - public SymbolDisplay() - { - ControlTemplate = DefaultControlTemplate; - this.PropertyChanged += SymbolDisplay_PropertyChanged; - } - - private void SymbolDisplay_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(Window) && Window != null) - { - if (_lastScaleFactor != GetScaleFactor()) - { - Refresh(); - } - - _displayDensityChangedListener?.Detach(); - - _displayDensityChangedListener = new WeakEventListener(this, Window) - { - OnEventAction = static (instance, source, eventArgs) => - { - if (instance._lastScaleFactor != instance.GetScaleFactor()) - { - instance.Refresh(); - } - }, - OnDetachAction = static (instance, source, weakEventListener) => source.DisplayDensityChanged -= weakEventListener.OnEvent, - }; - Window.DisplayDensityChanged += _displayDensityChangedListener.OnEvent; - } - } - - /// - /// Gets or sets the symbol to render. - /// - public Symbology.Symbol? Symbol - { - get => (Symbol?)GetValue(SymbolProperty); - set => SetValue(SymbolProperty, value); - } - - /// - /// Identifies the bindable property. - /// - public static readonly BindableProperty SymbolProperty = - BindableProperty.Create(nameof(Symbol), typeof(Symbol), typeof(SymbolDisplay), null, propertyChanged: OnSymbolPropertyChanged); - - private static void OnSymbolPropertyChanged(BindableObject sender, object? oldValue, object? newValue) - { - ((SymbolDisplay)sender).OnSymbolChanged(oldValue as Symbol, newValue as Symbol); - } - - private void OnSymbolChanged(Symbology.Symbol? oldValue, Symbology.Symbol? newValue) - { - if (oldValue != null) - { - _inpcListener?.Detach(); - _inpcListener = null; - } - - if (newValue != null) - { - _inpcListener = new WeakEventListener(this, newValue) - { - OnEventAction = static (instance, source, eventArgs) => - { - instance.Refresh(); - }, - OnDetachAction = static (instance, source, weakEventListener) => source.PropertyChanged -= weakEventListener.OnEvent, - }; - newValue.PropertyChanged += _inpcListener.OnEvent; - } - - Refresh(); - } - - /// - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - image = GetTemplateChild("image") as Image; - Refresh(); - } - - private async Task UpdateSwatchAsync() - { - - if (image is null) - { - return; - } - - if (Symbol == null) - { - image.Source = null; - image.MaximumWidthRequest = 0; - image.MaximumHeightRequest = 0; - } - else - { - try - { - var scale = GetScaleFactor(); - _lastScaleFactor = scale; - - if (double.IsNaN(scale)) - { - return; - } -#pragma warning disable ESRI1800 // Add ConfigureAwait(false) - This is UI Dependent code and must return to UI Thread - var imageData = await Symbol.CreateSwatchAsync(scale * 96); - image.MaximumWidthRequest = imageData.Width / scale; - image.MaximumHeightRequest = imageData.Height / scale; - image.Source = await imageData.ToImageSourceAsync(); +using Esri.ArcGISRuntime.Symbology; +using Esri.ArcGISRuntime.Toolkit.Internal; + +namespace Esri.ArcGISRuntime.Toolkit.Maui; + +/// +/// A control that renders a . +/// +public partial class SymbolDisplay : TemplatedView +{ + private WeakEventListener? _inpcListener; + private WeakEventListener? _displayDensityChangedListener; + private Task? _currentUpdateTask; + private bool _isRefreshRequired; + private static readonly ControlTemplate DefaultControlTemplate; + private Image? image; + private double _lastScaleFactor = double.NaN; + + static SymbolDisplay() + { + string template = @""; + DefaultControlTemplate = new ControlTemplate() + { + LoadTemplate = () => + { + return Microsoft.Maui.Controls.Xaml.Extensions.LoadFromXaml(new Image(), template); + } + }; + } + /// + /// Initializes a new instance of the class. + /// + public SymbolDisplay() + { + ControlTemplate = DefaultControlTemplate; + this.PropertyChanged += SymbolDisplay_PropertyChanged; + } + + private void SymbolDisplay_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Window) && Window != null) + { + if (_lastScaleFactor != GetScaleFactor()) + { + Refresh(); + } + + _displayDensityChangedListener?.Detach(); + + _displayDensityChangedListener = new WeakEventListener(this, Window) + { + OnEventAction = static (instance, source, eventArgs) => + { + if (instance._lastScaleFactor != instance.GetScaleFactor()) + { + instance.Refresh(); + } + }, + OnDetachAction = static (instance, source, weakEventListener) => source.DisplayDensityChanged -= weakEventListener.OnEvent, + }; + Window.DisplayDensityChanged += _displayDensityChangedListener.OnEvent; + } + } + + /// + /// Gets or sets the symbol to render. + /// + public Symbology.Symbol? Symbol + { + get => (Symbol?)GetValue(SymbolProperty); + set => SetValue(SymbolProperty, value); + } + + /// + /// Identifies the bindable property. + /// + public static readonly BindableProperty SymbolProperty = + BindableProperty.Create(nameof(Symbol), typeof(Symbol), typeof(SymbolDisplay), null, propertyChanged: OnSymbolPropertyChanged); + + private static void OnSymbolPropertyChanged(BindableObject sender, object? oldValue, object? newValue) + { + ((SymbolDisplay)sender).OnSymbolChanged(oldValue as Symbol, newValue as Symbol); + } + + private void OnSymbolChanged(Symbology.Symbol? oldValue, Symbology.Symbol? newValue) + { + if (oldValue != null) + { + _inpcListener?.Detach(); + _inpcListener = null; + } + + if (newValue != null) + { + _inpcListener = new WeakEventListener(this, newValue) + { + OnEventAction = static (instance, source, eventArgs) => + { + instance.Refresh(); + }, + OnDetachAction = static (instance, source, weakEventListener) => source.PropertyChanged -= weakEventListener.OnEvent, + }; + newValue.PropertyChanged += _inpcListener.OnEvent; + } + + Refresh(); + } + + /// + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + image = GetTemplateChild("image") as Image; + Refresh(); + } + + private async Task UpdateSwatchAsync() + { + + if (image is null) + { + return; + } + + if (Symbol == null) + { + image.Source = null; + image.MaximumWidthRequest = 0; + image.MaximumHeightRequest = 0; + } + else + { + try + { + var scale = GetScaleFactor(); + _lastScaleFactor = scale; + + if (double.IsNaN(scale)) + { + return; + } +#pragma warning disable ESRI1800 // Add ConfigureAwait(false) - This is UI Dependent code and must return to UI Thread + var imageData = await Symbol.CreateSwatchAsync(scale * 96); + image.MaximumWidthRequest = imageData.Width / scale; + image.MaximumHeightRequest = imageData.Height / scale; + image.Source = await imageData.ToImageSourceAsync(); SourceUpdated?.Invoke(this, EventArgs.Empty); -#pragma warning restore ESRI1800 - } - catch - { - image.Source = null; - image.MaximumWidthRequest = 0; - image.MaximumHeightRequest = 0; - } - } - } - - private double GetScaleFactor() => Window?.DisplayDensity ?? double.NaN; - - private async void Refresh() - { - try - { - if (_currentUpdateTask != null) - { - // Instead of refreshing immediately when a refresh is already in progress, avoid updating too frequently, but just flag it dirty - // This avoid multiple refreshes where properties change very frequently, but just the latest state gets refreshed. - _isRefreshRequired = true; - return; - } - -#pragma warning disable SA1500 // Braces for multi-line statements should not share line - - do - { - _isRefreshRequired = false; - var task = _currentUpdateTask = UpdateSwatchAsync(); - await task; - } while (_isRefreshRequired); - -#pragma warning restore SA1500 // Braces for multi-line statements should not share line - - _currentUpdateTask = null; - } - catch (Exception) - { - // Ignore - } - } - // Even though this code doesn't apply to .NET standard build, Visual Studio sill warns about it. - /// - /// Triggered when the image source has updated - /// - [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] - public event System.EventHandler? SourceUpdated; +#pragma warning restore ESRI1800 + } + catch + { + image.Source = null; + image.MaximumWidthRequest = 0; + image.MaximumHeightRequest = 0; + } + } + } + + private double GetScaleFactor() => Window?.DisplayDensity ?? double.NaN; + + private async void Refresh() + { + try + { + if (_currentUpdateTask != null) + { + // Instead of refreshing immediately when a refresh is already in progress, avoid updating too frequently, but just flag it dirty + // This avoid multiple refreshes where properties change very frequently, but just the latest state gets refreshed. + _isRefreshRequired = true; + return; + } + +#pragma warning disable SA1500 // Braces for multi-line statements should not share line + + do + { + _isRefreshRequired = false; + var task = _currentUpdateTask = UpdateSwatchAsync(); + await task; + } while (_isRefreshRequired); + +#pragma warning restore SA1500 // Braces for multi-line statements should not share line + + _currentUpdateTask = null; + } + catch (Exception) + { + // Ignore + } + } + // Even though this code doesn't apply to .NET standard build, Visual Studio sill warns about it. + /// + /// Triggered when the image source has updated + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public event System.EventHandler? SourceUpdated; } \ No newline at end of file From e082673cc5ce0f9cba1c7480016221b6fd166a99 Mon Sep 17 00:00:00 2001 From: Prathamesh Narkhede Date: Wed, 18 Sep 2024 12:13:48 -0700 Subject: [PATCH 6/7] . --- src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs b/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs index 206f6bbcb..c0cb335fb 100644 --- a/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs +++ b/src/Toolkit/Toolkit.Maui/SymbolDisplay/SymbolDisplay.cs @@ -161,7 +161,7 @@ private async Task UpdateSwatchAsync() image.MaximumWidthRequest = imageData.Width / scale; image.MaximumHeightRequest = imageData.Height / scale; image.Source = await imageData.ToImageSourceAsync(); - SourceUpdated?.Invoke(this, EventArgs.Empty); + SourceUpdated?.Invoke(this, EventArgs.Empty); #pragma warning restore ESRI1800 } catch From 268e396df336fc64c41cb2c2644ed4c0697853a6 Mon Sep 17 00:00:00 2001 From: Prathamesh Narkhede Date: Thu, 19 Sep 2024 16:50:03 -0700 Subject: [PATCH 7/7] Fixing SymbolDisplay on Android Pixel 4 device by removing Border element --- .../Samples/SymbolDisplaySample.xaml.cs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Samples/Toolkit.SampleApp.Maui/Samples/SymbolDisplaySample.xaml.cs b/src/Samples/Toolkit.SampleApp.Maui/Samples/SymbolDisplaySample.xaml.cs index f988eab08..d0bd7c66e 100644 --- a/src/Samples/Toolkit.SampleApp.Maui/Samples/SymbolDisplaySample.xaml.cs +++ b/src/Samples/Toolkit.SampleApp.Maui/Samples/SymbolDisplaySample.xaml.cs @@ -38,23 +38,14 @@ private void AddSymbol(Symbol symbol) { int columnCount = LayoutRoot.ColumnDefinitions.Count; var sd = new SymbolDisplay() { Symbol = symbol }; - Border border = new Border() - { - HorizontalOptions = LayoutOptions.Center, - VerticalOptions = LayoutOptions.Center, - Padding = 0, - StrokeThickness = 1, - }; - border.SetAppThemeColor(Microsoft.Maui.Controls.Border.StrokeProperty, Colors.Black, Colors.White); - border.Content = sd; int count = LayoutRoot.Children.Count; var row = count / columnCount; var column = count % columnCount; if (column == 0) LayoutRoot.RowDefinitions.Add(new RowDefinition()); - Grid.SetRow(border, row); - Grid.SetColumn(border, column); - LayoutRoot.Children.Add(border); + Grid.SetRow(sd, row); + Grid.SetColumn(sd, column); + LayoutRoot.Children.Add(sd); } } } \ No newline at end of file