double OOPLyric::GetLineProgress() const { LineInfo *currLine = *m_currLine; size_t lineHasGone = m_stopWatch->Time() - currLine->GetStartTime(); if (lineHasGone >= currLine->GetMilSeconds()) { return 1; } // 假如除数为 0,不会来到这里 return double(lineHasGone) / currLine->GetMilSeconds(); }
void OOPLyric::OnMouseEvent(VdkMouseEvent &e) { switch (e.evtCode) { case RIGHT_UP: { // 拖动歌词时不要响应右键事件 if (TestState(OLST_DRAGGING)) { return; } wxPoint menuPos(AbsoluteRect().GetPosition()); menuPos.x += e.mousePos.x; menuPos.y += e.mousePos.y; m_Window->ShowContextMenu(this, menuPos); break; } case LEFT_DOWN: { m_draggDistance = e.mousePos.y; SetAddinState(OLST_DRAGGING_STARTED); break; } case DRAGGING: { // 不接受先在一首歌的会话中拖动歌词,然后在未释放鼠标的情况下 // 另一首歌开始播放,继续前面的拖动事件 if (!TestState(OLST_DRAGGING_STARTED)) { break; } if (!IsOk()) { break; } if (m_timer.IsRunning()) { Pause(); SetAddinState(OLST_RUNNING_BEFORE_DRAGGING); } SetAddinState(OLST_DRAGGING); //====================================================== int ystart; GetViewStartCoord(NULL, &ystart); int dY = e.mousePos.y - m_draggDistance; m_draggDistance = e.mousePos.y; // 这是一行我们手工加上去的空行 int rowHeight = GetRowHeight(); int upperBound = (*(m_parser->begin()))->GetLyric().empty() ? rowHeight : 0; // 无法继续将帘布向上卷(再拖下去就到下一首了) // 我们将拖到尽头的事件视为无效 bool lastLine = false; // 情景:将虚拟画布像窗帘一样向下拖 // 拖动尽头了,不能再把窗帘哪怕拖下一寸 if (ystart - dY < upperBound) { dY = ystart - upperBound; // 加加减减的原因参照(*) } else { int maxy; GetMaxViewStartCoord(NULL, &maxy); // 将帘布向上卷,卷到尽头了,再卷下去就会导致 // 无法完整遮住窗口 if (ystart - dY > maxy) { dY = ystart - maxy; // 加加减减的原因参照(*) // 无效拖动事件 lastLine = true; } } if (dY) { SetViewStart(0, ystart - dY, &e.dc); //………………(*) } // 无效拖动事件 if (lastLine) { m_draggHit = m_parser->end(); } //=================================================== // 绘制中间线段 wxRect rc(GetAbsoluteRect()); int y = rc.y + m_blankLinesTop * GetRowHeight(); m_Window->ResetDcOrigin(e.dc); e.dc.SetPen(wxPen(m_TextColor)); e.dc.DrawLine(rc.x, y, rc.GetRight(), y); break; } case NORMAL: case LEFT_UP: { if (!TestState(OLST_DRAGGING) || !TestState(OLST_DRAGGING_STARTED)) { break; } wxASSERT(IsOk()); //----------------------------------------------------- RemoveState(OLST_DRAGGING | OLST_DRAGGING_STARTED); // 拖到最下面了,尽头 if (m_draggHit == m_parser->end()) { RefreshState(&e.dc); } else { int ystart; // 起始绘图坐标 GetViewStartCoord(NULL, &ystart); LineInfo *lineDraggHit = *m_draggHit; size_t timeToGo = lineDraggHit->GetStartTime(); int rowHeight = GetRowHeight(); double linePercentage = double(ystart % rowHeight) / rowHeight ; timeToGo += lineDraggHit->GetMilSeconds() * linePercentage; // 歌词可能并不匹配正在播放的歌曲 if (timeToGo < m_parser->GetTimeSum()) { m_currLine = m_draggHit; if (IsReadyForEvent()) { FireEvent(&e.dc, (void *) timeToGo); } } } bool resume = TestState(OLST_RUNNING_BEFORE_DRAGGING); if (resume) { RemoveState(OLST_RUNNING_BEFORE_DRAGGING); Resume(); } break; } default: break; } }
VdkCusdrawReturnFlag OOPLyric::DoDrawCellText (const VdkLcCell *cell, int col_index, int index0, wxDC &dc, VdkLcHilightState state) { wxASSERT(m_parser); // 注意:index 是不计算加入的空行的 int index = (int) (cell->GetClientData()) - 1; if (index == -1) { // 此时 ClientData == NULL,是我们添加的空行 return VCCDRF_DODEFAULT; } dc.SetTextForeground(m_TextColor); // 暂停时高亮当前行,情景见于用户正在拖动歌词。 // 另外先暂停,然后拖动歌词完毕,此时假如歌词秀是以卡拉OK // 方式进行显示时,那么不会保持半高亮的状态,而是全高亮。 if (TestState(OLST_PAUSED)) { int yStart; GetViewStartCoord(NULL, &yStart); int rowHeight = GetRowHeight(); int dragRegion = yStart + rowHeight * m_blankLinesTop; int index2 = index + m_blankLinesTop; // 检测拖动歌词时中间线下面的一行 if (rowHeight * index2 <= dragRegion && rowHeight * (index2 + 1) > dragRegion) { m_draggHit = m_parser->GetLine(index); dc.SetTextForeground(m_HilightColor); } return VCCDRF_DODEFAULT; } LineInfo *currLine = *m_currLine; // TODO: 是否考虑优化? size_t currLineIndex = m_parser->IndexOf(m_currLine); int lineHasGone = m_stopWatch->Time() - currLine->GetStartTime(); if ((index == currLineIndex - 1) && !cell->GetLabel().empty()) { // 使用渐变色还原上一句歌词 if (lineHasGone < ALPHA_SHOW_LAST_LINE_MS) { unsigned char r, g, b; double alpha2 = double(lineHasGone) / ALPHA_SHOW_LAST_LINE_MS; double alpha1 = 1 - alpha2; r = m_HilightColor.Red() * alpha1 + m_TextColor.Red() * alpha2; g = m_HilightColor.Green() * alpha1 + m_TextColor.Green() * alpha2; b = m_HilightColor.Blue() * alpha1 + m_TextColor.Blue() * alpha2; dc.SetTextForeground(wxColour(r, g, b)); } } else if (index == currLineIndex) { // 高亮当前文本行 // 尽管这是一种很罕见的情况,但一旦出现了就会导致下面 (*) 表达式 // 的除数为 0 if (currLine->GetMilSeconds() == 0) { return VCCDRF_DODEFAULT; } if (!cell->IsEmpty()) { /* 经验教训: 1. SetClippingRegiion 有叠加效应,因此在执行新的 SetClippingRegiion 前别忘了销毁原来的 ClippingRegiion 。 2. 关于表达式中整数与浮点数混用:注意中间运算结果会 被强制转换成 int 然后参加下一步的运算,并不是对 最终结果进行转换,使之成为一个浮点数。 */ const int rowHeight = GetRowHeight(); int y = (currLineIndex + m_blankLinesTop) * rowHeight; cell->DrawLabel(dc, 0, y); // (*) double lineProgress = double(lineHasGone) / currLine->GetMilSeconds(); // 要实现 KALA-OK 效果的文本宽度 int w = (m_Rect.width - cell->GetX_Padding() * 2) * lineProgress; wxRect rc(GetAbsoluteRect()); const int bottom = rc.y + rc.height; int yStart; VdkScrolledWindow::GetViewStartCoord(NULL, &yStart); rc.y += y - yStart; rc.width = cell->GetX_Padding() + w; rc.height = rowHeight; // 不能使 KALA-OK 效果的 ClippingRegion 超出列表窗口 if ((rc.y + rc.height) > bottom) { rc.height = bottom - rc.y; } VdkDcDeviceOriginSaver saver(dc); dc.SetDeviceOrigin(0, 0); VdkDcClippingRegionDestroyer destroyer(dc, rc); # ifdef __WXGTK__ dc.SetBrush(m_crossBrush1); dc.DrawRectangle(rc); # endif dc.SetTextForeground(m_HilightColor); cell->DrawLabel(dc, rc.x, rc.y); } return VCCDRF_SKIPDEFAULT; } return VCCDRF_DODEFAULT; }