Files
winapp/ContentPanel.cpp
2026-06-12 22:37:02 +08:00

647 lines
15 KiB
C++

//---------------------------------------------------------------------------
#include <vcl.h>
#include <windows.h>
#pragma hdrstop
#include "GameUnit.h"
#include "ContentPanel.h"
#include "GlobalUnit.h"
#include "UtilityUnit.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
TColor clrAxis = TColor(RGB(3,5,7)), //XY轴及边框
clrLineTen = TColor(RGB(215,215,215)), //十倍横线
clrLineNor = TColor(0x00EDEDED),// TColor(RGB(250,250,250)), //普通线
clrHeadFont = TColor(RGB(83,109,165)), //头文字
clrSelect = TColor(RGB(150, 155, 159));
TColor u_clrLink[] = {clBlue, clWebLimeGreen, clRed, clWebRoyalBlue};
//---------------------------------------------------------------------------
__fastcall TContentPanel::TContentPanel(TComponent* AOwner)
: Vcl::Extctrls::TPanel(AOwner)
{
Visible = false;
DoubleBuffered = true;
Ctl3D = false;
Color = clBtnHighlight;//clWhite;
BorderStyle = bsSingle;
OnMouseMove = PanMouseMove;
m_nTopGap = m_nLeftBound = m_nTopBound = 0;
m_nRows = 50;
m_xScaleGap = m_nGap = DPIX(8);
m_yZero = 0;
m_yScaleGap = m_nGap * 5;
m_nRightBound = m_nLeftBound + m_nGap * m_nCols;
m_nBottomBound = m_nTopBound + m_nGap * m_nRows;
m_nAmoUnit = 100;
m_nAmoMax = m_nAmoMin = m_nRowMax = m_nRowMin = 0;
m_iType = m_nOriDots = 0;
m_iSample = 1;
m_iMask = 0;
m_iPeakU = m_iPeakL = -1;
m_xHalfGap = m_xScaleGap>>1;
m_iHint = m_xMaxHint = 0;
}
//---------------------------------------------------------------------------
void __fastcall TContentPanel::CreateWnd(void)
{
Vcl::Extctrls::TPanel::CreateWnd();
for(int i=0; i<2; i++){
m_lblHint[i] = new TLabel(NULL);
m_lblHint[i]->Visible = false;
m_lblHint[i]->Parent = this;
m_lblHint[i]->Font->Color = clrHeadFont;
// m_lblHint[i]->AutoSize = false;
// m_lblHint[i]->Width = 52;
// m_lblHint[i]->Height = 16;
// m_lblHint[i]->Alignment = taCenter;
// m_lblHint[i]->Layout = tlCenter;
}
UpdateView(0, true);
}
//---------------------------------------------------------------------------
__fastcall TContentPanel::~TContentPanel()
{
for(int i=0; i<2; i++)
delete m_lblHint[i];
}
//---------------------------------------------------------------------------
void __fastcall TContentPanel::SetView(int iType)
{
if(iType!=m_iType){
m_iType = iType;
m_nAmoUnit = iType==3 ? 10 : 100;
UpdateView(iType, true);
}
}
//---------------------------------------------------------------------------
void __fastcall TContentPanel::WMSize(TWMSize &msg)
{
m_nCols = (msg.Width - m_nLeftBound - DPIX(4)) / m_nGap;
m_nRightBound = m_nLeftBound + m_nGap * m_nCols;
m_xScaleGap = m_nGap;
m_xHalfGap = m_xScaleGap>>1;
m_aDots.clear();
m_nOriDots = 0;
m_iSample = 1;
m_iMask = 0;
if(!CalcXUnit())
InvalidateRect(Handle, NULL, TRUE);
}
//---------------------------------------------------------------------------
void __fastcall TContentPanel::Paint()
{
TRect rtInvalid = Canvas->ClipRect;
bool bPeakU = m_iPeakU>=0, bPeakL = m_iPeakL>=0;
vector<TDot>& aDots = m_aDots.empty() ? g_aDots[m_iType] : m_aDots;
//绘制选中线
if(m_iHint>0){
int x = m_xZero + m_iHint * m_xScaleGap;
Canvas->Pen->Color = clrSelect;
Canvas->Pen->Width = 1;
Canvas->Pen->Style = psDot;
Canvas->MoveTo(x, m_nTopBound+1);
Canvas->LineTo(x, m_nBottomBound-1);
}
int x0 = m_xZero,
y0 = m_nTopBound,
x1 = x0,
l = ( rtInvalid.left - 1 - x0 ) / m_xScaleGap,
r = ( rtInvalid.right + 1 - x0 ) / m_xScaleGap + 1,
total = aDots.size();
if(total>0)
y0 += (int)((m_nRowMax - aDots[0].val) * m_yScale + 0.5);
Canvas->Pen->Color = u_clrLink[m_iType];
Canvas->Pen->Width = 2;
Canvas->MoveTo( x0, y0 );
for( int i=1; i<total; i++ ){
x1 += m_xScaleGap;
int c = (m_nRowMax - aDots[i].val) * m_yScale + 0.5;
int y1 = m_nTopBound + c;
if(l <= i && r >= i){
//绘制连接线
Canvas->LineTo( x1, y1 );
}
else{
Canvas->MoveTo( x1, y1 );
}
}
}
//---------------------------------------------------------------------------
void __fastcall TContentPanel::WMEraseBkgnd(TWMEraseBkgnd &Message)
{
TCanvas* tempCanvas = new TCanvas;
tempCanvas->Handle = Message.DC;
//背景色
TRect rect( 0, 0, ClientWidth, ClientHeight );
tempCanvas->Brush->Color = clBtnFace;//clWhite;
tempCanvas->FillRect( rect );
//外边框
rect.left = m_nLeftBound;
rect.right = m_nRightBound + 1;
rect.top = m_nTopBound;
rect.bottom = m_nBottomBound + 1;
tempCanvas->Brush->Color = clrAxis;
tempCanvas->FrameRect( rect );
//纵线
int x = m_xZero,
t = rect.top+1,
b = rect.bottom-1;
tempCanvas->Pen->Color = clrLineNor;
for( int i=1; i<m_nCols; i++ ){
x += m_nGap;
tempCanvas->MoveTo( x, t );
tempCanvas->LineTo( x, b );
}
//刻度
tempCanvas->Font->Color = clrHeadFont;
tempCanvas->Brush->Style = bsClear;
int y = m_nTopGap,//, iY0 = 0,
num = m_nRowMax;
for( int i=0; i<=10; i++ ){
String sTxt = IntToStr(num);
TSize szTxt = tempCanvas->TextExtent( sTxt );
x = m_nLeftBound - szTxt.cx - 1;
tempCanvas->TextOut( x, y, sTxt );
num -= m_iAmoUnit;
y += m_yScaleGap;
}
//横线
tempCanvas->Brush->Style = bsSolid;
y = m_nTopBound;
int l = m_xZero+1,
r = rect.right-1;
for( int i=1; i<m_nRows; i++ ){
y += m_nGap;
tempCanvas->Pen->Color = i==m_yZero ? clrAxis
: i % 5 ? clrLineNor
: clrLineTen;
tempCanvas->MoveTo( l, y );
tempCanvas->LineTo( r, y );
}
delete tempCanvas;
Message.Result = true;
}
//---------------------------------------------------------------------------
void __fastcall TContentPanel::UpdateView(int iType, bool bInit)
{
if(m_iType!=iType)
return;
vector<TDot>& dots = g_aDots[m_iType];
int nTotal = dots.size();
if(bInit){
int nMax(0), nMin(0);
if(nTotal>0){
nMax = nMin = dots[0].val;
for(int i=1; i<nTotal; i++){
int p = dots[i].val;
if(p>nMax){
nMax = p;
m_iPeakU = i;
}
else if(p<nMin){
nMin = p;
m_iPeakL = i;
}
}
}
m_nAmoMax = nMax;
m_nAmoMin = nMin;
m_xScaleGap = m_nGap;
m_xHalfGap = m_xScaleGap>>1;
m_aDots.clear();
m_nOriDots = 0;
m_iSample = 1;
m_iMask = 0;
CalcYUnit(nMax, nMin, true);
return;
}
if(!nTotal || m_nOriDots==nTotal)
return;
int nOriCount = m_nOriDots,
nLocCount = m_aDots.size();
bool bYChange = false;
for(int i=m_nOriDots; i<nTotal; i++){
int p = dots[i].val;
if(!m_aDots.empty()){
m_aDots.push_back(dots[i]);
}
if(p > m_nAmoMax){
m_nAmoMax = p;
bYChange = true;
if(p > m_nRowMax) m_iPeakU = nTotal - 1;
}
else if(p < m_nAmoMin){
m_nAmoMin = p;
bYChange = true;
if(p < m_nRowMin) m_iPeakL = nTotal -1;
}
}
m_nOriDots = nTotal;
if(bYChange && CalcYUnit(m_nAmoMax, m_nAmoMin))
return;
else if(CalcXUnit())
return;
TRect rectDraw;
rectDraw.top = m_nTopBound - 1;
rectDraw.bottom = m_nBottomBound + 1;
if(m_aDots.empty()){
rectDraw.left = m_xZero + (nOriCount-1) * m_xScaleGap;
rectDraw.right = rectDraw.Left + (nTotal-nOriCount) * m_xScaleGap + 2;
}
else{
rectDraw.left = m_xZero + (nLocCount-1) * m_xScaleGap;
rectDraw.right = rectDraw.Left + (m_aDots.size()-nLocCount) * m_xScaleGap + 2;
}
InvalidateRect( Handle, &rectDraw, false );
}
//---------------------------------------------------------------------------
void __fastcall TContentPanel::Resample()
{
vector<TDot>& dots = g_aDots[m_iType];
m_aDots.clear();
m_aDots.push_back(dots[0]);
int total = dots.size(),
num = total-20; //保留最少20个点
bool bNoSample = m_iSample==1,
bMask = m_iMask>m_iSample;
int iS = 0,
iM = !bMask,
iMask = m_iMask + 1;
TDot dtMax = dots[0],
dtMin = dots[0];
for(int i=1; i<num; i++){
if(bNoSample || ++iS==m_iSample){
iS = 0;
if(bMask && ++iM==iMask)
iM = 0;
}
if(dots[i].val>dtMax.val)
dtMax = dots[i];
else if(dots[i].val<dtMin.val)
dtMin = dots[i];
if(iM && !iS){
bool bPushMax = true;
int nTmp = m_aDots.back().val;
if(dtMin.val>=nTmp || dtMax.val==m_nAmoMax)
bPushMax = true;
else if(dtMax.val<=nTmp || dtMin.val==m_nAmoMin)
bPushMax = false;
else if(m_aDots.size()>1){
int nSec = m_aDots[m_aDots.size()-2].val;
if(nTmp<nSec)
bPushMax = false;
// else
// bPushMax = true;
}
else if(dtMax.val-nTmp < nTmp-dtMin.val)
bPushMax = false;
// else
// bPushMax = true;
if(bPushMax && dtMax.ts)
m_aDots.push_back(dtMax);
else if(!bPushMax && dtMin.ts)
m_aDots.push_back(dtMin);
dtMax = dtMin = dots[i];
}
}
for(int i=num; i<total; i++)
m_aDots.push_back(dots[i]);
m_xMaxHint = m_nLeftBound + m_aDots.size() * m_xScaleGap - m_xHalfGap;
m_nOriDots = total;
}
//---------------------------------------------------------------------------
bool __fastcall TContentPanel::CalcXUnit()
{
int nWidth = ClientWidth - m_nLeftBound - DPIX(4) - 1;
vector<TDot>& aDots = m_aDots.empty() ? g_aDots[m_iType] : m_aDots;
int total = aDots.size();
int xGap = m_xScaleGap;
int xMinGap = 3;
if(total * xGap < nWidth){
m_xMaxHint = m_nLeftBound + total * m_xScaleGap - m_xHalfGap;
return false;
}
total = g_aDots[m_iType].size();
while(xGap>xMinGap && total * xGap >= nWidth){
xGap--;
}
if(xGap!=m_xScaleGap){
m_xScaleGap = xGap;
m_xHalfGap = m_xScaleGap>>1;
if(total * xGap < nWidth){
// DebugPrint("Change xGap: %d", xGap);
m_xMaxHint = m_nLeftBound + total * xGap - m_xHalfGap;
if(m_iHint>0){
m_iHint = 0;
m_lblHint[0]->Visible = false;
m_lblHint[1]->Visible = false;
}
InvalidateRect( Handle, NULL, true );
return true;
}
}
int nCol = nWidth / xGap;
int iSample = m_iSample,
iMask = m_iMask;
while((total-20) / iSample + 20 + 10 > nCol){
if(iMask<=iSample)
iMask = iSample + 10;
while(iMask>iSample && (total-20) * iMask / ((iMask+1)*iSample) + 20 + 10 > nCol){
iMask--;
}
if(iMask>iSample)
break;
iSample++;
}
if(iSample!=m_iSample || iMask!=m_iMask){
m_iMask = iMask;
m_iSample = iSample;
// DebugPrint("Change sample: %d mask: %d", iSample, iMask);
}
Resample();
if(m_iHint>0){
m_iHint = 0;
m_lblHint[0]->Visible = false;
m_lblHint[1]->Visible = false;
}
InvalidateRect( Handle, NULL, true );
return true;
}
//---------------------------------------------------------------------------
bool __fastcall TContentPanel::CalcYUnit(int nMax, int nMin, bool bInit)
{
if(bInit || nMax > m_nRowMax || nMin < m_nRowMin ){ //初始化||范围变动
int scale = 100 / m_nAmoUnit;
if(nMax==nMin){
m_nRowMax = (nMax / m_nAmoUnit + 1) * m_nAmoUnit;
m_nRowMin = m_nRowMax - 2 * m_nAmoUnit;
if(m_nRowMax>0 && m_nRowMin<0)
m_yZero = 25;
else
m_yZero = 0;
}
else if(nMax>0 && nMin<0){
nMax = (nMax / m_nAmoUnit + ((nMax % m_nAmoUnit) ? 1 : 0)) * m_nAmoUnit;
nMin = (nMin / m_nAmoUnit - ((nMin % m_nAmoUnit) ? 1 : 0)) * m_nAmoUnit;
//找可整除的0轴
int m = (nMax - nMin) * scale / 50;
if(0 == (nMin * scale) % m){
m_nRowMax = nMax;
m_nRowMin = nMin;
}
else{
int rMax = nMax,
rMin = nMin,
tmpMin = nMin,
rm = m_iType==3 ? 10000 : 10000000;
//寻找最佳方案
bool bUp = g_aDots[m_iType].back().val > 0;
for(int m1=m; m1 < (bUp ? rm : rm+1); m1+=2){ //最后的数据为负,优先扩下半区,或者尽量上下同扩
if(m1>m && 0 == tmpMin % m1){
rMax = nMax;
rMin = tmpMin;
rm = m1;
break;
}
int tmpMax = nMax;
for(int m2=m1+2; -tmpMin>=m2 && m2 < (bUp ? rm+1: rm); m2+=2){ //最后的数据为正,优先扩上半区
tmpMax += m_nAmoUnit;
if(0==tmpMin % m2){
rMax = tmpMax;
rMin = tmpMin;
rm = m2;
break;
}
}
tmpMin -= m_nAmoUnit;
}
m_nRowMax = rMax;
m_nRowMin = rMin;
m = rm;
}
int n = - m_nRowMin * scale / m;
if(n<1) n = 1;
else if(n>49) n = 49;
m_yZero = 50 - n;
}
else{
m_nRowMax = nMax / m_nAmoUnit * m_nAmoUnit;
if(nMax > 0 && nMax % m_nAmoUnit) m_nRowMax += m_nAmoUnit;
m_nRowMin = nMin / m_nAmoUnit * m_nAmoUnit;
if(nMin < 0 && nMin % m_nAmoUnit) m_nRowMin -= m_nAmoUnit;
m_yZero = 0;
}
TSize szTxt1 = Canvas->TextExtent(IntToStr(m_nRowMax)),
szTxt2 = Canvas->TextExtent(IntToStr(m_nRowMin));
// int hhTxt = szTxt1.cy / 2;
// if(hhTxt<16){
// m_nTopGap = 16 - hhTxt;
// m_nTopBound = 16;
// }
// else{
// m_nTopGap = 0;
// m_nTopBound = hhTxt;
// }
m_nTopBound = szTxt1.cy;
m_nTopGap = szTxt1.cy / 2;
m_nBottomBound = m_nTopBound + m_nGap * m_nRows;
m_nLeftBound = max(szTxt1.cx, szTxt2.cx) + 2;
m_xZero = m_nLeftBound;
m_iAmoUnit = (m_nRowMax - m_nRowMin) / 10;
m_yScale = 5.0 * m_nGap / m_iAmoUnit;
m_nCols = (ClientWidth - m_nLeftBound - DPIX(4)) / m_nGap;
m_nRightBound = m_nLeftBound + m_nGap * m_nCols;
CalcXUnit();
InvalidateRect( Handle, NULL, true );
return true;
}
return false;
}
//---------------------------------------------------------------------------
void __fastcall TContentPanel::PanMouseMove(TObject *Sender, TShiftState Shift, int X, int Y)
{
ResetIdle();
int iHit = HitTest(X, Y);
if(m_iHint!=iHit)
ShowTimeHint(iHit);
}
//---------------------------------------------------------------------------
void __fastcall TContentPanel::PanKeyDown(WORD Key)
{
if(Key==37){
if(!m_iHint){
vector<TDot>& aDots = m_aDots.empty() ? g_aDots[m_iType] : m_aDots;
if(aDots.size()>1)
ShowTimeHint(aDots.size()-1);
}
else if(m_iHint>1){
ShowTimeHint(m_iHint-1);
}
}
else if(Key==39){
vector<TDot>& aDots = m_aDots.empty() ? g_aDots[m_iType] : m_aDots;
if(m_iHint>0){
if(m_iHint<aDots.size()-1)
ShowTimeHint(m_iHint+1);
}
else if(aDots.size()>1){
ShowTimeHint(1);
}
}
}
//---------------------------------------------------------------------------
int __fastcall TContentPanel::HitTest(int x, int y)
{
int iHit = 0;
if(y>m_nTopBound && y<m_nBottomBound && x>m_nLeftBound && x<m_xMaxHint){
int dx = x - m_xZero,
p = dx % m_xScaleGap;
if(p <= m_xHalfGap)
iHit = dx / m_xScaleGap;
else
iHit = dx / m_xScaleGap + 1;
}
return iHit;
}
//---------------------------------------------------------------------------
void __fastcall TContentPanel::ShowTimeHint(int iHint)
{
TRect rectDraw;
rectDraw.top = m_nTopBound + 1;
rectDraw.bottom = m_nBottomBound - 1;
if(m_iHint>0){
int x = m_xZero + m_iHint * m_xScaleGap;
rectDraw.left = x - 1;
rectDraw.right = x + 1;
InvalidateRect( Handle, &rectDraw, false );
}
m_iHint = iHint;
if(iHint>0){
int x = m_xZero + iHint * m_xScaleGap;
rectDraw.left = x - 1;
rectDraw.right = x + 1;
InvalidateRect( Handle, &rectDraw, false );
vector<TDot>& aDots = m_aDots.empty() ? g_aDots[m_iType] : m_aDots;
int ts = aDots[iHint].ts - 57600,
r = ts % 86400,
h, m, s;
h = r / 3600, r -= h * 3600;
m = r / 60, r -= m * 60;
s = r;
m_lblHint[0]->Top = m_nTopBound - m_lblHint[0]->Height;
m_lblHint[0]->Caption = IntToStr(aDots[iHint].val);
m_lblHint[1]->Top = m_nBottomBound + 1;
m_lblHint[1]->Caption = Format(String("%.2d:%.2d:%.2d"), ARRAYOFCONST((h, m, s)));
int xMin = m_xZero-1, xMax = m_nRightBound+2;
for(int i=0; i<2; i++){
int l = x - (m_lblHint[i]->Width>>1);
if(l<xMin)
l = xMin;
else if(l > xMax - m_lblHint[i]->Width)
l = xMax - m_lblHint[i]->Width;
m_lblHint[i]->Left = l;
m_lblHint[i]->Visible = true;
}
}
else{
m_lblHint[0]->Visible = false;
m_lblHint[1]->Visible = false;
}
}
//---------------------------------------------------------------------------