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. 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. 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. 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. - Komplettes visuelles Redesign der Hauptoberfläche mit modernerem Layout und konsistenter Typografie.
- Navigation, Content und Header farblich auf dynamische Konfigurationsfarben umgestellt. - Navigation, Content und Header farblich auf dynamische Konfigurationsfarben umgestellt.
- Einheitliche Button-Optik für Primary-Actions, Top-Bar-Actions und dialogbezogene Aktionen. - 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 und Interaktion
- Navigation-Buttons neu ausgerichtet (horizontal/vertikal), Icons vergrößert und Zustände vereinheitlicht. - Navigation-Buttons neu ausgerichtet (horizontal/vertikal), Icons vergrößert und Zustände vereinheitlicht.
@@ -49,6 +55,7 @@
- Mehrdeutigkeitsfehler (`Brushes`) behoben. - Mehrdeutigkeitsfehler (`Brushes`) behoben.
- Vor erfolgreichem Konfig-Ladevorgang wird die Oberfläche analog Offline-Zustand eingeschränkt (nur Info-Navigation und Info-Panel sichtbar). - 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. - 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 ### Lokalisierung
- DE/EN-Ressourcen sprachlich bereinigt und vereinheitlicht. - DE/EN-Ressourcen sprachlich bereinigt und vereinheitlicht.

View File

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

View File

@@ -10,12 +10,14 @@ using System.Linq;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Forms; using System.Windows.Forms;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Shell; using System.Windows.Shell;
@@ -74,8 +76,18 @@ namespace C4IT_CustomerPanel
private TimeSpan _lastAdHocInterval; private TimeSpan _lastAdHocInterval;
private TimeSpan _lastRegularInterval; private TimeSpan _lastRegularInterval;
private bool _allowApplicationShutdown = false; private bool _allowApplicationShutdown = false;
private bool _mainSurfaceClipInitialized = false;
internal DateTime lastUpdate; 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>>(); //private Dictionary<announcementType, List<Guid>> announcementIDCollection = new Dictionary<announcementType, List<Guid>>();
public bool _userIsAuthenticated = false; public bool _userIsAuthenticated = false;
public enumOnlineState OnlineState = enumOnlineState.Initializing; 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); FormHelper.GetLocation((int)this.Width, (int)this.Height, (double)ConfigSettings.local_currentDPI / 96);
SetAppearance(true); SetAppearance(true);
UpdateMainSurfaceClip();
SetLocation(); SetLocation();
CustomerPanelSecurePassword.Init(); CustomerPanelSecurePassword.Init();
@@ -231,6 +244,16 @@ namespace C4IT_CustomerPanel
private void Window_Loaded(object sender, RoutedEventArgs e) private void Window_Loaded(object sender, RoutedEventArgs e)
{ {
ComputerInfoCtrl.FillMainInfoGrid(); 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); NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(AddressChangedCallback);
} }
@@ -2157,9 +2180,139 @@ namespace C4IT_CustomerPanel
private void Window_SizeChanged(object sender, SizeChangedEventArgs e) private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{ {
UpdateMainSurfaceClip();
ApplyRoundedWindowRegion();
SetLocation(); 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 public class cMainFunctionInfo