Recent

Author Topic: Themed checkboxes for TreeView - Dark mode  (Read 541 times)

apeoperaio

  • Sr. Member
  • ****
  • Posts: 281
Themed checkboxes for TreeView - Dark mode
« on: September 19, 2024, 03:04:26 pm »
I have an application using a treeview with checkboxes. In order to do that I fill an ImageList with CheckBox images using ThemeService.

It works fine with light mode but not with dark mode, since the images have white background.
Is there a way to adapt the icons to the light/dark mode?

I use the following code to populate the image list:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.fillimagelist();
  2. var
  3.   aSize: TSize;
  4.   aBMP: TBitmap;
  5.   aDetails: TThemedElementDetails;
  6.   aRect: TRect;
  7. begin
  8.   ImageList1.Clear;
  9.   aDetails:= ThemeServices.GetElementDetails(tbCheckBoxUncheckedNormal);
  10.   aSize:= ThemeServices.GetDetailSize(aDetails);
  11.   ImageList1.Width:= aSize.cx;
  12.   ImageList1.Height:= aSize.cy;
  13.   aBMP:= TBitmap.Create;
  14.   with aBMP do
  15.     begin
  16.       SetSize(aSize.cx, aSize.cy);
  17.       Transparent:= True;
  18.       TransparentColor:= clForm;
  19.       Brush.Color:= TransparentColor;
  20.       Canvas.FillRect(0,0, Width,Height);
  21.     end;
  22.   aRect:=Rect(0, 0, aSize.cx, aSize.cy);
  23.   // 0 - tbCheckBoxUncheckedNormal - none selected
  24.   ThemeServices.DrawElement(aBMP.Canvas.Handle, aDetails, aRect, nil);
  25.   ImageList1.Add(aBMP, nil);
  26.  
  27.   // 1 - tbCheckBoxCheckedNormal - all selected
  28.   aBMP.Canvas.FillRect(0,0, Width,Height);
  29.   aDetails:=ThemeServices.GetElementDetails(tbCheckBoxCheckedNormal);
  30.   ThemeServices.DrawElement(aBMP.Canvas.Handle, aDetails, aRect, nil);
  31.   ImageList1.Add(aBMP, nil);
  32.  
  33.   // 2 - tbCheckBoxMixedNormal -  mixed selection
  34.   aBMP.Canvas.FillRect(0,0, Width,Height);
  35.   aDetails:=ThemeServices.GetElementDetails(tbCheckBoxMixedNormal);
  36.   ThemeServices.DrawElement(aBMP.Canvas.Handle, aDetails, aRect, nil);
  37.   ImageList1.Add(aBMP, nil);
  38.  
  39.   // 3 - tbCheckBoxUncheckedDisabled - unchecked disabled
  40.   aBMP.Canvas.FillRect(0,0, Width,Height);
  41.   aDetails:=ThemeServices.GetElementDetails(tbCheckBoxUncheckedDisabled);
  42.   ThemeServices.DrawElement(aBMP.Canvas.Handle, aDetails, aRect, nil);
  43.   ImageList1.Add(aBMP, nil);
  44.  
  45.   // 4 - tbCheckBoxCheckedDisabled - checked disabled (e.g. 3d descriptor when tbOnly2D checked)
  46.   aBMP.Canvas.FillRect(0,0, Width,Height);
  47.   aDetails:=ThemeServices.GetElementDetails(tbCheckBoxCheckedDisabled);
  48.   ThemeServices.DrawElement(aBMP.Canvas.Handle, aDetails, aRect, nil);
  49.   ImageList1.Add(aBMP, nil);
  50.  
  51.   // 5 - tbCheckBoxMixedDisabled -  mixed selection disabled
  52.   aBMP.Canvas.FillRect(0,0, Width,Height);
  53.   aDetails:=ThemeServices.GetElementDetails(tbCheckBoxMixedDisabled);
  54.   ThemeServices.DrawElement(aBMP.Canvas.Handle, aDetails, aRect, nil);
  55.   ImageList1.Add(aBMP, nil);
  56.  
  57.   FreeAndNil(aBMP);
  58. end;
  59.  

I attached a simple project to reproduce the behaviour.

Lazarus 3.5 (rev lazarus_3_4-120-g7df430ba84) FPC 3.2.2 aarch64-darwin-cocoa
« Last Edit: September 19, 2024, 03:06:08 pm by apeoperaio »

wp

  • Hero Member
  • *****
  • Posts: 12459
Re: Themed checkboxes for TreeView - Dark mode
« Reply #1 on: September 19, 2024, 04:37:41 pm »
Not only a mac issue, it happens in the same way also in Linux/qt5. In qt6, the background is dark, but there are tiny bright pixels at the corner of the boxes, probably where  there is an intermediate alpha value.

I would not work with the ancient TransparentColor concept any more. The better transparency concept is the alpha channel, i.e. 32bit per pixels where one byte is reserved for transparency. A problem is how to initialize the alpha channel with 0 (this means: fully transparent pixel) because the LCL color constants occupy the 4th byte for something else. A possible solution is to recreate the bitmap for each checkbox type again (because this initializes the pixel memory with zero each time), or to use an intermediate TLazIntfImage which gives access to the alpha channel:

Code: Pascal  [Select][+][-]
  1. uses
  2.   FPColor, IntfGraphics;
  3.  
  4. procedure FillTransparent(bmp: TBitmap);
  5. var
  6.   img: TLazIntfImage;
  7. begin
  8.   img := bmp.CreateIntfImage;
  9.   try
  10.     img.FillPixels(colTransparent);
  11.     bmp.LoadfromIntfImage(img);
  12.   finally
  13.     img.Free;
  14.   end;
  15. end;

Code: Pascal  [Select][+][-]
  1. procedure TForm1.fillimagelist();
  2. var
  3.   aSize: TSize;
  4.   aBMP: TBitmap;
  5.   aDetails: TThemedElementDetails;
  6.   aRect: TRect;
  7. begin
  8.   ImageList1.Clear;
  9.   aDetails:= ThemeServices.GetElementDetails(tbCheckBoxUncheckedNormal);
  10.   aSize:= ThemeServices.GetDetailSize(aDetails);
  11.   ImageList1.Width:= aSize.cx;
  12.   ImageList1.Height:= aSize.cy;
  13.   aBMP:= TBitmap.Create;
  14.   with aBMP do
  15.     begin
  16.       PixelFormat := pf32Bit;
  17.       SetSize(aSize.cx, aSize.cy);
  18.     end;
  19.   FillTransparent(aBMP);
  20.   aRect:=Rect(0, 0, aSize.cx, aSize.cy);
  21.   // 0 - tbCheckBoxUncheckedNormal - none selected
  22.   ThemeServices.DrawElement(aBMP.Canvas.Handle, aDetails, aRect, nil);
  23.   ImageList1.Add(aBMP, nil);
  24.  
  25.   // 1 - tbCheckBoxCheckedNormal - all selected
  26.   FillTransparent(aBMP);
  27.   aDetails:=ThemeServices.GetElementDetails(tbCheckBoxCheckedNormal);
  28.   ThemeServices.DrawElement(aBMP.Canvas.Handle, aDetails, aRect, nil);
  29.   ImageList1.Add(aBMP, nil);
  30.  
  31.   // 2 - tbCheckBoxMixedNormal -  mixed selection
  32.   FillTransparent(aBMP);
  33.   aDetails:=ThemeServices.GetElementDetails(tbCheckBoxMixedNormal);
  34.   ThemeServices.DrawElement(aBMP.Canvas.Handle, aDetails, aRect, nil);
  35.   ImageList1.Add(aBMP, nil);
  36.  
  37.   // 3 - tbCheckBoxUncheckedDisabled - unchecked disabled
  38.   FillTransparent(aBMP);
  39.   aDetails:=ThemeServices.GetElementDetails(tbCheckBoxUncheckedDisabled);
  40.   ThemeServices.DrawElement(aBMP.Canvas.Handle, aDetails, aRect, nil);
  41.   ImageList1.Add(aBMP, nil);
  42.  
  43.   // 4 - tbCheckBoxCheckedDisabled - checked disabled (e.g. 3d descriptor when tbOnly2D checked)
  44.   FillTransparent(aBMP);
  45.   aDetails:=ThemeServices.GetElementDetails(tbCheckBoxCheckedDisabled);
  46.   ThemeServices.DrawElement(aBMP.Canvas.Handle, aDetails, aRect, nil);
  47.   ImageList1.Add(aBMP, nil);
  48.  
  49.   // 5 - tbCheckBoxMixedDisabled -  mixed selection disabled
  50.   FillTransparent(aBMP);
  51.   aDetails:=ThemeServices.GetElementDetails(tbCheckBoxMixedDisabled);
  52.   ThemeServices.DrawElement(aBMP.Canvas.Handle, aDetails, aRect, nil);
  53.   ImageList1.Add(aBMP, nil);
  54.  
  55.   FreeAndNil(aBMP);
  56. end;              

This works in qt5 (see screenshot for Manjaro Linux), Windows, and, basically, in qt6 (but at least in the darkmode of my qt6 there is hardly no contrast between the background and the border of the checkboxes). It does not work in gt2, gtk3 and - cocoa. So - sorry, no solution for you issue. In your case, I think that this is due to an issue in the way how cocoa handles the ThemeServices. There were some changes recently regarding ThemeServices. Maybe you should file a regular bug report so that the specialists can take a look (many developers only rarely visit the forum).
« Last Edit: September 19, 2024, 04:40:42 pm by wp »

apeoperaio

  • Sr. Member
  • ****
  • Posts: 281
Re: Themed checkboxes for TreeView - Dark mode
« Reply #2 on: September 19, 2024, 06:35:29 pm »

apeoperaio

  • Sr. Member
  • ****
  • Posts: 281
Re: Themed checkboxes for TreeView - Dark mode
« Reply #3 on: September 23, 2024, 03:38:09 pm »
fixed in 60f7e4e9.

 

TinyPortal © 2005-2018