Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 40 additions & 5 deletions src/Controls/src/Core/Handlers/Items2/Windows/MauiItemsView.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Maui.Controls.Platform;
using System;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Graphics;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
Expand Down Expand Up @@ -333,11 +334,14 @@ void ApplyLayoutOrientation()
// Using MinWidth (not Width) preserves horizontal scrolling when content exceeds
// the viewport — DesiredSize = max(contentWidth, MinWidth), so the ScrollViewer
// still sees the full content extent for many-item scenarios.
bool itemsWidthChanged = false;
var measured = base.MeasureOverride(availableSize);

if (_isHorizontalLayout && _itemsRepeater is not null)
{
if (!emptyViewWillFill && !double.IsInfinity(availableSize.Width) && availableSize.Width > 0)
{
_itemsRepeater.MinWidth = availableSize.Width;
itemsWidthChanged = ApplyItemsRepeaterMinWidth(availableSize);
}
else
{
Expand All @@ -353,14 +357,14 @@ void ApplyLayoutOrientation()
_itemsRepeater.ClearValue(MinWidthProperty);
}

var measured = base.MeasureOverride(availableSize);

// When the EmptyView is visible, stretch it to fill the remaining viewport
// space (after Header/Footer/Items) so it occupies the entire available area.
// The EmptyView is laid out inside the StackPanel between the items repeater
// and the footer; setting MinHeight/MinWidth here pushes the Footer to the
// far edge of the viewport when empty.
if (SizeEmptyViewToFillViewport(availableSize))
bool emptyViewSizeChanged = SizeEmptyViewToFillViewport(availableSize);

if (emptyViewSizeChanged || itemsWidthChanged)
{
// MinHeight/MinWidth changed — re-measure so the StackPanel picks it up.
measured = base.MeasureOverride(availableSize);
Expand All @@ -369,6 +373,37 @@ void ApplyLayoutOrientation()
return measured;
}

/// <summary>
/// Sets the _itemsRepeater MinWidth to the available viewport width minus the
/// Header/Footer's own widths, so items stretch to fill the remaining space.
/// Returns true if the MinWidth value changed.
/// </summary>
bool ApplyItemsRepeaterMinWidth(global::Windows.Foundation.Size availableSize)
{
if (_itemsRepeater is null)
{
return false;
}

var headerWidth = (_headerContentControl is not null && _headerContentControl.Visibility == WVisibility.Visible)
? _headerContentControl.DesiredSize.Width
: 0;
var footerWidth = (_footerContentControl is not null && _footerContentControl.Visibility == WVisibility.Visible)
? _footerContentControl.DesiredSize.Width
: 0;

var remainingWidth = availableSize.Width - headerWidth - footerWidth;
var newMinWidth = Math.Max(remainingWidth, 0);

if (_itemsRepeater.MinWidth == newMinWidth)
{
return false;
}

_itemsRepeater.MinWidth = newMinWidth;
return true;
}

bool SizeEmptyViewToFillViewport(global::Windows.Foundation.Size availableSize)
{
if (_emptyViewContentControl is null || _emptyViewVisibility != WVisibility.Visible)
Expand Down
5 changes: 1 addition & 4 deletions src/Controls/tests/DeviceTests/Elements/Modal/ModalTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,7 @@ namespace Microsoft.Maui.DeviceTests
[Trait(RendererHandlerVariant.TraitName, RendererHandlerVariant.AndroidShellRenderer)] // See RendererHandlerVariant.cs
public partial class ModalTests : ControlsHandlerTestBase
{
protected virtual void SetupBuilder() =>
SetupBuilder(includeNavigationViewHandler: true);

void SetupBuilder(bool includeNavigationViewHandler)
protected virtual void SetupBuilder(bool includeNavigationViewHandler = true)
{
EnsureHandlerCreated(builder =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ MaterialToolbar GetShellHandlerToolbar(IElementHandler handler)
[Trait(RendererHandlerVariant.TraitName, RendererHandlerVariant.AndroidShellHandler)] // See RendererHandlerVariant.cs
public partial class ModalHandlerTests : ModalTests
{
protected override void SetupBuilder()
protected override void SetupBuilder(bool includeNavigationViewHandler = true)
{
EnsureHandlerCreated(builder =>
{
Expand Down
69 changes: 67 additions & 2 deletions src/Core/src/Animations/PlatformTicker.Windows.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
using Microsoft.UI.Xaml.Media;
using System;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Media;
using ViewManagement = Windows.UI.ViewManagement;

namespace Microsoft.Maui.Animations
{
/// <inheritdoc/>
public class PlatformTicker : Ticker
public class PlatformTicker : Ticker, IDisposable
{
readonly ViewManagement.UISettings _uiSettings = new();
readonly DispatcherQueue? _dispatcherQueue;
bool _disposed;

/// <summary>
/// Creates a new Windows <see cref="PlatformTicker"/> that respects the "Show animations in Windows" accessibility setting.
/// </summary>
public PlatformTicker()
{
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
SystemEnabled = _uiSettings.AnimationsEnabled;
_uiSettings.AnimationsEnabledChanged += OnAnimationsEnabledChanged;
}

/// <inheritdoc/>
public override void Start()
{
if (_disposed)
{
return;
}

CompositionTarget.Rendering += RenderingFrameEventHandler;
}

Expand All @@ -17,6 +39,49 @@ public override void Stop()
CompositionTarget.Rendering -= RenderingFrameEventHandler;
}

/// <inheritdoc/>
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}

_disposed = true;

if (disposing)
{
Stop();
_uiSettings.AnimationsEnabledChanged -= OnAnimationsEnabledChanged;
}
}

/// <inheritdoc/>
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}

void OnAnimationsEnabledChanged(ViewManagement.UISettings sender, ViewManagement.UISettingsAnimationsEnabledChangedEventArgs args)
{
if (_disposed)
{
return;
}

if (_dispatcherQueue is not null)
{
_dispatcherQueue.TryEnqueue(() =>
{
if (!_disposed)
{
SystemEnabled = _uiSettings.AnimationsEnabled;
}
});
}
}

void RenderingFrameEventHandler(object? sender, object? args)
{
Fire?.Invoke();
Expand Down
64 changes: 61 additions & 3 deletions src/Core/src/Animations/PlatformTicker.iOS.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
using CoreAnimation;
using System;
using CoreAnimation;
using Foundation;
using UIKit;

namespace Microsoft.Maui.Animations
{
/// <inheritdoc/>
public class PlatformTicker : Ticker
public class PlatformTicker : Ticker, IDisposable
{
CADisplayLink? _link;
NSObject? _observer;
bool _disposed;

/// <summary>
/// Creates a new iOS/MacCatalyst <see cref="PlatformTicker"/> that respects the Reduce Motion accessibility setting.
/// </summary>
public PlatformTicker()
{
SystemEnabled = !UIAccessibility.IsReduceMotionEnabled;

_observer = NSNotificationCenter.DefaultCenter.AddObserver(
UIApplication.ReduceMotionStatusDidChangeNotification,
OnReduceMotionStatusChanged);
}

/// <inheritdoc/>
public override bool IsRunning =>
Expand All @@ -15,8 +31,10 @@ public class PlatformTicker : Ticker
/// <inheritdoc/>
public override void Start()
{
if (_link != null)
if (_disposed || _link is not null)
{
return;
}

_link = CADisplayLink.Create(() => Fire?.Invoke());
_link.AddToRunLoop(NSRunLoop.Current, NSRunLoopMode.Common);
Expand All @@ -32,5 +50,45 @@ public override void Stop()
_link?.Dispose();
_link = null;
}

/// <inheritdoc/>
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}

_disposed = true;

if (disposing)
{
Stop();

if (_observer is not null)
{
NSNotificationCenter.DefaultCenter.RemoveObserver(_observer);
_observer.Dispose();
_observer = null;
}
}
}

/// <inheritdoc/>
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}

void OnReduceMotionStatusChanged(NSNotification notification)
{
if (_disposed)
{
return;
}

SystemEnabled = !UIAccessibility.IsReduceMotionEnabled;
}
}
}
2 changes: 2 additions & 0 deletions src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ static Microsoft.Maui.Handlers.EntryHandler.MapBackground(Microsoft.Maui.Handler
static Microsoft.Maui.Handlers.RadioButtonHandler.MapBackground(Microsoft.Maui.Handlers.IRadioButtonHandler! handler, Microsoft.Maui.IRadioButton! radioButton) -> void
static Microsoft.Maui.Handlers.WindowHandler.MapStatusBarTheme(Microsoft.Maui.Handlers.IWindowHandler! handler, Microsoft.Maui.IWindow! window) -> void
static Microsoft.Maui.SafeAreaEdges.Container.get -> Microsoft.Maui.SafeAreaEdges
Microsoft.Maui.Animations.PlatformTicker.Dispose() -> void
virtual Microsoft.Maui.Animations.PlatformTicker.Dispose(bool disposing) -> void
static Microsoft.Maui.Handlers.TimePickerHandler.MapFlowDirection(Microsoft.Maui.Handlers.ITimePickerHandler! handler, Microsoft.Maui.ITimePicker! timePicker) -> void
static Microsoft.Maui.Handlers.StepperHandler.MapFlowDirection(Microsoft.Maui.Handlers.IStepperHandler! handler, Microsoft.Maui.IStepper! stepper) -> void
static Microsoft.Maui.Handlers.ButtonHandler.MapBackground(Microsoft.Maui.Handlers.IButtonHandler! handler, Microsoft.Maui.IButton! button) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ static Microsoft.Maui.Handlers.EntryHandler.MapBackground(Microsoft.Maui.Handler
static Microsoft.Maui.Handlers.RadioButtonHandler.MapBackground(Microsoft.Maui.Handlers.IRadioButtonHandler! handler, Microsoft.Maui.IRadioButton! radioButton) -> void
static Microsoft.Maui.Handlers.WindowHandler.MapStatusBarTheme(Microsoft.Maui.Handlers.IWindowHandler! handler, Microsoft.Maui.IWindow! window) -> void
static Microsoft.Maui.SafeAreaEdges.Container.get -> Microsoft.Maui.SafeAreaEdges
Microsoft.Maui.Animations.PlatformTicker.Dispose() -> void
virtual Microsoft.Maui.Animations.PlatformTicker.Dispose(bool disposing) -> void
static Microsoft.Maui.Handlers.StepperHandler.MapFlowDirection(Microsoft.Maui.Handlers.IStepperHandler! handler, Microsoft.Maui.IStepper! stepper) -> void
static Microsoft.Maui.Handlers.ShapeViewHandler.MapFlowDirection(Microsoft.Maui.Handlers.IShapeViewHandler! handler, Microsoft.Maui.IShapeView! shapeView) -> void
static Microsoft.Maui.Handlers.SearchBarHandler.MapCursorPosition(Microsoft.Maui.Handlers.ISearchBarHandler! handler, Microsoft.Maui.ISearchBar! searchBar) -> void
Expand Down
2 changes: 2 additions & 0 deletions src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Microsoft.Maui.ITab.Title.get -> string!
Microsoft.Maui.TabBarPlacement
Microsoft.Maui.TabBarPlacement.Bottom = 1 -> Microsoft.Maui.TabBarPlacement
Microsoft.Maui.TabBarPlacement.Top = 0 -> Microsoft.Maui.TabBarPlacement
Microsoft.Maui.Animations.PlatformTicker.Dispose() -> void
virtual Microsoft.Maui.Animations.PlatformTicker.Dispose(bool disposing) -> void
static Microsoft.Maui.GridLength.implicit operator Microsoft.Maui.GridLength(string! value) -> Microsoft.Maui.GridLength
static Microsoft.Maui.SafeAreaEdges.Container.get -> Microsoft.Maui.SafeAreaEdges
override Microsoft.Maui.Platform.ContentPanel.MeasureOverride(Windows.Foundation.Size availableSize) -> Windows.Foundation.Size
Expand Down
Loading