GUI elements

    All elements has common thing, like area for clicking. I need to create base class for them. There must me method to set their parents.

    I need to put elements somewhere. Best solution is to create GUI window. Window is a container. In future it will be easy to create for example tab control. Window will have invisible `root` element, all elements in window will be his children and grand children.

    Base class for all GUI elements

    
    class slGUIElement : public slUserData, public slHierarchy, public slGUICommon
    {
    protected:
    	slGUIWindow* m_window = 0;
    public:
    	slGUIElement();
    	virtual ~slGUIElement();
    
    	slGUIWindow* GetWindow() { return m_window; }
    
    	slVec4f m_margin;
    
    	slVec2f m_scroll = 0.f;
    	slVec2f m_scrollLimit = 0.f;
    
    	enum Alignment
    	{
    		LeftTop,
    		Top,
    		RightTop,
    		Right,
    		RightBottom,
    		Bottom,
    		LeftBottom,
    		Left,
    		Center
    	};
    	Alignment m_alignment = Alignment::LeftTop;
    
    	virtual void OnMouseEnter();
    	virtual void OnMouseLeave();
    	virtual void OnClickLMB();
    	virtual void OnClickRMB();
    	virtual void OnClickMMB();
    	virtual void OnClickX1MB();
    	virtual void OnClickX2MB();
    	virtual void OnReleaseLMB();
    	virtual void OnReleaseRMB();
    	virtual void OnReleaseMMB();
    	virtual void OnReleaseX1MB();
    	virtual void OnReleaseX2MB();
    
    	// Make this element last for drawing, first for input.
    	// It will be like on top of all other elements.
    	// It's just changing the order.
    	virtual void ToTop();
    };
    

    I added new class for making hierarchy

    
    class slHierarchy
    {
    	slHierarchy* m_parent = 0;
    	slList<slHierarchy*> m_children;
    public:
    	slHierarchy() {}
    	virtual ~slHierarchy() {}
    
    	void SetParent(slHierarchy* o)
    	{
    		if (m_parent)
    			m_parent->m_children.erase_first(this);
    
    		m_parent = o;
    
    		if (o)
    			m_parent->m_children.push_back(this);
    	}
    
    	virtual slHierarchy* GetParent() { return m_parent; }
    	virtual slList<slHierarchy*>* GetChildren() { return &m_children; }
    };
    

    It easy to create this class with one method SetParent. If add something like AddChild it will add chaos in the code. Better to call

    
    object->SetParent(o);
    

    Return to slGUIElement

    I want to use C# WPF thing like margin (I saw it there). When margin is (0,0,0,0) then all parent area will be filled by this element. When window will be resized, it will be easy to recalculate all rectangles. I not used this before. I think, and I hope, result will be very good, everything will be fast, not much chaos in the code, easy to modify.

    Style. It will be like this.

    
    struct slGUIStyle
    {
    	slColor m_windowBGColor1;
    	slColor m_windowBGColor2;
    	slColor m_windowBorderColor;
    	slColor m_windowTitleBGColor1;
    	slColor m_windowTitleBGColor2;
    	slColor m_windowTitleTextColor;
    	...
    };
    

    Base class for GUI elements and for window

    
    class slGUICommon
    {
    public:
    	enum
    	{
    		flag_visible = 0x1,
    		flag_enabled = 0x2,
    		flag_drawBG = 0x4,
    	};
    
    protected:
    	slGUIStyle* m_style = 0;
    
    	uint32_t m_flags = flag_visible | flag_enabled | flag_drawBG;
    public:
    	slGUICommon() {}
    	virtual ~slGUICommon() {}
    
    	virtual void SetStyle(slGUIStyle* s)
    	{
    		s ? m_style = s : m_style = slFramework::GetGUIStyle(slGUIStyleTheme::Light);
    	}
    
    	virtual slGUIStyle* GetStyle() 
    	{
    		return m_style; 
    	}
    
    	// Set visibility. It will set to children too.
    	virtual void SetVisible(bool v) {
    		if (v)
    			m_flags |= slGUICommon::flag_visible;
    		else
    			m_flags &= ~slGUICommon::flag_visible;
    	}
    	virtual bool IsVisible() { return (m_flags & flag_visible); }
    
    	virtual void SetEnabled(bool v) {
    		if (v)
    			m_flags |= slGUICommon::flag_enabled;
    		else
    			m_flags &= ~slGUICommon::flag_enabled;
    	}
    	virtual bool IsEnabled() { return (m_flags & flag_enabled); }
    
    	virtual void SetDrawBG(bool v) {
    		if (v)
    			m_flags |= slGUICommon::flag_drawBG;
    		else
    			m_flags &= ~slGUICommon::flag_drawBG;
    	}
    	virtual bool IsDrawBG() { return (m_flags & flag_drawBG); }
    	
    	// Size of element + all children. It like AABB. find maximum x and y.
    	// Size of window with all elements.
    	// Use it for scrolling
    	slVec2f m_contentSize;
    
    	uint32_t m_id = 0;
    
    	// calculate rects.
    	// Better to call this after GUI creation and after changing widow size
    	virtual void Rebuild() = 0;
    	
    	// all mouse things, click things
    	virtual void Update(slInputData*) = 0;
    	
    	// draw it
    	virtual void Draw(slGS* gs, float dt) = 0;
    
    	slVec4f m_buildRect;  // build element using this
    	slVec4f m_clipRect;   // clip using this
    	slVec4f m_activeRect; // mouse sensor area
    };
    

    GUI window

    I will not create movable window, right now, and it will be without titlebar. I just need container for elements.

    
    class slGUIWindow : public slUserData, public slGUICommon
    {
    public:
    	enum
    	{
    		windowFlag_withCloseButton = 0x1,
    		windowFlag_withCollapseButton = 0x2,
    		windowFlag_withTitleBar = 0x4,
    		windowFlag_canMove = 0x8,
    		windowFlag_canResize = 0x10,
    		windowFlag_canDock = 0x20,
    		windowFlag_canToTop = 0x40,
    	};
    
    private:
    	slGUIElement* m_rootElement = 0;
    
    	slVec2f m_position;
    	slVec2f m_size;
    	slVec2f m_sizeMinimum = slVec2f(100.f, 30.f);
    
    	slString m_title;
    	uint32_t m_windowFlags = 0;
    public:
    	slGUIWindow();
    	virtual ~slGUIWindow();
    
    	// need Rebuild
    	void SetPositionAndSize(const slVec2f& p, const slVec2f& sz);
    	
    	void SetSizeMinimum(const slVec2f& sz) { m_sizeMinimum = sz; }
    
    	slGUIElement* GetRootElement() { return m_rootElement; }
    
    	virtual void Rebuild() override;
    	virtual void Update(slInputData*) override;
    	virtual void Draw(slGS* gs, float dt) override;
    	
    	void SetTitle(const char32_t*);
    };
    

    Creating. Save new window into framework.

    
    slGUIWindow* slFramework::SummonGUIWindow()
    {
    	slGUIWindow* newWindow = slCreate<slGUIWindow>();
    	g_framework->m_GUIWindows.push_back(newWindow);
    	return newWindow;
    }
    

    Destroying

    
    void slFramework::DestroyGUIWindow(slGUIWindow* w)
    {
    	SL_ASSERT_ST(w);
    	_DestroyGUIElement(w->GetRootElement());
    	g_framework->m_GUIWindows.erase_first(w);
    	slDestroy(w);
    }
    

    Destroying element

    
    void DestroyGUIElement_internal(slGUIElement* e)
    {
    	if (e->GetChildren()->m_head)
    	{
    		auto children = e->GetChildren();
    		if (children->m_head)
    		{
    			auto curr = children->m_head;
    			auto last = curr->m_left;
    			while (1)
    			{
    				DestroyGUIElement_internal(dynamic_cast<slGUIElement*>(curr->m_data));
    				if (curr == last)
    					break;
    				curr = curr->m_right;
    			}
    		}
    	}
    
    	slDestroy(e);
    }
    
    void _DestroyGUIElement(slGUIElement* e)
    {
    	e->SetParent(0);
    	DestroyGUIElement_internal(e);
    }
    void slFramework::DestroyGUIElement(slGUIElement* e)
    {
    	SL_ASSERT_ST(e);
    	if (e->GetWindow()->GetRootElement() == e)
    		return;
    	_DestroyGUIElement(e);
    }
    

    I need to know some things, like window under cursor, element under cursor and other things like this.

    
    struct slGUIState
    {
    	slGUIWindow* m_windowUnderCursor = 0;
    	slGUIWindow* m_activeWindow = 0;
    };
    ...
    // in slFramework
    slGUIState* slFramework::GetGUIState()
    {
    	return &g_framework->m_GUIState;
    }
    

    Updating GUI (detection all that mouse moves and other things) will be in frameworks Update. Need to update window by window. Update from last window to first, because drawing will be from first to last, and last window is top window. We want to click that window who on top.

    
    void slFrameworkImpl::UpdateGUI()
    {
    	if (m_GUIWindows.m_head)
    	{
    		// reset it here, it will set in Update if cursor in window rect
    		m_GUIState.m_windowUnderCursor = 0;
    
    		auto last = m_GUIWindows.m_head;
    		auto curr = last->m_left;
    		while (1)
    		{
    			if(curr->m_data->IsVisible() && !m_GUIState.m_windowUnderCursor)
    			{
    				curr->m_data->Update(&m_input);
    			}
    
    			if (curr == last)
    				break;
    			curr = curr->m_left;
    		}
    	}
    }
    

    Update window

    
    void _slGUIWindow_UpdateElement(slInputData* in, slGUIElement* e)
    {
    	e->Update(in);
    
    	if (e->GetChildren()->m_head)
    	{
    		auto children = e->GetChildren();
    		if (children->m_head)
    		{
    			auto curr = children->m_head;
    			auto last = curr->m_left;
    			while (1)
    			{
    				_slGUIWindow_UpdateElement(in, dynamic_cast<slGUIElement*>(curr->m_data));
    				if (curr == last)
    					break;
    				curr = curr->m_right;
    			}
    		}
    	}
    }
    
    void slGUIWindow::Update(slInputData* input)
    {
    	if (slMath::pointInRect(input->mousePosition, m_activeRect))
    	{
    		g_framework->m_GUIState.m_windowUnderCursor = this;
    
    		_slGUIWindow_UpdateElement(input, m_rootElement);
    	}
    }
    

    Root element is just almost empty class

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

    GUI window create root element

    
    slGUIWindow::slGUIWindow()
    {
    	m_rootElement = dynamic_cast<slGUIElement*>(slCreate<slGUIRootElement>());
    }
    

    GUI draw window and elements

    
    void _slGUIWindow_DrawElement(slGS* gs, slGUIElement* e, float dt)
    {
    	e->Draw(gs, dt);
    
    	if (e->GetChildren()->m_head)
    	{
    		auto children = e->GetChildren();
    		if (children->m_head)
    		{
    			auto curr = children->m_head;
    			auto last = curr->m_left;
    			while (1)
    			{
    				_slGUIWindow_DrawElement(gs, dynamic_cast<slGUIElement*>(curr->m_data), dt);
    				if (curr == last)
    					break;
    				curr = curr->m_right;
    			}
    		}
    	}
    }
    
    void slGUIWindow::Draw(slGS* gs, float dt)
    {
    	gs->DrawGUIRectangle(m_buildRect, ColorWhite, ColorLime, 0, 0);
    
    	_slGUIWindow_DrawElement(gs, m_rootElement, dt);
    }
    

    In window draw its background (DrawGUIRectangle)

    Rebuilding works in same way.

    In framework add new method

    
    void slFramework::DrawGUI(slGS* gs)
    {
    	if (g_framework->m_GUIWindows.m_head)
    	{
    		auto last = g_framework->m_GUIWindows.m_head;
    		auto curr = last->m_left;
    		while (1)
    		{
    			if (curr->m_data->IsVisible())
    			{
    				curr->m_data->Draw(gs, g_framework->m_deltaTime);
    			}
    
    			if (curr == last)
    				break;
    			curr = curr->m_left;
    		}
    	}
    }
    

    Try to draw

    
    auto guiWindow = slFramework::SummonGUIWindow();
    guiWindow->SetPositionAndSize(slVec2f(100.f, 100.f), slVec2f(300.f));
    slFramework::RebuildGUI();
    ...
    // in main loop
    slFramework::DrawGUI(app.m_gs);
    

    Thats all. Window need a lot of things, I not wrote many of them. I will finish it (maybe will add moving) and will start to adding buttons.

    Download