xref: /MusicPlayer2/MusicPlayer2/CUIDrawer.cpp (revision 507f198b3a60abec70c8d9764aeee564193fcdf2)
1 #include "stdafx.h"
2 #include "CUIDrawer.h"
3 #include "MusicPlayer2.h"
4 
5 
6 CUIDrawer::CUIDrawer(UIColors& colors)
7     : m_colors(colors)
8 {
9 }
10 
11 
12 CUIDrawer::~CUIDrawer()
13 {
14 }
15 
16 void CUIDrawer::DrawLryicCommon(CRect rect, Alignment align)
17 {
18     SetDrawArea(rect);
19 
20     if (!IsDrawMultiLine(rect.Height()))
21         DrawLyricTextSingleLine(rect, true, align);
22     else
23         DrawLyricTextMultiLine(rect, align);
24 }
25 
26 int CUIDrawer::GetLyricTextHeight() const
27 {
28     //计算文本高度
29     if (!m_for_cortana_lyric)
30         m_pDC->SelectObject(&theApp.m_font_set.lyric.GetFont(theApp.m_ui_data.full_screen));
31     else
32         m_pDC->SelectObject(&theApp.m_font_set.cortana.GetFont());
33     return m_pDC->GetTextExtent(L"文").cy;	//根据当前的字体设置计算文本的高度
34 }
35 
36 void CUIDrawer::Create(CDC* pDC, CWnd* pMainWnd)
37 {
38     CDrawCommon::Create(pDC, pMainWnd);
39 }
40 
41 bool CUIDrawer::IsDrawMultiLine(int height) const
42 {
43     return height >= static_cast<int>(GetLyricTextHeight() * 3.5);
44 }
45 
46 void CUIDrawer::SetForCortanaLyric(bool for_cortana_lyric)
47 {
48     m_for_cortana_lyric = for_cortana_lyric;
49 }
50 
51 void CUIDrawer::DrawLyricTextMultiLine(CRect lyric_area, Alignment align)
52 {
53     int line_space{};
54     if (m_for_cortana_lyric)
55     {
56         line_space = theApp.DPI(4);
57     }
58     else
59     {
60         line_space = theApp.m_lyric_setting_data.lyric_line_space;
61         if (theApp.m_ui_data.full_screen)
62             line_space = static_cast<int>(line_space * CONSTVAL::FULL_SCREEN_ZOOM_FACTOR);
63     }
64 
65     int lyric_height = GetLyricTextHeight() + line_space;			//文本高度加上行间距
66     int lyric_height2 = lyric_height * 2 + line_space;		//包含翻译的歌词高度
67 
68     CFont* pOldFont = SetLyricFont();
69     if (CPlayer::GetInstance().IsPlaylistEmpty())   //当前播放为空时在歌词区域显示播放提示
70     {
71         CFont* font = SetFont(&theApp.m_font_set.font10.GetFont());
72         CString no_track_tip_str{ CCommon::LoadTextFormat(IDS_NO_TRACKS_TIP_INFO, { theApp.m_accelerator_res.GetShortcutDescriptionById(ID_SHOW_PLAYLIST), theApp.m_accelerator_res.GetShortcutDescriptionById(ID_FILE_OPEN), theApp.m_accelerator_res.GetShortcutDescriptionById(ID_FILE_OPEN_FOLDER), theApp.m_accelerator_res.GetShortcutDescriptionById(ID_SET_PATH)})};
73         DrawWindowText(lyric_area, no_track_tip_str, m_colors.color_text_2, Alignment::LEFT, false, true);
74         SetFont(font);
75     }
76     else if (CPlayerUIHelper::IsMidiLyric())
77     {
78         wstring current_lyric{ CPlayer::GetInstance().GetMidiLyric() };
79         DrawWindowText(lyric_area, current_lyric.c_str(), m_colors.color_text, Alignment::CENTER, false, true);
80     }
81     else if (CPlayer::GetInstance().m_Lyrics.IsEmpty())
82     {
83         DrawWindowText(lyric_area, CCommon::LoadText(IDS_NO_LYRIC_INFO), m_colors.color_text_2, Alignment::CENTER);
84     }
85     else
86     {
87         //CRect arect{ lyric_area };		//一行歌词的矩形区域
88         //arect.bottom = arect.top + lyric_height;
89         //vector<CRect> rects(CPlayer::GetInstance().m_Lyrics.GetLyricCount() + 1, arect);
90         //为每一句歌词创建一个矩形,保存在容器里
91         vector<CRect> rects;
92         int lyric_count = CPlayer::GetInstance().m_Lyrics.GetLyricCount() + 1;		//获取歌词数量(由于第一行歌词需要显示标题,所以这里要+1)
93         for (int i{}; i < lyric_count; i++)
94         {
95             CRect arect{ lyric_area };
96             if (!CPlayer::GetInstance().m_Lyrics.GetLyric(i - 1).translate.empty() && theApp.m_lyric_setting_data.show_translate)
97                 arect.bottom = arect.top + lyric_height2;
98             else
99                 arect.bottom = arect.top + lyric_height;
100             rects.push_back(arect);
101         }
102         int center_pos = (lyric_area.top + lyric_area.bottom) / 2;		//歌词区域的中心y坐标
103         Time time{ CPlayer::GetInstance().GetCurrentPosition() };		//当前播放时间
104         int lyric_index = CPlayer::GetInstance().m_Lyrics.GetLyricIndex(time);		            // 当前歌词的序号
105         int progress{ CPlayer::GetInstance().m_Lyrics.GetLyricProgress(time, false, false, [this](const wstring& str) { return GetTextExtent(str.c_str()).cx; }) };		// 当前歌词进度(范围为0~1000),多行歌词使用的进度不含进度符号
106         int y_progress;			//当前歌词在y轴上的进度
107         if (!CPlayer::GetInstance().m_Lyrics.GetLyric(lyric_index).translate.empty() && theApp.m_lyric_setting_data.show_translate)
108             y_progress = progress * lyric_height2 / 1000;
109         else
110             y_progress = progress * lyric_height / 1000;
111         //int start_pos = center_pos - y_progress - (lyric_index + 1)*lyric_height;		//第1句歌词的起始y坐标
112         //计算第1句歌词的起始y坐标
113         //由于当前歌词需要显示在歌词区域的中心位置,因此从中心位置开始,减去当前歌词在Y轴上的进度
114         //再依次减去之前每一句歌词的高度,即得到了第一句歌词的起始位置
115         int start_pos;
116         start_pos = center_pos - y_progress;
117         for (int i{ lyric_index - 1 }; i >= -1; i--)
118         {
119             if (theApp.m_lyric_setting_data.show_translate && !CPlayer::GetInstance().m_Lyrics.GetLyric(i).translate.empty())
120                 start_pos -= lyric_height2;
121             else
122                 start_pos -= lyric_height;
123         }
124 
125         //依次绘制每一句歌词
126         for (int i{ -1 }; i < static_cast<int>(rects.size()) - 1; i++)
127         {
128             //计算每一句歌词的位置
129             if (i == -1)
130                 rects[i + 1].MoveToY(start_pos);
131             else
132                 rects[i + 1].MoveToY(rects[i].bottom);
133             //绘制歌词文本
134             if (!(rects[i + 1] & lyric_area).IsRectEmpty())		//只有当一句歌词的矩形区域和歌词区域的矩形有交集时,才绘制歌词
135             {
136                 //设置歌词文本和翻译文本的矩形区域
137                 CRect rect_text{ rects[i + 1] };
138                 CRect rect_translate;
139                 const CLyrics::Lyric& lyric_i = CPlayer::GetInstance().m_Lyrics.GetLyric(i);
140                 if (!lyric_i.translate.empty() && theApp.m_lyric_setting_data.show_translate)
141                 {
142                     rect_text.MoveToY(rect_text.top + line_space);
143                     rect_text.bottom = rect_text.top + GetLyricTextHeight();
144                     rect_translate = rect_text;
145                     rect_translate.MoveToY(rect_text.bottom + line_space);
146                 }
147 
148                 if (i == lyric_index && progress < 1000)		//绘制正在播放的歌词(处于逐渐过度到高亮过程中的歌词)
149                 {
150                     //这里实现文本从非高亮缓慢变化到高亮效果
151                     int last_time_span = time - lyric_i.time_start;     //当前播放的歌词已持续的时间
152                     int fade_percent = last_time_span / 8;         //计算颜色高亮变化的百分比,除数越大则持续时间越长,10则为1秒
153                     COLORREF text_color = CColorConvert::GetGradientColor(m_colors.color_text_2, m_colors.color_text, fade_percent);
154                     //绘制歌词文本
155                     SetLyricFont();
156                     if (theApp.m_lyric_setting_data.lyric_karaoke_disp)
157                         DrawWindowText(rect_text, lyric_i.text.c_str(), m_colors.color_text, m_colors.color_text_2, progress, align, true);
158                     else
159                         DrawWindowText(rect_text, lyric_i.text.c_str(), text_color, text_color, progress, align, true);
160                     //绘制翻译文本
161                     if (!lyric_i.translate.empty() && theApp.m_lyric_setting_data.show_translate)
162                     {
163                         SetLyricFontTranslated();
164                         DrawWindowText(rect_translate, lyric_i.translate.c_str(), text_color, text_color, progress, align, true);
165                     }
166                 }
167                 else		//绘制非正在播放的歌词
168                 {
169                     SetLyricFont();
170                     COLORREF text_color;
171                     if (i == lyric_index - 1 || (i == lyric_index && progress == 1000))         // 绘制正在取消高亮的歌词(这里实现一句歌词颜色从高亮缓慢变化到非高亮效果),逐字歌词最后一句在此处取消高亮
172                     {
173                         int last_time_span = time - (lyric_i.time_start + lyric_i.time_span);   // 引入逐字歌词后上句歌词结束后时长不等于当前播放的歌词已持续的时间,此处应当使用上句歌词结束时间
174                         int fade_percent = last_time_span / 20;         //计算颜色高亮变化的百分比,当持续时间为2000毫秒时为100%,即颜色缓慢变化的时间为2秒
175                         text_color = CColorConvert::GetGradientColor(m_colors.color_text, m_colors.color_text_2, fade_percent);
176                     }
177                     else
178                     {
179                         text_color = m_colors.color_text_2;
180                     }
181                     //绘制歌词文本
182                     DrawWindowText(rect_text, lyric_i.text.c_str(), text_color, align, true);
183                     //绘制翻译文本
184                     if (!lyric_i.translate.empty() && theApp.m_lyric_setting_data.show_translate)
185                     {
186                         SetLyricFontTranslated();
187                         DrawWindowText(rect_translate, lyric_i.translate.c_str(), text_color, align, true);
188                     }
189                 }
190             }
191         }
192     }
193     SetFont(pOldFont);
194 }
195 
196 void CUIDrawer::DrawLyricTextSingleLine(CRect rect, bool double_line, Alignment align)
197 {
198     CFont* pOldFont = SetLyricFont();
199 
200     if (CPlayerUIHelper::IsMidiLyric())
201     {
202         wstring current_lyric{ CPlayer::GetInstance().GetMidiLyric() };
203         DrawWindowText(rect, current_lyric.c_str(), m_colors.color_text, Alignment::CENTER, false, true);
204     }
205     else if (CPlayer::GetInstance().m_Lyrics.IsEmpty())
206     {
207         DrawWindowText(rect, CCommon::LoadText(IDS_NO_LYRIC_INFO), m_colors.color_text_2, Alignment::CENTER);
208     }
209     else
210     {
211         SetDrawArea(rect);
212         CRect lyric_rect = rect;
213 
214         const bool karaoke{ theApp.m_lyric_setting_data.lyric_karaoke_disp };
215         const bool ignore_blank{ theApp.m_lyric_setting_data.donot_show_blank_lines};
216         auto& now_lyrics{ CPlayer::GetInstance().m_Lyrics };
217         Time time{ CPlayer::GetInstance().GetCurrentPosition() };
218         CLyrics::Lyric& current_lyric{ now_lyrics.GetLyric(time, false, ignore_blank, karaoke) };
219         int progress{ now_lyrics.GetLyricProgress(time, ignore_blank, karaoke, [this](const wstring& str) { return GetTextExtent(str.c_str()).cx; }) };
220         static int last_progress{ -1 };
221 
222         if (current_lyric.text.empty())
223             current_lyric.text = CCommon::LoadText(IDS_DEFAULT_LYRIC_TEXT);
224         //双行显示歌词
225         if (double_line && (current_lyric.translate.empty() || !theApp.m_lyric_setting_data.show_translate) && rect.Height() > static_cast<int>(GetLyricTextHeight() * 1.73))
226         {
227             wstring next_lyric_text;
228             next_lyric_text = now_lyrics.GetLyric(time, true, ignore_blank, karaoke).text;
229             if (next_lyric_text.empty())
230                 next_lyric_text = CCommon::LoadText(IDS_DEFAULT_LYRIC_TEXT);
231             //这里实现文本从非高亮缓慢变化到高亮效果
232             int last_time_span = time - current_lyric.time_start;     //当前播放的歌词已持续的时间
233             int fade_percent = last_time_span / 8;         //计算颜色高亮变化的百分比,除数越大则持续时间越长,10则为1秒
234             if (progress == 1000) fade_percent = 0;         // 进度为1000时当前歌词“已完成”不再高亮
235             // 这里的fade_percent当合并空行开启时可能为负,在颜色渐变处规范取值,此处不再处理
236             DrawLyricDoubleLine(lyric_rect, current_lyric.text.c_str(), next_lyric_text.c_str(), progress, last_progress > progress, fade_percent);
237         }
238         else
239         {
240             // 单行歌词在这里显示翻译,同时更新歌词区域为单行有翻译时的位置
241             if (theApp.m_lyric_setting_data.show_translate && !current_lyric.translate.empty() && rect.Height() > static_cast<int>(GetLyricTextHeight() * 1.73))
242             {
243                 lyric_rect.bottom = lyric_rect.top + rect.Height() / 2;
244                 CRect translate_rect = lyric_rect;
245                 translate_rect.MoveToY(lyric_rect.bottom);
246 
247                 SetLyricFontTranslated();
248                 DrawWindowText(translate_rect, current_lyric.translate.c_str(), m_colors.color_text, m_colors.color_text, progress, align, true);
249             }
250             // 绘制单行歌词
251             SetLyricFont();
252             if (theApp.m_lyric_setting_data.lyric_karaoke_disp)
253                 DrawWindowText(lyric_rect, current_lyric.text.c_str(), m_colors.color_text, m_colors.color_text_2, progress, align, true);
254             else if (0 < progress && progress < 1000)   // 仅高亮“正在进行”的歌词
255                 DrawWindowText(lyric_rect, current_lyric.text.c_str(), m_colors.color_text, m_colors.color_text, progress, align, true);
256             else
257                 DrawWindowText(lyric_rect, current_lyric.text.c_str(), m_colors.color_text_2, m_colors.color_text_2, progress, align, true);
258         }
259         last_progress = progress;
260     }
261 
262     SetFont(pOldFont);
263 }
264 
265 void CUIDrawer::DrawSpectrum(CRect rect, SpectrumCol col, bool draw_reflex /*= false*/, bool low_freq_in_center, bool fixed_width, Alignment alignment)
266 {
267     int cols;		//要显示的频谱柱形的数量
268     switch (col)
269     {
270     case CUIDrawer::SC_64:
271         cols = 64;
272         break;
273     case CUIDrawer::SC_32:
274         cols = 32;
275         break;
276     case CUIDrawer::SC_16:
277         cols = 16;
278         break;
279     case CUIDrawer::SC_8:
280         cols = 8;
281         break;
282     default:
283         cols = SPECTRUM_COL;
284         break;
285     }
286     int max_width{ rect.Width() };
287     if (fixed_width)
288     {
289         if (col == SC_64)
290         {
291             max_width = DPI(280);
292         }
293     }
294     int gap_width{ max_width * (SPECTRUM_COL / cols) / 168 };		//频谱柱形间隙宽度
295     if (theApp.m_ui_data.full_screen && !m_for_cortana_lyric)
296         gap_width *= CONSTVAL::FULL_SCREEN_ZOOM_FACTOR;
297     int width = (max_width - (cols - 1) * gap_width) / (cols - 1);
298     if (gap_width < 1)
299         gap_width = 1;
300     if (width < 1)
301         width = 1;
302 
303     if (fixed_width)
304         SetDrawArea(rect);
305     DrawSpectrum(rect, width, gap_width, cols, m_colors.color_spectrum, draw_reflex, low_freq_in_center, alignment);
306 }
307 
308 void CUIDrawer::DrawSpectrum(CRect rect, int col_width, int gap_width, int cols, COLORREF color, bool draw_reflex /*= false*/, bool low_freq_in_center, Alignment alignment)
309 {
310     CRect rc_spectrum_top = rect;
311     if (draw_reflex)     //如果要绘制倒影,则倒影占总高度的1/3
312         rc_spectrum_top.bottom = rect.top + (rect.Height() * 2 / 3);
313 
314     CRect rects[SPECTRUM_COL];
315     rects[0] = rc_spectrum_top;
316     rects[0].right = rects[0].left + col_width;
317 
318     //频谱的实际宽度
319     int width_actrual{ col_width * cols + gap_width * (cols - 1) };
320     //如果频谱的实际宽度小于矩形的宽度,则让根据alignment的值让频谱居中或右对齐显示
321     if ((width_actrual < rect.Width() && alignment == Alignment::CENTER) || (width_actrual >= rect.Width() && theApp.m_app_setting_data.spectrum_low_freq_in_center))
322         rects[0].MoveToX(rects[0].left + (rect.Width() - width_actrual) / 2);
323     else if (width_actrual < rect.Width() && alignment == Alignment::RIGHT)
324         rects[0].MoveToX(rects[0].left + (rect.Width() - width_actrual));
325 
326     for (int i{ 1 }; i < cols; i++)
327     {
328         rects[i] = rects[0];
329         rects[i].left += (i * (col_width + gap_width));
330         rects[i].right += (i * (col_width + gap_width));
331     }
332     for (int i{}; i < cols; i++)
333     {
334         int index;
335         if (low_freq_in_center)
336         {
337             if (i < cols / 2)
338                 index = (-i + cols / 2) * 2 - 1;
339             else
340                 index = (i - cols / 2) * 2;
341         }
342         else
343         {
344             index = i;
345         }
346         if (index >= cols)
347             index = cols;
348         if (index < 0)
349             index = 0;
350         float spetral_data = CPlayer::GetInstance().GetSpectralData()[index * (SPECTRUM_COL / cols)];
351         float peak_data = CPlayer::GetInstance().GetSpectralPeakData()[index * (SPECTRUM_COL / cols)];
352 
353         CRect rect_tmp{ rects[i] };
354         int spetral_height = static_cast<int>(spetral_data * rects[0].Height() / 30 * theApp.m_app_setting_data.sprctrum_height / 100);
355         int peak_height = static_cast<int>(peak_data * rects[0].Height() / 30 * theApp.m_app_setting_data.sprctrum_height / 100);
356         if (spetral_height < 0 || CPlayer::GetInstance().IsError()) spetral_height = 0;		//如果播放出错,不显示频谱
357         if (peak_height < 0 || CPlayer::GetInstance().IsError()) peak_height = 0;
358 
359         int peak_rect_height = max(theApp.DPIRound(1.1), gap_width / 2);        //顶端矩形的高度
360         spetral_height += peak_rect_height;                                     //频谱至少和顶端矩形一样高
361         peak_height += peak_rect_height;
362 
363         rect_tmp.top = rect_tmp.bottom - spetral_height;
364         if (rect_tmp.top < rects[0].top) rect_tmp.top = rects[0].top;
365         FillRect(rect_tmp, color, true);
366         //绘制倒影
367         if (draw_reflex)
368         {
369             CRect rc_invert = rect_tmp;
370             rc_invert.bottom = rect_tmp.top + rect_tmp.Height() * 2 / 3;
371             rc_invert.MoveToY(rect_tmp.bottom + gap_width);
372             FillAlphaRect(rc_invert, color, 96, true);
373         }
374 
375         //绘制顶端
376         CRect rect_peak{ rect_tmp };
377         rect_peak.bottom = rect_tmp.bottom - peak_height - gap_width;
378         rect_peak.top = rect_peak.bottom - peak_rect_height;
379         FillRect(rect_peak, color, true);
380         ////绘制顶端倒影
381         //CRect rc_peak_invert = rect_peak;
382         //rc_peak_invert.MoveToY(rc_invert.top + peak_height + theApp.DPIRound(1.1));
383         //FillAlphaRect(rc_peak_invert, color, 96);
384     }
385 
386 }
387 
388 int CUIDrawer::DPI(int pixel)
389 {
390     if (theApp.m_ui_data.full_screen && !m_for_cortana_lyric)
391         return static_cast<int>(theApp.DPI(pixel * CONSTVAL::FULL_SCREEN_ZOOM_FACTOR));
392     else
393         return theApp.DPI(pixel);
394 }
395 
396 void CUIDrawer::DrawLyricDoubleLine(CRect rect, LPCTSTR lyric, LPCTSTR next_lyric, int progress, bool switch_flag, int fade_percent)
397 {
398     CFont* pOldFont = SetLyricFont();
399     static bool swap{};
400     swap ^= switch_flag;
401 
402     CRect up_rect{ rect }, down_rect{ rect };		//上半部分和下半部分歌词的矩形区域
403     up_rect.bottom = up_rect.top + (up_rect.Height() / 2);
404     down_rect.top = down_rect.bottom - (down_rect.Height() / 2);
405 
406     //根据下一句歌词的文本计算需要的宽度,从而实现下一行歌词右对齐
407     //GetDC()->SelectObject(&theApp.m_font_set.lyric.GetFont(theApp.m_ui_data.full_screen));
408     int width;
409     if (!swap)
410         width = GetTextExtent(next_lyric).cx;
411     else
412         width = GetTextExtent(lyric).cx;
413     if (width < rect.Width())
414         down_rect.left = down_rect.right - width;
415 
416     COLORREF color1, color2;
417     if (theApp.m_lyric_setting_data.lyric_karaoke_disp)
418     {
419         color1 = m_colors.color_text;
420         color2 = m_colors.color_text_2;
421     }
422     else
423     {
424         color1 = color2 = CColorConvert::GetGradientColor(m_colors.color_text_2, m_colors.color_text, fade_percent);
425     }
426 
427     // 绘制当前歌词
428     if (!swap)
429     {
430         DrawWindowText(up_rect, lyric, color1, color2, progress);
431         DrawWindowText(down_rect, next_lyric, m_colors.color_text_2);
432     }
433     else
434     {
435         DrawWindowText(up_rect, next_lyric, m_colors.color_text_2);
436         DrawWindowText(down_rect, lyric, color1, color2, progress);
437     }
438     SetFont(pOldFont);
439 }
440 
441 CFont* CUIDrawer::SetLyricFont()
442 {
443     if (!m_for_cortana_lyric)
444         return SetFont(&theApp.m_font_set.lyric.GetFont(theApp.m_ui_data.full_screen));
445     else
446         return SetFont(&theApp.m_font_set.cortana.GetFont());
447 }
448 
449 CFont* CUIDrawer::SetLyricFontTranslated()
450 {
451     if (!m_for_cortana_lyric)
452         return SetFont(&theApp.m_font_set.lyric_translate.GetFont(theApp.m_ui_data.full_screen));
453     else
454         return SetFont(&theApp.m_font_set.cortana_translate.GetFont());
455 }
456