' Helper routines for resizing and moving / stretching controls
' in a window and setting the minimum size of a window
   
'factors for placement, used as string: 
'  "TL" or "0.0, 0.0"  = Top left
'  "TM" or "0.5, 0.0"  = Top middle
'  "TR" or "1.0, 0.0"  = Top right
'  "ML" or "0.0, 0.5"  = Middle left 
'  "MM" or "0.5, 0.5"  = Middle middle (Centre)
'  "MR" or "1.0, 0.5"  = Middle right 
'  "BL" or "0.0, 1.0"  = Bottom left
'  "BM" or "0.5, 1.0"  = Bottom middle
'  "BR" or "1.0, 1.0"  = Bottom right

'
'Examples:
'
'Pin control on the bottom-right-hand side of the window
'pinCtl(hCtl, hWin, "BR")
'pinCtl(hCtl, hWin, "1.0, 1.0")

'Move / Stretch control if window is resized, from offset 1 to offset 2
'pinCtl(hCtl, hWin, "TL", "BR" )
'pinCtl(hCtl, hWin, "0.0, 0.0", "1.0, 1.0")

'Stop resizing and moving control if the window falls below a certain size (pixels)
'unpinCtl(hCtl, hWin, 450, 350)

'Move / Resize the controls when WM_SIZE message of parent window is handled:
'resizeControls(hWin)

'Limit the minimum permitted size of a window to width and height (pixels),
'can be used with the WM_GETMINMAXINFO message:
'setMinSize(450, 350)

=================================================================================== 

uses corewin

% HWND_DESKTOP=0
% WM_SIZING=532
% WM_GETMINMAXINFO=36
% SWP_NOZORDER=4
% SWP_DEFERERASE=0x2000

type Sizel
   int cx    'width
   int cy    'height
end type

type PFactor
   double fx
   double fy
end type
      
type sizing_data
   int index
   sys hParent
   sys hCtl
   PFactor pf1                    'placement factors
   PFactor pf2                    'placement factors
   RECT ofs                       'offset Left, Top, Right, Bottom
   int unpinned                   '0=pinned, 1=unpinned
   int w, h                       'width and height of parent
end type

'bounds for record data
type MaxPinnedCtls
   int count
end type

MaxPinnedCtls MaxPinned = {10}   
redim sizing_data PinCtl_Rec[MaxPinned.count]


'Attach position of the Control relative to the Parent window
sub pinCtl(sys hCtl, sys hParent, string placement1, optional string placement2="NONE_")
    PFactor pf1, pf2 

    string place1=lcase(ltrim rtrim(placement1))
    string place2=lcase(ltrim rtrim(placement2))

    'Method 1: Static attachment
    while 1 
       if place1="tl" then pf1.fx=0.0 : pf1.fy=0.0 : exit while
       if place1="tm" then pf1.fx=0.5 : pf1.fy=0.0 : exit while
       if place1="tr" then pf1.fx=1.0 : pf1.fy=0.0 : exit while
       if place1="ml" then pf1.fx=0.0 : pf1.fy=0.5 : exit while
       if place1="mm" then pf1.fx=0.5 : pf1.fy=0.5 : exit while
       if place1="mr" then pf1.fx=1.0 : pf1.fy=0.5 : exit while
       if place1="bl" then pf1.fx=0.0 : pf1.fy=1.0 : exit while
       if place1="bm" then pf1.fx=0.5 : pf1.fy=1.0 : exit while
       if place1="br" then pf1.fx=1.0 : pf1.fy=1.0 : exit while              
       'factors as values?
       int pos=instr(place1,",")
       if pos then pf1.fx=val(left(place1,pos-1)) : pf1.fy=val(mid(place1,pos+1)) : exit while
       mbox "Error: wrong placement 1 in pinCtl function" : exit while
    wend
    if place2="none_" then pf2.fx=pf1.fx : pf2.fy=pf1.fy
       
    'Method 2: Stretchy attachment?
    while 1
       if place2="none_" then exit while 
       if place2="tl" then pf2.fx=0.0 : pf2.fy=0.0 : exit while
       if place2="tm" then pf2.fx=0.5 : pf2.fy=0.0 : exit while
       if place2="tr" then pf2.fx=1.0 : pf2.fy=0.0 : exit while
       if place2="ml" then pf2.fx=0.0 : pf2.fy=0.5 : exit while
       if place2="mm" then pf2.fx=0.5 : pf2.fy=0.5 : exit while
       if place2="mr" then pf2.fx=1.0 : pf2.fy=0.5 : exit while
       if place2="bl" then pf2.fx=0.0 : pf2.fy=1.0 : exit while
       if place2="bm" then pf2.fx=0.5 : pf2.fy=1.0 : exit while
       if place2="br" then pf2.fx=1.0 : pf2.fy=1.0 : exit while              
       'factors as values?
       pos=instr(place2,",")
       if pos then pf2.fx=val(left(place2,pos-1)) : pf2.fy=val(mid(place2,pos+1)) : exit while
       mbox "Error: wrong placement 2 in pinCtl function" : exit while
    wend   

    Sizel ctlSize
    RECT pRect, cRect
    RECT ofs
     
    'Get the size of the hParent window to be able to calculate offsets
    GetClientRect(hParent, &pRect)
    ctlSize.cx = pRect.right : ctlSize.cy = pRect.bottom
    GetWindowRect(hCtl, &cRect)
       
    'Calculate the offsets of the left, top, bottom, and right of the control.
    MapWindowPoints(HWND_DESKTOP, GetParent(hCtl),  &cRect, 2)
    ofs.left=  cRect.left   - ctlSize.cx*pf1.fx
    ofs.top=   cRect.top    - ctlSize.cy*pf1.fy
    ofs.right= cRect.right  - ctlSize.cx*pf2.fx
    ofs.bottom=cRect.bottom - ctlSize.cy*pf2.fy

    'Add entry to the data records
    static int idx 
    'does hCtl already exist?
    int i
    for i=1 to idx
      if PinCtl_Rec[i].hCtl=hCtl then
        idx=i-1
        exit for
      end if
    next i               
    idx+=1
    if idx>MaxPinned.count then
      MaxPinned.count+=10
      redim sizing_data PinCtl_Rec[MaxPinned.count]
    end if  
    PinCtl_Rec[idx].index=idx
    PinCtl_Rec[idx].hParent=hParent
    PinCtl_Rec[idx].hCtl=hCtl
    PinCtl_Rec[idx].pf1.fx=pf1.fx
    PinCtl_Rec[idx].pf1.fy=pf1.fy
    PinCtl_Rec[idx].pf2.fx=pf2.fx
    PinCtl_Rec[idx].pf2.fy=pf2.fy
    PinCtl_Rec[idx].ofs.left=ofs.left
    PinCtl_Rec[idx].ofs.top=ofs.top
    PinCtl_Rec[idx].ofs.right=ofs.right
    PinCtl_Rec[idx].ofs.bottom=ofs.bottom    
end sub

'Mark control to stop resizing / moving at certain size of window (pixels)
sub unpinCtl(sys hCtl, sys hParent, int width, height)
    int ix
    bool found=false
    for ix=1 to MaxPinned.count
       if PinCtl_Rec[ix].hCtl=hCtl then
         PinCtl_Rec[ix].unpinned=1
         PinCtl_Rec[ix].w=width
         PinCtl_Rec[ix].h=height
         found=true : exit for
       end if  
    next ix
    if found=false then mbox "Error: Cannot find hCtl: " hCtl "in PinCtl_Rec"
end sub

'Procedure for resizing attached controls when WM_SIZE message is handled
sub resizeControls(sys hWin)
    PFactor pf1, pf2
    RECT ofs   
    Sizel ctlSize   
    RECT pRect, cRect   'parent, control

    'Get the new size of the window
    GetClientRect(hWin, &pRect)
    ctlSize.cx = pRect.right : ctlSize.cy = pRect.bottom   

    int ix
    'Go through each element in the data entries to see if it needs to be moved.
    for ix = 1 to MaxPinned.count
        if PinCtl_Rec[ix].index=0 then exit for
        'If the element was inside the window that was resized, it needs to move.
        if hWin = PinCtl_Rec[ix].hParent then
            'Get the data out of the data entries for that record
            pf1.fx=PinCtl_Rec[ix].pf1.fx : pf1.fy=PinCtl_Rec[ix].pf1.fy : pf2.fx=PinCtl_Rec[ix].pf2.fx : pf2.fy=PinCtl_Rec[ix].pf2.fy            
            ofs.left=PinCtl_Rec[ix].ofs.left : ofs.top=PinCtl_Rec[ix].ofs.top : ofs.right=PinCtl_Rec[ix].ofs.right : ofs.bottom=PinCtl_Rec[ix].ofs.bottom            

            'Stop resizing / moving if hParent falls below a certain size (pixels)?
            if PinCtl_Rec[ix].unpinned=1 then
              if ctlSize.cx < PinCtl_Rec[ix].w then ctlSize.cx=PinCtl_Rec[ix].w
              if ctlSize.cy < PinCtl_Rec[ix].h then ctlSize.cy=PinCtl_Rec[ix].h                            
            end if
 
            'Calculate the new position and size of the control
            ofs.left+=ctlSize.cx*pf1.fx : ofs.top+=ctlSize.cy*pf1.fy : ofs.right+=ctlSize.cx*pf2.fx : ofs.bottom+=ctlSize.cy*pf2.fy
            SetWindowPos(PinCtl_Rec[ix].hCtl, 0, ofs.left, ofs.top, (ofs.right-ofs.left), (ofs.bottom-ofs.top), SWP_NOZORDER|SWP_DEFERERASE)                                                     
        end if
    next ix
end sub

'Can be combined with WM_GETMINMAXINFO message
macro setMinSize(xMin, yMin)
   MINMAXINFO *mm
   @mm = lParam

   mm.ptMinTrackSize.x = xMin
   mm.ptMinTrackSize.y = yMin
end macro

=============================================================================
'Adapt offsets of an existing control, using the given factors
'Optional add delta to position 
sub modpinCtl(sys hCtl, optional int dx=0,dy=0,dcx=0,dcy=0)
    Sizel ctlSize   
    RECT pRect, cRect   'parent, control
    RECT ofs
    
    int i
    for i=1 to MaxPinned.count
      if PinCtl_Rec[i].hCtl=hCtl then
        exit for
      end if
    next i
    if PinCtl_Rec[i].hCtl != hCtl then exit sub               

    'Get the size of the hParent window to be able to calculate offsets
    GetClientRect(PinCtl_Rec[i].hParent, &pRect)
    ctlSize.cx = pRect.right : ctlSize.cy = pRect.bottom
    GetWindowRect(hCtl, &cRect)

    'Calculate the offsets of the left, top, bottom, and right of the control.
    MapWindowPoints(HWND_DESKTOP, GetParent(hCtl),  &cRect, 2)
    ofs.left=  cRect.left   + dx  - ctlSize.cx*PinCtl_Rec[i].pf1.fx
    ofs.top=   cRect.top    + dy  - ctlSize.cy*PinCtl_Rec[i].pf1.fy
    ofs.right= cRect.right  + dcx - ctlSize.cx*PinCtl_Rec[i].pf2.fx
    ofs.bottom=cRect.bottom + dcy - ctlSize.cy*PinCtl_Rec[i].pf2.fy

    PinCtl_Rec[i].ofs.left=ofs.left
    PinCtl_Rec[i].ofs.top=ofs.top
    PinCtl_Rec[i].ofs.right=ofs.right
    PinCtl_Rec[i].ofs.bottom=ofs.bottom    
end sub
