fix(ui): consolidate mainwindow corner/clip fixes

This commit is contained in:
Meik
2026-03-05 17:25:56 +01:00
parent a7fc42b876
commit 97f87b9dcd
4 changed files with 213 additions and 36 deletions

View File

@@ -8,3 +8,4 @@
4. Relevante Änderungen sind immer tageweise in `Changelog.md` zu dokumentieren.
5. Korrekturschleifen oder reine Nachbesserungen dürfen nicht als eigene Changelog-Einträge auftauchen.
6. `Releasenotes.md` ist vor Veröffentlichung oder auf explizite Anforderung aus dem `Changelog.md` zu synchronisieren.
7. Für Pushes ist standardmäßig `git pushr` (Retry/Backoff) zu verwenden; falls nicht verfügbar, ist `git push` mit manuellen Wiederholungen durchzuführen.

View File

@@ -5,7 +5,13 @@
- Komplettes visuelles Redesign der Hauptoberfläche mit modernerem Layout und konsistenter Typografie.
- Navigation, Content und Header farblich auf dynamische Konfigurationsfarben umgestellt.
- Einheitliche Button-Optik für Primary-Actions, Top-Bar-Actions und dialogbezogene Aktionen.
- Außenrahmen der Haupt-GUI als durchgehender Outline-Rahmen umgesetzt, damit die Kontur auch über die Rundungen konsistent sichtbar bleibt.
- Außenrahmen der Haupt-GUI als durchgehende Border auf der Hauptfläche umgesetzt, damit die Kontur über alle Rundungen konsistent bleibt.
- Eckradius von WindowChrome, Hauptfläche und innerem Inhalts-Clip abgestimmt, damit Inhalte in allen vier Ecken innerhalb des Rahmens bleiben.
- Hauptlayout von festen `Window.ActualWidth`-Bindings entkoppelt, damit Header/Content nicht über die gerundete Innenfläche hinausragen.
- Feste Hauptlayout-Höhen (Navigation/Content) auf die verfügbare Innenhöhe abgestimmt und Clipping im Dock-Bereich aktiviert, damit keine Inhalte in die Rundungsbereiche überlaufen.
- Initialisierung des Rounded-Clips auf den finalen Layout-Zeitpunkt erweitert (Loaded/Render), damit die Rundungsbegrenzung stabil greift.
- MainWindow-Ecküberstände beseitigt: Navigation-Rail unten links an den Fensterradius angepasst, Content-Bereich im `DockPanel` als Fill-Element geführt, zusätzlicher Surface-Clip auf Radius `20` gesetzt, den gesamten `MainWindowContentRoot` als konstanten `1px`-Inset innerhalb des Rahmens geführt, den Body-Host per Geometrie mit zum Innenradius passenden gerundeten unteren Ecken begrenzt, eine On-Top-Corner-Maske (`Rectangle - RoundedRectangle`) ergänzt, eine explizite On-Top-Rahmenkontur als `Path` ergänzt, die linke Rail-Kante ohne eigene Außenlinie geführt (kein Doppelrahmen links) und zusätzlich per Win32-Window-Region (`SetWindowRgn`) die Fensterkontur selbst auf Rundung begrenzt (WPF-Typen für Geometriepunkte/-größen explizit qualifiziert, um Mehrdeutigkeiten mit `System.Drawing` zu vermeiden).
- Sichtbarkeit des Main-Contents wiederhergestellt: Navigations-/Content-Host auf feste Breitenaufteilung (`75 + 425`) umgestellt, damit das Inhaltspanel nicht mehr durch einen Zero-Width-Viewport abgeschnitten wird.
### Navigation und Interaktion
- Navigation-Buttons neu ausgerichtet (horizontal/vertikal), Icons vergrößert und Zustände vereinheitlicht.
@@ -49,6 +55,7 @@
- Mehrdeutigkeitsfehler (`Brushes`) behoben.
- Vor erfolgreichem Konfig-Ladevorgang wird die Oberfläche analog Offline-Zustand eingeschränkt (nur Info-Navigation und Info-Panel sichtbar).
- Header-Verbindungsindikator für Offline/Verbindungsaufbau als größerer farbiger Status-Badge (rot/orange) sichtbarer umgesetzt, mit Tooltip versehen und so positioniert, dass er nicht von Action-Buttons überdeckt wird.
- AGENTS-Prozessregel ergänzt: Pushes standardmäßig über `git pushr` mit Retry/Backoff ausführen (Fallback: manuelle Wiederholungen mit `git push`).
### Lokalisierung
- DE/EN-Ressourcen sprachlich bereinigt und vereinheitlicht.

View File

@@ -12,7 +12,7 @@
WindowStyle="None"
AllowsTransparency="False"
ResizeMode="NoResize"
Background="Transparent"
Background="{DynamicResource backgroundColor}"
Loaded="Window_Loaded"
Icon="Resources/icons/logo_CustomerPanel.ico"
KeyDown="App_KeyDown"
@@ -346,27 +346,28 @@
</Window.TaskbarItemInfo>
<shell:WindowChrome.WindowChrome>
<shell:WindowChrome CaptionHeight="72"
CornerRadius="18"
CornerRadius="20"
GlassFrameThickness="0"
ResizeBorderThickness="0"
UseAeroCaptionButtons="False" />
</shell:WindowChrome.WindowChrome>
<Grid x:Name="MainGrid"
Background="{DynamicResource backgroundColor}">
Background="Transparent">
<Border x:Name="MainWindowSurface"
CornerRadius="18"
BorderThickness="0"
CornerRadius="20"
BorderThickness="1"
BorderBrush="Transparent"
Background="{DynamicResource backgroundColor}"
SnapsToDevicePixels="True"
ClipToBounds="True">
<Grid>
<DockPanel Width="{Binding ActualWidth, ElementName=Window, Mode=OneWay}"
x:Name="MainDock"
<Grid x:Name="MainWindowContentRoot"
Margin="1">
<DockPanel x:Name="MainDock"
Background="{DynamicResource headerColor}">
<Grid DockPanel.Dock="Top"
x:Name="GridTop"
Width="{Binding ActualWidth, ElementName=Window, Mode=OneWay}"
HorizontalAlignment="Left"
HorizontalAlignment="Stretch"
Margin="0"
Background="{DynamicResource headerColor}"
Height="72"
@@ -485,32 +486,36 @@
BorderThickness="0"
BorderBrush="Gray"
CornerRadius="0,0,14,14"
Width="{Binding ActualWidth, ElementName=Window, Mode=OneWay}">
HorizontalAlignment="Stretch">
<Border.Background>
<ImageBrush ImageSource="Resources/top_cp.png"
Stretch="UniformToFill" />
</Border.Background>
</Border>
</Grid>
<StackPanel Orientation="Horizontal"
Width="75"
DockPanel.Dock="Top"
ClipToBounds="false"
x:Name="btnSP"
HorizontalAlignment="Left">
<Canvas ClipToBounds="false"
<Grid Width="500"
Height="576"
ClipToBounds="True"
x:Name="btnSP"
HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75" />
<ColumnDefinition Width="425" />
</Grid.ColumnDefinitions>
<Canvas Grid.Column="0"
ClipToBounds="True"
Panel.ZIndex="1000"
Margin="0,0,0,0"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Height="590"
Height="576"
Width="75">
<Border Canvas.Left="0"
Canvas.Top="8"
Width="75"
Height="580"
CornerRadius="20,0,0,0"
BorderThickness="1"
Height="568"
CornerRadius="20,0,0,20"
BorderThickness="0,1,1,1"
BorderBrush="{DynamicResource navigationRailBorderColor}"
Background="{DynamicResource navigationRailColor}" />
<Button Visibility="Hidden"
@@ -938,21 +943,24 @@
</Button>
</Canvas>
<Canvas Margin="0,0,0,0"
<Canvas Grid.Column="1"
Margin="0,0,0,0"
Panel.ZIndex="1"
ClipToBounds="False"
ClipToBounds="True"
Height="576"
Width="425"
>
<Border Canvas.Left="-75"
Canvas.Top="8"
Width="500"
Height="580"
Height="568"
CornerRadius="20"
BorderThickness="1"
BorderBrush="{DynamicResource panelBorderColor}"
Background="{DynamicResource panelBackgroundColor}" />
<Canvas x:Name="StPaMain"
Panel.ZIndex="1000"
Height="590"
Height="576"
Width="500"
Background="Transparent"
Canvas.Left="-75">
@@ -986,7 +994,7 @@
</Canvas>
</Canvas>
</StackPanel>
</Grid>
</DockPanel>
<Image Source="Resources/United-Kingdom-Flag-icon.png"
Panel.ZIndex="1001"
@@ -996,14 +1004,22 @@
Margin="80,0,0,140" />
</Grid>
</Border>
<Border x:Name="MainWindowOutline"
CornerRadius="18"
BorderThickness="1"
BorderBrush="{DynamicResource panelBorderColor}"
Background="Transparent"
IsHitTestVisible="False"
SnapsToDevicePixels="True"
Panel.ZIndex="3000" />
<Path x:Name="MainWindowCornerMaskOverlay"
Panel.ZIndex="3000"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Fill="{DynamicResource backgroundColor}"
IsHitTestVisible="False"
SnapsToDevicePixels="True" />
<Path x:Name="MainWindowFrameOverlay"
Panel.ZIndex="3001"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Fill="Transparent"
Stroke="{DynamicResource panelBorderColor}"
StrokeThickness="1"
IsHitTestVisible="False"
SnapsToDevicePixels="True" />
</Grid>
</Window>

View File

@@ -10,12 +10,14 @@ using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shell;
@@ -74,8 +76,18 @@ namespace C4IT_CustomerPanel
private TimeSpan _lastAdHocInterval;
private TimeSpan _lastRegularInterval;
private bool _allowApplicationShutdown = false;
private bool _mainSurfaceClipInitialized = false;
internal DateTime lastUpdate;
[DllImport("gdi32.dll")]
private static extern IntPtr CreateRoundRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect, int nWidthEllipse, int nHeightEllipse);
[DllImport("user32.dll")]
private static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw);
[DllImport("gdi32.dll")]
private static extern bool DeleteObject(IntPtr hObject);
//private Dictionary<announcementType, List<Guid>> announcementIDCollection = new Dictionary<announcementType, List<Guid>>();
public bool _userIsAuthenticated = false;
public enumOnlineState OnlineState = enumOnlineState.Initializing;
@@ -144,6 +156,7 @@ namespace C4IT_CustomerPanel
FormHelper.GetLocation((int)this.Width, (int)this.Height, (double)ConfigSettings.local_currentDPI / 96);
SetAppearance(true);
UpdateMainSurfaceClip();
SetLocation();
CustomerPanelSecurePassword.Init();
@@ -231,6 +244,16 @@ namespace C4IT_CustomerPanel
private void Window_Loaded(object sender, RoutedEventArgs e)
{
ComputerInfoCtrl.FillMainInfoGrid();
UpdateMainSurfaceClip();
ApplyRoundedWindowRegion();
if (!_mainSurfaceClipInitialized)
{
Dispatcher.BeginInvoke(new Action(UpdateMainSurfaceClip), DispatcherPriority.Loaded);
Dispatcher.BeginInvoke(new Action(UpdateMainSurfaceClip), DispatcherPriority.Render);
Dispatcher.BeginInvoke(new Action(ApplyRoundedWindowRegion), DispatcherPriority.Loaded);
Dispatcher.BeginInvoke(new Action(ApplyRoundedWindowRegion), DispatcherPriority.Render);
}
NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(AddressChangedCallback);
}
@@ -2157,9 +2180,139 @@ namespace C4IT_CustomerPanel
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateMainSurfaceClip();
ApplyRoundedWindowRegion();
SetLocation();
}
private void UpdateMainSurfaceClip()
{
if (MainWindowSurface == null || MainWindowContentRoot == null || MainGrid == null)
return;
double surfaceWidth = MainWindowSurface.ActualWidth;
double surfaceHeight = MainWindowSurface.ActualHeight;
double contentWidth = MainWindowContentRoot.ActualWidth;
double contentHeight = MainWindowContentRoot.ActualHeight;
if (surfaceWidth <= 0 || surfaceHeight <= 0 || contentWidth <= 0 || contentHeight <= 0)
return;
const double surfaceRadius = 20d;
const double contentRadius = 19d;
MainGrid.Clip = null;
this.Clip = null;
MainWindowSurface.Clip = new RectangleGeometry(new Rect(0d, 0d, surfaceWidth, surfaceHeight), surfaceRadius, surfaceRadius);
MainWindowContentRoot.Clip = new RectangleGeometry(new Rect(0d, 0d, contentWidth, contentHeight), contentRadius, contentRadius);
if (MainWindowCornerMaskOverlay != null)
{
MainWindowCornerMaskOverlay.Data = CreateCornerOverflowMaskGeometry(surfaceWidth, surfaceHeight, surfaceRadius);
}
if (MainWindowFrameOverlay != null)
{
MainWindowFrameOverlay.Data = CreateFrameOverlayGeometry(surfaceWidth, surfaceHeight, surfaceRadius, 1d);
}
if (btnSP != null && btnSP.ActualWidth > 0d && btnSP.ActualHeight > 0d)
{
btnSP.Clip = CreateBottomRoundedRectGeometry(btnSP.ActualWidth, btnSP.ActualHeight, contentRadius);
}
_mainSurfaceClipInitialized = true;
}
private static Geometry CreateBottomRoundedRectGeometry(double width, double height, double radius)
{
if (width <= 0d || height <= 0d)
return Geometry.Empty;
double r = Math.Max(0d, Math.Min(radius, Math.Min(width / 2d, height / 2d)));
if (r <= 0d)
return new RectangleGeometry(new Rect(0d, 0d, width, height));
var geometry = new StreamGeometry();
using (var context = geometry.Open())
{
context.BeginFigure(new System.Windows.Point(0d, 0d), true, true);
context.LineTo(new System.Windows.Point(width, 0d), true, false);
context.LineTo(new System.Windows.Point(width, height - r), true, false);
context.ArcTo(new System.Windows.Point(width - r, height), new System.Windows.Size(r, r), 0d, false, SweepDirection.Clockwise, true, false);
context.LineTo(new System.Windows.Point(r, height), true, false);
context.ArcTo(new System.Windows.Point(0d, height - r), new System.Windows.Size(r, r), 0d, false, SweepDirection.Clockwise, true, false);
context.LineTo(new System.Windows.Point(0d, 0d), true, false);
}
geometry.Freeze();
return geometry;
}
private static Geometry CreateCornerOverflowMaskGeometry(double width, double height, double radius)
{
if (width <= 0d || height <= 0d)
return Geometry.Empty;
double r = Math.Max(0d, Math.Min(radius, Math.Min(width / 2d, height / 2d)));
if (r <= 0d)
return Geometry.Empty;
var fullRect = new RectangleGeometry(new Rect(0d, 0d, width, height));
var roundedRect = new RectangleGeometry(new Rect(0d, 0d, width, height), r, r);
var mask = new CombinedGeometry(GeometryCombineMode.Exclude, fullRect, roundedRect);
mask.Freeze();
return mask;
}
private static Geometry CreateFrameOverlayGeometry(double width, double height, double radius, double strokeThickness)
{
if (width <= 0d || height <= 0d)
return Geometry.Empty;
double inset = Math.Max(0d, strokeThickness / 2d);
double frameWidth = Math.Max(0d, width - strokeThickness);
double frameHeight = Math.Max(0d, height - strokeThickness);
if (frameWidth <= 0d || frameHeight <= 0d)
return Geometry.Empty;
double frameRadius = Math.Max(0d, Math.Min(radius - inset, Math.Min(frameWidth / 2d, frameHeight / 2d)));
var frame = new RectangleGeometry(new Rect(inset, inset, frameWidth, frameHeight), frameRadius, frameRadius);
frame.Freeze();
return frame;
}
private void ApplyRoundedWindowRegion()
{
if (!IsLoaded || WindowState == WindowState.Minimized)
return;
IntPtr hwnd = new WindowInteropHelper(this).Handle;
if (hwnd == IntPtr.Zero)
return;
PresentationSource source = PresentationSource.FromVisual(this);
if (source?.CompositionTarget == null)
return;
Matrix toDevice = source.CompositionTarget.TransformToDevice;
int pxWidth = Math.Max(1, (int)Math.Ceiling(ActualWidth * toDevice.M11));
int pxHeight = Math.Max(1, (int)Math.Ceiling(ActualHeight * toDevice.M22));
const double radiusDip = 20d;
int ellipseWidth = Math.Max(2, (int)Math.Ceiling(radiusDip * 2d * toDevice.M11));
int ellipseHeight = Math.Max(2, (int)Math.Ceiling(radiusDip * 2d * toDevice.M22));
IntPtr hRgn = CreateRoundRectRgn(0, 0, pxWidth + 1, pxHeight + 1, ellipseWidth, ellipseHeight);
if (hRgn == IntPtr.Zero)
return;
if (SetWindowRgn(hwnd, hRgn, true) == 0)
{
DeleteObject(hRgn);
}
}
}
public class cMainFunctionInfo