Sfoglia il codice sorgente

Integrated hotkeys with InterfaceObjectConfigurable

Ivan Savenko 2 anni fa
parent
commit
6c637dd8e6

+ 30 - 60
client/battle/BattleWindow.cpp

@@ -51,19 +51,23 @@ BattleWindow::BattleWindow(BattleInterface & owner):
 	
 	const JsonNode config(ResourceID("config/widgets/BattleWindow.json"));
 	
-	addCallback("options", std::bind(&BattleWindow::bOptionsf, this));
-	addCallback("surrender", std::bind(&BattleWindow::bSurrenderf, this));
-	addCallback("flee", std::bind(&BattleWindow::bFleef, this));
-	addCallback("autofight", std::bind(&BattleWindow::bAutofightf, this));
-	addCallback("spellbook", std::bind(&BattleWindow::bSpellf, this));
-	addCallback("wait", std::bind(&BattleWindow::bWaitf, this));
-	addCallback("defence", std::bind(&BattleWindow::bDefencef, this));
-	addCallback("consoleUp", std::bind(&BattleWindow::bConsoleUpf, this));
-	addCallback("consoleDown", std::bind(&BattleWindow::bConsoleDownf, this));
-	addCallback("tacticNext", std::bind(&BattleWindow::bTacticNextStack, this));
-	addCallback("tacticEnd", std::bind(&BattleWindow::bTacticPhaseEnd, this));
-	addCallback("alternativeAction", std::bind(&BattleWindow::bSwitchActionf, this));
-	
+	addShortcut(EShortcut::GLOBAL_OPTIONS, std::bind(&BattleWindow::bOptionsf, this));
+	addShortcut(EShortcut::BATTLE_SURRENDER, std::bind(&BattleWindow::bSurrenderf, this));
+	addShortcut(EShortcut::BATTLE_RETREAT, std::bind(&BattleWindow::bFleef, this));
+	addShortcut(EShortcut::BATTLE_AUTOCOMBAT, std::bind(&BattleWindow::bAutofightf, this));
+	addShortcut(EShortcut::BATTLE_CAST_SPELL, std::bind(&BattleWindow::bSpellf, this));
+	addShortcut(EShortcut::BATTLE_WAIT, std::bind(&BattleWindow::bWaitf, this));
+	addShortcut(EShortcut::BATTLE_DEFEND, std::bind(&BattleWindow::bDefencef, this));
+	addShortcut(EShortcut::BATTLE_CONSOLE_UP, std::bind(&BattleWindow::bConsoleUpf, this));
+	addShortcut(EShortcut::BATTLE_CONSOLE_DOWN, std::bind(&BattleWindow::bConsoleDownf, this));
+	addShortcut(EShortcut::BATTLE_TACTICS_NEXT, std::bind(&BattleWindow::bTacticNextStack, this));
+	addShortcut(EShortcut::BATTLE_TACTICS_END, std::bind(&BattleWindow::bTacticPhaseEnd, this));
+	addShortcut(EShortcut::BATTLE_SELECT_ACTION, std::bind(&BattleWindow::bSwitchActionf, this));
+
+	addShortcut(EShortcut::BATTLE_TOGGLE_QUEUE, [this](){ this->toggleQueueVisibility();});
+	addShortcut(EShortcut::BATTLE_USE_CREATURE_SPELL, [this](){ this->owner.actionsController->enterCreatureCastingMode(); });
+	addShortcut(EShortcut::GLOBAL_CANCEL, [this](){ this->owner.actionsController->endCastingSpell(); });
+
 	build(config);
 	
 	console = widget<BattleConsole>("console");
@@ -190,19 +194,7 @@ void BattleWindow::keyPressed(EShortcut key)
 		owner.openingEnd();
 		return;
 	}
-
-	if(key == EShortcut::BATTLE_TOGGLE_QUEUE)
-	{
-		toggleQueueVisibility();
-	}
-	else if(key == EShortcut::BATTLE_USE_CREATURE_SPELL)
-	{
-		owner.actionsController->enterCreatureCastingMode();
-	}
-	else if(key == EShortcut::GLOBAL_CANCEL)
-	{
-		owner.actionsController->endCastingSpell();
-	}
+	InterfaceObjectConfigurable::keyPressed(key);
 }
 
 void BattleWindow::clickRight(tribool down, bool previousState)
@@ -538,40 +530,18 @@ void BattleWindow::blockUI(bool on)
 
 	bool canWait = owner.stacksController->getActiveStack() ? !owner.stacksController->getActiveStack()->waitedThisTurn : false;
 
-	if(auto w = widget<CButton>("options"))
-		w->block(on);
-	if(auto w = widget<CButton>("flee"))
-		w->block(on || !owner.curInt->cb->battleCanFlee());
-	if(auto w = widget<CButton>("surrender"))
-		w->block(on || owner.curInt->cb->battleGetSurrenderCost() < 0);
-	if(auto w = widget<CButton>("cast"))
-		w->block(on || owner.tacticsMode || !canCastSpells);
-	if(auto w = widget<CButton>("wait"))
-		w->block(on || owner.tacticsMode || !canWait);
-	if(auto w = widget<CButton>("defence"))
-		w->block(on || owner.tacticsMode);
-	if(auto w = widget<CButton>("alternativeAction"))
-		w->block(on || owner.tacticsMode);
-	if(auto w = widget<CButton>("autofight"))
-		w->block(owner.actionsController->spellcastingModeActive());
-
-	auto btactEnd = widget<CButton>("tacticEnd");
-	auto btactNext = widget<CButton>("tacticNext");
-	if(owner.tacticsMode && btactEnd && btactNext)
-	{
-		btactNext->block(on);
-		btactEnd->block(on);
-	}
-	else
-	{
-		auto bConsoleUp = widget<CButton>("consoleUp");
-		auto bConsoleDown = widget<CButton>("consoleDown");
-		if(bConsoleUp && bConsoleDown)
-		{
-			bConsoleUp->block(on);
-			bConsoleDown->block(on);
-		}
-	}
+	setShortcutBlocked(EShortcut::GLOBAL_OPTIONS, on);
+	setShortcutBlocked(EShortcut::BATTLE_RETREAT, on || !owner.curInt->cb->battleCanFlee());
+	setShortcutBlocked(EShortcut::BATTLE_SURRENDER, on || owner.curInt->cb->battleGetSurrenderCost() < 0);
+	setShortcutBlocked(EShortcut::BATTLE_CAST_SPELL, on || owner.tacticsMode || !canCastSpells);
+	setShortcutBlocked(EShortcut::BATTLE_WAIT, on || owner.tacticsMode || !canWait);
+	setShortcutBlocked(EShortcut::BATTLE_DEFEND, on || owner.tacticsMode);
+	setShortcutBlocked(EShortcut::BATTLE_SELECT_ACTION, on || owner.tacticsMode);
+	setShortcutBlocked(EShortcut::BATTLE_AUTOCOMBAT, owner.actionsController->spellcastingModeActive());
+	setShortcutBlocked(EShortcut::BATTLE_TACTICS_END, on && owner.tacticsMode);
+	setShortcutBlocked(EShortcut::BATTLE_TACTICS_NEXT, on && owner.tacticsMode);
+	setShortcutBlocked(EShortcut::BATTLE_CONSOLE_DOWN, on && !owner.tacticsMode);
+	setShortcutBlocked(EShortcut::BATTLE_CONSOLE_UP, on && !owner.tacticsMode);
 }
 
 std::optional<uint32_t> BattleWindow::getQueueHoveredUnitId()

+ 60 - 6
client/gui/InterfaceObjectConfigurable.cpp

@@ -201,19 +201,19 @@ std::pair<std::string, std::string> InterfaceObjectConfigurable::readHintText(co
 	return result;
 }
 
-EShortcut InterfaceObjectConfigurable::readKeycode(const JsonNode & config) const
+EShortcut InterfaceObjectConfigurable::readHotkey(const JsonNode & config) const
 {
-	logGlobal->debug("Reading keycode");
+	logGlobal->debug("Reading hotkey");
 
 	if(config.getType() != JsonNode::JsonType::DATA_STRING)
 	{
-		logGlobal->error("Invalid keycode format in interface configuration! Expected string!", config.String());
+		logGlobal->error("Invalid hotket format in interface configuration! Expected string!", config.String());
 		return EShortcut::NONE;
 	}
 
 	EShortcut result = GH.shortcutsHandler().findShortcut(config.String());
 	if (result == EShortcut::NONE)
-		logGlobal->error("Invalid keycode '%s' in interface configuration!", config.String());
+		logGlobal->error("Invalid hotkey '%s' in interface configuration!", config.String());
 	return result;;
 }
 
@@ -320,11 +320,27 @@ std::shared_ptr<CButton> InterfaceObjectConfigurable::buildButton(const JsonNode
 		button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
 	}
 	if(!config["callback"].isNull())
-		button->addCallback(std::bind(callbacks.at(config["callback"].String()), 0));
+	{
+		std::string callbackName = config["callback"].String();
+
+		if (callbacks.count(callbackName) > 0)
+			button->addCallback(std::bind(callbacks.at(callbackName), 0));
+		else
+			logGlobal->error("Invalid callback '%s' in widget", callbackName );
+	}
 	if(!config["hotkey"].isNull())
 	{
 		if(config["hotkey"].getType() == JsonNode::JsonType::DATA_STRING)
-			button->assignedKey = readKeycode(config["hotkey"]);
+		{
+			button->assignedKey = readHotkey(config["hotkey"]);
+
+			auto target = shortcuts.find(button->assignedKey);
+			if (target != shortcuts.end())
+			{
+				button->addCallback(target->second.callback);
+				target->second.assignedToButton = true;
+			}
+		}
 	}
 	return button;
 }
@@ -430,3 +446,41 @@ std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(JsonNode co
 	logGlobal->error("Builder with type %s is not registered", type);
 	return nullptr;
 }
+
+void InterfaceObjectConfigurable::setShortcutBlocked(EShortcut shortcut, bool isBlocked)
+{
+	auto target = shortcuts.find(key);
+	if (target == shortcuts.end())
+		return;
+
+	target->second.blocked = isBlocked;
+
+	for	(auto & entry : widgets)
+	{
+		auto button = std::dynamic_pointer_cast<CButton>(entry.second);
+
+		if (button && button->assignedKey == shortcut)
+			button->block(isBlocked);
+	}
+}
+
+void InterfaceObjectConfigurable::addShortcut(EShortcut shortcut, std::function<void()> callback)
+{
+	assert(shortcuts.count(shortcut) == 0);
+	shortcuts[shortcut].callback = callback;
+}
+
+void InterfaceObjectConfigurable::keyPressed(EShortcut key)
+{
+	auto target = shortcuts.find(key);
+	if (target == shortcuts.end())
+		return;
+
+	if (target->second.assignedToButton)
+		return; // will be handled by button instance
+
+	if (target->second.blocked)
+		return;
+
+	target->second.callback();
+}

+ 16 - 2
client/gui/InterfaceObjectConfigurable.h

@@ -35,7 +35,14 @@ public:
 	InterfaceObjectConfigurable(const JsonNode & config, int used=0, Point offset=Point());
 
 protected:
-	
+	/// Set blocked status for all buttons assotiated with provided shortcut
+	void setShortcutBlocked(EShortcut shortcut, bool isBlocked);
+
+	/// Registers provided callback to be called whenever specified shortcut is triggered
+	void addShortcut(EShortcut shortcut, std::function<void()> callback);
+
+	void keyPressed(EShortcut key) override;
+
 	using BuilderFunction = std::function<std::shared_ptr<CIntObject>(const JsonNode &)>;
 	void registerBuilder(const std::string &, BuilderFunction);
 	
@@ -64,7 +71,7 @@ protected:
 	EFonts readFont(const JsonNode &) const;
 	std::string readText(const JsonNode &) const;
 	std::pair<std::string, std::string> readHintText(const JsonNode &) const;
-	EShortcut readKeycode(const JsonNode &) const;
+	EShortcut readHotkey(const JsonNode &) const;
 	
 	//basic widgets
 	std::shared_ptr<CPicture> buildPicture(const JsonNode &) const;
@@ -82,9 +89,16 @@ protected:
 	std::shared_ptr<CIntObject> buildWidget(JsonNode config) const;
 	
 private:
+	struct ShortcutState
+	{
+		std::function<void()> callback;
+		mutable bool assignedToButton = false;
+		bool blocked = false;
+	};
 	
 	int unnamedObjectId = 0;
 	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<EShortcut, ShortcutState> shortcuts;
 };

+ 1 - 0
client/gui/Shortcut.h

@@ -124,6 +124,7 @@ enum class EShortcut
 	BATTLE_CONSOLE_DOWN,
 	BATTLE_TACTICS_NEXT,
 	BATTLE_TACTICS_END,
+	BATTLE_SELECT_ACTION, // Alternative actions toggle
 
 	// Town screen
 	TOWN_OPEN_TAVERN,

+ 2 - 0
client/gui/ShortcutHandler.cpp

@@ -126,6 +126,7 @@ std::vector<EShortcut> ShortcutHandler::translateKeycode(SDL_Keycode key) const
 		{SDLK_SPACE,     EShortcut::BATTLE_TACTICS_NEXT       },
 		{SDLK_RETURN,    EShortcut::BATTLE_TACTICS_END        },
 		{SDLK_KP_ENTER,  EShortcut::BATTLE_TACTICS_END        },
+		{SDLK_s,         EShortcut::BATTLE_SELECT_ACTION      },
 		{SDLK_t,         EShortcut::TOWN_OPEN_TAVERN          },
 		{SDLK_SPACE,     EShortcut::TOWN_SWAP_ARMIES          },
 		{SDLK_END,       EShortcut::RECRUITMENT_MAX           },
@@ -255,6 +256,7 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
 		{"battleConsoleDown",        EShortcut::BATTLE_CONSOLE_DOWN       },
 		{"battleTacticsNext",        EShortcut::BATTLE_TACTICS_NEXT       },
 		{"battleTacticsEnd",         EShortcut::BATTLE_TACTICS_END        },
+		{"battleSelectAction",       EShortcut::BATTLE_SELECT_ACTION      },
 		{"townOpenTavern",           EShortcut::TOWN_OPEN_TAVERN          },
 		{"townSwapArmies",           EShortcut::TOWN_SWAP_ARMIES          },
 		{"recruitmentMax",           EShortcut::RECRUITMENT_MAX           },

+ 0 - 11
config/widgets/battleWindow.json

@@ -21,7 +21,6 @@
 			"position": {"x": 4, "y": 560},
 			"image": "icm003",
 			"help": "core.help.381",
-			"callback": "options",
 			"hotkey": "globalOptions"
 		},
 
@@ -31,7 +30,6 @@
 			"position": {"x": 55, "y": 560},
 			"image": "icm001",
 			"help": "core.help.379",
-			"callback": "surrender",
 			"hotkey": "battleSurrender"
 		},
 
@@ -41,7 +39,6 @@
 			"position": {"x": 106, "y": 560},
 			"image": "icm002",
 			"help": "core.help.380",
-			"callback": "flee",
 			"hotkey": "battleRetreat"
 		},
 
@@ -51,7 +48,6 @@
 			"position": {"x": 157, "y": 560},
 			"image": "icm004",
 			"help": "core.help.382",
-			"callback": "autofight",
 			"hotkey": "battleAutocombat"
 		},
 
@@ -61,7 +57,6 @@
 			"position": {"x": 646, "y": 560},
 			"image": "icm005",
 			"help": "core.help.385",
-			"callback": "spellbook",
 			"hotkey": "battleCastSpell"
 		},
 
@@ -71,7 +66,6 @@
 			"position": {"x": 697, "y": 560},
 			"image": "icm006",
 			"help": "core.help.386",
-			"callback": "wait",
 			"hotkey": "battleWait"
 		},
 
@@ -81,7 +75,6 @@
 			"position": {"x": 748, "y": 560},
 			"image": "icm007",
 			"help": "core.help.387",
-			"callback": "defence",
 			"hotkey": "battleDefend"
 		},
 
@@ -90,7 +83,6 @@
 			"name": "consoleUp",
 			"position": {"x": 625, "y": 560},
 			"image": "ComSlide",
-			"callback": "consoleUp",
 			"imageOrder": [0, 1, 0, 0],
 			"hotkey": "battleConsoleUp"
 		},
@@ -100,7 +92,6 @@
 			"name": "consoleDown",
 			"position": {"x": 625, "y": 579},
 			"image": "ComSlide",
-			"callback": "consoleDown",
 			"imageOrder": [2, 3, 2, 2],
 			"hotkey": "battleConsoleDown"
 		},
@@ -117,7 +108,6 @@
 			"name": "tacticNext",
 			"position": {"x": 213, "y": 560},
 			"image": "icm011",
-			"callback": "tacticNext",
 			"hotkey": "battleTacticsNext"
 		},
 		
@@ -126,7 +116,6 @@
 			"name": "tacticEnd",
 			"position": {"x": 419, "y": 560},
 			"image": "icm012",
-			"callback": "tacticEnd",
 			"hotkey": "battleTacticsEnd"
 		}
 	]