Browse Source

Merge branch 'master' into fix/numericParsing

pull/1539/head
Benedikt Schroeder 8 years ago
committed by GitHub
parent
commit
36be969fc6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      src/Avalonia.Controls/ContextMenu.cs
  2. 39
      src/Avalonia.Controls/Primitives/Popup.cs
  3. 26
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  4. 6
      src/Avalonia.Themes.Default/MenuItem.xaml
  5. 4
      src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs
  6. 7
      src/Windows/Avalonia.Win32/ScreenImpl.cs
  7. 18
      src/Windows/Avalonia.Win32/Win32Platform.cs
  8. 18
      tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs

7
src/Avalonia.Controls/ContextMenu.cs

@ -19,7 +19,7 @@ namespace Avalonia.Controls
{
ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);
MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);
}
/// <summary>
@ -75,13 +75,14 @@ namespace Avalonia.Controls
{
if (control != null)
{
if(_popup == null)
if (_popup == null)
{
_popup = new Popup()
{
PlacementMode = PlacementMode.Pointer,
PlacementTarget = control,
StaysOpen = false
StaysOpen = false,
ObeyScreenEdges = true
};
_popup.Closed += PopupClosed;

39
src/Avalonia.Controls/Primitives/Popup.cs

@ -40,6 +40,12 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<PlacementMode> PlacementModeProperty =
AvaloniaProperty.Register<Popup, PlacementMode>(nameof(PlacementMode), defaultValue: PlacementMode.Bottom);
/// <summary>
/// Defines the <see cref="ObeyScreenEdges"/> property.
/// </summary>
public static readonly StyledProperty<bool> ObeyScreenEdgesProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges));
/// <summary>
/// Defines the <see cref="HorizontalOffset"/> property.
/// </summary>
@ -136,6 +142,16 @@ namespace Avalonia.Controls.Primitives
set { SetValue(PlacementModeProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether the popup positions itself within the nearest screen boundary
/// when its opened at a position where it would otherwise overlap the screen edge.
/// </summary>
public bool ObeyScreenEdges
{
get => GetValue(ObeyScreenEdgesProperty);
set => SetValue(ObeyScreenEdgesProperty, value);
}
/// <summary>
/// Gets or sets the Horizontal offset of the popup in relation to the <see cref="PlacementTarget"/>
/// </summary>
@ -216,12 +232,12 @@ namespace Avalonia.Controls.Primitives
var window = _topLevel as Window;
if (window != null)
{
window.Deactivated += WindowDeactivated;
window.Deactivated += WindowDeactivated;
}
else
{
var parentPopuproot = _topLevel as PopupRoot;
if(parentPopuproot != null && parentPopuproot.Parent!=null)
if (parentPopuproot != null && parentPopuproot.Parent != null)
{
((Popup)(parentPopuproot.Parent)).Closed += ParentClosed;
}
@ -234,13 +250,18 @@ namespace Avalonia.Controls.Primitives
_popupRoot.Show();
if (ObeyScreenEdges)
{
_popupRoot.SnapInsideScreenEdges();
}
_ignoreIsOpenChanged = true;
IsOpen = true;
_ignoreIsOpenChanged = false;
Opened?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Closes the popup.
/// </summary>
@ -346,8 +367,10 @@ namespace Avalonia.Controls.Primitives
/// <returns>The popup's position in screen coordinates.</returns>
protected virtual Point GetPosition()
{
return GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot,
var result = GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot,
HorizontalOffset, VerticalOffset);
return result;
}
internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
@ -399,8 +422,8 @@ namespace Avalonia.Controls.Primitives
{
if (!StaysOpen)
{
if(!IsChildOrThis((IVisual)e.Source))
{
if (!IsChildOrThis((IVisual)e.Source))
{
Close();
e.Handled = true;
}
@ -412,12 +435,12 @@ namespace Avalonia.Controls.Primitives
IVisual root = child.GetVisualRoot();
while (root is PopupRoot)
{
if (root == PopupRoot) return true;
if (root == PopupRoot) return true;
root = ((PopupRoot)root).Parent.GetVisualRoot();
}
return false;
}
private void WindowDeactivated(object sender, EventArgs e)
{
if (!StaysOpen)

26
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -2,10 +2,12 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Presenters;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Styling;
@ -75,6 +77,30 @@ namespace Avalonia.Controls.Primitives
/// <inheritdoc/>
public void Dispose() => PlatformImpl?.Dispose();
/// <summary>
/// Moves the Popups position so that it doesnt overlap screen edges.
/// This method can be called immediately after Show has been called.
/// </summary>
public void SnapInsideScreenEdges()
{
var window = this.GetSelfAndLogicalAncestors().OfType<Window>().First();
var screen = window.Screens.ScreenFromPoint(Position);
var screenX = Position.X + Bounds.Width - screen.Bounds.X;
var screenY = Position.Y + Bounds.Height - screen.Bounds.Y;
if (screenX > screen.Bounds.Width)
{
Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width));
}
if (screenY > screen.Bounds.Height)
{
Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height));
}
}
/// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{

6
src/Avalonia.Themes.Default/MenuItem.xaml

@ -45,7 +45,8 @@
<Popup Name="PART_Popup"
PlacementMode="Right"
StaysOpen="True"
IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}">
IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}"
ObeyScreenEdges="True">
<Border Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1">
@ -92,7 +93,8 @@
</ContentPresenter>
<Popup Name="PART_Popup"
IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}"
StaysOpen="True">
StaysOpen="True"
ObeyScreenEdges="True">
<Border Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1">

4
src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs

@ -41,12 +41,12 @@ namespace Avalonia.Rendering
{
add
{
_tick += value;
if (_subscriberCount++ == 0)
{
Start();
}
_tick += value;
}
remove

7
src/Windows/Avalonia.Win32/ScreenImpl.cs

@ -2,16 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Platform;
using Avalonia.Utilities;
using static Avalonia.Win32.Interop.UnmanagedMethods;
#if NETSTANDARD
using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception;
#endif
namespace Avalonia.Win32
{
public class ScreenImpl : IScreenImpl

18
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -1,27 +1,23 @@
// 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.Input.Platform;
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reactive.Disposables;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;
using Avalonia.Win32.Input;
using Avalonia.Win32.Interop;
using Avalonia.Controls;
using Avalonia.Rendering;
using Avalonia.Threading;
using System.IO;
#if NETSTANDARD
using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception;
#else
using System.ComponentModel;
#endif
using Avalonia.Win32.Input;
using Avalonia.Win32.Interop;
namespace Avalonia
{

18
tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs

@ -58,7 +58,7 @@ namespace Avalonia.Markup.UnitTests.Data
[Fact]
public async Task Should_Convert_Get_String_To_Double()
{
var data = new Class1 { StringValue = "5.6" };
var data = new Class1 { StringValue = $"{5.6}" };
var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double));
var result = await target.Take(1);
@ -94,12 +94,12 @@ namespace Avalonia.Markup.UnitTests.Data
[Fact]
public void Should_Convert_Set_String_To_Double()
{
var data = new Class1 { StringValue = (5.6).ToString() };
var data = new Class1 { StringValue = $"{5.6}" };
var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double));
target.OnNext(6.7);
Assert.Equal((6.7).ToString(), data.StringValue);
Assert.Equal($"{6.7}", data.StringValue);
GC.KeepAlive(data);
}
@ -111,7 +111,7 @@ namespace Avalonia.Markup.UnitTests.Data
var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string));
var result = await target.Take(1);
Assert.Equal((5.6).ToString(), result);
Assert.Equal($"{5.6}", result);
GC.KeepAlive(data);
}
@ -122,7 +122,7 @@ namespace Avalonia.Markup.UnitTests.Data
var data = new Class1 { DoubleValue = 5.6 };
var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string));
target.OnNext("6.7");
target.OnNext($"{6.7}");
Assert.Equal(6.7, data.DoubleValue);
@ -318,15 +318,15 @@ namespace Avalonia.Markup.UnitTests.Data
target.Subscribe(x => result.Add(x));
target.OnNext(1.2);
target.OnNext("3.4");
target.OnNext($"{3.4}");
target.OnNext("bar");
Assert.Equal(
new[]
{
new BindingNotification("5.6"),
new BindingNotification("1.2"),
new BindingNotification("3.4"),
new BindingNotification($"{5.6}"),
new BindingNotification($"{1.2}"),
new BindingNotification($"{3.4}"),
new BindingNotification(
new InvalidCastException("'bar' is not a valid number."),
BindingErrorType.Error)

Loading…
Cancel
Save