Display flow arrows when selecting a path
This commit is contained in:
@@ -433,9 +433,12 @@ namespace PamhagenSysCtrl.Helpers {
|
|||||||
|
|
||||||
public void ColorPipesAdjacientToPath(Path path, Brush? color1, Brush? color2) {
|
public void ColorPipesAdjacientToPath(Path path, Brush? color1, Brush? color2) {
|
||||||
var color = color1;
|
var color = color1;
|
||||||
|
var flowPathOffset = 0.0;
|
||||||
HashSet<IEdge> visited = [];
|
HashSet<IEdge> visited = [];
|
||||||
foreach (var (edge, node) in path.Hops) {
|
foreach (var (edge, node) in path.Hops) {
|
||||||
edge.Highlight = color1;
|
edge.Highlight = color1;
|
||||||
|
edge.Flow = color1 == PamhagenBrushes.Green ? (edge.End == node, flowPathOffset) : null;
|
||||||
|
if (edge.Flow != null) flowPathOffset += edge.FlowLength;
|
||||||
visited.Add(edge);
|
visited.Add(edge);
|
||||||
if (node is Pump) color = color2;
|
if (node is Pump) color = color2;
|
||||||
TraverseSubgraph(node, visited, edgeAction: (e) => e.Highlight = color);
|
TraverseSubgraph(node, visited, edgeAction: (e) => e.Highlight = color);
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace PamhagenSysCtrl.Helpers.Pipeline {
|
||||||
|
internal sealed class FlowArrow {
|
||||||
|
|
||||||
|
private const double Speed = 45;
|
||||||
|
private const double Spacing = 40;
|
||||||
|
private const double HalfLength = 5;
|
||||||
|
private const double HalfWidth = 3.5;
|
||||||
|
|
||||||
|
private static readonly Stopwatch Clock = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
private readonly Canvas _layer;
|
||||||
|
private readonly Point[] _path;
|
||||||
|
private Point[] _points = [];
|
||||||
|
private double _length;
|
||||||
|
private double _pathOffset;
|
||||||
|
private bool _isActive;
|
||||||
|
|
||||||
|
public double Length => GetLength(_path[0], _path[1]) + GetLength(_path[1], _path[2]);
|
||||||
|
|
||||||
|
private FlowArrow(Canvas canvas, Point[] path) {
|
||||||
|
_path = path;
|
||||||
|
_layer = new Canvas() {
|
||||||
|
IsHitTestVisible = false,
|
||||||
|
Visibility = Visibility.Collapsed,
|
||||||
|
};
|
||||||
|
_layer.Unloaded += (_, _) => Stop();
|
||||||
|
canvas.Children.Add(_layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FlowArrow Create(Canvas canvas, Line first, Line second) {
|
||||||
|
return new FlowArrow(canvas, [
|
||||||
|
new Point(first.X1, first.Y1),
|
||||||
|
new Point(first.X2, first.Y2),
|
||||||
|
new Point(second.X2, second.Y2),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FlowArrow Create(Canvas canvas, Polyline path) {
|
||||||
|
return new FlowArrow(canvas, [.. path.Points]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update((bool TowardEnd, double PathOffset)? flow) {
|
||||||
|
if (flow == null) {
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var points = flow.Value.TowardEnd
|
||||||
|
? _path
|
||||||
|
: [.. _path.Reverse()];
|
||||||
|
var length = GetLength(points[0], points[1]) + GetLength(points[1], points[2]);
|
||||||
|
if (length < 16) {
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_points = points;
|
||||||
|
_length = length;
|
||||||
|
_pathOffset = flow.Value.PathOffset;
|
||||||
|
if (!_isActive) {
|
||||||
|
CompositionTarget.Rendering += OnRendering;
|
||||||
|
_isActive = true;
|
||||||
|
}
|
||||||
|
_layer.Visibility = Visibility.Visible;
|
||||||
|
Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRendering(object? sender, EventArgs args) {
|
||||||
|
Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Render() {
|
||||||
|
var distance = PositiveModulo(Clock.Elapsed.TotalSeconds * Speed - _pathOffset, Spacing);
|
||||||
|
var arrowIndex = 0;
|
||||||
|
while (distance <= _length) {
|
||||||
|
var arrow = GetArrow(arrowIndex++);
|
||||||
|
SetPosition(arrow, distance);
|
||||||
|
arrow.Visibility = Visibility.Visible;
|
||||||
|
distance += Spacing;
|
||||||
|
}
|
||||||
|
while (arrowIndex < _layer.Children.Count) {
|
||||||
|
_layer.Children[arrowIndex++].Visibility = Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Polygon GetArrow(int index) {
|
||||||
|
if (index < _layer.Children.Count) {
|
||||||
|
return (Polygon)_layer.Children[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
var arrow = new Polygon() {
|
||||||
|
Fill = Brushes.Black,
|
||||||
|
IsHitTestVisible = false,
|
||||||
|
Points = [
|
||||||
|
new Point(HalfLength, 0),
|
||||||
|
new Point(-HalfLength, HalfWidth),
|
||||||
|
new Point(-HalfLength, -HalfWidth),
|
||||||
|
],
|
||||||
|
RenderTransform = new MatrixTransform(Matrix.Identity),
|
||||||
|
};
|
||||||
|
_layer.Children.Add(arrow);
|
||||||
|
return arrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetPosition(Polygon arrow, double distance) {
|
||||||
|
var firstLength = GetLength(_points[0], _points[1]);
|
||||||
|
var secondLength = GetLength(_points[1], _points[2]);
|
||||||
|
Point start;
|
||||||
|
Point end;
|
||||||
|
double segmentDistance;
|
||||||
|
if ((firstLength > 0 && distance <= firstLength) || secondLength == 0) {
|
||||||
|
start = _points[0];
|
||||||
|
end = _points[1];
|
||||||
|
segmentDistance = distance;
|
||||||
|
} else {
|
||||||
|
start = _points[1];
|
||||||
|
end = _points[2];
|
||||||
|
segmentDistance = distance - firstLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
var segmentLength = GetLength(start, end);
|
||||||
|
if (segmentLength == 0) return;
|
||||||
|
var ux = (end.X - start.X) / segmentLength;
|
||||||
|
var uy = (end.Y - start.Y) / segmentLength;
|
||||||
|
var x = start.X + ux * segmentDistance;
|
||||||
|
var y = start.Y + uy * segmentDistance;
|
||||||
|
((MatrixTransform)arrow.RenderTransform).Matrix = new Matrix(
|
||||||
|
ux, uy, -uy, ux, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Stop() {
|
||||||
|
if (_isActive) {
|
||||||
|
CompositionTarget.Rendering -= OnRendering;
|
||||||
|
_isActive = false;
|
||||||
|
}
|
||||||
|
_layer.Visibility = Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double PositiveModulo(double value, double modulus) {
|
||||||
|
return (value % modulus + modulus) % modulus;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double GetLength(Point start, Point end) {
|
||||||
|
var dx = end.X - start.X;
|
||||||
|
var dy = end.Y - start.Y;
|
||||||
|
return Math.Sqrt(dx * dx + dy * dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
|
|||||||
public bool IsTwoWay { get; }
|
public bool IsTwoWay { get; }
|
||||||
public bool Orientation { get; }
|
public bool Orientation { get; }
|
||||||
public Brush? Highlight { get; set; }
|
public Brush? Highlight { get; set; }
|
||||||
|
public (bool TowardEnd, double PathOffset)? Flow { get; set; }
|
||||||
|
public double FlowLength { get; }
|
||||||
|
|
||||||
public void Draw(Canvas canvas);
|
public void Draw(Canvas canvas);
|
||||||
public void Update();
|
public void Update();
|
||||||
|
|||||||
@@ -11,11 +11,14 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
|
|||||||
public bool IsTwoWay => false;
|
public bool IsTwoWay => false;
|
||||||
public bool Orientation { get; set; }
|
public bool Orientation { get; set; }
|
||||||
public Brush? Highlight { get; set; }
|
public Brush? Highlight { get; set; }
|
||||||
|
public (bool TowardEnd, double PathOffset)? Flow { get; set; }
|
||||||
|
public double FlowLength => _flowArrow?.Length ?? 0;
|
||||||
|
|
||||||
private Line? _outer1;
|
private Line? _outer1;
|
||||||
private Line? _outer2;
|
private Line? _outer2;
|
||||||
private Line? _inner1;
|
private Line? _inner1;
|
||||||
private Line? _inner2;
|
private Line? _inner2;
|
||||||
|
private FlowArrow? _flowArrow;
|
||||||
|
|
||||||
public Inlet(INode start, ISink end, bool orientation = false) {
|
public Inlet(INode start, ISink end, bool orientation = false) {
|
||||||
Start = start;
|
Start = start;
|
||||||
@@ -74,11 +77,13 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
|
|||||||
canvas.Children.Add(_outer2);
|
canvas.Children.Add(_outer2);
|
||||||
canvas.Children.Add(_inner1);
|
canvas.Children.Add(_inner1);
|
||||||
canvas.Children.Add(_inner2);
|
canvas.Children.Add(_inner2);
|
||||||
|
_flowArrow = FlowArrow.Create(canvas, _inner1!, _inner2!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update() {
|
public void Update() {
|
||||||
_inner1?.Stroke = Highlight ?? Brushes.DarkGray;
|
_inner1?.Stroke = Highlight ?? Brushes.DarkGray;
|
||||||
_inner2?.Stroke = Highlight ?? Brushes.DarkGray;
|
_inner2?.Stroke = Highlight ?? Brushes.DarkGray;
|
||||||
|
_flowArrow?.Update(Highlight == PamhagenBrushes.Green ? Flow : null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,11 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
|
|||||||
public bool IsTwoWay => false;
|
public bool IsTwoWay => false;
|
||||||
public bool Orientation { get; set; }
|
public bool Orientation { get; set; }
|
||||||
public Brush? Highlight { get; set; }
|
public Brush? Highlight { get; set; }
|
||||||
|
public (bool TowardEnd, double PathOffset)? Flow { get; set; }
|
||||||
|
public double FlowLength => _flowArrow?.Length ?? 0;
|
||||||
|
|
||||||
private Polygon? _hopper;
|
private Polygon? _hopper;
|
||||||
|
private FlowArrow? _flowArrow;
|
||||||
private Line? _outer1;
|
private Line? _outer1;
|
||||||
private Line? _outer2;
|
private Line? _outer2;
|
||||||
private Line? _inner1;
|
private Line? _inner1;
|
||||||
@@ -81,15 +84,17 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
|
|||||||
};
|
};
|
||||||
canvas.Children.Add(_outer1);
|
canvas.Children.Add(_outer1);
|
||||||
canvas.Children.Add(_outer2);
|
canvas.Children.Add(_outer2);
|
||||||
canvas.Children.Add(_hopper);
|
|
||||||
canvas.Children.Add(_inner1);
|
canvas.Children.Add(_inner1);
|
||||||
canvas.Children.Add(_inner2);
|
canvas.Children.Add(_inner2);
|
||||||
|
_flowArrow = FlowArrow.Create(canvas, _inner1!, _inner2!);
|
||||||
|
canvas.Children.Add(_hopper);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update() {
|
public void Update() {
|
||||||
_inner1?.Stroke = Highlight ?? Brushes.DarkGray;
|
_inner1?.Stroke = Highlight ?? Brushes.DarkGray;
|
||||||
_inner2?.Stroke = Highlight ?? Brushes.DarkGray;
|
_inner2?.Stroke = Highlight ?? Brushes.DarkGray;
|
||||||
_hopper?.Fill = Highlight ?? Brushes.DarkGray;
|
_hopper?.Fill = Highlight ?? Brushes.DarkGray;
|
||||||
|
_flowArrow?.Update(Highlight == PamhagenBrushes.Green ? Flow : null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
|
|||||||
public bool IsTwoWay { get; set; }
|
public bool IsTwoWay { get; set; }
|
||||||
public bool Orientation { get; set; }
|
public bool Orientation { get; set; }
|
||||||
public Brush? Highlight { get; set; }
|
public Brush? Highlight { get; set; }
|
||||||
|
public (bool TowardEnd, double PathOffset)? Flow { get; set; }
|
||||||
|
public double FlowLength => _flowArrow?.Length ?? 0;
|
||||||
|
|
||||||
private Polyline? _outer;
|
private Polyline? _outer;
|
||||||
private Polyline? _inner;
|
private Polyline? _inner;
|
||||||
|
private FlowArrow? _flowArrow;
|
||||||
|
|
||||||
public Pipe(INode start, INode end, bool orientation = false, bool twoWay = false) {
|
public Pipe(INode start, INode end, bool orientation = false, bool twoWay = false) {
|
||||||
Start = start;
|
Start = start;
|
||||||
@@ -46,10 +49,12 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
|
|||||||
};
|
};
|
||||||
canvas.Children.Add(_outer);
|
canvas.Children.Add(_outer);
|
||||||
canvas.Children.Add(_inner);
|
canvas.Children.Add(_inner);
|
||||||
|
_flowArrow = FlowArrow.Create(canvas, _inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update() {
|
public void Update() {
|
||||||
_inner?.Stroke = Highlight ?? Brushes.DarkGray;
|
_inner?.Stroke = Highlight ?? Brushes.DarkGray;
|
||||||
|
_flowArrow?.Update(Highlight == PamhagenBrushes.Green ? Flow : null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ namespace PamhagenSysCtrl.Windows {
|
|||||||
private ISet<INode> UpdateScheme(Point pos, bool down) {
|
private ISet<INode> UpdateScheme(Point pos, bool down) {
|
||||||
foreach (var e in Graph.Edges) {
|
foreach (var e in Graph.Edges) {
|
||||||
e.Highlight = null;
|
e.Highlight = null;
|
||||||
|
e.Flow = null;
|
||||||
}
|
}
|
||||||
foreach (var p in Graph.SelectedPaths) {
|
foreach (var p in Graph.SelectedPaths) {
|
||||||
Graph.ColorPipesAdjacientToPath(p, PamhagenBrushes.Green, PamhagenBrushes.DimOrange);
|
Graph.ColorPipesAdjacientToPath(p, PamhagenBrushes.Green, PamhagenBrushes.DimOrange);
|
||||||
@@ -96,6 +97,7 @@ namespace PamhagenSysCtrl.Windows {
|
|||||||
if (_lastSource != null) {
|
if (_lastSource != null) {
|
||||||
foreach (var edge in _lastSource.Outputs) {
|
foreach (var edge in _lastSource.Outputs) {
|
||||||
edge.Highlight = PamhagenBrushes.Green;
|
edge.Highlight = PamhagenBrushes.Green;
|
||||||
|
edge.Flow = (true, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Graph.Update(hover, down);
|
Graph.Update(hover, down);
|
||||||
|
|||||||
Reference in New Issue
Block a user