mirror of
https://github.com/egmont11/NetworkDiagram.git
synced 2026-07-24 07:09:43 +02:00
Compare commits
2
Commits
v1.4
..
924f90e86d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
924f90e86d | ||
|
|
b4346a1461 |
@@ -4,11 +4,10 @@
|
|||||||
Title="{DynamicResource ExportOptionsTitle}" Width="400" Height="180"
|
Title="{DynamicResource ExportOptionsTitle}" Width="400" Height="180"
|
||||||
WindowStartupLocation="CenterOwner" CanResize="False"
|
WindowStartupLocation="CenterOwner" CanResize="False"
|
||||||
Background="{DynamicResource BgWhite}">
|
Background="{DynamicResource BgWhite}">
|
||||||
<StackPanel Margin="20" Spacing="20">
|
<StackPanel Margin="20" Spacing="10">
|
||||||
<TextBlock Text="{DynamicResource ExportWhiteBgMsg}" TextWrapping="Wrap" HorizontalAlignment="Center" TextAlignment="Center"/>
|
<TextBlock Text="{DynamicResource ExportWhiteBgMsg}" TextWrapping="Wrap" HorizontalAlignment="Center" TextAlignment="Center" Margin="0,0,0,10"/>
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="20">
|
<Button Content="{DynamicResource ExportWhiteBtn}" Click="White_Click" HorizontalAlignment="Stretch" Classes="ModernButtonStyle"/>
|
||||||
<Button Content="Yes (White)" Click="White_Click" Width="100" Classes="ModernButtonStyle"/>
|
<Button Content="{DynamicResource ExportTransparentBtn}" Click="Transparent_Click" HorizontalAlignment="Stretch" Classes="SecondaryButtonStyle"/>
|
||||||
<Button Content="No (Transparent)" Click="Transparent_Click" Width="140" Classes="SecondaryButtonStyle"/>
|
<Button Content="{DynamicResource ExportBWBtn}" Click="BlackAndWhite_Click" HorizontalAlignment="Stretch" Classes="SecondaryButtonStyle"/>
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -3,6 +3,14 @@ using Avalonia.Interactivity;
|
|||||||
|
|
||||||
namespace NetworkDiagram
|
namespace NetworkDiagram
|
||||||
{
|
{
|
||||||
|
public enum ExportOption
|
||||||
|
{
|
||||||
|
Cancel,
|
||||||
|
Transparent,
|
||||||
|
White,
|
||||||
|
BlackAndWhite
|
||||||
|
}
|
||||||
|
|
||||||
public partial class ExportOptionsWindow : Window
|
public partial class ExportOptionsWindow : Window
|
||||||
{
|
{
|
||||||
public ExportOptionsWindow()
|
public ExportOptionsWindow()
|
||||||
@@ -12,12 +20,17 @@ namespace NetworkDiagram
|
|||||||
|
|
||||||
private void White_Click(object? sender, RoutedEventArgs e)
|
private void White_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Close(true);
|
Close(ExportOption.White);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Transparent_Click(object? sender, RoutedEventArgs e)
|
private void Transparent_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Close(false);
|
Close(ExportOption.Transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BlackAndWhite_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close(ExportOption.BlackAndWhite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,10 @@
|
|||||||
<x:String x:Key="SaveDialogBtn">Uložit</x:String>
|
<x:String x:Key="SaveDialogBtn">Uložit</x:String>
|
||||||
|
|
||||||
<!-- Messages -->
|
<!-- Messages -->
|
||||||
<x:String x:Key="ExportWhiteBgMsg">Chcete bílé pozadí? (Ne = průhlednost)</x:String>
|
<x:String x:Key="ExportWhiteBgMsg">Vyberte styl pozadí a barev:</x:String>
|
||||||
|
<x:String x:Key="ExportWhiteBtn">Bílé pozadí</x:String>
|
||||||
|
<x:String x:Key="ExportTransparentBtn">Průhledné pozadí</x:String>
|
||||||
|
<x:String x:Key="ExportBWBtn">Pro tisk (šedotisk)</x:String>
|
||||||
<x:String x:Key="ExportOptionsTitle">Možnosti exportu</x:String>
|
<x:String x:Key="ExportOptionsTitle">Možnosti exportu</x:String>
|
||||||
<x:String x:Key="ExportSuccess">Export byl úspěšný!</x:String>
|
<x:String x:Key="ExportSuccess">Export byl úspěšný!</x:String>
|
||||||
<x:String x:Key="EmptyDiagramMsg">Diagram je prázdný.</x:String>
|
<x:String x:Key="EmptyDiagramMsg">Diagram je prázdný.</x:String>
|
||||||
|
|||||||
@@ -19,7 +19,10 @@
|
|||||||
<x:String x:Key="SaveDialogBtn">Save</x:String>
|
<x:String x:Key="SaveDialogBtn">Save</x:String>
|
||||||
|
|
||||||
<!-- Messages -->
|
<!-- Messages -->
|
||||||
<x:String x:Key="ExportWhiteBgMsg">Do you want a white background? (No will result in transparency)</x:String>
|
<x:String x:Key="ExportWhiteBgMsg">Select export background and color style:</x:String>
|
||||||
|
<x:String x:Key="ExportWhiteBtn">White Background</x:String>
|
||||||
|
<x:String x:Key="ExportTransparentBtn">Transparent Background</x:String>
|
||||||
|
<x:String x:Key="ExportBWBtn">Printer Friendly (Grayscale)</x:String>
|
||||||
<x:String x:Key="ExportOptionsTitle">Export Options</x:String>
|
<x:String x:Key="ExportOptionsTitle">Export Options</x:String>
|
||||||
<x:String x:Key="ExportSuccess">Export successful!</x:String>
|
<x:String x:Key="ExportSuccess">Export successful!</x:String>
|
||||||
<x:String x:Key="EmptyDiagramMsg">Diagram is empty.</x:String>
|
<x:String x:Key="EmptyDiagramMsg">Diagram is empty.</x:String>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ using Avalonia.Media.Imaging;
|
|||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Avalonia.VisualTree;
|
using Avalonia.VisualTree;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using NetworkDiagram.Models;
|
using NetworkDiagram.Models;
|
||||||
using Avalonia.Controls.Templates;
|
using Avalonia.Controls.Templates;
|
||||||
|
|
||||||
@@ -342,7 +343,9 @@ namespace NetworkDiagram
|
|||||||
nameText.Bind(TextBlock.TextProperty, new Avalonia.Data.Binding("Name"));
|
nameText.Bind(TextBlock.TextProperty, new Avalonia.Data.Binding("Name"));
|
||||||
stack.Children.Add(nameText);
|
stack.Children.Add(nameText);
|
||||||
|
|
||||||
|
var textDark = Application.Current!.FindResource("TextDark");
|
||||||
var descText = new TextBlock { FontSize = 11, FontStyle = FontStyle.Italic, HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center, TextWrapping = TextWrapping.Wrap, TextAlignment = TextAlignment.Center, Opacity = 0.8 };
|
var descText = new TextBlock { FontSize = 11, FontStyle = FontStyle.Italic, HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center, TextWrapping = TextWrapping.Wrap, TextAlignment = TextAlignment.Center, Opacity = 0.8 };
|
||||||
|
if (textDark is IBrush td) descText.Foreground = td;
|
||||||
descText.Bind(TextBlock.TextProperty, new Avalonia.Data.Binding("Description"));
|
descText.Bind(TextBlock.TextProperty, new Avalonia.Data.Binding("Description"));
|
||||||
descText.Bind(TextBlock.IsVisibleProperty, new Avalonia.Data.Binding("Description") { Converter = Avalonia.Data.Converters.StringConverters.IsNotNullOrEmpty });
|
descText.Bind(TextBlock.IsVisibleProperty, new Avalonia.Data.Binding("Description") { Converter = Avalonia.Data.Converters.StringConverters.IsNotNullOrEmpty });
|
||||||
stack.Children.Add(descText);
|
stack.Children.Add(descText);
|
||||||
@@ -737,8 +740,8 @@ namespace NetworkDiagram
|
|||||||
if (_currentDiagram.Devices.Count == 0) return;
|
if (_currentDiagram.Devices.Count == 0) return;
|
||||||
|
|
||||||
var prompt = new ExportOptionsWindow();
|
var prompt = new ExportOptionsWindow();
|
||||||
var result = await prompt.ShowDialog<bool?>(this);
|
var result = await prompt.ShowDialog<ExportOption>(this);
|
||||||
if (result == null) return;
|
if (result == ExportOption.Cancel) return;
|
||||||
|
|
||||||
var topLevel = GetTopLevel(this);
|
var topLevel = GetTopLevel(this);
|
||||||
if (topLevel == null) return;
|
if (topLevel == null) return;
|
||||||
@@ -764,7 +767,7 @@ namespace NetworkDiagram
|
|||||||
var tempCanvas = new Canvas {
|
var tempCanvas = new Canvas {
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
Background = result.Value ? Brushes.White : Brushes.Transparent
|
Background = (result == ExportOption.White || result == ExportOption.BlackAndWhite) ? Brushes.White : Brushes.Transparent
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a container to host temp canvas for rendering
|
// Create a container to host temp canvas for rendering
|
||||||
@@ -773,13 +776,28 @@ namespace NetworkDiagram
|
|||||||
|
|
||||||
var rtb = new RenderTargetBitmap(new PixelSize(width, height), new Vector(96, 96));
|
var rtb = new RenderTargetBitmap(new PixelSize(width, height), new Vector(96, 96));
|
||||||
|
|
||||||
|
// Fetch resources once for efficiency and consistency
|
||||||
|
var primaryBlue = Application.Current!.FindResource("PrimaryBlue") as IBrush;
|
||||||
|
var accentBlue = Application.Current!.FindResource("AccentBlue") as IBrush;
|
||||||
|
var textDark = Application.Current!.FindResource("TextDark") as IBrush ?? Brushes.Black;
|
||||||
|
var gridBlue = Application.Current!.FindResource("GridBlue") as IBrush;
|
||||||
|
|
||||||
|
bool isGrayscale = result == ExportOption.BlackAndWhite;
|
||||||
|
if (isGrayscale)
|
||||||
|
{
|
||||||
|
primaryBlue = Brushes.Black;
|
||||||
|
accentBlue = Brushes.Black;
|
||||||
|
textDark = Brushes.Black;
|
||||||
|
gridBlue = Brushes.Black;
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Draw connections
|
// 1. Draw connections
|
||||||
foreach (var conn in _currentDiagram.Connections)
|
foreach (var conn in _currentDiagram.Connections)
|
||||||
{
|
{
|
||||||
if (_connectionLines.TryGetValue(conn, out var line))
|
if (_connectionLines.TryGetValue(conn, out var line))
|
||||||
{
|
{
|
||||||
var tempLine = new Line {
|
var tempLine = new Line {
|
||||||
Stroke = line.Stroke,
|
Stroke = isGrayscale ? Brushes.Black : line.Stroke,
|
||||||
StrokeThickness = line.StrokeThickness,
|
StrokeThickness = line.StrokeThickness,
|
||||||
StrokeDashArray = line.StrokeDashArray,
|
StrokeDashArray = line.StrokeDashArray,
|
||||||
StartPoint = new Point(line.StartPoint.X - minX, line.StartPoint.Y - minY),
|
StartPoint = new Point(line.StartPoint.X - minX, line.StartPoint.Y - minY),
|
||||||
@@ -799,8 +817,8 @@ namespace NetworkDiagram
|
|||||||
CornerRadius = new CornerRadius(8),
|
CornerRadius = new CornerRadius(8),
|
||||||
Padding = new Thickness(12),
|
Padding = new Thickness(12),
|
||||||
Background = Brushes.White,
|
Background = Brushes.White,
|
||||||
BorderBrush = (IBrush)Application.Current!.FindResource("GridBlue")!,
|
BorderBrush = gridBlue ?? Brushes.Blue,
|
||||||
BorderThickness = new Thickness(1),
|
BorderThickness = new Thickness(isGrayscale ? 2 : 1),
|
||||||
MinWidth = 100,
|
MinWidth = 100,
|
||||||
MinHeight = 80,
|
MinHeight = 80,
|
||||||
MaxWidth = 180
|
MaxWidth = 180
|
||||||
@@ -808,22 +826,21 @@ namespace NetworkDiagram
|
|||||||
|
|
||||||
var stack = new StackPanel { Spacing = 4 };
|
var stack = new StackPanel { Spacing = 4 };
|
||||||
|
|
||||||
var img = new Image { Width = 64, Height = 64 };
|
var img = new Image { Width = 64, Height = 64, HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center };
|
||||||
if (ImageConverter.Instance.Convert(device.IconPath, typeof(IImage), null, System.Globalization.CultureInfo.CurrentCulture) is IImage iimg)
|
if (ImageConverter.Instance.Convert(device.IconPath, typeof(IImage), null, System.Globalization.CultureInfo.CurrentCulture) is IImage iimg)
|
||||||
{
|
{
|
||||||
img.Source = iimg;
|
img.Source = iimg;
|
||||||
}
|
}
|
||||||
|
|
||||||
var primaryBlue = Application.Current!.FindResource("PrimaryBlue");
|
|
||||||
var txt = new TextBlock {
|
var txt = new TextBlock {
|
||||||
Text = device.Name,
|
Text = device.Name,
|
||||||
FontWeight = FontWeight.Bold,
|
FontWeight = FontWeight.Bold,
|
||||||
FontSize = 12,
|
FontSize = isGrayscale ? 14 : 12,
|
||||||
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
|
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
|
||||||
TextWrapping = TextWrapping.Wrap,
|
TextWrapping = TextWrapping.Wrap,
|
||||||
TextAlignment = TextAlignment.Center
|
TextAlignment = TextAlignment.Center,
|
||||||
|
Foreground = primaryBlue
|
||||||
};
|
};
|
||||||
if (primaryBlue is IBrush brush) txt.Foreground = brush;
|
|
||||||
|
|
||||||
stack.Children.Add(img);
|
stack.Children.Add(img);
|
||||||
stack.Children.Add(txt);
|
stack.Children.Add(txt);
|
||||||
@@ -832,27 +849,28 @@ namespace NetworkDiagram
|
|||||||
{
|
{
|
||||||
var desc = new TextBlock {
|
var desc = new TextBlock {
|
||||||
Text = device.Description,
|
Text = device.Description,
|
||||||
FontSize = 11,
|
FontSize = isGrayscale ? 18 : 11,
|
||||||
|
FontWeight = isGrayscale ? FontWeight.Bold : FontWeight.Normal,
|
||||||
FontStyle = FontStyle.Italic,
|
FontStyle = FontStyle.Italic,
|
||||||
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
|
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
|
||||||
TextWrapping = TextWrapping.Wrap,
|
TextWrapping = TextWrapping.Wrap,
|
||||||
TextAlignment = TextAlignment.Center,
|
TextAlignment = TextAlignment.Center,
|
||||||
Opacity = 0.8,
|
Foreground = textDark,
|
||||||
Foreground = Brushes.Black
|
Opacity = isGrayscale ? 1.0 : 0.8
|
||||||
};
|
};
|
||||||
stack.Children.Add(desc);
|
stack.Children.Add(desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
var accentBlue = Application.Current!.FindResource("AccentBlue");
|
|
||||||
foreach (var ip in device.IpAddresses)
|
foreach (var ip in device.IpAddresses)
|
||||||
{
|
{
|
||||||
var tb = new TextBlock {
|
var tb = new TextBlock {
|
||||||
Text = ip,
|
Text = ip,
|
||||||
FontSize = 10,
|
FontSize = isGrayscale ? 14 : 10,
|
||||||
|
FontWeight = isGrayscale ? FontWeight.Bold : FontWeight.Normal,
|
||||||
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
|
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
|
||||||
TextWrapping = TextWrapping.Wrap
|
TextWrapping = TextWrapping.Wrap,
|
||||||
|
Foreground = accentBlue
|
||||||
};
|
};
|
||||||
if (accentBlue is IBrush b) tb.Foreground = b;
|
|
||||||
stack.Children.Add(tb);
|
stack.Children.Add(tb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -870,8 +888,65 @@ namespace NetworkDiagram
|
|||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
rtb.Render(container);
|
rtb.Render(container);
|
||||||
|
|
||||||
using var stream = await file.OpenWriteAsync();
|
if (result == ExportOption.BlackAndWhite)
|
||||||
rtb.Save(stream);
|
{
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
rtb.Save(ms);
|
||||||
|
ms.Position = 0;
|
||||||
|
var wb = WriteableBitmap.Decode(ms);
|
||||||
|
ApplyGrayscale(wb);
|
||||||
|
using var stream = await file.OpenWriteAsync();
|
||||||
|
wb.Save(stream);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using var stream = await file.OpenWriteAsync();
|
||||||
|
rtb.Save(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyGrayscale(WriteableBitmap bitmap)
|
||||||
|
{
|
||||||
|
using (var lockedBitmap = bitmap.Lock())
|
||||||
|
{
|
||||||
|
int totalBytes = lockedBitmap.RowBytes * lockedBitmap.Size.Height;
|
||||||
|
byte[] buffer = new byte[totalBytes];
|
||||||
|
Marshal.Copy(lockedBitmap.Address, buffer, 0, totalBytes);
|
||||||
|
|
||||||
|
for (int y = 0; y < lockedBitmap.Size.Height; y++)
|
||||||
|
{
|
||||||
|
int rowOffset = y * lockedBitmap.RowBytes;
|
||||||
|
for (int x = 0; x < lockedBitmap.Size.Width; x++)
|
||||||
|
{
|
||||||
|
int i = rowOffset + x * 4;
|
||||||
|
if (i + 3 >= totalBytes) continue;
|
||||||
|
|
||||||
|
byte b = buffer[i];
|
||||||
|
byte g = buffer[i + 1];
|
||||||
|
byte r = buffer[i + 2];
|
||||||
|
byte a = buffer[i + 3];
|
||||||
|
|
||||||
|
if (a < 128)
|
||||||
|
{
|
||||||
|
// Transparent -> White
|
||||||
|
buffer[i] = 255;
|
||||||
|
buffer[i + 1] = 255;
|
||||||
|
buffer[i + 2] = 255;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Standard luminance for grayscale
|
||||||
|
byte val = (byte)(0.299 * r + 0.587 * g + 0.114 * b);
|
||||||
|
buffer[i] = val;
|
||||||
|
buffer[i + 1] = val;
|
||||||
|
buffer[i + 2] = val;
|
||||||
|
}
|
||||||
|
buffer[i + 3] = 255; // Always opaque for printing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Marshal.Copy(buffer, 0, lockedBitmap.Address, totalBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
Reference in New Issue
Block a user