diff --git a/src/Controls/samples/Controls.Sample/Pages/Core/InputTransparentPage.xaml b/src/Controls/samples/Controls.Sample/Pages/Core/InputTransparentPage.xaml
index 70ff593aed87..5c10fc7f2ffb 100644
--- a/src/Controls/samples/Controls.Sample/Pages/Core/InputTransparentPage.xaml
+++ b/src/Controls/samples/Controls.Sample/Pages/Core/InputTransparentPage.xaml
@@ -3,70 +3,100 @@
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Pages.InputTransparentPage">
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/src/Core/IInputTransparentContainerElement.cs b/src/Controls/src/Core/IInputTransparentContainerElement.cs
new file mode 100644
index 000000000000..19c9070a0630
--- /dev/null
+++ b/src/Controls/src/Core/IInputTransparentContainerElement.cs
@@ -0,0 +1,14 @@
+#nullable disable
+
+namespace Microsoft.Maui.Controls
+{
+ // There are 2 Layout types: Controls and Compatibility
+ interface IInputTransparentContainerElement
+ {
+ bool InputTransparent { get; }
+
+ bool CascadeInputTransparent { get; }
+
+ Element Parent { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Controls/src/Core/Layout.cs b/src/Controls/src/Core/Layout.cs
index bd84179ac968..637b9dba3e8c 100644
--- a/src/Controls/src/Core/Layout.cs
+++ b/src/Controls/src/Core/Layout.cs
@@ -63,7 +63,7 @@ Size ILayoutManager.ArrangeChildren(Rect bounds)
}
}
- public abstract class Layout : View, ILayout, ILayoutController, IPaddingElement, IView, IVisualTreeElement
+ public abstract class Layout : View, ILayout, ILayoutController, IPaddingElement, IView, IVisualTreeElement, IInputTransparentContainerElement
{
/// Bindable property for .
public static readonly BindableProperty IsClippedToBoundsProperty =
@@ -72,7 +72,8 @@ public abstract class Layout : View, ILayout, ILayoutController, IPaddingElement
/// Bindable property for .
public static readonly BindableProperty CascadeInputTransparentProperty =
- BindableProperty.Create(nameof(CascadeInputTransparent), typeof(bool), typeof(Layout), true);
+ BindableProperty.Create(nameof(CascadeInputTransparent), typeof(bool), typeof(Layout), true,
+ propertyChanged: OnCascadeInputTransparentPropertyChanged);
/// Bindable property for .
public static readonly BindableProperty PaddingProperty = PaddingElement.PaddingProperty;
@@ -518,5 +519,15 @@ public Size CrossPlatformArrange(Rect bounds)
return Frame.Size;
}
+
+ static void OnCascadeInputTransparentPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ // We only need to update if the cascade changes anything, namely when InputTransparent=true.
+ // When InputTransparent=false, then the cascade property has no effect.
+ if (bindable is Layout layout && layout.InputTransparent)
+ {
+ layout.RefreshInputTransparentProperty();
+ }
+ }
}
}
diff --git a/src/Controls/src/Core/Layout/Layout.Android.cs b/src/Controls/src/Core/Layout/Layout.Android.cs
index 9b0727a51a6c..9051d3efff17 100644
--- a/src/Controls/src/Core/Layout/Layout.Android.cs
+++ b/src/Controls/src/Core/Layout/Layout.Android.cs
@@ -7,31 +7,8 @@ namespace Microsoft.Maui.Controls
{
public partial class Layout
{
- public static void MapInputTransparent(LayoutHandler handler, Layout layout) =>
- UpdateInputTransparent(handler, layout);
+ public static void MapInputTransparent(LayoutHandler handler, Layout layout) { }
- public static void MapInputTransparent(ILayoutHandler handler, Layout layout) =>
- UpdateInputTransparent(handler, layout);
-
- static void MapInputTransparent(IViewHandler handler, IView layout) =>
- UpdateInputTransparent(handler, layout);
-
- static void UpdateInputTransparent(IViewHandler handler, IView layout)
- {
- if (handler is ILayoutHandler layoutHandler && layout is Layout controlsLayout)
- {
- if (layoutHandler.PlatformView is LayoutViewGroup layoutViewGroup)
- {
- // Handle input transparent for this view
- layoutViewGroup.InputTransparent = layout.InputTransparent;
- }
-
- controlsLayout.UpdateDescendantInputTransparent();
- }
- else
- {
- ControlsVisualElementMapper.UpdateProperty(handler, layout, nameof(IView.InputTransparent));
- }
- }
+ public static void MapInputTransparent(ILayoutHandler handler, Layout layout) { }
}
}
diff --git a/src/Controls/src/Core/Layout/Layout.Mapper.cs b/src/Controls/src/Core/Layout/Layout.Mapper.cs
index ab7fdf8142f2..30ab4848e259 100644
--- a/src/Controls/src/Core/Layout/Layout.Mapper.cs
+++ b/src/Controls/src/Core/Layout/Layout.Mapper.cs
@@ -15,8 +15,6 @@ public abstract partial class Layout
public static IPropertyMapper ControlsLayoutMapper = new PropertyMapper(ControlsVisualElementMapper)
{
- [nameof(CascadeInputTransparent)] = MapInputTransparent,
- [nameof(IView.InputTransparent)] = MapInputTransparent,
};
}
}
diff --git a/src/Controls/src/Core/Layout/Layout.Tizen.cs b/src/Controls/src/Core/Layout/Layout.Tizen.cs
index 5a54a70be665..079d21ba8bda 100644
--- a/src/Controls/src/Core/Layout/Layout.Tizen.cs
+++ b/src/Controls/src/Core/Layout/Layout.Tizen.cs
@@ -3,36 +3,8 @@ namespace Microsoft.Maui.Controls
{
public partial class Layout
{
- public static void MapInputTransparent(LayoutHandler handler, Layout layout) =>
- UpdateInputTransparent(handler, layout);
+ public static void MapInputTransparent(LayoutHandler handler, Layout layout) { }
- public static void MapInputTransparent(ILayoutHandler handler, Layout layout) =>
- UpdateInputTransparent(handler, layout);
-
- static void MapInputTransparent(IViewHandler handler, IView layout) =>
- UpdateInputTransparent(handler, layout);
-
- static void UpdateInputTransparent(IViewHandler handler, IView view)
- {
- if (handler.PlatformView is not Microsoft.Maui.Platform.LayoutViewGroup platformView ||
- view is not Layout layout)
- {
- return;
- }
-
- if (layout.CascadeInputTransparent)
- {
- // Sensitive property on NUI View was false, disabled all touch event including children
- platformView.Sensitive = !layout.InputTransparent;
- platformView.InputTransparent = false;
- }
- else
- {
- // InputTransparent property on LayoutViewGroup was false,
- // Only LayoutViewGroup event was disabled but children are allowed
- platformView.InputTransparent = layout.InputTransparent;
- platformView.Sensitive = true;
- }
- }
+ public static void MapInputTransparent(ILayoutHandler handler, Layout layout) { }
}
}
diff --git a/src/Controls/src/Core/Layout/Layout.Windows.cs b/src/Controls/src/Core/Layout/Layout.Windows.cs
index b118ff2ef221..1c2001468d15 100644
--- a/src/Controls/src/Core/Layout/Layout.Windows.cs
+++ b/src/Controls/src/Core/Layout/Layout.Windows.cs
@@ -5,26 +5,8 @@ namespace Microsoft.Maui.Controls
{
public partial class Layout
{
- public static void MapInputTransparent(LayoutHandler handler, Layout layout) =>
- UpdateInputTransparent(handler, layout);
+ public static void MapInputTransparent(LayoutHandler handler, Layout layout) { }
- public static void MapInputTransparent(ILayoutHandler handler, Layout layout) =>
- UpdateInputTransparent(handler, layout);
-
- static void MapInputTransparent(IViewHandler handler, IView layout) =>
- UpdateInputTransparent(handler, layout);
-
- static void UpdateInputTransparent(IViewHandler handler, IView layout)
- {
- if (handler is ILayoutHandler layoutHandler && layout is Layout controlsLayout)
- {
- layoutHandler.PlatformView?.UpdateInputTransparent(layoutHandler, controlsLayout);
- controlsLayout.UpdateDescendantInputTransparent();
- }
- else
- {
- ControlsVisualElementMapper.UpdateProperty(handler, layout, nameof(IView.InputTransparent));
- }
- }
+ public static void MapInputTransparent(ILayoutHandler handler, Layout layout) { }
}
}
\ No newline at end of file
diff --git a/src/Controls/src/Core/Layout/Layout.cs b/src/Controls/src/Core/Layout/Layout.cs
index 94a29eccb50d..6acd909886d0 100644
--- a/src/Controls/src/Core/Layout/Layout.cs
+++ b/src/Controls/src/Core/Layout/Layout.cs
@@ -11,7 +11,7 @@ namespace Microsoft.Maui.Controls
{
///
[ContentProperty(nameof(Children))]
- public abstract partial class Layout : View, Maui.ILayout, IList, IBindableLayout, IPaddingElement, IVisualTreeElement, ISafeAreaView
+ public abstract partial class Layout : View, Maui.ILayout, IList, IBindableLayout, IPaddingElement, IVisualTreeElement, ISafeAreaView, IInputTransparentContainerElement
{
protected ILayoutManager _layoutManager;
@@ -211,9 +211,6 @@ protected virtual void OnAdd(int index, IView view)
{
NotifyHandler(nameof(ILayoutHandler.Add), index, view);
- // Make sure CascadeInputTransparent is applied, if necessary
- Handler?.UpdateValue(nameof(CascadeInputTransparent));
-
// Take care of the Element internal bookkeeping
if (view is Element element)
{
@@ -241,9 +238,6 @@ protected virtual void OnInsert(int index, IView view)
{
NotifyHandler(nameof(ILayoutHandler.Insert), index, view);
- // Make sure CascadeInputTransparent is applied, if necessary
- Handler?.UpdateValue(nameof(CascadeInputTransparent));
-
// Take care of the Element internal bookkeeping
if (view is Element element)
{
@@ -254,9 +248,6 @@ protected virtual void OnInsert(int index, IView view)
protected virtual void OnUpdate(int index, IView view, IView oldView)
{
NotifyHandler(nameof(ILayoutHandler.Update), index, view);
-
- // Make sure CascadeInputTransparent is applied, if necessary
- Handler?.UpdateValue(nameof(CascadeInputTransparent));
}
void NotifyHandler(string action, int index, IView view)
@@ -287,7 +278,8 @@ public Graphics.Size CrossPlatformArrange(Graphics.Rect bounds)
}
public static readonly BindableProperty CascadeInputTransparentProperty =
- BindableProperty.Create(nameof(CascadeInputTransparent), typeof(bool), typeof(Layout), true);
+ BindableProperty.Create(nameof(CascadeInputTransparent), typeof(bool), typeof(Layout), true,
+ propertyChanged: OnCascadeInputTransparentPropertyChanged);
public bool CascadeInputTransparent
{
@@ -295,21 +287,13 @@ public bool CascadeInputTransparent
set => SetValue(CascadeInputTransparentProperty, value);
}
- void UpdateDescendantInputTransparent()
+ static void OnCascadeInputTransparentPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
- if (!InputTransparent || !CascadeInputTransparent)
+ // We only need to update if the cascade changes anything, namely when InputTransparent=true.
+ // When InputTransparent=false, then the cascade property has no effect.
+ if (bindable is Layout layout && layout.InputTransparent)
{
- // We only need to propagate values if the layout is InputTransparent AND Cascade is true
- return;
- }
-
- // Set all the child InputTransparent values to match this one
- for (int n = 0; n < Count; n++)
- {
- if (this[n] is VisualElement visualElement)
- {
- visualElement.InputTransparent = true;
- }
+ layout.RefreshInputTransparentProperty();
}
}
}
diff --git a/src/Controls/src/Core/Layout/Layout.iOS.cs b/src/Controls/src/Core/Layout/Layout.iOS.cs
index 2f69361182f5..079d21ba8bda 100644
--- a/src/Controls/src/Core/Layout/Layout.iOS.cs
+++ b/src/Controls/src/Core/Layout/Layout.iOS.cs
@@ -3,26 +3,8 @@ namespace Microsoft.Maui.Controls
{
public partial class Layout
{
- public static void MapInputTransparent(LayoutHandler handler, Layout layout) =>
- UpdateInputTransparent(handler, layout);
+ public static void MapInputTransparent(LayoutHandler handler, Layout layout) { }
- public static void MapInputTransparent(ILayoutHandler handler, Layout layout) =>
- UpdateInputTransparent(handler, layout);
-
- static void MapInputTransparent(IViewHandler handler, IView layout) =>
- UpdateInputTransparent(handler, layout);
-
- static void UpdateInputTransparent(IViewHandler handler, IView layout)
- {
- if (handler is ILayoutHandler layoutHandler && layout is Layout controlsLayout)
- {
- layoutHandler.PlatformView?.UpdateInputTransparent(layoutHandler, controlsLayout);
- controlsLayout.UpdateDescendantInputTransparent();
- }
- else
- {
- ControlsVisualElementMapper.UpdateProperty(handler, layout, nameof(IView.InputTransparent));
- }
- }
+ public static void MapInputTransparent(ILayoutHandler handler, Layout layout) { }
}
}
diff --git a/src/Controls/src/Core/VisualElement/VisualElement.cs b/src/Controls/src/Core/VisualElement/VisualElement.cs
index 5b91b38c97d1..6d1a12d382ec 100644
--- a/src/Controls/src/Core/VisualElement/VisualElement.cs
+++ b/src/Controls/src/Core/VisualElement/VisualElement.cs
@@ -21,8 +21,12 @@ public partial class VisualElement : NavigableElement, IAnimatable, IVisualEleme
///
public new static readonly BindableProperty StyleProperty = NavigableElement.StyleProperty;
+ bool _inputTransparentExplicit = (bool)InputTransparentProperty.DefaultValue;
+
/// Bindable property for .
- public static readonly BindableProperty InputTransparentProperty = BindableProperty.Create("InputTransparent", typeof(bool), typeof(VisualElement), default(bool));
+ public static readonly BindableProperty InputTransparentProperty = BindableProperty.Create(
+ "InputTransparent", typeof(bool), typeof(VisualElement), default(bool),
+ propertyChanged: OnInputTransparentPropertyChanged, coerceValue: CoerceInputTransparentProperty);
bool _isEnabledExplicit = (bool)IsEnabledProperty.DefaultValue;
@@ -586,6 +590,40 @@ protected virtual bool IsEnabledCore
}
}
+ ///
+ /// This value represents the cumulative InputTransparent value.
+ /// All types that override this property need to also invoke
+ /// the RefreshInputTransparentProperty() method if the value will change.
+ ///
+ /// This method is not virtual as none of the derived types actually need
+ /// to change the calculation. If this ever needs to change, then the
+ /// RefreshInputTransparentProperty() method should also call the
+ /// RefreshPropertyValue() method - just like how the
+ /// RefreshIsEnabledProperty() method does.
+ ///
+ private protected bool InputTransparentCore
+ {
+ get
+ {
+ if (_inputTransparentExplicit == true)
+ {
+ // If the explicitly set value is true, then nothing else matters
+ // And we can save the effort of a Parent check
+ return true;
+ }
+
+ var parent = Parent as IInputTransparentContainerElement;
+ while (parent is not null)
+ {
+ if (parent.CascadeInputTransparent && parent.InputTransparent)
+ return true;
+ parent = parent.Parent as IInputTransparentContainerElement;
+ }
+
+ return _inputTransparentExplicit;
+ }
+ }
+
///
public bool IsFocused => (bool)GetValue(IsFocusedProperty);
@@ -1276,6 +1314,22 @@ static void OnIsEnabledPropertyChanged(BindableObject bindable, object oldValue,
(bindable as IPropertyPropagationController)?.PropagatePropertyChanged(VisualElement.IsEnabledProperty.PropertyName);
}
+ static object CoerceInputTransparentProperty(BindableObject bindable, object value)
+ {
+ if (bindable is VisualElement visualElement)
+ {
+ visualElement._inputTransparentExplicit = (bool)value;
+ return visualElement.InputTransparentCore;
+ }
+
+ return false;
+ }
+
+ static void OnInputTransparentPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as IPropertyPropagationController)?.PropagatePropertyChanged(VisualElement.InputTransparentProperty.PropertyName);
+ }
+
static void OnIsFocusedPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var element = (VisualElement)bindable;
@@ -1335,6 +1389,9 @@ void IPropertyPropagationController.PropagatePropertyChanged(string propertyName
if (propertyName == null || propertyName == IsEnabledProperty.PropertyName)
this.RefreshPropertyValue(IsEnabledProperty, _isEnabledExplicit);
+ if (propertyName == null || propertyName == InputTransparentProperty.PropertyName)
+ this.RefreshPropertyValue(InputTransparentProperty, _inputTransparentExplicit);
+
PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, ((IVisualTreeElement)this).GetVisualChildren());
}
@@ -1345,6 +1402,20 @@ void IPropertyPropagationController.PropagatePropertyChanged(string propertyName
protected void RefreshIsEnabledProperty() =>
this.RefreshPropertyValue(IsEnabledProperty, _isEnabledExplicit);
+ ///
+ /// This method must always be called if some event occurs and the value of
+ /// the InputTransparentCore property will change.
+ ///
+ private protected void RefreshInputTransparentProperty()
+ {
+ // This method does not need to call the
+ // this.RefreshPropertyValue(InputTransparentProperty, _inputTransparentExplicit);
+ // method because none of the derived types will affect this view. All we
+ // need to do is propagate the new value to all the children.
+
+ (this as IPropertyPropagationController)?.PropagatePropertyChanged(VisualElement.InputTransparentProperty.PropertyName);
+ }
+
void UpdateBoundsComponents(Rect bounds)
{
_frame = bounds;
diff --git a/src/Controls/tests/Core.UnitTests/Layouts/LayoutTests.cs b/src/Controls/tests/Core.UnitTests/Layouts/LayoutTests.cs
index 20c3817aa681..77979af6927e 100644
--- a/src/Controls/tests/Core.UnitTests/Layouts/LayoutTests.cs
+++ b/src/Controls/tests/Core.UnitTests/Layouts/LayoutTests.cs
@@ -128,63 +128,6 @@ static Layout CreateLayout(Type TLayout)
return layout;
}
- [Fact]
- public void AddRespectsCascadeInputTransparent()
- {
- var layout = new VerticalStackLayout()
- {
- InputTransparent = true,
- CascadeInputTransparent = true
- };
-
- var handler = Substitute.For();
- layout.Handler = handler;
-
- var child0 = new Button() { InputTransparent = false };
- layout.Add(child0);
-
- handler.Received().UpdateValue(Arg.Is(nameof(Layout.CascadeInputTransparent)));
- }
-
- [Fact]
- public void InsertRespectsCascadeInputTransparent()
- {
- var layout = new VerticalStackLayout()
- {
- InputTransparent = true,
- CascadeInputTransparent = true
- };
-
- var handler = Substitute.For();
- layout.Handler = handler;
-
- var child0 = new Button() { InputTransparent = false };
- layout.Insert(0, child0);
-
- handler.Received().UpdateValue(Arg.Is(nameof(Layout.CascadeInputTransparent)));
- }
-
- [Fact]
- public void UpdateRespectsCascadeInputTransparent()
- {
- var layout = new VerticalStackLayout()
- {
- InputTransparent = true,
- CascadeInputTransparent = true
- };
-
- var handler = Substitute.For();
- layout.Handler = handler;
-
- var child0 = new Button() { InputTransparent = false };
- layout.Add(child0);
-
- var child1 = new Button() { InputTransparent = false };
- layout[0] = child1;
-
- handler.Received(2).UpdateValue(Arg.Is(nameof(Layout.CascadeInputTransparent)));
- }
-
IMauiContext SetupContext(ILayoutManagerFactory layoutManagerFactory)
{
var services = Substitute.For();
diff --git a/src/Controls/tests/Core.UnitTests/VisualElementInputTransparentTests.cs b/src/Controls/tests/Core.UnitTests/VisualElementInputTransparentTests.cs
new file mode 100644
index 000000000000..101265199279
--- /dev/null
+++ b/src/Controls/tests/Core.UnitTests/VisualElementInputTransparentTests.cs
@@ -0,0 +1,557 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Maui.Controls.Shapes;
+using Microsoft.Maui.Graphics;
+using Microsoft.Maui.Primitives;
+using Xunit;
+using Xunit.Sdk;
+
+namespace Microsoft.Maui.Controls.Core.UnitTests
+{
+ public class VisualElementInputTransparentTests
+ {
+ // this is both for color diff and cols
+ const bool truee = true;
+
+ static IReadOnlyDictionary<(bool, bool, bool, bool, bool), (bool, bool)> States = new Dictionary<(bool, bool, bool, bool, bool), (bool, bool)>
+ {
+ [(truee, truee, truee, truee, truee)] = (truee, truee),
+ [(truee, truee, truee, truee, false)] = (truee, truee),
+ [(truee, truee, truee, false, truee)] = (truee, truee),
+ [(truee, truee, truee, false, false)] = (truee, truee),
+ [(truee, truee, false, truee, truee)] = (truee, truee),
+ [(truee, truee, false, truee, false)] = (truee, truee),
+ [(truee, truee, false, false, truee)] = (truee, truee),
+ [(truee, truee, false, false, false)] = (truee, truee),
+ [(truee, false, truee, truee, truee)] = (truee, truee),
+ [(truee, false, truee, truee, false)] = (truee, truee),
+ [(truee, false, truee, false, truee)] = (truee, truee),
+ [(truee, false, truee, false, false)] = (truee, false),
+ [(truee, false, false, truee, truee)] = (false, truee),
+ [(truee, false, false, truee, false)] = (false, false),
+ [(truee, false, false, false, truee)] = (false, truee),
+ [(truee, false, false, false, false)] = (false, false),
+ [(false, truee, truee, truee, truee)] = (truee, truee),
+ [(false, truee, truee, truee, false)] = (truee, truee),
+ [(false, truee, truee, false, truee)] = (truee, truee),
+ [(false, truee, truee, false, false)] = (truee, false),
+ [(false, truee, false, truee, truee)] = (false, truee),
+ [(false, truee, false, truee, false)] = (false, false),
+ [(false, truee, false, false, truee)] = (false, truee),
+ [(false, truee, false, false, false)] = (false, false),
+ [(false, false, truee, truee, truee)] = (truee, truee),
+ [(false, false, truee, truee, false)] = (truee, truee),
+ [(false, false, truee, false, truee)] = (truee, truee),
+ [(false, false, truee, false, false)] = (truee, false),
+ [(false, false, false, truee, truee)] = (false, truee),
+ [(false, false, false, truee, false)] = (false, false),
+ [(false, false, false, false, truee)] = (false, truee),
+ [(false, false, false, false, false)] = (false, false),
+ };
+
+ public static IEnumerable