38 changed files with 920 additions and 938 deletions
@ -0,0 +1,4 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<packages> |
||||
|
<package id="Serilog" version="1.5.9" targetFramework="net46" /> |
||||
|
</packages> |
||||
@ -1,445 +1,176 @@ |
|||||
namespace Perspex.Controls |
namespace Perspex.Controls |
||||
{ |
{ |
||||
using System; |
using System; |
||||
using System.Collections.Generic; |
|
||||
using System.Diagnostics.CodeAnalysis; |
|
||||
using System.Linq; |
|
||||
using Layout; |
|
||||
|
|
||||
public class DockPanel : Panel |
/// <summary>
|
||||
|
/// Defines the available docking modes for a control in a <see cref="DockPanel"/>.
|
||||
|
/// </summary>
|
||||
|
public enum Dock |
||||
{ |
{ |
||||
public static readonly PerspexProperty<Dock> DockProperty = PerspexProperty.RegisterAttached<DockPanel, Control, Dock>("Dock"); |
Left = 0, |
||||
|
Bottom, |
||||
|
Right, |
||||
|
Top |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// A panel which arranges its children at the top, bottom, left, right or center.
|
||||
|
/// </summary>
|
||||
|
public class DockPanel : Panel |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the Dock attached property.
|
||||
|
/// </summary>
|
||||
|
public static readonly PerspexProperty<Dock> DockProperty = |
||||
|
PerspexProperty.RegisterAttached<DockPanel, Control, Dock>("Dock"); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="LastChildFill"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly PerspexProperty<bool> LastChildFillProperty = |
||||
|
PerspexProperty.Register<DockPanel, bool>( |
||||
|
nameof(LastChildFillProperty), |
||||
|
defaultValue: true); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes static members of the <see cref="DockPanel"/> class.
|
||||
|
/// </summary>
|
||||
static DockPanel() |
static DockPanel() |
||||
{ |
{ |
||||
AffectsArrange(DockProperty); |
AffectsArrange(DockProperty); |
||||
} |
} |
||||
|
|
||||
// ReSharper disable once UnusedMember.Global
|
/// <summary>
|
||||
public static Dock GetDock(PerspexObject perspexObject) |
/// Gets the value of the Dock attached property on the specified control.
|
||||
|
/// </summary>
|
||||
|
/// <param name="control">The control.</param>
|
||||
|
/// <returns>The Dock attached property.</returns>
|
||||
|
public static Dock GetDock(Control control) |
||||
{ |
{ |
||||
return perspexObject.GetValue(DockProperty); |
return control.GetValue(DockProperty); |
||||
} |
} |
||||
|
|
||||
// ReSharper disable once UnusedMember.Global
|
/// <summary>
|
||||
public static void SetDock(PerspexObject element, Dock dock) |
/// Sets the value of the Dock attached property on the specified control.
|
||||
|
/// </summary>
|
||||
|
/// <param name="control">The control.</param>
|
||||
|
/// <param name="value">The value of the Dock property.</param>
|
||||
|
public static void SetDock(Control control, Dock value) |
||||
{ |
{ |
||||
element.SetValue(DockProperty, dock); |
control.SetValue(DockProperty, value); |
||||
} |
} |
||||
|
|
||||
public static readonly PerspexProperty<bool> LastChildFillProperty = PerspexProperty.Register<DockPanel, bool>(nameof(LastChildFillProperty), defaultValue: true); |
/// <summary>
|
||||
|
/// Gets or sets a value which indicates whether the last child of the
|
||||
|
/// <see cref="DockPanel"/> fills the remaining space in the panel.
|
||||
|
/// </summary>
|
||||
public bool LastChildFill |
public bool LastChildFill |
||||
{ |
{ |
||||
get { return GetValue(LastChildFillProperty); } |
get { return GetValue(LastChildFillProperty); } |
||||
set { SetValue(LastChildFillProperty, value); } |
set { SetValue(LastChildFillProperty, value); } |
||||
} |
} |
||||
|
|
||||
protected override Size MeasureOverride(Size availableSize) |
/// <inheritdoc/>
|
||||
{ |
protected override Size MeasureOverride(Size constraint) |
||||
if (!LastChildFill) |
|
||||
{ |
|
||||
return MeasureItemsThatWillBeDocked(availableSize, Children); |
|
||||
} |
|
||||
|
|
||||
var sizeRequiredByDockingItems = MeasureItemsThatWillBeDocked(availableSize, Children.WithoutLast()); |
|
||||
var elementThatWillFill = Children.Last(); |
|
||||
elementThatWillFill.Measure(availableSize - sizeRequiredByDockingItems); |
|
||||
var finalSize = sizeRequiredByDockingItems.Inflate(new Thickness(elementThatWillFill.DesiredSize.Width, elementThatWillFill.DesiredSize.Height)); |
|
||||
return finalSize; |
|
||||
} |
|
||||
|
|
||||
private static Size MeasureItemsThatWillBeDocked(Size availableSize, IEnumerable<IControl> children) |
|
||||
{ |
{ |
||||
var requiredHorizontalLength = 0D; |
double usedWidth = 0.0; |
||||
var requiredVerticalLength = 0D; |
double usedHeight = 0.0; |
||||
|
double maximumWidth = 0.0; |
||||
|
double maximumHeight = 0.0; |
||||
|
|
||||
foreach (var control in children) |
// Measure each of the Children
|
||||
|
foreach (Control element in Children) |
||||
{ |
{ |
||||
control.Measure(availableSize); |
// Get the child's desired size
|
||||
|
Size remainingSize = new Size( |
||||
var dock = control.GetValue(DockProperty); |
Math.Max(0.0, constraint.Width - usedWidth), |
||||
if (IsHorizontal(dock)) |
Math.Max(0.0, constraint.Height - usedHeight)); |
||||
{ |
element.Measure(remainingSize); |
||||
requiredHorizontalLength += control.DesiredSize.Width; |
Size desiredSize = element.DesiredSize; |
||||
} |
|
||||
else |
// Decrease the remaining space for the rest of the children
|
||||
|
switch (GetDock(element)) |
||||
{ |
{ |
||||
requiredVerticalLength += control.DesiredSize.Height; |
case Dock.Left: |
||||
|
case Dock.Right: |
||||
|
maximumHeight = Math.Max(maximumHeight, usedHeight + desiredSize.Height); |
||||
|
usedWidth += desiredSize.Width; |
||||
|
break; |
||||
|
case Dock.Top: |
||||
|
case Dock.Bottom: |
||||
|
maximumWidth = Math.Max(maximumWidth, usedWidth + desiredSize.Width); |
||||
|
usedHeight += desiredSize.Height; |
||||
|
break; |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
return new Size(requiredHorizontalLength, requiredVerticalLength); |
maximumWidth = Math.Max(maximumWidth, usedWidth); |
||||
} |
maximumHeight = Math.Max(maximumHeight, usedHeight); |
||||
|
return new Size(maximumWidth, maximumHeight); |
||||
private static bool IsHorizontal(Dock dock) |
|
||||
{ |
|
||||
return dock == Dock.Left || dock == Dock.Right; |
|
||||
} |
|
||||
|
|
||||
protected override Size ArrangeOverride(Size finalSize) |
|
||||
{ |
|
||||
if (!LastChildFill) |
|
||||
{ |
|
||||
return ArrangeAllChildren(finalSize); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
return ArrangeChildrenAndFillLastChild(finalSize); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private Size ArrangeChildrenAndFillLastChild(Size finalSize) |
|
||||
{ |
|
||||
var docker = new DockingArranger(); |
|
||||
var requiredSize = docker.ArrangeAndGetUsedSize(finalSize, Children.WithoutLast()); |
|
||||
ArrangeToFill(Children.Last(), finalSize, docker.UsedMargin); |
|
||||
return requiredSize; |
|
||||
} |
|
||||
|
|
||||
private Size ArrangeAllChildren(Size finalSize) |
|
||||
{ |
|
||||
return new DockingArranger().ArrangeAndGetUsedSize(finalSize, Children); |
|
||||
} |
} |
||||
|
|
||||
private static void ArrangeToFill(ILayoutable layoutable, Size containerSize, Margin margin) |
/// <inheritdoc/>
|
||||
|
protected override Size ArrangeOverride(Size arrangeSize) |
||||
{ |
{ |
||||
var containerRect = new Rect(new Point(0, 0), containerSize); |
double left = 0.0; |
||||
var marginsCutout = margin.AsThickness(); |
double top = 0.0; |
||||
var withoutMargins = containerRect.Deflate(marginsCutout); |
double right = 0.0; |
||||
|
double bottom = 0.0; |
||||
layoutable.Arrange(withoutMargins); |
|
||||
} |
|
||||
|
|
||||
private class DockingArranger |
// Arrange each of the Children
|
||||
{ |
var children = Children; |
||||
public Margin UsedMargin { get; private set; } |
int dockedCount = children.Count - (LastChildFill ? 1 : 0); |
||||
|
int index = 0; |
||||
|
|
||||
public Size ArrangeAndGetUsedSize(Size availableSize, IEnumerable<IControl> children) |
foreach (Control element in children) |
||||
{ |
{ |
||||
var leftArranger = new LeftDocker(availableSize); |
// Determine the remaining space left to arrange the element
|
||||
var rightArranger = new RightDocker(availableSize); |
Rect remainingRect = new Rect( |
||||
var topArranger = new LeftDocker(availableSize.Swap()); |
left, |
||||
var bottomArranger = new RightDocker(availableSize.Swap()); |
top, |
||||
|
Math.Max(0.0, arrangeSize.Width - left - right), |
||||
UsedMargin = new Margin(); |
Math.Max(0.0, arrangeSize.Height - top - bottom)); |
||||
|
|
||||
foreach (var control in children) |
// Trim the remaining Rect to the docked size of the element
|
||||
|
// (unless the element should fill the remaining space because
|
||||
|
// of LastChildFill)
|
||||
|
if (index < dockedCount) |
||||
{ |
{ |
||||
Rect dockedRect; |
Size desiredSize = element.DesiredSize; |
||||
var dock = control.GetValue(DockProperty); |
switch (GetDock(element)) |
||||
switch (dock) |
|
||||
{ |
{ |
||||
case Dock.Left: |
case Dock.Left: |
||||
dockedRect = leftArranger.GetDockedRect(control.DesiredSize, UsedMargin, control.GetAlignments()); |
left += desiredSize.Width; |
||||
|
remainingRect = remainingRect.WithWidth(desiredSize.Width); |
||||
break; |
break; |
||||
|
|
||||
case Dock.Top: |
case Dock.Top: |
||||
UsedMargin.Swap(); |
top += desiredSize.Height; |
||||
dockedRect = topArranger.GetDockedRect(control.DesiredSize.Swap(), UsedMargin, control.GetAlignments().Swap()).Swap(); |
remainingRect = remainingRect.WithHeight(desiredSize.Height); |
||||
UsedMargin.Swap(); |
|
||||
break; |
break; |
||||
|
|
||||
case Dock.Right: |
case Dock.Right: |
||||
dockedRect = rightArranger.GetDockedRect(control.DesiredSize, UsedMargin, control.GetAlignments()); |
right += desiredSize.Width; |
||||
|
remainingRect = new Rect( |
||||
|
Math.Max(0.0, arrangeSize.Width - right), |
||||
|
remainingRect.Y, |
||||
|
desiredSize.Width, |
||||
|
remainingRect.Height); |
||||
break; |
break; |
||||
|
|
||||
case Dock.Bottom: |
case Dock.Bottom: |
||||
UsedMargin.Swap(); |
bottom += desiredSize.Height; |
||||
dockedRect = bottomArranger.GetDockedRect(control.DesiredSize.Swap(), UsedMargin, control.GetAlignments().Swap()).Swap(); |
remainingRect = new Rect( |
||||
UsedMargin.Swap(); |
remainingRect.X, |
||||
|
Math.Max(0.0, arrangeSize.Height - bottom), |
||||
|
remainingRect.Width, |
||||
|
desiredSize.Height); |
||||
break; |
break; |
||||
|
|
||||
default: |
|
||||
throw new InvalidOperationException($"Invalid dock value {dock}"); |
|
||||
} |
} |
||||
|
|
||||
control.Arrange(dockedRect); |
|
||||
} |
} |
||||
|
|
||||
return availableSize; |
element.Arrange(remainingRect); |
||||
} |
index++; |
||||
} |
|
||||
|
|
||||
private class LeftDocker : Docker |
|
||||
{ |
|
||||
public LeftDocker(Size availableSize) : base(availableSize) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public override Rect GetDockedRect(Size childSize, Margin margin, Alignments alignments) |
|
||||
{ |
|
||||
var marginsCutout = margin.AsThickness(); |
|
||||
var availableRect = OriginalRect.Deflate(marginsCutout); |
|
||||
var alignedRect = AlignToLeft(availableRect, childSize, alignments.Vertical); |
|
||||
|
|
||||
AccumulatedOffset += childSize.Width; |
|
||||
margin.Horizontal = margin.Horizontal.Offset(childSize.Width, 0); |
|
||||
|
|
||||
return alignedRect; |
|
||||
} |
|
||||
|
|
||||
private static Rect AlignToLeft(Rect availableRect, Size childSize, Alignment verticalAlignment) |
|
||||
{ |
|
||||
return availableRect.AlignChild(childSize, Alignment.Start, verticalAlignment); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private class RightDocker : Docker |
|
||||
{ |
|
||||
public RightDocker(Size availableSize) : base(availableSize) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public override Rect GetDockedRect(Size childSize, Margin margin, Alignments alignments) |
|
||||
{ |
|
||||
var marginsCutout = margin.AsThickness(); |
|
||||
var withoutMargins = OriginalRect.Deflate(marginsCutout); |
|
||||
var finalRect = withoutMargins.AlignChild(childSize, Alignment.End, alignments.Vertical); |
|
||||
|
|
||||
AccumulatedOffset += childSize.Width; |
|
||||
margin.Horizontal = margin.Horizontal.Offset(0, childSize.Width); |
|
||||
|
|
||||
return finalRect; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private abstract class Docker |
|
||||
{ |
|
||||
protected Docker(Size availableSize) |
|
||||
{ |
|
||||
OriginalRect = new Rect(new Point(0, 0), availableSize); |
|
||||
} |
} |
||||
|
|
||||
protected double AccumulatedOffset { get; set; } |
return arrangeSize; |
||||
|
|
||||
protected Rect OriginalRect { get; } |
|
||||
|
|
||||
public abstract Rect GetDockedRect(Size childSize, Margin margin, Alignments alignments); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public class Margin |
|
||||
{ |
|
||||
public Segment Horizontal { get; set; } |
|
||||
public Segment Vertical { get; set; } |
|
||||
} |
|
||||
|
|
||||
public enum Alignment |
|
||||
{ |
|
||||
Stretch, Start, Middle, End, |
|
||||
} |
|
||||
|
|
||||
public static class SegmentMixin |
|
||||
{ |
|
||||
public static Segment AlignToStart(this Segment container, double length) |
|
||||
{ |
|
||||
return new Segment(container.Start, container.Start + length); |
|
||||
} |
|
||||
|
|
||||
public static Segment AlignToEnd(this Segment container, double length) |
|
||||
{ |
|
||||
return new Segment(container.End - length, container.End); |
|
||||
} |
|
||||
|
|
||||
public static Segment AlignToMiddle(this Segment container, double length) |
|
||||
{ |
|
||||
var start = container.Start + (container.Length - length) / 2; |
|
||||
return new Segment(start, start + length); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public struct Alignments |
|
||||
{ |
|
||||
public Alignments(Alignment horizontal, Alignment vertical) |
|
||||
{ |
|
||||
Horizontal = horizontal; |
|
||||
Vertical = vertical; |
|
||||
} |
|
||||
|
|
||||
public Alignment Horizontal { get; } |
|
||||
|
|
||||
public Alignment Vertical { get; } |
|
||||
} |
|
||||
|
|
||||
public static class CoordinateMixin |
|
||||
{ |
|
||||
private static Point Swap(this Point p) |
|
||||
{ |
|
||||
return new Point(p.Y, p.X); |
|
||||
} |
|
||||
|
|
||||
public static Size Swap(this Size s) |
|
||||
{ |
|
||||
return new Size(s.Height, s.Width); |
|
||||
} |
|
||||
|
|
||||
public static Rect Swap(this Rect r) |
|
||||
{ |
|
||||
return new Rect(r.Position.Swap(), r.Size.Swap()); |
|
||||
} |
|
||||
|
|
||||
public static Segment Offset(this Segment l, double startOffset, double endOffset) |
|
||||
{ |
|
||||
return new Segment(l.Start + startOffset, l.End + endOffset); |
|
||||
} |
|
||||
|
|
||||
public static void Swap(this Margin m) |
|
||||
{ |
|
||||
var v = m.Vertical; |
|
||||
m.Vertical = m.Horizontal; |
|
||||
m.Horizontal = v; |
|
||||
} |
|
||||
|
|
||||
public static Thickness AsThickness(this Margin margin) |
|
||||
{ |
|
||||
return new Thickness(margin.Horizontal.Start, margin.Vertical.Start, margin.Horizontal.End, margin.Vertical.End); |
|
||||
} |
|
||||
|
|
||||
private static Alignment AsAlignment(this HorizontalAlignment horz) |
|
||||
{ |
|
||||
switch (horz) |
|
||||
{ |
|
||||
case HorizontalAlignment.Stretch: |
|
||||
return Alignment.Stretch; |
|
||||
case HorizontalAlignment.Left: |
|
||||
return Alignment.Start; |
|
||||
case HorizontalAlignment.Center: |
|
||||
return Alignment.Middle; |
|
||||
case HorizontalAlignment.Right: |
|
||||
return Alignment.End; |
|
||||
default: |
|
||||
throw new ArgumentOutOfRangeException(nameof(horz), horz, null); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private static Alignment AsAlignment(this VerticalAlignment vert) |
|
||||
{ |
|
||||
switch (vert) |
|
||||
{ |
|
||||
case VerticalAlignment.Stretch: |
|
||||
return Alignment.Stretch; |
|
||||
case VerticalAlignment.Top: |
|
||||
return Alignment.Start; |
|
||||
case VerticalAlignment.Center: |
|
||||
return Alignment.Middle; |
|
||||
case VerticalAlignment.Bottom: |
|
||||
return Alignment.End; |
|
||||
default: |
|
||||
throw new ArgumentOutOfRangeException(nameof(vert), vert, null); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static Alignments GetAlignments(this ILayoutable layoutable) |
|
||||
{ |
|
||||
return new Alignments(layoutable.HorizontalAlignment.AsAlignment(), layoutable.VerticalAlignment.AsAlignment()); |
|
||||
} |
|
||||
|
|
||||
public static Alignments Swap(this Alignments alignments) |
|
||||
{ |
|
||||
return new Alignments(alignments.Vertical, alignments.Horizontal); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public enum Dock |
|
||||
{ |
|
||||
Left = 0, |
|
||||
Bottom, |
|
||||
Right, |
|
||||
Top |
|
||||
} |
|
||||
|
|
||||
public static class RectMixin |
|
||||
{ |
|
||||
public static Rect AlignChild(this Rect container, Size childSize, Alignment horizontalAlignment, Alignment verticalAlignment) |
|
||||
{ |
|
||||
var horzSegment = container.GetHorizontalCoordinates(); |
|
||||
var vertSegment = container.GetVerticalCoordinates(); |
|
||||
|
|
||||
var horzResult = GetAlignedSegment(childSize.Width, horizontalAlignment, horzSegment); |
|
||||
var vertResult = GetAlignedSegment(childSize.Height, verticalAlignment, vertSegment); |
|
||||
|
|
||||
return FromSegments(horzResult, vertResult); |
|
||||
} |
|
||||
|
|
||||
private static Rect FromSegments(Segment horzSegment, Segment vertSegment) |
|
||||
{ |
|
||||
return new Rect(horzSegment.Start, vertSegment.Start, horzSegment.Length, vertSegment.Length); |
|
||||
} |
|
||||
|
|
||||
private static Segment GetAlignedSegment(double width, Alignment alignment, Segment horzSegment) |
|
||||
{ |
|
||||
switch (alignment) |
|
||||
{ |
|
||||
case Alignment.Start: |
|
||||
return horzSegment.AlignToStart(width); |
|
||||
|
|
||||
case Alignment.Middle: |
|
||||
return horzSegment.AlignToMiddle(width); |
|
||||
|
|
||||
case Alignment.End: |
|
||||
return horzSegment.AlignToEnd(width); |
|
||||
|
|
||||
default: |
|
||||
return new Segment(horzSegment.Start, horzSegment.End); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private static Segment GetHorizontalCoordinates(this Rect rect) |
|
||||
{ |
|
||||
return new Segment(rect.X, rect.Right); |
|
||||
} |
|
||||
|
|
||||
private static Segment GetVerticalCoordinates(this Rect rect) |
|
||||
{ |
|
||||
return new Segment(rect.Y, rect.Bottom); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public struct Segment |
|
||||
{ |
|
||||
public Segment(double start, double end) |
|
||||
{ |
|
||||
Start = start; |
|
||||
End = end; |
|
||||
} |
|
||||
|
|
||||
public double Start { get; } |
|
||||
public double End { get; } |
|
||||
|
|
||||
public double Length => End - Start; |
|
||||
|
|
||||
public override string ToString() |
|
||||
{ |
|
||||
return $"Start: {Start}, End: {End}"; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static class EnumerableMixin |
|
||||
{ |
|
||||
private static IEnumerable<T> Shrink<T>(this IEnumerable<T> source, int left, int right) |
|
||||
{ |
|
||||
int i = 0; |
|
||||
var buffer = new Queue<T>(right + 1); |
|
||||
|
|
||||
foreach (T x in source) |
|
||||
{ |
|
||||
if (i >= left) // Read past left many elements at the start
|
|
||||
{ |
|
||||
buffer.Enqueue(x); |
|
||||
if (buffer.Count > right) // Build a buffer to drop right many elements at the end
|
|
||||
yield return buffer.Dequeue(); |
|
||||
} |
|
||||
else i++; |
|
||||
} |
|
||||
} |
|
||||
public static IEnumerable<T> WithoutLast<T>(this IEnumerable<T> source, int n = 1) |
|
||||
{ |
|
||||
return source.Shrink(0, n); |
|
||||
} |
|
||||
public static IEnumerable<T> WithoutFirst<T>(this IEnumerable<T> source, int n = 1) |
|
||||
{ |
|
||||
return source.Shrink(n, 0); |
|
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
@ -1,120 +0,0 @@ |
|||||
// Copyright (c) The Perspex Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Reactive.Linq; |
|
||||
using Perspex.Controls; |
|
||||
using Perspex.Diagnostics.ViewModels; |
|
||||
using Perspex.Input; |
|
||||
using Perspex.Themes.Default; |
|
||||
using ReactiveUI; |
|
||||
|
|
||||
namespace Perspex.Diagnostics |
|
||||
{ |
|
||||
public class DevTools : Decorator |
|
||||
{ |
|
||||
public static readonly PerspexProperty<Control> RootProperty = |
|
||||
PerspexProperty.Register<DevTools, Control>("Root"); |
|
||||
|
|
||||
private readonly DevToolsViewModel _viewModel; |
|
||||
|
|
||||
public DevTools() |
|
||||
{ |
|
||||
_viewModel = new DevToolsViewModel(); |
|
||||
this.GetObservable(RootProperty).Subscribe(x => _viewModel.Root = x); |
|
||||
|
|
||||
InitializeComponent(); |
|
||||
} |
|
||||
|
|
||||
public Control Root |
|
||||
{ |
|
||||
get { return GetValue(RootProperty); } |
|
||||
set { SetValue(RootProperty, value); } |
|
||||
} |
|
||||
|
|
||||
public static IDisposable Attach(Window window) |
|
||||
{ |
|
||||
return window.AddHandler( |
|
||||
KeyDownEvent, |
|
||||
WindowPreviewKeyDown, |
|
||||
Interactivity.RoutingStrategies.Tunnel); |
|
||||
} |
|
||||
|
|
||||
private static void WindowPreviewKeyDown(object sender, KeyEventArgs e) |
|
||||
{ |
|
||||
if (e.Key == Key.F12) |
|
||||
{ |
|
||||
Window window = new Window |
|
||||
{ |
|
||||
Width = 1024, |
|
||||
Height = 512, |
|
||||
Content = new DevTools |
|
||||
{ |
|
||||
Root = (Window)sender, |
|
||||
}, |
|
||||
}; |
|
||||
|
|
||||
window.Show(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void InitializeComponent() |
|
||||
{ |
|
||||
DataTemplates.Add(new ViewLocator<ReactiveObject>()); |
|
||||
Styles.Add(new DefaultTheme()); |
|
||||
|
|
||||
Child = new Grid |
|
||||
{ |
|
||||
RowDefinitions = new RowDefinitions("*,Auto"), |
|
||||
Children = new Controls.Controls |
|
||||
{ |
|
||||
new TabControl |
|
||||
{ |
|
||||
Items = new[] |
|
||||
{ |
|
||||
new TabItem |
|
||||
{ |
|
||||
Header = "Logical Tree", |
|
||||
[!ContentControl.ContentProperty] = _viewModel.WhenAnyValue(x => x.LogicalTree), |
|
||||
}, |
|
||||
new TabItem |
|
||||
{ |
|
||||
Header = "Visual Tree", |
|
||||
[!ContentControl.ContentProperty] = _viewModel.WhenAnyValue(x => x.VisualTree), |
|
||||
} |
|
||||
}, |
|
||||
}, |
|
||||
new StackPanel |
|
||||
{ |
|
||||
Orientation = Orientation.Horizontal, |
|
||||
Gap = 4, |
|
||||
[Grid.RowProperty] = 1, |
|
||||
Children = new Controls.Controls |
|
||||
{ |
|
||||
new TextBlock |
|
||||
{ |
|
||||
Text = "Focused: " |
|
||||
}, |
|
||||
new TextBlock |
|
||||
{ |
|
||||
[!TextBlock.TextProperty] = _viewModel |
|
||||
.WhenAnyValue(x => x.FocusedControl) |
|
||||
.Select(x => x?.GetType().Name ?? "(null)") |
|
||||
}, |
|
||||
new TextBlock |
|
||||
{ |
|
||||
Text = "Pointer Over: " |
|
||||
}, |
|
||||
new TextBlock |
|
||||
{ |
|
||||
[!TextBlock.TextProperty] = _viewModel |
|
||||
.WhenAnyValue(x => x.PointerOverElement) |
|
||||
.Select(x => x?.GetType().Name ?? "(null)") |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,18 @@ |
|||||
|
<UserControl xmlns="https://github.com/perspex"> |
||||
|
<Grid RowDefinitions="Auto,*,Auto"> |
||||
|
<TabStrip SelectedIndex="{Binding SelectedTab, Mode=TwoWay}"> |
||||
|
<TabStripItem Content="Logical Tree"/> |
||||
|
<TabStripItem Content="Visual Tree"/> |
||||
|
</TabStrip> |
||||
|
|
||||
|
<ContentControl Content="{Binding Content}" Grid.Row="1"/> |
||||
|
|
||||
|
<StackPanel Gap="4" Orientation="Horizontal" Grid.Row="2"> |
||||
|
<TextBlock>Focused:</TextBlock> |
||||
|
<TextBlock Text="{Binding FocusedControl}"/> |
||||
|
<Separator/> |
||||
|
<TextBlock>Pointer Over:</TextBlock> |
||||
|
<TextBlock Text="{Binding PointerOverElement}"/> |
||||
|
</StackPanel> |
||||
|
</Grid> |
||||
|
</UserControl> |
||||
@ -0,0 +1,92 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Perspex.Controls; |
||||
|
using Perspex.Controls.Templates; |
||||
|
using Perspex.Diagnostics.ViewModels; |
||||
|
using Perspex.Input; |
||||
|
using Perspex.Interactivity; |
||||
|
using Perspex.Markup.Xaml; |
||||
|
using ReactiveUI; |
||||
|
|
||||
|
namespace Perspex.Diagnostics |
||||
|
{ |
||||
|
public class DevTools : UserControl |
||||
|
{ |
||||
|
private static Dictionary<Window, Window> s_open = new Dictionary<Window, Window>(); |
||||
|
|
||||
|
public DevTools(IControl root) |
||||
|
{ |
||||
|
InitializeComponent(); |
||||
|
Root = root; |
||||
|
DataContext = new DevToolsViewModel(root); |
||||
|
Root.PointerMoved += RootPointerMoved; |
||||
|
} |
||||
|
|
||||
|
public IControl Root { get; } |
||||
|
|
||||
|
public static IDisposable Attach(Window window) |
||||
|
{ |
||||
|
return window.AddHandler( |
||||
|
KeyDownEvent, |
||||
|
WindowPreviewKeyDown, |
||||
|
RoutingStrategies.Tunnel); |
||||
|
} |
||||
|
|
||||
|
private static void WindowPreviewKeyDown(object sender, KeyEventArgs e) |
||||
|
{ |
||||
|
if (e.Key == Key.F12) |
||||
|
{ |
||||
|
var window = (Window)sender; |
||||
|
var devToolsWindow = default(Window); |
||||
|
|
||||
|
if (s_open.TryGetValue(window, out devToolsWindow)) |
||||
|
{ |
||||
|
devToolsWindow.Activate(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
devToolsWindow = new Window |
||||
|
{ |
||||
|
Width = 1024, |
||||
|
Height = 512, |
||||
|
Content = new DevTools(window), |
||||
|
DataTemplates = new DataTemplates |
||||
|
{ |
||||
|
new ViewLocator<ReactiveObject>(), |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
devToolsWindow.Closed += DevToolsClosed; |
||||
|
s_open.Add((Window)sender, devToolsWindow); |
||||
|
devToolsWindow.Show(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void DevToolsClosed(object sender, EventArgs e) |
||||
|
{ |
||||
|
var devToolsWindow = (Window)sender; |
||||
|
var devTools = (DevTools)devToolsWindow.Content; |
||||
|
var window = (Window)devTools.Root; |
||||
|
|
||||
|
s_open.Remove(window); |
||||
|
devToolsWindow.Closed -= DevToolsClosed; |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
PerspexXamlLoader.Load(this); |
||||
|
} |
||||
|
|
||||
|
private void RootPointerMoved(object sender, PointerEventArgs e) |
||||
|
{ |
||||
|
var modifiers = InputModifiers.Control | InputModifiers.Shift; |
||||
|
|
||||
|
if ((e.InputModifiers & modifiers) == modifiers) |
||||
|
{ |
||||
|
var vm = (DevToolsViewModel)DataContext; |
||||
|
vm.SelectControl((IControl)e.Source); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,34 +0,0 @@ |
|||||
// Copyright (c) The Perspex Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using System.Reactive.Linq; |
|
||||
using Perspex.Controls; |
|
||||
using ReactiveUI; |
|
||||
|
|
||||
namespace Perspex.Diagnostics.ViewModels |
|
||||
{ |
|
||||
internal class LogicalTreeViewModel : ReactiveObject |
|
||||
{ |
|
||||
private LogicalTreeNode _selected; |
|
||||
|
|
||||
private readonly ObservableAsPropertyHelper<ControlDetailsViewModel> _details; |
|
||||
|
|
||||
public LogicalTreeViewModel(Control root) |
|
||||
{ |
|
||||
Nodes = LogicalTreeNode.Create(root); |
|
||||
_details = this.WhenAnyValue(x => x.SelectedNode) |
|
||||
.Select(x => x != null ? new ControlDetailsViewModel(x.Control) : null) |
|
||||
.ToProperty(this, x => x.Details); |
|
||||
} |
|
||||
|
|
||||
public LogicalTreeNode[] Nodes { get; } |
|
||||
|
|
||||
public LogicalTreeNode SelectedNode |
|
||||
{ |
|
||||
get { return _selected; } |
|
||||
set { this.RaiseAndSetIfChanged(ref _selected, value); } |
|
||||
} |
|
||||
|
|
||||
public ControlDetailsViewModel Details => _details.Value; |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,102 @@ |
|||||
|
// Copyright (c) The Perspex Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System.Reactive.Linq; |
||||
|
using Perspex.Controls; |
||||
|
using Perspex.VisualTree; |
||||
|
using ReactiveUI; |
||||
|
|
||||
|
namespace Perspex.Diagnostics.ViewModels |
||||
|
{ |
||||
|
internal class TreePageViewModel : ReactiveObject |
||||
|
{ |
||||
|
private TreeNode _selected; |
||||
|
|
||||
|
private readonly ObservableAsPropertyHelper<ControlDetailsViewModel> _details; |
||||
|
|
||||
|
public TreePageViewModel(TreeNode[] nodes) |
||||
|
{ |
||||
|
Nodes = nodes; |
||||
|
_details = this.WhenAnyValue(x => x.SelectedNode) |
||||
|
.Select(x => x != null ? new ControlDetailsViewModel(x.Control) : null) |
||||
|
.ToProperty(this, x => x.Details); |
||||
|
} |
||||
|
|
||||
|
public TreeNode[] Nodes { get; protected set; } |
||||
|
|
||||
|
public TreeNode SelectedNode |
||||
|
{ |
||||
|
get { return _selected; } |
||||
|
set { this.RaiseAndSetIfChanged(ref _selected, value); } |
||||
|
} |
||||
|
|
||||
|
public ControlDetailsViewModel Details => _details.Value; |
||||
|
|
||||
|
public TreeNode FindNode(IControl control) |
||||
|
{ |
||||
|
foreach (var node in Nodes) |
||||
|
{ |
||||
|
var result = FindNode(node, control); |
||||
|
|
||||
|
if (result != null) |
||||
|
{ |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public void SelectControl(IControl control) |
||||
|
{ |
||||
|
var node = default(TreeNode); |
||||
|
|
||||
|
while (node == null && control != null) |
||||
|
{ |
||||
|
node = FindNode(control); |
||||
|
|
||||
|
if (node == null) |
||||
|
{ |
||||
|
control = control.GetVisualParent<IControl>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (node != null) |
||||
|
{ |
||||
|
SelectedNode = node; |
||||
|
ExpandNode(node.Parent); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void ExpandNode(TreeNode node) |
||||
|
{ |
||||
|
if (node != null) |
||||
|
{ |
||||
|
node.IsExpanded = true; |
||||
|
ExpandNode(node.Parent); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private TreeNode FindNode(TreeNode node, IControl control) |
||||
|
{ |
||||
|
if (node.Control == control) |
||||
|
{ |
||||
|
return node; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
foreach (var child in node.Children) |
||||
|
{ |
||||
|
var result = FindNode(child, control); |
||||
|
|
||||
|
if (result != null) |
||||
|
{ |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,34 +0,0 @@ |
|||||
// Copyright (c) The Perspex Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using System.Reactive.Linq; |
|
||||
using Perspex.Controls; |
|
||||
using ReactiveUI; |
|
||||
|
|
||||
namespace Perspex.Diagnostics.ViewModels |
|
||||
{ |
|
||||
internal class VisualTreeViewModel : ReactiveObject |
|
||||
{ |
|
||||
private VisualTreeNode _selected; |
|
||||
|
|
||||
private readonly ObservableAsPropertyHelper<ControlDetailsViewModel> _details; |
|
||||
|
|
||||
public VisualTreeViewModel(Control root) |
|
||||
{ |
|
||||
Nodes = VisualTreeNode.Create(root); |
|
||||
_details = this.WhenAnyValue(x => x.SelectedNode) |
|
||||
.Select(x => x != null ? new ControlDetailsViewModel(x.Control) : null) |
|
||||
.ToProperty(this, x => x.Details); |
|
||||
} |
|
||||
|
|
||||
public VisualTreeNode[] Nodes { get; } |
|
||||
|
|
||||
public VisualTreeNode SelectedNode |
|
||||
{ |
|
||||
get { return _selected; } |
|
||||
set { this.RaiseAndSetIfChanged(ref _selected, value); } |
|
||||
} |
|
||||
|
|
||||
public ControlDetailsViewModel Details => _details.Value; |
|
||||
} |
|
||||
} |
|
||||
@ -1,99 +0,0 @@ |
|||||
// Copyright (c) The Perspex Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Reactive.Linq; |
|
||||
using Perspex.Controls; |
|
||||
using Perspex.Controls.Templates; |
|
||||
using Perspex.Diagnostics.ViewModels; |
|
||||
using ReactiveUI; |
|
||||
|
|
||||
namespace Perspex.Diagnostics.Views |
|
||||
{ |
|
||||
using Controls = Controls.Controls; |
|
||||
|
|
||||
internal class LogicalTreeView : TreePage |
|
||||
{ |
|
||||
private static readonly PerspexProperty<LogicalTreeViewModel> ViewModelProperty = |
|
||||
PerspexProperty.Register<LogicalTreeView, LogicalTreeViewModel>("ViewModel"); |
|
||||
|
|
||||
public LogicalTreeView() |
|
||||
{ |
|
||||
InitializeComponent(); |
|
||||
this.GetObservable(DataContextProperty) |
|
||||
.Subscribe(x => ViewModel = (LogicalTreeViewModel)x); |
|
||||
} |
|
||||
|
|
||||
public LogicalTreeViewModel ViewModel |
|
||||
{ |
|
||||
get { return GetValue(ViewModelProperty); } |
|
||||
private set { SetValue(ViewModelProperty, value); } |
|
||||
} |
|
||||
|
|
||||
private void InitializeComponent() |
|
||||
{ |
|
||||
TreeView tree; |
|
||||
|
|
||||
Content = new Grid |
|
||||
{ |
|
||||
ColumnDefinitions = new ColumnDefinitions |
|
||||
{ |
|
||||
new ColumnDefinition(1, GridUnitType.Star), |
|
||||
new ColumnDefinition(4, GridUnitType.Pixel), |
|
||||
new ColumnDefinition(3, GridUnitType.Star), |
|
||||
}, |
|
||||
Children = new Controls |
|
||||
{ |
|
||||
(tree = new TreeView |
|
||||
{ |
|
||||
DataTemplates = new DataTemplates |
|
||||
{ |
|
||||
new FuncTreeDataTemplate<LogicalTreeNode>(GetHeader, x => x.Children), |
|
||||
}, |
|
||||
[!ItemsControl.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Nodes), |
|
||||
}), |
|
||||
new GridSplitter |
|
||||
{ |
|
||||
Width = 4, |
|
||||
Orientation = Orientation.Vertical, |
|
||||
[Grid.ColumnProperty] = 1, |
|
||||
}, |
|
||||
new ContentControl |
|
||||
{ |
|
||||
[!ContentProperty] = this.WhenAnyValue(x => x.ViewModel.Details), |
|
||||
[Grid.ColumnProperty] = 2, |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
tree.GetObservable(TreeView.SelectedItemProperty) |
|
||||
.OfType<LogicalTreeNode>() |
|
||||
.Subscribe(x => ViewModel.SelectedNode = x); |
|
||||
} |
|
||||
|
|
||||
private Control GetHeader(LogicalTreeNode node) |
|
||||
{ |
|
||||
var result = new StackPanel |
|
||||
{ |
|
||||
Orientation = Orientation.Horizontal, |
|
||||
Gap = 8, |
|
||||
Children = new Controls |
|
||||
{ |
|
||||
new TextBlock |
|
||||
{ |
|
||||
Text = node.Type, |
|
||||
}, |
|
||||
new TextBlock |
|
||||
{ |
|
||||
[!TextBlock.TextProperty] = node.WhenAnyValue(x => x.Classes), |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
result.PointerEnter += AddAdorner; |
|
||||
result.PointerLeave += RemoveAdorner; |
|
||||
|
|
||||
return result; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,43 +0,0 @@ |
|||||
// Copyright (c) The Perspex Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using Perspex.Controls; |
|
||||
using Perspex.Controls.Primitives; |
|
||||
using Perspex.Controls.Shapes; |
|
||||
using Perspex.Diagnostics.ViewModels; |
|
||||
using Perspex.Input; |
|
||||
using Perspex.Media; |
|
||||
|
|
||||
namespace Perspex.Diagnostics.Views |
|
||||
{ |
|
||||
internal class TreePage : UserControl |
|
||||
{ |
|
||||
private Control _adorner; |
|
||||
|
|
||||
protected void AddAdorner(object sender, PointerEventArgs e) |
|
||||
{ |
|
||||
var node = (TreeNode)((Control)sender).DataContext; |
|
||||
var layer = AdornerLayer.GetAdornerLayer(node.Control); |
|
||||
|
|
||||
if (layer != null) |
|
||||
{ |
|
||||
_adorner = new Rectangle |
|
||||
{ |
|
||||
Fill = new SolidColorBrush(0x80a0c5e8), |
|
||||
[AdornerLayer.AdornedElementProperty] = node.Control, |
|
||||
}; |
|
||||
|
|
||||
layer.Children.Add(_adorner); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected void RemoveAdorner(object sender, PointerEventArgs e) |
|
||||
{ |
|
||||
if (_adorner != null) |
|
||||
{ |
|
||||
((Panel)_adorner.Parent).Children.Remove(_adorner); |
|
||||
_adorner = null; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,70 @@ |
|||||
|
using Perspex.Controls; |
||||
|
using Perspex.Controls.Generators; |
||||
|
using Perspex.Controls.Primitives; |
||||
|
using Perspex.Controls.Shapes; |
||||
|
using Perspex.Diagnostics.ViewModels; |
||||
|
using Perspex.Input; |
||||
|
using Perspex.Markup.Xaml; |
||||
|
using Perspex.Media; |
||||
|
|
||||
|
namespace Perspex.Diagnostics.Views |
||||
|
{ |
||||
|
public class TreePageView : UserControl |
||||
|
{ |
||||
|
private Control _adorner; |
||||
|
private TreeView _tree; |
||||
|
|
||||
|
public TreePageView() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
_tree.ItemContainerGenerator.Index.Materialized += TreeViewItemMaterialized; |
||||
|
} |
||||
|
|
||||
|
protected void AddAdorner(object sender, PointerEventArgs e) |
||||
|
{ |
||||
|
var node = (TreeNode)((Control)sender).DataContext; |
||||
|
var layer = AdornerLayer.GetAdornerLayer(node.Control); |
||||
|
|
||||
|
if (layer != null) |
||||
|
{ |
||||
|
_adorner = new Rectangle |
||||
|
{ |
||||
|
Fill = new SolidColorBrush(0x80a0c5e8), |
||||
|
[AdornerLayer.AdornedElementProperty] = node.Control, |
||||
|
}; |
||||
|
|
||||
|
layer.Children.Add(_adorner); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected void RemoveAdorner(object sender, PointerEventArgs e) |
||||
|
{ |
||||
|
if (_adorner != null) |
||||
|
{ |
||||
|
((Panel)_adorner.Parent).Children.Remove(_adorner); |
||||
|
_adorner = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
PerspexXamlLoader.Load(this); |
||||
|
_tree = this.FindControl<TreeView>("tree"); |
||||
|
} |
||||
|
|
||||
|
private void TreeViewItemMaterialized(object sender, ItemContainerEventArgs e) |
||||
|
{ |
||||
|
var item = (TreeViewItem)e.Containers[0].ContainerControl; |
||||
|
item.TemplateApplied += TreeViewItemTemplateApplied; |
||||
|
} |
||||
|
|
||||
|
private void TreeViewItemTemplateApplied(object sender, TemplateAppliedEventArgs e) |
||||
|
{ |
||||
|
var item = (TreeViewItem)sender; |
||||
|
var header = item.HeaderPresenter.Child; |
||||
|
header.PointerEnter += AddAdorner; |
||||
|
header.PointerLeave += RemoveAdorner; |
||||
|
item.TemplateApplied -= TreeViewItemTemplateApplied; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
<UserControl xmlns="https://github.com/perspex" |
||||
|
xmlns:vm="clr-namespace:Perspex.Diagnostics.ViewModels;assembly=Perspex.Diagnostics"> |
||||
|
<Grid ColumnDefinitions="*,4,3*"> |
||||
|
<TreeView Name="tree" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}"> |
||||
|
<TreeView.DataTemplates> |
||||
|
<TreeDataTemplate DataType="vm:TreeNode" |
||||
|
ItemsSource="{Binding Children}"> |
||||
|
<StackPanel Orientation="Horizontal" Gap="8"> |
||||
|
<TextBlock Text="{Binding Type}"/> |
||||
|
<TextBlock Text="{Binding Classes}"/> |
||||
|
</StackPanel> |
||||
|
</TreeDataTemplate> |
||||
|
</TreeView.DataTemplates> |
||||
|
<TreeView.Styles> |
||||
|
<Style Selector="TreeViewItem"> |
||||
|
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/> |
||||
|
</Style> |
||||
|
</TreeView.Styles> |
||||
|
</TreeView> |
||||
|
|
||||
|
<GridSplitter Width="4" Orientation="Vertical" Grid.Column="1"/> |
||||
|
<ContentControl Content="{Binding Details}" Grid.Column="2"/> |
||||
|
</Grid> |
||||
|
</UserControl> |
||||
@ -1,101 +0,0 @@ |
|||||
// Copyright (c) The Perspex Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Reactive.Linq; |
|
||||
using Perspex.Controls; |
|
||||
using Perspex.Controls.Templates; |
|
||||
using Perspex.Diagnostics.ViewModels; |
|
||||
using Perspex.Media; |
|
||||
using ReactiveUI; |
|
||||
|
|
||||
namespace Perspex.Diagnostics.Views |
|
||||
{ |
|
||||
using Controls = Controls.Controls; |
|
||||
|
|
||||
internal class VisualTreeView : TreePage |
|
||||
{ |
|
||||
private static readonly PerspexProperty<VisualTreeViewModel> ViewModelProperty = |
|
||||
PerspexProperty.Register<VisualTreeView, VisualTreeViewModel>("ViewModel"); |
|
||||
|
|
||||
public VisualTreeView() |
|
||||
{ |
|
||||
InitializeComponent(); |
|
||||
this.GetObservable(DataContextProperty) |
|
||||
.Subscribe(x => ViewModel = (VisualTreeViewModel)x); |
|
||||
} |
|
||||
|
|
||||
public VisualTreeViewModel ViewModel |
|
||||
{ |
|
||||
get { return GetValue(ViewModelProperty); } |
|
||||
private set { SetValue(ViewModelProperty, value); } |
|
||||
} |
|
||||
|
|
||||
private void InitializeComponent() |
|
||||
{ |
|
||||
TreeView tree; |
|
||||
|
|
||||
Content = new Grid |
|
||||
{ |
|
||||
ColumnDefinitions = new ColumnDefinitions |
|
||||
{ |
|
||||
new ColumnDefinition(1, GridUnitType.Star), |
|
||||
new ColumnDefinition(4, GridUnitType.Pixel), |
|
||||
new ColumnDefinition(3, GridUnitType.Star), |
|
||||
}, |
|
||||
Children = new Controls |
|
||||
{ |
|
||||
(tree = new TreeView |
|
||||
{ |
|
||||
DataTemplates = new DataTemplates |
|
||||
{ |
|
||||
new FuncTreeDataTemplate<VisualTreeNode>(GetHeader, x => x.Children), |
|
||||
}, |
|
||||
[!ItemsControl.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Nodes), |
|
||||
}), |
|
||||
new GridSplitter |
|
||||
{ |
|
||||
Width = 4, |
|
||||
Orientation = Orientation.Vertical, |
|
||||
[Grid.ColumnProperty] = 1, |
|
||||
}, |
|
||||
new ContentControl |
|
||||
{ |
|
||||
[!ContentProperty] = this.WhenAnyValue(x => x.ViewModel.Details), |
|
||||
[Grid.ColumnProperty] = 2, |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
tree.GetObservable(TreeView.SelectedItemProperty) |
|
||||
.OfType<VisualTreeNode>() |
|
||||
.Subscribe(x => ViewModel.SelectedNode = x); |
|
||||
} |
|
||||
|
|
||||
private Control GetHeader(VisualTreeNode node) |
|
||||
{ |
|
||||
var result = new StackPanel |
|
||||
{ |
|
||||
Orientation = Orientation.Horizontal, |
|
||||
Gap = 8, |
|
||||
Children = new Controls |
|
||||
{ |
|
||||
new TextBlock |
|
||||
{ |
|
||||
FontStyle = node.IsInTemplate ? FontStyle.Italic : FontStyle.Normal, |
|
||||
Text = node.Type, |
|
||||
}, |
|
||||
new TextBlock |
|
||||
{ |
|
||||
[!TextBlock.TextProperty] = node.WhenAnyValue(x => x.Classes), |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
result.PointerEnter += AddAdorner; |
|
||||
result.PointerLeave += RemoveAdorner; |
|
||||
|
|
||||
return result; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,7 +1,13 @@ |
|||||
#!/bin/sh |
#!/bin/sh |
||||
rm -rf native |
rm -rf native native.zip |
||||
mkdir -p native |
mkdir -p native |
||||
cd native |
cd native |
||||
|
if which curl |
||||
|
then |
||||
|
curl `cat ../native.url` -o native.zip |
||||
|
else |
||||
wget `cat ../native.url` -O native.zip |
wget `cat ../native.url` -O native.zip |
||||
unzip native.zip |
fi |
||||
|
|
||||
|
unzip native.zip |
||||
|
chmod -R +x . |
||||
|
|||||
@ -0,0 +1,62 @@ |
|||||
|
// Copyright (c) The Perspex Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Perspex.Controls.UnitTests |
||||
|
{ |
||||
|
public class DockPanelTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_Dock_Controls_Horizontal_First() |
||||
|
{ |
||||
|
var target = new DockPanel |
||||
|
{ |
||||
|
Children = new Controls |
||||
|
{ |
||||
|
new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Top }, |
||||
|
new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Bottom }, |
||||
|
new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Left }, |
||||
|
new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Right }, |
||||
|
new Border { }, |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
target.Measure(Size.Infinity); |
||||
|
target.Arrange(new Rect(target.DesiredSize)); |
||||
|
|
||||
|
Assert.Equal(new Rect(0, 0, 500, 500), target.Bounds); |
||||
|
Assert.Equal(new Rect(0, 0, 500, 50), target.Children[0].Bounds); |
||||
|
Assert.Equal(new Rect(0, 450, 500, 50), target.Children[1].Bounds); |
||||
|
Assert.Equal(new Rect(0, 50, 50, 400), target.Children[2].Bounds); |
||||
|
Assert.Equal(new Rect(450, 50, 50, 400), target.Children[3].Bounds); |
||||
|
Assert.Equal(new Rect(50, 50, 400, 400), target.Children[4].Bounds); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Dock_Controls_Vertical_First() |
||||
|
{ |
||||
|
var target = new DockPanel |
||||
|
{ |
||||
|
Children = new Controls |
||||
|
{ |
||||
|
new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Left }, |
||||
|
new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Right }, |
||||
|
new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Top }, |
||||
|
new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Bottom }, |
||||
|
new Border { }, |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
target.Measure(Size.Infinity); |
||||
|
target.Arrange(new Rect(target.DesiredSize)); |
||||
|
|
||||
|
Assert.Equal(new Rect(0, 0, 600, 400), target.Bounds); |
||||
|
Assert.Equal(new Rect(0, 0, 50, 400), target.Children[0].Bounds); |
||||
|
Assert.Equal(new Rect(550, 0, 50, 400), target.Children[1].Bounds); |
||||
|
Assert.Equal(new Rect(50, 0, 500, 50), target.Children[2].Bounds); |
||||
|
Assert.Equal(new Rect(50, 350, 500, 50), target.Children[3].Bounds); |
||||
|
Assert.Equal(new Rect(50, 50, 500, 300), target.Children[4].Bounds); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue