Scrolling

    Scrolling can have windows and elements. What I need for this? I am not sure, but what I found out:

    Element must have information about scrolling, 2D vector, because we can scroll in vertical and horizontal directions, and limit.

    
    //I moved it from slGUIElement to slGUICommon
    slVec2f m_scroll = 0.f;
    slVec2f m_scrollLimit = 0.f;
    

    If we don't have limit, we can scroll too much and elements will go out of window\element.

    For calculating limit we need to know the size of element (build rect), and the size of its children (not grandchildren)

    
    // Size of element + all children (not grandchildren). It like AABB. find maximum x and y.
    // Size of window with all elements.
    // Use it for scrolling
    slVec2f m_contentSize;
    

    I think, content size should be found for every GUI element type in unique way, but of course they have something common. I think I need to add new method in slGUICommon

    
    void slGUICommon::UpdateContentSize()
    {
    	m_contentSize.x = m_buildRect.z - m_buildRect.x;
    	m_contentSize.y = m_buildRect.w - m_buildRect.y;
    	UpdateScrollLimit();
    }
    void slGUICommon::UpdateScrollLimit()
    {
    	m_scrollLimit.x = 0.f;
    	m_scrollLimit.y = 0.f;
    	
    	slVec2f buildRectSize;
    	buildRectSize.x = m_baseRect.z - m_baseRect.x;
    	buildRectSize.y = m_baseRect.w - m_baseRect.y;
    
    	if (m_contentSize.x > buildRectSize.x)
    		m_scrollLimit.x = m_contentSize.x - buildRectSize.x;
    	if (m_contentSize.y > buildRectSize.y)
    		m_scrollLimit.y = m_contentSize.y - buildRectSize.y;
    }
    

    Root element must do this

    
    void slGUIRootElement::UpdateContentSize()
    {
    	slVec2f LB;
    
    	if (GetChildren()->m_head)
    	{
    		auto children = GetChildren();
    		if (children->m_head)
    		{
    			auto curr = children->m_head;
    			auto last = curr->m_left;
    			while (1)
    			{
    				slGUIElement* child = dynamic_cast<slGUIElement*>(curr->m_data);
    				
    				if (child->m_baseRect.z > LB.x) LB.x = child->m_baseRect.z;
    				if (child->m_baseRect.w > LB.y) LB.y = child->m_baseRect.w;
    
    				if (curr == last)
    					break;
    				curr = curr->m_right;
    			}
    		}
    	}
    
    	m_contentSize.x = LB.x - m_baseRect.x;
    	m_contentSize.y = LB.y - m_baseRect.y;
    
    	slGUICommon::UpdateScrollLimit();
    
    	//printf("SIZE: %f %f\n", m_contentSize.x, m_contentSize.y);
    	//printf("LIMIT: %f %f\n", m_scrollLimit.x, m_scrollLimit.y);
    }
    

    Call this method when rebuild window

    
    void _slGUIWindow_RebuildElement(slGUIElement* e)
    {
    	e->Rebuild();
    	if (e->GetChildren()->m_head){
    ...
    	}
    	e->UpdateContentSize(); // here
    }
    

    Looks like everyting is ok. Now, I will put button below window, and will try to use mouse wheel to scroll window

    I needed to add

    
    slVec4f m_baseRect;
    

    into slGUICommon, now slGUIElement::Rebuild method will be use this object. For scrolling I need to update m_buildRect every frame, and create it from m_baseRect

    I moved code for m_buildRect m_clipRect m_activeRect into Update

    And there is scrolling (vertical)

    
    void slGUIElement::Update(slInputData* id)
    {
    	slGUICommon::Update(id);
    
    	// maybe here callback
    	// OnRects(); or OnRebuild();
    
    	m_buildRect = m_baseRect;
    	// usuially m_clipRect is == m_baseRect;
    	m_clipRect = m_buildRect;
    
    	slGUIElement* parent = dynamic_cast<slGUIElement*>(GetParent());
    	if (parent)
    	{
    		m_buildRect.x -= parent->m_scroll.x;
    		m_buildRect.y -= parent->m_scroll.y;
    		m_buildRect.z -= parent->m_scroll.x;
    		m_buildRect.w -= parent->m_scroll.y;
    		
    		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;
    	
    	if (IsCursorInRect())
    	{
    		if (id->mouseWheelDelta < 0.f)
    		{
    			m_scrollTarget.y += 10.f;
    			if (m_scrollTarget.y > m_scrollLimit.y)
    				m_scrollTarget.y = m_scrollLimit.y;
    		}
    		else if (id->mouseWheelDelta > 0.f)
    		{
    			m_scrollTarget.y -= 10.f;
    
    			if (m_scrollTarget.y < 0.f)
    				m_scrollTarget.y = 0.f;
    		}
    	}
    	m_scroll.y = slMath::lerp1(m_scroll.y, m_scrollTarget.y, 0.1f);
    	
    	Rebuild();
    }
    

    I am not sure about how it will work, especially if in window add list box

    Another thing to do is to add scrollbar. I don't want to do it now, I want to finish this simple GUI.

    Checkbox, radio button

    It's almost like button. I need to add default icons.

    What is icons? I think, its same with font, image and UV coordinates. But, actually I can just use font...I need to create picture.

    Now add new default font

    
    slArray<slGUIFont*> m_defaultFonts; // now use this
    

    add

    
    enum class slGUIDefaultFont
    {
    	Text,
    	Icons
    };
    

    change method

    
    slGUIFont* slFramework::GetDefaultFont(const slGUIDefaultFont& t)
    {
    	SL_ASSERT_ST(g_framework->m_defaultFonts.m_size);
    	
    	switch (t)
    	{
    	case slGUIDefaultFont::Icons:
    	case slGUIDefaultFont::Text:
    		return g_framework->m_defaultFonts.m_data[(uint32_t)t];
    	default:
    		slLog::PrintWarning("%s : not implemented\n", SL_FUNCTION);
    		break;
    	}
    	return g_framework->m_defaultFonts.m_data[0];
    }
    

    Modify slFramework::InitDefaultFonts

    
    void slFramework::InitDefaultFonts(slGS* gs)
    {
    	static bool isInit = false;
    
    	if (!isInit)
    	{
    		auto getImage = [](uint8_t* buf, uint32_t sz)->slImage* {
    			for (uint32_t i = 0; i < slFramework::GetImageLoadersNum(); ++i)
    			{
    				auto il = slFramework::GetImageLoader(i);
    				for (uint32_t o = 0; o < il->GetSupportedFilesCount(); ++o)
    				{
    					auto str = il->GetSupportedFileExtension(o);
    					if (str == U"png")
    					{
    						return il->Load("something/file.png", buf, sz);
    					}
    				}
    			}
    			return 0;
    		};
    		
    		auto getTexture = [gs](slImage* img)->slTexture* {
    			slTextureInfo ti;
    			slTexture* t = gs->SummonTexture(img, ti);
    			SLSAFE_DESTROY(img);
    			return t;
    		};
    
    		slImage* img = getImage(g_defaultFontPNG, 9065);
    
    		if (!img)
    			return;		
    
    		slTexture* myFontTexture = getTexture(img);
    
    		if (!myFontTexture)
    			return;
    

    add somewhere this

    
    enum class slGUIDefaultIconID : uint32_t
    {
    	CheckboxUncheck = 1,
    	CheckboxCheck,
    	RadioUncheck,
    	RadioCheck,
    	Plus,
    	Minus,
    	ArrowUp,
    	ArrowDonw,
    	ArrowRight,
    	ArrowLeft,
    };
    

    Create new font and add glyphs

    
    img = getImage(g_defaultIconsPNG, 4825);
    if (img)
    {
    	myFontTexture = getTexture(img);
    	if (myFontTexture)
    	{
    		g_framework->m_texturesForDestroy.push_back(myFontTexture);
    
    		slGUIFont* myFont = slFramework::SummonFont();
    		myFont->AddTexture(myFontTexture);
    		myFont->AddGlyph((uint32_t)slGUIDefaultIconID::CheckboxUncheck, slVec2f(0, 0), slPoint(14, 14), 0, slPoint(512, 512));
    		myFont->AddGlyph((uint32_t)slGUIDefaultIconID::RadioUncheck, slVec2f(0, 0), slPoint(14, 14), 0, slPoint(512, 512));
    		myFont->AddGlyph((uint32_t)slGUIDefaultIconID::CheckboxCheck, slVec2f(28, 0), slPoint(14, 14), 0, slPoint(512, 512));
    		myFont->AddGlyph((uint32_t)slGUIDefaultIconID::RadioCheck, slVec2f(14, 0), slPoint(14, 14), 0, slPoint(512, 512));
    		myFont->AddGlyph((uint32_t)slGUIDefaultIconID::Minus, slVec2f(43, 0), slPoint(14, 14), 0, slPoint(512, 512));
    		myFont->AddGlyph((uint32_t)slGUIDefaultIconID::Plus, slVec2f(57, 0), slPoint(14, 14), 0, slPoint(512, 512));
    		myFont->AddGlyph((uint32_t)slGUIDefaultIconID::ArrowUp, slVec2f(71, 0), slPoint(14, 14), 0, slPoint(512, 512));
    		myFont->AddGlyph((uint32_t)slGUIDefaultIconID::ArrowDonw, slVec2f(85, 0), slPoint(14, 14), 0, slPoint(512, 512));
    		myFont->AddGlyph((uint32_t)slGUIDefaultIconID::ArrowRight, slVec2f(97, 0), slPoint(14, 14), 0, slPoint(512, 512));
    		myFont->AddGlyph((uint32_t)slGUIDefaultIconID::ArrowLeft, slVec2f(109, 0), slPoint(14, 14), 0, slPoint(512, 512));
    
    		g_framework->m_defaultFonts.push_back(myFont);
    	}
    }
    

    Now check box and radio button. So it's button.

    
    class slGUICheckRadioBox : public slGUIButton
    {
    	slGUICheckRadioBoxTextDrawCallback m_iconDrawCallback;
    public:
    	slGUICheckRadioBox(slGUIWindow*, const slVec2f& position, const slVec2f& size);
    	virtual ~slGUICheckRadioBox();
    	virtual void Rebuild() final;
    	//virtual void Update(slInputData*) final;
    	virtual void Draw(slGS* gs, float dt) final;	
    	float m_iconVerticalIndent = 0.f;
    
    
    	virtual void OnClickLMB() override;
    	bool m_isChecked = false;
    
    	virtual void OnCheck();
    	virtual void OnUnCheck();
    
    	bool m_asRadioButton = false;
    	uint32_t m_radiouGroup = 0;
    };
    ...
    slGUICheckRadioBox::slGUICheckRadioBox(slGUIWindow* w, const slVec2f& position, const slVec2f& size)
    	:
    	slGUIButton(w, position, size)
    {
    	m_iconVerticalIndent = 2.f;
    	m_iconDrawCallback.m_button = this;
    	SetDrawBG(false);
    }
    
    slGUICheckRadioBox::~slGUICheckRadioBox(){}
    
    void slGUICheckRadioBox::Rebuild()
    {
    	slGUIButton::Rebuild();
    }
    
    void slGUICheckRadioBox::Draw(slGS* gs, float dt)
    {
    	slGUIDrawTextCallback* prevcb = m_textDrawCallback;
    	m_textDrawCallback = dynamic_cast<slGUIDrawTextCallback*>(&m_iconDrawCallback);
    	slGUIButton::Draw(gs, dt);
    	m_textDrawCallback = prevcb;
    }
    

    Just draw as button.

    What I need? Draw icon. It must be inside m_clipRect. In future I will add text alignment for button.

    I need default text draw callback for icons

    
    class slGUICheckRadioBoxTextDrawCallback : public slGUIDrawTextCallback
    {
    	friend class slGUICheckRadioBox;
    	slGUICheckRadioBox* m_button = 0;
    	slGUIFont* m_defaultFont = 0;
    	slGUIFont* m_icons = 0;
    	slGUIFont* m_currFont = 0;
    public:
    	slGUICheckRadioBoxTextDrawCallback();
    	virtual ~slGUICheckRadioBoxTextDrawCallback() {}
    
    	virtual slGUIFont* OnFont(uint32_t r, char32_t) override;
    	virtual slColor* OnColor(uint32_t r, char32_t) override;
    };
    

    Implementation

    
    slGUICheckRadioBoxTextDrawCallback::slGUICheckRadioBoxTextDrawCallback()
    {
    	m_defaultFont = slFramework::GetDefaultFont(slGUIDefaultFont::Text);
    	m_icons = slFramework::GetDefaultFont(slGUIDefaultFont::Icons);
    	m_currFont = m_defaultFont;
    }
    
    slGUIFont* slGUICheckRadioBoxTextDrawCallback::OnFont(uint32_t r, char32_t c)
    {
    	return m_currFont;
    }
    
    slColor* slGUICheckRadioBoxTextDrawCallback::OnColor(uint32_t r, char32_t c)
    {
    	switch (r)
    	{
    	case slGUIDrawTextCallback::Reason_icon:
    		return &m_button->GetStyle()->m_colorWhite;
    	case slGUIDrawTextCallback::Reason_pressed:
    		return &m_button->GetStyle()->m_chkradioMousePressTextColor;
    	case slGUIDrawTextCallback::Reason_mouseAbove:
    		return &m_button->GetStyle()->m_chkradioMouseHoverTextColor;
    	case slGUIDrawTextCallback::Reason_disabled:
    		return &m_button->GetStyle()->m_chkradioDisabledTextColor;
    	case slGUIDrawTextCallback::Reason_default:
    		break;
    	}
    	return &m_button->GetStyle()->m_chkradioTextColor;
    }
    

    Add

    
    virtual void OnClickLMB() override;
    bool m_isChecked = false;
    
    virtual void OnCheck();
    virtual void OnUnCheck();
    

    Implementation

    
    void slGUICheckRadioBox::OnClickLMB()
    {
    	m_isChecked = m_isChecked ? false : true;
    	m_isChecked ? OnCheck() : OnUnCheck();
    }
    
    void slGUICheckRadioBox::OnCheck()
    {
    }
    
    void slGUICheckRadioBox::OnUnCheck()
    {
    }
    

    Checkbox is done

    Now radiobutton. Change icon

    
    if (m_asRadioButton)
    {
    	if (m_isChecked)
    		text[0] = (char32_t)slGUIDefaultIconID::RadioCheck;
    	else
    		text[0] = (char32_t)slGUIDefaultIconID::RadioUncheck;
    }
    else
    {
    	if (m_isChecked)
    		text[0] = (char32_t)slGUIDefaultIconID::CheckboxCheck;
    }
    

    Uncheck other radiobuttons

    
    void slGUICheckRadioBox::OnClickLMB()
    {
    	if (m_asRadioButton)
    	{
    		if (!m_isChecked)
    		{
    			m_isChecked = true;
    
    			// uncheck other
    			slGUIElement* parent = dynamic_cast<slGUIElement*>(GetParent());
    			if (parent)
    			{
    				auto & children = *parent->GetChildren();
    				for (auto _child : children)
    				{
    					auto child = dynamic_cast<slGUICheckRadioBox*>(_child);
    					if (child)
    					{
    						if (child != this && child->m_asRadioButton)
    						{
    							if (child->m_radiouGroup == m_radiouGroup)
    							{
    								child->m_isChecked = false;
    								child->OnUnCheck();
    							}
    						}
    					}
    				}
    			}
    		}
    	}
    	else
    	{
    		m_isChecked = m_isChecked ? false : true;
    		m_isChecked ? OnCheck() : OnUnCheck();
    	}
    }
    

    Demo

    
    class MyCheckbox: public slGUICheckRadioBox
    {
    public:
    	MyCheckbox(slGUIWindow* w, const slVec2f& position, const slVec2f& size)
    		:
    		slGUICheckRadioBox(w, position, size)
    	{}
    	virtual ~MyCheckbox() {}
    };
    
    class MyRadio : public slGUICheckRadioBox
    {
    public:
    	MyRadio(slGUIWindow* w, const slVec2f& position, const slVec2f& size)
    		:
    		slGUICheckRadioBox(w, position, size)
    	{
    		m_asRadioButton = true;
    	}
    	virtual ~MyRadio() {}
    };
    
    
    
    auto chck = slCreate<MyCheckbox>(guiWindow, slVec2f(110.f, 100.f), slVec2f(100.f, 20.f));
    chck->m_alignment = slGUIElement::Alignment::Right;
    chck->SetText(U"Checkbox");
    
    auto radio = slCreate<MyRadio>(guiWindow, slVec2f(110.f, 120.f), slVec2f(100.f, 20.f));
    radio->m_alignment = slGUIElement::Alignment::Right;
    radio->SetText(U"Radio (1)");
    radio = slCreate<MyRadio>(guiWindow, slVec2f(110.f, 140.f), slVec2f(100.f, 20.f));
    radio->m_alignment = slGUIElement::Alignment::Right;
    radio->SetText(U"Radio (1)");
    radio = slCreate<MyRadio>(guiWindow, slVec2f(110.f, 160.f), slVec2f(100.f, 20.f));
    radio->m_alignment = slGUIElement::Alignment::Right;
    radio->SetText(U"Radio (1)");
    
    radio = slCreate<MyRadio>(guiWindow, slVec2f(110.f, 180.f), slVec2f(100.f, 20.f));
    radio->m_alignment = slGUIElement::Alignment::Right;
    radio->SetText(U"Radio (2)");
    radio->m_radiouGroup = 1;
    radio = slCreate<MyRadio>(guiWindow, slVec2f(110.f, 200.f), slVec2f(100.f, 20.f));
    radio->m_alignment = slGUIElement::Alignment::Right;
    radio->SetText(U"Radio (2)");
    radio->m_radiouGroup = 1;
    radio = slCreate<MyRadio>(guiWindow, slVec2f(110.f, 220.f), slVec2f(100.f, 20.f));
    radio->m_alignment = slGUIElement::Alignment::Right;
    radio->SetText(U"Radio (2)");
    radio->m_radiouGroup = 1;
    

    Download