Skip to content

Commit 9aded46

Browse files
authored
Merge pull request #633 from C7-Game/yegor/city-scene-label-rewrite
Rewrite CityLabelScene with Godot UI nodes
2 parents b91b7bb + ef1d8e0 commit 9aded46

3 files changed

Lines changed: 164 additions & 140 deletions

File tree

C7/Map/CityLabelScene.cs

Lines changed: 161 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,49 @@
1-
using System;
21
using C7GameData;
32
using ConvertCiv3Media;
43
using Godot;
5-
using Serilog;
6-
using Serilog.Events;
74

85
namespace C7.Map {
96
public partial class CityLabelScene : Node2D {
10-
private ILogger log = LogManager.ForContext<CityLabelScene>();
11-
12-
private City city;
13-
private Tile tile;
14-
private Vector2I tileCenter;
15-
16-
private ImageTexture cityTexture;
7+
City city;
8+
Vector2I tileCenter;
9+
Color civColor;
1710

11+
const byte TRANSPARENCY = 192; // 25%
1812
const int CITY_LABEL_HEIGHT = 23;
1913
const int LEFT_RIGHT_BOXES_WIDTH = 24;
2014
const int LEFT_RIGHT_BOXES_HEIGHT = CITY_LABEL_HEIGHT - 2;
21-
const int TEXT_ROW_HEIGHT = 9;
15+
const int CENTRAL_PANEL_SEPARATOR_WIDTH = 4;
16+
17+
static Pcx cityIcons = Util.LoadPCX("Art/Cities/city icons.pcx");
18+
static Image nonEmbassyStar;
19+
static Theme smallFontTheme = new();
20+
static Theme popThemeRed = new();
21+
static Theme popSizeTheme = new();
2222

23-
ImageTexture cityLabel = new ImageTexture();
23+
PanelContainer labelPanel = new();
24+
HBoxContainer mainContainer = new();
25+
PanelContainer popSizePanel = new();
26+
VBoxContainer centerContainer = new();
27+
PanelContainer capitalPanel = new();
28+
HSeparator centerDivider = new();
2429

25-
private TextureRect labelTextureRect = new TextureRect();
26-
Label cityNameLabel = new Label();
27-
Label productionLabel = new Label();
28-
Label popSizeLabel = new Label();
30+
HSeparator borderTop = new();
31+
HSeparator borderBottom = new();
2932

30-
private static FontFile smallFont = new FontFile();
31-
private static FontFile midSizedFont = new FontFile();
33+
VSeparator leftSeparator = new();
34+
VSeparator rightSeparator = new();
3235

33-
private static Pcx cityIcons = Util.LoadPCX("Art/Cities/city icons.pcx");
34-
private static Image nonEmbassyStar;
35-
private static Theme smallFontTheme = new Theme();
36-
private static Theme popThemeRed = new Theme();
37-
private static Theme popSizeTheme = new Theme();
36+
Label cityNameLabel = new();
37+
Label productionLabel = new();
38+
Label popSizeLabel = new();
3839

39-
private int lastLabelWidth = 0;
40+
static FontFile smallFont = new();
41+
static FontFile midSizedFont = new();
42+
43+
Color topRowGrey = Color.Color8(32, 32, 32, TRANSPARENCY);
44+
Color bottomRowGrey = Color.Color8(48, 48, 48, TRANSPARENCY);
45+
Color backgroundGrey = Color.Color8(64, 64, 64, TRANSPARENCY);
46+
Color borderGrey = Color.Color8(80, 80, 80, TRANSPARENCY);
4047

4148
static CityLabelScene() {
4249
smallFontTheme.DefaultFont = smallFont;
@@ -60,144 +67,161 @@ static CityLabelScene() {
6067
nonEmbassyStar = PCXToGodot.getImageFromPCX(cityIcons, new(20, 1, 18, 18));
6168
}
6269

63-
public CityLabelScene(City city, Tile tile, Vector2I tileCenter) {
70+
public CityLabelScene(City city, Vector2I tileCenter) {
6471
this.city = city;
65-
this.tile = tile;
6672
this.tileCenter = tileCenter;
6773

74+
civColor = new Color(Util.LoadColor(city.owner.colorIndex), TRANSPARENCY);
6875

69-
labelTextureRect.MouseFilter = Control.MouseFilterEnum.Ignore;
70-
cityNameLabel.MouseFilter = Control.MouseFilterEnum.Ignore;
71-
productionLabel.MouseFilter = Control.MouseFilterEnum.Ignore;
72-
popSizeLabel.MouseFilter = Control.MouseFilterEnum.Ignore;
76+
// Set up UI hierarchy
77+
AddChild(labelPanel);
78+
labelPanel.AddChild(mainContainer);
7379

74-
AddChild(labelTextureRect);
75-
AddChild(cityNameLabel);
76-
AddChild(productionLabel);
77-
AddChild(popSizeLabel);
78-
}
80+
// Left side (population)
81+
mainContainer.AddChild(popSizePanel);
82+
popSizePanel.AddChild(popSizeLabel);
7983

80-
public override void _Draw() {
81-
base._Draw();
84+
// Center (city name, production and population growth)
85+
mainContainer.AddChild(leftSeparator);
86+
mainContainer.AddChild(centerContainer);
87+
mainContainer.AddChild(rightSeparator);
88+
SetupCenterPanel();
8289

83-
int turnsUntilGrowth = city.TurnsUntilGrowth();
84-
string turnsUntilGrowthText = turnsUntilGrowth == int.MaxValue || turnsUntilGrowth < 0 ? "- -" : "" + turnsUntilGrowth;
85-
string cityNameAndGrowth = $"{city.name} : {turnsUntilGrowthText}";
86-
string productionDescription = city.itemBeingProduced.name + " : " + city.TurnsUntilProductionFinished();
87-
88-
int cityNameAndGrowthWidth = (int)smallFont.GetStringSize(cityNameAndGrowth).X;
89-
int productionDescriptionWidth = (int)smallFont.GetStringSize(productionDescription).X;
90-
int maxTextWidth = Math.Max(cityNameAndGrowthWidth, productionDescriptionWidth);
91-
92-
int cityLabelWidth = maxTextWidth + (city.IsCapital()? 70 : 45); //TODO: Is 65 right? 70? Will depend on whether it's capital, too
93-
int textAreaWidth = cityLabelWidth - (city.IsCapital() ? 50 : 25);
94-
if (log.IsEnabled(LogEventLevel.Verbose)) {
95-
log.Verbose("Width of city name = " + maxTextWidth);
96-
log.Verbose("City label width: " + cityLabelWidth);
97-
log.Verbose("Text area width: " + textAreaWidth);
98-
}
90+
mainContainer.AddThemeConstantOverride("separation", 0);
91+
centerContainer.AddThemeConstantOverride("separation", 0);
9992

100-
if (cityLabelWidth != lastLabelWidth) {
101-
Image labelBackground = CreateLabelBackground(cityLabelWidth, city, textAreaWidth);
102-
cityLabel = ImageTexture.CreateFromImage(labelBackground);
103-
lastLabelWidth = cityLabelWidth;
104-
}
93+
borderTop.AddThemeConstantOverride("separation", 1);
94+
borderBottom.AddThemeConstantOverride("separation", 1);
95+
centerDivider.AddThemeConstantOverride("separation", 1);
96+
97+
popSizeLabel.HorizontalAlignment = HorizontalAlignment.Center;
98+
popSizeLabel.VerticalAlignment = VerticalAlignment.Center;
99+
popSizePanel.CustomMinimumSize = new Vector2(LEFT_RIGHT_BOXES_WIDTH, LEFT_RIGHT_BOXES_HEIGHT);
100+
101+
labelPanel.MouseFilter = Control.MouseFilterEnum.Ignore;
105102

106-
DrawLabelOnScreen(tileCenter, cityLabelWidth, city, cityLabel);
107-
DrawTextOnLabel(tileCenter, cityNameAndGrowthWidth, productionDescriptionWidth, city, cityNameAndGrowth, productionDescription, cityLabelWidth);
103+
cityNameLabel.Theme = smallFontTheme;
104+
productionLabel.Theme = smallFontTheme;
105+
popSizeLabel.Theme = popSizeTheme;
106+
107+
ApplyStyles();
108108
}
109109

110-
private void DrawLabelOnScreen(Vector2I tileCenter, int cityLabelWidth, City city, ImageTexture cityLabel) {
111-
labelTextureRect.OffsetLeft = tileCenter.X + (cityLabelWidth / -2);
112-
labelTextureRect.OffsetTop = tileCenter.Y + 24;
113-
labelTextureRect.Texture = cityLabel;
110+
private void SetupCenterPanel() {
111+
centerContainer.AddChild(borderTop);
112+
centerContainer.AddChild(cityNameLabel);
113+
centerContainer.AddChild(centerDivider);
114+
centerContainer.AddChild(productionLabel);
115+
centerContainer.AddChild(borderBottom);
116+
117+
centerContainer.CustomMinimumSize = new Vector2(60, -1);
118+
119+
cityNameLabel.HorizontalAlignment = HorizontalAlignment.Center;
120+
cityNameLabel.VerticalAlignment = VerticalAlignment.Center;
121+
122+
productionLabel.HorizontalAlignment = HorizontalAlignment.Center;
123+
productionLabel.VerticalAlignment = VerticalAlignment.Center;
114124
}
115125

116-
private void DrawTextOnLabel(Vector2I tileCenter, int cityNameAndGrowthWidth, int productionDescriptionWidth, City city, string cityNameAndGrowth, string productionDescription, int cityLabelWidth) {
126+
private void SetupCapitalPanel() {
127+
capitalPanel = new PanelContainer();
117128

118-
//Destination for font is based on lower-left of baseline of font, not upper left as for blitted rectangles
119-
int cityNameOffset = cityNameAndGrowthWidth / -2;
120-
int prodDescriptionOffset = productionDescriptionWidth / -2;
121-
if (!city.IsCapital()) {
122-
cityNameOffset += 12;
123-
prodDescriptionOffset += 12;
124-
}
129+
StyleBoxFlat capitalStyle = new() {
130+
BgColor = civColor
131+
};
125132

126-
cityNameLabel.Theme = smallFontTheme;
127-
cityNameLabel.Text = cityNameAndGrowth;
128-
cityNameLabel.OffsetLeft = tileCenter.X + cityNameOffset;
129-
cityNameLabel.OffsetTop = tileCenter.Y + 22;
133+
capitalPanel.AddThemeStyleboxOverride("panel", capitalStyle);
134+
capitalPanel.CustomMinimumSize = new Vector2(LEFT_RIGHT_BOXES_WIDTH, LEFT_RIGHT_BOXES_HEIGHT);
130135

131-
productionLabel.Theme = smallFontTheme;
132-
productionLabel.Text = productionDescription;
133-
productionLabel.OffsetLeft = tileCenter.X + prodDescriptionOffset;
134-
productionLabel.OffsetTop = tileCenter.Y + 32;
136+
TextureRect starTextureRect = new() {
137+
Texture = ImageTexture.CreateFromImage(nonEmbassyStar),
138+
StretchMode = TextureRect.StretchModeEnum.KeepCentered
139+
};
135140

136-
//City pop size
137-
string popSizeString = "" + city.size;
138-
int popSizeWidth = (int)midSizedFont.GetStringSize(popSizeString).X;
139-
int popSizeOffset = LEFT_RIGHT_BOXES_WIDTH / 2 - popSizeWidth / 2;
141+
capitalPanel.AddChild(starTextureRect);
142+
}
140143

141-
popSizeLabel.Theme = popSizeTheme;
144+
private void ApplyStyles() {
145+
// style for the main panel
146+
StyleBoxFlat labelStyle = new() {
147+
BgColor = backgroundGrey,
148+
BorderColor = borderGrey,
149+
BorderWidthBottom = 1,
150+
BorderWidthLeft = 1,
151+
BorderWidthRight = 1,
152+
BorderWidthTop = 1
153+
};
154+
155+
StyleBoxFlat popStyle = new() {
156+
BgColor = civColor
157+
};
158+
159+
StyleBoxLine centerSeparatorStyle = new() {
160+
Color = civColor,
161+
GrowBegin = CENTRAL_PANEL_SEPARATOR_WIDTH,
162+
GrowEnd = CENTRAL_PANEL_SEPARATOR_WIDTH,
163+
};
164+
165+
StyleBoxLine bottomBorderStyle = new() {
166+
Color = bottomRowGrey,
167+
GrowBegin = CENTRAL_PANEL_SEPARATOR_WIDTH,
168+
GrowEnd = CENTRAL_PANEL_SEPARATOR_WIDTH,
169+
};
170+
171+
StyleBoxLine topBorderStyle = new() {
172+
Color = topRowGrey,
173+
GrowBegin = CENTRAL_PANEL_SEPARATOR_WIDTH,
174+
GrowEnd = CENTRAL_PANEL_SEPARATOR_WIDTH,
175+
};
176+
177+
StyleBoxLine separatorStyle = new() {
178+
Color = new Color(0, 0, 0, 0), // transparent,
179+
Thickness = CENTRAL_PANEL_SEPARATOR_WIDTH,
180+
Vertical = true
181+
};
182+
183+
labelPanel.AddThemeStyleboxOverride("panel", labelStyle);
184+
popSizePanel.AddThemeStyleboxOverride("panel", popStyle);
185+
centerDivider.AddThemeStyleboxOverride("separator", centerSeparatorStyle);
186+
borderTop.AddThemeStyleboxOverride("separator", topBorderStyle);
187+
borderBottom.AddThemeStyleboxOverride("separator", bottomBorderStyle);
188+
leftSeparator.AddThemeStyleboxOverride("separator", separatorStyle);
189+
rightSeparator.AddThemeStyleboxOverride("separator", separatorStyle);
190+
}
142191

192+
public override void _Draw() {
193+
UpdateContent();
194+
}
195+
196+
private void UpdateContent() {
197+
int turnsUntilGrowth = city.TurnsUntilGrowth();
198+
string turnsUntilGrowthText = turnsUntilGrowth == int.MaxValue || turnsUntilGrowth < 0 ? "- -" : "" + turnsUntilGrowth;
199+
200+
cityNameLabel.Text = $"{city.name} : {turnsUntilGrowthText}";
201+
productionLabel.Text = $"{city.itemBeingProduced.name} : {city.TurnsUntilProductionFinished()}";
202+
popSizeLabel.Text = city.size.ToString();
203+
204+
// Update population label color based on growth
143205
if (city.TurnsUntilGrowth() < 0) {
144206
popSizeLabel.Theme = popThemeRed;
207+
} else {
208+
popSizeLabel.Theme = popSizeTheme;
145209
}
146210

147-
popSizeLabel.Text = popSizeString;
148-
popSizeLabel.OffsetLeft = tileCenter.X + cityLabelWidth / -2 + popSizeOffset;
149-
popSizeLabel.OffsetTop = tileCenter.Y + 22;
150-
}
211+
// Update the panel with the capital star
212+
bool hasCapitalIndicator = capitalPanel.GetParent() == mainContainer;
151213

152-
private Image CreateLabelBackground(int cityLabelWidth, City city, int textAreaWidth) {
153-
//Label/name/producing area
154-
Image labelImage = Image.Create(cityLabelWidth, CITY_LABEL_HEIGHT, false, Image.Format.Rgba8);
155-
labelImage.Fill(Color.Color8(0, 0, 0, 0));
156-
byte transparencyLevel = 192; //25%
157-
Color civColor = Util.LoadColor(city.owner.colorIndex);
158-
civColor = new Color(civColor, transparencyLevel);
159-
Color civColorDarker = Color.Color8(0, 0, 138, transparencyLevel); //todo: automate the darker() function. maybe less transparency?
160-
Color topRowGrey = Color.Color8(32, 32, 32, transparencyLevel);
161-
Color bottomRowGrey = Color.Color8(48, 48, 48, transparencyLevel);
162-
Color backgroundGrey = Color.Color8(64, 64, 64, transparencyLevel);
163-
Color borderGrey = Color.Color8(80, 80, 80, transparencyLevel);
164-
165-
Image horizontalBorder = Image.Create(cityLabelWidth - 2, 1, false, Image.Format.Rgba8);
166-
horizontalBorder.Fill(borderGrey);
167-
labelImage.BlitRect(horizontalBorder, new Rect2I(0, 0, new Vector2I(cityLabelWidth - 2, 1)), new Vector2I(1, 0));
168-
labelImage.BlitRect(horizontalBorder, new Rect2I(0, 0, new Vector2I(cityLabelWidth - 2, 1)), new Vector2I(1, 22));
169-
170-
Image verticalBorder = Image.Create(1, CITY_LABEL_HEIGHT - 2, false, Image.Format.Rgba8);
171-
verticalBorder.Fill(borderGrey);
172-
labelImage.BlitRect(verticalBorder, new Rect2I(0, 0, new Vector2I(1, 23)), new Vector2I(0, 1));
173-
labelImage.BlitRect(verticalBorder, new Rect2I(0, 0, new Vector2I(1, 23)), new Vector2I(cityLabelWidth - 1, 1));
174-
175-
Image bottomRow = Image.Create(textAreaWidth, 1, false, Image.Format.Rgba8);
176-
bottomRow.Fill(bottomRowGrey);
177-
labelImage.BlitRect(bottomRow, new Rect2I(0, 0, new Vector2I(textAreaWidth, 1)), new Vector2I(25, 21));
178-
179-
Image topRow = Image.Create(textAreaWidth, 1, false, Image.Format.Rgba8);
180-
topRow.Fill(topRowGrey);
181-
labelImage.BlitRect(topRow, new Rect2I(0, 0, new Vector2I(textAreaWidth, 1)), new Vector2I(25, 1));
182-
183-
Image background = Image.Create(textAreaWidth, TEXT_ROW_HEIGHT, false, Image.Format.Rgba8);
184-
background.Fill(backgroundGrey);
185-
labelImage.BlitRect(background, new Rect2I(0, 0, new Vector2I(textAreaWidth, 9)), new Vector2I(25, 2));
186-
labelImage.BlitRect(background, new Rect2I(0, 0, new Vector2I(textAreaWidth, 9)), new Vector2I(25, 12));
187-
188-
Image centerDivider = Image.Create(textAreaWidth, 1, false, Image.Format.Rgba8);
189-
centerDivider.Fill(civColor);
190-
labelImage.BlitRect(centerDivider, new Rect2I(0, 0, new Vector2I(textAreaWidth, 1)), new Vector2I(25, 11));
191-
192-
Image leftAndRightBoxes = Image.Create(LEFT_RIGHT_BOXES_WIDTH, LEFT_RIGHT_BOXES_HEIGHT, false, Image.Format.Rgba8);
193-
leftAndRightBoxes.Fill(civColor);
194-
labelImage.BlitRect(leftAndRightBoxes, new Rect2I(0, 0, new Vector2I(24, 21)), new Vector2I(1, 1));
195-
if (city.IsCapital()) {
196-
labelImage.BlitRect(leftAndRightBoxes, new Rect2I(0, 0, new Vector2I(24, 21)), new Vector2I(cityLabelWidth - 25, 1));
197-
labelImage.BlendRect(nonEmbassyStar, new Rect2I(0, 0, new Vector2I(18, 18)), new Vector2I(cityLabelWidth - 24, 2));
214+
if (city.IsCapital() && !hasCapitalIndicator) {
215+
SetupCapitalPanel();
216+
mainContainer.AddChild(capitalPanel);
217+
} else if (!city.IsCapital() && hasCapitalIndicator) {
218+
mainContainer.RemoveChild(capitalPanel);
198219
}
199-
//todo: darker shades of civ color around edges
200-
return labelImage;
220+
221+
labelPanel.Position = new Vector2(tileCenter.X - labelPanel.Size.X / 2, tileCenter.Y + 24);
222+
223+
// Force the layout to recalculate
224+
labelPanel.Size = Vector2.Zero;
201225
}
202226
}
203227
}

C7/Map/CityLayer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public override void drawObject(LooseView looseView, GameData gameData, Tile til
2424

2525
City city = tile.cityAtTile;
2626
if (!citySceneLookup.ContainsKey(city)) {
27-
CityScene cityScene = new CityScene(city, tile, new Vector2I((int)tileCenter.X, (int)tileCenter.Y));
27+
CityScene cityScene = new CityScene(city, new Vector2I((int)tileCenter.X, (int)tileCenter.Y));
2828
looseView.AddChild(cityScene);
2929
citySceneLookup[city] = cityScene;
3030
} else {

C7/Map/CityScene.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ public partial class CityScene : Node2D {
1313
private TextureRect cityGraphics = new TextureRect();
1414
private CityLabelScene cityLabelScene;
1515

16-
public CityScene(City city, Tile tile, Vector2I tileCenter) {
17-
cityLabelScene = new CityLabelScene(city, tile, tileCenter);
16+
public CityScene(City city, Vector2I tileCenter) {
17+
cityLabelScene = new CityLabelScene(city, tileCenter);
1818

1919
//TODO: Generalize, support multiple city types, etc.
2020
Pcx pcx = Util.LoadPCX("Art/Cities/rMIDEAST.PCX");

0 commit comments

Comments
 (0)