feat(ui): add hover overlay scroll for left panel

This commit is contained in:
2026-08-05 13:44:12 +03:00
parent 94bb15bbaf
commit b7b9119339
3 changed files with 391 additions and 19 deletions
+143 -15
View File
@@ -24,7 +24,8 @@ interface
uses
Classes, SysUtils, StrUtils, FreqDisplay, DeviceForm, FilterPopup,
VfoOverlay, SampleRateOverlay, BandPlanOverlay,
FlatButton, FlatSlider, FlatDropDown, AppTheme, Forms, Controls, Graphics, Dialogs,
FlatButton, FlatSlider, FlatDropDown, OverlayScrollBar, AppTheme,
Forms, Controls, Graphics, Dialogs,
StdCtrls, ExtCtrls, Buttons, Menus, Math, Types, SyncObjs,
OpenGLContextEx,
LCLIntf, LCLType, GraphType,
@@ -104,6 +105,7 @@ const
SMETER_MARGIN = 3;
TOP_SMETER_GAP = 8;
TOP_VFO_FONT = 25;
LEFT_SCROLL_GUTTER_W = 8;
LEFT_PANEL_BTN_PAD = 3;
LEFT_PANEL_BTN_GAP = 2;
@@ -263,6 +265,8 @@ type
// ---- Left panel ----
PanelLeft: TPanel;
FLeftScrollBar: TOverlayScrollBar;
FLeftScrollOffset: Integer;
PanelVfoA: TPanel;
LblVfoALabel: TLabel;
@@ -627,6 +631,9 @@ type
procedure ApplyXvtrToNetwork;
procedure RebuildXvtrButtons;
procedure RelayoutBelowBands;
procedure UpdateLeftPanelScroll;
function CursorOverLeftPanel: Boolean;
procedure LeftScrollChanged(Sender: TObject);
procedure BtnXvtrBandClick(Sender: TObject);
procedure ActivateXvtrBand(Idx: Integer);
procedure DeactivateXvtr;
@@ -1939,9 +1946,19 @@ begin
PanelLeft := TPanel.Create(Self);
PanelLeft.Parent := Self;
PanelLeft.Align := alLeft;
PanelLeft.Width := LEFT_W;
// Узкий правый gutter зарезервирован постоянно: появление overlay-scrollbar
// не меняет ширину панели и не дёргает область спектра.
PanelLeft.Width := LEFT_W + LEFT_SCROLL_GUTTER_W;
PanelLeft.BevelOuter := bvNone;
FLeftScrollOffset := 0;
FLeftScrollBar := TOverlayScrollBar.Create(Self);
FLeftScrollBar.Parent := PanelLeft;
FLeftScrollBar.Align := alRight;
FLeftScrollBar.Width := LEFT_SCROLL_GUTTER_W;
FLeftScrollBar.HoverTarget := PanelLeft;
FLeftScrollBar.OnPositionChange := LeftScrollChanged;
Y := 0;
// Compact top VFO group
@@ -2134,7 +2151,8 @@ begin
BtnTXProfile.OnMouseDown := BtnTXProfileMouseDown;
StyleButton(BtnTXProfile, False);
FTXProfileDropDown := TFlatDropDown.Create(BtnTXProfile, PanelLeft, 1, BTN_SM);
FTXProfileDropDown := TFlatDropDown.Create(BtnTXProfile, PanelLeft, 1, BTN_SM,
LEFT_SCROLL_GUTTER_W);
FTXProfileDropDown.OnSelect := OnTXProfileDropDownSelect;
RenderTXProfile;
@@ -2179,7 +2197,8 @@ begin
BtnChannels.OnMouseDown := BtnChannelsMouseDown;
StyleButton(BtnChannels, False);
FChannelsDropDown := TFlatDropDown.Create(BtnChannels, PanelLeft, 1, BTN_SM);
FChannelsDropDown := TFlatDropDown.Create(BtnChannels, PanelLeft, 1, BTN_SM,
LEFT_SCROLL_GUTTER_W);
FChannelsDropDown.OnSelect := OnChannelsDropDownSelect;
RefreshChannelsDropDown;
@@ -2485,7 +2504,8 @@ begin
BTN_H, BtnFMCTCSSToneClick);
StyleButton(BtnFMCTCSSTone, False);
FCTCSSDropDown := TFlatDropDown.Create(BtnFMCTCSSTone, PanelLeft, 4, BTN_SM);
FCTCSSDropDown := TFlatDropDown.Create(BtnFMCTCSSTone, PanelLeft, 4, BTN_SM,
LEFT_SCROLL_GUTTER_W);
FCTCSSDropDown.SetItems(CTCSS_NAMES);
FCTCSSDropDown.SetItemIndex(FController.FFMCTCSSToneIdx);
FCTCSSDropDown.OnSelect := OnCTCSSDropDownSelect;
@@ -2509,7 +2529,8 @@ begin
BTN_H, BtnFMStepSelClick);
StyleButton(BtnFMStepSel, False);
FStepDropDown := TFlatDropDown.Create(BtnFMStepSel, PanelLeft, FM_STEP_COUNT, BTN_SM);
FStepDropDown := TFlatDropDown.Create(BtnFMStepSel, PanelLeft, FM_STEP_COUNT,
BTN_SM, LEFT_SCROLL_GUTTER_W);
FStepDropDown.SetItems(FM_STEP_NAMES);
FStepDropDown.SetItemIndex(FController.FFMStepIdx);
FStepDropDown.OnSelect := OnStepDropDownSelect;
@@ -2634,6 +2655,7 @@ end;
procedure TMainForm.RightPanelResize(Sender: TObject);
begin
UpdateLeftPanelScroll;
ResizeSpectrumPanels;
end;
@@ -3149,6 +3171,9 @@ begin
Color := T.BG; Font.Color := T.Text;
DP(PanelToolbar); DP(PanelLeft);
if FLeftScrollBar <> nil then
FLeftScrollBar.SetColors(T.Panel, T.Border,
T.SliderThumbHot, T.SliderThumbDrag);
DP(PanelTopVfoGroup); DP(PanelTopVfoA); DP(PanelTopVfoB);
DP(PanelVfoA); DP(PanelVfoB); DP(PanelVfoButtons);
DP(PanelBands); DP(PanelMode); DP(PanelFilter);
@@ -8092,36 +8117,123 @@ end;
// создаёт новые для enabled слотов в 3-м и 4-м ряду PanelBands.
procedure TMainForm.RelayoutBelowBands;
var
Y: Integer;
Y, Gap: Integer;
begin
if PanelBands = nil then Exit;
Y := PanelBands.Top + PanelBands.Height + 2;
PanelMode.Top := Y; Y := Y + PanelMode.Height + 2;
Gap := Scale96ToForm(2);
Y := PanelBands.Top + PanelBands.Height + Gap;
PanelMode.Top := Y; Y := Y + PanelMode.Height + Gap;
if PanelFilter.Visible then
begin
PanelFilter.Top := Y;
Y := Y + PanelFilter.Height + 2;
Y := Y + PanelFilter.Height + Gap;
end;
if (PanelRawDev <> nil) and PanelRawDev.Visible then
begin
PanelRawDev.Top := Y; Y := Y + PanelRawDev.Height + 2;
PanelRawDev.Top := Y; Y := Y + PanelRawDev.Height + Gap;
end;
if (PanelFMStep <> nil) and PanelFMStep.Visible then
begin
PanelFMStep.Top := Y; Y := Y + PanelFMStep.Height + 2;
PanelFMStep.Top := Y; Y := Y + PanelFMStep.Height + Gap;
end;
if (PanelFMSQ <> nil) and PanelFMSQ.Visible then
begin
PanelFMSQ.Top := Y; Y := Y + PanelFMSQ.Height + 2;
PanelFMSQ.Top := Y; Y := Y + PanelFMSQ.Height + Gap;
end;
if (PanelFMCTCSS <> nil) and PanelFMCTCSS.Visible then
begin
PanelFMCTCSS.Top := Y; Y := Y + PanelFMCTCSS.Height + 2;
PanelFMCTCSS.Top := Y; Y := Y + PanelFMCTCSS.Height + Gap;
end;
if (PanelFMRpt <> nil) and PanelFMRpt.Visible then
begin
PanelFMRpt.Top := Y; Y := Y + PanelFMRpt.Height + 2;
PanelFMRpt.Top := Y; Y := Y + PanelFMRpt.Height + Gap;
end;
UpdateLeftPanelScroll;
end;
function TMainForm.CursorOverLeftPanel: Boolean;
var
P: TPoint;
begin
Result := False;
if (PanelLeft = nil) or (not PanelLeft.Visible) then Exit;
P := PanelLeft.ScreenToClient(Mouse.CursorPos);
Result := (P.X >= 0) and (P.Y >= 0) and
(P.X < PanelLeft.ClientWidth) and (P.Y < PanelLeft.ClientHeight);
end;
procedure TMainForm.LeftScrollChanged(Sender: TObject);
var
NewValue, Delta: Integer;
procedure MovePanel(P: TPanel);
begin
if P <> nil then P.Top := P.Top + Delta;
end;
begin
if FLeftScrollBar = nil then Exit;
NewValue := FLeftScrollBar.Position;
if NewValue = FLeftScrollOffset then Exit;
// Top хранится уже с учётом текущего scroll offset. При движении вниз
// содержимое уходит вверх, поэтому знак у дельты обратный.
Delta := FLeftScrollOffset - NewValue;
FLeftScrollOffset := NewValue;
MovePanel(PanelTX);
MovePanel(PanelRXBlock);
MovePanel(PanelAGC);
MovePanel(PanelPlutoAGC);
MovePanel(PanelBands);
MovePanel(PanelMode);
MovePanel(PanelFilter);
MovePanel(PanelRawDev);
MovePanel(PanelFMStep);
MovePanel(PanelFMSQ);
MovePanel(PanelFMCTCSS);
MovePanel(PanelFMRpt);
if FTXProfileDropDown <> nil then FTXProfileDropDown.ClosePopup;
if FChannelsDropDown <> nil then FChannelsDropDown.ClosePopup;
if FCTCSSDropDown <> nil then FCTCSSDropDown.ClosePopup;
if FStepDropDown <> nil then FStepDropDown.ClosePopup;
end;
procedure TMainForm.UpdateLeftPanelScroll;
var
ContentBottom, ViewH, NewMax: Integer;
procedure MeasurePanel(P: TPanel);
var
B: Integer;
begin
if (P = nil) or (not P.Visible) then Exit;
// Возвращаем логическую позицию до сдвига viewport.
B := P.Top + FLeftScrollOffset + P.Height;
if B > ContentBottom then ContentBottom := B;
end;
begin
if (PanelLeft = nil) or (FLeftScrollBar = nil) then Exit;
ViewH := PanelLeft.ClientHeight;
if ViewH <= 0 then Exit;
ContentBottom := 0;
MeasurePanel(PanelTX);
MeasurePanel(PanelRXBlock);
MeasurePanel(PanelAGC);
MeasurePanel(PanelPlutoAGC);
MeasurePanel(PanelBands);
MeasurePanel(PanelMode);
MeasurePanel(PanelFilter);
MeasurePanel(PanelRawDev);
MeasurePanel(PanelFMStep);
MeasurePanel(PanelFMSQ);
MeasurePanel(PanelFMCTCSS);
MeasurePanel(PanelFMRpt);
NewMax := Max(0, ContentBottom + Scale96ToForm(4) - ViewH);
FLeftScrollBar.SetRange(NewMax, ViewH);
end;
procedure TMainForm.RebuildXvtrButtons;
@@ -8362,6 +8474,22 @@ begin
// виджет крутил бы B, а этот хендлер — активный A.
if OverFreqDisp(FreqDispA) or OverFreqDisp(FreqDispB) then Exit;
// Если левая колонка не помещается, колесо над любой её частью прокручивает
// колонку, а не частоту. Scrollbar остаётся overlay и появляется по hover.
if CursorOverLeftPanel then
begin
UpdateLeftPanelScroll;
if (FLeftScrollBar <> nil) and (FLeftScrollBar.Maximum > 0) then
begin
if WheelDelta > 0 then
FLeftScrollBar.ScrollBy(-Scale96ToForm(64))
else if WheelDelta < 0 then
FLeftScrollBar.ScrollBy(Scale96ToForm(64));
Handled := True;
Exit;
end;
end;
// Колесо над раскрытым списком устройств во флаге — прокрутка этого списка.
for i := 0 to MAX_PANS - 1 do
if (FPans[i] <> nil) and Assigned(FPans[i].PbSpectrum) and