From f7d271f84607385c60b020d941286d4dcb8cb2b7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 26 May 2015 19:54:53 +0200 Subject: [PATCH] Something resembling a menu now... --- Perspex.Controls/Menu.cs | 2 + Perspex.Styling/Selector.cs | 2 +- Perspex.Styling/Selectors.cs | 38 ++++---- Perspex.Themes.Default/MenuItemStyle.cs | 88 +++++++++++++++++-- TestApplication/Program.cs | 18 ++-- .../Perspex.Styling.UnitTests.csproj | 2 +- ...Tests_Parent.cs => SelectorTests_Child.cs} | 23 +++-- 7 files changed, 134 insertions(+), 39 deletions(-) rename Tests/Perspex.Styling.UnitTests/{SelectorTests_Parent.cs => SelectorTests_Child.cs} (82%) diff --git a/Perspex.Controls/Menu.cs b/Perspex.Controls/Menu.cs index 6fa9fbccbd..266cbb5b1b 100644 --- a/Perspex.Controls/Menu.cs +++ b/Perspex.Controls/Menu.cs @@ -4,6 +4,8 @@ // // ----------------------------------------------------------------------- +using Perspex.Controls.Generators; + namespace Perspex.Controls { public class Menu : ItemsControl diff --git a/Perspex.Styling/Selector.cs b/Perspex.Styling/Selector.cs index e141f9dabb..2fb0d12047 100644 --- a/Perspex.Styling/Selector.cs +++ b/Perspex.Styling/Selector.cs @@ -21,7 +21,7 @@ namespace Perspex.Styling public Selector() { - this.evaluate = _ => new SelectorMatch(true); + this.evaluate = _ => SelectorMatch.True; } public Selector( diff --git a/Perspex.Styling/Selectors.cs b/Perspex.Styling/Selectors.cs index 4ed699821a..70dfb8a223 100644 --- a/Perspex.Styling/Selectors.cs +++ b/Perspex.Styling/Selectors.cs @@ -13,6 +13,13 @@ namespace Perspex.Styling public static class Selectors { + public static Selector Child(this Selector previous) + { + Contract.Requires(previous != null); + + return new Selector(previous, x => MatchChild(x, previous), " < ", stopTraversal: true); + } + public static Selector Class(this Selector previous, string name) { Contract.Requires(previous != null); @@ -59,13 +66,6 @@ namespace Perspex.Styling return previous.OfType(typeof(T)); } - public static Selector Parent(this Selector previous) - { - Contract.Requires(previous != null); - - return new Selector(previous, x => MatchParent(x, previous), " < ", stopTraversal: true); - } - public static Selector PropertyEquals(this Selector previous, PerspexProperty property, object value) { Contract.Requires(previous != null); @@ -86,6 +86,20 @@ namespace Perspex.Styling stopTraversal: true); } + private static SelectorMatch MatchChild(IStyleable control, Selector previous) + { + var parent = ((ILogical)control).LogicalParent; + + if (parent != null) + { + return previous.Match((IStyleable)parent); + } + else + { + return SelectorMatch.False; + } + } + private static SelectorMatch MatchClass(IStyleable control, string name) { return new SelectorMatch( @@ -111,7 +125,7 @@ namespace Perspex.Styling { if (match.ImmediateResult == true) { - return new SelectorMatch(true); + return SelectorMatch.True; } } else @@ -143,17 +157,11 @@ namespace Perspex.Styling return new SelectorMatch(controlType == type); } - private static SelectorMatch MatchParent(IStyleable control, Selector previous) - { - var parent = ((ILogical)control).LogicalParent; - return previous.Match((IStyleable)parent); - } - private static SelectorMatch MatchPropertyEquals(IStyleable x, PerspexProperty property, object value) { if (!x.IsRegistered(property)) { - return new SelectorMatch(false); + return SelectorMatch.False; } else { diff --git a/Perspex.Themes.Default/MenuItemStyle.cs b/Perspex.Themes.Default/MenuItemStyle.cs index d6fc330bf8..2cc0de8fd0 100644 --- a/Perspex.Themes.Default/MenuItemStyle.cs +++ b/Perspex.Themes.Default/MenuItemStyle.cs @@ -27,7 +27,14 @@ namespace Perspex.Themes.Default { new Setter(MenuItem.BorderThicknessProperty, 1.0), new Setter(MenuItem.PaddingProperty, new Thickness(6, 0)), - new Setter(MenuItem.TemplateProperty, ControlTemplate.Create(this.Template)), + new Setter(MenuItem.TemplateProperty, ControlTemplate.Create(this.PopupTemplate)), + }, + }, + new Style(x => x.OfType().Child().OfType()) + { + Setters = new[] + { + new Setter(MenuItem.TemplateProperty, ControlTemplate.Create(this.TopLevelTemplate)), }, }, new Style(x => x.OfType().Class(":pointerover").Template().Name("root")) @@ -37,11 +44,77 @@ namespace Perspex.Themes.Default new Setter(Border.BackgroundProperty, new SolidColorBrush(0x3d26a0da)), new Setter(Border.BorderBrushProperty, new SolidColorBrush(0xff26a0da)), }, - } + }, }); } - private Control Template(MenuItem control) + private Control TopLevelTemplate(MenuItem control) + { + Popup popup; + + var result = new Border + { + Name = "root", + [~Border.BackgroundProperty] = control[~MenuItem.BackgroundProperty], + [~Border.BorderBrushProperty] = control[~MenuItem.BorderBrushProperty], + [~Border.BorderThicknessProperty] = control[~MenuItem.BorderThicknessProperty], + Content = new Panel + { + Children = new Controls + { + new ContentPresenter + { + [~ContentPresenter.ContentProperty] = control[~MenuItem.HeaderProperty], + [~ContentPresenter.MarginProperty] = control[~MenuItem.PaddingProperty], + [Grid.ColumnProperty] = 1, + }, + (popup = new Popup + { + Name = "popup", + StaysOpen = false, + [!!Popup.IsOpenProperty] = control[!!MenuItem.IsSubMenuOpenProperty], + Child = new Border + { + Background = new SolidColorBrush(0xfff0f0f0), + BorderBrush = new SolidColorBrush(0xff999999), + BorderThickness = 1, + Padding = new Thickness(2), + Content = new ScrollViewer + { + Content = new Panel + { + Children = new Controls + { + new Rectangle + { + Name = "iconSeparator", + Fill = new SolidColorBrush(0xffd7d7d7), + HorizontalAlignment = HorizontalAlignment.Left, + Margin = new Thickness(29, 2, 0, 2), + Width = 1, + }, + new ItemsPresenter + { + Name = "itemsPresenter", + [~ItemsPresenter.ItemsProperty] = control[~Menu.ItemsProperty], + [~ItemsPresenter.ItemsPanelProperty] = control[~Menu.ItemsPanelProperty], + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, + } + } + } + } + } + }) + }, + } + }; + + popup.PlacementTarget = result; + + return result; + } + + private Control PopupTemplate(MenuItem control) { Popup popup; @@ -55,8 +128,10 @@ namespace Perspex.Themes.Default { ColumnDefinitions = new ColumnDefinitions { - new ColumnDefinition(GridLength.Auto), - new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(22, GridUnitType.Pixel), + new ColumnDefinition(13, GridUnitType.Pixel), + new ColumnDefinition(1, GridUnitType.Star), + new ColumnDefinition(20, GridUnitType.Pixel), }, Children = new Controls { @@ -80,9 +155,10 @@ namespace Perspex.Themes.Default }, new ContentPresenter { + VerticalAlignment = VerticalAlignment.Center, [~ContentPresenter.ContentProperty] = control[~MenuItem.HeaderProperty], [~ContentPresenter.MarginProperty] = control[~MenuItem.PaddingProperty], - [Grid.ColumnProperty] = 1, + [Grid.ColumnProperty] = 2, }, (popup = new Popup { diff --git a/TestApplication/Program.cs b/TestApplication/Program.cs index 9400b19ca7..fa467476a3 100644 --- a/TestApplication/Program.cs +++ b/TestApplication/Program.cs @@ -141,43 +141,43 @@ namespace TestApplication { new MenuItem { - Header = "_File", + Header = "File", Items = new[] { new MenuItem { - Header = "_Open...", + Header = "Open...", }, new MenuItem { - Header = "_Save", + Header = "Save", }, new MenuItem { - Header = "Save _As", + Header = "Save As", }, new MenuItem { - Header = "E_xit", + Header = "Exit", }, } }, new MenuItem { - Header = "_Edit", + Header = "Edit", Items = new[] { new MenuItem { - Header = "Cu_t", + Header = "Cut", }, new MenuItem { - Header = "_Copy", + Header = "Copy", }, new MenuItem { - Header = "_Paste", + Header = "Paste", }, } } diff --git a/Tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj b/Tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj index f42de58b34..87740daf9d 100644 --- a/Tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj +++ b/Tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj @@ -83,7 +83,7 @@ - + diff --git a/Tests/Perspex.Styling.UnitTests/SelectorTests_Parent.cs b/Tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs similarity index 82% rename from Tests/Perspex.Styling.UnitTests/SelectorTests_Parent.cs rename to Tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs index b4fe7be237..5ad50a76d6 100644 --- a/Tests/Perspex.Styling.UnitTests/SelectorTests_Parent.cs +++ b/Tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs @@ -14,23 +14,23 @@ namespace Perspex.Styling.UnitTests using Perspex.Styling; using Xunit; - public class SelectorTests_Parent + public class SelectorTests_Child { [Fact] - public void Parent_Matches_Control_When_It_Is_Child_OfType() + public void Child_Matches_Control_When_It_Is_Child_OfType() { var parent = new TestLogical1(); var child = new TestLogical2(); child.LogicalParent = parent; - var selector = new Selector().OfType().Parent().OfType(); + var selector = new Selector().OfType().Child().OfType(); Assert.True(selector.Match(child).ImmediateResult); } [Fact] - public void Parent_Doesnt_Match_Control_When_It_Is_Grandchild_OfType() + public void Child_Doesnt_Match_Control_When_It_Is_Grandchild_OfType() { var grandparent = new TestLogical1(); var parent = new TestLogical2(); @@ -39,20 +39,20 @@ namespace Perspex.Styling.UnitTests parent.LogicalParent = grandparent; child.LogicalParent = parent; - var selector = new Selector().OfType().Parent().OfType(); + var selector = new Selector().OfType().Child().OfType(); Assert.False(selector.Match(child).ImmediateResult); } [Fact] - public async Task Parent_Matches_Control_When_It_Is_Child_OfType_And_Class() + public async Task Child_Matches_Control_When_It_Is_Child_OfType_And_Class() { var parent = new TestLogical1(); var child = new TestLogical2(); child.LogicalParent = parent; - var selector = new Selector().OfType().Class("foo").Parent().OfType(); + var selector = new Selector().OfType().Class("foo").Child().OfType(); var activator = selector.Match(child).ObservableResult; Assert.False(await activator.Take(1)); @@ -62,6 +62,15 @@ namespace Perspex.Styling.UnitTests Assert.False(await activator.Take(1)); } + [Fact] + public void Child_Doesnt_Match_Control_When_It_Has_No_Parent() + { + var control = new TestLogical3(); + var selector = new Selector().OfType().Child().OfType(); + + Assert.False(selector.Match(control).ImmediateResult); + } + public abstract class TestLogical : ILogical, IStyleable { public TestLogical()