Skip to content

Commit 162b779

Browse files
committed
Improved Visualizer
1 parent b9c436c commit 162b779

2 files changed

Lines changed: 167 additions & 80 deletions

File tree

‎FluentFlyoutWPF/Classes/Visualizer.cs‎

Lines changed: 165 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,25 @@ public class Visualizer : IDisposable
4141
private DateTime _lastDataAvailableUtc = DateTime.MinValue;
4242
private int _restartInProgress; // 0=false, 1=true (Interlocked)
4343

44+
private readonly struct BarGeometry
45+
{
46+
public readonly float Left, Right, Top, Bottom;
47+
public readonly float InnerLeft, InnerRight, InnerTop, InnerBottom;
48+
49+
public BarGeometry(int x, int width, int y, int endY, float radius)
50+
{
51+
Left = x;
52+
Right = x + width;
53+
Top = y;
54+
Bottom = endY;
55+
56+
InnerLeft = Left + radius;
57+
InnerRight = Right - radius;
58+
InnerTop = Top + radius;
59+
InnerBottom = Bottom - radius;
60+
}
61+
}
62+
4463
public WriteableBitmap? Bitmap
4564
{
4665
get
@@ -431,85 +450,7 @@ private void UpdateBitmap()
431450

432451
buffer.Clear();
433452

434-
// Draw bars
435-
int barWidth = (ImageWidth - (BarCount - 1) * BarSpacing) / BarCount;
436-
437-
// get dominant color for bars
438-
SolidColorBrush brush = BitmapHelper.SavedDominantColors.Count > 0 ?
439-
BitmapHelper.SavedDominantColors.Last()
440-
: (SolidColorBrush)Application.Current.TryFindResource("MicaWPF.Brushes.SystemAccentColorTertiary");
441-
442-
bool centeredBars = SettingsManager.Current.TaskbarVisualizerCenteredBars;
443-
int barBaseline = SettingsManager.Current.TaskbarVisualizerBaseline ? 4 : 0;
444-
445-
int centerY = ImageHeight / 2;
446-
float cornerRadius = 6f / MathF.Max(1f, SettingsManager.Current.TaskbarVisualizerBarCount / 10f);
447-
448-
for (int i = 0; i < BarCount; i++)
449-
{
450-
float normalizedValue = Math.Clamp(_barValues[i], 0f, 1f);
451-
int barHeight = Math.Max((int)(normalizedValue * ImageHeight), barBaseline);
452-
int barX = i * (barWidth + BarSpacing);
453-
454-
int barY, barEndY;
455-
if (centeredBars)
456-
{
457-
// Center the bars - expand up and down from middle
458-
int halfHeight = barHeight / 2;
459-
barY = centerY - halfHeight;
460-
barEndY = centerY + halfHeight;
461-
}
462-
else
463-
{
464-
// Original behavior - bars rise from bottom
465-
barY = ImageHeight - barHeight;
466-
barEndY = ImageHeight;
467-
}
468-
469-
for (int y = barY; y < barEndY && y < ImageHeight && y >= 0; y++)
470-
{
471-
for (int x = barX; x < barX + barWidth && x < ImageWidth; x++)
472-
{
473-
float alpha = 1f;
474-
475-
// Calculate relative position within the bar
476-
int relativeY = y - barY;
477-
478-
// Check corners
479-
bool inTopLeftCorner = (relativeY < cornerRadius) && (x - barX < cornerRadius);
480-
bool inTopRightCorner = (relativeY < cornerRadius) && (barX + barWidth - x <= cornerRadius);
481-
bool inBottomLeftCorner = centeredBars && (barEndY - y <= cornerRadius) && (x - barX < cornerRadius);
482-
bool inBottomRightCorner = centeredBars && (barEndY - y <= cornerRadius) && (barX + barWidth - x <= cornerRadius);
483-
484-
if (inTopLeftCorner || inTopRightCorner || inBottomLeftCorner || inBottomRightCorner)
485-
{
486-
float dx = (inTopLeftCorner || inBottomLeftCorner) ?
487-
(cornerRadius - 0.5f - (x - barX)) :
488-
(cornerRadius - 0.5f - (barX + barWidth - x));
489-
float dy = (inTopLeftCorner || inTopRightCorner) ?
490-
(cornerRadius - 0.5f - relativeY) :
491-
(cornerRadius - 0.5f - (barEndY - y));
492-
float distance = MathF.Sqrt(dx * dx + dy * dy);
493-
494-
if (distance > cornerRadius)
495-
continue; // Outside corner
496-
497-
// Anti-aliasing: fade at edge
498-
if (distance > cornerRadius - 1f)
499-
alpha = cornerRadius - distance;
500-
}
501-
502-
int index = y * stride + x * 4;
503-
if (index + 3 < buffer.Length)
504-
{
505-
buffer[index] = brush.Color.B;
506-
buffer[index + 1] = brush.Color.G;
507-
buffer[index + 2] = brush.Color.R;
508-
buffer[index + 3] = (byte)(255 * alpha);
509-
}
510-
}
511-
}
512-
}
453+
DrawBars(stride, buffer);
513454
}
514455

515456
_bitmap.AddDirtyRect(new Int32Rect(0, 0, ImageWidth, ImageHeight));
@@ -522,6 +463,151 @@ private void UpdateBitmap()
522463
}, System.Windows.Threading.DispatcherPriority.Render);
523464
}
524465

466+
private unsafe void DrawBars(int stride, Span<byte> buffer)
467+
{
468+
// Resolve brush once (avoid repeated lookups)
469+
SolidColorBrush brush = BitmapHelper.SavedDominantColors.Count > 0
470+
? BitmapHelper.SavedDominantColors.Last()
471+
: (SolidColorBrush)Application.Current.TryFindResource("MicaWPF.Brushes.SystemAccentColorTertiary");
472+
473+
byte b = brush.Color.B;
474+
byte g = brush.Color.G;
475+
byte r = brush.Color.R;
476+
477+
bool centeredBars = SettingsManager.Current.TaskbarVisualizerCenteredBars;
478+
int barBaseline = SettingsManager.Current.TaskbarVisualizerBaseline ? 4 : 0;
479+
480+
int centerY = ImageHeight / 2;
481+
482+
// Layout calculation (float-based to avoid spacing drift)
483+
float totalSpacing = (BarCount - 1) * BarSpacing;
484+
float exactBarWidth = (ImageWidth - totalSpacing) / (float)BarCount;
485+
float totalBarsWidth = BarCount * exactBarWidth + totalSpacing;
486+
float offsetX = (ImageWidth - totalBarsWidth) * 0.5f;
487+
488+
for (int i = 0; i < BarCount; i++)
489+
{
490+
// Normalize and scale height
491+
float normalizedValue = Math.Clamp(_barValues[i], 0f, 1f);
492+
int barHeight = Math.Max((int)(normalizedValue * ImageHeight), barBaseline);
493+
494+
// Compute precise X bounds (prevents cumulative rounding errors)
495+
ComputeBarBounds(i, exactBarWidth, offsetX, out int barX, out int barWidth);
496+
497+
if (barWidth <= 0)
498+
continue;
499+
500+
// Compute vertical bounds
501+
ComputeBarVertical(centeredBars, centerY, barHeight, out int barY, out int barEndY);
502+
503+
// Precompute geometry
504+
float radius = MathF.Min(barWidth, barHeight) * 0.5f;
505+
506+
var geom = new BarGeometry(barX, barWidth, barY, barEndY, radius);
507+
508+
// Precompute SDF constants
509+
float aa = 1.25f;
510+
float invAA = 1f / aa;
511+
float radiusSq = radius * radius;
512+
513+
// Rasterize
514+
RasterizeBar(buffer, stride, geom, centeredBars, b, g, r, invAA, radiusSq);
515+
}
516+
}
517+
518+
private unsafe void RasterizeBar(
519+
Span<byte> buffer,
520+
int stride,
521+
BarGeometry g,
522+
bool centeredBars,
523+
byte b, byte gr, byte r,
524+
float invAA,
525+
float radiusSq)
526+
{
527+
for (int y = (int)g.Top; y < g.Bottom && y < ImageHeight && y >= 0; y++)
528+
{
529+
int row = y * stride;
530+
531+
for (int x = (int)g.Left; x < g.Right && x < ImageWidth; x++)
532+
{
533+
int index = row + x * 4;
534+
if (index + 3 >= buffer.Length)
535+
continue;
536+
537+
// Fast path: center rectangle
538+
if (x >= g.InnerLeft && x <= g.InnerRight)
539+
{
540+
WritePixel(buffer, index, b, gr, r, 255);
541+
continue;
542+
}
543+
544+
// Fast path: vertical sides (no AA)
545+
if (y >= g.InnerTop && y <= g.InnerBottom)
546+
{
547+
WritePixel(buffer, index, b, gr, r, 255);
548+
continue;
549+
}
550+
551+
// Flat bottom if not centered
552+
if (!centeredBars && y >= g.InnerBottom)
553+
{
554+
WritePixel(buffer, index, b, gr, r, 255);
555+
continue;
556+
}
557+
558+
// Rounded caps only
559+
float cx = x < g.InnerLeft ? g.InnerLeft : (x > g.InnerRight ? g.InnerRight : x);
560+
float cy = y < g.InnerTop ? g.InnerTop : (y > g.InnerBottom ? g.InnerBottom : y);
561+
562+
float dx = x - cx;
563+
float dy = y - cy;
564+
565+
float distSq = dx * dx + dy * dy;
566+
float sdf = (distSq - radiusSq) / (2f * MathF.Sqrt(radiusSq)); // radius = sqrt(radiusSq)
567+
568+
float alpha = Math.Clamp(0.5f - sdf * invAA, 0f, 1f);
569+
570+
if (alpha <= 0f)
571+
continue;
572+
573+
WritePixel(buffer, index, b, gr, r, (byte)(255 * alpha));
574+
}
575+
}
576+
}
577+
578+
private void ComputeBarBounds(int i, float width, float offsetX, out int x, out int barWidth)
579+
{
580+
float fx = offsetX + i * (width + BarSpacing);
581+
float fxNext = fx + width;
582+
583+
x = (int)MathF.Round(fx);
584+
int right = (int)MathF.Round(fxNext);
585+
barWidth = right - x;
586+
}
587+
588+
private void ComputeBarVertical(bool centered, int centerY, int height, out int y, out int endY)
589+
{
590+
if (centered)
591+
{
592+
int half = height / 2;
593+
y = centerY - half;
594+
endY = centerY + half;
595+
}
596+
else
597+
{
598+
y = ImageHeight - height;
599+
endY = ImageHeight;
600+
}
601+
}
602+
603+
private static void WritePixel(Span<byte> buffer, int index, byte b, byte g, byte r, byte a)
604+
{
605+
buffer[index] = b;
606+
buffer[index + 1] = g;
607+
buffer[index + 2] = r;
608+
buffer[index + 3] = a;
609+
}
610+
525611
private void OnRecordingStopped(object? sender, StoppedEventArgs e)
526612
{
527613
if (e.Exception != null)

‎FluentFlyoutWPF/ViewModels/AboutViewModel.cs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ public partial class AboutViewModel : ObservableObject
2121
"xsm2",
2222
"Simnico99",
2323
"neegool",
24-
"mak7im01"
24+
"mak7im01",
25+
"Hikaru11747"
2526
];
2627

2728
public ObservableCollection<string> Translators { get; } =

0 commit comments

Comments
 (0)