فهرست منبع

Refactor combo box

nordsoft 2 سال پیش
والد
کامیت
c064b805c2

+ 28 - 0
client/gui/InterfaceObjectConfigurable.cpp

@@ -20,6 +20,7 @@
 #include "../render/Graphics.h"
 #include "../render/IFont.h"
 #include "../widgets/CComponent.h"
+#include "../widgets/ComboBox.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/MiscWidgets.h"
 #include "../widgets/ObjectLists.h"
@@ -52,6 +53,7 @@ InterfaceObjectConfigurable::InterfaceObjectConfigurable(int used, Point offset)
 	REGISTER_BUILDER("labelGroup", &InterfaceObjectConfigurable::buildLabelGroup);
 	REGISTER_BUILDER("slider", &InterfaceObjectConfigurable::buildSlider);
 	REGISTER_BUILDER("layout", &InterfaceObjectConfigurable::buildLayout);
+	REGISTER_BUILDER("comboBox", &InterfaceObjectConfigurable::buildComboBox);
 }
 
 void InterfaceObjectConfigurable::registerBuilder(const std::string & type, BuilderFunction f)
@@ -513,6 +515,32 @@ std::shared_ptr<CFilledTexture> InterfaceObjectConfigurable::buildTexture(const
 	return std::make_shared<CFilledTexture>(image, rect);
 }
 
+std::shared_ptr<ComboBox> InterfaceObjectConfigurable::buildComboBox(const JsonNode & config)
+{
+	logGlobal->debug("Building widget ComboBox");
+	auto position = readPosition(config["position"]);
+	auto image = config["image"].String();
+	auto help = readHintText(config["help"]);
+	auto result = std::make_shared<ComboBox>(position, image, help, config["dropDown"]);
+	if(!config["items"].isNull())
+	{
+		for(const auto & item : config["items"].Vector())
+		{
+			result->addOverlay(buildWidget(item));
+		}
+	}
+	if(!config["imageOrder"].isNull())
+	{
+		auto imgOrder = config["imageOrder"].Vector();
+		assert(imgOrder.size() >= 4);
+		result->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
+	}
+
+	loadButtonBorderColor(result, config["borderColor"]);
+	loadButtonHotkey(result, config["hotkey"]);
+	return result;
+}
+
 /// Small helper class that provides ownership for shared_ptr's of child elements
 class InterfaceLayoutWidget : public CIntObject
 {

+ 2 - 0
client/gui/InterfaceObjectConfigurable.h

@@ -27,6 +27,7 @@ class CSlider;
 class CAnimImage;
 class CShowableAnim;
 class CFilledTexture;
+class ComboBox;
 
 #define REGISTER_BUILDER(type, method) registerBuilder(type, std::bind(method, this, std::placeholders::_1))
 
@@ -99,6 +100,7 @@ protected:
 	std::shared_ptr<CShowableAnim> buildAnimation(const JsonNode &) const;
 	std::shared_ptr<CFilledTexture> buildTexture(const JsonNode &) const;
 	std::shared_ptr<CIntObject> buildLayout(const JsonNode &);
+	std::shared_ptr<ComboBox> buildComboBox(const JsonNode &);
 		
 	//composite widgets
 	std::shared_ptr<CIntObject> buildWidget(JsonNode config) const;

+ 30 - 162
client/lobby/RandomMapTab.cpp

@@ -18,6 +18,7 @@
 #include "../gui/MouseButton.h"
 #include "../gui/WindowHandler.h"
 #include "../widgets/CComponent.h"
+#include "../widgets/ComboBox.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/MiscWidgets.h"
 #include "../widgets/ObjectLists.h"
@@ -102,11 +103,6 @@ RandomMapTab::RandomMapTab():
 	});
 	
 	//new callbacks available only from mod
-	addCallback("templateSelection", [&](int)
-	{
-		GH.windows().createAndPushWindow<TemplatesDropBox>(*this, int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()});
-	});
-	
 	addCallback("teamAlignments", [&](int)
 	{
 		GH.windows().createAndPushWindow<TeamAlignmentsWidget>(*this);
@@ -125,6 +121,35 @@ RandomMapTab::RandomMapTab():
 	const JsonNode config(ResourceID("config/widgets/randomMapTab.json"));
 	build(config);
 	
+	//set combo box callbacks
+	if(auto w = widget<ComboBox>("templateList"))
+	{
+		w->onConstructItems = [](std::vector<const void *> & curItems){
+			auto templates = VLC->tplh->getTemplates();
+		
+			boost::range::sort(templates, [](const CRmgTemplate * a, const CRmgTemplate * b){
+				return a->getName() < b->getName();
+			});
+
+			curItems.push_back(nullptr); //default template
+			
+			for(auto & t : templates)
+				curItems.push_back(t);
+		};
+		
+		w->onSetItem = [&](const void * item){
+			this->setTemplate(reinterpret_cast<const CRmgTemplate *>(item));
+		};
+		
+		w->getItemText = [this](int idx, const void * item){
+			if(item)
+				return reinterpret_cast<const CRmgTemplate *>(item)->getName();
+			if(idx == 0)
+				return readText(variables["randomTemplate"]);
+			return std::string("");
+		};
+	}
+	
 	updateMapInfoByHost();
 }
 
@@ -360,163 +385,6 @@ std::vector<int> RandomMapTab::getPossibleMapSizes()
 	return {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE, CMapHeader::MAP_SIZE_HUGE, CMapHeader::MAP_SIZE_XHUGE, CMapHeader::MAP_SIZE_GIANT};
 }
 
-TemplatesDropBox::ListItem::ListItem(const JsonNode & config, TemplatesDropBox & _dropBox, Point position)
-	: InterfaceObjectConfigurable(LCLICK | HOVER, position),
-	dropBox(_dropBox)
-{
-	OBJ_CONSTRUCTION;
-	
-	build(config);
-	
-	if(auto w = widget<CPicture>("hoverImage"))
-	{
-		pos.w = w->pos.w;
-		pos.h = w->pos.h;
-	}
-	setRedrawParent(true);
-}
-
-void TemplatesDropBox::ListItem::updateItem(int idx, const CRmgTemplate * _item)
-{
-	if(auto w = widget<CLabel>("labelName"))
-	{
-		item = _item;
-		if(item)
-		{
-			w->setText(item->getName());
-		}
-		else
-		{
-			if(idx)
-				w->setText("");
-			else
-				w->setText(readText(dropBox.variables["randomTemplate"]));
-		}
-	}
-}
-
-void TemplatesDropBox::ListItem::hover(bool on)
-{
-	auto h = widget<CPicture>("hoverImage");
-	auto w = widget<CLabel>("labelName");
-	if(h && w)
-	{
-		if(w->getText().empty())
-			h->visible = false;
-		else
-			h->visible = on;
-	}
-	redraw();
-}
-
-void TemplatesDropBox::ListItem::clickPressed(const Point & cursorPosition)
-{
-	if(isHovered())
-		dropBox.setTemplate(item);
-}
-
-void TemplatesDropBox::ListItem::clickReleased(const Point & cursorPosition)
-{
-	dropBox.clickPressed(cursorPosition);
-	dropBox.clickReleased(cursorPosition);
-}
-
-TemplatesDropBox::TemplatesDropBox(RandomMapTab & randomMapTab, int3 size):
-	InterfaceObjectConfigurable(LCLICK | HOVER),
-	randomMapTab(randomMapTab)
-{
-	REGISTER_BUILDER("templateListItem", &TemplatesDropBox::buildListItem);
-	
-	curItems = VLC->tplh->getTemplates();
-
-	boost::range::sort(curItems, [](const CRmgTemplate * a, const CRmgTemplate * b){
-		return a->getName() < b->getName();
-	});
-
-	curItems.insert(curItems.begin(), nullptr); //default template
-	
-	const JsonNode config(ResourceID("config/widgets/randomMapTemplateWidget.json"));
-	
-	addCallback("sliderMove", std::bind(&TemplatesDropBox::sliderMove, this, std::placeholders::_1));
-	
-	OBJ_CONSTRUCTION;
-	pos = randomMapTab.pos;
-	
-	build(config);
-	
-	if(auto w = widget<CSlider>("slider"))
-	{
-		w->setAmount(curItems.size());
-	}
-
-	//FIXME: this should be done by InterfaceObjectConfigurable, but might have side-effects
-	pos = children.front()->pos;
-	for (auto const & child : children)
-		pos = pos.include(child->pos);
-	
-	updateListItems();
-}
-
-std::shared_ptr<CIntObject> TemplatesDropBox::buildListItem(const JsonNode & config)
-{
-	auto position = readPosition(config["position"]);
-	listItems.push_back(std::make_shared<ListItem>(config, *this, position));
-	return listItems.back();
-}
-
-void TemplatesDropBox::sliderMove(int slidPos)
-{
-	auto w = widget<CSlider>("slider");
-	if(!w)
-		return; // ignore spurious call when slider is being created
-	updateListItems();
-	redraw();
-}
-
-bool TemplatesDropBox::receiveEvent(const Point & position, int eventType) const
-{
-	if (eventType == LCLICK)
-		return true; // we want drop box to close when clicking outside drop box borders
-
-	return CIntObject::receiveEvent(position, eventType);
-}
-
-void TemplatesDropBox::clickPressed(const Point & cursorPosition)
-{
-	if (!pos.isInside(cursorPosition))
-	{
-		assert(GH.windows().isTopWindow(this));
-		GH.windows().popWindows(1);
-	}
-}
-
-void TemplatesDropBox::updateListItems()
-{
-	if(auto w = widget<CSlider>("slider"))
-	{
-		int elemIdx = w->getValue();
-		for(auto item : listItems)
-		{
-			if(elemIdx < curItems.size())
-			{
-				item->updateItem(elemIdx, curItems[elemIdx]);
-				elemIdx++;
-			}
-			else
-			{
-				item->updateItem(elemIdx);
-			}
-		}
-	}
-}
-
-void TemplatesDropBox::setTemplate(const CRmgTemplate * tmpl)
-{
-	randomMapTab.setTemplate(tmpl);
-	assert(GH.windows().isTopWindow(this));
-	GH.windows().popWindows(1);
-}
-
 TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
 	InterfaceObjectConfigurable()
 {

+ 0 - 37
client/lobby/RandomMapTab.h

@@ -51,43 +51,6 @@ private:
 	std::set<int> playerCountAllowed, playerTeamsAllowed, compCountAllowed, compTeamsAllowed;
 };
 
-class TemplatesDropBox : public InterfaceObjectConfigurable
-{
-	struct ListItem : public InterfaceObjectConfigurable
-	{
-		TemplatesDropBox & dropBox;
-		const CRmgTemplate * item = nullptr;
-		
-		ListItem(const JsonNode &, TemplatesDropBox &, Point position);
-		void updateItem(int index, const CRmgTemplate * item = nullptr);
-		
-		void hover(bool on) override;
-		void clickPressed(const Point & cursorPosition) override;
-		void clickReleased(const Point & cursorPosition) override;
-	};
-	
-	friend struct ListItem;
-	
-public:
-	TemplatesDropBox(RandomMapTab & randomMapTab, int3 size);
-	
-	bool receiveEvent(const Point & position, int eventType) const override;
-	void clickPressed(const Point & cursorPosition) override;
-	void setTemplate(const CRmgTemplate *);
-	
-private:
-	std::shared_ptr<CIntObject> buildListItem(const JsonNode & config);
-	
-	void sliderMove(int slidPos);
-	void updateListItems();
-	
-	RandomMapTab & randomMapTab;
-	std::vector<std::shared_ptr<ListItem>> listItems;
-	
-	std::vector<const CRmgTemplate *> curItems;
-	
-};
-
 class TeamAlignmentsWidget: public InterfaceObjectConfigurable
 {
 public:

+ 1 - 1
client/widgets/Buttons.h

@@ -35,7 +35,7 @@ public:
 		BLOCKED=2,
 		HIGHLIGHTED=3
 	};
-private:
+protected:
 	std::vector<std::string> imageNames;//store list of images that can be used by this button
 	size_t currentImage;
 

+ 176 - 8
client/widgets/ComboBox.cpp

@@ -1,8 +1,176 @@
-//
-//  ComboBox.cpp
-//  vcmiclient
-//
-//  Created by nordsoft on 24.08.2023.
-//
-
-#include "ComboBox.hpp"
+/*
+ * ComboBox.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "ComboBox.h"
+
+#include "Slider.h"
+#include "Images.h"
+#include "TextControls.h"
+#include "../gui/CGuiHandler.h"
+#include "../gui/WindowHandler.h"
+
+ComboBox::DropDown::Item::Item(const JsonNode & config, ComboBox::DropDown & _dropDown, Point position)
+	: InterfaceObjectConfigurable(LCLICK | HOVER, position),
+	dropDown(_dropDown)
+{
+	build(config);
+	
+	if(auto w = widget<CPicture>("hoverImage"))
+	{
+		pos.w = w->pos.w;
+		pos.h = w->pos.h;
+	}
+	setRedrawParent(true);
+}
+
+void ComboBox::DropDown::Item::updateItem(int idx, const void * _item)
+{
+	if(auto w = widget<CLabel>("labelName"))
+	{
+		item = _item;
+		w->setText(dropDown.comboBox.getItemText(idx, item));
+	}
+}
+
+void ComboBox::DropDown::Item::hover(bool on)
+{
+	auto h = widget<CPicture>("hoverImage");
+	auto w = widget<CLabel>("labelName");
+	if(h && w)
+	{
+		if(w->getText().empty())
+			h->visible = false;
+		else
+			h->visible = on;
+	}
+	redraw();
+}
+
+void ComboBox::DropDown::Item::clickPressed(const Point & cursorPosition)
+{
+	if(isHovered())
+		dropDown.setItem(item);
+}
+
+void ComboBox::DropDown::Item::clickReleased(const Point & cursorPosition)
+{
+	dropDown.clickPressed(cursorPosition);
+	dropDown.clickReleased(cursorPosition);
+}
+
+ComboBox::DropDown::DropDown(const JsonNode & config, ComboBox & _comboBox):
+	InterfaceObjectConfigurable(LCLICK | HOVER),
+	comboBox(_comboBox)
+{
+	REGISTER_BUILDER("item", &ComboBox::DropDown::buildItem);
+	
+	comboBox.onConstructItems(curItems);
+	
+	addCallback("sliderMove", std::bind(&ComboBox::DropDown::sliderMove, this, std::placeholders::_1));
+	
+	pos = comboBox.pos;
+	
+	build(config);
+	
+	if(auto w = widget<CSlider>("slider"))
+	{
+		w->setAmount(curItems.size());
+	}
+
+	//FIXME: this should be done by InterfaceObjectConfigurable, but might have side-effects
+	pos = children.front()->pos;
+	for (auto const & child : children)
+		pos = pos.include(child->pos);
+	
+	updateListItems();
+}
+
+std::shared_ptr<ComboBox::DropDown::Item> ComboBox::DropDown::buildItem(const JsonNode & config)
+{
+	auto position = readPosition(config["position"]);
+	items.push_back(std::make_shared<Item>(config, *this, position));
+	return items.back();
+}
+
+void ComboBox::DropDown::sliderMove(int slidPos)
+{
+	auto w = widget<CSlider>("slider");
+	if(!w)
+		return; // ignore spurious call when slider is being created
+	updateListItems();
+	redraw();
+}
+
+bool ComboBox::DropDown::receiveEvent(const Point & position, int eventType) const
+{
+	if (eventType == LCLICK)
+		return true; // we want drop box to close when clicking outside drop box borders
+
+	return CIntObject::receiveEvent(position, eventType);
+}
+
+void ComboBox::DropDown::clickPressed(const Point & cursorPosition)
+{
+	if (!pos.isInside(cursorPosition))
+	{
+		assert(GH.windows().isTopWindow(this));
+		GH.windows().popWindows(1);
+	}
+}
+
+void ComboBox::DropDown::updateListItems()
+{
+	if(auto w = widget<CSlider>("slider"))
+	{
+		int elemIdx = w->getValue();
+		for(auto item : items)
+		{
+			if(elemIdx < curItems.size())
+			{
+				item->updateItem(elemIdx, curItems[elemIdx]);
+				elemIdx++;
+			}
+			else
+			{
+				item->updateItem(elemIdx);
+			}
+		}
+	}
+}
+
+void ComboBox::DropDown::setItem(const void * item)
+{
+	comboBox.setItem(item);
+	
+	assert(GH.windows().isTopWindow(this));
+	GH.windows().popWindows(1);
+}
+
+void ComboBox::DropDown::constructItems()
+{
+	comboBox.onConstructItems(curItems);
+}
+
+ComboBox::ComboBox(Point position, const std::string & defName, const std::pair<std::string, std::string> & help, const JsonNode & dropDownDescriptor, EShortcut key, bool playerColoredButton):
+	CButton(position, defName, help, 0, key, playerColoredButton)
+{
+	addCallback([&, dropDownDescriptor]()
+	{
+		GH.windows().createAndPushWindow<ComboBox::DropDown>(dropDownDescriptor, *this);
+	});
+}
+
+void ComboBox::setItem(const void * item)
+{
+	if(auto w = std::dynamic_pointer_cast<CLabel>(overlay))
+		addTextOverlay(getItemText(0, item), w->font, w->color);
+	
+	onSetItem(item);
+}

+ 65 - 10
client/widgets/ComboBox.h

@@ -1,13 +1,68 @@
-//
-//  ComboBox.hpp
-//  vcmiclient
-//
-//  Created by nordsoft on 24.08.2023.
-//
+/*
+ * ComboBox.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
 
-#ifndef ComboBox_hpp
-#define ComboBox_hpp
+#include "../gui/InterfaceObjectConfigurable.h"
+#include "Buttons.h"
 
-#include <stdio.h>
+class ComboBox : public CButton
+{
+	class DropDown : public InterfaceObjectConfigurable
+	{
+		struct Item : public InterfaceObjectConfigurable
+		{
+			DropDown & dropDown;
+			const void * item = nullptr;
+			
+			Item(const JsonNode &, ComboBox::DropDown &, Point position);
+			void updateItem(int index, const void * item = nullptr);
+			
+			void hover(bool on) override;
+			void clickPressed(const Point & cursorPosition) override;
+			void clickReleased(const Point & cursorPosition) override;
+		};
+		
+		friend struct Item;
+		
+	public:
+		DropDown(const JsonNode &, ComboBox &);
+		
+		void constructItems();
+		bool receiveEvent(const Point & position, int eventType) const override;
+		void clickPressed(const Point & cursorPosition) override;
+		void setItem(const void *);
+			
+	private:
+		std::shared_ptr<DropDown::Item> buildItem(const JsonNode & config);
+		
+		void sliderMove(int slidPos);
+		void updateListItems();
+		
+		ComboBox & comboBox;
+		std::vector<std::shared_ptr<Item>> items;
+		std::vector<const void *> curItems;
+	};
+	
+	friend class DropDown;
+	
+	void setItem(const void *);
 
-#endif /* ComboBox_hpp */
+public:
+	ComboBox(Point position, const std::string & defName, const std::pair<std::string, std::string> & help, const JsonNode & dropDownDescriptor, EShortcut key = {}, bool playerColoredButton = false);
+	
+	//define this callback to fill input vector with data for the combo box
+	std::function<void(std::vector<const void *> &)> onConstructItems;
+	
+	//callback is called when item is selected and its value can be used
+	std::function<void(const void *)> onSetItem;
+	
+	//return text value from item data
+	std::function<std::string(int, const void *)> getItemText;
+};