Skip to content

Commit

Permalink
feat: WinUI RadioButtons
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinZikmund authored and jeromelaban committed Dec 17, 2020
1 parent f3b0e46 commit e48de38
Show file tree
Hide file tree
Showing 12 changed files with 1,346 additions and 5 deletions.
12 changes: 9 additions & 3 deletions src/Uno.UI/Helpers/WinUI/SharedHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,12 @@
//

using System;
using System.Collections.Generic;
using System.Text;
using Windows.Foundation;
using Windows.Foundation.Metadata;
using Windows.Graphics.Display;
using Windows.System;
using Windows.System.Profile;
using Windows.System.Threading;
using Windows.UI.Text;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Automation;
Expand Down Expand Up @@ -874,6 +871,15 @@ FrameworkElement FindInVisualTreeInner(DependencyObject parent, Func<FrameworkEl
return FindInVisualTreeInner(parent, isMatch);
}

public static bool IsTrue(bool? nullableBool)
{
if (nullableBool != null)
{
return nullableBool.Value;
}
return false;
}

// Sometimes we want to get a string representation from an arbitrary object. E.g. for constructing a UIA Name
// from an automation peer. There is no guarantee that an arbitrary object is convertable to a string, so
// this function may return an empty string.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Windows.UI.Xaml;

namespace Microsoft.UI.Xaml.Controls.Primitives
{
public partial class ColumnMajorUniformToLargestGridLayout
{
public double ColumnSpacing
{
get => (double)GetValue(ColumnSpacingProperty);
set => SetValue(ColumnSpacingProperty, value);
}

public static readonly DependencyProperty ColumnSpacingProperty =
DependencyProperty.Register(nameof(ColumnSpacing), typeof(double), typeof(ColumnMajorUniformToLargestGridLayout), new PropertyMetadata(default(double), OnColumnSpacingPropertyChanged));

public int MaxColumns
{
get => (int)GetValue(MaxColumnsProperty);
set => SetValue(MaxColumnsProperty, value);
}

public static readonly DependencyProperty MaxColumnsProperty =
DependencyProperty.Register(nameof(MaxColumns), typeof(int), typeof(ColumnMajorUniformToLargestGridLayout), new PropertyMetadata(default(int), OnMaxColumnsPropertyChanged));

public double RowSpacing
{
get => (double)GetValue(RowSpacingProperty);
set => SetValue(RowSpacingProperty, value);
}

public static readonly DependencyProperty RowSpacingProperty =
DependencyProperty.Register(nameof(RowSpacing), typeof(double), typeof(ColumnMajorUniformToLargestGridLayout), new PropertyMetadata(default(double), OnRowSpacingPropertyChanged));

private static void OnColumnSpacingPropertyChanged(
DependencyObject sender,
DependencyPropertyChangedEventArgs args)
{
var owner = (ColumnMajorUniformToLargestGridLayout)sender;
owner.OnColumnSpacingPropertyChanged(args);
}

private static void OnMaxColumnsPropertyChanged(
DependencyObject sender,
DependencyPropertyChangedEventArgs args)
{
var owner = (ColumnMajorUniformToLargestGridLayout)sender;

var value = (int)args.NewValue;
var coercedValue = value;
owner.ValidateGreaterThanZero(coercedValue);
if (value != coercedValue)
{
sender.SetValue(args.Property, coercedValue);
return;
}

owner.OnMaxColumnsPropertyChanged(args);
}

private static void OnRowSpacingPropertyChanged(
DependencyObject sender,
DependencyPropertyChangedEventArgs args)
{
var owner = (ColumnMajorUniformToLargestGridLayout)sender;
owner.OnRowSpacingPropertyChanged(args);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
using System;
using Windows.Foundation;
using Windows.UI.Xaml;

namespace Microsoft.UI.Xaml.Controls.Primitives
{
public partial class ColumnMajorUniformToLargestGridLayout : NonVirtualizingLayout
{
private int m_actualColumnCount = 1;
private Size m_largestChildSize = Size.Empty;

//Testhooks helpers, only function while m_testHooksEnabled == true
private bool m_testHooksEnabled = false;

private int m_rows = -1;

private int m_columns = -1;

private int m_largerColumns = -1;

protected internal override Size MeasureOverride(NonVirtualizingLayoutContext context, Size availableSize)
{
var children = context.Children;
if (children != null)
{
Size GetLargestChildSize()
{
var largestChildWidth = 0.0;
var largestChildHeight = 0.0;
foreach (var child in children)
{
child.Measure(availableSize);
var desiredSize = child.DesiredSize;
if (desiredSize.Width > largestChildWidth)
{
largestChildWidth = desiredSize.Width;
}
if (desiredSize.Height > largestChildHeight)
{
largestChildHeight = desiredSize.Height;
}
}
return new Size(largestChildWidth, largestChildHeight);
}

m_largestChildSize = GetLargestChildSize();

m_actualColumnCount = CalculateColumns(children.Count, m_largestChildSize.Width, availableSize.Width);
var maxItemsPerColumn = (int)(Math.Ceiling((double)(children.Count) / (double)(m_actualColumnCount)));
return new Size(
(m_largestChildSize.Width * m_actualColumnCount) +
((float)(ColumnSpacing) * (m_actualColumnCount - 1)),
(m_largestChildSize.Height * maxItemsPerColumn) +
((float)(RowSpacing) * (maxItemsPerColumn - 1))
);
}
return new Size(0, 0);
}

protected internal override Size ArrangeOverride(NonVirtualizingLayoutContext context, Size finalSize)
{
var children = context.Children;
if (children != null)
{
var itemCount = children.Count;
var minitemsPerColumn = (int)(Math.Floor((float)(itemCount) / m_actualColumnCount));
var numberOfColumnsWithExtraElements = (int)(itemCount % (int)(m_actualColumnCount));

var columnSpacing = (float)(ColumnSpacing);
var rowSpacing = (float)(RowSpacing);

var horizontalOffset = 0.0;
var verticalOffset = 0.0;
var index = 0;
var column = 0;
foreach (var child in children)
{
var desiredSize = child.DesiredSize;
child.Arrange(new Rect(horizontalOffset, verticalOffset, desiredSize.Width, desiredSize.Height));
if (column < numberOfColumnsWithExtraElements)
{
if (index % (minitemsPerColumn + 1) == minitemsPerColumn)
{
horizontalOffset += m_largestChildSize.Width + columnSpacing;
verticalOffset = 0.0;
column++;
}
else
{
verticalOffset += m_largestChildSize.Height + rowSpacing;
}
}
else
{
var indexAfterExtraLargeColumns = index - (numberOfColumnsWithExtraElements * (minitemsPerColumn + 1));
if (indexAfterExtraLargeColumns % minitemsPerColumn == minitemsPerColumn - 1)
{
horizontalOffset += m_largestChildSize.Width + columnSpacing;
verticalOffset = 0.0;
column++;
}
else
{
verticalOffset += m_largestChildSize.Height + rowSpacing;
}
}
index++;
}

if (m_testHooksEnabled)
{
//Testhooks setup
if (m_largerColumns != numberOfColumnsWithExtraElements ||
m_columns != column ||
m_rows != minitemsPerColumn)
{
m_largerColumns = numberOfColumnsWithExtraElements;
m_columns = column;
m_rows = minitemsPerColumn;

LayoutChanged?.Invoke(this, null);
}
}
}
return finalSize;
}

void OnColumnSpacingPropertyChanged(DependencyPropertyChangedEventArgs args)
{
InvalidateMeasure();
}

void OnRowSpacingPropertyChanged(DependencyPropertyChangedEventArgs args)
{
InvalidateMeasure();
}

void OnMaxColumnsPropertyChanged(DependencyPropertyChangedEventArgs args)
{
InvalidateMeasure();
}

int CalculateColumns(int childCount, double maxItemWidth, double availableWidth)
{
/*
--------------------------------------------------------------
| |-----------|-----------| | widthNeededForExtraColumn |
| | |
| |------| |------| | ColumnSpacing |
| |----| |----| |----| | maxItemWidth |
| O RB O RB O RB | |
--------------------------------------------------------------
*/

// Every column execpt the first takes this ammount of space to fit on screen.
var widthNeededForExtraColumn = ColumnSpacing + maxItemWidth;
// The number of columns from data and api ignoring available space
var requestedColumnCount = Math.Min(MaxColumns, childCount);

// If columns can be added with effectively 0 extra space return as many columns as needed.
if (widthNeededForExtraColumn < float.Epsilon)
{
return requestedColumnCount;
}

var extraWidthAfterFirstColumn = availableWidth - maxItemWidth;
var maxExtraColumns = Math.Max(0.0, Math.Floor(extraWidthAfterFirstColumn / widthNeededForExtraColumn));

// The smaller of number of columns from data and api and
// the number of columns the available space can support
var effectiveColumnCount = Math.Min((double)(requestedColumnCount), maxExtraColumns + 1);
// return 1 even if there isn't any data
return Math.Max(1, (int)(effectiveColumnCount));
}

private void ValidateGreaterThanZero(int value)
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
}

//Testhooks helpers, only function while m_testHooksEnabled == true
internal void SetTestHooksEnabled(bool enabled)
{
m_testHooksEnabled = enabled;
}

internal int GetRows()
{
return m_rows;
}

internal int GetColumns()
{
return m_columns;
}

internal int GetLargerColumns()
{
return m_largerColumns;
}

internal event TypedEventHandler<ColumnMajorUniformToLargestGridLayout, object> LayoutChanged;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. See LICENSE in the project root for license information. -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<Style TargetType="RadioButton" BasedOn="{StaticResource DefaultRadioButtonStyle}" />

</ResourceDictionary>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace Microsoft.UI.Xaml.Controls
{
public partial class RadioButtons
{
// This is an object that RadioButtons intends to attach to its child RadioButton elements.
// It contains the revokers for the events on RadioButton that RadioButttons listens to
// in order to manage selection. Attaching the revokers to the object allows the parent
// RadioButtons the ability to "Set it and forget it" since the lifetime of the RadioButton
// and these event registrations are now intrically linked.
// This is not needed in Uno, as we can directly unsubscribe the events
//private class ChildHandlers
//{
// ToggleButton Checked_revoker checkedRevoker;
// winrt::ToggleButton::Unchecked_revoker uncheckedRevoker;
//}

private int m_selectedIndex = -1;

// This is used to guard against reentrency when calling select, since select changes
// the Selected Index/Item which in turn calls select.
private bool m_currentlySelecting = false;

// We block selection before the control has loaded.
// This is to ensure that we do not overwrite a provided Selected Index/Item value.
private bool m_blockSelecting = true;

private ItemsRepeater m_repeater = null;

private RadioButtonsElementFactory m_radioButtonsElementFactory = null;

private bool m_testHooksEnabled = false;

private const string s_repeaterName = "InnerRepeater";
private const string s_childHandlersPropertyName = "ChildHandlers";
}
}
Loading

0 comments on commit e48de38

Please sign in to comment.