Skip to content

Commit

Permalink
feat: Responsive markup extension
Browse files Browse the repository at this point in the history
  • Loading branch information
silviuo committed Nov 22, 2023
1 parent 5850dc6 commit 286b723
Show file tree
Hide file tree
Showing 7 changed files with 420 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ protected override async void OnLaunched(XamlLaunchActivatedEventArgs e)
_window = XamlWindow.Current;
#endif

var helper = UI.Helpers.ResponsiveHelper.GetForCurrentView();
helper.HookupEvent(_window);

if (_window.Content is null)
{
var loadable = new ManualLoadable { IsExecuting = true };
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<Page x:Class="Uno.Toolkit.Samples.Content.Helpers.ResponsiveExtensionsSamplePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sample="using:Uno.Toolkit.Samples"
xmlns:utu="using:Uno.Toolkit.UI"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<Orientation x:Key="NarrowOrientation">Vertical</Orientation>
<Orientation x:Key="WideOrientation">Horizontal</Orientation>
<x:Double x:Key="NarrowFontSize">15</x:Double>
<x:Double x:Key="WideFontSize">30</x:Double>
<SolidColorBrush x:Key="NarrowRed">Crimson</SolidColorBrush>
<SolidColorBrush x:Key="WideBlue">Blue</SolidColorBrush>
</Page.Resources>

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<sample:SamplePageLayout IsDesignAgnostic="True">
<sample:SamplePageLayout.DesignAgnosticTemplate>
<DataTemplate>
<StackPanel Spacing="20">
<TextBlock Text="Orientation test | Normal=Vertical | Wide=Horizontal" FontWeight="Bold" />
<StackPanel Orientation="{utu:Responsive Normal={StaticResource NarrowOrientation}, Wide={StaticResource WideOrientation}}">
<TextBlock Text="A" />
<TextBlock Text="B" />
<TextBlock Text="C" />
</StackPanel>

<TextBlock Text="Text test" FontWeight="Bold" />
<TextBlock Text="{utu:Responsive Narrow='Narrow Threshold 300', Normal='Normal Threshold 600', Wide='Wide Threshold 800'}" />

<TextBlock Text="FontSize test" FontWeight="Bold" />
<TextBlock Text="Normal 15 Wide 30"
FontSize="{utu:Responsive Normal={StaticResource NarrowFontSize},
Wide={StaticResource WideFontSize}}" />

<TextBlock Text="Color test | Normal Red | Wide Blue" FontWeight="Bold" />
<Border Width="30"
Height="30"
HorizontalAlignment="Left"
Background="{utu:Responsive Normal={StaticResource NarrowRed},
Wide={StaticResource WideBlue}}" />
</StackPanel>
</DataTemplate>
</sample:SamplePageLayout.DesignAgnosticTemplate>
</sample:SamplePageLayout>
</Grid>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using Uno.Toolkit.Samples.Entities;
using Uno.Toolkit.Samples.Helpers;
using Uno.Toolkit.Samples.ViewModels;
using Windows.Foundation;
using Windows.Foundation.Collections;
#if IS_WINUI
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
#else
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
#endif

namespace Uno.Toolkit.Samples.Content.Helpers;

[SamplePage(SampleCategory.Helpers, "Responsive Extensions")]
public sealed partial class ResponsiveExtensionsSamplePage : Page
{
public ResponsiveExtensionsSamplePage()
{
this.InitializeComponent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Content\Helpers\BindingExtensionsSamplePage.xaml.cs">
<DependentUpon>BindingExtensionsSamplePage.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Content\Helpers\ResponsiveExtensionsSamplePage.xaml.cs">
<DependentUpon>ResponsiveExtensionsSamplePage.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Content\NestedSamples\FluentNavigationBarSampleNestedPage1.xaml.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Content\NestedSamples\FluentNavigationBarSampleNestedPage2.xaml.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Content\NestedSamples\M3MaterialBottomBarSampleNestedPage.xaml.cs">
Expand Down Expand Up @@ -289,6 +292,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Content\Helpers\ResponsiveExtensionsSamplePage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Content\NestedSamples\FluentNavigationBarSampleNestedPage1.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down
26 changes: 26 additions & 0 deletions src/Uno.Toolkit.UI/Extensions/DependencyObjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,32 @@ internal static void SetParent(this DependencyObject dependencyObject, object? p
return property;
}

public static DependencyProperty? FindDependencyPropertyUsingReflection(this DependencyObject dependencyObject, string propertyName)
{
var type = dependencyObject.GetType();
var key = (ownerType: type, propertyName);

if (_dependencyPropertyReflectionCache.TryGetValue(key, out var property))
{
return property;
}

property =
type.GetProperty(propertyName, Public | Static | FlattenHierarchy)?.GetValue(null) as DependencyProperty ??
type.GetField(propertyName, Public | Static | FlattenHierarchy)?.GetValue(null) as DependencyProperty;

#if HAS_UNO
if (property == null)
{
typeof(DependencyObjectExtensions).Log().LogWarning($"The '{type}.{propertyName}' dependency property does not exist.");
}
#endif

_dependencyPropertyReflectionCache[key] = property;

return property;
}

public static bool TryGetValue(this DependencyObject dependencyObject, DependencyProperty dependencyProperty, out DependencyObject? value)
{
value = default;
Expand Down
176 changes: 176 additions & 0 deletions src/Uno.Toolkit.UI/Helpers/ResponsiveHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#if HAS_UNO
#define UNO14502_WORKAROUND // https://github.com/unoplatform/uno/issues/14502
#endif

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Windows.Foundation;

namespace Uno.Toolkit.UI.Helpers;

internal interface IResponsiveCallback
{
void OnSizeChanged(Size size, ResponsiveLayout layout);
}

public partial class ResponsiveLayout : DependencyObject
{
#region DependencyProperty: Narrowest

public static DependencyProperty NarrowestProperty { get; } = DependencyProperty.Register(
nameof(Narrowest),
typeof(double),
typeof(ResponsiveLayout),
new PropertyMetadata(0d));

public double Narrowest
{
get => (double)GetValue(NarrowestProperty);
set => SetValue(NarrowestProperty, value);
}

#endregion
#region DependencyProperty: Narrow

public static DependencyProperty NarrowProperty { get; } = DependencyProperty.Register(
nameof(Narrow),
typeof(double),
typeof(ResponsiveLayout),
new PropertyMetadata(0d));

public double Narrow
{
get => (double)GetValue(NarrowProperty);
set => SetValue(NarrowProperty, value);
}

#endregion
#region DependencyProperty: Normal

public static DependencyProperty NormalProperty { get; } = DependencyProperty.Register(
nameof(Normal),
typeof(double),
typeof(ResponsiveLayout),
new PropertyMetadata(0d));

public double Normal
{
get => (double)GetValue(NormalProperty);
set => SetValue(NormalProperty, value);
}

#endregion
#region DependencyProperty: Wide

public static DependencyProperty WideProperty { get; } = DependencyProperty.Register(
nameof(Wide),
typeof(double),
typeof(ResponsiveLayout),
new PropertyMetadata(0d));

public double Wide
{
get => (double)GetValue(WideProperty);
set => SetValue(WideProperty, value);
}

#endregion
#region DependencyProperty: Widest

public static DependencyProperty WidestProperty { get; } = DependencyProperty.Register(
nameof(Widest),
typeof(double),
typeof(ResponsiveLayout),
new PropertyMetadata(0d));

public double Widest
{
get => (double)GetValue(WidestProperty);
set => SetValue(WidestProperty, value);
}

#endregion

public static ResponsiveLayout Create(double narrowest, double narrow, double normal, double wide, double widest) => new()
{
Narrowest = narrowest,
Narrow = narrow,
Normal = normal,
Wide = wide,
Widest = widest,
};
}

internal class ResponsiveHelper
{
private static readonly Lazy<ResponsiveHelper> _instance = new Lazy<ResponsiveHelper>(() => new ResponsiveHelper());
private readonly List<WeakReference> _callbacks = new();
#if UNO14502_WORKAROUND
private List<IResponsiveCallback> _hardCallbackReferences = new();
#endif

public ResponsiveLayout Layout { get; private set; } = ResponsiveLayout.Create(150, 300, 600, 800, 1080);
public Size WindowSize { get; private set; } = Size.Empty;

public static ResponsiveHelper GetForCurrentView() => _instance.Value;

private ResponsiveHelper() { }

public void HookupEvent(Window window)
{
WindowSize = new Size(window.Bounds.Width, window.Bounds.Height);

window.SizeChanged += OnWindowSizeChanged;
}

private void OnWindowSizeChanged(object sender, WindowSizeChangedEventArgs e)
{
WindowSize = e.Size;

// Clean up collected references
_callbacks.RemoveAll(reference => !reference.IsAlive);

foreach (var reference in _callbacks.ToArray())
{
if (reference.IsAlive && reference.Target is IResponsiveCallback callback)
{
#if UNO14502_WORKAROUND
// Note: In ResponsiveExtensionsSamplePage, if we are using SamplePageLayout with the template,
// it seems to keep the controls (_weakTarget) alive, even if we navigate out and back (new page).
// However, if we remove the SamplePageLayout, and add the template as a child instead,
// the controls will be properly collected.

// We are using a hard reference to keep the markup extension alive.
// We need to check if its reference target is still alive. If it is not, then it should be removed.
if (callback is ResponsiveExtension { _weakTarget: { IsAlive: false } })
{
_hardCallbackReferences.Remove(callback);
_callbacks.Remove(reference);

continue;
}
#endif
callback.OnSizeChanged(WindowSize, Layout);
}
}
}

internal void Register(IResponsiveCallback host)
{
#if UNO14502_WORKAROUND
// The workaround is only needed for ResponsiveExtension (MarkupExtension)
if (host is ResponsiveExtension)
{
_hardCallbackReferences.Add(host);
}
#endif

var wr = new WeakReference(host);
_callbacks.Add(wr);
}
}
Loading

0 comments on commit 286b723

Please sign in to comment.