Browse Source

Refactoring. Regressions fixing.

SoundSSGood 1 năm trước cách đây
mục cha
commit
cc14f85445
33 tập tin đã thay đổi với 321 bổ sung262 xóa
  1. 2 2
      client/CMakeLists.txt
  2. 6 0
      client/widgets/Buttons.cpp
  3. 1 0
      client/widgets/Buttons.h
  4. 2 2
      client/widgets/CArtifactsOfHeroBackpack.cpp
  5. 2 2
      client/widgets/CArtifactsOfHeroBackpack.h
  6. 1 1
      client/widgets/CArtifactsOfHeroBase.cpp
  7. 1 1
      client/widgets/CArtifactsOfHeroBase.h
  8. 3 3
      client/widgets/CArtifactsOfHeroMarket.cpp
  9. 1 1
      client/widgets/CArtifactsOfHeroMarket.h
  10. 4 4
      client/widgets/CWindowWithArtifacts.cpp
  11. 1 1
      client/widgets/CWindowWithArtifacts.h
  12. 1 1
      client/widgets/Slider.cpp
  13. 3 1
      client/widgets/Slider.h
  14. 10 9
      client/widgets/markets/CAltarArtifacts.cpp
  15. 3 3
      client/widgets/markets/CAltarArtifacts.h
  16. 20 21
      client/widgets/markets/CAltarCreatures.cpp
  17. 6 6
      client/widgets/markets/CAltarCreatures.h
  18. 26 18
      client/widgets/markets/CArtifactsBuying.cpp
  19. 4 3
      client/widgets/markets/CArtifactsBuying.h
  20. 38 33
      client/widgets/markets/CArtifactsSelling.cpp
  21. 4 3
      client/widgets/markets/CArtifactsSelling.h
  22. 27 30
      client/widgets/markets/CFreelancerGuild.cpp
  23. 5 4
      client/widgets/markets/CFreelancerGuild.h
  24. 45 20
      client/widgets/markets/CMarketBase.cpp
  25. 26 13
      client/widgets/markets/CMarketBase.h
  26. 27 31
      client/widgets/markets/CMarketResources.cpp
  27. 5 4
      client/widgets/markets/CMarketResources.h
  28. 25 29
      client/widgets/markets/CTransferResources.cpp
  29. 5 4
      client/widgets/markets/CTransferResources.h
  30. 1 1
      client/widgets/markets/TradePanels.cpp
  31. 3 3
      client/widgets/markets/TradePanels.h
  32. 8 6
      client/windows/CMarketWindow.cpp
  33. 5 2
      client/windows/CMarketWindow.h

+ 2 - 2
client/CMakeLists.txt

@@ -130,7 +130,7 @@ set(client_SRCS
 	widgets/markets/CFreelancerGuild.cpp
 	widgets/markets/CMarketResources.cpp
 	widgets/markets/CTransferResources.cpp
-	widgets/markets/CTradeBase.cpp
+	widgets/markets/CMarketBase.cpp
 	widgets/markets/TradePanels.cpp
 
 	windows/CCastleInterface.cpp
@@ -318,7 +318,7 @@ set(client_HEADERS
 	widgets/markets/CFreelancerGuild.h
 	widgets/markets/CMarketResources.h
 	widgets/markets/CTransferResources.h
-	widgets/markets/CTradeBase.h
+	widgets/markets/CMarketBase.h
 	widgets/markets/TradePanels.h
 
 	windows/CCastleInterface.h

+ 6 - 0
client/widgets/Buttons.cpp

@@ -216,6 +216,12 @@ void CButton::setActOnDown(bool on)
 	actOnDown = on;
 }
 
+void CButton::setHelp(const std::pair<std::string, std::string> & help)
+{
+	hoverTexts[0] = help.first;
+	helpBox = help.second;
+}
+
 void CButton::block(bool on)
 {
 	if(on || getState() == EButtonState::BLOCKED) //dont change button state if unblock requested, but it's not blocked

+ 1 - 0
client/widgets/Buttons.h

@@ -98,6 +98,7 @@ public:
 	void setHoverable(bool on);
 	void setSoundDisabled(bool on);
 	void setActOnDown(bool on);
+	void setHelp(const std::pair<std::string, std::string> & help);
 
 	/// State modifiers
 	bool isBlocked();

+ 2 - 2
client/widgets/CArtifactsOfHeroBackpack.cpp

@@ -56,11 +56,11 @@ void CArtifactsOfHeroBackpack::onSliderMoved(int newVal)
 	redraw();
 }
 
-void CArtifactsOfHeroBackpack::updateBackpackSlots()
+void CArtifactsOfHeroBackpack::updateBackpackSlots(const std::optional<const ArtifactPosition> & removedSlot)
 {
 	if(backpackListBox)
 		backpackListBox->resize(getActiveSlotRowsNum());
-	CArtifactsOfHeroBase::updateBackpackSlots();
+	CArtifactsOfHeroBase::updateBackpackSlots(removedSlot);
 }
 
 size_t CArtifactsOfHeroBackpack::getActiveSlotRowsNum()

+ 2 - 2
client/widgets/CArtifactsOfHeroBackpack.h

@@ -24,8 +24,8 @@ class CArtifactsOfHeroBackpack : public CArtifactsOfHeroBase
 public:
 	CArtifactsOfHeroBackpack(size_t slotsColumnsMax, size_t slotsRowsMax);
 	CArtifactsOfHeroBackpack();
-	void onSliderMoved(int newVal);
-	void updateBackpackSlots() override;
+	void onSliderMoved(int newVal);;
+	void updateBackpackSlots(const std::optional<const ArtifactPosition> & removedSlot = std::nullopt) override;
 	size_t getActiveSlotRowsNum();
 	size_t getSlotsNum();
 

+ 1 - 1
client/widgets/CArtifactsOfHeroBase.cpp

@@ -180,7 +180,7 @@ void CArtifactsOfHeroBase::updateWornSlots()
 		updateSlot(place.first);
 }
 
-void CArtifactsOfHeroBase::updateBackpackSlots()
+void CArtifactsOfHeroBase::updateBackpackSlots(const std::optional<const ArtifactPosition> & removedSlot)
 {
 	ArtifactPosition slot = ArtifactPosition::BACKPACK_START;
 	for(const auto & artPlace : backpack)

+ 1 - 1
client/widgets/CArtifactsOfHeroBase.h

@@ -41,7 +41,7 @@ public:
 	virtual void unmarkSlots();
 	virtual ArtPlacePtr getArtPlace(const ArtifactPosition & slot);
 	virtual void updateWornSlots();
-	virtual void updateBackpackSlots();
+	virtual void updateBackpackSlots(const std::optional<const ArtifactPosition> & removedSlot = std::nullopt);
 	virtual void updateSlot(const ArtifactPosition & slot);
 	virtual const CArtifactInstance * getPickedArtifact();
 	void addGestureCallback(CArtPlace::ClickFunctor callback);

+ 3 - 3
client/widgets/CArtifactsOfHeroMarket.cpp

@@ -12,7 +12,7 @@
 
 #include "../../lib/mapObjects/CGHeroInstance.h"
 
-CArtifactsOfHeroMarket::CArtifactsOfHeroMarket(const Point & position)
+CArtifactsOfHeroMarket::CArtifactsOfHeroMarket(const Point & position, const int selectionWidth)
 {
 	init(
 		std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2),
@@ -21,9 +21,9 @@ CArtifactsOfHeroMarket::CArtifactsOfHeroMarket(const Point & position)
 		std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1));
 
 	for(const auto & [slot, artPlace] : artWorn)
-		artPlace->setSelectionWidth(2);
+		artPlace->setSelectionWidth(selectionWidth);
 	for(auto artPlace : backpack)
-		artPlace->setSelectionWidth(2);
+		artPlace->setSelectionWidth(selectionWidth);
 };
 
 void CArtifactsOfHeroMarket::scrollBackpack(bool left)

+ 1 - 1
client/widgets/CArtifactsOfHeroMarket.h

@@ -16,6 +16,6 @@ class CArtifactsOfHeroMarket : public CArtifactsOfHeroBase
 public:
 	std::function<void(CArtPlace*)> selectArtCallback;
 
-	CArtifactsOfHeroMarket(const Point & position);
+	CArtifactsOfHeroMarket(const Point & position, const int selectionWidth);
 	void scrollBackpack(bool left) override;
 };

+ 4 - 4
client/widgets/CWindowWithArtifacts.cpp

@@ -277,7 +277,7 @@ void CWindowWithArtifacts::gestureArtPlaceHero(CArtifactsOfHeroBase & artsInst,
 
 void CWindowWithArtifacts::artifactRemoved(const ArtifactLocation & artLoc)
 {
-	updateSlots();
+	updateSlots(artLoc.slot);
 }
 
 void CWindowWithArtifacts::artifactMoved(const ArtifactLocation & srcLoc, const ArtifactLocation & destLoc, bool withRedraw)
@@ -361,14 +361,14 @@ void CWindowWithArtifacts::artifactAssembled(const ArtifactLocation & artLoc)
 	updateSlots();
 }
 
-void CWindowWithArtifacts::updateSlots()
+void CWindowWithArtifacts::updateSlots(const ArtifactPosition & removedSlot)
 {
-	auto updateSlotBody = [](auto artSetWeak) -> void
+	auto updateSlotBody = [removedSlot](auto artSetWeak) -> void
 	{
 		if(const auto artSetPtr = artSetWeak.lock())
 		{
 			artSetPtr->updateWornSlots();
-			artSetPtr->updateBackpackSlots();
+			artSetPtr->updateBackpackSlots(removedSlot);
 			artSetPtr->redraw();
 		}
 	};

+ 1 - 1
client/widgets/CWindowWithArtifacts.h

@@ -46,7 +46,7 @@ protected:
 	std::vector<CArtifactsOfHeroPtr> artSets;
 	CloseCallback closeCallback;
 
-	void updateSlots();
+	void updateSlots(const ArtifactPosition & removedSlot = ArtifactPosition::PRE_FIRST);
 	std::optional<std::tuple<const CGHeroInstance*, const CArtifactInstance*>> getState();
 	std::optional<CArtifactsOfHeroPtr> findAOHbyRef(CArtifactsOfHeroBase & artsInst);
 	void markPossibleSlots();

+ 1 - 1
client/widgets/Slider.cpp

@@ -175,7 +175,7 @@ bool CSlider::receiveEvent(const Point &position, int eventType) const
 	return testTarget.isInside(position);
 }
 
-CSlider::CSlider(Point position, int totalw, const std::function<void(int)> & Moved, int Capacity, int Amount, int Value, Orientation orientation, CSlider::EStyle style)
+CSlider::CSlider(Point position, int totalw, const SliderMovingFunctor & Moved, int Capacity, int Amount, int Value, Orientation orientation, CSlider::EStyle style)
 	: Scrollable(LCLICK | DRAG, position, orientation ),
 	capacity(Capacity),
 	amount(Amount),

+ 3 - 1
client/widgets/Slider.h

@@ -75,13 +75,15 @@ public:
 	void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override;
 	void showAll(Canvas & to) override;
 
+	using SliderMovingFunctor = std::function<void(int)>;
+
 	 /// @param position coordinates of slider
 	 /// @param length length of slider ribbon, including left/right buttons
 	 /// @param Moved function that will be called whenever slider moves
 	 /// @param Capacity maximal number of visible at once elements
 	 /// @param Amount total amount of elements, including not visible
 	 /// @param Value starting position
-	CSlider(Point position, int length, const std::function<void(int)> & Moved, int Capacity, int Amount,
+	CSlider(Point position, int length, const SliderMovingFunctor & Moved, int Capacity, int Amount,
 		int Value, Orientation orientation, EStyle style = BROWN);
 	~CSlider();
 };

+ 10 - 9
client/widgets/markets/CAltarArtifacts.cpp

@@ -26,16 +26,16 @@
 #include "../../../lib/mapObjects/CGMarket.h"
 
 CAltarArtifacts::CAltarArtifacts(const IMarket * market, const CGHeroInstance * hero)
-	: CTradeBase(market, hero, [this](){return CAltarArtifacts::getSelectionParams();})
+	: CMarketBase(market, hero, [this](){return CAltarArtifacts::getSelectionParams();})
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
 
-	assert(market);
+	assert(dynamic_cast<const CGArtifactsAltar*>(market));
 	auto altarObj = dynamic_cast<const CGArtifactsAltar*>(market);
 	altarId = altarObj->id;
 	altarArtifacts = altarObj;
 
-	deal = std::make_shared<CButton>(dealButtonPos, AnimationPath::builtin("ALTSACR.DEF"),
+	deal = std::make_shared<CButton>(Point(269, 520), AnimationPath::builtin("ALTSACR.DEF"),
 		CGI->generaltexth->zelp[585], [this]() {CAltarArtifacts::makeDeal(); });
 	labels.emplace_back(std::make_shared<CLabel>(450, 32, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[477]));
 	labels.emplace_back(std::make_shared<CLabel>(302, 424, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[478]));
@@ -60,7 +60,7 @@ CAltarArtifacts::CAltarArtifacts(const IMarket * market, const CGHeroInstance *
 	offerTradePanel->updateSlotsCallback = std::bind(&CAltarArtifacts::updateAltarSlots, this);
 	offerTradePanel->moveTo(pos.topLeft() + Point(315, 52));
 
-	CTradeBase::updateSelected();
+	CMarketBase::updateSelected();
 	CAltarArtifacts::deselect();
 };
 
@@ -75,8 +75,8 @@ TExpType CAltarArtifacts::calcExpAltarForHero()
 
 void CAltarArtifacts::deselect()
 {
-	CTradeBase::deselect();
-	expForHero->setText(std::to_string(0));
+	CMarketBase::deselect();
+	CExperienceAltar::deselect();
 	tradeSlotsMap.clear();
 	// The event for removing artifacts from the altar will not be triggered. Therefore, we clean the altar immediately.
 	for(auto & slot : offerTradePanel->slots)
@@ -86,9 +86,10 @@ void CAltarArtifacts::deselect()
 	}
 }
 
-void CAltarArtifacts::updateSlots()
+void CAltarArtifacts::update()
 {
-	CExperienceAltar::updateSlots();
+	CMarketBase::update();
+	CExperienceAltar::update();
 	if(const auto art = hero->getArt(ArtifactPosition::TRANSITION_POS))
 	{
 		hRight = offerTradePanel->selectedSlot;
@@ -189,7 +190,7 @@ void CAltarArtifacts::putBackArtifacts()
 		LOCPLINT->cb->bulkMoveArtifacts(altarId, heroArts->getHero()->id, false, true, true);
 }
 
-CTradeBase::SelectionParams CAltarArtifacts::getSelectionParams() const
+CMarketBase::SelectionParams CAltarArtifacts::getSelectionParams() const
 {
 	if(hRight)
 		return std::make_tuple(

+ 3 - 3
client/widgets/markets/CAltarArtifacts.h

@@ -10,7 +10,7 @@
 #pragma once
 
 #include "../CArtifactsOfHeroAltar.h"
-#include "CTradeBase.h"
+#include "CMarketBase.h"
 
 class CAltarArtifacts : public CExperienceAltar
 {
@@ -19,7 +19,7 @@ public:
 	TExpType calcExpAltarForHero() override;
 	void deselect() override;
 	void makeDeal() override;
-	void updateSlots() override;
+	void update() override;
 	void sacrificeAll() override;
 	void sacrificeBackpack();
 	std::shared_ptr<CArtifactsOfHeroAltar> getAOHset() const;
@@ -33,7 +33,7 @@ private:
 	std::map<std::shared_ptr<CTradeableItem>, const CArtifactInstance*> tradeSlotsMap;
 
 	void updateAltarSlots();
-	CTradeBase::SelectionParams getSelectionParams() const override;
+	CMarketBase::SelectionParams getSelectionParams() const override;
 	void onSlotClickPressed(const std::shared_ptr<CTradeableItem> & altarSlot, std::shared_ptr<CTradeableItem> & hCurSlot) override;
 	TExpType calcExpCost(ArtifactID id);
 };

+ 20 - 21
client/widgets/markets/CAltarCreatures.cpp

@@ -13,7 +13,6 @@
 
 #include "../../gui/CGuiHandler.h"
 #include "../../widgets/Buttons.h"
-#include "../../widgets/Slider.h"
 #include "../../widgets/TextControls.h"
 
 #include "../../CGameInfo.h"
@@ -26,18 +25,19 @@
 #include "../../../lib/mapObjects/CGMarket.h"
 
 CAltarCreatures::CAltarCreatures(const IMarket * market, const CGHeroInstance * hero)
-	: CTradeBase(market, hero, [this](){return CAltarCreatures::getSelectionParams();})
+	: CMarketBase(market, hero, [this](){return CAltarCreatures::getSelectionParams();})
+	, CMarketSlider(std::bind(&CAltarCreatures::onOfferSliderMoved, this, _1))
 {
 	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255 - DISPOSE);
 
-	deal = std::make_shared<CButton>(dealButtonPos, AnimationPath::builtin("ALTSACR.DEF"),
+	deal = std::make_shared<CButton>(dealButtonPosWithSlider, AnimationPath::builtin("ALTSACR.DEF"),
 		CGI->generaltexth->zelp[584], [this]() {CAltarCreatures::makeDeal();});
 	labels.emplace_back(std::make_shared<CLabel>(155, 30, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW,
 		boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->getNameTranslated())));
 	labels.emplace_back(std::make_shared<CLabel>(450, 30, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[479]));
 	texts.emplace_back(std::make_unique<CTextBox>(CGI->generaltexth->allTexts[480], Rect(320, 56, 256, 40), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW));
-	offerSlider = std::make_shared<CSlider>(Point(231, 481), 137, std::bind(&CAltarCreatures::onOfferSliderMoved, this, _1), 0, 0, 0, Orientation::HORIZONTAL);
-	maxAmount = std::make_shared<CButton>(Point(147, 520), AnimationPath::builtin("IRCBTNS.DEF"), CGI->generaltexth->zelp[578], std::bind(&CSlider::scrollToMax, offerSlider));
+	offerSlider->moveTo(pos.topLeft() + Point(231, 481));
+	maxAmount->setHelp(CGI->generaltexth->zelp[578]);
 
 	unitsOnAltar.resize(GameConstants::ARMY_SIZE, 0);
 	expPerUnit.resize(GameConstants::ARMY_SIZE, 0);
@@ -81,7 +81,7 @@ void CAltarCreatures::readExpValues()
 	}
 }
 
-void CAltarCreatures::updateControls()
+void CAltarCreatures::highlightingChanged()
 {
 	int sliderAmount = 0;
 	if(hLeft)
@@ -111,19 +111,21 @@ void CAltarCreatures::updateControls()
 	if(hLeft)
 		offerSlider->scrollTo(unitsOnAltar[hLeft->serial]);
 	maxAmount->block(offerSlider->getAmount() == 0);
+	updateSelected();
 }
 
-void CAltarCreatures::updateSlots()
+void CAltarCreatures::update()
 {
-	CExperienceAltar::updateSlots();
+	CMarketBase::update();
+	CExperienceAltar::update();
 	assert(bidTradePanel->slots.size() == offerTradePanel->slots.size());
 }
 
 void CAltarCreatures::deselect()
 {
-	CTradeBase::deselect();
-	updateSelected();
-	expForHero->setText(std::to_string(0));
+	CMarketBase::deselect();
+	CExperienceAltar::deselect();
+	CMarketSlider::deselect();
 }
 
 TExpType CAltarCreatures::calcExpAltarForHero()
@@ -139,8 +141,6 @@ TExpType CAltarCreatures::calcExpAltarForHero()
 
 void CAltarCreatures::makeDeal()
 {
-	deselect();
-
 	std::vector<TradeItemSell> ids;
 	std::vector<ui32> toSacrifice;
 
@@ -163,9 +163,10 @@ void CAltarCreatures::makeDeal()
 		heroSlot->setType(EType::CREATURE_PLACEHOLDER);
 		heroSlot->subtitle->clear();
 	}
+	deselect();
 }
 
-CTradeBase::SelectionParams CAltarCreatures::getSelectionParams() const
+CMarketBase::SelectionParams CAltarCreatures::getSelectionParams() const
 {
 	std::optional<SelectionParamOneSide> bidSelected = std::nullopt;
 	std::optional<SelectionParamOneSide> offerSelected = std::nullopt;
@@ -197,7 +198,7 @@ void CAltarCreatures::sacrificeAll()
 
 	if(hRight)
 		offerSlider->scrollTo(unitsOnAltar[hRight->serial]);
-	offerTradePanel->updateSlots();
+	offerTradePanel->update();
 	updateSelected();
 
 	deal->block(calcExpAltarForHero() == 0);
@@ -218,8 +219,7 @@ void CAltarCreatures::onOfferSliderMoved(int newVal)
 	if(hRight)
 		updateAltarSlot(hRight);
 	deal->block(calcExpAltarForHero() == 0);
-	updateControls();
-	updateSelected();
+	highlightingChanged();
 	redraw();
 }
 
@@ -230,7 +230,7 @@ void CAltarCreatures::onSlotClickPressed(const std::shared_ptr<CTradeableItem> &
 
 	auto * oppositeSlot = &hLeft;
 	auto oppositePanel = bidTradePanel;
-	CTradeBase::onSlotClickPressed(newSlot, hCurSlot);
+	CMarketBase::onSlotClickPressed(newSlot, hCurSlot);
 	if(hCurSlot == hLeft)
 	{
 		oppositeSlot = &hRight;
@@ -244,8 +244,7 @@ void CAltarCreatures::onSlotClickPressed(const std::shared_ptr<CTradeableItem> &
 			break;
 		}
 	assert(oppositeNewSlot);
-	CTradeBase::onSlotClickPressed(oppositeNewSlot, *oppositeSlot);
-	updateControls();
-	updateSelected();
+	CMarketBase::onSlotClickPressed(oppositeNewSlot, *oppositeSlot);
+	highlightingChanged();
 	redraw();
 }

+ 6 - 6
client/widgets/markets/CAltarCreatures.h

@@ -9,13 +9,13 @@
  */
 #pragma once
 
-#include "CTradeBase.h"
+#include "CMarketBase.h"
 
-class CAltarCreatures : public CExperienceAltar, public CCreaturesSelling
+class CAltarCreatures : public CExperienceAltar, public CCreaturesSelling, public CMarketSlider
 {
 public:
 	CAltarCreatures(const IMarket * market, const CGHeroInstance * hero);
-	void updateSlots() override;
+	void update() override;
 	void deselect() override;
 	TExpType calcExpAltarForHero() override;
 	void makeDeal() override;
@@ -25,10 +25,10 @@ private:
 	std::vector<int> unitsOnAltar;
 	std::vector<int> expPerUnit;
 
-	CTradeBase::SelectionParams getSelectionParams() const override;
+	CMarketBase::SelectionParams getSelectionParams() const override;
 	void updateAltarSlot(const std::shared_ptr<CTradeableItem> & slot);
 	void readExpValues();
-	void updateControls();
-	void onOfferSliderMoved(int newVal);
+	void highlightingChanged();
+	void onOfferSliderMoved(int newVal) override;
 	void onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot) override;
 };

+ 26 - 18
client/widgets/markets/CArtifactsBuying.cpp

@@ -13,7 +13,6 @@
 
 #include "../../gui/CGuiHandler.h"
 #include "../../widgets/Buttons.h"
-#include "../../widgets/Slider.h"
 #include "../../widgets/TextControls.h"
 
 #include "../../CGameInfo.h"
@@ -26,16 +25,19 @@
 #include "../../../lib/mapObjects/CGTownInstance.h"
 
 CArtifactsBuying::CArtifactsBuying(const IMarket * market, const CGHeroInstance * hero)
-	: CTradeBase(market, hero, [this](){return CArtifactsBuying::getSelectionParams();})
+	: CMarketBase(market, hero, [this](){return CArtifactsBuying::getSelectionParams();})
 	, CResourcesSelling([this](const std::shared_ptr<CTradeableItem> & heroSlot){CArtifactsBuying::onSlotClickPressed(heroSlot, hLeft);})
 {
 	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255 - DISPOSE);
 
-	assert(dynamic_cast<const CGTownInstance*>(market));
-	labels.emplace_back(std::make_shared<CLabel>(299, 27, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW,
-		(*CGI->townh)[dynamic_cast<const CGTownInstance*>(market)->getFaction()]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated()));
-	deal = std::make_shared<CButton>(Point(270, 520), AnimationPath::builtin("TPMRKB.DEF"),
-		CGI->generaltexth->zelp[595], [this]() {CArtifactsBuying::makeDeal(); });
+	std::string title;
+	if(auto townMarket = dynamic_cast<const CGTownInstance*>(market))
+		title = (*CGI->townh)[townMarket->getFaction()]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated();
+	else
+		title = "Black market";	// find string allTexts!!
+	labels.emplace_back(std::make_shared<CLabel>(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, title));
+	deal = std::make_shared<CButton>(dealButtonPos, AnimationPath::builtin("TPMRKB.DEF"),
+		CGI->generaltexth->zelp[595], [this](){CArtifactsBuying::makeDeal();});
 	labels.emplace_back(std::make_shared<CLabel>(445, 148, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[168]));
 
 	// Player's resources
@@ -49,7 +51,7 @@ CArtifactsBuying::CArtifactsBuying(const IMarket * market, const CGHeroInstance
 			CArtifactsBuying::onSlotClickPressed(newSlot, hRight);
 		}, [this]()
 		{
-			CTradeBase::updateSubtitles(EMarketMode::RESOURCE_ARTIFACT);
+			CMarketBase::updateSubtitles(EMarketMode::RESOURCE_ARTIFACT);
 		}, market->availableItemsIds(EMarketMode::RESOURCE_ARTIFACT));
 	offerTradePanel->deleteSlotsCheck = [this](const std::shared_ptr<CTradeableItem> & slot)
 	{
@@ -57,8 +59,8 @@ CArtifactsBuying::CArtifactsBuying(const IMarket * market, const CGHeroInstance
 	};
 	offerTradePanel->moveTo(pos.topLeft() + Point(328, 181));
 
-	CTradeBase::updateSlots();
-	CTradeBase::deselect();
+	CMarketBase::update();
+	CMarketBase::deselect();
 }
 
 void CArtifactsBuying::makeDeal()
@@ -67,7 +69,7 @@ void CArtifactsBuying::makeDeal()
 	deselect();
 }
 
-CTradeBase::SelectionParams CArtifactsBuying::getSelectionParams() const
+CMarketBase::SelectionParams CArtifactsBuying::getSelectionParams() const
 {
 	if(hLeft && hRight)
 		return std::make_tuple(
@@ -77,12 +79,8 @@ CTradeBase::SelectionParams CArtifactsBuying::getSelectionParams() const
 		return std::make_tuple(std::nullopt, std::nullopt);
 }
 
-void CArtifactsBuying::onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot)
+void CArtifactsBuying::highlightingChanged()
 {
-	if(newSlot == hCurSlot)
-		return;
-
-	CTradeBase::onSlotClickPressed(newSlot, hCurSlot);
 	if(hLeft)
 	{
 		if(hRight)
@@ -90,8 +88,18 @@ void CArtifactsBuying::onSlotClickPressed(const std::shared_ptr<CTradeableItem>
 			market->getOffer(hLeft->id, hRight->id, bidQty, offerQty, EMarketMode::RESOURCE_ARTIFACT);
 			deal->block(LOCPLINT->cb->getResourceAmount(GameResID(hLeft->id)) >= bidQty ? false : true);
 		}
-		updateSelected();
-		offerTradePanel->updateSlots();
+		offerTradePanel->update();
 	}
+	updateSelected();
+}
+
+void CArtifactsBuying::onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot)
+{
+	assert(newSlot);
+	if(newSlot == hCurSlot)
+		return;
+
+	CMarketBase::onSlotClickPressed(newSlot, hCurSlot);
+	highlightingChanged();
 	redraw();
 }

+ 4 - 3
client/widgets/markets/CArtifactsBuying.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "CTradeBase.h"
+#include "CMarketBase.h"
 
 class CArtifactsBuying : public CResourcesSelling
 {
@@ -18,6 +18,7 @@ public:
 	void makeDeal() override;
 
 private:
-	CTradeBase::SelectionParams getSelectionParams() const override;
-	void CArtifactsBuying::onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot);
+	CMarketBase::SelectionParams getSelectionParams() const override;
+	void highlightingChanged();
+	void onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot);
 };

+ 38 - 33
client/widgets/markets/CArtifactsSelling.cpp

@@ -13,7 +13,6 @@
 
 #include "../../gui/CGuiHandler.h"
 #include "../../widgets/Buttons.h"
-#include "../../widgets/Slider.h"
 #include "../../widgets/TextControls.h"
 
 #include "../../CGameInfo.h"
@@ -25,21 +24,31 @@
 #include "../../../lib/CGeneralTextHandler.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
 #include "../../../lib/mapObjects/CGMarket.h"
+#include "../../../lib/mapObjects/CGTownInstance.h"
 
 CArtifactsSelling::CArtifactsSelling(const IMarket * market, const CGHeroInstance * hero)
-	: CTradeBase(market, hero, [this](){return CArtifactsSelling::getSelectionParams();})
+	: CMarketBase(market, hero, [this](){return CArtifactsSelling::getSelectionParams();})
 	, CResourcesBuying(
 		[this](const std::shared_ptr<CTradeableItem> & resSlot){CArtifactsSelling::onSlotClickPressed(resSlot, hRight);},
-		[this](){CTradeBase::updateSubtitles(EMarketMode::ARTIFACT_RESOURCE);})
+		[this](){CMarketBase::updateSubtitles(EMarketMode::ARTIFACT_RESOURCE);})
 {
 	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255 - DISPOSE);
 
-	deal = std::make_shared<CButton>(Point(270, 520), AnimationPath::builtin("TPMRKB.DEF"),
+	labels.emplace_back(std::make_shared<CLabel>(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW,
+		(*CGI->townh)[dynamic_cast<const CGTownInstance*>(market)->getFaction()]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated()));
+	labels.push_back(std::make_shared<CLabel>(155, 56, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[271]) % hero->getNameTranslated())));
+	deal = std::make_shared<CButton>(dealButtonPos, AnimationPath::builtin("TPMRKB.DEF"),
 		CGI->generaltexth->zelp[595], [this](){CArtifactsSelling::makeDeal();});
 	bidSelectedSlot = std::make_shared<CTradeableItem>(Rect(Point(123, 470), Point(69, 66)), EType::ARTIFACT_TYPE, 0, 0);
 
+	// Market resources panel
+	assert(offerTradePanel);
+	offerTradePanel->moveTo(pos.topLeft() + Point(326, 184));
+	offerTradePanel->selectedSlot->moveTo(pos.topLeft() + Point(409, 473));
+	offerTradePanel->selectedSlot->subtitle->moveBy(Point(0, 1));
+	
 	// Hero's artifacts
-	heroArts = std::make_shared<CArtifactsOfHeroMarket>(Point(-361, 46));
+	heroArts = std::make_shared<CArtifactsOfHeroMarket>(Point(-361, 46), offerTradePanel->selectionWidth);
 	heroArts->setHero(hero);
 	heroArts->selectArtCallback = [this](CArtPlace * artPlace)
 	{
@@ -49,29 +58,18 @@ CArtifactsSelling::CArtifactsSelling(const IMarket * market, const CGHeroInstanc
 		bidSelectedSlot->setID(artForTrade->getTypeId().num);
 		hLeft = bidSelectedSlot;
 		selectedHeroSlot = artPlace->slot;
-		if(hRight)
-		{	// dublicate
-			this->market->getOffer(hLeft->id, hRight->id, bidQty, offerQty, EMarketMode::ARTIFACT_RESOURCE);
-			deal->block(false);
-		}
-		CArtifactsSelling::updateSelected();
-		offerTradePanel->updateSlots();
+		CArtifactsSelling::highlightingChanged();
+		offerTradePanel->update();
 		redraw();
 	};
 
-	// Market resources panel
-	assert(offerTradePanel);
-	offerTradePanel->moveTo(pos.topLeft() + Point(326, 184));
-	offerTradePanel->selectedSlot->moveTo(pos.topLeft() + Point(409, 473));
-	offerTradePanel->selectedSlot->subtitle->moveBy(Point(0, 1));
-	
 	CArtifactsSelling::updateSelected();
 	CArtifactsSelling::deselect();
 }
 
 void CArtifactsSelling::deselect()
 {
-	CTradeBase::deselect();
+	CMarketBase::deselect();
 	selectedHeroSlot = ArtifactPosition::PRE_FIRST;
 	heroArts->unmarkSlots();
 }
@@ -85,7 +83,7 @@ void CArtifactsSelling::makeDeal()
 
 void CArtifactsSelling::updateSelected()
 {
-	CTradeBase::updateSelected();
+	CMarketBase::updateSelected();
 
 	if(hLeft && hRight)
 	{
@@ -101,11 +99,16 @@ void CArtifactsSelling::updateSelected()
 	}
 }
 
-void CArtifactsSelling::updateSlots()
+void CArtifactsSelling::update()
 {
-	CTradeBase::updateSlots();
+	CMarketBase::update();
 	if(selectedHeroSlot != ArtifactPosition::PRE_FIRST)
 	{
+		if(selectedHeroSlot.num >= ArtifactPosition::BACKPACK_START + hero->artifactsInBackpack.size())
+		{
+			selectedHeroSlot = ArtifactPosition::BACKPACK_START;
+			deselect();
+		}
 		if(hero->getArt(selectedHeroSlot) == nullptr)
 			deselect();
 	}
@@ -116,7 +119,7 @@ std::shared_ptr<CArtifactsOfHeroMarket> CArtifactsSelling::getAOHset() const
 	return heroArts;
 }
 
-CTradeBase::SelectionParams CArtifactsSelling::getSelectionParams() const
+CMarketBase::SelectionParams CArtifactsSelling::getSelectionParams() const
 {
 	if(hLeft && hRight)
 		return std::make_tuple(
@@ -127,21 +130,23 @@ CTradeBase::SelectionParams CArtifactsSelling::getSelectionParams() const
 		return std::make_tuple(std::nullopt, std::nullopt);
 }
 
+void CArtifactsSelling::highlightingChanged()
+{
+	if(hLeft && hRight)
+	{
+		market->getOffer(hLeft->id, hRight->id, bidQty, offerQty, EMarketMode::ARTIFACT_RESOURCE);
+		deal->block(false);
+	}
+	updateSelected();
+}
+
 void CArtifactsSelling::onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot)
 {
 	assert(newSlot);
 	if(newSlot == hCurSlot)
 		return;
 
-	CTradeBase::onSlotClickPressed(newSlot, hCurSlot);
-	if(hLeft)
-	{
-		if(hRight)
-		{
-			market->getOffer(hLeft->id, hRight->id, bidQty, offerQty, EMarketMode::ARTIFACT_RESOURCE);
-			deal->block(false);
-		}
-		updateSelected();
-	}
+	CMarketBase::onSlotClickPressed(newSlot, hCurSlot);
+	highlightingChanged();
 	redraw();
 }

+ 4 - 3
client/widgets/markets/CArtifactsSelling.h

@@ -10,7 +10,7 @@
 #pragma once
 
 #include "../CArtifactsOfHeroMarket.h"
-#include "CTradeBase.h"
+#include "CMarketBase.h"
 
 class CArtifactsSelling : public CResourcesBuying
 {
@@ -19,7 +19,7 @@ public:
 	void deselect() override;
 	void makeDeal() override;
 	void updateSelected() override;
-	void updateSlots() override;
+	void update() override;
 	std::shared_ptr<CArtifactsOfHeroMarket> getAOHset() const;
 
 private:
@@ -28,6 +28,7 @@ private:
 	std::shared_ptr<CTradeableItem> bidSelectedSlot;
 	ArtifactPosition selectedHeroSlot;
 
-	CTradeBase::SelectionParams getSelectionParams() const override;
+	CMarketBase::SelectionParams getSelectionParams() const override;
+	void highlightingChanged();
 	void onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot) override;
 };

+ 27 - 30
client/widgets/markets/CFreelancerGuild.cpp

@@ -13,7 +13,6 @@
 
 #include "../../gui/CGuiHandler.h"
 #include "../../widgets/Buttons.h"
-#include "../../widgets/Slider.h"
 #include "../../widgets/TextControls.h"
 
 #include "../../CGameInfo.h"
@@ -27,25 +26,21 @@
 #include "../../../lib/mapObjects/CGTownInstance.h"
 
 CFreelancerGuild::CFreelancerGuild(const IMarket * market, const CGHeroInstance * hero)
-	: CTradeBase(market, hero, [this](){return CFreelancerGuild::getSelectionParams();})
+	: CMarketBase(market, hero, [this](){return CFreelancerGuild::getSelectionParams();})
 	, CResourcesBuying(
 		[this](const std::shared_ptr<CTradeableItem> & heroSlot){CFreelancerGuild::onSlotClickPressed(heroSlot, hLeft);},
-		[this](){CTradeBase::updateSubtitles(EMarketMode::CREATURE_RESOURCE);})
+		[this](){CMarketBase::updateSubtitles(EMarketMode::CREATURE_RESOURCE);})
+	, CMarketSlider([this](int newVal){CMarketSlider::onOfferSliderMoved(newVal);})
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
 
-	labels.emplace_back(std::make_shared<CLabel>(299, 27, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW,
+	labels.emplace_back(std::make_shared<CLabel>(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW,
 		(*CGI->townh)[ETownType::STRONGHOLD]->town->buildings[BuildingID::FREELANCERS_GUILD]->getNameTranslated()));
 	labels.emplace_back(std::make_shared<CLabel>(155, 103, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE,
 		boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->getNameTranslated())));
-	deal = std::make_shared<CButton>(Point(306, 520), AnimationPath::builtin("TPMRKB.DEF"),
+	deal = std::make_shared<CButton>(dealButtonPosWithSlider, AnimationPath::builtin("TPMRKB.DEF"),
 		CGI->generaltexth->zelp[595], [this]() {CFreelancerGuild::makeDeal();});
-	maxAmount = std::make_shared<CButton>(Point(228, 520), AnimationPath::builtin("IRCBTNS.DEF"), CGI->generaltexth->zelp[596],
-		[this]() {offerSlider->scrollToMax();});
-	offerSlider = std::make_shared<CSlider>(Point(232, 489), 137, [this](int newVal)
-		{
-			CFreelancerGuild::onOfferSliderMoved(newVal);
-		}, 0, 0, 0, Orientation::HORIZONTAL);
+	offerSlider->moveTo(pos.topLeft() + Point(232, 489));
 
 	// Hero creatures panel
 	assert(bidTradePanel);
@@ -70,7 +65,13 @@ CFreelancerGuild::CFreelancerGuild(const IMarket * market, const CGHeroInstance
 			};
 		});
 
-	CTradeBase::deselect();
+	CFreelancerGuild::deselect();
+}
+
+void CFreelancerGuild::deselect()
+{
+	CMarketBase::deselect();
+	CMarketSlider::deselect();
 }
 
 void CFreelancerGuild::makeDeal()
@@ -82,7 +83,7 @@ void CFreelancerGuild::makeDeal()
 	}
 }
 
-CTradeBase::SelectionParams CFreelancerGuild::getSelectionParams() const
+CMarketBase::SelectionParams CFreelancerGuild::getSelectionParams() const
 {
 	if(hLeft && hRight)
 		return std::make_tuple(
@@ -92,22 +93,8 @@ CTradeBase::SelectionParams CFreelancerGuild::getSelectionParams() const
 		return std::make_tuple(std::nullopt, std::nullopt);
 }
 
-void CFreelancerGuild::onOfferSliderMoved(int newVal)
-{
-	if(hLeft && hRight)
-	{
-		offerSlider->scrollTo(newVal);
-		updateSelected();
-		redraw();
-	}
-}
-
-void CFreelancerGuild::onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot)
+void CFreelancerGuild::highlightingChanged()
 {
-	if(newSlot == hCurSlot)
-		return;
-
-	CTradeBase::onSlotClickPressed(newSlot, hCurSlot);
 	if(hLeft)
 	{
 		if(hRight)
@@ -119,8 +106,18 @@ void CFreelancerGuild::onSlotClickPressed(const std::shared_ptr<CTradeableItem>
 			maxAmount->block(false);
 			deal->block(false);
 		}
-		updateSelected();
-		offerTradePanel->updateSlots();
+		offerTradePanel->update();
 	}
+	updateSelected();
+}
+
+void CFreelancerGuild::onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot)
+{
+	assert(newSlot);
+	if(newSlot == hCurSlot)
+		return;
+
+	CMarketBase::onSlotClickPressed(newSlot, hCurSlot);
+	highlightingChanged();
 	redraw();
 }

+ 5 - 4
client/widgets/markets/CFreelancerGuild.h

@@ -9,16 +9,17 @@
  */
 #pragma once
 
-#include "CTradeBase.h"
+#include "CMarketBase.h"
 
-class CFreelancerGuild : public CCreaturesSelling , public CResourcesBuying
+class CFreelancerGuild : public CCreaturesSelling , public CResourcesBuying, public CMarketSlider
 {
 public:
 	CFreelancerGuild(const IMarket * market, const CGHeroInstance * hero);
+	void deselect() override;
 	void makeDeal() override;
 
 private:
-	CTradeBase::SelectionParams getSelectionParams() const override;
-	void onOfferSliderMoved(int newVal);
+	CMarketBase::SelectionParams getSelectionParams() const override;
+	void highlightingChanged();
 	void onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot) override;
 };

+ 45 - 20
client/widgets/markets/CTradeBase.cpp → client/widgets/markets/CMarketBase.cpp

@@ -1,5 +1,5 @@
 /*
- * CTradeBase.cpp, part of VCMI engine
+ * CMarketBase.cpp, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -8,14 +8,13 @@
  *
  */
 #include "StdInc.h"
-#include "CTradeBase.h"
+#include "CMarketBase.h"
 
 #include "../MiscWidgets.h"
 
 #include "../Images.h"
 #include "../../gui/CGuiHandler.h"
 #include "../../widgets/Buttons.h"
-#include "../../widgets/Slider.h"
 #include "../../widgets/TextControls.h"
 
 #include "../../CGameInfo.h"
@@ -28,14 +27,14 @@
 #include "../../../lib/CHeroHandler.h"
 #include "../../../lib/mapObjects/CGMarket.h"
 
-CTradeBase::CTradeBase(const IMarket * market, const CGHeroInstance * hero, const SelectionParamsFunctor & getParamsCallback)
+CMarketBase::CMarketBase(const IMarket * market, const CGHeroInstance * hero, const SelectionParamsFunctor & getParamsCallback)
 	: market(market)
 	, hero(hero)
 	, selectionParamsCallback(getParamsCallback)
 {
 }
 
-void CTradeBase::deselect()
+void CMarketBase::deselect()
 {
 	if(hLeft)
 		hLeft->selectSlot(false);
@@ -43,19 +42,12 @@ void CTradeBase::deselect()
 		hRight->selectSlot(false);
 	hLeft = hRight = nullptr;
 	deal->block(true);
-	if(maxAmount)
-		maxAmount->block(true);
-	if(offerSlider)
-	{
-		offerSlider->scrollTo(0);
-		offerSlider->block(true);
-	}
 	bidQty = 0;
 	offerQty = 0;
 	updateSelected();
 }
 
-void CTradeBase::onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot)
+void CMarketBase::onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot)
 {
 	if(newSlot == hCurSlot)
 		return;
@@ -66,15 +58,15 @@ void CTradeBase::onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newS
 	newSlot->selectSlot(true);
 }
 
-void CTradeBase::updateSlots()
+void CMarketBase::update()
 {
 	if(bidTradePanel)
-		bidTradePanel->updateSlots();
+		bidTradePanel->update();
 	if(offerTradePanel)
-		offerTradePanel->updateSlots();
+		offerTradePanel->update();
 }
 
-void CTradeBase::updateSubtitles(EMarketMode marketMode)
+void CMarketBase::updateSubtitles(EMarketMode marketMode)
 {
 	if(hLeft)
 		for(const auto & slot : offerTradePanel->slots)
@@ -88,7 +80,7 @@ void CTradeBase::updateSubtitles(EMarketMode marketMode)
 		offerTradePanel->clearSubtitles();
 };
 
-void CTradeBase::updateSelected()
+void CMarketBase::updateSelected()
 {
 	const auto updateSelectedBody = [](std::shared_ptr<TradePanelBase> & tradePanel, const std::optional<const SelectionParamOneSide> & params)
 	{
@@ -125,9 +117,13 @@ CExperienceAltar::CExperienceAltar()
 	expForHero = std::make_shared<CLabel>(76, 545, FONT_SMALL, ETextAlignment::CENTER);
 }
 
-void CExperienceAltar::updateSlots()
+void CExperienceAltar::deselect()
+{
+	expForHero->setText(std::to_string(0));
+}
+
+void CExperienceAltar::update()
 {
-	CTradeBase::updateSlots();
 	expToLevel->setText(std::to_string(CGI->heroh->reqExp(CGI->heroh->level(hero->exp) + 1) - hero->exp));
 }
 
@@ -180,3 +176,32 @@ void CResourcesSelling::updateSubtitles()
 	for(const auto & slot : bidTradePanel->slots)
 		slot->subtitle->setText(std::to_string(LOCPLINT->cb->getResourceAmount(static_cast<EGameResID>(slot->serial))));
 }
+
+CMarketSlider::CMarketSlider(const CSlider::SliderMovingFunctor & movingCallback)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
+
+	offerSlider = std::make_shared<CSlider>(Point(230, 489), 137, movingCallback, 0, 0, 0, Orientation::HORIZONTAL);
+	maxAmount = std::make_shared<CButton>(Point(228, 520), AnimationPath::builtin("IRCBTNS.DEF"), CGI->generaltexth->zelp[596],
+		[this]()
+		{
+			offerSlider->scrollToMax();
+		});
+}
+
+void CMarketSlider::deselect()
+{
+	maxAmount->block(true);
+	offerSlider->scrollTo(0);
+	offerSlider->block(true);
+}
+
+void CMarketSlider::onOfferSliderMoved(int newVal)
+{
+	if(hLeft && hRight)
+	{
+		offerSlider->scrollTo(newVal);
+		updateSelected();
+		redraw();
+	}
+}

+ 26 - 13
client/widgets/markets/CTradeBase.h → client/widgets/markets/CMarketBase.h

@@ -1,5 +1,5 @@
 /*
- * CTradeBase.h, part of VCMI engine
+ * CMarketBase.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -10,6 +10,7 @@
 #pragma once
 
 #include "TradePanels.h"
+#include "../../widgets/Slider.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -21,7 +22,7 @@ VCMI_LIB_NAMESPACE_END
 class CButton;
 class CSlider;
 
-class CTradeBase : public CIntObject
+class CMarketBase : public CIntObject
 {
 public:
 	struct SelectionParamOneSide
@@ -42,42 +43,42 @@ public:
 	std::shared_ptr<CTradeableItem> hLeft;
 	std::shared_ptr<CTradeableItem> hRight;
 	std::shared_ptr<CButton> deal;
-	std::shared_ptr<CSlider> offerSlider;
-	std::shared_ptr<CButton> maxAmount;
 	std::vector<std::shared_ptr<CLabel>> labels;
 	std::vector<std::shared_ptr<CTextBox>> texts;
 	SelectionParamsFunctor selectionParamsCallback;
 	int bidQty;
 	int offerQty;
+	const Point dealButtonPos = Point(270, 520);
+	const Point titlePos = Point(299, 27);
 
-	CTradeBase(const IMarket * market, const CGHeroInstance * hero, const SelectionParamsFunctor & getParamsCallback);
+	CMarketBase(const IMarket * market, const CGHeroInstance * hero, const SelectionParamsFunctor & getParamsCallback);
 	virtual void makeDeal() = 0;
 	virtual void deselect();
-	virtual void updateSlots();
+	virtual void update();
 
 protected:
 	virtual void onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot);
 	virtual void updateSubtitles(EMarketMode marketMode);
 	virtual void updateSelected();
-	virtual CTradeBase::SelectionParams getSelectionParams() const = 0;
+	virtual CMarketBase::SelectionParams getSelectionParams() const = 0;
 };
 
 // Market subclasses
-class CExperienceAltar : virtual public CTradeBase
+class CExperienceAltar : virtual public CMarketBase
 {
 public:
 	std::shared_ptr<CLabel> expToLevel;
 	std::shared_ptr<CLabel> expForHero;
 	std::shared_ptr<CButton> sacrificeAllButton;
-	const Point dealButtonPos = Point(269, 520);
 
 	CExperienceAltar();
-	void updateSlots() override;
+	void deselect() override;
+	void update() override;
 	virtual void sacrificeAll() = 0;
 	virtual TExpType calcExpAltarForHero() = 0;
 };
 
-class CCreaturesSelling : virtual public CTradeBase
+class CCreaturesSelling : virtual public CMarketBase
 {
 public:
 	CCreaturesSelling();
@@ -85,16 +86,28 @@ public:
 	void updateSubtitles();
 };
 
-class CResourcesBuying : virtual public CTradeBase
+class CResourcesBuying : virtual public CMarketBase
 {
 public:
 	CResourcesBuying(const CTradeableItem::ClickPressedFunctor & clickPressedCallback,
 		const TradePanelBase::UpdateSlotsFunctor & updSlotsCallback);
 };
 
-class CResourcesSelling : virtual public CTradeBase
+class CResourcesSelling : virtual public CMarketBase
 {
 public:
 	CResourcesSelling(const CTradeableItem::ClickPressedFunctor & clickPressedCallback);
 	void updateSubtitles();
 };
+
+class CMarketSlider : virtual public CMarketBase
+{
+public:
+	std::shared_ptr<CSlider> offerSlider;
+	std::shared_ptr<CButton> maxAmount;
+	const Point dealButtonPosWithSlider = Point(306, 520);
+
+	CMarketSlider(const CSlider::SliderMovingFunctor & movingCallback);
+	void deselect() override;
+	virtual void onOfferSliderMoved(int newVal);
+};

+ 27 - 31
client/widgets/markets/CMarketResources.cpp

@@ -13,7 +13,6 @@
 
 #include "../../gui/CGuiHandler.h"
 #include "../../widgets/Buttons.h"
-#include "../../widgets/Slider.h"
 #include "../../widgets/TextControls.h"
 
 #include "../../CGameInfo.h"
@@ -25,23 +24,18 @@
 #include "../../../lib/mapObjects/CGMarket.h"
 
 CMarketResources::CMarketResources(const IMarket * market, const CGHeroInstance * hero)
-	: CTradeBase(market, hero, [this](){return CMarketResources::getSelectionParams();})
+	: CMarketBase(market, hero, [this](){return CMarketResources::getSelectionParams();})
 	, CResourcesBuying(
 		[this](const std::shared_ptr<CTradeableItem> & resSlot){CMarketResources::onSlotClickPressed(resSlot, hRight);},
 		[this](){CMarketResources::updateSubtitles();})
 	, CResourcesSelling([this](const std::shared_ptr<CTradeableItem> & heroSlot){CMarketResources::onSlotClickPressed(heroSlot, hLeft);})
+	, CMarketSlider([this](int newVal){CMarketSlider::onOfferSliderMoved(newVal);})
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
 
-	labels.emplace_back(std::make_shared<CLabel>(299, 27, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[158]));
-	deal = std::make_shared<CButton>(Point(306, 520), AnimationPath::builtin("TPMRKB.DEF"),
+	labels.emplace_back(std::make_shared<CLabel>(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[158]));
+	deal = std::make_shared<CButton>(dealButtonPosWithSlider, AnimationPath::builtin("TPMRKB.DEF"),
 		CGI->generaltexth->zelp[595], [this]() {CMarketResources::makeDeal(); });
-	maxAmount = std::make_shared<CButton>(Point(228, 520), AnimationPath::builtin("IRCBTNS.DEF"), CGI->generaltexth->zelp[596],
-		[this]() {offerSlider->scrollToMax(); });
-	offerSlider = std::make_shared<CSlider>(Point(230, 489), 137, [this](int newVal)
-		{
-			CMarketResources::onOfferSliderMoved(newVal);
-		}, 0, 0, 0, Orientation::HORIZONTAL);
 
 	// Player's resources
 	assert(bidTradePanel);
@@ -57,8 +51,14 @@ CMarketResources::CMarketResources(const IMarket * market, const CGHeroInstance
 			};
 		});
 
-	CTradeBase::updateSlots();
-	CTradeBase::deselect();
+	CMarketBase::update();
+	CMarketResources::deselect();
+}
+
+void CMarketResources::deselect()
+{
+	CMarketBase::deselect();
+	CMarketSlider::deselect();
 }
 
 void CMarketResources::makeDeal()
@@ -70,7 +70,7 @@ void CMarketResources::makeDeal()
 	}
 }
 
-CTradeBase::SelectionParams CMarketResources::getSelectionParams() const
+CMarketBase::SelectionParams CMarketResources::getSelectionParams() const
 {
 	if(hLeft && hRight && hLeft->id != hRight->id)
 		return std::make_tuple(
@@ -80,22 +80,8 @@ CTradeBase::SelectionParams CMarketResources::getSelectionParams() const
 		return std::make_tuple(std::nullopt, std::nullopt);
 }
 
-void CMarketResources::onOfferSliderMoved(int newVal)
-{
-	if(hLeft && hRight)
-	{
-		offerSlider->scrollTo(newVal);
-		updateSelected();
-		redraw();
-	}
-}
-
-void CMarketResources::onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot)
+void CMarketResources::highlightingChanged()
 {
-	if(newSlot == hCurSlot)
-		return;
-
-	CTradeBase::onSlotClickPressed(newSlot, hCurSlot);
 	if(hLeft)
 	{
 		if(hRight)
@@ -108,15 +94,25 @@ void CMarketResources::onSlotClickPressed(const std::shared_ptr<CTradeableItem>
 			maxAmount->block(isControlsBlocked);
 			deal->block(isControlsBlocked);
 		}
-		updateSelected();
-		offerTradePanel->updateSlots();
+		offerTradePanel->update();
 	}
+	updateSelected();
+}
+
+void CMarketResources::onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot)
+{
+	assert(newSlot);
+	if(newSlot == hCurSlot)
+		return;
+
+	CMarketBase::onSlotClickPressed(newSlot, hCurSlot);
+	highlightingChanged();
 	redraw();
 }
 
 void CMarketResources::updateSubtitles()
 {
-	CTradeBase::updateSubtitles(EMarketMode::RESOURCE_RESOURCE);
+	CMarketBase::updateSubtitles(EMarketMode::RESOURCE_RESOURCE);
 	if(hLeft)
 		offerTradePanel->slots[hLeft->serial]->subtitle->setText(CGI->generaltexth->allTexts[164]); // n/a
 }

+ 5 - 4
client/widgets/markets/CMarketResources.h

@@ -9,17 +9,18 @@
  */
 #pragma once
 
-#include "CTradeBase.h"
+#include "CMarketBase.h"
 
-class CMarketResources : public CResourcesSelling, public CResourcesBuying
+class CMarketResources : public CResourcesSelling, public CResourcesBuying, public CMarketSlider
 {
 public:
 	CMarketResources(const IMarket * market, const CGHeroInstance * hero);
+	void deselect() override;
 	void makeDeal() override;
 
 private:
-	CTradeBase::SelectionParams getSelectionParams() const override;
-	void onOfferSliderMoved(int newVal);
+	CMarketBase::SelectionParams getSelectionParams() const override;
+	void highlightingChanged();
 	void onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot);
 	void updateSubtitles();
 };

+ 25 - 29
client/widgets/markets/CTransferResources.cpp

@@ -13,7 +13,6 @@
 
 #include "../../gui/CGuiHandler.h"
 #include "../../widgets/Buttons.h"
-#include "../../widgets/Slider.h"
 #include "../../widgets/TextControls.h"
 
 #include "../../CGameInfo.h"
@@ -24,21 +23,16 @@
 #include "../../../lib/CGeneralTextHandler.h"
 
 CTransferResources::CTransferResources(const IMarket * market, const CGHeroInstance * hero)
-	: CTradeBase(market, hero, [this](){return CTransferResources::getSelectionParams();})
+	: CMarketBase(market, hero, [this](){return CTransferResources::getSelectionParams();})
 	, CResourcesSelling([this](const std::shared_ptr<CTradeableItem> & heroSlot){CTransferResources::onSlotClickPressed(heroSlot, hLeft);})
+	, CMarketSlider([this](int newVal){CMarketSlider::onOfferSliderMoved(newVal);})
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
 
-	labels.emplace_back(std::make_shared<CLabel>(299, 27, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[158]));
+	labels.emplace_back(std::make_shared<CLabel>(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[158]));
 	labels.emplace_back(std::make_shared<CLabel>(445, 56, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[169]));
-	deal = std::make_shared<CButton>(Point(306, 520), AnimationPath::builtin("TPMRKB.DEF"),
+	deal = std::make_shared<CButton>(dealButtonPosWithSlider, AnimationPath::builtin("TPMRKB.DEF"),
 		CGI->generaltexth->zelp[595], [this]() {CTransferResources::makeDeal();});
-	maxAmount = std::make_shared<CButton>(Point(228, 520), AnimationPath::builtin("IRCBTNS.DEF"), CGI->generaltexth->zelp[596],
-		[this]() {offerSlider->scrollToMax();});
-	offerSlider = std::make_shared<CSlider>(Point(230, 489), 137, [this](int newVal)
-		{
-			CTransferResources::onOfferSliderMoved(newVal);
-		}, 0, 0, 0, Orientation::HORIZONTAL);
 
 	// Player's resources
 	assert(bidTradePanel);
@@ -51,10 +45,16 @@ CTransferResources::CTransferResources(const IMarket * market, const CGHeroInsta
 		});
 	offerTradePanel->moveTo(pos.topLeft() + Point(333, 84));
 
-	CTradeBase::updateSlots();
+	CMarketBase::update();
 	CTransferResources::deselect();
 }
 
+void CTransferResources::deselect()
+{
+	CMarketBase::deselect();
+	CMarketSlider::deselect();
+}
+
 void CTransferResources::makeDeal()
 {
 	if(auto toTrade = offerSlider->getValue(); toTrade != 0)
@@ -64,7 +64,7 @@ void CTransferResources::makeDeal()
 	}
 }
 
-CTradeBase::SelectionParams CTransferResources::getSelectionParams() const
+CMarketBase::SelectionParams CTransferResources::getSelectionParams() const
 {
 	if(hLeft && hRight)
 		return std::make_tuple(
@@ -74,22 +74,8 @@ CTradeBase::SelectionParams CTransferResources::getSelectionParams() const
 		return std::make_tuple(std::nullopt, std::nullopt);
 }
 
-void CTransferResources::onOfferSliderMoved(int newVal)
-{
-	if(hLeft && hRight)
-	{
-		offerSlider->scrollTo(newVal);
-		updateSelected();
-		redraw();
-	}
-}
-
-void CTransferResources::onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot)
+void CTransferResources::highlightingChanged()
 {
-	if(newSlot == hCurSlot)
-		return;
-
-	CTradeBase::onSlotClickPressed(newSlot, hCurSlot);
 	if(hLeft)
 	{
 		if(hRight)
@@ -100,8 +86,18 @@ void CTransferResources::onSlotClickPressed(const std::shared_ptr<CTradeableItem
 			maxAmount->block(false);
 			deal->block(false);
 		}
-		updateSelected();
-		offerTradePanel->updateSlots();
+		offerTradePanel->update();
 	}
+	updateSelected();
+}
+
+void CTransferResources::onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot)
+{
+	assert(newSlot);
+	if(newSlot == hCurSlot)
+		return;
+
+	CMarketBase::onSlotClickPressed(newSlot, hCurSlot);
+	highlightingChanged();
 	redraw();
 }

+ 5 - 4
client/widgets/markets/CTransferResources.h

@@ -9,16 +9,17 @@
  */
 #pragma once
 
-#include "CTradeBase.h"
+#include "CMarketBase.h"
 
-class CTransferResources : public CResourcesSelling
+class CTransferResources : public CResourcesSelling, public CMarketSlider
 {
 public:
 	CTransferResources(const IMarket * market, const CGHeroInstance * hero);
+	void deselect() override;
 	void makeDeal() override;
 
 private:
-	CTradeBase::SelectionParams getSelectionParams() const override;
-	void onOfferSliderMoved(int newVal);
+	CMarketBase::SelectionParams getSelectionParams() const override;
+	void highlightingChanged();
 	void onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot);
 };

+ 1 - 1
client/widgets/markets/TradePanels.cpp

@@ -196,7 +196,7 @@ void CTradeableItem::showPopupWindow(const Point & cursorPosition)
 	}
 }
 
-void TradePanelBase::updateSlots()
+void TradePanelBase::update()
 {
 	if(deleteSlotsCheck)
 		slots.erase(std::remove_if(slots.begin(), slots.end(), deleteSlotsCheck), slots.end());

+ 3 - 3
client/widgets/markets/TradePanels.h

@@ -51,11 +51,11 @@ public:
 	std::vector<std::shared_ptr<CTradeableItem>> slots;
 	UpdateSlotsFunctor updateSlotsCallback;
 	DeleteSlotsCheck deleteSlotsCheck;
-	std::shared_ptr<CTradeableItem> selected;
 	const int selectionWidth = 2;
-	std::shared_ptr<CTradeableItem> selectedSlot;
+	std::shared_ptr<CTradeableItem> selectedSlot;		// Separate slot that displays the contents of the highlighted slot
+	std::shared_ptr<CTradeableItem> highlightedSlot;	// One of the slots highlighted by a frame
 
-	virtual void updateSlots();
+	virtual void update();
 	virtual void deselect();
 	virtual void clearSubtitles();
 	void updateOffer(CTradeableItem & slot, int, int);

+ 8 - 6
client/windows/CMarketWindow.cpp

@@ -63,25 +63,25 @@ CMarketWindow::CMarketWindow(const IMarket * market, const CGHeroInstance * hero
 void CMarketWindow::updateArtifacts()
 {
 	assert(marketWidget);
-	marketWidget->updateSlots();
+	marketWidget->update();
 }
 
 void CMarketWindow::updateGarrisons()
 {
 	assert(marketWidget);
-	marketWidget->updateSlots();
+	marketWidget->update();
 }
 
 void CMarketWindow::updateResource()
 {
 	assert(marketWidget);
-	marketWidget->updateSlots();
+	marketWidget->update();
 }
 
 void CMarketWindow::updateHero()
 {
 	assert(marketWidget);
-	marketWidget->updateSlots();
+	marketWidget->update();
 }
 
 void CMarketWindow::close()
@@ -100,7 +100,7 @@ bool CMarketWindow::holdsGarrison(const CArmedInstance * army)
 
 void CMarketWindow::artifactRemoved(const ArtifactLocation & artLoc)
 {
-	marketWidget->updateSlots();
+	marketWidget->update();
 	CWindowWithArtifacts::artifactRemoved(artLoc);
 }
 
@@ -109,7 +109,7 @@ void CMarketWindow::artifactMoved(const ArtifactLocation & srcLoc, const Artifac
 	if(!getState().has_value())
 		return;
 	assert(marketWidget);
-	marketWidget->updateSlots();
+	marketWidget->update();
 	CWindowWithArtifacts::artifactMoved(srcLoc, destLoc, withRedraw);
 }
 
@@ -201,6 +201,8 @@ void CMarketWindow::createArtifactsSelling(const IMarket * market, const CGHeroI
 	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255 - DISPOSE);
 
 	background = createBg(ImagePath::builtin("TPMRKASS.bmp"), PLAYER_COLORED);
+	// Create image that copies part of background containing slot MISC_1 into position of slot MISC_5
+	artSlotBack = std::make_shared<CPicture>(background->getSurface(), Rect(20, 187, 47, 47), 18, 339);
 	auto artsSellingMarket = std::make_shared<CArtifactsSelling>(market, hero);
 	artSets.clear();
 	addSetAndCallbacks(artsSellingMarket->getAOHset());

+ 5 - 2
client/windows/CMarketWindow.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "../widgets/markets/CTradeBase.h"
+#include "../widgets/markets/CMarketBase.h"
 #include "../widgets/CWindowWithArtifacts.h"
 #include "CWindowObject.h"
 
@@ -43,5 +43,8 @@ private:
 	std::shared_ptr<CButton> quitButton;
 	std::function<void()> windowClosedCallback;
 	const Point quitButtonPos = Point(516, 520);
-	std::shared_ptr<CTradeBase> marketWidget;
+	std::shared_ptr<CMarketBase> marketWidget;
+
+	// This is workaround for bug in H3 files where this slot for ragdoll on this screen is missing
+	std::shared_ptr<CPicture> artSlotBack;
 };