Skip to content

Widget creation

johan-buret edited this page Oct 31, 2023 · 9 revisions

What is a widget ?

A widget is a graphic element displayed on your screen, either purely decorative, or with acction and information attached

The issue with WidgetTypes.GENERAL

Long story short : WidgetTypes.GENERAL has neither meaning nor behaviour attached.

The main advantage of WidgetTypes.GENERAL is that it doesn't bind the graphical component to a predetermined behaviour when clicked, hovered, or dragged and dropped.

This feature becomes an issue when we want a component to have the same behaviour across all screens, or having access to state not exposed via the Python API. It forces to make calls that are slow, forces to duplicate game logic on both Python script and C++. All of this is explained in issue #929

How to create a widget

In our example, we will create the widget for Tax Information, the one displayed at the top of Africa and Europe Its name will be WIDGET_HELP_TAX_CALCULATION

image

Declare the widget in GlobalTypes.xml

The path of the file within the mod is Assets/XML/GlobalTypes.xml

image

The best practice is to add as the last element of the list.

Rebuild the DLL to take it into account

Create the new Widget

Most of the declarative work takes place in CvDLLWidgetData.cpp

template<> // This lines tells the compiler we use a fully defined template
class WidgetContainer<WIDGET_HELP_TAX_CALCULATION> : public WidgetData
{
public:
	WidgetContainer(const CvWidgetDataStruct widgetDataStruct)
		: eColonyPlayer((PlayerTypes)widgetDataStruct.m_iData1)
		, bForceDetailedInfo(widgetDataStruct.m_iData2 == 1) //If set to 1, will display the detailed info, regardless of hidden variable display option
	{}

	const PlayerTypes eColonyPlayer;
	const bool bForceDetailedInfo;

	void parseHelp(CvWStringBuffer& szBuffer) const
	{
		CvPlayerAI& kColony = GET_PLAYER(eColonyPlayer);
		CvPlayerAI& kKing = *kColony.getParentPlayer();
		const bool bNoHidden = GC.getGameINLINE().isOption(GAMEOPTION_NO_MORE_VARIABLES_HIDDEN);
		if (bNoHidden || bForceDetailedInfo)
		{
			const int chancePerThousand = kKing.getTaxRaiseChance();
			szBuffer.append(gDLL->getText("TXT_KEY_TAX_BAR",
				kKing.getFullYieldScore(), //apply the Global Ratio
				kKing.getTaxThresold(), // get a comparable quantity
				GC.getYieldInfo(YIELD_TRADE_GOODS).getChar(),
				chancePerThousand / 10,	chancePerThousand % 10,
				GLOBAL_DEFINE_TAX_RATE_RETAINED_FRACTION
				));
		}
		else
		{
			const CvWString st = gDLL->getText("TXT_KEY_MISC_TAX_RATE", 
				kColony.getTaxRate(), 
				kColony.NBMOD_GetMaxTaxRate());
			// st.ToUpper?
			szBuffer.append(st);
		}
	}
};

the first lines are the most crucial to understand of all this work.

And then edit the WidgetData::getNew method to explicitely include this new templated class

WidgetData* WidgetData::getNew(const CvWidgetDataStruct& widgetData)
{
	switch (widgetData.m_eWidgetType)
	{
	case WIDGET_PEDIA_JUMP_TO_UNIT: return new WidgetContainer<WIDGET_PEDIA_JUMP_TO_UNIT>(widgetData);
	case WIDGET_HELP_TAX_CALCULATION: return new WidgetContainer<WIDGET_HELP_TAX_CALCULATION >(widgetData); //New ! Shiny!
	}

	return NULL;
}

Python invocation

When placing graphical elements with python calls, there are 3 parameters at the end.

screen.setText(self.getNextWidgetName(), "Background", szTaxRate, CvUtil.FONT_RIGHT_JUSTIFY,
    self.XResolution - CyInterface().determineWidth(szExit) - self.STANDARD_MARGIN * 2, self.STANDARD_MARGIN, 0, FontTypes.TITLE_FONT,
    WidgetTypes.WIDGET_HELP_TAX_CALCULATION, self.player_id, -1 )  # here

Doing so will attach the behaviour of the C++ class we defined above to this zone of text.