Something

    Using `margin` is interesting, but, what if I want to put elements outside of the window? How I need to do this? If I change window size, element still will be outside.

    In element I need to add size

    
    //In slGUIElement
    slVec2f m_size;
    

    And I need to change alignment. I think it will be better to use it as flags

    
    enum Alignment
    {
    	Left = 0x1,
    	Top = 0x2,
    	Right = 0x4,
    	Bottom = 0x8,
    	Center = Left | Top | Right | Bottom
    };
    Alignment m_alignment = Alignment::Center;
    

    If size is 0,0 then elements rectangle will be rebuilded using only margin. If there is size, then `margin` will play `position` role. And in this case alignment will be applied.

    Now, I think... I can create elements without problems.

    Button

    Copy root element and rename

    
    class slGUIButton : public slGUIElement
    {
    public:
    	slGUIButton();
    	virtual ~slGUIButton();
    	virtual void Rebuild() final;
    	virtual void Update(slInputData*) final;
    	virtual void Draw(slGS* gs, float dt) final;
    };
    

    I need to use slGUIDrawTextCallback, and I put it in slGUICommon

    
    slGUIDrawTextCallback* m_textDrawCallback = 0;
    ...
    virtual void SetDrawTextCallback(slGUIDrawTextCallback* cb) { m_textDrawCallback = cb; }
    

    Button can be with text. Add.

    
    //slGUIButton
    slString m_text;
    ...
    virtual void SetText(const slString&);
    

    Colors for button

    
    slColor m_buttonBGColor1;
    slColor m_buttonBGColor2;
    slColor m_buttonBorderColor;
    slColor m_buttonTextColor;
    slColor m_buttonDisabledBGColor1;
    slColor m_buttonDisabledBGColor2;
    slColor m_buttonDisabledBorderColor;
    slColor m_buttonDisabledTextColor;
    slColor m_buttonMouseHoverBGColor1;
    slColor m_buttonMouseHoverBGColor2;
    slColor m_buttonMouseHoverTextColor;
    slColor m_buttonMousePressBGColor1;
    slColor m_buttonMousePressBGColor2;
    slColor m_buttonMousePressTextColor;
    ...
    g_framework->m_GUIStyleThemeLight.m_buttonBGColor1 = 0x999999;
    g_framework->m_GUIStyleThemeLight.m_buttonBGColor2 = 0x666666;
    g_framework->m_GUIStyleThemeLight.m_buttonBorderColor = 0x999999;
    g_framework->m_GUIStyleThemeLight.m_buttonTextColor = 0x0;
    g_framework->m_GUIStyleThemeLight.m_buttonDisabledBGColor1 = 0x999999;
    g_framework->m_GUIStyleThemeLight.m_buttonDisabledBGColor2 = 0x666666;
    g_framework->m_GUIStyleThemeLight.m_buttonDisabledBorderColor = 0x999999;
    g_framework->m_GUIStyleThemeLight.m_buttonDisabledTextColor = 0x555555;
    g_framework->m_GUIStyleThemeLight.m_buttonMouseHoverBGColor1 = 0xAAAAAA;
    g_framework->m_GUIStyleThemeLight.m_buttonMouseHoverBGColor2 = 0x777777;
    g_framework->m_GUIStyleThemeLight.m_buttonMouseHoverTextColor = 0x222222;
    g_framework->m_GUIStyleThemeLight.m_buttonMousePressBGColor1 = 0x777777;
    g_framework->m_GUIStyleThemeLight.m_buttonMousePressBGColor2 = 0x444444;
    g_framework->m_GUIStyleThemeLight.m_buttonMousePressTextColor = 0x0;
    

    Create Button.

    
    //slFramework
    static slGUIButton* SummonGUIButton(slGUIWindow*);
    ...
    slGUIButton* slFramework::SummonGUIButton(slGUIWindow* w)
    {
    	SL_ASSERT_ST(w);
    	return slCreate<slGUIButton>(w);
    }
    

    Rebuild button. First, it will build rects using button parameters, second, it will take parent rects (and scroll?) and will update rects. Bebuild rectangles is common thing for all elements so implement Bebuild() in slGUIElement

    
    void slGUIElement::Rebuild()
    {
    	slGUIElement* parent = dynamic_cast<slGUIElement*>(GetParent());
    	if (parent)
    	{
    		// build buildRect using margin
    		m_buildRect.x = parent->m_buildRect.x + m_margin.x;
    		m_buildRect.y = parent->m_buildRect.y + m_margin.y;
    		
    		// if width or height != 0 then do not use m_margin
    		if (m_size.x != 0.f)
    			m_buildRect.z = m_buildRect.x + m_size.x;
    		else
    			m_buildRect.z = parent->m_buildRect.z - m_margin.z;
    
    		if (m_size.y != 0.f)
    			m_buildRect.w = m_buildRect.y + m_size.y;
    		else
    			m_buildRect.w = parent->m_buildRect.w - m_margin.w;
    
    
    		// scrolling here somewhere
    		
    		// usuially m_clipRect is == m_buildRect;
    		m_clipRect = m_buildRect;
    
    		// but parent has own clip rect
    		if (m_clipRect.x < parent->m_clipRect.x)
    			m_clipRect.x = parent->m_clipRect.x;
    		if (m_clipRect.y < parent->m_clipRect.y)
    			m_clipRect.y = parent->m_clipRect.y;
    		if (m_clipRect.z > parent->m_clipRect.z)
    			m_clipRect.z = parent->m_clipRect.z;
    		if (m_clipRect.w > parent->m_clipRect.w)
    			m_clipRect.w = parent->m_clipRect.w;
    
    		m_activeRect = m_clipRect;
    
    
    		// maybe here callback
    		// OnRects(); or OnRebuild();
    	}
    	// else
    	// only root element without parent
    	// rebuild root in slGUIWindow::Rebuild();
    }
    

    This code without scrolling and alignment.

    I need to rebuild root element before rebuild other elements. Rebuilding will be in GUI window, because root elements must have area == area of window.

    
    void slGUIWindow::Rebuild()
    {
    	// calculate rects
    	m_buildRect.x = m_position.x;
    	m_buildRect.y = m_position.y;
    	m_buildRect.z = m_buildRect.x + m_size.x;
    	m_buildRect.w = m_buildRect.y + m_size.y;
    	
    	m_clipRect = m_buildRect;
    	m_activeRect = m_clipRect;
    
    	// rebuild root here
    	m_rootElement->m_buildRect.x = m_buildRect.x;
    	m_rootElement->m_buildRect.y = m_buildRect.y;
    	m_rootElement->m_buildRect.z = m_buildRect.x + m_size.x;
    	m_rootElement->m_buildRect.w = m_buildRect.y + m_size.y;
    	m_rootElement->m_clipRect = m_rootElement->m_buildRect;
    	m_rootElement->m_activeRect = m_rootElement->m_clipRect;
    
    	// then rebuild other elements
    	_slGUIWindow_RebuildElement(m_rootElement);
    }
    

    Rebuild button. Later here will be code for moving text in the center.

    
    void slGUIButton::Rebuild()
    {
    	slGUIElement::Rebuild();
    }
    

    Button creating

    
    slGUIButton* slFramework::SummonGUIButton(slGUIWindow* w)
    {
    	SL_ASSERT_ST(w);
    	slGUIButton* b = slCreate<slGUIButton>(w);
    	b->SetStyle(slFramework::GetGUIStyle(slGUIStyleTheme::Light));
    	return b;
    }
    

    Add button in demo app

    
    auto btn = slFramework::SummonGUIButton(guiWindow);
    btn->m_margin.set(10.f);
    

    And in window callback OnSize resize gui window and call rebuild

    
    virtual void OnSize(slWindow* w) override
    {
    	App* app = (App*)w->GetUserData();
    	if (app)
    	{
    ...
    		app->m_GUIWindow->SetPositionAndSize(slVec2f(100.f), slVec2f(w->GetCurrentSize()->x - 500.f, w->GetCurrentSize()->y - 300.f));
    		app->m_GUIWindow->Rebuild();
    	}
    }
    

    How it looks:

    Now it's time to make all that mouse interactions. All basic things like `mous enter rect` or 'mouse leave' is common thing for element and for window, so implement slGUICommon::Update

    
    void slGUICommon::Update(slInputData* id)
    {
    	if (slMath::pointInRect(id->mousePosition, m_activeRect))
    	{
    		if ((m_flags & slGUICommon::flag_cursorInRect) == 0)
    			OnMouseEnter();
    
    		m_flags |= slGUICommon::flag_cursorInRect;
    	}
    	else
    	{
    		if(IsCursorInRect())
    			OnMouseLeave();
    
    		m_flags &= ~slGUICommon::flag_cursorInRect;
    	}
    
    	if (IsEnabled() && IsCursorInRect())
    	{
    		if (slInput::IsLMBHit())
    		{
    			m_flags |= slGUICommon::flag_clickedLMB;
    			OnClickLMB();
    		}
    		else if (slInput::IsLMBRelease())
    		{
    			if (IsClickedLMB())
    				OnReleaseLMB();
    		}
    
    		if (slInput::IsRMBHit())
    		{
    			m_flags |= slGUICommon::flag_clickedRMB;
    			OnClickRMB();
    		}
    		else if (slInput::IsRMBRelease())
    		{
    			if (IsClickedRMB())
    				OnReleaseRMB();
    		}
    
    		if (slInput::IsMMBHit())
    		{
    			m_flags |= slGUICommon::flag_clickedMMB;
    			OnClickMMB();
    		}
    		else if (slInput::IsMMBRelease())
    		{
    			if (IsClickedMMB())
    				OnReleaseMMB();
    		}
    
    		if (slInput::IsX1MBHit())
    		{
    			m_flags |= slGUICommon::flag_clickedX1MB;
    			OnClickX1MB();
    		}
    		else if (slInput::IsX1MBRelease())
    		{
    			if (IsClickedX1MB())
    				OnReleaseX1MB();
    		}
    
    		if (slInput::IsX2MBHit())
    		{
    			m_flags |= slGUICommon::flag_clickedX2MB;
    			OnClickX2MB();
    		}
    		else if (slInput::IsX2MBRelease())
    		{
    			if (IsClickedX2MB())
    				OnReleaseX2MB();
    		}
    	}
    
    	// I need to remove flags when cursor outside of rect
    	if (slInput::IsLMBRelease())
    		m_flags &= ~slGUICommon::flag_clickedLMB;
    	if (slInput::IsRMBRelease())
    		m_flags &= ~slGUICommon::flag_clickedRMB;
    	if (slInput::IsMMBRelease())
    		m_flags &= ~slGUICommon::flag_clickedMMB;
    	if (slInput::IsX1MBRelease())
    		m_flags &= ~slGUICommon::flag_clickedX1MB;
    	if (slInput::IsX2MBRelease())
    		m_flags &= ~slGUICommon::flag_clickedX2MB;
    }
    

    And I moved all that OnMouseEnter OnClickLMB... methods in slGUICommon

    I need to save all that states when mouse entered the area, or area was clicked by mouse, I added new flags for this, and methods

    
    //in slGUICommon
    // for internal use, can read but not set
    		flag_clickedX2MB = 0x4000000,
    		flag_clickedX1MB = 0x8000000,
    		flag_clickedMMB = 0x10000000,
    		flag_clickedRMB = 0x20000000,
    		flag_clickedLMB = 0x40000000,
    		flag_cursorInRect = 0x80000000,
    	};
    ...
    virtual bool IsCursorInRect() { return (m_flags & flag_cursorInRect); }
    virtual bool IsClickedLMB() { return (m_flags & flag_clickedLMB); }
    virtual bool IsClickedRMB() { return (m_flags & flag_clickedRMB); }
    virtual bool IsClickedMMB() { return (m_flags & flag_clickedMMB); }
    virtual bool IsClickedX1MB() { return (m_flags & flag_clickedX1MB); }
    virtual bool IsClickedX2MB() { return (m_flags & flag_clickedX2MB); }
    

    Update button drawing. Now it will react when mouse will be in active area.

    
    void slGUIButton::Draw(slGS* gs, float dt)
    {
    	gs->SetScissorRect(m_clipRect, 0);
    	if (IsDrawBG())
    	{
    		if (IsEnabled())
    		{
    			if (IsClickedLMB())
    			{
    				gs->DrawGUIRectangle(m_buildRect, m_style->m_buttonMousePressBGColor1, m_style->m_buttonMousePressBGColor2, 0, 0);
    			}
    			else
    			{
    				if(IsCursorInRect())
    					gs->DrawGUIRectangle(m_buildRect, m_style->m_buttonMouseHoverBGColor1, m_style->m_buttonMouseHoverBGColor2, 0, 0);
    				else
    					gs->DrawGUIRectangle(m_buildRect, m_style->m_buttonBGColor1, m_style->m_buttonBGColor2, 0, 0);
    			}
    		}
    		else
    			gs->DrawGUIRectangle(m_buildRect, m_style->m_buttonDisabledBGColor1, m_style->m_buttonDisabledBGColor2, 0, 0);
    	}
    }
    

    I forgot something. I want to create GUI elements by creating derived classes. I need to think how I can do this. Probably, constructor of slGUIElement must ask slGUWindow for argument

    
    slGUIElement::slGUIElement(slGUIWindow* w) 
    {
    	SL_ASSERT_ST(w);
    	m_window = w;
    	SetStyle(slFramework::GetGUIStyle(slGUIStyleTheme::Light));
    }
    

    Remove SummonGUIButton from slFramework, and try to create button class in demo app

    
    class MyButton : public slGUIButton
    {
    public:
    	MyButton(slGUIWindow* w)
    		:
    		slGUIButton(w)
    	{}
    	virtual ~MyButton() {}
    
    	virtual void OnMouseEnter() final
    	{
    		slLog::PrintInfo("%s\n", SL_FUNCTION);
    	}
    
    	virtual void OnMouseLeave() final
    	{
    		slLog::PrintInfo("%s\n", SL_FUNCTION);
    	}
    
    	virtual void OnClickLMB() final
    	{
    		slLog::PrintInfo("%s\n", SL_FUNCTION);
    	}
    
    	virtual void OnClickRMB() final
    	{
    		slLog::PrintInfo("%s\n", SL_FUNCTION);
    	}
    
    	virtual void OnClickMMB() final
    	{
    		slLog::PrintInfo("%s\n", SL_FUNCTION);
    	}
    };
    

    Create button

    
    auto btn = slCreate<MyButton>(guiWindow);
    btn->m_margin.set(10.f);
    

    Working very well

    Text and alignment will be in other page

    Download