IDE: Guttermarks, improve layout

This commit is contained in:
Martin 2024-04-22 02:23:22 +02:00
parent 7dfa9c442c
commit 1b27926055
4 changed files with 248 additions and 130 deletions

View File

@ -30,13 +30,23 @@ type
FDebugMarksImageIndex: Integer;
FInternalImage: TSynInternalImage;
FNoInternalImage: Boolean;
protected type
TSynEditMarkDrawInfo = record
Mark: TSynEditMark;
Images: TCustomImageList;
IconIdx: integer;
end;
TSynEditMarkDrawInfoArray = array of TSynEditMarkDrawInfo;
protected
FBookMarkOpt: TSynBookMarkOpt;
FTempDrawInfo: TSynEditMarkDrawInfoArray;
procedure Init; override;
function PreferedWidth: Integer; override;
function LeftMarginAtCurrentPPI: Integer;
function GetImgListRes(const ACanvas: TCanvas;
const AImages: TCustomImageList): TScaledImageListResolution; virtual;
function GetImgListRes(const ACanvas: TCanvas; const AImages: TCustomImageList): TScaledImageListResolution; virtual;
function MarksToDrawInfo(AMLine: TSynEditMarkLine; var ADrawInfo: TSynEditMarkDrawInfoArray;
AMaxEntries: integer;
out aFirstCustomColumnIdx: integer; out AHasNonBookmark: boolean): integer; virtual;
// PaintMarks: True, if it has any Mark, that is *not* a bookmark
function PaintMarks(aScreenLine: Integer; Canvas : TCanvas; AClip : TRect;
var aFirstCustomColumnIdx: integer): Boolean;
@ -99,8 +109,8 @@ begin
inherited Destroy;
end;
function TSynGutterMarks.GetImgListRes(const ACanvas: TCanvas;
const AImages: TCustomImageList): TScaledImageListResolution;
function TSynGutterMarks.GetImgListRes(const ACanvas: TCanvas; const AImages: TCustomImageList
): TScaledImageListResolution;
var
Scale: Double;
PPI: Integer;
@ -117,18 +127,123 @@ begin
Result := AImages.ResolutionForPPI[0, PPI, Scale];
end;
function TSynGutterMarks.MarksToDrawInfo(AMLine: TSynEditMarkLine;
var ADrawInfo: TSynEditMarkDrawInfoArray; AMaxEntries: integer; out
aFirstCustomColumnIdx: integer; out AHasNonBookmark: boolean): integer;
var
i, CntUniq, CntRep2, CntKeepRep2, CntDel3: Integer;
prev_iidx, pprev_iidx: LongInt;
begin
Result := AMLine.VisibleCount;
if (FOptions * [sgmoDeDuplicateMarks, sgmoDeDuplicateMarksKeepTwo] <> []) or
((sgmoDeDuplicateMarksOnOverflow in FOptions) and (Result > ColumnCount))
then begin
CntUniq := 0;
CntRep2 := 0;
prev_iidx := low(integer);
pprev_iidx := low(integer);
for i := 0 to AMLine.Count - 1 do begin
if (not AMLine[i].Visible) or
(AMLine[i].IsBookmark and (not FBookMarkOpt.GlyphsVisible))
then
continue;
if AMLine[i].ImageIndex <> prev_iidx then
inc(CntUniq) // Uniq
else
if (i=1) or (AMLine[i].ImageIndex <> pprev_iidx) then
inc(CntRep2); // sgmoDeDuplicateMarksKeepTwo
pprev_iidx := prev_iidx;
prev_iidx := AMLine[i].ImageIndex;
end;
if (sgmoDeDuplicateMarks in FOptions) then begin
Result := Min(cntUniq, AMaxEntries);
CntKeepRep2 := 0;
end
else
if (sgmoDeDuplicateMarksKeepTwo in FOptions) then begin
CntKeepRep2 := Min(CntRep2, Max(0, AMaxEntries- cntUniq));
Result := Min(cntUniq+CntKeepRep2, AMaxEntries);
end
else begin
// only dedup for MaxExtraMarksColums
CntKeepRep2 := Min(CntRep2, Max(0, AMaxEntries - cntUniq));
Result := Min(cntUniq+CntKeepRep2, AMaxEntries);
end;
CntDel3 := AMLine.VisibleCount - Result;
end
else begin
Result := Min(AMLine.VisibleCount, AMaxEntries);
CntKeepRep2 := MaxInt; // keep duplicate if exactly 2nd
CntDel3 := 0; // del 3rd or later
end;
if Length(ADrawInfo) < Result then
SetLength(ADrawInfo, AMaxEntries); // Expand to max needed (for further runs)
aFirstCustomColumnIdx := 0;
AHasNonBookmark := False;
prev_iidx := low(integer);
pprev_iidx := low(integer);
for i := 0 to AMLine.Count - 1 do begin
if (not AMLine[i].Visible) or
(AMLine[i].IsBookmark and (not FBookMarkOpt.GlyphsVisible))
then
continue;
if (i = 0) and FBookMarkOpt.DrawBookmarksFirst and
(Result < ColumnCount) and (not AMLine[i].IsBookmark)
then begin
ADrawInfo[aFirstCustomColumnIdx].Mark := nil;
ADrawInfo[aFirstCustomColumnIdx].IconIdx := 0;
ADrawInfo[aFirstCustomColumnIdx].Images := nil;
inc(Result);
inc(aFirstCustomColumnIdx);
end;
if AMLine[i].ImageIndex = prev_iidx then begin
if (AMLine[i].ImageIndex = pprev_iidx) and (CntDel3 > 0) then begin
dec(CntDel3);
continue;
end;
if CntKeepRep2 = 0 then
Continue;
dec(CntKeepRep2);
end;
AHasNonBookmark := AHasNonBookmark or (not AMLine[i].IsBookmark); // Line has a none-bookmark glyph
ADrawInfo[aFirstCustomColumnIdx].Mark := AMLine[i];
ADrawInfo[aFirstCustomColumnIdx].IconIdx := 0; //AMLine[i].ImageIndex;
ADrawInfo[aFirstCustomColumnIdx].Images := nil; //AMLine[i].ImageList;
inc(aFirstCustomColumnIdx);
if aFirstCustomColumnIdx >= AMaxEntries then
break;
pprev_iidx := prev_iidx;
prev_iidx := AMLine[i].ImageIndex;
end;
end;
function TSynGutterMarks.PaintMarks(aScreenLine: Integer; Canvas : TCanvas;
AClip : TRect; var aFirstCustomColumnIdx: integer): Boolean;
var
LineHeight: Integer;
BkMkOptImg: TScaledImageListResolution;
BkMkOptImgDone: Boolean;
procedure DoPaintMark(CurMark: TSynEditMark; aRect: TRect);
procedure DoPaintMark(const CurMarkInfo: TSynEditMarkDrawInfo; aRect: TRect);
var
img: TScaledImageListResolution;
CurMark: TSynEditMark;
idx: Integer;
begin
if CurMark.InternalImage or
( (not assigned(FBookMarkOpt.BookmarkImages)) and
(not assigned(CurMark.ImageList)) )
CurMark := CurMarkInfo.Mark;
if (CurMark <> nil) and
( CurMark.InternalImage or
( (not assigned(FBookMarkOpt.BookmarkImages)) and
(not assigned(CurMark.ImageList)) )
)
then begin
// draw internal image
if CurMark.ImageIndex in [0..9] then
@ -146,26 +261,40 @@ var
end
else begin
// draw from ImageList
if assigned(CurMark.ImageList) then
img := GetImgListRes(Canvas, CurMark.ImageList)
if CurMark = nil then begin
if CurMarkInfo.Images = nil then
exit;
img := GetImgListRes(Canvas, CurMarkInfo.Images);
idx := CurMarkInfo.IconIdx;
end
else
img := GetImgListRes(Canvas, FBookMarkOpt.BookmarkImages);
if assigned(CurMark.ImageList) then begin
img := GetImgListRes(Canvas, CurMark.ImageList);
idx := CurMark.ImageIndex;
end
else begin
if not BkMkOptImgDone then begin
BkMkOptImg := GetImgListRes(Canvas, FBookMarkOpt.BookmarkImages);
BkMkOptImgDone := True;
end;
img := BkMkOptImg;
idx := CurMark.ImageIndex;
end;
if (CurMark.ImageIndex <= img.Count) and (CurMark.ImageIndex >= 0) then begin
if (idx <= img.Count) and (idx >= 0) then begin
if LineHeight > img.Height then
aRect.Top := (aRect.Top + aRect.Bottom - img.Height) div 2;
img.Draw(Canvas, aRect.Left, aRect.Top, CurMark.ImageIndex, True);
img.Draw(Canvas, aRect.Left, aRect.Top, idx, True);
end;
end
end;
var
j, lm, StoredColumnWidth, lx, vcnt, VCntU, VCnt2, k2cnt, del3cnt: Integer;
j, lm, StoredColumnWidth, lx, vcnt: Integer;
MLine: TSynEditMarkLine;
MarkRect: TRect;
iRange: TLineRange;
prev_iidx, pprev_iidx: LongInt;
begin
Result := False;
aFirstCustomColumnIdx := 0;
@ -186,59 +315,14 @@ begin
else
MLine.Sort(smsoBookMarkLast, smsoPriority);
vcnt := MLine.VisibleCount;
vcnt := MarksToDrawInfo(MLine, FTempDrawInfo, ColumnCount + MaxExtraMarksColums, aFirstCustomColumnIdx, Result);
if (FOptions * [sgmoDeDuplicateMarks, sgmoDeDuplicateMarksKeepTwo] <> []) or
((sgmoDeDuplicateMarksOnOverflow in FOptions) and (vcnt > ColumnCount))
then begin
VCntU := 0;
VCnt2 := 0;
prev_iidx := low(integer);
pprev_iidx := low(integer);
for j := 0 to MLine.Count - 1 do begin
if (not MLine[j].Visible) or
(MLine[j].IsBookmark and (not FBookMarkOpt.GlyphsVisible))
then
continue;
if MLine[j].ImageIndex <> prev_iidx then
inc(VCntU) // Uniq
else
if (j=1) or (MLine[j].ImageIndex <> pprev_iidx) then
inc(VCnt2); // sgmoDeDuplicateMarksKeepTwo
pprev_iidx := prev_iidx;
prev_iidx := MLine[j].ImageIndex;
end;
if (sgmoDeDuplicateMarks in FOptions) then begin
vcnt := Min(vcntU, ColumnCount + MaxExtraMarksColums);
k2cnt := 0;
end
else
if (sgmoDeDuplicateMarksKeepTwo in FOptions) then begin
k2cnt := Min(VCnt2, Max(0, ColumnCount + MaxExtraMarksColums - vcntU));
vcnt := Min(vcntU+k2cnt, ColumnCount + MaxExtraMarksColums);
end
else begin
// only dedup for MaxExtraMarksColums
k2cnt := Min(VCnt2, Max(0, ColumnCount + MaxExtraMarksColums - vcntU));
vcnt := Min(vcntU+k2cnt, ColumnCount + MaxExtraMarksColums);
end;
del3cnt := MLine.VisibleCount - vcnt;
end
else begin
vcnt := Min(vcnt, ColumnCount + MaxExtraMarksColums);
k2cnt := MaxInt; // keep duplicate if exactly 2nd
del3cnt := 0; // del 3rd or later
end;
aFirstCustomColumnIdx := 0;
LineHeight := SynEdit.LineHeight;
//Gutter.Paint always supplies AClip.Left = GutterPart.Left
lm := LeftMarginAtCurrentPPI;
BkMkOptImgDone := False;
LineHeight := SynEdit.LineHeight;
StoredColumnWidth := FColumnWidth;
prev_iidx := low(integer);
pprev_iidx := low(integer);
try
lm := LeftMarginAtCurrentPPI;
lx := 0;
if vcnt > ColumnCount then begin
lx := FColumnWidth;
@ -250,44 +334,11 @@ begin
AClip.Left + lm - lx + FColumnWidth,
AClip.Top + LineHeight);
for j := 0 to vcnt - 1 do begin
DoPaintMark(FTempDrawInfo[j], MarkRect);
for j := 0 to MLine.Count - 1 do begin
if (not MLine[j].Visible) or
(MLine[j].IsBookmark and (not FBookMarkOpt.GlyphsVisible))
then
continue;
if (j = 0) and FBookMarkOpt.DrawBookmarksFirst and
(vcnt < ColumnCount) and (not MLine[j].IsBookmark)
then begin
// leave one column empty
MarkRect.Left := MarkRect.Right;
MarkRect.Right := Min(MarkRect.Right + FColumnWidth, AClip.Right);
inc(aFirstCustomColumnIdx);
end;
if MLine[j].ImageIndex = prev_iidx then begin
if (MLine[j].ImageIndex = pprev_iidx) and (del3cnt > 0) then begin
dec(del3cnt);
continue;
end;
if k2cnt = 0 then
Continue;
dec(k2cnt);
end;
DoPaintMark(MLine[j], MarkRect);
MarkRect.Left := MarkRect.Right;
MarkRect.Right := Min(MarkRect.Right + FColumnWidth, AClip.Right);
Result := Result or (not MLine[j].IsBookmark); // Line has a none-bookmark glyph
inc(aFirstCustomColumnIdx);
if aFirstCustomColumnIdx > ColumnCount + MaxExtraMarksColums then
break;
pprev_iidx := prev_iidx;
prev_iidx := MLine[j].ImageIndex;
end;
finally
FColumnWidth := StoredColumnWidth;

View File

@ -3429,7 +3429,7 @@ end;
procedure TSourceEditorSharedValues.CreateExecutionMark;
begin
FExecutionMark := TSourceMark.Create(SharedEditors[0], nil);
FExecutionMark := TExecutionMark.Create(SharedEditors[0], nil);
SourceEditorMarks.Add(FExecutionMark);
FExecutionMark.LineColorAttrib := ahaExecutionPoint;
FExecutionMark.Priority := 1;

View File

@ -185,11 +185,12 @@ type
public
property IsBreakPoint: boolean read FIsBreakPoint write SetIsBreakPoint;
end;
TSourceMarkClass = class of TSourceMark;
PSourceMark = ^TSourceMark;
TExecutionMark = class(TSourceMark) end;
{ TSourceMarks }
TSourceMarks = class(TComponent)

View File

@ -446,6 +446,7 @@ type
private
FDebugMarkInfo: TIDESynDebugMarkInfo;
FMarkInfoTextBuffer: TSynEditStrings;
FCurLineHasDebugMark: boolean;
protected
procedure CheckTextBuffer; // Todo: Add a notification, when TextBuffer Changes
Procedure PaintLine(aScreenLine: Integer; Canvas : TCanvas; AClip : TRect); override;
@ -453,6 +454,9 @@ type
function GetImgListRes(const ACanvas: TCanvas;
const AImages: TCustomImageList): TScaledImageListResolution; override;
function MarksToDrawInfo(AMLine: TSynEditMarkLine; var ADrawInfo: TSynEditMarkDrawInfoArray;
AMaxEntries: integer; out aFirstCustomColumnIdx: integer; out AHasNonBookmark: boolean
): integer; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
@ -2311,8 +2315,7 @@ end;
procedure TIDESynGutterMarks.PaintLine(aScreenLine: Integer; Canvas: TCanvas; AClip: TRect);
var
aGutterOffs, TxtIdx: Integer;
HasAnyMark: Boolean;
aGutterOffs, TxtIdx, aScreenLine2: Integer;
iRange: TLineRange;
procedure DrawDebugMark(Line: Integer);
@ -2321,37 +2324,35 @@ var
LineHeight: LongInt;
img: TScaledImageListResolution;
begin
if Line < 0 then Exit;
if Assigned(FBookMarkOpt.BookmarkImages) and
(DebugMarksImageIndex <= FBookMarkOpt.BookmarkImages.Count) and
(DebugMarksImageIndex >= 0) then
begin
LineHeight := TSynEdit(SynEdit).LineHeight;
img := GetImgListRes(Canvas, FBookMarkOpt.BookmarkImages);
iTop := 0;
if LineHeight > img.Height then
iTop := (LineHeight - img.Height) div 2;
LineHeight := TSynEdit(SynEdit).LineHeight;
img := GetImgListRes(Canvas, FBookMarkOpt.BookmarkImages);
iTop := 0;
if LineHeight > img.Height then
iTop := (LineHeight - img.Height) div 2;
img.Draw
(Canvas, AClip.Left + LeftMarginAtCurrentPPI + (ColumnCount-1) * ColumnWidth,
AClip.Top + iTop, DebugMarksImageIndex, True);
end
img.Draw
(Canvas, AClip.Left + LeftMarginAtCurrentPPI + (ColumnCount-1) * ColumnWidth,
AClip.Top + iTop, DebugMarksImageIndex, True);
end;
begin
CheckTextBuffer;
aScreenLine2 := aScreenLine + ToIdx(GutterArea.TextArea.TopLine);
TxtIdx:= ViewedTextBuffer.DisplayView.ViewToTextIndexEx(aScreenLine2, iRange);
FCurLineHasDebugMark := (aScreenLine2 = iRange.Top) and (aScreenLine2 >= 0) and
(TxtIdx >= 0) and (TxtIdx < TSynEdit(SynEdit).Lines.Count) and
(HasDebugMarks) and (TxtIdx < FDebugMarkInfo.Count) and
(FDebugMarkInfo.SrcLineToMarkLine[TxtIdx] > 0) and
Assigned(FBookMarkOpt.BookmarkImages) and
(DebugMarksImageIndex <= FBookMarkOpt.BookmarkImages.Count) and
(DebugMarksImageIndex >= 0);
aGutterOffs := 0;
HasAnyMark := PaintMarks(aScreenLine, Canvas, AClip, aGutterOffs);
aScreenLine := aScreenLine + ToIdx(GutterArea.TextArea.TopLine);
TxtIdx:= ViewedTextBuffer.DisplayView.ViewToTextIndexEx(aScreenLine, iRange);
if aScreenLine <> iRange.Top then
exit;
if (TxtIdx < 0) or (TxtIdx >= TSynEdit(SynEdit).Lines.Count) then
exit;
if (aGutterOffs < ColumnCount) and (HasDebugMarks) and (TxtIdx < FDebugMarkInfo.Count) and
(FDebugMarkInfo.SrcLineToMarkLine[TxtIdx] > 0)
then
DrawDebugMark(aScreenLine);
PaintMarks(aScreenLine, Canvas, AClip, aGutterOffs);
if FCurLineHasDebugMark then
DrawDebugMark(aScreenLine2);
end;
function TIDESynGutterMarks.PreferedWidthAtCurrentPPI: Integer;
@ -2450,6 +2451,71 @@ begin
Result := AImages.ResolutionForPPI[ImageHeight, PPI, Scale];
end;
function TIDESynGutterMarks.MarksToDrawInfo(AMLine: TSynEditMarkLine;
var ADrawInfo: TSynEditMarkDrawInfoArray; AMaxEntries: integer; out
aFirstCustomColumnIdx: integer; out AHasNonBookmark: boolean): integer;
var
i, j: Integer;
begin
Result := inherited MarksToDrawInfo(AMLine, ADrawInfo, AMaxEntries, aFirstCustomColumnIdx,
AHasNonBookmark);
if (Result > 1) and (ADrawInfo[0].Mark = nil) and (ADrawInfo[1].Mark is TETMark) and
( (ColumnCount <= 2) or
((Result = ColumnCount) and (ADrawInfo[Result-1].Mark is TETMark)) )
then begin
dec(Result);
for i := 0 to Result - 1 do
ADrawInfo[i] := ADrawInfo[i+1];
end;
if Result <= ColumnCount then begin
i := Result - 1;
while (i >= 0) and (ADrawInfo[i].Mark <> nil) and
( (ADrawInfo[i].Mark is TExecutionMark) or
((ADrawInfo[i].Mark is TSourceMark) and (TSourceMark(ADrawInfo[i].Mark).IsBreakPoint))
)
do
dec(i);
inc(i);
if i < Result then begin
if Result < ColumnCount then begin
i := Result - i; // columns to move
if Length(ADrawInfo) < ColumnCount then
SetLength(ADrawInfo, Max(ColumnCount, AMaxEntries));
for j := 1 to i do
ADrawInfo[ColumnCount - j] := ADrawInfo[Result - j];
for j := Result - i to ColumnCount - 1 - i do begin
ADrawInfo[j].Mark := nil;
ADrawInfo[j].Images := nil;
end;
Result := ColumnCount;
end;
end
else begin
// debug line mark ?
if FCurLineHasDebugMark then begin
if Length(ADrawInfo) < ColumnCount+1 then
SetLength(ADrawInfo, Max(ColumnCount+1, AMaxEntries));
for i := Result to ColumnCount - 1 do begin
ADrawInfo[i].Mark := nil;
ADrawInfo[i].Images := nil;
end;
if Result < ColumnCount then
Result := ColumnCount
else
Result := ColumnCount + 1;
ADrawInfo[Result-1].Mark := nil;
ADrawInfo[Result-1].Images := FBookMarkOpt.BookmarkImages;
ADrawInfo[Result-1].IconIdx := DebugMarksImageIndex;
end;
end;
end;
FCurLineHasDebugMark := False; // done
end;
constructor TIDESynGutterMarks.Create(AOwner: TComponent);
begin
inherited Create(AOwner);