diff --git a/MainForm.pas b/MainForm.pas index cfe3f72..6ac9192 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -478,6 +478,7 @@ type procedure WebOnANF(On_: Boolean); procedure WebOnFreqB(Hz: Double); procedure WebOnActiveVfo(Idx: Integer); + procedure WebOnCenter(Hz: Double); // Synchronize-обёртки (выполняются в UI-потоке) procedure SyncWebFreq; procedure SyncWebMode; @@ -498,6 +499,7 @@ type procedure SyncWebANF; procedure SyncWebFreqB; procedure SyncWebActiveVfo; + procedure SyncWebCenter; procedure BtnWfAGCClick(Sender: TObject); procedure BtnWfNFClick(Sender: TObject); procedure BtnHidePanelClick(Sender: TObject); @@ -828,6 +830,7 @@ begin FWebServer.OnANF := WebOnANF; FWebServer.OnFreqB := WebOnFreqB; FWebServer.OnActiveVfo := WebOnActiveVfo; + FWebServer.OnCenter := WebOnCenter; FWebServer.Start; FSMeterPeak := -130; FSMeterMin := -130; @@ -4755,6 +4758,30 @@ begin ActivateVfo(FWebSyncInt); end; +// ── CTUN drag center scroll (из веба) ──────────────────────────────────── + +procedure TMainForm.WebOnCenter(Hz: Double); +begin + FWebSyncFreq := Hz; + FWebSyncM := SyncWebCenter; + TThread.Synchronize(nil, FWebSyncM); +end; + +procedure TMainForm.SyncWebCenter; +begin + // Сдвигаем DDC-центр без смены VFO (аналог CTUN-drag в десктопе) + FCenterFreq := FWebSyncFreq; + if FWDSPReady then + begin + if FActiveVfo = 0 then FDSPEngine.SetShift(FVfoA - FCenterFreq) + else FDSPEngine.SetShift(FVfoB - FCenterFreq); + end; + if FNetwork.Connected and FNetwork.Running then + FNetwork.UpdateState(FCenterFreq, FCenterFreq, FDriveLevel, FTransmitting, True, True); + FRulerLastFreq := -1.0; FRulerLastVfo := -1.0; + FSpectrumDirty := True; +end; + procedure TMainForm.TrkDriveChange(Sender: TObject); begin FDriveLevel := Round(TrkDrive.Position / 100.0 * 255); diff --git a/WebPageHtml.pas b/WebPageHtml.pas index df50cd4..524c546 100644 --- a/WebPageHtml.pas +++ b/WebPageHtml.pas @@ -449,10 +449,53 @@ begin 'cv.addEventListener("contextmenu",e=>e.preventDefault());' + 'cv.addEventListener("mouseenter",()=>{wTarget=cv.id;});' + 'cv.addEventListener("wheel",wh,{passive:false,capture:true});' + - 'cv.addEventListener("mousedown",e=>{audioKick();wTarget=cv.id;if(e.button===2){markerOn=Math.abs(markerX-e.offsetX)>=8||!markerOn;markerX=e.offsetX;return;}if(e.button!==0)return;dragging=true;dragX0=e.offsetX;dragCen0=cur.center_hz||0;dragVfo0=activeVfoHz();dragMs=0;});' + - 'cv.addEventListener("mousemove",e=>{wTarget=cv.id;hoverX=e.offsetX;if(markerOn)markerX=e.offsetX;if(!dragging)return;const n=Date.now();if(n-dragMs<35)return;dragMs=n;const dHz=(e.offsetX-dragX0)/Math.max(1,cv.clientWidth)*(cur.span_hz||0);if(cur.ctun)ws2({cmd:"freq",hz:Math.round(dragCen0-dHz)});else{const nf=cl(Math.round(dragVfo0-dHz),30000,60000000);setActiveVfoHz(nf);if(activeVfo==="A")cur.vfo_a_hz=nf;else cur.vfo_b_hz=nf;renderVfos();pend("freq");ws2({cmd:"freq",hz:nf});}});' + - 'cv.addEventListener("mouseup",e=>{if(e.button===2)return;if(Math.abs(e.offsetX-dragX0)<4){const nf=cl(Math.round(xToHz(e.offsetX,cv.clientWidth)/100)*100,30000,60000000);setActiveVfoHz(nf);if(activeVfo==="A")cur.vfo_a_hz=nf;else cur.vfo_b_hz=nf;renderVfos();pend("freq");ws2({cmd:"freq",hz:nf});}dragging=false;});' + - 'cv.addEventListener("mouseleave",()=>{if(!markerOn)hoverX=-1;wTarget="";dragging=false;});}' + + 'cv.addEventListener("mousedown",e=>{' + + 'audioKick();wTarget=cv.id;' + + 'if(e.button===2){markerOn=Math.abs(markerX-e.offsetX)>=8||!markerOn;markerX=e.offsetX;return;}' + + 'if(e.button!==0)return;' + + 'dragging=true;dragX0=e.offsetX;' + + // CTUN ON: drag scrolls the spectrum window (center moves, VFO stays) + // CTUN OFF: drag moves active VFO (and center follows it) + 'dragCen0=cur.center_hz||activeVfoHz();' + + 'dragVfo0=activeVfoHz();' + + 'dragMs=0;' + + 'cv.style.cursor="ew-resize";' + + '});' + + 'cv.addEventListener("mousemove",e=>{' + + 'wTarget=cv.id;hoverX=e.offsetX;' + + 'if(markerOn&&!dragging)markerX=e.offsetX;' + + 'if(!dragging)return;' + + 'const n=Date.now();if(n-dragMs<16)return;dragMs=n;' + + 'const span=cur.span_hz||192000;' + + 'const dHz=(e.offsetX-dragX0)/Math.max(1,cv.clientWidth)*span;' + + 'if(cur.ctun){' + + // CTUN ON: move DDC center window, VFO marker moves on screen + 'const newCen=Math.round(dragCen0-dHz);' + + 'cur.center_hz=newCen;' + + 'ws2({cmd:"set_center",hz:newCen});' + + '} else {' + + // CTUN OFF: move active VFO, center follows → update center_hz locally + 'const nf=cl(Math.round(dragVfo0-dHz),30000,60000000);' + + 'setActiveVfoHz(nf);' + + // Optimistically update center so ruler/filter band stay in sync while dragging + 'cur.center_hz=nf;' + + 'if(activeVfo==="A"){cur.vfo_a_hz=nf;}else{cur.vfo_b_hz=nf;}' + + 'renderVfos();pend("freq");ws2({cmd:"freq",hz:nf});' + + '}' + + '});' + + 'cv.addEventListener("mouseup",e=>{' + + 'cv.style.cursor="";' + + 'if(e.button===2)return;' + + 'if(dragging&&Math.abs(e.offsetX-dragX0)<4){' + + // Click (no drag): tune active VFO to clicked frequency + 'const nf=cl(Math.round(xToHz(e.offsetX,cv.clientWidth)/100)*100,30000,60000000);' + + 'setActiveVfoHz(nf);cur.center_hz=nf;' + + 'if(activeVfo==="A")cur.vfo_a_hz=nf;else cur.vfo_b_hz=nf;' + + 'renderVfos();pend("freq");ws2({cmd:"freq",hz:nf});' + + '}' + + 'dragging=false;' + + '});' + + 'cv.addEventListener("mouseleave",()=>{cv.style.cursor="";if(!markerOn)hoverX=-1;wTarget="";dragging=false;});}' + 'bindCv(specCv);bindCv(wfCv);' + 'window.addEventListener("wheel",e=>{if(e.target?.closest?.(".vfo-ro"))return;if(!wTarget){if(e.target===specCv)wTarget="spec";else if(e.target===wfCv)wTarget="wf";}if(wTarget)doWheel(e);},{passive:false,capture:true});' + diff --git a/WebServer.pas b/WebServer.pas index 4064c87..04f22b7 100644 --- a/WebServer.pas +++ b/WebServer.pas @@ -123,6 +123,7 @@ type TWebCmdANF = procedure(On_: Boolean) of object; TWebCmdFreqB = procedure(Hz: Double) of object; TWebCmdActiveVfo = procedure(Idx: Integer) of object; + TWebCmdCenter = procedure(Hz: Double) of object; // ── Главный класс сервера ───────────────────────────────────────────────── TWebServer = class @@ -202,6 +203,7 @@ type FOnANF: TWebCmdANF; FOnFreqB: TWebCmdFreqB; FOnActiveVfo: TWebCmdActiveVfo; + FOnCenter: TWebCmdCenter; FWebClientActive: Boolean; @@ -267,6 +269,7 @@ type property OnANF: TWebCmdANF read FOnANF write FOnANF; property OnFreqB: TWebCmdFreqB read FOnFreqB write FOnFreqB; property OnActiveVfo: TWebCmdActiveVfo read FOnActiveVfo write FOnActiveVfo; + property OnCenter: TWebCmdCenter read FOnCenter write FOnCenter; end; implementation @@ -972,6 +975,12 @@ begin if ModeValue > 1 then ModeValue := 1; FStateLock.Enter; FActiveVfo := ModeValue; FStateLock.Leave; if Assigned(FOnActiveVfo) then FOnActiveVfo(ModeValue); + end + else if Cmd = 'set_center' then + begin + HzF := JsonGetFloat(Json, 'hz', FCenterHz); + FStateLock.Enter; FCenterHz := HzF; FStateLock.Leave; + if Assigned(FOnCenter) then FOnCenter(HzF); end; end;