소스 검색

Merge pull request #2157 from IvanSavenko/interface_layouts

Update in-game settings window to recent changes
Ivan Savenko 2 년 전
부모
커밋
10fc6cecef

+ 4 - 2
Mods/vcmi/config/vcmi/english.json

@@ -46,8 +46,10 @@
 	"vcmi.systemOptions.otherGroup" : "Other Settings", // unused right now
 	"vcmi.systemOptions.townsGroup" : "Town Screen",
 
-	"vcmi.systemOptions.fullscreenButton.hover" : "Fullscreen",
-	"vcmi.systemOptions.fullscreenButton.help"  : "{Fullscreen}\n\nIf selected, VCMI will run in fullscreen mode, otherwise it will run in windowed mode",
+	"vcmi.systemOptions.fullscreenBorderless.hover" : "Fullscreen (borderless)",
+	"vcmi.systemOptions.fullscreenBorderless.help"  : "{Borderless Fullscreen}\n\nIf selected, VCMI will run in borderless fullscreen mode. In this mode, game will always use same resolution as desktop, ignoring selected resolution.",
+	"vcmi.systemOptions.fullscreenExclusive.hover"  : "Fullscreen (exclusive)",
+	"vcmi.systemOptions.fullscreenExclusive.help"   : "{Fullscreen}\n\nIf selected, VCMI will run in exclusive fullscreen mode. In this mode, game will change resolution of monitor to selected resolution.",
 	"vcmi.systemOptions.resolutionButton.hover" : "Resolution: %wx%h",
 	"vcmi.systemOptions.resolutionButton.help"  : "{Select Resolution}\n\nChange in-game screen resolution.",
 	"vcmi.systemOptions.resolutionMenu.hover"   : "Select Resolution",

+ 0 - 2
Mods/vcmi/config/vcmi/german.json

@@ -46,8 +46,6 @@
 	"vcmi.systemOptions.otherGroup" : "Andere Einstellungen", // unused right now
 	"vcmi.systemOptions.townsGroup" : "Stadt-Bildschirm",
 
-	"vcmi.systemOptions.fullscreenButton.hover" : "Vollbild",
-	"vcmi.systemOptions.fullscreenButton.help"  : "{Vollbild}\n\n Wenn ausgewählt wird VCMI im Vollbildmodus laufen, ansonsten im Fenstermodus",
 	"vcmi.systemOptions.resolutionButton.hover" : "Auflösung: %wx%h",
 	"vcmi.systemOptions.resolutionButton.help"  : "{Wähle Auflösung}\n\n Ändert die Spielauflösung. Spielneustart ist erforderlich um neue Auflösung zu übernehmen.",
 	"vcmi.systemOptions.resolutionMenu.hover"   : "Wähle Auflösung",

+ 0 - 3
Mods/vcmi/config/vcmi/polish.json

@@ -37,13 +37,10 @@
 	"vcmi.systemOptions.otherGroup" : "Inne ustawienia", // unused right now
 	"vcmi.systemOptions.townsGroup" : "Ekran miasta",
 
-	"vcmi.systemOptions.fullscreenButton.hover" : "Pełny ekran",
-	"vcmi.systemOptions.fullscreenButton.help"  : "{Pełny ekran}\n\n Po wybraniu VCMI uruchomi się w trybie pełnoekranowym, w przeciwnym wypadku uruchomi się w oknie",
 	"vcmi.systemOptions.resolutionButton.hover" : "Rozdzielczość: %wx%h",
 	"vcmi.systemOptions.resolutionButton.help"  : "{Wybierz rozdzielczość}\n\n Zmień rozdzielczość ekranu w grze. Restart gry jest wymagany, by zmiany zostały uwzględnione.",
 	"vcmi.systemOptions.resolutionMenu.hover"   : "Wybierz rozdzielczość",
 	"vcmi.systemOptions.resolutionMenu.help"    : "Zmień rozdzielczość ekranu w grze.",
-	"vcmi.systemOptions.fullscreenFailed"       : "{Pełny ekran}\n\n Nieudane przełączenie w tryb pełnoekranowy! Obecna rozdzielczość nie jest wspierana przez wyświetlacz!",
 	"vcmi.systemOptions.framerateButton.hover"  : "Pokaż FPS",
 	"vcmi.systemOptions.framerateButton.help"   : "{Pokaż FPS}\n\n Przełącza widoczność licznika klatek na sekundę (FPS) w rogu okna gry.",
 

+ 0 - 3
Mods/vcmi/config/vcmi/russian.json

@@ -37,13 +37,10 @@
 	"vcmi.systemOptions.otherGroup" : "Иное", // unused right now
 	"vcmi.systemOptions.townsGroup" : "Экран города",
 
-	"vcmi.systemOptions.fullscreenButton.hover" : "Полный экран",
-	"vcmi.systemOptions.fullscreenButton.help"  : "{Полный экран}\n\n Если выбрано, то VCMI будет работать в полноэкранном режиме, если нет - в окне",
 	"vcmi.systemOptions.resolutionButton.hover" : "Разрешение %wx%h",
 	"vcmi.systemOptions.resolutionButton.help"  : "{Разрешение экрана}\n\n Изменение разрешения экрана. Для применения нового разрешения требуется перезапуск игры.",
 	"vcmi.systemOptions.resolutionMenu.hover"   : "Выбрать разрешения экрана",
 	"vcmi.systemOptions.resolutionMenu.help"    : "Изменение разрешения экрана в игре.",
-	"vcmi.systemOptions.fullscreenFailed"       : "{Полный экран}\n\n Невозможно переключиться в полноэкранный режим - выбранное разрешение не поддерживается дисплеем!",
 	"vcmi.systemOptions.framerateButton.hover"  : "Показывать частоту кадров",
 	"vcmi.systemOptions.framerateButton.help"   : "{Показывать частоту кадров}\n\n Включить счетчик частоты кадров в углу игрового клиента",
 

+ 0 - 3
Mods/vcmi/config/vcmi/spanish.json

@@ -46,13 +46,10 @@
 	"vcmi.systemOptions.otherGroup" : "Otras configuraciones", // actualmente no utilizada
 	"vcmi.systemOptions.townsGroup" : "Pantalla de la ciudad",
 
-	"vcmi.systemOptions.fullscreenButton.hover" : "Pantalla completa",
-	"vcmi.systemOptions.fullscreenButton.help"  : "{Pantalla completa}\n\n Si se selecciona, VCMI se ejecutará en modo de pantalla completa, de lo contrario se ejecutará en ventana",
 	"vcmi.systemOptions.resolutionButton.hover" : "Resolución: %wx%h",
 	"vcmi.systemOptions.resolutionButton.help"  : "{Seleccionar resolución}\n\n Cambia la resolución de la pantalla del juego. Se requiere reiniciar el juego para aplicar la nueva resolución.",
 	"vcmi.systemOptions.resolutionMenu.hover"   : "Seleccionar resolución",
 	"vcmi.systemOptions.resolutionMenu.help"    : "Cambia la resolución de la pantalla del juego.",
-	"vcmi.systemOptions.fullscreenFailed"       : "{Pantalla completa}\n\n ¡Fallo al cambiar a modo de pantalla completa! ¡La resolución actual no es compatible con la pantalla!",
 	"vcmi.systemOptions.framerateButton.hover"  : "Mostrar FPS",
 	"vcmi.systemOptions.framerateButton.help"   : "{Mostrar FPS}\n\n Muestra el contador de Frames Por Segundo en la esquina de la ventana del juego.",
 

+ 6 - 3
Mods/vcmi/config/vcmi/ukrainian.json

@@ -38,15 +38,18 @@
 	"vcmi.systemOptions.otherGroup" : "Інші налаштування",
 	"vcmi.systemOptions.townsGroup" : "Екран міста",
 
-    "vcmi.adventureOptions.infoBarPick.help" : "{Повідомлення у панелі статусу}\n\nЗа можливості, повідомлення про відвідування об'єктів карти пригод будуть відображені у панелі статусу замість окремого вікна",
-    "vcmi.adventureOptions.infoBarPick.hover" : "Повідомлення у панелі статусу",
+	"vcmi.systemOptions.fullscreenBorderless.hover" : "На весь екран (безрамкове вікно)",
+	"vcmi.systemOptions.fullscreenBorderless.help"  : "{На весь екран (безрамкове вікно)}\n\nЯкщо обрано, VCMI працюватиме у режимі безрамкового вікна на весь екран. У цьому режимі гра завжди використовує ту саму роздільну здатність, що й робочий стіл, ігноруючи вибрану роздільну здатність",
+	"vcmi.systemOptions.fullscreenExclusive.hover"  : "На весь екран (ексклюзивний режим)",
+	"vcmi.systemOptions.fullscreenExclusive.help"   : "{На весь екран (ексклюзивний режим)}\n\nnЯкщо вибрано, VCMI працюватиме у ексклюзивному повноекранному режимі. У цьому режимі гра змінюватиме роздільну здатність монітора на вибрану роздільну здатність",
+	"vcmi.adventureOptions.infoBarPick.help" : "{Повідомлення у панелі статусу}\n\nЗа можливості, повідомлення про відвідування об'єктів карти пригод будуть відображені у панелі статусу замість окремого вікна",
+	"vcmi.adventureOptions.infoBarPick.hover" : "Повідомлення у панелі статусу",
 	"vcmi.systemOptions.fullscreenButton.hover" : "Повноекранний режим",
 	"vcmi.systemOptions.fullscreenButton.help"  : "{Повноекранний режим}\n\n Якщо обрано, VCMI буде запускатися в режимі на весь екран, інакше — віконний режим",
 	"vcmi.systemOptions.resolutionButton.hover" : "Роздільна здатність: %wx%h",
 	"vcmi.systemOptions.resolutionButton.help"  : "{Роздільна здатність}\n\n Зміна розширення екрану в грі. Аби зміни набули чинності необхідно перезавантажити гру.",
 	"vcmi.systemOptions.resolutionMenu.hover"   : "Обрати роздільну здатність",
 	"vcmi.systemOptions.resolutionMenu.help"    : "Змінити роздільну здатність екрану в грі.",
-	"vcmi.systemOptions.fullscreenFailed"       : "{Повноекранний режим}\n\n Не вдалося перейти в повноекранний режим! Поточна роздільна здатність не підтримується дисплеєм!",
 	"vcmi.systemOptions.framerateButton.hover"  : "Лічильник кадрів",
 	"vcmi.systemOptions.framerateButton.help"   : "{Лічильник кадрів}\n\n Перемикає видимість лічильника кадрів на секунду у кутку ігрового вікна",
 

+ 100 - 2
client/gui/InterfaceObjectConfigurable.cpp

@@ -26,6 +26,7 @@
 #include "../windows/InfoWindows.h"
 
 #include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/filesystem/ResourceID.h"
 
 InterfaceObjectConfigurable::InterfaceObjectConfigurable(const JsonNode & config, int used, Point offset):
 	InterfaceObjectConfigurable(used, offset)
@@ -46,6 +47,7 @@ InterfaceObjectConfigurable::InterfaceObjectConfigurable(int used, Point offset)
 	REGISTER_BUILDER("button", &InterfaceObjectConfigurable::buildButton);
 	REGISTER_BUILDER("labelGroup", &InterfaceObjectConfigurable::buildLabelGroup);
 	REGISTER_BUILDER("slider", &InterfaceObjectConfigurable::buildSlider);
+	REGISTER_BUILDER("layout", &InterfaceObjectConfigurable::buildLayout);
 }
 
 void InterfaceObjectConfigurable::registerBuilder(const std::string & type, BuilderFunction f)
@@ -65,20 +67,47 @@ void InterfaceObjectConfigurable::deleteWidget(const std::string & name)
 		widgets.erase(iter);
 }
 
+void InterfaceObjectConfigurable::loadCustomBuilders(const JsonNode & config)
+{
+	for(auto & item : config.Struct())
+	{
+		std::string typeName = item.first;
+		JsonNode baseConfig = item.second;
+
+		auto const & functor = [this, baseConfig](const JsonNode & widgetConfig) -> std::shared_ptr<CIntObject>
+		{
+			JsonNode actualConfig = widgetConfig;
+			JsonUtils::mergeCopy(actualConfig, baseConfig);
+
+			return this->buildWidget(actualConfig);
+		};
+		registerBuilder(typeName, functor);
+	}
+}
+
 void InterfaceObjectConfigurable::build(const JsonNode &config)
 {
 	OBJ_CONSTRUCTION;
+
 	logGlobal->debug("Building configurable interface object");
 	auto * items = &config;
 	
 	if(config.getType() == JsonNode::JsonType::DATA_STRUCT)
 	{
+		if (!config["library"].isNull())
+		{
+			const JsonNode library(ResourceID(config["library"].String()));
+			loadCustomBuilders(library);
+		}
+
+		loadCustomBuilders(config["customTypes"]);
+
 		for(auto & item : config["variables"].Struct())
 		{
 			logGlobal->debug("Read variable named %s", item.first);
 			variables[item.first] = item.second;
 		}
-		
+
 		items = &config["items"];
 	}
 	
@@ -86,6 +115,11 @@ void InterfaceObjectConfigurable::build(const JsonNode &config)
 		addWidget(item["name"].String(), buildWidget(item));
 }
 
+void InterfaceObjectConfigurable::addConditional(const std::string & name, bool active)
+{
+	conditionals[name] = active;
+}
+
 void InterfaceObjectConfigurable::addWidget(const std::string & namePreferred, std::shared_ptr<CIntObject> widget)
 {
 	static const std::string unnamedObjectPrefix = "__widget_";
@@ -268,7 +302,8 @@ std::shared_ptr<CToggleGroup> InterfaceObjectConfigurable::buildToggleGroup(cons
 		for(const auto & item : config["items"].Vector())
 		{
 			itemIdx = item["index"].isNull() ? itemIdx + 1 : item["index"].Integer();
-			group->addToggle(itemIdx, std::dynamic_pointer_cast<CToggleBase>(buildWidget(item)));
+			auto newToggle = std::dynamic_pointer_cast<CToggleButton>(buildWidget(item));
+			group->addToggle(itemIdx, newToggle);
 		}
 	}
 	if(!config["selected"].isNull())
@@ -446,6 +481,69 @@ std::shared_ptr<CFilledTexture> InterfaceObjectConfigurable::buildTexture(const
 	return std::make_shared<CFilledTexture>(image, rect);
 }
 
+/// Small helper class that provides ownership for shared_ptr's of child elements
+class InterfaceLayoutWidget : public CIntObject
+{
+public:
+	std::vector<std::shared_ptr<CIntObject>> ownedChildren;
+};
+
+std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildLayout(const JsonNode & config)
+{
+	logGlobal->debug("Building widget Layout");
+	bool vertical = config["vertical"].Bool();
+	bool horizontal = config["horizontal"].Bool();
+	bool dynamic = config["dynamic"].Bool();
+	int distance = config["distance"].Integer();
+	std::string customType = config["customType"].String();
+	auto position = readPosition(config["position"]);
+
+	auto result = std::make_shared<InterfaceLayoutWidget>();
+	result->moveBy(position);
+	Point layoutPosition;
+
+	for(auto item : config["items"].Vector())
+	{
+		if (item["type"].String().empty())
+			item["type"].String() = customType;
+
+		if (!item["created"].isNull())
+		{
+			std::string name = item["created"].String();
+
+			if (conditionals.count(name) != 0)
+			{
+				if (!conditionals.at(name))
+					continue;
+			}
+			else
+			{
+				logMod->warn("Unknown condition %s in widget!", name);
+			}
+		}
+
+		auto widget = buildWidget(item);
+
+		addWidget(item["name"].String(), widget);
+		result->ownedChildren.push_back(widget);
+		result->addChild(widget.get(), false);
+
+		widget->moveBy(position + layoutPosition);
+
+		if (dynamic && vertical)
+			layoutPosition.y += widget->pos.h;
+		if (dynamic && horizontal)
+			layoutPosition.x += widget->pos.w;
+
+		if (vertical)
+			layoutPosition.y += distance;
+		if (horizontal)
+			layoutPosition.x += distance;
+	}
+
+	return result;
+}
+
 std::shared_ptr<CShowableAnim> InterfaceObjectConfigurable::buildAnimation(const JsonNode & config) const
 {
 	logGlobal->debug("Building widget CShowableAnim");

+ 6 - 0
client/gui/InterfaceObjectConfigurable.h

@@ -45,10 +45,14 @@ protected:
 
 	using BuilderFunction = std::function<std::shared_ptr<CIntObject>(const JsonNode &)>;
 	void registerBuilder(const std::string &, BuilderFunction);
+
+	void loadCustomBuilders(const JsonNode & config);
 	
 	//must be called after adding callbacks
 	void build(const JsonNode & config);
 
+	void addConditional(const std::string & name, bool active);
+
 	void addWidget(const std::string & name, std::shared_ptr<CIntObject> widget);
 	
 	void addCallback(const std::string & callbackName, std::function<void(int)> callback);
@@ -91,6 +95,7 @@ protected:
 	std::shared_ptr<CAnimImage> buildImage(const JsonNode &) const;
 	std::shared_ptr<CShowableAnim> buildAnimation(const JsonNode &) const;
 	std::shared_ptr<CFilledTexture> buildTexture(const JsonNode &) const;
+	std::shared_ptr<CIntObject> buildLayout(const JsonNode &);
 		
 	//composite widgets
 	std::shared_ptr<CIntObject> buildWidget(JsonNode config) const;
@@ -107,5 +112,6 @@ private:
 	std::map<std::string, BuilderFunction> builders;
 	std::map<std::string, std::shared_ptr<CIntObject>> widgets;
 	std::map<std::string, std::function<void(int)>> callbacks;
+	std::map<std::string, bool> conditionals;
 	std::map<EShortcut, ShortcutState> shortcuts;
 };

+ 62 - 20
client/windows/settings/GeneralOptionsTab.cpp

@@ -78,6 +78,13 @@ GeneralOptionsTab::GeneralOptionsTab()
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 	type |= REDRAW_PARENT;
 
+	addConditional("mobile", false);
+	addConditional("desktop", true);
+#ifdef VCMI_MOBILE
+	addConditional("mobile", true);
+	addConditional("desktop", false);
+#endif
+
 	const JsonNode config(ResourceID("config/widgets/settings/generalOptionsTab.json"));
 	addCallback("spellbookAnimationChanged", [](bool value)
 	{
@@ -102,9 +109,13 @@ GeneralOptionsTab::GeneralOptionsTab()
 			targetLabel->setText(std::to_string(value) + "%");
 	});
 	//settings that do not belong to base game:
-	addCallback("fullscreenChanged", [this](bool value)
+	addCallback("fullscreenBorderlessChanged", [this](bool value)
 	{
-		setFullscreenMode(value);
+		setFullscreenMode(value, false);
+	});
+	addCallback("fullscreenExclusiveChanged", [this](bool value)
+	{
+		setFullscreenMode(value, true);
 	});
 	addCallback("setGameResolution", [this](int dummyValue)
 	{
@@ -134,21 +145,19 @@ GeneralOptionsTab::GeneralOptionsTab()
 
 	const auto & currentResolution = settings["video"]["resolution"];
 
-	std::shared_ptr<CLabel> resolutionLabel = widget<CLabel>("resolutionLabel");
-	resolutionLabel->setText(resolutionToLabelString(currentResolution["width"].Integer(), currentResolution["height"].Integer()));
-
 	std::shared_ptr<CLabel> scalingLabel = widget<CLabel>("scalingLabel");
 	scalingLabel->setText(scalingToLabelString(currentResolution["scaling"].Integer()));
 
 	std::shared_ptr<CToggleButton> spellbookAnimationCheckbox = widget<CToggleButton>("spellbookAnimationCheckbox");
 	spellbookAnimationCheckbox->setSelected(settings["video"]["spellbookAnimation"].Bool());
 
-	std::shared_ptr<CToggleButton> fullscreenCheckbox = widget<CToggleButton>("fullscreenCheckbox");
-	fullscreenCheckbox->setSelected(settings["video"]["fullscreen"].Bool());
-	onFullscreenChanged([&](const JsonNode &newState) //used when pressing F4 etc. to change fullscreen checkbox state
-	{
-		widget<CToggleButton>("fullscreenCheckbox")->setSelected(newState.Bool());
-	});
+	std::shared_ptr<CToggleButton> fullscreenBorderlessCheckbox = widget<CToggleButton>("fullscreenBorderlessCheckbox");
+	if (fullscreenBorderlessCheckbox)
+		fullscreenBorderlessCheckbox->setSelected(settings["video"]["fullscreen"].Bool() && !settings["video"]["realFullscreen"].Bool());
+
+	std::shared_ptr<CToggleButton> fullscreenExclusiveCheckbox = widget<CToggleButton>("fullscreenExclusiveCheckbox");
+	if (fullscreenExclusiveCheckbox)
+		fullscreenExclusiveCheckbox->setSelected(settings["video"]["fullscreen"].Bool() && settings["video"]["realFullscreen"].Bool());
 
 	std::shared_ptr<CToggleButton> framerateCheckbox = widget<CToggleButton>("framerateCheckbox");
 	framerateCheckbox->setSelected(settings["video"]["showfps"].Bool());
@@ -171,15 +180,33 @@ GeneralOptionsTab::GeneralOptionsTab()
 	std::shared_ptr<CLabel> soundVolumeLabel = widget<CLabel>("soundValueLabel");
 	soundVolumeLabel->setText(std::to_string(CCS->soundh->getVolume()) + "%");
 
-#ifdef VCMI_MOBILE
-	// On mobile platforms, VCMI always uses OS screen resolutions
-	// Players can control UI size via "Interface Scaling" option instead
+	updateResolutionSelector();
+}
+
+void GeneralOptionsTab::updateResolutionSelector()
+{
 	std::shared_ptr<CButton> resolutionButton = widget<CButton>("resolutionButton");
+	std::shared_ptr<CLabel> resolutionLabel = widget<CLabel>("resolutionLabel");
+
+	if (settings["video"]["fullscreen"].Bool() && !settings["video"]["realFullscreen"].Bool())
+	{
+		if (resolutionButton)
+			resolutionButton->disable();
+
+		if (resolutionLabel)
+			resolutionLabel->setText(resolutionToLabelString(GH.screenDimensions().x, GH.screenDimensions().y));
+	}
+	else
+	{
+		const auto & currentResolution = settings["video"]["resolution"];
+
+		if (resolutionButton)
+			resolutionButton->enable();
+
+		if (resolutionLabel)
+			resolutionLabel->setText(resolutionToLabelString(currentResolution["width"].Integer(), currentResolution["height"].Integer()));
+	}
 
-	resolutionButton->disable();
-	resolutionLabel->disable();
-	fullscreenCheckbox->block(true);
-#endif
 }
 
 void GeneralOptionsTab::selectGameResolution()
@@ -224,17 +251,32 @@ void GeneralOptionsTab::setGameResolution(int index)
 	widget<CLabel>("resolutionLabel")->setText(resolutionToLabelString(resolution.x, resolution.y));
 }
 
-void GeneralOptionsTab::setFullscreenMode(bool on)
+void GeneralOptionsTab::setFullscreenMode(bool on, bool exclusive)
 {
+	setBoolSetting("video", "realFullscreen", exclusive);
 	setBoolSetting("video", "fullscreen", on);
+
+	std::shared_ptr<CToggleButton> fullscreenExclusiveCheckbox = widget<CToggleButton>("fullscreenExclusiveCheckbox");
+	std::shared_ptr<CToggleButton> fullscreenBorderlessCheckbox = widget<CToggleButton>("fullscreenBorderlessCheckbox");
+
+	if (fullscreenBorderlessCheckbox)
+		fullscreenBorderlessCheckbox->setSelected(on && !exclusive);
+
+	if (fullscreenExclusiveCheckbox)
+		fullscreenExclusiveCheckbox->setSelected(on && exclusive);
+
+	updateResolutionSelector();
 }
 
 void GeneralOptionsTab::selectGameScaling()
 {
 	supportedScaling.clear();
 
+	// generate list of all possible scaling values, with 10% step
+	// also add one value over maximum, so if player can use scaling up to 123.456% he will be able to select 130%
+	// and let screen handler clamp that value to actual maximum
 	auto [minimalScaling, maximalScaling] = GH.screenHandler().getSupportedScalingRange();
-	for (int i = 0; i <= maximalScaling; i += 10)
+	for (int i = 0; i <= maximalScaling + 10 - 1; i += 10)
 	{
 		if (i >= minimalScaling)
 			supportedScaling.push_back(i);

+ 3 - 1
client/windows/settings/GeneralOptionsTab.h

@@ -21,7 +21,7 @@ private:
 	std::vector<Point> supportedResolutions;
 	std::vector<int> supportedScaling;
 
-	void setFullscreenMode( bool on);
+	void setFullscreenMode( bool on, bool exclusive);
 
 	void selectGameResolution();
 	void setGameResolution(int index);
@@ -31,4 +31,6 @@ private:
 
 public:
 	GeneralOptionsTab();
+
+	void updateResolutionSelector();
 };

+ 10 - 0
client/windows/settings/SettingsMainWindow.cpp

@@ -156,3 +156,13 @@ void SettingsMainWindow::showAll(SDL_Surface *to)
 	CIntObject::showAll(to);
 	CMessage::drawBorder(color, to, pos.w+28, pos.h+29, pos.x-14, pos.y-15);
 }
+
+void SettingsMainWindow::onScreenResize()
+{
+	InterfaceObjectConfigurable::onScreenResize();
+
+	auto tab = std::dynamic_pointer_cast<GeneralOptionsTab>(tabContentArea->getItem());
+
+	if (tab)
+		tab->updateResolutionSelector();
+}

+ 2 - 1
client/windows/settings/SettingsMainWindow.h

@@ -41,6 +41,7 @@ private:
 public:
 	SettingsMainWindow(BattleInterface * parentBattleInterface = nullptr);
 
-	void showAll(SDL_Surface * to);
+	void showAll(SDL_Surface * to) override;
+	void onScreenResize() override;
 };
 

+ 75 - 110
config/widgets/settings/adventureOptionsTab.json

@@ -1,4 +1,6 @@
 {
+	"library" : "config/widgets/settings/library.json",
+
 	"items":
 	[
 		{
@@ -28,37 +30,39 @@
 		},
 
 		{
-			"name": "topSettingsLabels",
-			"type": "labelGroup",
-			"font": "medium",
-			"alignment": "center",
-			"color": "yellow",
+			"type" : "verticalLayout60",
+			"customType" : "labelCentered",
+			"position": {"x": 150, "y": 62},
 			"items":
 			[
 				{
-					"position": {"x": 150, "y": 62},
 					"text": "core.genrltxt.569" // Hero Speed
 				},
 				{
-					"position": {"x": 150, "y": 122},
 					"text": "core.genrltxt.570" // Enemy Speed
 				},
 				{
-					"position": {"x": 150, "y": 182},
 					"text": "core.genrltxt.571" // Scrolling Speed
 				}
 			]
 		},
-		
 		{
-			"name": "heroSpeedValueLabel",
-			"type": "label",
-			"font": "medium",
-			"alignment": "center",
-			"color": "yellow",
-			"position": {"x": 324, "y": 90}
+			"type" : "verticalLayout60",
+			"customType" : "labelCentered",
+			"position": {"x": 324, "y": 90},
+			"items":
+			[
+				{
+					"name": "heroSpeedValueLabel",
+				},
+				{
+					"name": "enemySpeedValueLabel",
+				},
+				{
+					"name": "mapScrollingValueLabel",
+				}
+			]
 		},
-
 		{
 			"name": "heroMovementSpeedPicker",
 			"type": "toggleGroup",
@@ -110,16 +114,6 @@
 			],
 			"callback": "playerHeroSpeedChanged"
 		},
-
-		{
-			"name": "enemySpeedValueLabel",
-			"type": "label",
-			"font": "medium",
-			"alignment": "center",
-			"color": "yellow",
-			"position": {"x": 324, "y": 150}
-		},
-
 		{
 			"name": "enemyMovementSpeedPicker",
 			"type": "toggleGroup",
@@ -176,15 +170,6 @@
 			],
 			"callback": "enemyHeroSpeedChanged"
 		},
-
-		{
-			"name": "mapScrollingValueLabel",
-			"type": "label",
-			"font": "medium",
-			"alignment": "center",
-			"color": "yellow",
-			"position": {"x": 324, "y": 210}
-		},
 		{
 			"name": "mapScrollSpeedPicker",
 			"type": "toggleGroup",
@@ -296,120 +281,100 @@
 		
 /////////////////////////////////////// Right section - Original H3 options
 		{
-			"name": "rightCheckboxesLabels",
-			"type": "labelGroup",
-			"font": "medium",
-			"alignment": "left",
-			"color": "white",
+			"type" : "verticalLayout",
+			"customType" : "labelDescription",
+			"position": {"x": 415, "y": 55},
 			"items":
 			[
 				{
-					"position": {"x": 415, "y": 55},
 					"text": "core.genrltxt.572" // TODO: show move path
 				},
 				{
-					"position": {"x": 415, "y": 85},
 					"text": "core.genrltxt.573" // show hero reminder
 				},
 				{
-					"position": {"x": 415, "y": 115},
 					"text": "core.genrltxt.574" // quick combat
 				}
 			]
 		},
 		{
-			"name": "showMovePathPlaceholder",
-			"type": "picture",
-			"image": "settingsWindow/checkBoxEmpty",
+			"type" : "verticalLayout",
+			"customType" : "checkbox",
 			"position": {"x": 380, "y": 53},
+			"items":
+			[
+				{
+					"name": "showMovePathPlaceholder",
+					"type": "checkboxFake",
+				},
+				{
+					"name": "heroReminderCheckbox",
+					"help": "core.help.361",
+					"callback": "heroReminderChanged"
+				},
+				{
+					"name": "quickCombatCheckbox",
+					"help": "core.help.362",
+					"callback": "quickCombatChanged"
+				},
+			]
 		},
-		{
-			"name": "heroReminderCheckbox",
-			"type": "toggleButton",
-			"image": "sysopchk.def",
-			"help": "core.help.361",
-			"position": {"x": 380, "y": 83},
-			"callback": "heroReminderChanged"
-		},
-		{
-			"name": "quickCombatCheckbox",
-			"type": "toggleButton",
-			"image": "sysopchk.def",
-			"help": "core.help.362",
-			"position": {"x": 380, "y": 113},
-			"callback": "quickCombatChanged"
-		},
-		
 /////////////////////////////////////// Bottom section - VCMI Options
 		{
-			"name": "bottomCheckboxesLabels",
-			"type": "labelGroup",
-			"font": "medium",
-			"alignment": "left",
-			"color": "white",
+			"type": "verticalLayout",
+			"customType": "labelDescription",
+			"position": {"x": 45, "y": 295},
 			"items":
 			[
 				{
-					"position": {"x": 45, "y": 295},
 					"text": "vcmi.adventureOptions.numericQuantities.hover"
 				},
 				{
-					"position": {"x": 45, "y": 325},
 					"text": "vcmi.adventureOptions.forceMovementInfo.hover"
 				},
 				{
-					"position": {"x": 45, "y": 355},
 					"text": "vcmi.adventureOptions.showGrid.hover"
 				},
 				{
-					"position": {"x": 45, "y": 385},
 					"text": "vcmi.adventureOptions.mapSwipe.hover"
 				},
 				{
-					"position": {"x": 45, "y": 415},
 					"text": "vcmi.adventureOptions.infoBarPick.hover"
 				}
 			]
 		},
 		{
-			"name": "numericQuantitiesCheckbox",
-			"type": "toggleButton",
-			"image": "sysopchk.def",
-			"help": "vcmi.adventureOptions.numericQuantities",
+			"type": "verticalLayout",
+			"customType": "checkbox",
 			"position": {"x": 10, "y": 293},
-			"callback": "numericQuantitiesChanged"
-		},
-		{
-			"name": "forceMovementInfoCheckbox",
-			"type": "toggleButton",
-			"image": "sysopchk.def",
-			"help": "vcmi.adventureOptions.forceMovementInfo",
-			"position": {"x": 10, "y": 323},
-			"callback": "forceMovementInfoChanged"
-		},
-		{
-			"name": "showGridCheckbox",
-			"type": "toggleButton",
-			"image": "sysopchk.def",
-			"help": "vcmi.adventureOptions.showGrid",
-			"position": {"x": 10, "y": 353},
-			"callback": "showGridChanged"
-		},
-		{
-			"name": "mapSwipeCheckbox",
-			"type": "toggleButton",
-			"image": "sysopchk.def",
-			"help": "vcmi.adventureOptions.mapSwipe",
-			"position": {"x": 10, "y": 383},
-			"callback": "mapSwipeChanged"
-		},
-		{
-			"name": "infoBarPickCheckbox",
-			"type": "toggleButton",
-			"image": "sysopchk.def",
-			"help": "vcmi.adventureOptions.infoBarPick",
-			"position": {"x": 10, "y": 413},
-			"callback": "infoBarPickChanged"
+			"items":
+			[
+				{
+					"name": "numericQuantitiesCheckbox",
+					"help": "vcmi.adventureOptions.numericQuantities",
+					"callback": "numericQuantitiesChanged"
+				},
+				{
+					"name": "forceMovementInfoCheckbox",
+					"help": "vcmi.adventureOptions.forceMovementInfo",
+					"callback": "forceMovementInfoChanged"
+				},
+				{
+					"name": "showGridCheckbox",
+					"help": "vcmi.adventureOptions.showGrid",
+					"callback": "showGridChanged"
+				},
+				{
+					"name": "mapSwipeCheckbox",
+					"help": "vcmi.adventureOptions.mapSwipe",
+					"callback": "mapSwipeChanged"
+				},
+				{
+					"name": "infoBarPickCheckbox",
+					"help": "vcmi.adventureOptions.infoBarPick",
+					"callback": "infoBarPickChanged"
+				}
+			]
 		}
 	]
 }

+ 78 - 142
config/widgets/settings/battleOptionsTab.json

@@ -1,4 +1,6 @@
 {
+	"library" : "config/widgets/settings/library.json",
+
 	"items":
 	[
 		{
@@ -13,200 +15,140 @@
 			"image": "settingsWindow/lineHorizontal",
 			"rect": { "x" : 5, "y" : 319, "w": 365, "h": 3}
 		},
-
 		{
-			"name": "titlesLabels",
-			"type": "labelGroup",
-			"font": "medium",
-			"alignment": "left",
-			"color": "yellow",
-			"items":
-			[
-				{
-					"text": "core.genrltxt.396", // Auto-combat options
-					"position": {"x": 380, "y": 55}
-				},
-				{
-					"text": "core.genrltxt.397", // Creature info
-					"position": {"x": 10, "y": 235}
-				}
-			]
+			"type" : "labelTitle",
+			"text": "core.genrltxt.396", // Auto-combat options
+			"position": {"x": 380, "y": 55}
+		},
+		{
+			"type" : "labelTitle",
+			"text": "core.genrltxt.397", // Creature info
+			"position": {"x": 10, "y": 235}
 		},
 /////////////////////////////////////// Right section - Auto-combat settings (NOT IMPLEMENTED)
 		{
 			"name": "autoCombatLabels",
-			"type": "labelGroup",
-			"font": "medium",
-			"alignment": "left",
-			"color": "white",
+			"type" : "verticalLayout",
+			"customType" : "labelDescription",
+			"position": {"x": 415, "y": 85},
 			"items":
 			[
 				{
-					"text": "core.genrltxt.398", // Creatures
-					"position": {"x": 415, "y": 85}
+					"text": "core.genrltxt.398" // Creatures
 				},
 				{
-					"text": "core.genrltxt.399", // Spells
-					"position": {"x": 415, "y": 115}
+					"text": "core.genrltxt.399" // Spells
 				},
 				{
-					"text": "core.genrltxt.400", // Catapult
-					"position": {"x": 415, "y": 145}
+					"text": "core.genrltxt.400" // Catapult
 				},
 				{
-					"text": "core.genrltxt.151", // Ballista
-					"position": {"x": 415, "y": 175}
+					"text": "core.genrltxt.151" // Ballista
 				},
 				{
-					"text": "core.genrltxt.401", // First Aid Tent
-					"position": {"x": 415, "y": 205}
+					"text": "core.genrltxt.401" // First Aid Tent
 				}
 			]
 		},
 		{
-			"name": "autoCombatCreaturesPlaceholder",
-			"type": "picture",
-			"image": "settingsWindow/checkBoxEmpty",
+			"name": "autoCombatCheckboxes",
+			"type" : "verticalLayout",
+			"customType" : "checkboxFake",
 			"position": {"x": 380, "y": 83},
+			"items":
+			[
+				{},
+				{},
+				{},
+				{},
+				{}
+			]
 		},
+/////////////////////////////////////// Left section - checkboxes
 		{
-			"name": "autoCombatSpellsPlaceholder",
-			"type": "picture",
-			"image": "settingsWindow/checkBoxEmpty",
-			"position": {"x": 380, "y": 113},
-		},
-		{
-			"name": "autoCombatCatapultPlaceholder",
-			"type": "picture",
-			"image": "settingsWindow/checkBoxEmpty",
-			"position": {"x": 380, "y": 143},
-		},
-		{
-			"name": "autoCombatBallistaPlaceholder",
-			"type": "picture",
-			"image": "settingsWindow/checkBoxEmpty",
-			"position": {"x": 380, "y": 173},
+			"type": "labelDescription",
+			"text": "core.genrltxt.402", // All Stats
+			"position": {"x": 45, "y": 265}
 		},
 		{
-			"name": "autoCombatFirstAidTentPlaceholder",
-			"type": "picture",
-			"image": "settingsWindow/checkBoxEmpty",
-			"position": {"x": 380, "y": 203},
-		},
-/////////////////////////////////////// Left section - checkboxes
-		{
-			"name": "creatureInfoLabels",
-			"type": "labelGroup",
-			"font": "medium",
-			"alignment": "left",
-			"color": "white",
-			"items":
-			[
-				{
-					"text": "core.genrltxt.402", // All Stats
-					"position": {"x": 45, "y": 265}
-				},
-				{
-					"text": "core.genrltxt.403", // Spells only
-					"position": {"x": 45, "y": 295}
-				}
-			]
+			"type": "labelDescription",
+			"text": "core.genrltxt.403", // Spells only
+			"position": {"x": 45, "y": 295}
 		},
 		{
 			"name": "creatureInfoAllPlaceholder",
-			"type": "picture",
-			"image": "settingsWindow/checkBoxEmpty",
+			"type": "checkboxFake",
 			"position": {"x": 10, "y": 263},
 		},
 		{
 			"name": "creatureInfoSpellsPlaceholder",
-			"type": "picture",
-			"image": "settingsWindow/checkBoxEmpty",
+			"type": "checkboxFake",
 			"position": {"x": 10, "y": 293},
 		},
-
 		{
 			"name": "generalOptionsLabels",
-			"type": "labelGroup",
-			"font": "medium",
-			"alignment": "left",
-			"color": "white",
+			"type" : "verticalLayout",
+			"customType" : "labelDescription",
+			"position": {"x": 45, "y": 55},
 			"items":
 			[
 				{
 					"text": "core.genrltxt.404",
-					"position": {"x": 45, "y": 55}
 				},
 				{
 					"text": "core.genrltxt.405",
-					"position": {"x": 45, "y": 85}
 				},
 				{
 					"text": "vcmi.battleOptions.movementHighlightOnHover.hover",
-					"position": {"x": 45, "y": 115}
 				},
 				{
 					"text": "core.genrltxt.406",
-					"position": {"x": 45, "y": 145}
 				},
 				{
 					"text": "vcmi.battleOptions.skipBattleIntroMusic.hover",
-					"position": {"x": 45, "y": 175}
 				},
 				{
 					"text": "vcmi.battleOptions.touchscreenMode.hover",
-					"position": {"x": 45, "y": 205}
 				}
 			]
 		},
-
 		{
-			"name": "viewGridCheckbox",
-			"type": "toggleButton",
-			"image": "sysopchk.def",
-			"help": "core.help.427",
+			"type" : "verticalLayout",
+			"customType" : "checkbox",
 			"position": {"x": 10, "y": 53},
-			"callback": "viewGridChanged"
-		},
-		{
-			"name": "movementShadowCheckbox",
-			"type": "toggleButton",
-			"image": "sysopchk.def",
-			"help": "core.help.428",
-			"position": {"x": 10, "y": 83},
-			"callback": "movementShadowChanged"
-		},
-		{
-			"name": "movementHighlightOnHoverCheckbox",
-			"type": "toggleButton",
-			"image": "sysopchk.def",
-			"help": "vcmi.battleOptions.movementHighlightOnHover",
-			"position": {"x": 10, "y": 113},
-			"callback": "movementHighlightOnHoverChanged"
-		},
-		{
-			"name": "mouseShadowCheckbox",
-			"type": "toggleButton",
-			"image": "sysopchk.def",
-			"help": "core.help.429",
-			"position": {"x": 10, "y": 143},
-			"callback": "mouseShadowChanged"
-		},
-		{
-			"name": "skipBattleIntroMusicCheckbox",
-			"type": "toggleButton",
-			"image": "sysopchk.def",
-			"help": "vcmi.battleOptions.skipBattleIntroMusic",
-			"position": {"x": 10, "y": 173},
-			"callback": "skipBattleIntroMusicChanged"
-		},
-		{
-			"name": "touchscreenModeCheckbox",
-			"type": "toggleButton",
-			"image": "sysopchk.def",
-			"help": "vcmi.battleOptions.touchscreenMode",
-			"position": {"x": 10, "y": 203},
-			"callback": "touchscreenModeChanged"
+			"items":
+			[
+				{
+					"name": "viewGridCheckbox",
+					"help": "core.help.427",
+					"callback": "viewGridChanged"
+				},
+				{
+					"name": "movementShadowCheckbox",
+					"help": "core.help.428",
+					"callback": "movementShadowChanged"
+				},
+				{
+					"name": "movementHighlightOnHoverCheckbox",
+					"help": "vcmi.battleOptions.movementHighlightOnHover",
+					"callback": "movementHighlightOnHoverChanged"
+				},
+				{
+					"name": "mouseShadowCheckbox",
+					"help": "core.help.429",
+					"callback": "mouseShadowChanged"
+				},
+				{
+					"name": "skipBattleIntroMusicCheckbox",
+					"help": "vcmi.battleOptions.skipBattleIntroMusic",
+					"callback": "skipBattleIntroMusicChanged"
+				},
+				{
+					"name": "touchscreenModeCheckbox",
+					"help": "vcmi.battleOptions.touchscreenMode",
+					"callback": "touchscreenModeChanged"
+				},
+			]
 		},
 /////////////////////////////////////// Bottom section - Animation Speed and Turn Order
 		{
@@ -217,19 +159,13 @@
 		},
 		{
 			"name": "animationSpeedLabel",
-			"type": "label",
-			"font": "medium",
-			"alignment": "center",
-			"color": "yellow",
+			"type": "labelCentered",
 			"text": "core.genrltxt.393",
 			"position": {"x": 150, "y": 362}
 		},
 		{
 			"name": "animationSpeedValueLabel",
-			"type": "label",
-			"font": "medium",
-			"alignment": "center",
-			"color": "yellow",
+			"type": "labelCentered",
 			"position": {"x": 324, "y": 390}
 		},
 		{

+ 93 - 165
config/widgets/settings/generalOptionsTab.json

@@ -1,4 +1,6 @@
 {
+	"library" : "config/widgets/settings/library.json",
+
 	"items":
 	[
 		{
@@ -7,153 +9,105 @@
 			"image": "settingsWindow/lineHorizontal",
 			"rect": { "x" : 5, "y" : 289, "w": 365, "h": 3}
 		},
-		
 		{
-			"name": "settingGroupTitles",
-			"type": "labelGroup",
-			"font": "medium",
-			"alignment": "left",
-			"color": "yellow",
-			"items":
-			[
-				{
-					"position": {"x": 10, "y": 55},
-					"text": "vcmi.systemOptions.videoGroup"
-				},
-				{
-					"position": {"x": 380, "y": 55},
-					"text": "vcmi.systemOptions.audioGroup"
-				},
-				{
-					"position": {"x": 10, "y": 295},
-					"text": "vcmi.systemOptions.townsGroup"
-				}
-			]
-		},
-/////////////////////////////////////// Left section - Video Settings
-		{
-			"name": "resolutionLabel",
-			"type": "label",
-			"font": "medium",
-			"alignment": "left",
-			"color": "white",
-			"position": {"x": 45, "y": 85},
-			"text": "vcmi.systemOptions.resolutionButton.hover"
+			"type" : "labelTitle",
+			"position": {"x": 10, "y": 55},
+			"text": "vcmi.systemOptions.videoGroup"
 		},
 		{
-			"name": "resolutionButton",
-			"type": "button",
-			"position": {"x": 10, "y": 83},
-			"image": "settingsWindow/button32",
-			"help": "vcmi.systemOptions.resolutionButton",
-			"callback": "setGameResolution",
-			"items":
-			[
-				{
-					"name": "gearIcon",
-					"type": "picture",
-					"image": "settingsWindow/gear",
-					"position": {"x": 0, "y": 0 }
-				}
-			]
+			"type" : "labelTitle",
+			"position": {"x": 380, "y": 55},
+			"text": "vcmi.systemOptions.audioGroup"
 		},
-		
 		{
-			"name": "scalingLabel",
-			"type": "label",
-			"font": "medium",
-			"alignment": "left",
-			"color": "white",
-			"position": {"x": 45, "y": 115},
-			"text": "vcmi.systemOptions.scalingButton.hover"
+			"type" : "labelTitle",
+			"position": {"x": 10, "y": 295},
+			"text": "vcmi.systemOptions.townsGroup"
 		},
+/////////////////////////////////////// Left section - Video Settings
 		{
-			"name": "scalingButton",
-			"type": "button",
-			"position": {"x": 10, "y": 113},
-			"image": "settingsWindow/button32",
-			"help": "vcmi.systemOptions.scalingButton",
-			"callback": "setGameScaling",
-			"items":
-			[
+			"type" : "verticalLayout",
+			"customType" : "labelDescription",
+			"position" : {"x": 45, "y": 85},
+			"items" : [
 				{
-					"name": "gearIcon",
-					"type": "picture",
-					"image": "settingsWindow/gear",
-					"position": {"x": 0, "y": 0 }
+					"name": "resolutionLabel",
+					"text": "vcmi.systemOptions.resolutionButton.hover",
+					"created" : "desktop"
+				},
+				{
+					"name": "scalingLabel",
+					"text": "vcmi.systemOptions.scalingButton.hover"
+				},
+				{
+					"text": "vcmi.systemOptions.fullscreenBorderless.hover",
+					"created" : "desktop"
+				},
+				{
+					"text": "vcmi.systemOptions.fullscreenExclusive.hover",
+					"created" : "desktop"
+				},
+				{
+					"text": "vcmi.systemOptions.framerateButton.hover"
+				},
+				{
+					"text": "core.genrltxt.577"
 				}
 			]
 		},
-		
 		{
-			"name": "topCheckboxesLabels",
-			"type": "labelGroup",
-			"font": "medium",
-			"alignment": "left",
-			"color": "white",
-			"items":
-			[
+			"type" : "verticalLayout",
+			"customType" : "checkbox",
+			"position" : {"x": 10, "y": 83},
+			"items" : [
 				{
-					"position": {"x": 45, "y": 145},
-					"text": "vcmi.systemOptions.fullscreenButton.hover"
+					"name": "resolutionButton",
+					"type": "buttonGear",
+					"help": "vcmi.systemOptions.resolutionButton",
+					"callback": "setGameResolution",
+					"created" : "desktop"
 				},
 				{
-					"position": {"x": 45, "y": 175},
-					"text": "vcmi.systemOptions.framerateButton.hover"
+					"name": "scalingButton",
+					"type": "buttonGear",
+					"help": "vcmi.systemOptions.scalingButton",
+					"callback": "setGameScaling",
 				},
 				{
-					"position": {"x": 45, "y": 205},
-					"text": "core.genrltxt.577"
+					"name": "fullscreenBorderlessCheckbox",
+					"help": "vcmi.systemOptions.fullscreenBorderless",
+					"callback": "fullscreenBorderlessChanged"
+					"created" : "desktop"
+				},
+				{
+					"name": "fullscreenExclusiveCheckbox",
+					"help": "vcmi.systemOptions.fullscreenExclusive",
+					"callback": "fullscreenExclusiveChanged"
+					"created" : "desktop"
+				},
+				{
+					"name": "framerateCheckbox",
+					"help": "vcmi.systemOptions.framerateButton",
+					"callback": "framerateChanged"
+				},
+				{
+					"name": "spellbookAnimationCheckbox",
+					"help": "core.help.364",
+					"callback": "spellbookAnimationChanged"
 				},
-
 			]
 		},
+/////////////////////////////////////// Right section - Audio Settings
 		{
-			"name": "fullscreenCheckbox",
-			"type": "toggleButton",
-			"image": "sysopchk.def",
-			"help": "vcmi.systemOptions.fullscreenButton",
-			"position": {"x": 10, "y": 143},
-			"callback": "fullscreenChanged"
-		},
-		{
-			"name": "framerateCheckbox",
-			"type": "toggleButton",
-			"image": "sysopchk.def",
-			"help": "vcmi.systemOptions.framerateButton",
-			"position": {"x": 10, "y": 173},
-			"callback": "framerateChanged"
-		},
-		
-		{
-			"name": "spellbookAnimationCheckbox",
-			"type": "toggleButton",
-			"image": "sysopchk.def",
-			"help": "core.help.364",
-			"position": {"x": 10, "y": 203},
-			"callback": "spellbookAnimationChanged"
+			"type" : "labelCentered",
+			"position": {"x": 460, "y": 98},
+			"text": "core.genrltxt.394" // Music Volume
 		},
-
-/////////////////////////////////////// Right section - Audio Settings
 		{
-			"name": "settingAudioTitles",
-			"type": "labelGroup",
-			"font": "medium",
-			"alignment": "center",
-			"color": "yellow",
-			"items":
-			[
-				{
-					"position": {"x": 460, "y": 98},
-					"text": "core.genrltxt.394" // Music Volume
-				},
-				{
-					"position": {"x": 460, "y": 158},
-					"text": "core.genrltxt.395" // Effects volume
-				}
-			]
+			"type" : "labelCentered",
+			"position": {"x": 460, "y": 158},
+			"text": "core.genrltxt.395" // Effects volume
 		},
-
 		{
 			"name": "frameMusic",
 			"type": "picture",
@@ -162,76 +116,54 @@
 		},
 		{
 			"name": "musicSlider",
-			"type": "slider",
+			"type": "audioSlider",
 			"position": {"x": 385, "y": 115},
-			"scrollBounds" : { "x" : -4, "y" : -34, "w" : 208, "h" : 52 },
-			"size": 200,
-			"style": "brown",
-			"orientation": "horizontal",
-			"itemsVisible": 0,
-			"itemsTotal": 100,
 			"callback": "setMusic"
 		},
 		{
 			"name": "musicValueLabel",
-			"type": "label",
-			"font": "medium",
-			"alignment": "center",
-			"color": "yellow",
+			"type": "labelCentered",
 			"position": {"x": 565, "y": 98}
 		},
-		
 		{
 			"name": "frameSound",
 			"type": "picture",
 			"image": "settingsWindow/frameAudio",
 			"position": {"x": 380, "y": 140 }
 		},
-
 		{
 			"name": "soundVolumeSlider",
-			"type": "slider",
+			"type": "audioSlider",
 			"position": {"x": 385, "y": 175},
-			"scrollBounds" : { "x" : -4, "y" : -34, "w" : 208, "h" : 52 },
-			"size": 200,
-			"style": "brown",
-			"orientation": "horizontal",
-			"itemsVisible": 0,
-			"itemsTotal": 100,
 			"callback": "setVolume"
 		},
 		{
 			"name": "soundValueLabel",
-			"type": "label",
-			"font": "medium",
-			"alignment": "center",
-			"color": "yellow",
+			"type": "labelCentered",
 			"position": {"x": 565, "y": 158}
 		},
 /////////////////////////////////////// Bottom section - Towns Settings
 		{
-			"name": "townCheckboxesLabels",
-			"type": "labelGroup",
-			"font": "medium",
-			"alignment": "left",
-			"color": "white",
-			"items":
-			[
+			"type" : "verticalLayout",
+			"customType" : "labelDescription",
+			"position": {"x": 45, "y": 325},
+			"items" : [
 				{
 					"text": "vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover",
-					"position": {"x": 45, "y": 325}
 				},
 				{
 					"text": "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover",
-					"position": {"x": 45, "y": 355}
 				},
 				{
 					"text": "vcmi.otherOptions.compactTownCreatureInfo.hover",
-					"position": {"x": 45, "y": 385}
 				}
 			]
 		},
-		
+		{
+			"name": "availableCreaturesAsDwellingPicker",
+			"type": "toggleGroup",
+			"callback": "availableCreaturesAsDwellingChanged"
+		},
 		{
 			"name": "availableCreaturesAsDwellingPicker",
 			"type": "toggleGroup",
@@ -240,26 +172,22 @@
 			[
 				{
 					"index": 0,
-					"type": "toggleButton",
-					"image": "sysopchk.def",
+					"type": "checkbox",
 					"help": "vcmi.otherOptions.creatureGrowthAsDwellingLabel",
 					"position": {"x": 0, "y": 0}
 				},
 				{
 					"index": 1,
-					"type": "toggleButton",
-					"image": "sysopchk.def",
+					"type": "checkbox",
 					"help": "vcmi.otherOptions.availableCreaturesAsDwellingLabel",
 					"position": {"x": 0, "y": 30}
 				},
 			],
 			"callback": "availableCreaturesAsDwellingChanged"
 		},
-
 		{
 			"name": "compactTownCreatureInfoCheckbox",
-			"type": "toggleButton",
-			"image": "sysopchk.def",
+			"type": "checkbox",
 			"help": "vcmi.otherOptions.compactTownCreatureInfo",
 			"position": {"x": 10, "y": 383},
 			"callback": "compactTownCreatureInfoChanged"

+ 62 - 0
config/widgets/settings/library.json

@@ -0,0 +1,62 @@
+{
+	"labelTitle" : {
+		"type": "label",
+		"font": "medium",
+		"alignment": "left",
+		"color": "yellow"
+	},
+	"labelCentered" : {
+		"type": "label",
+		"font": "medium",
+		"alignment": "center",
+		"color": "yellow"
+	},
+	"labelDescription" : {
+		"type": "label",
+		"font": "medium",
+		"alignment": "left",
+		"color": "white"
+	},
+	"checkbox" : {
+		"type": "toggleButton",
+		"image": "sysopchk.def",
+	},
+	"buttonGear" : {
+		"type": "button",
+		"image": "settingsWindow/button32",
+		"items":
+		[
+			{
+				"name": "gearIcon",
+				"type": "picture",
+				"image": "settingsWindow/gear",
+				"position": {"x": 0, "y": 0 }
+			}
+		]
+	},
+	"checkboxFake" : {
+		"type": "picture",
+		"image": "settingsWindow/checkBoxEmpty"
+	},
+	"audioSlider" : {
+		"type": "slider",
+		"scrollBounds" : { "x" : -4, "y" : -34, "w" : 208, "h" : 52 },
+		"size": 200,
+		"style": "brown",
+		"orientation": "horizontal",
+		"itemsVisible": 0,
+		"itemsTotal": 100,
+	},
+	"verticalLayout" : {
+		"type" : "layout",
+		"vertical" : true,
+		"dynamic" : false,
+		"distance" : 30
+	},
+	"verticalLayout60" : {
+		"type" : "layout",
+		"vertical" : true,
+		"dynamic" : false,
+		"distance" : 60
+	}
+}