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
+11 -4
View File
@@ -18,7 +18,7 @@ unit FlatDropDown;
interface interface
uses uses
Classes, SysUtils, Controls, Graphics, ExtCtrls, FlatButton; Classes, SysUtils, Controls, Graphics, ExtCtrls, Math, FlatButton;
type type
TButtonStyleProc = procedure(B: TFlatButton; Active: Boolean) of object; TButtonStyleProc = procedure(B: TFlatButton; Active: Boolean) of object;
@@ -33,13 +33,14 @@ type
FItemIndex: Integer; FItemIndex: Integer;
FColumns: Integer; FColumns: Integer;
FItemBtnH: Integer; FItemBtnH: Integer;
FRightInset96: Integer;
FIsOpen: Boolean; FIsOpen: Boolean;
FOnSelect: TDropDownSelectEvent; FOnSelect: TDropDownSelectEvent;
procedure ItemClick(Sender: TObject); procedure ItemClick(Sender: TObject);
procedure PositionPopup; procedure PositionPopup;
public public
constructor Create(AAnchor: TFlatButton; APopupParent: TWinControl; constructor Create(AAnchor: TFlatButton; APopupParent: TWinControl;
ACols, ABtnH: Integer); ACols, ABtnH: Integer; ARightInset96: Integer = 0);
destructor Destroy; override; destructor Destroy; override;
procedure SetItems(const ANames: array of string); procedure SetItems(const ANames: array of string);
procedure SetItemIndex(Idx: Integer); procedure SetItemIndex(Idx: Integer);
@@ -54,13 +55,15 @@ type
implementation implementation
constructor TFlatDropDown.Create(AAnchor: TFlatButton; APopupParent: TWinControl; constructor TFlatDropDown.Create(AAnchor: TFlatButton; APopupParent: TWinControl;
ACols, ABtnH: Integer); ACols, ABtnH: Integer;
ARightInset96: Integer = 0);
begin begin
inherited Create; inherited Create;
FAnchor := AAnchor; FAnchor := AAnchor;
FPopupParent := APopupParent; FPopupParent := APopupParent;
FColumns := ACols; FColumns := ACols;
FItemBtnH := ABtnH; FItemBtnH := ABtnH;
FRightInset96 := Max(0, ARightInset96);
FItemIndex := 0; FItemIndex := 0;
FIsOpen := False; FIsOpen := False;
FPopup := TPanel.Create(nil); FPopup := TPanel.Create(nil);
@@ -85,7 +88,11 @@ begin
FPopup.Controls[0].Free; FPopup.Controls[0].Free;
Count := Length(ANames); Count := Length(ANames);
SetLength(FItemBtns, Count); SetLength(FItemBtns, Count);
PopW := FPopupParent.ClientWidth; // Popup может жить в контейнере с постоянно зарезервированным overlay-
// scrollbar gutter. Inset задаётся в 96-DPI координатах и пересчитывается
// при каждом наполнении: SetItems бывает и до, и после DPI-scale формы.
PopW := Max(1, FPopupParent.ClientWidth -
FPopupParent.Scale96ToForm(FRightInset96));
BtnW := (PopW - 4) div FColumns; BtnW := (PopW - 4) div FColumns;
FPopup.SetBounds(0, 0, PopW, FPopup.SetBounds(0, 0, PopW,
((Count + FColumns - 1) div FColumns) * FItemBtnH); ((Count + FColumns - 1) div FColumns) * FItemBtnH);
+143 -15
View File
@@ -24,7 +24,8 @@ interface
uses uses
Classes, SysUtils, StrUtils, FreqDisplay, DeviceForm, FilterPopup, Classes, SysUtils, StrUtils, FreqDisplay, DeviceForm, FilterPopup,
VfoOverlay, SampleRateOverlay, BandPlanOverlay, 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, StdCtrls, ExtCtrls, Buttons, Menus, Math, Types, SyncObjs,
OpenGLContextEx, OpenGLContextEx,
LCLIntf, LCLType, GraphType, LCLIntf, LCLType, GraphType,
@@ -104,6 +105,7 @@ const
SMETER_MARGIN = 3; SMETER_MARGIN = 3;
TOP_SMETER_GAP = 8; TOP_SMETER_GAP = 8;
TOP_VFO_FONT = 25; TOP_VFO_FONT = 25;
LEFT_SCROLL_GUTTER_W = 8;
LEFT_PANEL_BTN_PAD = 3; LEFT_PANEL_BTN_PAD = 3;
LEFT_PANEL_BTN_GAP = 2; LEFT_PANEL_BTN_GAP = 2;
@@ -263,6 +265,8 @@ type
// ---- Left panel ---- // ---- Left panel ----
PanelLeft: TPanel; PanelLeft: TPanel;
FLeftScrollBar: TOverlayScrollBar;
FLeftScrollOffset: Integer;
PanelVfoA: TPanel; PanelVfoA: TPanel;
LblVfoALabel: TLabel; LblVfoALabel: TLabel;
@@ -627,6 +631,9 @@ type
procedure ApplyXvtrToNetwork; procedure ApplyXvtrToNetwork;
procedure RebuildXvtrButtons; procedure RebuildXvtrButtons;
procedure RelayoutBelowBands; procedure RelayoutBelowBands;
procedure UpdateLeftPanelScroll;
function CursorOverLeftPanel: Boolean;
procedure LeftScrollChanged(Sender: TObject);
procedure BtnXvtrBandClick(Sender: TObject); procedure BtnXvtrBandClick(Sender: TObject);
procedure ActivateXvtrBand(Idx: Integer); procedure ActivateXvtrBand(Idx: Integer);
procedure DeactivateXvtr; procedure DeactivateXvtr;
@@ -1939,9 +1946,19 @@ begin
PanelLeft := TPanel.Create(Self); PanelLeft := TPanel.Create(Self);
PanelLeft.Parent := Self; PanelLeft.Parent := Self;
PanelLeft.Align := alLeft; PanelLeft.Align := alLeft;
PanelLeft.Width := LEFT_W; // Узкий правый gutter зарезервирован постоянно: появление overlay-scrollbar
// не меняет ширину панели и не дёргает область спектра.
PanelLeft.Width := LEFT_W + LEFT_SCROLL_GUTTER_W;
PanelLeft.BevelOuter := bvNone; 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; Y := 0;
// Compact top VFO group // Compact top VFO group
@@ -2134,7 +2151,8 @@ begin
BtnTXProfile.OnMouseDown := BtnTXProfileMouseDown; BtnTXProfile.OnMouseDown := BtnTXProfileMouseDown;
StyleButton(BtnTXProfile, False); 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; FTXProfileDropDown.OnSelect := OnTXProfileDropDownSelect;
RenderTXProfile; RenderTXProfile;
@@ -2179,7 +2197,8 @@ begin
BtnChannels.OnMouseDown := BtnChannelsMouseDown; BtnChannels.OnMouseDown := BtnChannelsMouseDown;
StyleButton(BtnChannels, False); 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; FChannelsDropDown.OnSelect := OnChannelsDropDownSelect;
RefreshChannelsDropDown; RefreshChannelsDropDown;
@@ -2485,7 +2504,8 @@ begin
BTN_H, BtnFMCTCSSToneClick); BTN_H, BtnFMCTCSSToneClick);
StyleButton(BtnFMCTCSSTone, False); 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.SetItems(CTCSS_NAMES);
FCTCSSDropDown.SetItemIndex(FController.FFMCTCSSToneIdx); FCTCSSDropDown.SetItemIndex(FController.FFMCTCSSToneIdx);
FCTCSSDropDown.OnSelect := OnCTCSSDropDownSelect; FCTCSSDropDown.OnSelect := OnCTCSSDropDownSelect;
@@ -2509,7 +2529,8 @@ begin
BTN_H, BtnFMStepSelClick); BTN_H, BtnFMStepSelClick);
StyleButton(BtnFMStepSel, False); 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.SetItems(FM_STEP_NAMES);
FStepDropDown.SetItemIndex(FController.FFMStepIdx); FStepDropDown.SetItemIndex(FController.FFMStepIdx);
FStepDropDown.OnSelect := OnStepDropDownSelect; FStepDropDown.OnSelect := OnStepDropDownSelect;
@@ -2634,6 +2655,7 @@ end;
procedure TMainForm.RightPanelResize(Sender: TObject); procedure TMainForm.RightPanelResize(Sender: TObject);
begin begin
UpdateLeftPanelScroll;
ResizeSpectrumPanels; ResizeSpectrumPanels;
end; end;
@@ -3149,6 +3171,9 @@ begin
Color := T.BG; Font.Color := T.Text; Color := T.BG; Font.Color := T.Text;
DP(PanelToolbar); DP(PanelLeft); 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(PanelTopVfoGroup); DP(PanelTopVfoA); DP(PanelTopVfoB);
DP(PanelVfoA); DP(PanelVfoB); DP(PanelVfoButtons); DP(PanelVfoA); DP(PanelVfoB); DP(PanelVfoButtons);
DP(PanelBands); DP(PanelMode); DP(PanelFilter); DP(PanelBands); DP(PanelMode); DP(PanelFilter);
@@ -8092,36 +8117,123 @@ end;
// создаёт новые для enabled слотов в 3-м и 4-м ряду PanelBands. // создаёт новые для enabled слотов в 3-м и 4-м ряду PanelBands.
procedure TMainForm.RelayoutBelowBands; procedure TMainForm.RelayoutBelowBands;
var var
Y: Integer; Y, Gap: Integer;
begin begin
if PanelBands = nil then Exit; if PanelBands = nil then Exit;
Y := PanelBands.Top + PanelBands.Height + 2; Gap := Scale96ToForm(2);
PanelMode.Top := Y; Y := Y + PanelMode.Height + 2; Y := PanelBands.Top + PanelBands.Height + Gap;
PanelMode.Top := Y; Y := Y + PanelMode.Height + Gap;
if PanelFilter.Visible then if PanelFilter.Visible then
begin begin
PanelFilter.Top := Y; PanelFilter.Top := Y;
Y := Y + PanelFilter.Height + 2; Y := Y + PanelFilter.Height + Gap;
end; end;
if (PanelRawDev <> nil) and PanelRawDev.Visible then if (PanelRawDev <> nil) and PanelRawDev.Visible then
begin begin
PanelRawDev.Top := Y; Y := Y + PanelRawDev.Height + 2; PanelRawDev.Top := Y; Y := Y + PanelRawDev.Height + Gap;
end; end;
if (PanelFMStep <> nil) and PanelFMStep.Visible then if (PanelFMStep <> nil) and PanelFMStep.Visible then
begin begin
PanelFMStep.Top := Y; Y := Y + PanelFMStep.Height + 2; PanelFMStep.Top := Y; Y := Y + PanelFMStep.Height + Gap;
end; end;
if (PanelFMSQ <> nil) and PanelFMSQ.Visible then if (PanelFMSQ <> nil) and PanelFMSQ.Visible then
begin begin
PanelFMSQ.Top := Y; Y := Y + PanelFMSQ.Height + 2; PanelFMSQ.Top := Y; Y := Y + PanelFMSQ.Height + Gap;
end; end;
if (PanelFMCTCSS <> nil) and PanelFMCTCSS.Visible then if (PanelFMCTCSS <> nil) and PanelFMCTCSS.Visible then
begin begin
PanelFMCTCSS.Top := Y; Y := Y + PanelFMCTCSS.Height + 2; PanelFMCTCSS.Top := Y; Y := Y + PanelFMCTCSS.Height + Gap;
end; end;
if (PanelFMRpt <> nil) and PanelFMRpt.Visible then if (PanelFMRpt <> nil) and PanelFMRpt.Visible then
begin begin
PanelFMRpt.Top := Y; Y := Y + PanelFMRpt.Height + 2; PanelFMRpt.Top := Y; Y := Y + PanelFMRpt.Height + Gap;
end; 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; end;
procedure TMainForm.RebuildXvtrButtons; procedure TMainForm.RebuildXvtrButtons;
@@ -8362,6 +8474,22 @@ begin
// виджет крутил бы B, а этот хендлер — активный A. // виджет крутил бы B, а этот хендлер — активный A.
if OverFreqDisp(FreqDispA) or OverFreqDisp(FreqDispB) then Exit; 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 for i := 0 to MAX_PANS - 1 do
if (FPans[i] <> nil) and Assigned(FPans[i].PbSpectrum) and if (FPans[i] <> nil) and Assigned(FPans[i].PbSpectrum) and
+237
View File
@@ -0,0 +1,237 @@
unit OverlayScrollBar;
{
Тонкий вертикальный overlay-scrollbar без системного оформления.
Сам контрол всегда занимает зарезервированный gutter, но трек и бегунок
рисуются только при Max > 0 и наведении мыши на HoverTarget. Содержимое
контрол не двигает: владелец подписывается на OnPositionChange.
}
{$mode objfpc}{$H+}
interface
uses
Classes, Controls, ExtCtrls, Forms, Graphics, Math, Types, LCLType;
type
TOverlayScrollBar = class(TPaintBox)
private
FHoverTarget: TControl;
FMaximum: Integer;
FPosition: Integer;
FViewportSize: Integer;
FHover: Boolean;
FDragging: Boolean;
FDragY: Integer;
FDragPosition: Integer;
FBGColor: TColor;
FTrackColor: TColor;
FThumbColor: TColor;
FThumbDragColor: TColor;
FOnPositionChange: TNotifyEvent;
procedure AppUserInput(Sender: TObject; Msg: Cardinal);
procedure SetPosition(AValue: Integer);
function CursorOverTarget: Boolean;
function ThumbRect: TRect;
protected
procedure Paint; override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); override;
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure SetRange(AMaximum, AViewportSize: Integer);
procedure ScrollBy(ADelta: Integer);
procedure SetColors(ABG, ATrack, AThumb, AThumbDrag: TColor);
property HoverTarget: TControl read FHoverTarget write FHoverTarget;
property Maximum: Integer read FMaximum;
property Position: Integer read FPosition write SetPosition;
property OnPositionChange: TNotifyEvent
read FOnPositionChange write FOnPositionChange;
end;
implementation
constructor TOverlayScrollBar.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Width := 8;
FMaximum := 0;
FPosition := 0;
FViewportSize := 0;
FHover := False;
FDragging := False;
FBGColor := TColor($00181818);
FTrackColor := TColor($00303030);
FThumbColor := TColor($00407040);
FThumbDragColor := TColor($00509050);
Application.AddOnUserInputHandler(@AppUserInput);
end;
destructor TOverlayScrollBar.Destroy;
begin
Application.RemoveOnUserInputHandler(@AppUserInput);
if GetCaptureControl = Self then SetCaptureControl(nil);
inherited Destroy;
end;
procedure TOverlayScrollBar.SetColors(ABG, ATrack, AThumb,
AThumbDrag: TColor);
begin
FBGColor := ABG;
FTrackColor := ATrack;
FThumbColor := AThumb;
FThumbDragColor := AThumbDrag;
Invalidate;
end;
procedure TOverlayScrollBar.SetRange(AMaximum, AViewportSize: Integer);
var
OldMaximum: Integer;
begin
OldMaximum := FMaximum;
FMaximum := Max(0, AMaximum);
FViewportSize := Max(0, AViewportSize);
if FPosition > FMaximum then
SetPosition(FMaximum)
else if OldMaximum <> FMaximum then
Invalidate;
end;
procedure TOverlayScrollBar.SetPosition(AValue: Integer);
var
NewValue: Integer;
begin
NewValue := EnsureRange(AValue, 0, FMaximum);
if NewValue = FPosition then Exit;
FPosition := NewValue;
if Assigned(FOnPositionChange) then FOnPositionChange(Self);
Invalidate;
end;
procedure TOverlayScrollBar.ScrollBy(ADelta: Integer);
begin
SetPosition(FPosition + ADelta);
end;
function TOverlayScrollBar.CursorOverTarget: Boolean;
var
P: TPoint;
begin
Result := False;
if (FHoverTarget = nil) or (not FHoverTarget.Visible) then Exit;
P := FHoverTarget.ScreenToClient(Mouse.CursorPos);
Result := (P.X >= 0) and (P.Y >= 0) and
(P.X < FHoverTarget.Width) and (P.Y < FHoverTarget.Height);
end;
procedure TOverlayScrollBar.AppUserInput(Sender: TObject; Msg: Cardinal);
var
NewHover: Boolean;
begin
NewHover := CursorOverTarget;
if NewHover = FHover then Exit;
FHover := NewHover;
Invalidate;
end;
function TOverlayScrollBar.ThumbRect: TRect;
var
TrackTop, TrackH, ThumbH, Travel, ContentH: Integer;
begin
Result := Rect(0, 0, 0, 0);
if (FMaximum <= 0) or (FViewportSize <= 0) then Exit;
TrackTop := Scale96ToForm(4);
TrackH := Height - 2 * TrackTop;
if TrackH <= 0 then Exit;
ContentH := FViewportSize + FMaximum;
ThumbH := Max(Scale96ToForm(28),
MulDiv(TrackH, FViewportSize, Max(1, ContentH)));
ThumbH := Min(ThumbH, TrackH);
Travel := TrackH - ThumbH;
Result.Left := Max(1, (Width - Scale96ToForm(4)) div 2);
Result.Right := Width - Result.Left;
Result.Top := TrackTop;
if (Travel > 0) and (FMaximum > 0) then
Inc(Result.Top, MulDiv(Travel, FPosition, FMaximum));
Result.Bottom := Result.Top + ThumbH;
end;
procedure TOverlayScrollBar.Paint;
var
R: TRect;
TrackX: Integer;
begin
Canvas.Brush.Style := bsSolid;
Canvas.Brush.Color := FBGColor;
Canvas.FillRect(ClientRect);
if (FMaximum <= 0) or ((not FHover) and (not FDragging)) then Exit;
TrackX := Width div 2;
Canvas.Pen.Color := FTrackColor;
Canvas.Pen.Width := Max(1, Scale96ToForm(1));
Canvas.Line(TrackX, Scale96ToForm(4),
TrackX, Height - Scale96ToForm(4));
R := ThumbRect;
if IsRectEmpty(R) then Exit;
if FDragging then Canvas.Brush.Color := FThumbDragColor
else Canvas.Brush.Color := FThumbColor;
Canvas.Pen.Style := psClear;
Canvas.RoundRect(R.Left, R.Top, R.Right, R.Bottom,
Scale96ToForm(3), Scale96ToForm(3));
Canvas.Pen.Style := psSolid;
end;
procedure TOverlayScrollBar.MouseDown(Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
R: TRect;
begin
inherited MouseDown(Button, Shift, X, Y);
if (Button <> mbLeft) or (FMaximum <= 0) then Exit;
R := ThumbRect;
if PtInRect(R, Point(X, Y)) then
begin
FDragging := True;
FDragY := Y;
FDragPosition := FPosition;
SetCaptureControl(Self);
end
else if Y < R.Top then
ScrollBy(-FViewportSize + Scale96ToForm(40))
else
ScrollBy(FViewportSize - Scale96ToForm(40));
Invalidate;
end;
procedure TOverlayScrollBar.MouseMove(Shift: TShiftState; X, Y: Integer);
var
R: TRect;
TrackH, Travel: Integer;
begin
inherited MouseMove(Shift, X, Y);
if not FDragging then Exit;
R := ThumbRect;
TrackH := Height - 2 * Scale96ToForm(4);
Travel := TrackH - (R.Bottom - R.Top);
if Travel <= 0 then Exit;
SetPosition(FDragPosition + MulDiv(Y - FDragY, FMaximum, Travel));
end;
procedure TOverlayScrollBar.MouseUp(Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
inherited MouseUp(Button, Shift, X, Y);
if Button <> mbLeft then Exit;
FDragging := False;
if GetCaptureControl = Self then SetCaptureControl(nil);
Invalidate;
end;
end.