Calculate minimum gadget sizes (all OS)

Share your advanced PureBasic knowledge/code with the community.
freak
PureBasic Team
PureBasic Team
Posts: 5940
Joined: Fri Apr 25, 2003 5:21 pm
Location: Germany

Calculate minimum gadget sizes (all OS)

Post by freak »

When developing crossplatform programs, one problem is the difference in font sizes among the OS. But even when developing only for Windows, it is a good practice to not assume a fixed font size as that will look bad for people who use a larger default font as well.

The following procedure is used by the PureBasic IDE to adjust gadget sizes to fit the text they contain. This allows the IDE dialogs to look good with any font size and also allows translated text to fit even if it is longer than the default text.

Of course with this you need a more complex code to arrange/resize the gadgets, but as you can see below when this is done you can even change the font (or gadget content) on the fly and easily resize the gadgets to fit.

Code: Select all

; 
;  Calculates the size required to display a Gadget properly.
;
;  Supported Gadgets:
;    Button, Checkbox, Option, Text, String, ComboBox, Image 
;
;  Note:
;    For Gadgets with variable content (String, ComboBox), only the returned height
;    is useful, as the width will only be an absolute minimum value.
;
;  The 'Flags' parameter gives gadget flags to include in the calculation.
;  Currently only #PB_Text_Border makes a difference there.   
;

CompilerIf Defined(Max, #PB_Procedure) = 0
  Procedure Max(a, b)
    If a > b
      ProcedureReturn a
    Else
      ProcedureReturn b
    EndIf
  EndProcedure
CompilerEndIf

CompilerIf #PB_Compiler_OS = #PB_OS_Linux
  Structure PB_Gadget
    *Gadget.GtkWidget
    *Container.GtkWidget
    *VT
    UserData.i
    GadgetData.i[4]
  EndStructure
CompilerEndIf

CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
  Structure OSX_Rect
    top.w
    left.w
    bottom.w
    right.w
  EndStructure
  
  #noErr = 0
CompilerEndIf


; Stores the result in *Width\l and *Height\l
;
Procedure GetRequiredSize(Gadget, *Width.LONG, *Height.LONG, Flags = 0)

  CompilerSelect #PB_Compiler_OS
  CompilerCase #PB_OS_Windows
  
    DC = GetDC_(GadgetID(Gadget))
    oldFont = SelectObject_(DC, GetGadgetFont(Gadget)) 
    Size.SIZE
  
    Select GadgetType(Gadget)
    
      Case #PB_GadgetType_Text
        Text$ = RemoveString(GetGadgetText(Gadget), Chr(10))
        count = CountString(Text$, Chr(13)) + 1
        empty = 0
        maxheight = 0 
        For index = 1 To count 
          Line$ = StringField(Text$, index, Chr(13))
          If Line$ = ""
            empty + 1
          Else 
            GetTextExtentPoint32_(DC, @Line$, Len(Line$), @LineSize.SIZE)
            Size\cx = Max(Size\cx, LineSize\cx)
            Size\cy + LineSize\cy
            maxheight = Max(maxheight, LineSize\cy)
          EndIf
        Next index            
        Size\cy + empty * maxheight  
        
        If Flags & #PB_Text_Border
          Size\cx + GetSystemMetrics_(#SM_CXEDGE) * 2
          Size\cy + GetSystemMetrics_(#SM_CYEDGE) * 2
        Else           
          Size\cx + 2
          Size\cy + 2
        EndIf

      Case #PB_GadgetType_CheckBox, #PB_GadgetType_Option
        Text$ = GetGadgetText(Gadget)
        GetTextExtentPoint32_(DC, @Text$, Len(Text$), @Size.SIZE)
        Size\cx + 20
        Size\cy = Max(Size\cy+2, 20)
        
      Case #PB_GadgetType_Button
        Text$ = GetGadgetText(Gadget)
        GetTextExtentPoint32_(DC, @Text$, Len(Text$), @Size.SIZE)
        Size\cx + GetSystemMetrics_(#SM_CXEDGE)*2
        Size\cy = Max(Size\cy+GetSystemMetrics_(#SM_CYEDGE)*2, 24)
        Size\cx + 10
        
      Case #PB_GadgetType_String
        Text$ = GetGadgetText(Gadget) + "Hg" 
        GetTextExtentPoint32_(DC, @Text$, Len(Text$), @Size.SIZE)
        Size\cx = GetSystemMetrics_(#SM_CXEDGE)*2 
        Size\cy = Max(Size\cy+GetSystemMetrics_(#SM_CXEDGE)*2, 20)
        
      Case #PB_GadgetType_ComboBox
        GetTextExtentPoint32_(DC, @"Hg", 2, @Size.SIZE)
        Size\cy = Max(Size\cy + 8, 21)
        Size\cx = Size\cy  
        
      Case #PB_GadgetType_Image
        Size\cx = GadgetWidth(Gadget)
        Size\cy = GadgetHeight(Gadget)
        
    EndSelect
    
    SelectObject_(DC, oldFont)
    ReleaseDC_(GadgetID(Gadget), DC)
    *Width\l  = Size\cx
    *Height\l = Size\cy
  
  CompilerCase #PB_OS_Linux
  
    *Gadget.PB_Gadget = IsGadget(Gadget)
    
    If *Gadget And *Gadget\Container And GadgetType(Gadget) <> #PB_GadgetType_ComboBox
      gtk_widget_size_request_(*Gadget\Container, @RealSize.GtkRequisition)
      gtk_widget_set_size_request_(*Gadget\Container, -1, -1)
      gtk_widget_size_request_(*Gadget\Container, @Size.GtkRequisition)      
      gtk_widget_set_size_request_(*Gadget\Container, RealSize\Width, RealSize\Height) 
    Else
      gtk_widget_size_request_(GadgetID(Gadget), @RealSize.GtkRequisition)
      gtk_widget_set_size_request_(GadgetID(Gadget), -1, -1)              
      gtk_widget_size_request_(GadgetID(Gadget), @Size.GtkRequisition)    
      gtk_widget_set_size_request_(GadgetID(Gadget), RealSize\Width, RealSize\Height)
    EndIf
    
    If GadgetType(Gadget) = #PB_GadgetType_ComboBox Or GadgetType(Gadget) = #PB_GadgetType_String
      *Width\l  = 20 
    Else
      *Width\l  = Size\Width
    EndIf
        
    *Height\l = Size\Height    

  CompilerCase #PB_OS_MacOS
  
    Type = GadgetType(Gadget)
    
    If Type = #PB_GadgetType_Image    
      *Width\l = GadgetWidth(Gadget)
      *Height\l = GadgetHeight(Gadget)
      
    ElseIf Type = #PB_GadgetType_Text
      realwidth   = GadgetWidth(Gadget)
      
      *Width\l = 40
      *Height\l = 20      
      
      ResizeGadget(Gadget, #PB_Ignore, #PB_Ignore, 1000, #PB_Ignore) 
      
      If GetBestControlRect_(GadgetID(Gadget), @Rect.OSX_Rect, @BaseLine.w) = #noErr
        Height = Rect\bottom - Rect\top
        If Height > 0
        
          Min = 0
          Max = 1000
          
          While Max > Min+2
            Mid = (Min + Max) / 2
            
            ResizeGadget(Gadget, #PB_Ignore, #PB_Ignore, Mid, #PB_Ignore) 
            
            If GetBestControlRect_(GadgetID(Gadget), @Rect.OSX_Rect, @BaseLine.w) <> #noErr
              ProcedureReturn 
            EndIf
            
            If Rect\bottom - Rect\top > Height
              Min = Mid
            Else
              Max = Mid
            EndIf
          Wend
          
          *Width\l = Rect\right - Rect\left + 2 
          *Height\l = Max(Height, 20) 
        EndIf
          
      EndIf 
      
      ResizeGadget(Gadget, #PB_Ignore, #PB_Ignore, realwidth, #PB_Ignore)

    Else 
      If GetBestControlRect_(GadgetID(Gadget), @Rect.OSX_Rect, @BaseLine.w) = #noErr
        *Width\l = Rect\right - Rect\left
        *Height\l = Max(Rect\bottom - Rect\top, 20) 
        
        If Type = #PB_GadgetType_Button Or Type = #PB_GadgetType_String
          *Height\l = Max(*Height\l, 24)        
        EndIf        
      Else 
        *Width\l = 40
        *Height\l = 20
      EndIf
      
      If Type = #PB_GadgetType_String Or Type  = #PB_GadgetType_ComboBox        
        *Width\l = 30 
      EndIf
    
    EndIf
  
  CompilerEndSelect
  
EndProcedure

; convinience wrappers if only one size is needed
;
Procedure GetRequiredWidth(Gadget, Flags = 0)
  Protected Width.l, Height.l
  GetRequiredSize(Gadget, @Width, @Height, Flags)
  ProcedureReturn Width
EndProcedure 

Procedure GetRequiredHeight(Gadget, Flags = 0)
  Protected Width.l, Height.l
  GetRequiredSize(Gadget, @Width, @Height, Flags)
  ProcedureReturn Height
EndProcedure 
Code example:

Code: Select all

Enumeration
  #Button_1
  #Button_2
  #Button_3
  #Checkbox_1
  #Checkbox_2
  #Checkbox_3  
  #String
  #ComboBox
  #ChangeFont
EndEnumeration

Procedure Resize()
  GetRequiredSize(#Button_1, @Button1Width.l, @Button1Height.l)
  GetRequiredSize(#Button_2, @Button2Width.l, @Button2Height.l)
  GetRequiredSize(#Button_3, @Button3Width.l, @Button3Height.l)
  
  Height = Max(Button1Height, Max(Button2Height, Button3Height))
  x = 10
  y = 10
  ResizeGadget(#Button_1, x, y, Button1Width, Height): x + Button1Width + 5
  ResizeGadget(#Button_2, x, y, Button2Width, Height): x + Button2Width + 5
  ResizeGadget(#Button_3, x, y, Button3Width, Height): x + Button3Width + 5
  
  y + Height + 10  
  WindowWidth = x
  
  GetRequiredSize(#Checkbox_1, @Check1Width.l, @Check1Height.l)
  GetRequiredSize(#Checkbox_2, @Check2Width.l, @Check2Height.l)
  GetRequiredSize(#Checkbox_3, @Check3Width.l, @Check3Height.l)
  
  Width = Max(Check1Width, Max(Check2Width, Check3Width))
  ResizeGadget(#Checkbox_1, 10, y, Width, Check1Height): y + Check1Height + 5
  ResizeGadget(#Checkbox_2, 10, y, Width, Check2Height): y + Check2Height + 5
  ResizeGadget(#Checkbox_3, 10, y, Width, Check3Height): y + Check3Height + 5
  
  y + 5
  WindowWidth = Max(WindowWidth, Width+20)
  
  Height = GetRequiredHeight(#String)
  ResizeGadget(#String, 10, y, WindowWidth-20, Height)
  y + Height + 5

  Height = GetRequiredHeight(#ComboBox)
  ResizeGadget(#ComboBox, 10, y, WindowWidth-20, Height)
  y + Height + 10
  
  GetRequiredSize(#ChangeFont, @ButtonWidth.l, @ButtonHeight.l)
  ResizeGadget(#ChangeFont, (WindowWidth-ButtonWidth)/2, y, ButtonWidth, ButtonHeight)
  y + ButtonHeight + 10
  
  ResizeWindow(0, #PB_Ignore, #PB_Ignore, WindowWidth, y)
EndProcedure

If OpenWindow(0, 100, 100, 0, 0, "Size test", #PB_Window_SystemMenu|#PB_Window_Invisible)
  ButtonGadget(#Button_1, 0, 0, 0, 0, "Short")
  ButtonGadget(#Button_2, 0, 0, 0, 0, "-- Longer --")
  ButtonGadget(#Button_3, 0, 0, 0, 0, "------- Longest ------")
  CheckBoxGadget(#Checkbox_1, 0, 0, 0, 0, "Hello world")
  CheckBoxGadget(#Checkbox_2, 0, 0, 0, 0, "The quick brown fox jumped over the lazy dog.")
  CheckBoxGadget(#Checkbox_3, 0, 0, 0, 0, "Whatever")
  StringGadget(#String, 0, 0, 0, 0, "String")
  ComboBoxGadget(#ComboBox, 0, 0, 0, 0)
  AddGadgetItem(#ComboBox, 0, "ComboBox")
  SetGadgetState(#ComboBox, 0)
  ButtonGadget(#ChangeFont, 0, 0, 0, 0, "Change font")

  Resize()
  HideWindow(0, 0)
  
  Repeat
    Event = WaitWindowEvent()
    
    If Event = #PB_Event_Gadget And EventGadget() = #ChangeFont
      If FontRequester("Arial", 20, 0)
        If LoadFont(0, SelectedFontName(), SelectedFontSize(), SelectedFontStyle())
          For Gadget = #Button_1 To #ChangeFont
            SetGadgetFont(Gadget, FontID(0))
          Next Gadget
          Resize()
        EndIf
      EndIf
    EndIf
  Until Event = #PB_Event_CloseWindow
EndIf
quidquid Latine dictum sit altum videtur
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Post by luis »

Very interesting especially for people approaching cross platfom programming for the first time.

Thank you!
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Post by Rescator »

Kickass Freak, bookmarking this for future reference, there's some API calls I wasn't aware of here.

So, does this mean there is a chance for a PureBasic get current system font size function? :P
sverson
Enthusiast
Enthusiast
Posts: 286
Joined: Sun Jul 04, 2004 12:15 pm
Location: Germany

Post by sverson »

Great example. Exactly what I was looking for.
Thank you!
sverson
Enthusiast
Enthusiast
Posts: 286
Joined: Sun Jul 04, 2004 12:15 pm
Location: Germany

Post by sverson »

@Freak: Is there a way to get the ButtonWidth of SpinGadgets and ComboBoxGadgets?
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

Post by PB »

> does this mean there is a chance for a PureBasic get current system font > size function?

http://www.purebasic.fr/english/viewtopic.php?t=3801

and

http://www.purebasic.fr/english/viewtopic.php?t=15361

;)
I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
freak
PureBasic Team
PureBasic Team
Posts: 5940
Joined: Fri Apr 25, 2003 5:21 pm
Location: Germany

Post by freak »

sverson wrote:@Freak: Is there a way to get the ButtonWidth of SpinGadgets and ComboBoxGadgets?
I didn't look for a solution to that as i did not have the need for it. Personally i just make them quite wide so there won't be a problem with that.

If you want to try: For Windows you'd probably have to measure the size of the longest text you want to put in it (as for the other gadgets), and then add some space for the dropdown button etc. For the other OS, you can just remove the code that actually cuts the output value of the api functions to match the Windows output and test if the output is satisfying.
quidquid Latine dictum sit altum videtur
WilliamL
Addict
Addict
Posts: 1252
Joined: Mon Aug 04, 2008 10:56 pm
Location: Seattle, USA

Re: Calculate minimum gadget sizes (all OS)

Post by WilliamL »

Thanks for the info freak! This is a great post!

This is a little twist on what you've done. I don't want my buttons to come out all different sizes, what I would like to do is find the font size that would fit best in the gadget size I've already got. I've got the size of the gadget, I've got the font I want to use, I just need to find the right size to fit the text into the gadget. I suppose the height of the font and the height of the gadget can be ignored (too complicated!) but the length is the important part. Maybe a brute force way to do it would be to load each size of the font then get the TextWidth and see if it is less than the gadget width. I wonder how much white space (pixels) is necessary around text? If I load all the fonts to one constant, will that have the effect of purging the last font?
MacBook Pro-M1 (2021), Sequoia 15.4, PB 6.20
User avatar
DoubleDutch
Addict
Addict
Posts: 3220
Joined: Thu Aug 07, 2003 7:01 pm
Location: United Kingdom
Contact:

Re: Calculate minimum gadget sizes (all OS)

Post by DoubleDutch »

@Freak, this doesn't appear to work properly with OSX (latest PB version).

Is there an update for the OSX part of this routine?
https://deluxepixel.com <- My Business website
https://reportcomplete.com <- School end of term reports system
freak
PureBasic Team
PureBasic Team
Posts: 5940
Joined: Fri Apr 25, 2003 5:21 pm
Location: Germany

Re: Calculate minimum gadget sizes (all OS)

Post by freak »

You can use GadgetWidth/GadgetHeight with #PB_Gadget_RequiredSize now. This code is obsolete.
quidquid Latine dictum sit altum videtur
User avatar
DoubleDutch
Addict
Addict
Posts: 3220
Joined: Thu Aug 07, 2003 7:01 pm
Location: United Kingdom
Contact:

Re: Calculate minimum gadget sizes (all OS)

Post by DoubleDutch »

Ahh.. Thanks for that, didn't know. :)
https://deluxepixel.com <- My Business website
https://reportcomplete.com <- School end of term reports system
Post Reply