Skip to content

Commit ba21f1f

Browse files
committed
Extract rendering code into ThemePreviewRenderer
1 parent 72902bd commit ba21f1f

File tree

2 files changed

+282
-276
lines changed

2 files changed

+282
-276
lines changed

src/Skia/ThemePreviewRenderer.cs

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
using SkiaSharp;
6+
using System;
7+
using System.Drawing;
8+
9+
namespace WinDynamicDesktop.Skia
10+
{
11+
internal class ThemePreviewRenderer
12+
{
13+
private const int MARGIN_STANDARD = 20;
14+
private const int BORDER_RADIUS = 5;
15+
private const int ARROW_AREA_WIDTH = 80;
16+
private const byte OVERLAY_ALPHA = 127;
17+
private const float OPACITY_NORMAL = 0.5f;
18+
private const float OPACITY_HOVER = 1.0f;
19+
private const float OPACITY_MESSAGE = 0.8f;
20+
21+
private readonly SKPaint basePaint;
22+
private readonly SKColor overlayColor;
23+
private readonly SKFont titleFont;
24+
private readonly SKFont previewFont;
25+
private readonly SKFont textFont;
26+
private readonly SKFont iconFont16;
27+
private readonly SKFont iconFont20;
28+
private readonly SKSamplingOptions samplingOptions;
29+
30+
// Hit test regions (updated during rendering)
31+
public Rectangle TitleBoxRect { get; private set; }
32+
public Rectangle PlayButtonRect { get; private set; }
33+
public Rectangle DownloadMessageRect { get; private set; }
34+
public Rectangle AuthorLabelRect { get; private set; }
35+
public Rectangle DownloadSizeLabelRect { get; private set; }
36+
public Rectangle LeftArrowRect { get; private set; }
37+
public Rectangle RightArrowRect { get; private set; }
38+
public Rectangle[] CarouselIndicatorRects { get; private set; }
39+
40+
public ThemePreviewRenderer(SKTypeface fontAwesome)
41+
{
42+
basePaint = new SKPaint { IsAntialias = true };
43+
overlayColor = new SKColor(0, 0, 0, OVERLAY_ALPHA);
44+
titleFont = new SKFont(SKTypeface.FromFamilyName("Segoe UI", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright), 19);
45+
previewFont = new SKFont(SKTypeface.FromFamilyName("Segoe UI", SKFontStyleWeight.Normal, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright), 16);
46+
textFont = new SKFont(SKTypeface.FromFamilyName("Segoe UI"), 16);
47+
iconFont16 = new SKFont(fontAwesome, 16);
48+
iconFont20 = new SKFont(fontAwesome, 20);
49+
samplingOptions = new SKSamplingOptions(SKCubicResampler.Mitchell);
50+
}
51+
52+
public void DrawImage(SKCanvas canvas, SKImage image, SKImageInfo info, float opacity)
53+
{
54+
var destRect = new SKRect(0, 0, info.Width, info.Height);
55+
56+
if (opacity >= 1.0f)
57+
{
58+
// Fast path for fully opaque images
59+
canvas.DrawImage(image, destRect, samplingOptions, null);
60+
}
61+
else
62+
{
63+
// Apply opacity with color filter
64+
using (var paint = new SKPaint())
65+
{
66+
paint.IsAntialias = true;
67+
paint.ColorFilter = SKColorFilter.CreateBlendMode(
68+
SKColors.White.WithAlpha((byte)(255 * opacity)),
69+
SKBlendMode.DstIn);
70+
71+
canvas.DrawImage(image, destRect, samplingOptions, paint);
72+
}
73+
}
74+
}
75+
76+
public void DrawOverlay(SKCanvas canvas, SKImageInfo info, ThemePreviewerViewModel viewModel,
77+
ThemePreviewer.HoveredItem hoveredItem)
78+
{
79+
// Set arrow hit regions
80+
LeftArrowRect = new Rectangle(0, 0, ARROW_AREA_WIDTH, info.Height);
81+
RightArrowRect = new Rectangle(info.Width - ARROW_AREA_WIDTH, 0, ARROW_AREA_WIDTH, info.Height);
82+
83+
// Draw left and right arrow button areas
84+
DrawArrowArea(canvas, info, true, hoveredItem == ThemePreviewer.HoveredItem.LeftArrow);
85+
DrawArrowArea(canvas, info, false, hoveredItem == ThemePreviewer.HoveredItem.RightArrow);
86+
87+
// Title and preview text box (top left)
88+
var titleBounds = new SKRect();
89+
titleFont.MeasureText(viewModel.Title ?? "", out titleBounds);
90+
var previewBounds = new SKRect();
91+
previewFont.MeasureText(viewModel.PreviewText ?? "", out previewBounds);
92+
93+
float boxWidth = Math.Max(titleBounds.Width, previewBounds.Width) + MARGIN_STANDARD;
94+
float boxHeight = 19 + 4 + 16 + MARGIN_STANDARD;
95+
TitleBoxRect = new Rectangle(MARGIN_STANDARD, MARGIN_STANDARD, (int)boxWidth, (int)boxHeight);
96+
97+
basePaint.Color = overlayColor;
98+
canvas.DrawRoundRect(SKRect.Create(TitleBoxRect.X, TitleBoxRect.Y, TitleBoxRect.Width, TitleBoxRect.Height), BORDER_RADIUS, BORDER_RADIUS, basePaint);
99+
100+
basePaint.Color = SKColors.White;
101+
canvas.DrawText(viewModel.Title ?? "", TitleBoxRect.X + 10, TitleBoxRect.Y + 8 + 19, titleFont, basePaint);
102+
canvas.DrawText(viewModel.PreviewText ?? "", TitleBoxRect.X + 10, TitleBoxRect.Y + 8 + 19 + 5 + 16, previewFont, basePaint);
103+
104+
// Play/Pause button (top right)
105+
int playButtonSize = 40;
106+
PlayButtonRect = new Rectangle(info.Width - playButtonSize - MARGIN_STANDARD, MARGIN_STANDARD, playButtonSize, playButtonSize);
107+
108+
basePaint.Color = overlayColor;
109+
canvas.DrawRoundRect(SKRect.Create(PlayButtonRect.X, PlayButtonRect.Y, PlayButtonRect.Width, PlayButtonRect.Height), BORDER_RADIUS, BORDER_RADIUS, basePaint);
110+
111+
float playOpacity = hoveredItem == ThemePreviewer.HoveredItem.PlayButton ? OPACITY_HOVER : OPACITY_NORMAL;
112+
basePaint.Color = SKColors.White.WithAlpha((byte)(255 * playOpacity));
113+
string playIcon = viewModel.IsPlaying ? "\uf04c" : "\uf04b";
114+
var textBounds = new SKRect();
115+
iconFont16.MeasureText(playIcon, out textBounds);
116+
float centerX = PlayButtonRect.X + PlayButtonRect.Width / 2;
117+
float centerY = PlayButtonRect.Y + PlayButtonRect.Height / 2;
118+
canvas.DrawText(playIcon, centerX - textBounds.MidX, centerY - textBounds.MidY, iconFont16, basePaint);
119+
120+
// Corner labels
121+
DrawCornerLabel(canvas, info, viewModel.Author, isBottomRight: true, out var authorRect);
122+
AuthorLabelRect = authorRect;
123+
DrawCornerLabel(canvas, info, viewModel.DownloadSize, isBottomRight: false, out var downloadSizeRect);
124+
DownloadSizeLabelRect = downloadSizeRect;
125+
126+
// Download message (centered bottom)
127+
if (!string.IsNullOrEmpty(viewModel.Message))
128+
{
129+
var msgBounds = new SKRect();
130+
textFont.MeasureText(viewModel.Message, out msgBounds);
131+
float msgWidth = msgBounds.Width + 16;
132+
float msgHeight = 6 + 16 + 6;
133+
DownloadMessageRect = new Rectangle((int)(info.Width / 2 - msgWidth / 2), info.Height - (int)msgHeight - 15, (int)msgWidth, (int)msgHeight);
134+
135+
basePaint.Color = overlayColor;
136+
canvas.DrawRoundRect(SKRect.Create(DownloadMessageRect.X, DownloadMessageRect.Y, DownloadMessageRect.Width, DownloadMessageRect.Height), BORDER_RADIUS, BORDER_RADIUS, basePaint);
137+
138+
float msgOpacity = hoveredItem == ThemePreviewer.HoveredItem.DownloadButton ? OPACITY_HOVER : OPACITY_MESSAGE;
139+
basePaint.Color = SKColors.White.WithAlpha((byte)(255 * msgOpacity));
140+
canvas.DrawText(viewModel.Message, DownloadMessageRect.X + 8, DownloadMessageRect.Y + 5 + 16, textFont, basePaint);
141+
}
142+
else
143+
{
144+
DownloadMessageRect = Rectangle.Empty;
145+
}
146+
147+
// Carousel indicators
148+
if (viewModel.CarouselIndicatorsVisible && viewModel.Items.Count > 0)
149+
{
150+
DrawCarouselIndicators(canvas, info, viewModel.Items.Count, viewModel.SelectedIndex);
151+
}
152+
else
153+
{
154+
CarouselIndicatorRects = null;
155+
}
156+
}
157+
158+
private void DrawArrowArea(SKCanvas canvas, SKImageInfo info, bool isLeft, bool isHovered)
159+
{
160+
float opacity = isHovered ? OPACITY_HOVER : OPACITY_NORMAL;
161+
162+
float x = isLeft ? 40 : info.Width - 40;
163+
float y = info.Height / 2;
164+
165+
basePaint.Color = SKColors.White.WithAlpha((byte)(255 * opacity));
166+
167+
string icon = isLeft ? "\uf053" : "\uf054";
168+
var textBounds = new SKRect();
169+
iconFont20.MeasureText(icon, out textBounds);
170+
canvas.DrawText(icon, x - textBounds.MidX, y - textBounds.MidY, iconFont20, basePaint);
171+
}
172+
173+
private void DrawCornerLabel(SKCanvas canvas, SKImageInfo info, string text, bool isBottomRight, out Rectangle labelRect)
174+
{
175+
if (string.IsNullOrEmpty(text))
176+
{
177+
labelRect = Rectangle.Empty;
178+
return;
179+
}
180+
181+
var textBounds = new SKRect();
182+
textFont.MeasureText(text, out textBounds);
183+
184+
float leftMargin = isBottomRight ? 8 : 11;
185+
float rightMargin = isBottomRight ? 11 : 8;
186+
float borderWidth = textBounds.Width + leftMargin + rightMargin;
187+
float borderHeight = 4 + 16 + 9;
188+
189+
float rectX = isBottomRight ? info.Width - borderWidth + 3 : -3;
190+
float rectY = info.Height - borderHeight + 3;
191+
labelRect = new Rectangle((int)rectX, (int)rectY, (int)borderWidth, (int)borderHeight);
192+
193+
basePaint.Color = overlayColor;
194+
canvas.DrawRoundRect(SKRect.Create(rectX, rectY, borderWidth, borderHeight), BORDER_RADIUS, BORDER_RADIUS, basePaint);
195+
196+
basePaint.Color = SKColors.White.WithAlpha(OVERLAY_ALPHA);
197+
float textX = isBottomRight ? info.Width - textBounds.Width - rightMargin + 3 : leftMargin - 3;
198+
float textY = rectY + 4 + 16;
199+
canvas.DrawText(text, textX, textY, textFont, basePaint);
200+
}
201+
202+
private void DrawCarouselIndicators(SKCanvas canvas, SKImageInfo info, int count, int selectedIndex)
203+
{
204+
int indicatorWidth = 30;
205+
int indicatorHeight = 3;
206+
int itemSpacing = 6;
207+
int totalWidth = count * indicatorWidth + (count - 1) * itemSpacing;
208+
int startX = (info.Width - totalWidth) / 2;
209+
int y = info.Height - 16 - 16; // bottom margin - half of clickable height
210+
211+
CarouselIndicatorRects = new Rectangle[count];
212+
213+
for (int i = 0; i < count; i++)
214+
{
215+
float opacity = (i == selectedIndex) ? OPACITY_HOVER : OPACITY_NORMAL;
216+
basePaint.Color = SKColors.White.WithAlpha((byte)(255 * opacity));
217+
218+
int rectX = startX + i * (indicatorWidth + itemSpacing);
219+
canvas.DrawRect(rectX, y - indicatorHeight / 2, indicatorWidth, indicatorHeight, basePaint);
220+
221+
// Cache full clickable area for hit testing
222+
CarouselIndicatorRects[i] = new Rectangle(rectX, y - 16, indicatorWidth, 32);
223+
}
224+
}
225+
226+
public void Dispose()
227+
{
228+
basePaint?.Dispose();
229+
titleFont?.Dispose();
230+
previewFont?.Dispose();
231+
textFont?.Dispose();
232+
iconFont16?.Dispose();
233+
iconFont20?.Dispose();
234+
}
235+
}
236+
}

0 commit comments

Comments
 (0)