Release v0.6.57: multi-PC input, shared clipboard and resilient Settings Save
This commit is contained in:
1 parent
82292c8a7e
commit
5de010ee44
39 files changed
+1707
-312
No files matched your search
@@ -0,0 +1,111 @@
|
||||
struct LanGridMetrics
|
||||
{
|
||||
int left=-1,right=1,top=-1,bottom=1,width{},height{};
|
||||
RECT cell(int x,int y)const{return {(x-left)*width/(right-left+1),(y-top)*height/(bottom-top+1),(x-left+1)*width/(right-left+1),(y-top+1)*height/(bottom-top+1)};}
|
||||
POINT point(POINT p)const{return {std::clamp(left+int(p.x)*(right-left+1)/std::max(1,width),left,right),std::clamp(top+int(p.y)*(bottom-top+1)/std::max(1,height),top,bottom)};}
|
||||
};
|
||||
LanGridMetrics lanGridMetrics(HWND w,const LanView &v)
|
||||
{
|
||||
RECT r{};GetClientRect(w,&r);LanGridMetrics g;g.width=r.right;g.height=r.bottom;
|
||||
const auto positions=v.snapshot.value("positions",nlohmann::json::object());
|
||||
for(auto &[id,cell]:positions.items()){
|
||||
int x=cell[0],y=cell[1];g.left=std::min(g.left,x-1);g.right=std::max(g.right,x+1);g.top=std::min(g.top,y-1);g.bottom=std::max(g.bottom,y+1);
|
||||
}
|
||||
g.left=std::max(-8,g.left);g.right=std::min(8,g.right);g.top=std::max(-8,g.top);g.bottom=std::min(8,g.bottom);return g;
|
||||
}
|
||||
void moveLanMonitor(HWND parent,LanView &v,const std::string &peer,int x,int y)
|
||||
{
|
||||
try{lanRequest(*v.app,{{"action","position"},{"peer",peer},{"x",x},{"y",y}});v.error.clear();}
|
||||
catch(const std::exception &e){v.error=wide(e.what());}
|
||||
refreshLan(parent,v);
|
||||
}
|
||||
LRESULT CALLBACK lanGridProc(HWND w,UINT msg,WPARAM wp,LPARAM lp)
|
||||
{
|
||||
auto *v=reinterpret_cast<LanView*>(GetWindowLongPtrW(w,GWLP_USERDATA));
|
||||
if(msg==WM_NCCREATE){v=static_cast<LanView*>(reinterpret_cast<CREATESTRUCTW*>(lp)->lpCreateParams);SetWindowLongPtrW(w,GWLP_USERDATA,reinterpret_cast<LONG_PTR>(v));}
|
||||
if(!v)return DefWindowProcW(w,msg,wp,lp);
|
||||
auto parent=GetParent(w);
|
||||
if(msg==WM_ERASEBKGND)return 1;
|
||||
if(msg==WM_GETDLGCODE)return DLGC_WANTARROWS;
|
||||
if(msg==WM_SETFOCUS||msg==WM_KILLFOCUS){InvalidateRect(w,nullptr,FALSE);return 0;}
|
||||
if(msg==WM_LBUTTONDOWN){
|
||||
SetFocus(w);POINT p{GET_X_LPARAM(lp),GET_Y_LPARAM(lp)};auto g=lanGridMetrics(w,*v);
|
||||
const auto positions=v->snapshot.value("positions",nlohmann::json::object());
|
||||
for(auto &[id,cell]:positions.items()){
|
||||
auto r=g.cell(cell[0],cell[1]);if(!PtInRect(&r,p))continue;
|
||||
auto found=std::find(v->ids.begin(),v->ids.end(),id);if(found==v->ids.end())break;
|
||||
SendDlgItemMessageW(parent,702,LB_SETCURSEL,std::distance(v->ids.begin(),found),0);
|
||||
v->dragPeer=id;v->dragStart=p;v->dragPoint=p;v->dragMoved=false;SetCapture(w);refreshLan(parent,*v);break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if(msg==WM_MOUSEMOVE&&!v->dragPeer.empty()){
|
||||
v->dragPoint={GET_X_LPARAM(lp),GET_Y_LPARAM(lp)};
|
||||
if(std::abs(v->dragPoint.x-v->dragStart.x)>scale(w,4)||std::abs(v->dragPoint.y-v->dragStart.y)>scale(w,4))v->dragMoved=true;
|
||||
InvalidateRect(w,nullptr,FALSE);return 0;
|
||||
}
|
||||
if(msg==WM_LBUTTONUP&&!v->dragPeer.empty()){
|
||||
auto peer=v->dragPeer;auto p=lanGridMetrics(w,*v).point({GET_X_LPARAM(lp),GET_Y_LPARAM(lp)});bool moved=v->dragMoved;
|
||||
RECT client{};GetClientRect(w,&client);POINT mouse{GET_X_LPARAM(lp),GET_Y_LPARAM(lp)};
|
||||
v->dragPeer.clear();v->dragMoved=false;ReleaseCapture();
|
||||
if(moved&&PtInRect(&client,mouse))moveLanMonitor(parent,*v,peer,p.x,p.y);
|
||||
InvalidateRect(w,nullptr,FALSE);return 0;
|
||||
}
|
||||
if(msg==WM_CAPTURECHANGED){v->dragPeer.clear();v->dragMoved=false;InvalidateRect(w,nullptr,FALSE);return 0;}
|
||||
if(msg==WM_KEYDOWN){
|
||||
if(wp==VK_ESCAPE&&!v->dragPeer.empty()){v->dragPeer.clear();ReleaseCapture();InvalidateRect(w,nullptr,FALSE);return 0;}
|
||||
auto peer=selectedLan(parent,*v);auto positions=v->snapshot.value("positions",nlohmann::json::object());
|
||||
if(positions.contains(peer)&&(wp==VK_LEFT||wp==VK_RIGHT||wp==VK_UP||wp==VK_DOWN)){
|
||||
int x=positions[peer][0],y=positions[peer][1];x+=(wp==VK_RIGHT)-(wp==VK_LEFT);y+=(wp==VK_DOWN)-(wp==VK_UP);
|
||||
moveLanMonitor(parent,*v,peer,x,y);return 0;
|
||||
}
|
||||
}
|
||||
if(msg==WM_PAINT){
|
||||
PAINTSTRUCT ps{};auto target=BeginPaint(w,&ps);RECT bounds{};GetClientRect(w,&bounds);
|
||||
auto dc=CreateCompatibleDC(target);auto bitmap=CreateCompatibleBitmap(target,std::max(1L,bounds.right),std::max(1L,bounds.bottom));auto oldBitmap=SelectObject(dc,bitmap);
|
||||
auto fill=CreateSolidBrush(RGB(247,249,251));FillRect(dc,&bounds,fill);DeleteObject(fill);
|
||||
auto oldFont=SelectObject(dc,GetPropW(parent,L"Font"));SetBkMode(dc,TRANSPARENT);
|
||||
auto gridPen=CreatePen(PS_DOT,1,RGB(214,222,230));auto oldPen=SelectObject(dc,gridPen);auto oldBrush=SelectObject(dc,GetStockObject(NULL_BRUSH));
|
||||
auto g=lanGridMetrics(w,*v);
|
||||
for(int x=g.left;x<=g.right+1;++x){int at=(x-g.left)*g.width/(g.right-g.left+1);MoveToEx(dc,at,0,nullptr);LineTo(dc,at,g.height);}
|
||||
for(int y=g.top;y<=g.bottom+1;++y){int at=(y-g.top)*g.height/(g.bottom-g.top+1);MoveToEx(dc,0,at,nullptr);LineTo(dc,g.width,at);}
|
||||
auto selected=selectedLan(parent,*v);
|
||||
auto draw=[&](const std::string &id,const std::wstring &name,int x,int y,bool local){
|
||||
auto cell=g.cell(x,y);int margin=scale(w,8),cellWidth=cell.right-cell.left;
|
||||
TEXTMETRICW metrics{};GetTextMetricsW(dc,&metrics);
|
||||
RECT title{cell.left+scale(w,3),cell.bottom-scale(w,3)-metrics.tmHeight,cell.right-scale(w,3),cell.bottom-scale(w,3)};
|
||||
int top=cell.top+scale(w,4),height=std::min(scale(w,36),int(title.top)-top-scale(w,8));
|
||||
int width=std::min({scale(w,110),cellWidth-2*margin,height*2});
|
||||
if(width<8||height<8)return;
|
||||
int left=(cell.left+cell.right-width)/2;
|
||||
bool active=!local&&v->snapshot.value("shareInput",false)&&v->snapshot.value("controlMode",0)!=0&&v->snapshot.value("controlPeer",std::string{})==id;
|
||||
COLORREF color=id==selected?RGB(0,105,190):active?RGB(30,130,90):RGB(105,120,133);
|
||||
auto pen=CreatePen(PS_SOLID,scale(w,2),color);SelectObject(dc,pen);
|
||||
auto brush=CreateSolidBrush(local?RGB(220,237,252):RGB(255,255,255));SelectObject(dc,brush);
|
||||
RoundRect(dc,left,top,left+width,top+height,scale(w,4),scale(w,4));SelectObject(dc,GetStockObject(NULL_BRUSH));DeleteObject(brush);
|
||||
int center=left+width/2;MoveToEx(dc,center,top+height,nullptr);LineTo(dc,center,top+height+scale(w,5));
|
||||
MoveToEx(dc,center-scale(w,12),top+height+scale(w,5),nullptr);LineTo(dc,center+scale(w,12),top+height+scale(w,5));
|
||||
RECT screen{left+scale(w,3),top+scale(w,2),left+width-scale(w,3),top+height-scale(w,2)};SetTextColor(dc,color);
|
||||
const wchar_t *caption=local?L"Local":active?L"Active":L"Paired";
|
||||
if(height>=metrics.tmHeight+scale(w,4))DrawTextW(dc,caption,-1,&screen,DT_CENTER|DT_VCENTER|DT_SINGLELINE|DT_END_ELLIPSIS);
|
||||
SetTextColor(dc,RGB(30,40,50));
|
||||
DrawTextW(dc,name.c_str(),-1,&title,DT_CENTER|DT_SINGLELINE|DT_END_ELLIPSIS|DT_NOPREFIX);
|
||||
if(id==selected){RECT outline{cell.left+scale(w,2),cell.top+scale(w,2),cell.right-scale(w,2),cell.bottom-scale(w,2)};if(GetFocus()==w)DrawFocusRect(dc,&outline);}
|
||||
SelectObject(dc,gridPen);DeleteObject(pen);
|
||||
};
|
||||
draw("",wide(v->snapshot.value("name",std::string("This PC"))),0,0,true);
|
||||
for(const auto &peer:v->snapshot.value("peers",nlohmann::json::array())){
|
||||
auto id=peer.at("id").get<std::string>();auto positions=v->snapshot.value("positions",nlohmann::json::object());
|
||||
if(positions.contains(id))draw(id,wide(peer.at("name").get<std::string>()),positions[id][0],positions[id][1],false);
|
||||
}
|
||||
if(v->dragMoved&&!v->dragPeer.empty()){
|
||||
auto point=g.point(v->dragPoint);auto r=g.cell(point.x,point.y);bool valid=true;
|
||||
try{lan::validateCell(v->snapshot.at("positions"),v->dragPeer,point.x,point.y);}catch(...){valid=false;}
|
||||
auto pen=CreatePen(PS_SOLID,scale(w,2),valid?RGB(0,105,190):RGB(190,50,40));SelectObject(dc,pen);
|
||||
Rectangle(dc,r.left+2,r.top+2,r.right-2,r.bottom-2);SelectObject(dc,gridPen);DeleteObject(pen);
|
||||
}
|
||||
SelectObject(dc,oldPen);SelectObject(dc,oldBrush);SelectObject(dc,oldFont);DeleteObject(gridPen);
|
||||
BitBlt(target,0,0,bounds.right,bounds.bottom,dc,0,0,SRCCOPY);SelectObject(dc,oldBitmap);DeleteObject(bitmap);DeleteDC(dc);EndPaint(w,&ps);return 0;
|
||||
}
|
||||
return DefWindowProcW(w,msg,wp,lp);
|
||||
}
|
||||
Reference in new issue
Block a user