diff --git a/docs/guidelines/build.md b/docs/guidelines/build.md index f865224ee2..682c50399f 100644 --- a/docs/guidelines/build.md +++ b/docs/guidelines/build.md @@ -6,12 +6,7 @@ Avalonia requires Visual Studio 2015 to build on Windows. ### Install GTK Sharp -To compile the full project under windows, you must have [gtk-sharp](http://www.mono-project.com/download/#download-win) installed. However, if you're -not interested in building the cross-platform bits you can simply unload these projects from Visual Studio: - - - Avalonia.Cairo - - Avalonia.Cairo.RenderTests - - Avalonia.Gtk +For the moment under windows, you must have [gtk-sharp](http://www.mono-project.com/download/#download-win) installed. Note that after installing the package your machine may require a restart before GTK# is added to your path. We hope to remove or make this dependency optional at some point in the future. ### Clone the Avalonia repository diff --git a/docs/images/screen.png b/docs/images/screen.png index a321a8d6e3..3214a44267 100644 Binary files a/docs/images/screen.png and b/docs/images/screen.png differ diff --git a/samples/XamlTestApplicationPcl/Views/MainWindow.xaml b/samples/XamlTestApplicationPcl/Views/MainWindow.xaml index 95c9874a8b..0c83b87894 100644 --- a/samples/XamlTestApplicationPcl/Views/MainWindow.xaml +++ b/samples/XamlTestApplicationPcl/Views/MainWindow.xaml @@ -154,14 +154,14 @@ - + - + @@ -179,14 +179,14 @@ - + - + diff --git a/src/Avalonia.Base/Collections/AvaloniaList.cs b/src/Avalonia.Base/Collections/AvaloniaList.cs index b064032e1c..e893fcb75b 100644 --- a/src/Avalonia.Base/Collections/AvaloniaList.cs +++ b/src/Avalonia.Base/Collections/AvaloniaList.cs @@ -150,16 +150,20 @@ namespace Avalonia.Collections Validate?.Invoke(value); T old = _inner[index]; - _inner[index] = value; - if (_collectionChanged != null) + if (!object.Equals(old, value)) { - var e = new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Replace, - value, - old, - index); - _collectionChanged(this, e); + _inner[index] = value; + + if (_collectionChanged != null) + { + var e = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Replace, + value, + old, + index); + _collectionChanged(this, e); + } } } } diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 056fe9e93a..9b5f8a9c1b 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -167,7 +167,6 @@ - diff --git a/src/Avalonia.Controls/ContentControl.cs b/src/Avalonia.Controls/ContentControl.cs index 822d9da11a..0eba3c13ae 100644 --- a/src/Avalonia.Controls/ContentControl.cs +++ b/src/Avalonia.Controls/ContentControl.cs @@ -21,6 +21,12 @@ namespace Avalonia.Controls public static readonly StyledProperty ContentProperty = AvaloniaProperty.Register(nameof(Content)); + /// + /// Defines the property. + /// + public static readonly StyledProperty ContentTemplateProperty = + AvaloniaProperty.Register(nameof(ContentTemplate)); + /// /// Defines the property. /// @@ -51,6 +57,15 @@ namespace Avalonia.Controls set { SetValue(ContentProperty, value); } } + /// + /// Gets or sets the data template used to display the content of the control. + /// + public IDataTemplate ContentTemplate + { + get { return GetValue(ContentTemplateProperty); } + set { SetValue(ContentTemplateProperty, value); } + } + /// /// Gets the presenter from the control's template. /// diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index 6dabe9f8e3..da5dd5daaf 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -68,7 +68,10 @@ namespace Avalonia.Controls /// protected override IItemContainerGenerator CreateItemContainerGenerator() { - return new ItemContainerGenerator(this, DropDownItem.ContentProperty); + return new ItemContainerGenerator( + this, + DropDownItem.ContentProperty, + DropDownItem.ContentTemplateProperty); } /// diff --git a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs index af7901d05d..8199333f47 100644 --- a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs @@ -18,6 +18,11 @@ namespace Avalonia.Controls.Generators /// IEnumerable Containers { get; } + /// + /// Gets or sets the data template used to display the items in the control. + /// + IDataTemplate ItemTemplate { get; set; } + /// /// Signalled whenever new containers are materialized. /// diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs index d3032d7c90..114d31c5a6 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs @@ -37,6 +37,11 @@ namespace Avalonia.Controls.Generators /// public event EventHandler Dematerialized; + /// + /// Gets or sets the data template used to display the items in the control. + /// + public IDataTemplate ItemTemplate { get; set; } + /// /// Gets the owner control. /// @@ -156,7 +161,7 @@ namespace Avalonia.Controls.Generators /// The created container control. protected virtual IControl CreateContainer(object item) { - var result = Owner.MaterializeDataTemplate(item); + var result = Owner.MaterializeDataTemplate(item, ItemTemplate); if (result != null && !(item is IControl)) { diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs index c1521124f5..c4bc730e15 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs @@ -19,15 +19,18 @@ namespace Avalonia.Controls.Generators /// /// The owner control. /// The container's Content property. + /// The container's ContentTemplate property. public ItemContainerGenerator( IControl owner, - AvaloniaProperty contentProperty) + AvaloniaProperty contentProperty, + AvaloniaProperty contentTemplateProperty) : base(owner) { Contract.Requires(owner != null); Contract.Requires(contentProperty != null); ContentProperty = contentProperty; + ContentTemplateProperty = contentTemplateProperty; } /// @@ -35,6 +38,11 @@ namespace Avalonia.Controls.Generators /// protected AvaloniaProperty ContentProperty { get; } + /// + /// Gets the container's ContentTemplate property. + /// + protected AvaloniaProperty ContentTemplateProperty { get; } + /// protected override IControl CreateContainer(object item) { @@ -51,6 +59,12 @@ namespace Avalonia.Controls.Generators else { var result = new T(); + + if (ContentTemplateProperty != null) + { + result.SetValue(ContentTemplateProperty, ItemTemplate); + } + result.SetValue(ContentProperty, item); if (!(item is IControl)) diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs index 307815a00e..681bea865a 100644 --- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs @@ -20,16 +20,18 @@ namespace Avalonia.Controls.Generators /// /// The owner control. /// The container's Content property. + /// The container's ContentTemplate property. /// The container's Items property. /// The container's IsExpanded property. /// The container index for the tree public TreeItemContainerGenerator( IControl owner, AvaloniaProperty contentProperty, + AvaloniaProperty contentTemplateProperty, AvaloniaProperty itemsProperty, AvaloniaProperty isExpandedProperty, TreeContainerIndex index) - : base(owner, contentProperty) + : base(owner, contentProperty, contentTemplateProperty) { Contract.Requires(owner != null); Contract.Requires(contentProperty != null); @@ -73,7 +75,7 @@ namespace Avalonia.Controls.Generators } else { - var template = GetTreeDataTemplate(item); + var template = GetTreeDataTemplate(item, ItemTemplate); var result = new T(); result.SetValue(ContentProperty, template.Build(item)); @@ -121,9 +123,9 @@ namespace Avalonia.Controls.Generators /// /// The item. /// The template. - private ITreeDataTemplate GetTreeDataTemplate(object item) + private ITreeDataTemplate GetTreeDataTemplate(object item, IDataTemplate primary) { - var template = Owner.FindDataTemplate(item) ?? FuncDataTemplate.Default; + var template = Owner.FindDataTemplate(item, primary) ?? FuncDataTemplate.Default; var treeTemplate = template as ITreeDataTemplate ?? new FuncTreeDataTemplate(typeof(object), template.Build, x => null); return treeTemplate; diff --git a/src/Avalonia.Controls/IContentControl.cs b/src/Avalonia.Controls/IContentControl.cs index d09f5bd9c6..863c132778 100644 --- a/src/Avalonia.Controls/IContentControl.cs +++ b/src/Avalonia.Controls/IContentControl.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Controls.Templates; using Avalonia.Layout; namespace Avalonia.Controls @@ -16,6 +17,11 @@ namespace Avalonia.Controls /// object Content { get; set; } + /// + /// Gets or sets the data template used to display the content of the control. + /// + IDataTemplate ContentTemplate { get; set; } + /// /// Gets or sets the horizontal alignment of the content within the control. /// diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 576437fe0a..3a77f61f1b 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -41,6 +41,12 @@ namespace Avalonia.Controls public static readonly StyledProperty> ItemsPanelProperty = AvaloniaProperty.Register>(nameof(ItemsPanel), DefaultPanel); + /// + /// Defines the property. + /// + public static readonly StyledProperty ItemTemplateProperty = + AvaloniaProperty.Register(nameof(ItemTemplate)); + /// /// Defines the property. /// @@ -65,6 +71,7 @@ namespace Avalonia.Controls { PseudoClasses.Add(":empty"); SubscribeToItems(_items); + ItemTemplateProperty.Changed.AddClassHandler(x => x.ItemTemplateChanged); } /// @@ -80,6 +87,7 @@ namespace Avalonia.Controls if (_itemContainerGenerator != null) { + _itemContainerGenerator.ItemTemplate = ItemTemplate; _itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e); _itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e); } @@ -108,6 +116,15 @@ namespace Avalonia.Controls set { SetValue(ItemsPanelProperty, value); } } + /// + /// Gets or sets the data template used to display the items in the control. + /// + public IDataTemplate ItemTemplate + { + get { return GetValue(ItemTemplateProperty); } + set { SetValue(ItemTemplateProperty, value); } + } + /// /// Selects a member from to use as the list item. /// @@ -354,7 +371,7 @@ namespace Avalonia.Controls /// /// Subscribes to an collection. /// - /// + /// The items collection. private void SubscribeToItems(IEnumerable items) { PseudoClasses.Set(":empty", items == null || items.Count() == 0); @@ -366,5 +383,18 @@ namespace Avalonia.Controls incc.CollectionChanged += ItemsCollectionChanged; } } + + /// + /// Called when the changes. + /// + /// The event args. + private void ItemTemplateChanged(AvaloniaPropertyChangedEventArgs e) + { + if (_itemContainerGenerator != null) + { + _itemContainerGenerator.ItemTemplate = (IDataTemplate)e.NewValue; + // TODO: Rebuild the item containers. + } + } } } diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index 06f6e23934..0f32bc91da 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -41,7 +41,10 @@ namespace Avalonia.Controls /// protected override IItemContainerGenerator CreateItemContainerGenerator() { - return new ItemContainerGenerator(this, ListBoxItem.ContentProperty); + return new ItemContainerGenerator( + this, + ListBoxItem.ContentProperty, + ListBoxItem.ContentTemplateProperty); } /// diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 7f51229061..3470a43c1c 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -48,6 +48,12 @@ namespace Avalonia.Controls.Presenters public static readonly StyledProperty ContentProperty = ContentControl.ContentProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty ContentTemplateProperty = + ContentControl.ContentTemplateProperty.AddOwner(); + /// /// Defines the property. /// @@ -140,6 +146,15 @@ namespace Avalonia.Controls.Presenters set { SetValue(ContentProperty, value); } } + /// + /// Gets or sets the data template used to display the content of the control. + /// + public IDataTemplate ContentTemplate + { + get { return GetValue(ContentTemplateProperty); } + set { SetValue(ContentTemplateProperty, value); } + } + /// /// Gets or sets the radius of the border rounded corners. /// @@ -200,7 +215,7 @@ namespace Avalonia.Controls.Presenters { var old = Child; var content = Content; - var result = this.MaterializeDataTemplate(content); + var result = this.MaterializeDataTemplate(content, ContentTemplate); if (old != null) { diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs index a0db90da6c..91ffec9c4c 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs @@ -27,6 +27,12 @@ namespace Avalonia.Controls.Presenters public static readonly StyledProperty> ItemsPanelProperty = ItemsControl.ItemsPanelProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty ItemTemplateProperty = + ItemsControl.ItemTemplateProperty.AddOwner(); + /// /// Defines the property. /// @@ -122,6 +128,15 @@ namespace Avalonia.Controls.Presenters set { SetValue(ItemsPanelProperty, value); } } + /// + /// Gets or sets the data template used to display the items in the control. + /// + public IDataTemplate ItemTemplate + { + get { return GetValue(ItemTemplateProperty); } + set { SetValue(ItemTemplateProperty, value); } + } + /// /// Selects a member from to use as the list item. /// diff --git a/src/Avalonia.Controls/Primitives/TabStrip.cs b/src/Avalonia.Controls/Primitives/TabStrip.cs index 4850b7f8a3..4a6c9af95f 100644 --- a/src/Avalonia.Controls/Primitives/TabStrip.cs +++ b/src/Avalonia.Controls/Primitives/TabStrip.cs @@ -20,7 +20,10 @@ namespace Avalonia.Controls.Primitives protected override IItemContainerGenerator CreateItemContainerGenerator() { - return new ItemContainerGenerator(this, ContentControl.ContentProperty); + return new ItemContainerGenerator( + this, + ContentControl.ContentProperty, + ContentControl.ContentTemplateProperty); } /// diff --git a/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs b/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs index 782386df6a..9eff4243b1 100644 --- a/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs +++ b/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs @@ -16,8 +16,15 @@ namespace Avalonia.Controls.Templates /// /// The control materializing the data template. /// The data. + /// + /// An optional primary template that can will be tried before the + /// in the tree are searched. + /// /// The data materialized as a control. - public static IControl MaterializeDataTemplate(this IControl control, object data) + public static IControl MaterializeDataTemplate( + this IControl control, + object data, + IDataTemplate primary = null) { if (data == null) { @@ -33,7 +40,7 @@ namespace Avalonia.Controls.Templates } else { - IDataTemplate template = control.FindDataTemplate(data); + IDataTemplate template = control.FindDataTemplate(data, primary); IControl result; if (template != null) @@ -57,9 +64,21 @@ namespace Avalonia.Controls.Templates /// /// The control searching for the data template. /// The data. + /// + /// An optional primary template that can will be tried before the + /// in the tree are searched. + /// /// The data template or null if no matching data template was found. - public static IDataTemplate FindDataTemplate(this IControl control, object data) + public static IDataTemplate FindDataTemplate( + this IControl control, + object data, + IDataTemplate primary = null) { + if (primary?.Match(data) == true) + { + return primary; + } + foreach (var i in control.GetSelfAndLogicalAncestors().OfType()) { foreach (IDataTemplate dt in i.DataTemplates) diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 05ab49ed79..028df729a5 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -99,6 +99,7 @@ namespace Avalonia.Controls /// static TextBlock() { + ClipToBoundsProperty.OverrideDefaultValue(true); AffectsRender(ForegroundProperty); AffectsRender(FontWeightProperty); AffectsRender(FontSizeProperty); @@ -355,7 +356,8 @@ namespace Avalonia.Controls FontSize, FontStyle, TextAlignment, - FontWeight); + FontWeight, + TextWrapping); result.Constraint = constraint; return result; } diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 1c0bd964b1..b966d09b1f 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -96,6 +96,7 @@ namespace Avalonia.Controls var result = new TreeItemContainerGenerator( this, TreeViewItem.HeaderProperty, + TreeViewItem.ItemTemplateProperty, TreeViewItem.ItemsProperty, TreeViewItem.IsExpandedProperty, new TreeContainerIndex()); diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index fb3917b6d9..bed27ef033 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -80,6 +80,7 @@ namespace Avalonia.Controls return new TreeItemContainerGenerator( this, TreeViewItem.HeaderProperty, + TreeViewItem.ItemTemplateProperty, TreeViewItem.ItemsProperty, TreeViewItem.IsExpandedProperty, _treeView?.ItemContainerGenerator.Index ?? new TreeContainerIndex()); @@ -90,6 +91,11 @@ namespace Avalonia.Controls { base.OnAttachedToLogicalTree(e); _treeView = this.GetLogicalAncestors().OfType().FirstOrDefault(); + + if (ItemTemplate == null && _treeView?.ItemTemplate != null) + { + ItemTemplate = _treeView.ItemTemplate; + } } protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj index 9b720ffbdb..84312f1927 100644 --- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj +++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj @@ -1,4 +1,4 @@ - + @@ -105,6 +105,7 @@ + TreePageView.xaml diff --git a/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs b/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs index 8e24f68b26..7b01e28cf4 100644 --- a/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs +++ b/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Diagnostics.ViewModels; +using Avalonia.Media; using Avalonia.Styling; using ReactiveUI; @@ -35,14 +36,8 @@ namespace Avalonia.Diagnostics.Views Content = new ScrollViewer { - Content = new Grid + Content = new SimpleGrid { - ColumnDefinitions = new ColumnDefinitions - { - new ColumnDefinition(GridLength.Auto), - new ColumnDefinition(GridLength.Auto), - new ColumnDefinition(GridLength.Auto), - }, Styles = new Styles { new Style(x => x.Is()) @@ -66,16 +61,19 @@ namespace Avalonia.Diagnostics.Views yield return new TextBlock { Text = property.Name, + TextWrapping = TextWrapping.NoWrap, [!ToolTip.TipProperty] = property.WhenAnyValue(x => x.Diagnostic), }; yield return new TextBlock { + TextWrapping = TextWrapping.NoWrap, [!TextBlock.TextProperty] = property.WhenAnyValue(v => v.Value).Select(v => v?.ToString()), }; yield return new TextBlock { + TextWrapping = TextWrapping.NoWrap, [!TextBlock.TextProperty] = property.WhenAnyValue(x => x.Priority), }; } diff --git a/src/Avalonia.Diagnostics/Views/GridRepeater.cs b/src/Avalonia.Diagnostics/Views/GridRepeater.cs index 4d05a71e60..ff12fde25b 100644 --- a/src/Avalonia.Diagnostics/Views/GridRepeater.cs +++ b/src/Avalonia.Diagnostics/Views/GridRepeater.cs @@ -11,10 +11,10 @@ namespace Avalonia.Diagnostics.Views internal static class GridRepeater { public static readonly AttachedProperty ItemsProperty = - AvaloniaProperty.RegisterAttached("Items", typeof(GridRepeater)); + AvaloniaProperty.RegisterAttached("Items", typeof(GridRepeater)); public static readonly AttachedProperty>> TemplateProperty = - AvaloniaProperty.RegisterAttached>>("Template", typeof(GridRepeater)); + AvaloniaProperty.RegisterAttached>>("Template", typeof(GridRepeater)); static GridRepeater() { @@ -23,7 +23,7 @@ namespace Avalonia.Diagnostics.Views private static void ItemsChanged(AvaloniaPropertyChangedEventArgs e) { - var grid = (Grid)e.Sender; + var grid = (SimpleGrid)e.Sender; var items = (IEnumerable)e.NewValue; var template = grid.GetValue(TemplateProperty); @@ -32,36 +32,18 @@ namespace Avalonia.Diagnostics.Views if (items != null) { int count = 0; - int cols = grid.ColumnDefinitions.Count; + int cols = 3; foreach (var item in items) { foreach (var control in template(item)) { grid.Children.Add(control); - Grid.SetColumn(control, count % cols); - Grid.SetRow(control, count / cols); + SimpleGrid.SetColumn(control, count % cols); + SimpleGrid.SetRow(control, count / cols); ++count; } } - - int rows = (int)Math.Ceiling((double)count / cols); - int difference = rows - grid.RowDefinitions.Count; - - if (difference > 0) - { - for (int i = 0; i < difference; ++i) - { - grid.RowDefinitions.Add(new RowDefinition(GridLength.Auto)); - } - } - else if (difference < 0) - { - for (int i = 0; i < difference; ++i) - { - grid.RowDefinitions.RemoveAt(grid.RowDefinitions.Count - 1); - } - } } } } diff --git a/src/Avalonia.Diagnostics/Views/SimpleGrid.cs b/src/Avalonia.Diagnostics/Views/SimpleGrid.cs new file mode 100644 index 0000000000..fb5b7821f3 --- /dev/null +++ b/src/Avalonia.Diagnostics/Views/SimpleGrid.cs @@ -0,0 +1,147 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System.Collections.Generic; +using System.Linq; +using Avalonia.Controls; + +namespace Avalonia.Diagnostics.Views +{ + /// + /// A simple grid control that lays out columns with a equal width and rows to their desired + /// size. + /// + /// + /// This is used in the devtools because our performance sucks. + /// + public class SimpleGrid : Panel + { + private List _columnWidths = new List(); + private List _rowHeights = new List(); + private double _totalWidth; + private double _totalHeight; + + /// + /// Defines the Column attached property. + /// + public static readonly AttachedProperty ColumnProperty = + AvaloniaProperty.RegisterAttached("Column"); + + /// + /// Defines the Row attached property. + /// + public static readonly AttachedProperty RowProperty = + AvaloniaProperty.RegisterAttached("Row"); + + /// + /// Gets the value of the Column attached property for a control. + /// + /// The control. + /// The control's column. + public static int GetColumn(IControl control) + { + return control.GetValue(ColumnProperty); + } + + /// + /// Gets the value of the Row attached property for a control. + /// + /// The control. + /// The control's row. + public static int GetRow(IControl control) + { + return control.GetValue(RowProperty); + } + + /// + /// Sets the value of the Column attached property for a control. + /// + /// The control. + /// The column value. + public static void SetColumn(IControl control, int value) + { + control.SetValue(ColumnProperty, value); + } + + + /// + /// Sets the value of the Row attached property for a control. + /// + /// The control. + /// The row value. + public static void SetRow(IControl control, int value) + { + control.SetValue(RowProperty, value); + } + + protected override Size MeasureOverride(Size availableSize) + { + _columnWidths.Clear(); + _rowHeights.Clear(); + _totalWidth = 0; + _totalHeight = 0; + + foreach (var child in Children) + { + var column = GetColumn(child); + var row = GetRow(child); + + child.Measure(availableSize); + + var desired = child.DesiredSize; + UpdateCell(_columnWidths, column, desired.Width, ref _totalWidth); + UpdateCell(_rowHeights, row, desired.Height, ref _totalHeight); + } + + return new Size(_totalWidth, _totalHeight); + } + + protected override Size ArrangeOverride(Size finalSize) + { + var columnWidth = finalSize.Width / _columnWidths.Count; + + foreach (var child in Children) + { + var column = GetColumn(child); + var row = GetRow(child); + var rect = new Rect(column * columnWidth, GetRowTop(row), columnWidth, _rowHeights[row]); + child.Arrange(rect); + } + + return new Size(finalSize.Width, _totalHeight); + } + + private double UpdateCell(IList cells, int cell, double value, ref double total) + { + while (cells.Count < cell + 1) + { + cells.Add(0); + } + + var existing = cells[cell]; + + if (value > existing) + { + cells[cell] = value; + total += value - existing; + return value; + } + else + { + return existing; + } + } + + private double GetRowTop(int row) + { + var result = 0.0; + + for (var i = 0; i < row; ++i) + { + result += _rowHeights[i]; + } + + return result; + } + } +} diff --git a/src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj b/src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj index 681f53f8fc..151ad0997e 100644 --- a/src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj +++ b/src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj @@ -1,4 +1,4 @@ - + @@ -68,6 +68,7 @@ + diff --git a/src/Avalonia.SceneGraph/Media/FormattedText.cs b/src/Avalonia.SceneGraph/Media/FormattedText.cs index af065d2f86..1169a56343 100644 --- a/src/Avalonia.SceneGraph/Media/FormattedText.cs +++ b/src/Avalonia.SceneGraph/Media/FormattedText.cs @@ -21,13 +21,15 @@ namespace Avalonia.Media /// The font style. /// The text alignment. /// The font weight. + /// The text wrapping mode. public FormattedText( string text, string fontFamilyName, double fontSize, - FontStyle fontStyle, - TextAlignment textAlignment, - FontWeight fontWeight) + FontStyle fontStyle = FontStyle.Normal, + TextAlignment textAlignment = TextAlignment.Left, + FontWeight fontWeight = FontWeight.Normal, + TextWrapping wrapping = TextWrapping.Wrap) { Contract.Requires(text != null); Contract.Requires(fontFamilyName != null); @@ -39,6 +41,7 @@ namespace Avalonia.Media FontStyle = fontStyle; FontWeight = fontWeight; TextAlignment = textAlignment; + Wrapping = wrapping; var platform = AvaloniaLocator.Current.GetService(); @@ -53,7 +56,8 @@ namespace Avalonia.Media fontSize, fontStyle, textAlignment, - fontWeight); + fontWeight, + wrapping); } /// @@ -76,63 +80,42 @@ namespace Avalonia.Media /// /// Gets the font family. /// - public string FontFamilyName - { - get; - private set; - } + public string FontFamilyName { get; } /// /// Gets the font size. /// - public double FontSize - { - get; - private set; - } + public double FontSize { get; } /// /// Gets the font style. /// - public FontStyle FontStyle - { - get; - private set; - } + public FontStyle FontStyle { get; } /// /// Gets the font weight. /// - public FontWeight FontWeight - { - get; - private set; - } + public FontWeight FontWeight { get; } /// /// Gets the text. /// - public string Text - { - get; - private set; - } + public string Text { get; } /// /// Gets platform-specific platform implementation. /// - public IFormattedTextImpl PlatformImpl - { - get; } + public IFormattedTextImpl PlatformImpl { get; } /// /// Gets the text alignment. /// - public TextAlignment TextAlignment - { - get; - private set; - } + public TextAlignment TextAlignment { get; } + + /// + /// Gets the text wrapping. + /// + public TextWrapping Wrapping { get; } /// /// Disposes of unmanaged resources associated with the formatted text. diff --git a/src/Avalonia.Controls/TextWrapping.cs b/src/Avalonia.SceneGraph/Media/TextWrapping.cs similarity index 81% rename from src/Avalonia.Controls/TextWrapping.cs rename to src/Avalonia.SceneGraph/Media/TextWrapping.cs index e1047414a9..7d50e10799 100644 --- a/src/Avalonia.Controls/TextWrapping.cs +++ b/src/Avalonia.SceneGraph/Media/TextWrapping.cs @@ -1,10 +1,10 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -namespace Avalonia.Controls +namespace Avalonia.Media { /// - /// Controls the wrapping mode in a . + /// Controls the wrapping mode of text. /// public enum TextWrapping { diff --git a/src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs b/src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs index 4d40957a60..6beb787aab 100644 --- a/src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs @@ -20,6 +20,7 @@ namespace Avalonia.Platform /// The font style. /// The text alignment. /// The font weight. + /// The text wrapping mode. /// An . IFormattedTextImpl CreateFormattedText( string text, @@ -27,7 +28,8 @@ namespace Avalonia.Platform double fontSize, FontStyle fontStyle, TextAlignment textAlignment, - FontWeight fontWeight); + FontWeight fontWeight, + TextWrapping wrapping); /// /// Creates a stream geometry implementation. diff --git a/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs b/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs index cd18b0f152..3157feb369 100644 --- a/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs +++ b/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs @@ -62,7 +62,8 @@ namespace Avalonia.Rendering var txt = new FormattedText("Frame #" + s_frameNum + " FPS: " + s_fps, "Arial", 18, FontStyle.Normal, TextAlignment.Left, - FontWeight.Normal)) + FontWeight.Normal, + TextWrapping.NoWrap)) { ctx.FillRectangle(Brushes.White, new Rect(pt, txt.Measure())); ctx.DrawText(Brushes.Black, pt, txt); diff --git a/src/Avalonia.SceneGraph/Visual.cs b/src/Avalonia.SceneGraph/Visual.cs index 6ae5d80677..9cf97c2767 100644 --- a/src/Avalonia.SceneGraph/Visual.cs +++ b/src/Avalonia.SceneGraph/Visual.cs @@ -94,7 +94,7 @@ namespace Avalonia { var visualChildren = new AvaloniaList(); visualChildren.ResetBehavior = ResetBehavior.Remove; - visualChildren.Validate = ValidateLogicalChild; + visualChildren.Validate = ValidateVisualChild; visualChildren.CollectionChanged += VisualChildrenChanged; VisualChildren = visualChildren; } @@ -414,15 +414,20 @@ namespace Avalonia } /// - /// Ensures a visual child is not null. + /// Ensures a visual child is not null and not already parented. /// /// The visual child. - private static void ValidateLogicalChild(IVisual c) + private static void ValidateVisualChild(IVisual c) { if (c == null) { throw new ArgumentNullException("Cannot add null to VisualChildren."); } + + if (c.VisualParent != null) + { + throw new InvalidOperationException("The control already has a visual parent."); + } } /// diff --git a/src/Avalonia.Themes.Default/Button.xaml b/src/Avalonia.Themes.Default/Button.xaml index bf8072d588..daa2973b21 100644 --- a/src/Avalonia.Themes.Default/Button.xaml +++ b/src/Avalonia.Themes.Default/Button.xaml @@ -14,6 +14,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}" TextBlock.Foreground="{TemplateBinding Foreground}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" diff --git a/src/Avalonia.Themes.Default/CheckBox.xaml b/src/Avalonia.Themes.Default/CheckBox.xaml index b1596e6b74..e88e93eac6 100644 --- a/src/Avalonia.Themes.Default/CheckBox.xaml +++ b/src/Avalonia.Themes.Default/CheckBox.xaml @@ -22,6 +22,7 @@ diff --git a/src/Avalonia.Themes.Default/ContentControl.xaml b/src/Avalonia.Themes.Default/ContentControl.xaml index 1af449a447..27f6648548 100644 --- a/src/Avalonia.Themes.Default/ContentControl.xaml +++ b/src/Avalonia.Themes.Default/ContentControl.xaml @@ -5,7 +5,8 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" - Content="{TemplateBinding Content}" + Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}"/> diff --git a/src/Avalonia.Themes.Default/ContextMenu.xaml b/src/Avalonia.Themes.Default/ContextMenu.xaml index 88454036dc..41d53aa6b3 100644 --- a/src/Avalonia.Themes.Default/ContextMenu.xaml +++ b/src/Avalonia.Themes.Default/ContextMenu.xaml @@ -13,6 +13,7 @@ diff --git a/src/Avalonia.Themes.Default/DropDown.xaml b/src/Avalonia.Themes.Default/DropDown.xaml index 20c497bfd4..5b8649457e 100644 --- a/src/Avalonia.Themes.Default/DropDown.xaml +++ b/src/Avalonia.Themes.Default/DropDown.xaml @@ -38,6 +38,7 @@ BorderThickness="1"> diff --git a/src/Avalonia.Themes.Default/DropDownItem.xaml b/src/Avalonia.Themes.Default/DropDownItem.xaml index f4e7e3a5b5..98e51a674f 100644 --- a/src/Avalonia.Themes.Default/DropDownItem.xaml +++ b/src/Avalonia.Themes.Default/DropDownItem.xaml @@ -10,6 +10,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" Padding="{TemplateBinding Padding}"/> diff --git a/src/Avalonia.Themes.Default/Expander.xaml b/src/Avalonia.Themes.Default/Expander.xaml index 4123d1a9df..9bf9958ecc 100644 --- a/src/Avalonia.Themes.Default/Expander.xaml +++ b/src/Avalonia.Themes.Default/Expander.xaml @@ -17,6 +17,7 @@ Grid.Row="1" IsVisible="{TemplateBinding IsExpanded}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" /> @@ -34,6 +35,7 @@ Grid.Row="0" IsVisible="{TemplateBinding IsExpanded}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" /> @@ -51,6 +53,7 @@ Grid.Column="1" IsVisible="{TemplateBinding IsExpanded}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" /> @@ -68,6 +71,7 @@ Grid.Column="0" IsVisible="{TemplateBinding IsExpanded}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" /> diff --git a/src/Avalonia.Themes.Default/ItemsControl.xaml b/src/Avalonia.Themes.Default/ItemsControl.xaml index aa56a21e82..7b6671b42c 100644 --- a/src/Avalonia.Themes.Default/ItemsControl.xaml +++ b/src/Avalonia.Themes.Default/ItemsControl.xaml @@ -4,6 +4,7 @@ diff --git a/src/Avalonia.Themes.Default/LayoutTransformControl.xaml b/src/Avalonia.Themes.Default/LayoutTransformControl.xaml index b1ebb2a819..c9df62625f 100644 --- a/src/Avalonia.Themes.Default/LayoutTransformControl.xaml +++ b/src/Avalonia.Themes.Default/LayoutTransformControl.xaml @@ -6,6 +6,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}"/> diff --git a/src/Avalonia.Themes.Default/ListBox.xaml b/src/Avalonia.Themes.Default/ListBox.xaml index ad410c1b5f..782cc36107 100644 --- a/src/Avalonia.Themes.Default/ListBox.xaml +++ b/src/Avalonia.Themes.Default/ListBox.xaml @@ -11,6 +11,7 @@ diff --git a/src/Avalonia.Themes.Default/ListBoxItem.xaml b/src/Avalonia.Themes.Default/ListBoxItem.xaml index af384ae83b..61466abd1b 100644 --- a/src/Avalonia.Themes.Default/ListBoxItem.xaml +++ b/src/Avalonia.Themes.Default/ListBoxItem.xaml @@ -7,6 +7,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}"/> diff --git a/src/Avalonia.Themes.Default/Menu.xaml b/src/Avalonia.Themes.Default/Menu.xaml index 397f247a4f..a1a5afb4a9 100644 --- a/src/Avalonia.Themes.Default/Menu.xaml +++ b/src/Avalonia.Themes.Default/Menu.xaml @@ -8,6 +8,7 @@ diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml index bb7c6337a6..a7af6c51cf 100644 --- a/src/Avalonia.Themes.Default/MenuItem.xaml +++ b/src/Avalonia.Themes.Default/MenuItem.xaml @@ -53,6 +53,7 @@ diff --git a/src/Avalonia.Themes.Default/RadioButton.xaml b/src/Avalonia.Themes.Default/RadioButton.xaml index 25a2510078..182c710b7c 100644 --- a/src/Avalonia.Themes.Default/RadioButton.xaml +++ b/src/Avalonia.Themes.Default/RadioButton.xaml @@ -21,6 +21,7 @@ VerticalAlignment="Center"/> diff --git a/src/Avalonia.Themes.Default/TabStrip.xaml b/src/Avalonia.Themes.Default/TabStrip.xaml index df52d7afbe..c42b62ee1f 100644 --- a/src/Avalonia.Themes.Default/TabStrip.xaml +++ b/src/Avalonia.Themes.Default/TabStrip.xaml @@ -5,7 +5,8 @@ + ItemsPanel="{TemplateBinding ItemsPanel}" + ItemTemplate="{TemplateBinding ItemTemplate}"/> diff --git a/src/Avalonia.Themes.Default/TabStripItem.xaml b/src/Avalonia.Themes.Default/TabStripItem.xaml index 8a33c7f8c1..35848bda59 100644 --- a/src/Avalonia.Themes.Default/TabStripItem.xaml +++ b/src/Avalonia.Themes.Default/TabStripItem.xaml @@ -9,6 +9,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}"/> diff --git a/src/Avalonia.Themes.Default/ToggleButton.xaml b/src/Avalonia.Themes.Default/ToggleButton.xaml index 32b9b4b991..0c8a3fa425 100644 --- a/src/Avalonia.Themes.Default/ToggleButton.xaml +++ b/src/Avalonia.Themes.Default/ToggleButton.xaml @@ -14,6 +14,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}" TextBlock.Foreground="{TemplateBinding Foreground}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" diff --git a/src/Avalonia.Themes.Default/ToolTip.xaml b/src/Avalonia.Themes.Default/ToolTip.xaml index 97a326f033..46e297fbc2 100644 --- a/src/Avalonia.Themes.Default/ToolTip.xaml +++ b/src/Avalonia.Themes.Default/ToolTip.xaml @@ -10,6 +10,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}"/> diff --git a/src/Avalonia.Themes.Default/Window.xaml b/src/Avalonia.Themes.Default/Window.xaml index 9953cba32a..49cd4ee747 100644 --- a/src/Avalonia.Themes.Default/Window.xaml +++ b/src/Avalonia.Themes.Default/Window.xaml @@ -6,7 +6,10 @@ - + diff --git a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs index 8092764c1b..5b85e6c68f 100644 --- a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs +++ b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs @@ -44,7 +44,8 @@ namespace Avalonia.Cairo double fontSize, FontStyle fontStyle, TextAlignment textAlignment, - Avalonia.Media.FontWeight fontWeight) + Avalonia.Media.FontWeight fontWeight, + TextWrapping wrapping) { return new FormattedTextImpl(s_pangoContext, text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight); } diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs index 6836dbf9df..a901c175c9 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs @@ -1,20 +1,66 @@ -// Copyright (c) The Avalonia Project. All rights reserved. +// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System.Reflection; using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.Markup.Data; +using System; namespace Avalonia.Markup.Xaml.Templates { public class MemberSelector : IMemberSelector { - public string MemberName { get; set; } + private ExpressionNode _expressionNode; + private string _memberName; + private ExpressionNode _memberValueNode; + + public string MemberName + { + get { return _memberName; } + set + { + if (_memberName != value) + { + _memberName = value; + _expressionNode = null; + _memberValueNode = null; + } + } + } public object Select(object o) { - // TODO: Handle nested property paths, changing values etc. - var property = o.GetType().GetRuntimeProperty(MemberName); - return property?.GetValue(o); + if (string.IsNullOrEmpty(MemberName)) + { + return o; + } + + if (_expressionNode == null) + { + _expressionNode = ExpressionNodeBuilder.Build(MemberName); + + _memberValueNode = _expressionNode; + + while (_memberValueNode.Next != null) + { + _memberValueNode = _memberValueNode.Next; + } + } + + _expressionNode.Target = new WeakReference(o); + + object result = _memberValueNode.CurrentValue.Target; + + if (result == AvaloniaProperty.UnsetValue) + { + return null; + } + else if (result is BindingError) + { + return null; + } + + return result; } } -} +} \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs b/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs index 4d6ccdf31a..d74ccf00a1 100644 --- a/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs +++ b/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs @@ -7,4 +7,5 @@ using System.Runtime.CompilerServices; [assembly: AssemblyTitle("Avalonia.Markup")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup")] -[assembly: InternalsVisibleTo("Avalonia.Markup.UnitTests")] \ No newline at end of file +[assembly: InternalsVisibleTo("Avalonia.Markup.UnitTests")] +[assembly: InternalsVisibleTo("Avalonia.Markup.Xaml")] \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index da256f07b8..ec61aea552 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -18,7 +18,7 @@ namespace Avalonia.Skia } public IFormattedTextImpl CreateFormattedText(string text, string fontFamilyName, double fontSize, FontStyle fontStyle, - TextAlignment textAlignment, FontWeight fontWeight) + TextAlignment textAlignment, FontWeight fontWeight, TextWrapping wrapping) { return FormattedTextImpl.Create(text, fontFamilyName, fontSize, fontStyle, textAlignment, fontWeight); } diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index ae2b5d314d..ff0bebd359 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -49,9 +49,10 @@ namespace Avalonia.Direct2D1 double fontSize, FontStyle fontStyle, TextAlignment textAlignment, - FontWeight fontWeight) + FontWeight fontWeight, + TextWrapping wrapping) { - return new FormattedTextImpl(text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight); + return new FormattedTextImpl(text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight, wrapping); } public IRenderTarget CreateRenderer(IPlatformHandle handle) diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs index 5e77d4399c..45d06b5e27 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs @@ -18,7 +18,8 @@ namespace Avalonia.Direct2D1.Media double fontSize, FontStyle fontStyle, TextAlignment textAlignment, - FontWeight fontWeight) + FontWeight fontWeight, + TextWrapping wrapping) { var factory = AvaloniaLocator.Current.GetService(); @@ -29,6 +30,9 @@ namespace Avalonia.Direct2D1.Media (DWrite.FontStyle)fontStyle, (float)fontSize)) { + format.WordWrapping = wrapping == TextWrapping.Wrap ? + DWrite.WordWrapping.Wrap : DWrite.WordWrapping.NoWrap; + TextLayout = new DWrite.TextLayout( factory, text ?? string.Empty, diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 1e86c72b5b..483de0d000 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -90,8 +90,6 @@ namespace Avalonia.Win32 value *= Scaling; value += BorderThickness; - System.Diagnostics.Debug.WriteLine($"Set client size to {value}"); - UnmanagedMethods.SetWindowPos( _hwnd, IntPtr.Zero, diff --git a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs index 2ebdfc628c..5f86b9878d 100644 --- a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs @@ -116,6 +116,24 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new[] { child }, target.GetLogicalChildren()); } + [Fact] + public void Should_Use_ContentTemplate_To_Create_Control() + { + var target = new ContentControl + { + Template = GetTemplate(), + ContentTemplate = new FuncDataTemplate(_ => new Canvas()), + }; + + target.Content = "Foo"; + target.ApplyTemplate(); + ((ContentPresenter)target.Presenter).UpdateChild(); + + var child = target.Presenter.Child; + + Assert.IsType(child); + } + [Fact] public void DataTemplate_Created_Control_Should_Be_Logical_Child_After_ApplyTemplate() { @@ -266,6 +284,7 @@ namespace Avalonia.Controls.UnitTests { Name = "PART_ContentPresenter", [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty], + [~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty], } }; }); diff --git a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs b/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs index c5b0a36657..2522f1ef03 100644 --- a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls.UnitTests.Generators { var items = new[] { "foo", "bar", "baz" }; var owner = new Decorator(); - var target = new ItemContainerGenerator(owner, ListBoxItem.ContentProperty); + var target = new ItemContainerGenerator(owner, ListBoxItem.ContentProperty, null); var containers = target.Materialize(0, items, null); var result = containers .Select(x => x.ContainerControl) diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index 3ff4ee03fc..92c89039f5 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -14,6 +14,22 @@ namespace Avalonia.Controls.UnitTests { public class ItemsControlTests { + [Fact] + public void Should_Use_ItemTemplate_To_Create_Control() + { + var target = new ItemsControl + { + Template = GetTemplate(), + ItemTemplate = new FuncDataTemplate(_ => new Canvas()), + }; + + target.Items = new[] { "Foo" }; + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + + Assert.IsType(target.Presenter.Panel.Children[0]); + } + [Fact] public void Panel_Should_Have_TemplatedParent_Set_To_ItemsControl() { diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index 996423d3b7..d5fd59d1fc 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -14,6 +14,27 @@ namespace Avalonia.Controls.UnitTests { public class ListBoxTests { + [Fact] + public void Should_Use_ItemTemplate_To_Create_Item_Content() + { + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + ItemTemplate = new FuncDataTemplate(_ => new Canvas()), + }; + + target.Items = new[] { "Foo" }; + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + + var container = (ListBoxItem)target.Presenter.Panel.Children[0]; + container.Template = ListBoxItemTemplate(); + container.ApplyTemplate(); + ((ContentPresenter)container.Presenter).UpdateChild(); + + Assert.IsType(container.Presenter.Child); + } + [Fact] public void ListBox_Should_Find_ItemsPresenter_In_ScrollViewer() { @@ -123,6 +144,7 @@ namespace Avalonia.Controls.UnitTests { Name = "PART_ContentPresenter", [!ContentPresenter.ContentProperty] = parent[!ListBoxItem.ContentProperty], + [!ContentPresenter.ContentTemplateProperty] = parent[!ListBoxItem.ContentTemplateProperty], }); } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs index 3c5dbfbb21..33bbf83140 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs @@ -201,7 +201,7 @@ namespace Avalonia.Controls.UnitTests.Presenters { protected override IItemContainerGenerator CreateItemContainerGenerator() { - return new ItemContainerGenerator(this, TestItem.ContentProperty); + return new ItemContainerGenerator(this, TestItem.ContentProperty, null); } } } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs index 89336131ea..519b9b3d1d 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs @@ -54,7 +54,8 @@ namespace Avalonia.Controls.UnitTests.Presenters target.ItemContainerGenerator = new ItemContainerGenerator( target, - ListBoxItem.ContentProperty); + ListBoxItem.ContentProperty, + null); target.ApplyTemplate(); Assert.Equal(2, target.Panel.Children.Count); @@ -332,7 +333,7 @@ namespace Avalonia.Controls.UnitTests.Presenters { protected override IItemContainerGenerator CreateItemContainerGenerator() { - return new ItemContainerGenerator(this, TestItem.ContentProperty); + return new ItemContainerGenerator(this, TestItem.ContentProperty, null); } } } diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 5404218a4b..7b19a6a4e2 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -35,6 +35,33 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new[] { "Grandchild2a" }, ExtractItemHeader(target, 2)); } + [Fact] + public void Items_Should_Be_Created_Using_ItemTemplate_If_Present() + { + TreeView target; + + var root = new TestRoot + { + Child = target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = CreateTestTreeData(), + ItemTemplate = new FuncTreeDataTemplate( + _ => new Canvas(), + x => x.Children), + } + }; + + ApplyTemplates(target); + + var items = target.ItemContainerGenerator.Index.Items + .OfType() + .ToList(); + + Assert.Equal(4, items.Count); + Assert.All(items, x => Assert.IsType(x.HeaderPresenter.Child)); + } + [Fact] public void Root_ItemContainerGenerator_Containers_Should_Be_Root_Containers() { @@ -302,6 +329,7 @@ namespace Avalonia.Controls.UnitTests control.Template = CreateTreeViewItemTemplate(); control.ApplyTemplate(); control.Presenter.ApplyTemplate(); + control.HeaderPresenter.ApplyTemplate(); ApplyTemplates(control.Presenter.Panel.Children); } } @@ -354,10 +382,21 @@ namespace Avalonia.Controls.UnitTests private IControlTemplate CreateTreeViewItemTemplate() { - return new FuncControlTemplate(parent => new ItemsPresenter + return new FuncControlTemplate(parent => new Panel { - Name = "PART_ItemsPresenter", - [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], + Children = new Controls + { + new ContentPresenter + { + Name = "PART_HeaderPresenter", + [~ContentPresenter.ContentProperty] = parent[~TreeViewItem.HeaderProperty], + }, + new ItemsPresenter + { + Name = "PART_ItemsPresenter", + [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], + } + } }); } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index a2f2d3db56..7f8c790492 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -1,4 +1,4 @@ - + @@ -103,6 +103,7 @@ + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs new file mode 100644 index 0000000000..4903ca7b35 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs @@ -0,0 +1,168 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Markup.Xaml.Templates; +using System; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Templates +{ + public class MemberSelectorTests + { + [Fact] + public void Should_Not_Hold_Reference_To_Object() + { + WeakReference dataRef = null; + + var selector = new MemberSelector() { MemberName = "Child.StringValue" }; + + Action run = () => + { + var data = new Item() + { + Child = new Item() { StringValue = "Value1" } + }; + + Assert.Same("Value1", selector.Select(data)); + + dataRef = new WeakReference(data); + }; + + run(); + + GC.Collect(); + + Assert.False(dataRef.IsAlive); + } + + [Fact] + public void Should_Select_Child_Property_Value() + { + var selector = new MemberSelector() { MemberName = "Child.StringValue" }; + + var data = new Item() + { + Child = new Item() { StringValue = "Value1" } + }; + + Assert.Same("Value1", selector.Select(data)); + } + + [Fact] + public void Should_Select_Child_Property_Value_In_Multiple_Items() + { + var selector = new MemberSelector() { MemberName = "Child.StringValue" }; + + var data = new Item[] + { + new Item() { Child = new Item() { StringValue = "Value1" } }, + new Item() { Child = new Item() { StringValue = "Value2" } }, + new Item() { Child = new Item() { StringValue = "Value3" } } + }; + + Assert.Same("Value1", selector.Select(data[0])); + Assert.Same("Value2", selector.Select(data[1])); + Assert.Same("Value3", selector.Select(data[2])); + } + + [Fact] + public void Should_Select_MoreComplex_Property_Value() + { + var selector = new MemberSelector() { MemberName = "Child.Child.Child.StringValue" }; + + var data = new Item() + { + Child = new Item() + { + Child = new Item() + { + Child = new Item() { StringValue = "Value1" } + } + } + }; + + Assert.Same("Value1", selector.Select(data)); + } + + [Fact] + public void Should_Select_Null_Value_On_Null_Object() + { + var selector = new MemberSelector() { MemberName = "StringValue" }; + + Assert.Equal(null, selector.Select(null)); + } + + [Fact] + public void Should_Select_Null_Value_On_Wrong_MemberName() + { + var selector = new MemberSelector() { MemberName = "WrongProperty" }; + + var data = new Item() { StringValue = "Value1" }; + + Assert.Same(null, selector.Select(data)); + } + + [Fact] + public void Should_Select_Simple_Property_Value() + { + var selector = new MemberSelector() { MemberName = "StringValue" }; + + var data = new Item() { StringValue = "Value1" }; + + Assert.Same("Value1", selector.Select(data)); + } + + [Fact] + public void Should_Select_Simple_Property_Value_In_Multiple_Items() + { + var selector = new MemberSelector() { MemberName = "StringValue" }; + + var data = new Item[] + { + new Item() { StringValue = "Value1" }, + new Item() { StringValue = "Value2" }, + new Item() { StringValue = "Value3" } + }; + + Assert.Same("Value1", selector.Select(data[0])); + Assert.Same("Value2", selector.Select(data[1])); + Assert.Same("Value3", selector.Select(data[2])); + } + + [Fact] + public void Should_Select_Target_On_Empty_MemberName() + { + var selector = new MemberSelector(); + + var data = new Item() { StringValue = "Value1" }; + + Assert.Same(data, selector.Select(data)); + } + + [Fact] + public void Should_Support_Change_Of_MemberName() + { + var selector = new MemberSelector() { MemberName = "StringValue" }; + + var data = new Item() + { + StringValue = "Value1", + IntValue = 1 + }; + + Assert.Same("Value1", selector.Select(data)); + + selector.MemberName = "IntValue"; + + Assert.Equal(1, selector.Select(data)); + } + + private class Item + { + public Item Child { get; set; } + public int IntValue { get; set; } + + public string StringValue { get; set; } + } + } +} \ No newline at end of file diff --git a/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.csproj b/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.csproj index f09c0d08e6..fbc639e335 100644 --- a/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.csproj +++ b/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.csproj @@ -1,4 +1,4 @@ - + Debug @@ -73,6 +73,7 @@ + diff --git a/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs b/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs new file mode 100644 index 0000000000..5b1d76cd0f --- /dev/null +++ b/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs @@ -0,0 +1,47 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Controls; +using Avalonia.Layout; +using Avalonia.Media; +using Xunit; + +#if AVALONIA_CAIRO +namespace Avalonia.Cairo.RenderTests.Controls +#elif AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests +#else +namespace Avalonia.Direct2D1.RenderTests.Controls +#endif +{ + public class TextBlockTests : TestBase + { + public TextBlockTests() + : base(@"Controls\TextBlock") + { + } + + [Fact] + public void Wrapping_NoWrap() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new TextBlock + { + Background = Brushes.Red, + FontSize = 12, + Foreground = Brushes.Black, + Text = "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit", + VerticalAlignment = VerticalAlignment.Top, + TextWrapping = TextWrapping.NoWrap, + } + }; + + RenderToFile(target); + CompareImages(); + } + } +} diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 9f23f81e9a..9bf1b9ae0a 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -58,6 +58,11 @@ namespace Avalonia.Direct2D1.RenderTests protected void RenderToFile(Control target, [CallerMemberName] string testName = "") { + if (!Directory.Exists(OutputPath)) + { + Directory.CreateDirectory(OutputPath); + } + string path = Path.Combine(OutputPath, testName + ".out.png"); using (RenderTargetBitmap bitmap = new RenderTargetBitmap( diff --git a/tests/Avalonia.SceneGraph.UnitTests/VisualTests.cs b/tests/Avalonia.SceneGraph.UnitTests/VisualTests.cs index c7d0de6f56..6679138e6d 100644 --- a/tests/Avalonia.SceneGraph.UnitTests/VisualTests.cs +++ b/tests/Avalonia.SceneGraph.UnitTests/VisualTests.cs @@ -98,5 +98,18 @@ namespace Avalonia.SceneGraph.UnitTests Assert.True(called1); Assert.True(called2); } + + [Fact] + public void Adding_Already_Parented_Control_Should_Throw() + { + var root1 = new TestRoot(); + var root2 = new TestRoot(); + var child = new TestVisual(); + + root1.AddChild(child); + + Assert.Throws(() => root2.AddChild(child)); + Assert.Equal(0, root2.GetVisualChildren().Count()); + } } } diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index a3f13af8a5..db0f600a0a 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -143,7 +143,8 @@ namespace Avalonia.UnitTests It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny()) == Mock.Of() && + It.IsAny(), + It.IsAny()) == Mock.Of() && x.CreateStreamGeometry() == Mock.Of( y => y.Open() == Mock.Of())); } diff --git a/tests/TestFiles/Direct2D1/Controls/TextBlock/Wrapping_NoWrap.expected.png b/tests/TestFiles/Direct2D1/Controls/TextBlock/Wrapping_NoWrap.expected.png new file mode 100644 index 0000000000..2296e02d68 Binary files /dev/null and b/tests/TestFiles/Direct2D1/Controls/TextBlock/Wrapping_NoWrap.expected.png differ