1
0
forked from 0ad/0ad

Allow multiple special sprites, to facilitate cases where we want to add an overlay to a sprite.

"color" can make use of the sprite's alpha texture, and the "add_color"
effect will now take the original image's alpha into account.
Remove the no longer needed "unaffordableMask" images on a variety of
panels.

Somewhat based on a patch by BoeseRaupe. Fixes #2421 and #3688.

This was SVN commit r17456.
This commit is contained in:
wraitii 2015-12-13 16:03:17 +00:00
parent 88d6f35126
commit 909b8d4369
19 changed files with 155 additions and 96 deletions

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<effect>
<technique>
<require shaders="fixed"/>
<pass shader="fixed:gui_solid"/>
</technique>
<technique>
<require shaders="glsl"/>
<pass shader="glsl/gui_solid_mask"/>
</technique>
</effect>

View File

@ -7,5 +7,7 @@ varying vec2 v_tex;
void main()
{
gl_FragColor = texture2D(tex, v_tex) + color;
vec4 t = texture2D(tex, v_tex);
gl_FragColor.rgb = t.rgb + color.rgb * t.a;
gl_FragColor.a = t.a;
}

View File

@ -0,0 +1,13 @@
#version 110
uniform sampler2D tex;
uniform vec4 color;
varying vec2 v_tex;
void main()
{
vec4 t = texture2D(tex, v_tex);
gl_FragColor.rgb = color.rgb;
gl_FragColor.a = t.a*color.a;
}

View File

@ -0,0 +1,15 @@
#version 110
uniform mat4 transform;
varying vec2 v_tex;
attribute vec3 a_vertex;
attribute vec2 a_uv0;
void main()
{
gl_Position = transform * vec4(a_vertex, 1.0);
v_tex = a_uv0;
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<program type="glsl">
<vertex file="glsl/gui_solid_mask.vs">
<stream name="pos"/>
<stream name="uv0"/>
<attrib name="a_vertex" semantics="gl_Vertex"/>
<attrib name="a_uv0" semantics="gl_MultiTexCoord0"/>
</vertex>
<fragment file="glsl/gui_solid_mask.fs"/>
</program>

View File

@ -1355,7 +1355,7 @@ function onGameAttributesChange()
ceasefire.selected = mapSettings.Ceasefire !== undefined && g_Ceasefire.Duration.indexOf(mapSettings.Ceasefire) != -1 ? g_Ceasefire.Duration.indexOf(mapSettings.Ceasefire) : g_Ceasefire.Default;
ceasefireText.caption = g_Ceasefire.Title[ceasefire.selected];
Engine.GetGUIObjectByName("mapPreview").sprite = "cropped:(0.78125,0.5859375)session/icons/mappreview/" + getMapPreview(mapName);
Engine.GetGUIObjectByName("mapPreview").sprite = "cropped:0.78125,0.5859375:session/icons/mappreview/" + getMapPreview(mapName);
// Handle map type specific logic

View File

@ -526,7 +526,7 @@ function updateGameSelection()
var mapData = getMapDescriptionAndPreview(game.mapType, game.mapName);
Engine.GetGUIObjectByName("sgMapDescription").caption = mapData.description;
Engine.GetGUIObjectByName("sgMapPreview").sprite = "cropped:(0.7812,0.5859)session/icons/mappreview/" + mapData.preview;
Engine.GetGUIObjectByName("sgMapPreview").sprite = "cropped:0.7812,0.5859:session/icons/mappreview/" + mapData.preview;
}
/**

View File

@ -214,7 +214,7 @@ function displayReplayDetails()
Engine.GetGUIObjectByName("sgNbPlayers").caption = replay.attribs.settings.PlayerData.length;
Engine.GetGUIObjectByName("sgPlayersNames").caption = getReplayTeamText(replay);
Engine.GetGUIObjectByName("sgMapDescription").caption = mapData.description;
Engine.GetGUIObjectByName("sgMapPreview").sprite = "cropped:(0.7812,0.5859)session/icons/mappreview/" + mapData.preview;
Engine.GetGUIObjectByName("sgMapPreview").sprite = "cropped:0.7812,0.5859:session/icons/mappreview/" + mapData.preview;
Engine.GetGUIObjectByName("summaryButton").hidden = !Engine.HasReplayMetadata(replay.directory);
}

View File

@ -18,7 +18,6 @@
* "rowLength": rowLength
* "numberOfItems": number of items that will be processed
* "button": gui Button object
* "affordableMask": gui Unaffordable overlay
* "icon": gui Icon object
* "guiSelection": gui button Selection overlay
* "countDisplay": gui caption space
@ -122,7 +121,6 @@ g_SelectionPanels.Barter = {
data.amount[a] = Engine.GetGUIObjectByName("unitBarter"+a+"Amount["+data.i+"]");
}
data.selectionIcon = Engine.GetGUIObjectByName("unitBarterSellSelection["+data.i+"]");
data.affordableMask = Engine.GetGUIObjectByName("unitBarterSellUnaffordable["+data.i+"]");
data.amountToSell = BARTER_RESOURCE_AMOUNT_TO_SELL;
if (Engine.HotkeyIsPressed("session.massbarter"))
@ -155,18 +153,22 @@ g_SelectionPanels.Barter = {
},
"setGraphics": function(data)
{
var grayscale = data.isSelected ? "grayscale:" : "";
data.button.Buy.hidden = data.isSelected;
data.button.Sell.hidden = false;
for each (var icon in data.icon)
icon.sprite = "stretched:"+grayscale+"session/icons/resources/" + data.item + ".png";
var grayscale = data.isSelected ? "color: 0 0 0 100:grayscale:" : "";
// do we have enough of this resource to sell?
var neededRes = {};
neededRes[data.item] = data.amountToSell;
if (Engine.GuiInterfaceCall("GetNeededResources", neededRes))
data.affordableMask.hidden = false;
else
data.affordableMask.hidden = true;
var canSellCurrent = Engine.GuiInterfaceCall("GetNeededResources", neededRes) != undefined ? "color:255 0 0 80:" : "";
// Let's see if we have enough resources to barter.
neededRes = {};
neededRes[g_barterSell] = data.amountToSell;
var canBuyAny = Engine.GuiInterfaceCall("GetNeededResources", neededRes) != undefined ? "color:255 0 0 80:" : "";
data.icon.Sell.sprite = canSellCurrent + "stretched:"+grayscale+"session/icons/resources/" + data.item + ".png";
data.icon.Buy.sprite = canBuyAny + "stretched:"+grayscale+"session/icons/resources/" + data.item + ".png";
data.button.Buy.hidden = data.isSelected;
data.button.Sell.hidden = false;
data.selectionIcon.hidden = !data.isSelected;
},
"setPosition": function(data)
@ -328,22 +330,21 @@ g_SelectionPanels.Construction = {
},
"setGraphics": function(data)
{
var grayscale = "";
var modifier = "";
if (!data.technologyEnabled || data.limits.canBeAddedCount == 0)
{
data.button.enabled = false;
grayscale = "grayscale:";
data.affordableMask.hidden = false;
data.affordableMask.sprite = "color: 0 0 0 127";
modifier += "color: 0 0 0 127:textureAsMask:";
modifier += "grayscale:";
}
else if (data.neededResources)
{
data.button.enabled = false;
data.affordableMask.hidden = false;
data.affordableMask.sprite = resourcesToAlphaMask(data.neededResources);
modifier += resourcesToAlphaMask(data.neededResources) +":";
}
if (data.template.icon)
data.icon.sprite = "stretched:" + grayscale + "session/portraits/" + data.template.icon;
data.icon.sprite = modifier + "stretched:session/portraits/" + data.template.icon;
},
"setPosition": function(data)
{
@ -449,7 +450,7 @@ g_SelectionPanels.Garrison = {
var grayscale = "";
var ents = data.item.ents;
var entplayer = GetEntityState(ents[0]).player;
data.button.sprite = "color: " + rgbToGuiColor(g_Players[entplayer].color);
data.button.sprite = "color:" + rgbToGuiColor(g_Players[entplayer].color) +":";
var player = Engine.GetPlayerID();
if(player != data.unitEntState.player && !g_DevSettings.controlAll)
@ -553,7 +554,6 @@ g_SelectionPanels.Gate = {
},
"setGraphics": function(data)
{
data.affordableMask.hidden = !data.neededResources;
var gateIcon;
if (data.item.gate)
{
@ -574,7 +574,7 @@ g_SelectionPanels.Gate = {
data.guiSelection.hidden = true;
}
data.icon.sprite = "stretched:session/" + gateIcon;
data.icon.sprite = (data.neededResources ? resourcesToAlphaMask(data.neededResources) + ":" : "") + "stretched:session/" + gateIcon;
},
"setPosition": function(data)
{
@ -770,11 +770,6 @@ g_SelectionPanels.Research = {
return Engine.GetGUIObjectByName("unitResearchButton["+p+"]");
});
data.affordableMask = data.positions.map(function(p) {
return Engine.GetGUIObjectByName("unitResearchUnaffordable["+p+"]");
});
data.icon = data.positions.map(function(p) {
return Engine.GetGUIObjectByName("unitResearchIcon["+p+"]");
});
@ -854,27 +849,23 @@ g_SelectionPanels.Research = {
{
var button = data.button[i];
button.hidden = false;
var grayscale = "";
var modifier = "";
if (!data.requirementsPassed[i])
{
button.enabled = false;
grayscale = "grayscale:";
data.affordableMask[i].hidden = false;
data.affordableMask[i].sprite = "color: 0 0 0 127";
modifier += "color: 0 0 0 127:";
modifier += "grayscale:";
}
else if (data.neededResources[i])
{
button.enabled = false;
data.affordableMask[i].hidden = false;
data.affordableMask[i].sprite = resourcesToAlphaMask(data.neededResources[i]);
modifier += resourcesToAlphaMask(data.neededResources[i]) + ":";
}
else
{
data.affordableMask[i].hidden = true;
button.enabled = true;
}
if (data.template[i].icon)
data.icon[i].sprite = "stretched:" + grayscale + "session/portraits/" + data.template[i].icon;
data.icon[i].sprite = modifier + "stretched:session/portraits/" + data.template[i].icon;
}
for (var button of data.buttonsToHide)
button.hidden = true;

View File

@ -11,7 +11,6 @@
<!-- sell -->
<object name="unitBarterSellButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottomBold">
<object name="unitBarterSellIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
<object name="unitBarterSellUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="color: 255 0 0 60"/>
<object name="unitBarterSellAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
<object name="unitBarterSellSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/corners.png"/>
</object>

View File

@ -6,7 +6,6 @@
<repeat count="24">
<object name="unitConstructionButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
<object name="unitConstructionIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
<object name="unitConstructionUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="color: 255 0 0 127"/>
</object>
</repeat>
</object>

View File

@ -7,7 +7,6 @@
<object name="unitGateButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
<object name="unitGateIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
<object name="unitGateSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/corners.png"/>
<object name="unitGateUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="color: 255 0 0 127"/>
</object>
</repeat>
</object>

View File

@ -7,7 +7,6 @@
<object name="unitResearchButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
<object name="unitResearchIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
<object name="unitResearchUnchosenIcon[n]" type="image" hidden="true" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/tech_pair_would_be_unavailable.png"/>
<object name="unitResearchUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="color: 255 0 0 60"/>
</object>
</repeat>
<repeat count="8">

View File

@ -6,7 +6,6 @@
<repeat count="24">
<object name="unitTrainingButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
<object name="unitTrainingIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
<object name="unitTrainingUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="color: 255 0 0 127"/>
<object name="unitTrainingCount[n]" ghost="true" style="groupIconsText" type="text" z="20"/>
</object>
</repeat>

View File

@ -89,7 +89,6 @@ function setupUnitPanel(guiName, unitEntState, playerState)
// add standard gui objects to the data
// depending on the actual XML, some of this may be undefined
data.button = Engine.GetGUIObjectByName("unit"+guiName+"Button["+i+"]");
data.affordableMask = Engine.GetGUIObjectByName("unit"+guiName+"Unaffordable["+i+"]");
data.icon = Engine.GetGUIObjectByName("unit"+guiName+"Icon["+i+"]");
data.guiSelection = Engine.GetGUIObjectByName("unit"+guiName+"Selection["+i+"]");
data.countDisplay = Engine.GetGUIObjectByName("unit"+guiName+"Count["+i+"]");
@ -104,9 +103,6 @@ function setupUnitPanel(guiName, unitEntState, playerState)
data.button.caption = "";
}
if (data.affordableMask)
data.affordableMask.hidden = true; // actually used for the red "lack of resource" overlay, and darkening if unavailable. Sort of a hack.
// GENERAL DATA
// add general data, and a chance to abort on faulty data
if (g_SelectionPanels[guiName].addData)
@ -146,16 +142,6 @@ function setupUnitPanel(guiName, unitEntState, playerState)
g_SelectionPanels[guiName].used = true;
}
function resourcesToAlphaMask(neededResources)
{
var totalCost = 0;
for each (var resource in neededResources)
totalCost += resource;
var alpha = 50 + Math.round(totalCost/10);
alpha = alpha > 125 ? 125 : alpha;
return "color: 255 0 0 " + (alpha);
}
/**
* Updates the selection panels where buttons are supposed to
* depend on the context.

View File

@ -156,3 +156,16 @@ function getEntityOrHolder(ent)
return ent;
}
/**
* Returns a "color:255 0 0 Alpha" string based on how many resources are needed.
*/
function resourcesToAlphaMask(neededResources)
{
var totalCost = 0;
for (var resource in neededResources)
totalCost += +neededResources[resource];
var alpha = 50 + Math.round(+totalCost/10.0);
alpha = alpha > 125 ? 125 : alpha;
return "color:255 0 0 " + alpha;
}

View File

@ -45,6 +45,7 @@ struct SGUIImageEffects
{
SGUIImageEffects() : m_Greyscale(false) {}
CColor m_AddColor;
CColor m_SolidColor;
bool m_Greyscale;
};

View File

@ -75,70 +75,70 @@ void GUIRenderer::UpdateDrawCallCache(DrawCalls& Calls, const CStr& SpriteName,
std::map<CStr, CGUISprite*>::iterator it(Sprites.find(SpriteName));
if (it == Sprites.end())
{
// Sprite not found. Check whether this a special sprite:
// "stretched:filename.ext" - stretched image
// "stretched:grayscale:filename.ext" - stretched grayscale image
// "cropped:(0.5, 0.25)" - stretch this ratio (x,y) of the top left of the image
// "color:r g b a" - solid color
//
// and if so, try to create it as a new sprite.
if (SpriteName.substr(0, 10) == "stretched:")
/*
* Sprite not found. Check whether this a special sprite,
* and if so create a new sprite:
* "stretched:filename.ext" - stretched image
* "stretched:grayscale:filename.ext" - stretched grayscale image.
* "cropped:0.5, 0.25" - stretch this ratio (x,y) of the top left of the image
* "color:r g b a" - solid color
* > "textureAsMask" - when using color, use the (optional) texture alpha channel as mask.
* These can be combined, but they must be separated by a ":"
* so you can have a white overlay over an stretched grayscale image with:
* "grayscale:color:255 255 255 100:stretched:filename.ext"
*/
// Check that this can be a special sprite.
if (SpriteName.ReverseFind(":") == -1 && SpriteName.Find("color(") == -1)
{
LOGERROR("Trying to use a sprite that doesn't exist (\"%s\").", SpriteName.c_str());
return;
}
CGUISprite* Sprite = new CGUISprite;
VfsPath TextureName = VfsPath("art/textures/ui") / wstring_from_utf8(SpriteName.AfterLast(":"));
if (SpriteName.Find("stretched:") != -1)
{
// TODO: Should check (nicely) that this is a valid file?
SGUIImage* Image = new SGUIImage;
Image->m_TextureName = TextureName;
// Allow grayscale images for disabled portraits
if (SpriteName.substr(10, 10) == "grayscale:")
if (SpriteName.Find("grayscale:") != -1)
{
Image->m_TextureName = VfsPath("art/textures/ui") / wstring_from_utf8(SpriteName.substr(20));
Image->m_Effects = new SGUIImageEffects;
Image->m_Effects->m_Greyscale = true;
}
else
{
Image->m_TextureName = VfsPath("art/textures/ui") / wstring_from_utf8(SpriteName.substr(10));
}
CClientArea ca(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100));
Image->m_Size = ca;
Image->m_TextureSize = ca;
CGUISprite* Sprite = new CGUISprite;
Sprite->AddImage(Image);
Sprites[SpriteName] = Sprite;
it = Sprites.find(SpriteName);
ENSURE(it != Sprites.end()); // The insertion above shouldn't fail
}
else if (SpriteName.substr(0, 8) == "cropped:")
else if (SpriteName.Find("cropped:") != -1)
{
// TODO: Should check (nicely) that this is a valid file?
SGUIImage* Image = new SGUIImage;
double xRatio = SpriteName.BeforeFirst(",").AfterLast("(").ToDouble();
double yRatio = SpriteName.BeforeFirst(")").AfterLast(",").ToDouble();
CStr info = SpriteName.AfterLast("cropped:").BeforeFirst(":");
double xRatio = info.BeforeFirst(",").ToDouble();
double yRatio = info.AfterLast(",").ToDouble();
int PathStart = SpriteName.Find(")") + 1;
Image->m_TextureName = VfsPath("art/textures/ui") / wstring_from_utf8(SpriteName.substr(PathStart));
Image->m_TextureName = TextureName;
CClientArea ca(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100));
CClientArea cb(CRect(0, 0, 0, 0), CRect(0, 0, 100/xRatio, 100/yRatio));
Image->m_Size = ca;
Image->m_TextureSize = cb;
CGUISprite* Sprite = new CGUISprite;
Sprite->AddImage(Image);
Sprites[SpriteName] = Sprite;
it = Sprites.find(SpriteName);
ENSURE(it != Sprites.end()); // The insertion above shouldn't fail
}
else if (SpriteName.substr(0, 6) == "color:")
if (SpriteName.Find("color:") != -1)
{
CStrW value = wstring_from_utf8(SpriteName.substr(6));
CStrW value = wstring_from_utf8(SpriteName.AfterLast("color:").BeforeFirst(":"));
CColor color;
// Check color is valid
@ -150,23 +150,32 @@ void GUIRenderer::UpdateDrawCallCache(DrawCalls& Calls, const CStr& SpriteName,
SGUIImage* Image = new SGUIImage;
Image->m_BackColor = color;
// If we are using a mask, this is an effect.
// Otherwise we can fallback to the "back color" attribute
// TODO: we are assuming there is a filename here.
if (SpriteName.Find("textureAsMask:") != -1)
{
Image->m_TextureName = TextureName;
Image->m_Effects = new SGUIImageEffects;
Image->m_Effects->m_SolidColor = color;
}
else
Image->m_BackColor = color;
CClientArea ca(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100));
Image->m_Size = ca;
Image->m_TextureSize = ca;
CGUISprite* Sprite = new CGUISprite;
Sprite->AddImage(Image);
Sprites[SpriteName] = Sprite;
it = Sprites.find(SpriteName);
ENSURE(it != Sprites.end()); // The insertion above shouldn't fail
}
else
it = Sprites.find(SpriteName);
// Otherwise, just complain and give up:
if (it == Sprites.end())
{
// Otherwise, just complain and give up:
SAFE_DELETE(Sprite);
LOGERROR("Trying to use a sprite that doesn't exist (\"%s\").", SpriteName.c_str());
return;
}
@ -243,6 +252,12 @@ void GUIRenderer::UpdateDrawCallCache(DrawCalls& Calls, const CStr& SpriteName,
{
Call.m_Shader = g_Renderer.GetShaderManager().LoadEffect(str_gui_grayscale);
}
else if ((*cit)->m_Effects->m_SolidColor != CColor())
{
Call.m_Shader = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid_mask);
Call.m_ShaderColorParameter = (*cit)->m_Effects->m_SolidColor;
Call.m_EnableBlending = !(fabs((*cit)->m_Effects->m_SolidColor.a - 1.0f) < 0.0000001f);
}
else /* Slight confusion - why no effects? */
{
Call.m_Shader = g_Renderer.GetShaderManager().LoadEffect(str_gui_basic);

View File

@ -95,6 +95,7 @@ X(gui_add)
X(gui_basic)
X(gui_grayscale)
X(gui_solid)
X(gui_solid_mask)
X(gui_text)
X(hdr)
X(height)