I made an enchanced version with two modes: #Box_AllSameSize and a default mode which sizes the children proportionally to their minimum sizes. These mode correspond to gtk boxes with and without the "homogenous" property set.
Also I changed to a smarter way of computing child sizes so that everything is now pixel perfect. There isn't any flickering spacing sizes (as with naive double math) nor excess spacing at the end of a box when rounding occurs (as with naive integer math).
What is still missing (and it's rather important) are the packing flags (fill/expand) and spacers (which make no sense without the packing flags).
Code: Select all
; gadget_size.pb
;
; Calculates the size required to display a Gadget properly.
; By freak
;
; 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(MaxI, #PB_Procedure) = 0
Procedure MaxI(a, b)
If a > b
ProcedureReturn a
Else
ProcedureReturn b
EndIf
EndProcedure
CompilerEndIf
CompilerIf Defined(MinI, #PB_Procedure) = 0
Procedure MinI(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\i and *Height\i
Procedure GetRequiredSize(Gadget, *Width.Integer, *Height.Integer, 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 = MaxI(Size\cx, LineSize\cx)
Size\cy + LineSize\cy
maxheight = MaxI(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 = MaxI(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 = MaxI(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 = MaxI(Size\cy+GetSystemMetrics_(#SM_CXEDGE)*2, 20)
Case #PB_GadgetType_ComboBox
GetTextExtentPoint32_(DC, @"Hg", 2, @Size.SIZE)
Size\cy = MaxI(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\i = Size\cx
*Height\i = Size\cy
CompilerCase #PB_OS_Linux
Protected RealSize.GtkRequisition
Protected Size.GtkRequisition
Protected *Gadget.PB_Gadget
*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\i = 20
Else
*Width\i = Size\Width
EndIf
*Height\i = Size\Height
CompilerCase #PB_OS_MacOS
Type = GadgetType(Gadget)
If Type = #PB_GadgetType_Image
*Width\i = GadgetWidth(Gadget)
*Height\i = GadgetHeight(Gadget)
ElseIf Type = #PB_GadgetType_Text
realwidth = GadgetWidth(Gadget)
*Width\i = 40
*Height\i = 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\i = Rect\right - Rect\left + 2
*Height\i = MaxI(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\i = Rect\right - Rect\left
*Height\i = MaxI(Rect\bottom - Rect\top, 20)
If Type = #PB_GadgetType_Button Or Type = #PB_GadgetType_String
*Height\i = MaxI(*Height\i, 24)
EndIf
Else
*Width\i = 40
*Height\i = 20
EndIf
If Type = #PB_GadgetType_String Or Type = #PB_GadgetType_ComboBox
*Width\i = 30
EndIf
EndIf
CompilerEndSelect
EndProcedure
Code: Select all
XIncludeFile "gadget_size.pb"
Enumeration
#SGadget_Gadget
#SGadget_Box
EndEnumeration
Enumeration
#Box_Horizontal = 0
#Box_Vertical = 1
#Box_AllSameSize = 2
EndEnumeration
Structure SIntArray
i.i[0]
EndStructure
Structure SSize
W.i
H.i
EndStructure
Structure SGadget
Kind.i
Number.i
Padding.i
MinSize.SSize
EndStructure
Structure SBox Extends SGadget
BoxOptions.i
Spacing.i
List Children.SBox()
EndStructure
Declare SGadget_UpdateRequiredSize(*G.SGadget)
Procedure SGadget_Box_UpdateRequiredSize(*G.SBox)
Protected LargestHeight
Protected LargestWidth
Protected ChildCount
Protected SpacingTotal
Protected MinHeight
Protected MinWidth
; Calculate child sizes
ForEach *G\Children()
SGadget_UpdateRequiredSize(*G\Children())
Next
; Fill some variables common to all layout modes
ChildCount = ListSize(*G\Children())
ForEach *G\Children()
LargestWidth = MaxI(*G\Children()\MinSize\W, LargestWidth)
LargestHeight = MaxI(*G\Children()\MinSize\H, LargestHeight)
Next
SpacingTotal = (ChildCount-1) * *G\Spacing
; Calculate own required size (depending on child sizes)
If *G\BoxOptions & #Box_Vertical
*G\MinSize\W = LargestWidth
If *G\BoxOptions & #Box_AllSameSize
*G\MinSize\H = LargestHeight * ChildCount + SpacingTotal
Else ; Not all same size
MinHeight = 0
ForEach *G\Children()
MinHeight + *G\Children()\MinSize\H
Next
MinHeight + SpacingTotal
*G\MinSize\H = MinHeight
EndIf
Else ; Horizontal
*G\MinSize\H = LargestHeight
If *G\BoxOptions & #Box_AllSameSize
*G\MinSize\W = LargestWidth * ChildCount + SpacingTotal
Else ; Not all same size
MinWidth = 0
ForEach *G\Children()
MinWidth + *G\Children()\MinSize\W
Next
MinWidth + SpacingTotal
*G\MinSize\W = MinWidth
EndIf
EndIf
EndProcedure
Declare SGadget_Resize(*G.SGadget, x, y, w, h)
Procedure SGadget_Box_Resize(*G.SBox, x, y, w, h)
Protected ChildCount, ChildCountDown
Protected LargestHeight
Protected LargestWidth
Protected SpacingTotal
Protected Leftover
Protected LeftoverForThis
Protected cx, cw, cy, ch
ChildCount = ListSize(*G\Children())
ChildCountDown = ChildCount
SpacingTotal = (ChildCount-1)**G\Spacing
If *G\BoxOptions & #Box_Vertical
If *G\BoxOptions & #Box_AllSameSize
; Find largest
ForEach *G\Children()
LargestHeight = MaxI(*G\Children()\MinSize\H, LargestHeight)
Next
; Find leftover space to divide it evenly among children
Leftover = h - LargestHeight*ChildCount - SpacingTotal
ForEach *G\Children()
LeftoverForThis = Leftover / ChildCountDown
ch = LargestHeight + LeftoverForThis
SGadget_Resize(*G\Children(), x, y+cy, w, ch)
Leftover - LeftoverForThis
ChildCountDown - 1
cy + ch + *G\Spacing
Next
Else ; Not all same size
; Find leftover space to divide it evenly among children
Leftover = h - *G\MinSize\h + *g\Padding*2
ForEach *G\Children()
LeftoverForThis = Leftover/ChildCountDown
ch = *G\Children()\MinSize\H + LeftoverForThis
SGadget_Resize(*G\Children(), x, y+cy, w, ch)
Leftover - LeftoverForThis
ChildCountDown - 1
cy + ch + *G\Spacing
Next
EndIf
Else ; Horizontal
If *G\BoxOptions & #Box_AllSameSize
; Find largest
ForEach *G\Children()
LargestWidth = MaxI(*G\Children()\MinSize\W, LargestWidth)
Next
; Find leftover space to divide it evenly among children
Leftover = w - LargestWidth*ChildCount - SpacingTotal
ForEach *G\Children()
LeftoverForThis = Leftover/ChildCountDown
cw = LargestWidth + LeftoverForThis
SGadget_Resize(*G\Children(), x+cx, y, cw, h)
Leftover - LeftoverForThis
ChildCountDown - 1
cx + cw + *G\Spacing
Next
Else ; Not all same size
; Find leftover space to divide it evenly among children
Leftover = w - *G\MinSize\W + *G\Padding*2
ForEach *G\Children()
LeftoverForThis = Leftover/ChildCountDown
cw = *G\Children()\MinSize\W + LeftoverForThis
SGadget_Resize(*G\Children(), x+cx, y, cw, h)
Leftover - LeftoverForThis
ChildCountDown - 1
cx + cw + *G\Spacing
Next
EndIf
EndIf
EndProcedure
Procedure SGadget_UpdateRequiredSize(*G.SGadget)
Select *G\Kind
Case #SGadget_Gadget
GetRequiredSize(*G\Number, @*g\MinSize\W, @*g\MinSize\H)
;Debug Str(*G\Number) + ": " + Str(*g\MinSize\W)
Case #SGadget_Box
SGadget_Box_UpdateRequiredSize(*G)
Default
CallDebugger
EndSelect
*G\MinSize\H + *G\Padding*2
*G\MinSize\W + *G\Padding*2
EndProcedure
Procedure SGadget_Resize(*G.SGadget, x, y, w, h)
x + *G\Padding
y + *G\Padding
w - *G\Padding*2
h - *G\Padding*2
Select *G\Kind
Case #SGadget_Gadget
ResizeGadget(*G\Number, x, y, w, h)
Case #SGadget_Box
SGadget_Box_Resize(*G, x, y, w, h)
Default
CallDebugger
EndSelect
EndProcedure
Procedure SGadget_SetWindowBounds(*G.SGadget, Window, AddWidth=0, AddHeight=0)
WindowBounds(Window, *G\MinSize\W+AddWidth, *G\MinSize\H+AddHeight, #PB_Ignore, #PB_Ignore)
EndProcedure
Procedure AddGadgetToBox(*Box.SBox, Gadget)
AddElement(*box\Children())
*Box\Children()\Number = Gadget
EndProcedure
Procedure SGadget_Box(*parent.SBox=0, BoxOptions=#Box_Horizontal, g0=-1, g1=-1, g2=-1, g3=-1, g4=-1, g5=-1, g6=-1, g7=-1, g8=-1)
Protected *Box.SBox
Protected *Gadgets.SIntArray
Protected I
If *parent
AddElement(*parent\Children())
*Box = *parent\Children()
Else
*Box = AllocateMemory(SizeOf(SBox))
InitializeStructure(*Box, SBox)
EndIf
*Box\Number = -1
*Box\BoxOptions = BoxOptions
*box\Kind = #SGadget_Box
*Gadgets = @g0
For I = 0 To 8
If *gadgets\i[i] <> -1
AddGadgetToBox(*Box, *Gadgets\i[i])
EndIf
Next
ProcedureReturn *Box
EndProcedure
;- EXAMPLE:
Enumeration
#g_toplabel
#g_label0 : #g_label1
#g_b0 : #g_b1 : #g_b2
#g_label2
EndEnumeration
#W = 512
#H = 384
OpenWindow(0, 100, 50, #W, #H, "", #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget | #PB_Window_Invisible)
TextGadget(#g_toplabel, 0, 0, 200, 22, "Adjustable gadgets: ")
Define *A.SBox, *B.SBox, *C.SBox, *D.SBox
*A = SGadget_Box(0, #Box_Horizontal)
; 3 Horizontally stacked buttons of variable width
*B = SGadget_Box(*A, #Box_Horizontal, #g_b0, #g_b1, #g_b2)
ButtonGadget(#g_b0,0,0,0,0,"1")
ButtonGadget(#g_b1,0,0,0,0,"22")
ButtonGadget(#g_b2,0,0,0,0,"333 variable width buttons")
; Multiple vertically stacked buttons and text of variable height
; And a child with all same size
*C = SGadget_Box(*A, #Box_Vertical)
TextGadget(#g_label2,0,0,0,0, "Testing the variable height")
For I = 0 To 5
If i = 3
AddGadgetToBox(*C, #g_label2)
EndIf
AddGadgetToBox(*C, ButtonGadget(#PB_Any, 0, 0, 0, 0, "Hello " + Str(I)))
Next
; Child box with all same height
*C2 = SGadget_Box(*C, #Box_Vertical | #Box_AllSameSize)
AddGadgetToBox(*C2, TextGadget(#PB_Any, 0, 0, 0, 0, "This text and 6-8 are all same height:"))
For I = 6 To 8
AddGadgetToBox(*C2, ButtonGadget(#PB_Any, 0, 0, 0, 0, Str(I)))
Next
; 2 Horizontally stacked text of same width
*D = SGadget_Box(*A, #Box_Horizontal | #Box_AllSameSize, #g_label0, #g_label1)
TextGadget(#g_label0,0,0,0,0,"Text 1", #PB_Text_Border)
TextGadget(#g_label1,0,0,0,0,"All same size text", #PB_Text_Border)
ForEach *A\Children()
*a\Children()\Padding = 5
Next
*A\Padding = 10
; *A\Spacing = 7
SGadget_UpdateRequiredSize(*A)
SGadget_Resize(*A, 0, 20, WindowWidth(0), WindowHeight(0)-20)
SGadget_SetWindowBounds(*A, 0, 0, 20)
HideWindow(0, 0)
Repeat
Select WaitWindowEvent()
Case #PB_Event_SizeWindow
SGadget_Resize(*A, 0, 20, WindowWidth(0), WindowHeight(0)-20)
Case #PB_Event_CloseWindow
Break
EndSelect
ForEver