/////////////////////////////////////////////////////////////////////////////
//
// This file is part of ResizableLib
// http://sourceforge.net/projects/resizablelib
//
// Copyright (C) 2000-2004 by Paolo Messina
// http://www.geocities.com/ppescher - mailto:ppescher@hotmail.com
//
// The contents of this file are subject to the Artistic License (the "License").
// You may not use this file except in compliance with the License. 
// You may obtain a copy of the License at:
// http://www.opensource.org/licenses/artistic-license.html
//
// If you find this code useful, credits would be nice!
//
/////////////////////////////////////////////////////////////////////////////

/*!
 *  @file
 *  @brief Interface for the CResizableLayout class.
 */

#if !defined(AFX_RESIZABLELAYOUT_H__INCLUDED_)
#define AFX_RESIZABLELAYOUT_H__INCLUDED_

#include <afxtempl.h>
#include "ResizableMsgSupport.h"
#include <map>

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

/*! @addtogroup CoreComponents
 *  @{
 */

//! @brief Special type for layout alignment
/*!
 *  Implements anchor points as a percentage of the parent window client area.
 *  Control corners are always kept at a fixed distance from their anchor points,
 *  thus allowing a control to resize proportionally as the parent window is resized.
 */
typedef struct tagANCHOR
{
	int cx = 0; //!< horizontal component, in percent
	int cy = 0; //!< vertical component, in percent

	tagANCHOR() = default;

	tagANCHOR(int x, int y) : cx(x), cy(y)
	{
	}
} ANCHOR, *PANCHOR, *LPANCHOR;

/*! @defgroup ConstAnchors Alignment Constants
 *  Define common layout alignment constants for anchor points.
 *  @{
 */
	//! Anchor to the top-left corner
	const ANCHOR TOP_LEFT(0, 0);
	//! Anchor to the top edge and center horizontally
	const ANCHOR TOP_CENTER(50, 0);
	//! Anchor to the top-right corner
	const ANCHOR TOP_RIGHT(100, 0);
	//! Anchor to the left edge and center vertically
	const ANCHOR MIDDLE_LEFT(0, 50);
	//! Anchor to the center
	const ANCHOR MIDDLE_CENTER(50, 50);
	//! Anchor to the right edge and center vertically
	const ANCHOR MIDDLE_RIGHT(100, 50);
	//! Anchor to the bottom-left corner
	const ANCHOR BOTTOM_LEFT(0, 100);
	//! Anchor to the bottom edge and center horizontally
	const ANCHOR BOTTOM_CENTER(50, 100);
	//! Anchor to the bottom-right corner
	const ANCHOR BOTTOM_RIGHT(100, 100);
// @}

//! @brief Holds a control layout settings
/*!
 *  Layout settings specify how a control must be moved and resized with respect to
 *  the parent window and how it reacts to dynamic changes to its size when painting
 *  its client area, with special care for flickering.
 */
typedef struct tagLAYOUTINFO
{
	//! Handle of the window the layout of which is being defined
	HWND hWnd = nullptr;
	//! Identification number assigned to the callback slot
	UINT_PTR nCallbackID = 0;

	//! Window class name to identify standard controls
	TCHAR sWndClass[MAX_PATH];

	//! Anchor point for the top-left corner
	ANCHOR anchorTopLeft;
	//! Fixed distance for the top-left corner
	SIZE marginTopLeft{};
	
	//! Anchor point for the bottom-right corner
	ANCHOR anchorBottomRight;
	//! Fixed distance for the bottom-right corner
	SIZE marginBottomRight{};

	//! Flag that enables support for custom windows
	BOOL bMsgSupport = FALSE;
	//! Redraw settings for anti-flickering and proper painting
	RESIZEPROPERTIES properties;

	tagLAYOUTINFO()
	{
		sWndClass[0] = 0;
	}

	tagLAYOUTINFO(HWND hwnd, ANCHOR tl_type, SIZE tl_margin, 
		ANCHOR br_type, SIZE br_margin)
		:
		hWnd(hwnd),
		anchorTopLeft(tl_type), marginTopLeft(tl_margin),
		anchorBottomRight(br_type), marginBottomRight(br_margin)
	{
		sWndClass[0] = 0;
	}

} LAYOUTINFO, *PLAYOUTINFO, *LPLAYOUTINFO;

//! @brief Layout manager implementation
/*!
 *  Derive from this class to implement resizable windows, adding the ability
 *  to dinamically resize and reposition child controls.
 *	Special care is taken to ensure a smooth animation during the resize
 *  operations performed by the users, without annoying flickering effects.
 */
class CResizableLayout
{
private:
	//@{
	//! @brief Collection of layout settings for each control
	CMap<HWND, HWND, POSITION, POSITION> m_mapLayout;
	CList<LAYOUTINFO, LAYOUTINFO&> m_listLayout;
	CList<LAYOUTINFO, LAYOUTINFO&> m_listLayoutCB;
	//@}

	std::map<HWND, std::tuple<ANCHOR, ANCHOR>> m_anchors;

	//@{
	//! @brief Used for clipping implementation
	HRGN m_hOldClipRgn = nullptr;
	int m_nOldClipRgn = 0;
	//@}

	//@{
	//! @brief Used for advanced anti-flickering
	RECT m_rectClientBefore{};
	BOOL m_bNoRecursion = FALSE;
	//@}

	//! @brief Apply clipping settings for the specified control
	void ClipChildWindow(const LAYOUTINFO &layout, CRgn* pRegion) const;

	//! @brief Helper function to calculate new layout
	void CalcNewChildPosition(const LAYOUTINFO &layout,
		const CRect &rectParent, CRect &rectChild, UINT& uFlags) const;

protected:

	BOOL IsInAnchorList(HWND hwnd)
	{
		POSITION pos;
		return m_mapLayout.Lookup(hwnd, pos);
	}
	//! @brief Override to initialize resize properties (clipping, refresh)
	virtual void InitResizeProperties(LAYOUTINFO& layout) const;

	//! @brief Override to specify clipping for unsupported windows
	virtual BOOL LikesClipping(const LAYOUTINFO &layout) const;

	//! @brief Override to specify refresh for unsupported windows
	virtual BOOL NeedsRefresh(const LAYOUTINFO &layout,
		const CRect &rectOld, const CRect &rectNew) const;

	//! @brief Clip controls in the layout out of the specified device context
	BOOL ClipChildren(CDC* pDC, BOOL bUndo);

	//! @brief Get the layout clipping region
	void GetClippingRegion(CRgn* pRegion) const;
	
	//! @brief Override for scrollable or expanding parent windows
	virtual void GetTotalClientRect(LPRECT lpRect) const;

	//@{
	//! @brief Add anchor points for the specified control to the layout
	void AddAnchor(HWND hWnd, ANCHOR anchorTopLeft, ANCHOR anchorBottomRight);

	void AddAnchor(HWND hWnd, ANCHOR anchorTopLeft)
	{
		AddAnchor(hWnd, anchorTopLeft, anchorTopLeft);
	}

	void AddAnchor(UINT nID, ANCHOR anchorTopLeft, ANCHOR anchorBottomRight)
	{
		AddAnchor(::GetDlgItem(GetResizableWnd()->GetSafeHwnd(), nID),
			anchorTopLeft, anchorBottomRight);
	}

	void AddAnchor(UINT nID, ANCHOR anchorTopLeft)
	{
		AddAnchor(::GetDlgItem(GetResizableWnd()->GetSafeHwnd(), nID),
			anchorTopLeft, anchorTopLeft);
	}
	//@}

	//! @brief Add a callback slot to the layout for dynamic controls or anchor points
	UINT_PTR AddAnchorCallback();

	//@{
	//! @brief Get position and size of a control in the layout from the parent's client area
	BOOL GetAnchorPosition(HWND hWnd, const CRect &rectParent,
		CRect &rectChild, UINT* lpFlags = NULL) const
	{
		POSITION pos;
		if (!m_mapLayout.Lookup(hWnd, pos))
			return FALSE;

		UINT uTmpFlags = 0;
		CalcNewChildPosition(m_listLayout.GetAt(pos), rectParent, rectChild,
			(lpFlags != NULL) ? (*lpFlags) : uTmpFlags);
		return TRUE;
	}

	BOOL GetAnchorPosition(UINT nID, const CRect &rectParent,
		CRect &rectChild, UINT* lpFlags = NULL) const
	{
		return GetAnchorPosition(::GetDlgItem(GetResizableWnd()->GetSafeHwnd(), nID),
			rectParent, rectChild, lpFlags);
	}
	//@}

	//@{
	//! @brief Get margins surrounding a control in the layout with the given size
	BOOL GetAnchorMargins(HWND hWnd, const CSize &sizeChild, CRect &rectMargins) const;

	BOOL GetAnchorMargins(UINT nID, const CSize &sizeChild, CRect &rectMargins) const
	{
		return GetAnchorMargins(::GetDlgItem(GetResizableWnd()->GetSafeHwnd(), nID),
			sizeChild, rectMargins);
	}
	//@}

	//@{
	//! @brief Remove a control from the layout
	BOOL RemoveAnchor(HWND hWnd)
	{
		POSITION pos;
		if (!m_mapLayout.Lookup(hWnd, pos))
			return FALSE;

		m_listLayout.RemoveAt(pos);
		return m_mapLayout.RemoveKey(hWnd);
	}

	BOOL RemoveAnchor(UINT nID)
	{
		return RemoveAnchor(::GetDlgItem(GetResizableWnd()->GetSafeHwnd(), nID));
	}
	//@}

	//! @brief Reset the layout content
	void RemoveAllAnchors()
	{
		m_mapLayout.RemoveAll();
		m_listLayout.RemoveAll();
		m_listLayoutCB.RemoveAll();
	}

	std::map<HWND, std::tuple<ANCHOR, ANCHOR>> GetAllAnchors() const { return m_anchors; }
	void AddAllAnchors(const std::map<HWND, std::tuple<ANCHOR, ANCHOR>>& anchors);

	//! @brief Reposition and size all the controls in the layout
	void ArrangeLayout() const;

	//! @brief Override to provide dynamic control's layout info
	virtual BOOL ArrangeLayoutCallback(LAYOUTINFO& layout) const;

	//! @brief Override to provide the parent window
	virtual CWnd* GetResizableWnd() const = 0;

	//! @brief Enhance anti-flickering
	void HandleNcCalcSize(BOOL bAfterDefault, LPNCCALCSIZE_PARAMS lpncsp, LRESULT& lResult);
	
	//! @brief Enable resizable style for top level parent windows
	void MakeResizable(LPCREATESTRUCT lpCreateStruct);

public:
	CResizableLayout()
	{
		m_hOldClipRgn = ::CreateRectRgn(0,0,0,0);
	}

	virtual ~CResizableLayout()
	{
		// just for safety
		RemoveAllAnchors();

		DeleteObject(m_hOldClipRgn); 
	}
};

// @}
#endif // !defined(AFX_RESIZABLELAYOUT_H__INCLUDED_)
