Recent

Author Topic: [SOLVED] Treeview flicker w. Hottrack when running inside NPP Plugin (dark mode)  (Read 10477 times)

d7_2_laz

  • Hero Member
  • *****
  • Posts: 650
Made good progress to bring an old 32bit DLL plugin for Notepad++ to Lazarus 64bit.
The code base for the demo i experiment with is from
"Notepad++ Plugin Template for Delphi & Lazarus"
https://bitbucket.org/rdipardo/delphiplugintemplate/src/default/

After some beginner struggle it seems to work quite fine.
Regarding the gui there are only two major things left. The one i'd like to ask here, because i think it might be LCL related too.

Treeview (will need a bit additional work on custom paint here, no problem):
In my own component (based on custom treeview) now i had to notice heavy horrible flicker on mouse move.
Tried to find the reason: it is here:
    TreeviewXY.Hottrack := true.
Without Hottrack no flicker.  And, no flicker in 'normal' mode (light theme) too.

What's going on?
What i do assume is that there is a concurrent painting LCL vs. NPP themeing where both are disturbing and triggering each other, which results in a heavy flicker. Pls. note that i use myself extensively custom drawing without seeing such flicker.

I could reproduce simply by placing a shelltreeview onto a plugin demo docking form.
Hotrack on:    heavy flicker in dark mode
Hotrack off:   no flicker at all

Unfortunately i need the Hottrack for some reasons and won't like to switch it off.
Found the guilty line here:

Code: Pascal  [Select][+][-]
  1. //treeview.inc:
  2. procedure TCustomTreeView.UpdateHotTrack(X, Y: Integer);
  3. begin
  4. ...............
  5. ..............  
  6.   Invalidate;     // OOPS! This will repaint the whole control .....
  7. end;

From my own code for 'mouse hover' i was used for such case:
- repaint the line where the mouse is over
- repaint/refresh the line the mouse pointer had left
That had been ugly enough, because i had been forced to duplicate the whole treeview paint code, only because there is not measure to override/influence the color of the background erasure at the beginning (i had posted a special forum's topic on this times ago). Anyhow.

My question to the LCL experts: is there a more granular approach possible, without engaging the massive cavalry of invalidating all?

If someone is interested to try a test project: i can provide one, which would only require:
Lazarus 3.x (up) Windows 64 bit
A notepad++ 64bit installation recent version (i mainly prefer portable versions)
Select "dark mode" in the NPP options
And adapt two paths in the project settings.

Edit: i assume that the Delphi users of the demo plugin template don't have this problem, as here the treeview is OS-based, not pure VCL object, differently to Lazarus.
« Last Edit: January 20, 2025, 12:15:35 pm by d7_2_laz »
Lazarus 4.4  FPC 3.2.2 Win10 64bit

d7_2_laz

  • Hero Member
  • *****
  • Posts: 650
I just did a little POC, and yeees

It works, but of course it covers only half of the story.

Code: Pascal  [Select][+][-]
  1. procedure TCustomTreeView.UpdateHotTrack(X, Y: Integer);
  2. var aNode: TTreeNode; R: TRect;
  3. begin
  4. .........
  5.    // Invalidate;   // please no brute force here
  6.    aNode := GetNodeAtY(Y);
  7.    If aNode <> nil then begin
  8.        R := ANode.DisplayRect(False);
  9.        InvalidateRect(Handle, @R, True);
  10.    end;

The previous mouse-hovered, now mouse-left node would be needed to be remembered and InvalidateRect'ed too. This memory would be needed to be resetted when another context appears.

Any ideas?

Lazarus 4.4  FPC 3.2.2 Win10 64bit

d7_2_laz

  • Hero Member
  • *****
  • Posts: 650
POC, part 2 - yeees - would work perfectly i think:

Code: Pascal  [Select][+][-]
  1. //comctrls.pp:
  2.   TCustomTreeView = class(TCustomControl)
  3.      FHotTrackedPrevNode: TTreeNode;
  4.  
  5. //treeview.inc:
  6.     constructor TCustomTreeView.Create(AnOwner: TComponent);
  7.         FHotTrackedPrevNode := nil;
  8.  
  9.     procedure TCustomTreeView.UpdateHotTrack(X, Y: Integer);
  10.  
  11.       // Invalidate;    // please no brute force
  12.  
  13.       if Assigned(FHotTrackedPrevNode) then begin
  14.         if FHotTrackedPrevNode.Visible then begin
  15.            R := FHotTrackedPrevNode.DisplayRect(False);
  16.            InvalidateRect(Handle, @R, True);
  17.         end;
  18.         FHotTrackedPrevNode := nil;
  19.       end;
  20.       if Assigned(FNodeUnderCursor) then begin
  21.           R := FNodeUnderCursor.DisplayRect(False);
  22.           InvalidateRect(Handle, @R, True);
  23.           FHotTrackedPrevNode := FNodeUnderCursor;
  24.       end;

Would this make sense?

Lazarus 4.4  FPC 3.2.2 Win10 64bit

d7_2_laz

  • Hero Member
  • *****
  • Posts: 650
For the NPP plugin the horrible flicker fully went away ..

Just retested outside the NPP plugin stuff, with a regular stand-alone exe,
and felt that even this would behave a bit more smoother now ...

Any opinions about?
« Last Edit: December 17, 2024, 04:32:48 pm by d7_2_laz »
Lazarus 4.4  FPC 3.2.2 Win10 64bit

AlexTP

  • Hero Member
  • *****
  • Posts: 2665
    • UVviewsoft
It is right approach. If I knew about this flicker before, I would do the same.

PS. Welcome to write plugin for _my_ editor, see signature.

AlexTP

  • Hero Member
  • *****
  • Posts: 2665
    • UVviewsoft
And pls check - is UpdateHotTrack called when I move mouse inside single tree node? If so you call InvalidateRect twice for the same item.

d7_2_laz

  • Hero Member
  • *****
  • Posts: 650
And pls check - is UpdateHotTrack called when I move mouse inside single tree node? If so you call InvalidateRect twice for the same item.

Good point Alex!
Means, something like this ?

Code: Pascal  [Select][+][-]
  1. procedure TCustomTreeView.UpdateHotTrack(X, Y: Integer);
  2. var aNode: TTreeNode; R: TRect; bDoInvalidate: Boolean;
  3. begin
  4. ...........
  5.   // Invalidate;    // Please no brute force here
  6.  
  7.   if Assigned(FHotTrackedPrevNode) then begin
  8.     if FHotTrackedPrevNode.Visible then begin
  9.        bDoInvalidate :=
  10.           ((Not Assigned(FNodeUnderCursor)) Or (FHotTrackedPrevNode.Index <> FNodeUnderCursor.Index));
  11.        if bDoInvalidate then begin
  12.           R := FHotTrackedPrevNode.DisplayRect(False);
  13.           InvalidateRect(Handle, @R, True);
  14.        end;
  15.     end;
  16.     FHotTrackedPrevNode := nil;
  17.   end;
  18.   if Assigned(FNodeUnderCursor) then begin
  19.       R := FNodeUnderCursor.DisplayRect(False);
  20.       InvalidateRect(Handle, @R, True);
  21.       FHotTrackedPrevNode := FNodeUnderCursor;
  22.   end;
  23. end;
Lazarus 4.4  FPC 3.2.2 Win10 64bit

AlexTP

  • Hero Member
  • *****
  • Posts: 2665
    • UVviewsoft
Something like this, thanks.

AlexTP

  • Hero Member
  • *****
  • Posts: 2665
    • UVviewsoft
If a node is deleted programmatically, you should handle this and maybe Nil the FHotTrackedPrevNode.

d7_2_laz

  • Hero Member
  • *****
  • Posts: 650
But if a node is deleted programmatically, eg. triggered by an external system - db, file system or such -, and this should be (randomly) the FHotTrackedPrevNode, then it will be already nil'ed, won't it?

Btw., in an app i did for own usage with same scenario, i didn't use the ttreenode as info about "previous", but the node's index. Don't know if this is a better way ('better a dangling integer than a dangling node'?).
If this should appear as more appropriate, then I'd like to change that of course.

Anyhow i'd need to retrieve back then the ttreenode itself from the index at each mouse move, at least for to ask if it' s visible; hm.
Lazarus 4.4  FPC 3.2.2 Win10 64bit

AlexTP

  • Hero Member
  • *****
  • Posts: 2665
    • UVviewsoft
Yes, better to have dangling index than dangling TTreeNode, so save to Prev index instead, and calculate Node from it.

d7_2_laz

  • Hero Member
  • *****
  • Posts: 650
Yes Alex, i'd agree and change this.
Before i do, i'd like to share an observation:

Need to go one step back.
I have im my custom painting two differenct scenarios, and both should be preserved by the change for not to be too restrictive.
Note, the InvalidateRects done are the trigger, so to say, that on the customdraw side the correct input is given
(= node xy needs to be repainted). Otherwise the customdraw might code what it wants, but the code won't be called ... No invalidation, no painting possible.

Ok, mouse-wheel. Scrolling within the tree via mouse wheel up and down.
How a "hottracked line" should behave, when one uses the mousewheel ?
2 possible flavours (as option):   when wheeling up,
a) the hottracked line stays statically, where it is (and the contents moves underneath it away, going upwards))
b) the hottracked line moves up resp. down too (MS-like style). Selected line and hovered line do change it's position.

I checked if the change proposal would support both behaviour options.
- The initial proposal (from reply #2) does it; ok. Had no side-effects.
- The last proposal does not reliably. Sometimes it may happen one highlighted line to remain as painting relict. Here, the prev-info had been nil'ed out too early.

With other words: the check, if FHotTrackedPrevNode and FNodeUnderCursor are different, might be too thrifty when using the mousewheel.
Aa due to the movement of the wheel both will cross heavily each other (in flavour a.) and so some needed line-invalidations might go lost.
So imo the easier/first solution (logic) here would be the better, as both flavours mentioned could be done with it fine.
« Last Edit: December 18, 2024, 10:05:49 am by d7_2_laz »
Lazarus 4.4  FPC 3.2.2 Win10 64bit

AlexTP

  • Hero Member
  • *****
  • Posts: 2665
    • UVviewsoft
I always thought that even for mousewheel, HotTrack must affect the node under cursor. If it makes some difficulty, it's bad.

d7_2_laz

  • Hero Member
  • *****
  • Posts: 650
Hi Alex,
not bad, doable.
- Hover node, scroll mousewheel -> the original node under the cursor wanders away, cursor stays at same position as before (flavour 1)
- Hover node, scroll mousewheel -> node under the cursor wanders away, but the hover marker follows him (flavour 2)
In my standalone-exe i implemented both (as option) with custom drawing.
The essential part here is only that the invalidation rules do send the right invalidations for any, so that a programmer can decide.

The proposal now is here as follows
- with using an index instead of a ttreenode for to remember the prev state
- i tried to cover your correct note, avoid "InvalidateRect twice for the same item", by comparing the 'Top's of both InvalidateRects and do only the first one if the Tops are identical.
  But i'm not fully convinced. Sometimes i felt sporadical relicts though, not quite sure. So i show it as comments here only.


Code: Pascal  [Select][+][-]
  1. //comctrls.pp:
  2.   TCustomTreeView = class(TCustomControl)
  3.    private
  4.      FHotTrackedIdxPrevNode: Integer;
  5.  
  6. //treeview.inc:
  7.   constructor TCustomTreeView.Create(AnOwner: TComponent);
  8.      FHotTrackedIdxPrevNode := -1;
  9.  
  10.   procedure TCustomTreeView.UpdateHotTrack(X, Y: Integer);
  11.   var aNode: TTreeNode; R: TRect; // rTop: Integer;
  12.   begin
  13.      .........
  14.      //Invalidate;    // Please no brute force here
  15.  
  16.      //rTop := -1;
  17.      if FHotTrackedIdxPrevNode > -1 then begin
  18.         aNode := Items[FHotTrackedIdxPrevNode];
  19.         if (Assigned(aNode) And aNode.Visible) then begin
  20.            R := aNode.DisplayRect(False);
  21.            InvalidateRect(Handle, @R, True);
  22.            //rTop := R.Top;
  23.         end;
  24.         FHotTrackedIdxPrevNode := -1;
  25.      end;
  26.      if Assigned(FNodeUnderCursor) then begin
  27.         R := FNodeUnderCursor.DisplayRect(False);
  28.         //if R.Top <> rTop then
  29.         InvalidateRect(Handle, @R, True);
  30.         FHotTrackedIdxPrevNode := FNodeUnderCursor.AbsoluteIndex;
  31.      end;

My concern here is mainly, to minimize flicker AND to assure that the TCustomTreeView.UpdateHotTrack would do the _correct_ invalidations for any case of custom painting.
Lazarus 4.4  FPC 3.2.2 Win10 64bit

AlexTP

  • Hero Member
  • *****
  • Posts: 2665
    • UVviewsoft
This code looks OK, so only the testing (with mousewheel too) will show is it correct or not...

PS. Indent = 3 spaces? weird. 2 spaces better.

 

TinyPortal © 2005-2018