Freeciv21
Develop your civilization from humble roots to a global empire
citybar.cpp
Go to the documentation of this file.
1 /*
2  Copyright (c) 1996-2023 Freeciv and Freeciv21 contributors. This file is
3  __ __ part of Freeciv21. Freeciv21 is free software: you can
4 / \\..// \ redistribute it and/or modify it under the terms of the GNU
5  ( oo ) General Public License as published by the Free Software
6  \__/ Foundation, either version 3 of the License, or (at your
7  option) any later version. You should have received
8  a copy of the GNU General Public License along with Freeciv21. If not,
9  see https://www.gnu.org/licenses/.
10  */
11 
12 #include <cmath>
13 #include <vector>
14 
15 // Qt
16 #include <QPainter>
17 #include <QTextCharFormat>
18 
19 // utility
20 #include "bugs.h"
21 #include "fcintl.h"
22 
23 // common
24 #include "city.h"
25 
26 // client
27 #include "canvas.h"
28 #include "client_main.h"
29 #include "colors_common.h"
30 #include "views/view_map_common.h"
31 
32 #include "citybar.h"
33 
35 std::unique_ptr<citybar_painter> citybar_painter::s_current = nullptr;
36 
46 class line_of_text {
47  struct block {
48  enum { TEXT_MODE, ICON_MODE, SPACER_MODE } mode; // What's in this block
49  QString text; // Text only
50  bool shadow = true; // Text only
51  QTextCharFormat format; // Text only
52  double ascent = 0, descent = 0; // Text only
53  const QPixmap *icon = nullptr; // Icon only
54  QMargins margins; // All modes
55  QSizeF base_size; // The base size of the block without margins
56 
57  QRectF draw_rect; // The laid out rect
58  };
59 
60 public:
61  void add_spacer();
62  void add_icon(const QPixmap *icon, const QMargins &margins = QMargins());
63  void add_text(const QString &text, const QTextCharFormat &format,
64  bool shadow = true, const QMargins &margins = QMargins());
65 
66  void set_text_shadow_brush(const QBrush &brush) { m_shadow_brush = brush; }
67 
68  double ideal_width() const;
69  void do_layout(double width = 0);
70  double height() const { return size().height(); }
71  QSizeF size() const { return m_size; }
72  void paint(QPainter &p, const QPointF &top_left) const;
73 
74 private:
76 
77  QSizeF m_size;
78  std::vector<block> m_blocks;
79 };
80 
86 {
87  m_blocks.emplace_back();
88  m_blocks.back().mode = block::SPACER_MODE;
89 }
90 
94 void line_of_text::add_icon(const QPixmap *icon, const QMargins &margins)
95 {
96  m_blocks.emplace_back();
97  m_blocks.back().mode = block::ICON_MODE;
98  m_blocks.back().icon = icon;
99  m_blocks.back().margins = margins;
100  m_blocks.back().base_size = icon->size();
101 }
102 
107 void line_of_text::add_text(const QString &text,
108  const QTextCharFormat &format, bool shadow,
109  const QMargins &margins)
110 {
111  m_blocks.emplace_back();
112  m_blocks.back().mode = block::TEXT_MODE;
113  m_blocks.back().text = text;
114  m_blocks.back().format = format;
115  m_blocks.back().shadow = shadow;
116  m_blocks.back().margins = margins;
117 
118  QFontMetricsF metrics(format.font());
119  // +1 to be on the safe side and avoid wrapping
120  m_blocks.back().base_size =
121  QSizeF(metrics.horizontalAdvance(text) + 1, metrics.height());
122  m_blocks.back().ascent = metrics.ascent();
123  m_blocks.back().descent = metrics.descent();
124  if (shadow) {
125  // Add some space for the shadow
126  m_blocks.back().base_size += QSizeF(1, 1);
127  // Bake it into the margin
128  m_blocks.back().margins.setBottom(margins.bottom() + 1);
129  }
130 }
131 
136 {
137  double width = 0;
138  for (const auto &blk : m_blocks) {
139  width +=
140  blk.margins.left() + blk.base_size.width() + blk.margins.right();
141  }
142  return width;
143 }
144 
152 void line_of_text::do_layout(double width)
153 {
154  // Compute the amount of horizontal space left
155  double empty_width;
156 
157  if (width <= 0) {
158  width = this->ideal_width();
159  empty_width = 0;
160  } else {
161  empty_width = width - this->ideal_width();
162  }
163 
164  // Set the widths and horizontal positions. Margins are counted outside of
165  // the rectangle.
166 
167  // Calculate the spacer block width
168  long num_spacers =
169  std::count_if(m_blocks.begin(), m_blocks.end(), [](const block &blk) {
170  return blk.mode == block::SPACER_MODE;
171  });
172  double spacer = empty_width / std::max(num_spacers, 1L);
173 
174  // Set the geometry
175  double x = 0;
176  for (auto &blk : m_blocks) {
177  blk.draw_rect.setX(x + blk.margins.left());
178  if (blk.mode == block::SPACER_MODE) {
179  x += spacer;
180  } else {
181  blk.draw_rect.setWidth(blk.base_size.width());
182  x += blk.margins.left() + blk.base_size.width() + blk.margins.right();
183  }
184  }
185 
186  // We're done with the horizontal layout. Set the width already.
187  m_size.setWidth(x);
188 
189  // Now do the vertical layout. The tricky bit here is to set the text
190  // position correctly so the baselines align nicely. We calculate the max
191  // ascent, descent and total height (for icons), then combine them as
192  // needed.
193  double ascent = 0, descent = 0, height = 0;
194  for (const auto &blk : m_blocks) {
195  ascent = std::max(ascent, blk.ascent + blk.margins.top());
196  descent = std::max(descent, blk.descent + blk.margins.bottom());
197  height = std::max(height, blk.margins.top() + blk.base_size.height()
198  + blk.margins.bottom());
199  }
200 
201  // The ascent might come from one block and the descent from another, in
202  // which case they're not summed in `height` yet.
203  height = std::max(height, ascent + descent);
204  m_size.setHeight(height);
205 
206  // Text baseline position with respect to the top of the bounding rect
207  double baseline = (height + ascent - descent) / 2;
208 
209  // Set y and height
210  for (auto &blk : m_blocks) {
211  if (blk.mode == block::TEXT_MODE) {
212  // Align text on the baseline
213  blk.draw_rect.setY(baseline - blk.ascent);
214  } else {
215  // Center the rest vertically
216  blk.draw_rect.setY((height - blk.base_size.height()) / 2);
217  }
218  blk.draw_rect.setHeight(blk.base_size.height());
219  }
220 }
221 
226 void line_of_text::paint(QPainter &p, const QPointF &top_left) const
227 {
228  for (const auto &blk : m_blocks) {
229  auto rect = blk.draw_rect.translated(top_left);
230  switch (blk.mode) {
231  case block::TEXT_MODE:
232  if (blk.format.background() != Qt::transparent) {
233  // Fill the background
234  auto fill_rect = rect.marginsAdded(blk.margins);
235  fill_rect.setY(top_left.y());
236  fill_rect.setHeight(height());
237  p.fillRect(fill_rect, blk.format.background());
238  }
239  p.setFont(blk.format.font());
240  if (blk.shadow) {
241  // Draw the shadow
242  p.setPen(QPen(m_shadow_brush, 1));
243  p.drawText(rect.translated(1, 1), blk.text);
244  }
245  p.setPen(QPen(blk.format.foreground(), 1));
246  p.drawText(rect, blk.text);
247  break;
248  case block::ICON_MODE:
249  p.drawPixmap(rect, *blk.icon, blk.icon->rect());
250  break;
251  case block::SPACER_MODE:
252  continue;
253  }
254  }
255 }
256 
262 {
263  // TRANS: City bar style
264  return {N_("Simple"), N_("Traditional"), N_("Polished")};
265 }
266 
272 {
273  static QVector<QString> vector;
274  if (vector.isEmpty()) {
275  for (auto &name : available()) {
276  vector << _(qUtf8Printable(name));
277  }
278  }
279  return &vector;
280 }
281 
287 {
290 }
291 
296 {
297  if (!s_current) {
299  }
300  return s_current.get();
301 }
302 
307 void citybar_painter::set_current(const QString &name)
308 {
309  if (name == QStringLiteral("Simple")) {
310  s_current = std::make_unique<simple_citybar_painter>();
311  return;
312  } else if (name == QStringLiteral("Traditional")) {
313  s_current = std::make_unique<traditional_citybar_painter>();
314  return;
315  } else if (name == QStringLiteral("Polished")) {
316  s_current = std::make_unique<polished_citybar_painter>();
317  return;
318  } else if (available().contains(name)) {
319  qCCritical(bugs_category,
320  "Could not instantiate known city bar style %s",
321  qUtf8Printable(name));
322  } else {
323  qCCritical(bugs_category, "Unknown city bar style %s",
324  qUtf8Printable(name));
325  }
326 
327  // Allocate the default to avoid crashes
328  s_current = std::make_unique<polished_citybar_painter>();
329 }
330 
334 QRect simple_citybar_painter::paint(QPainter &painter,
335  const QPointF &position,
336  const city *pcity) const
337 {
338  /*
339  * We draw two lines of centered text under each other, below the requested
340  * position: the first with the city name and the second with the
341  * production.
342  */
343  line_of_text first, second;
344 
345  // Get some city properties
346  const bool can_see_inside =
348 
349  char name[512], growth[32];
350  color_std growth_color, production_color;
351  get_city_mapview_name_and_growth(pcity, name, sizeof(name), growth,
352  sizeof(growth), &growth_color,
353  &production_color);
354 
355  // This is used for both lines
356  QTextCharFormat format;
357 
358  // Enable shadows
359  auto dark_color = get_color(tileset, COLOR_MAPVIEW_CITYTEXT_DARK);
360  first.set_text_shadow_brush(dark_color);
361  second.set_text_shadow_brush(dark_color);
362 
363  // First line
365  // City name
366  format.setFont(get_font(FONT_CITY_NAME));
367  format.setForeground(get_color(tileset, COLOR_MAPVIEW_CITYTEXT));
368  first.add_text(name, format);
369 
370  static const QString en_space = QStringLiteral(" ");
371 
372  // Growth string (eg "5")
373  if (gui_options->draw_city_growth && can_see_inside) {
374  // Separator (assuming the em space is wider for the city name)
375  first.add_text(en_space, format);
376 
377  // Text
378  format.setFont(get_font(FONT_CITY_PROD));
379  format.setForeground(get_color(tileset, growth_color));
380  first.add_text(growth, format);
381  }
382 
383  // Trade routes (eg "3/4")
384  if (gui_options->draw_city_trade_routes && can_see_inside) {
385  // Separator (can still be the city name format)
386  first.add_text(en_space, format);
387 
388  // Get the text
389  char trade_routes[32];
390  color_std trade_routes_color;
392  pcity, trade_routes, sizeof(trade_routes), &trade_routes_color);
393 
394  // Add it
395  format.setFont(get_font(FONT_CITY_PROD));
396  format.setForeground(get_color(tileset, trade_routes_color));
397  first.add_text(en_space + trade_routes, format);
398  }
399  }
400 
401  // Second line
402  if (gui_options->draw_city_productions && can_see_inside) {
403  // Get text
404  char prod[512];
405  get_city_mapview_production(pcity, prod, sizeof(prod));
406 
407  // Add text
408  format.setFont(get_font(FONT_CITY_PROD));
409  format.setForeground(get_color(tileset, production_color));
410  second.add_text(prod, format);
411  }
412 
413  // Do the text layout
414  double first_width = first.ideal_width();
415  double second_width = second.ideal_width();
416 
417  double width = std::max(first_width, second_width);
418  first.do_layout(width);
419  second.do_layout(width);
420 
421  // Paint
422  first.paint(painter, position - QPointF(first_width / 2, 0));
423  second.paint(painter,
424  position + QPointF(-second_width / 2, first.height()));
425 
426  return QRect(position.x() - width / 2, position.y(), width,
427  first.height() + second.height());
428 }
429 
434 QRect traditional_citybar_painter::paint(QPainter &painter,
435  const QPointF &position,
436  const city *pcity) const
437 {
438  /*
439  * We draw two lines of centered text under each other, below the requested
440  * position: the first with the city name and the second with the
441  * production.
442  */
443 
444  // Decide what to do
445  const bool can_see_inside =
447  const bool should_draw_productions =
448  can_see_inside && gui_options->draw_city_productions;
449  const bool should_draw_growth =
450  can_see_inside && gui_options->draw_city_growth;
451  const bool should_draw_trade_routes =
452  can_see_inside && gui_options->draw_city_trade_routes;
453  const bool should_draw_lower_bar = should_draw_productions
454  || should_draw_growth
455  || should_draw_trade_routes;
456 
457  if (!gui_options->draw_city_names && !should_draw_lower_bar) {
458  // Nothing to draw.
459  return QRect();
460  }
461 
462  // Get the tileset to grab stuff from
463  const auto t = get_tileset();
464 
465  // Get some city properties
466  const citybar_sprites *citybar = get_citybar_sprites(t);
467 
468  char name[512], growth[32];
469  color_std growth_color, production_color;
470  get_city_mapview_name_and_growth(pcity, name, sizeof(name), growth,
471  sizeof(growth), &growth_color,
472  &production_color);
473  const QMargins text_margins(3, 0, 3, 0);
474  QColor owner_color = get_player_color(t, city_owner(pcity));
475 
476  // This is used for both lines
477  QTextCharFormat format;
478 
479  // Fill the lines
480  line_of_text first, second;
481 
482  // First line
484  // Flag
485  first.add_icon(get_city_flag_sprite(t, pcity));
486 
487  // Units in city
489  unsigned long count = unit_list_size(pcity->tile->units);
490  count =
491  qBound(0UL, count,
492  static_cast<unsigned long>(citybar->occupancy.size() - 1));
493  first.add_icon(citybar->occupancy[count]);
494  } else {
495  if (pcity->client.occupied) {
496  first.add_icon(citybar->occupied);
497  } else {
498  first.add_icon(citybar->occupancy[0]);
499  }
500  }
501 
502  // City name
503  format.setForeground(get_color(t, COLOR_MAPVIEW_CITYTEXT));
504  first.add_spacer(); // Center it
505  first.add_text(name, format, false, text_margins);
506  first.add_spacer();
507 
508  // City size (on colored background)
509  format.setFont(get_font(FONT_CITY_NAME));
510  format.setBackground(owner_color);
511 
512  // Try to pick a color for city size text that contrasts with player
513  // color
514  QColor textcolors[2] = {get_color(t, COLOR_MAPVIEW_CITYTEXT),
515  get_color(t, COLOR_MAPVIEW_CITYTEXT_DARK)};
516  format.setForeground(color_best_contrast(owner_color, textcolors,
517  ARRAY_SIZE(textcolors)));
518 
519  first.add_text(QStringLiteral("%1").arg(city_size_get(pcity)), format,
520  false, text_margins);
521 
522  format.setBackground(Qt::transparent); // Reset
523  }
524 
525  // Second line
526  if (should_draw_lower_bar) {
527  // All items share the same font
528  format.setFont(get_font(FONT_CITY_PROD));
529 
530  if (should_draw_productions) {
531  // Icon
532  second.add_icon(citybar->shields);
533 
534  // Text
535  char prod[512];
536  get_city_mapview_production(pcity, prod, sizeof(prod));
537 
538  format.setForeground(get_color(t, production_color));
539  second.add_text(prod, format, false, text_margins);
540  }
541 
542  // Flush production to the left and the rest to the right
543  second.add_spacer();
544 
545  if (should_draw_growth) {
546  // Icon
547  second.add_icon(citybar->food);
548 
549  // Text
550  format.setForeground(get_color(t, growth_color));
551  second.add_text(growth, format, false, text_margins);
552  }
553 
554  if (should_draw_trade_routes) {
555  // Icon
556  second.add_icon(citybar->trade);
557 
558  // Text
559  char trade_routes[32];
560  color_std trade_routes_color;
562  pcity, trade_routes, sizeof(trade_routes), &trade_routes_color);
563 
564  format.setForeground(get_color(t, trade_routes_color));
565  second.add_text(trade_routes, format, false, text_margins);
566  }
567  }
568 
569  // Do the text layout. We need to align everything to integer pixels to
570  // avoid surprises with the rounding (but we still assume integer heights).
571  double width =
572  std::ceil(std::max(first.ideal_width(), second.ideal_width()));
573  first.do_layout(width);
574  second.do_layout(width);
575 
576  double x = std::floor(position.x() - width / 2 - 1);
577  double y = std::floor(position.y());
578 
579  int num_lines = (should_draw_lower_bar ? 2 : 1);
580  QRectF bounds =
581  QRectF(x, y, width + 2, first.height() + num_lines + second.height());
582 
583  // Paint the background, frame and separator
584  painter.drawTiledPixmap(bounds, *citybar->background);
585  painter.setPen(owner_color);
586  painter.drawRect(bounds);
587  if (gui_options->draw_city_names && should_draw_lower_bar) {
588  painter.drawLine(bounds.topLeft() + QPointF(0, first.height() + 1),
589  bounds.topRight() + QPointF(0, first.height() + 1));
590  }
591 
592  // Draw text and icons on top
593  first.paint(painter, QPointF(x + 1, y + 1)); // +1 for the frame
594  second.paint(painter, QPointF(x + 1, y + 1 + num_lines + first.height()));
595 
596  return QRect(x, y, x + width + 2, bounds.height());
597 }
598 
603 QRect polished_citybar_painter::paint(QPainter &painter,
604  const QPointF &position,
605  const city *pcity) const
606 {
611  // Decide what to do
612  const bool can_see_inside =
614  const bool should_draw_productions =
615  can_see_inside && gui_options->draw_city_productions;
616  const bool should_draw_growth =
617  can_see_inside && gui_options->draw_city_growth;
618  const bool should_draw_trade_routes =
619  can_see_inside && gui_options->draw_city_trade_routes;
620 
621  // Get the tileset to grab stuff from
622  const auto t = get_tileset();
623 
624  // Get some city properties
625  const citybar_sprites *citybar = get_citybar_sprites(t);
626 
627  char name[512], growth[32];
628  color_std growth_color, production_color;
629  get_city_mapview_name_and_growth(pcity, name, sizeof(name), growth,
630  sizeof(growth), &growth_color,
631  &production_color);
632 
633  // Decide colors
634  QColor owner_color = get_player_color(t, city_owner(pcity));
635  QColor textcolors[2] = {get_color(t, COLOR_MAPVIEW_CITYTEXT),
636  get_color(t, COLOR_MAPVIEW_CITYTEXT_DARK)};
637  QColor text_color = pcity->owner == client_player()
638  ? get_color(t, COLOR_MAPVIEW_CITYTEXT)
639  : color_best_contrast(owner_color, textcolors,
640  ARRAY_SIZE(textcolors));
641 
642  // Decide on the target height. It's the max of the font sizes and the
643  // occupied indicator (we assume all indicators have the same size).
644  // It's used to scale the flag, progress bars and production.
645  double target_height = citybar->occupancy[0]->height();
646  target_height = std::max(target_height,
647  QFontMetricsF(get_font(FONT_CITY_NAME)).height());
648  target_height = std::max(target_height,
649  QFontMetricsF(get_font(FONT_CITY_PROD)).height());
650 
651  // Build the contents
652  line_of_text line;
653 
654  QMargins text_margins(3, 0, 3, 0);
655  QTextCharFormat format;
656 
657  // Size
658  format.setFont(get_font(FONT_CITY_NAME));
659  format.setForeground(text_color);
660  line.add_text(QString::number(pcity->size), format, false, text_margins);
661 
662  // Growth
663  std::unique_ptr<QPixmap> growth_progress;
664  if (should_draw_growth) {
665  // Progress bar
666  growth_progress = std::make_unique<QPixmap>(
667  QSize(6, target_height) * painter.device()->devicePixelRatio());
668  growth_progress->setDevicePixelRatio(
669  painter.device()->devicePixelRatio());
670  growth_progress->fill(Qt::black);
671 
672  QPainter p(growth_progress.get());
673 
674  // Avoid div by 0
675  int granary_max = std::max(1, city_granary_size(city_size_get(pcity)));
676  double current = double(pcity->food_stock) / granary_max;
677  double next_turn = qBound(
678  0.0, current + double(pcity->surplus[O_FOOD]) / granary_max, 1.0);
679 
680  current *= target_height;
681  next_turn *= target_height;
682 
683  // Surplus (yellow)
684  if (next_turn > current) {
685  p.fillRect(QRectF(0, target_height, 6, -next_turn),
686  QColor(200, 200, 60));
687  }
688 
689  // Stock (green)
690  p.fillRect(QRectF(0, target_height, 6, -current), QColor(70, 120, 50));
691 
692  // Negative surplus (red)
693  if (next_turn < current) {
694  p.fillRect(QRectF(0, target_height - current, 6, current - next_turn),
695  Qt::red);
696  }
697 
698  // Add it
699  line.add_icon(growth_progress.get());
700 
701  // Text
702  format.setFont(get_font(FONT_CITY_PROD));
703  format.setFontPointSize(format.fontPointSize() / 1.5);
704  format.setForeground(get_color(t, growth_color));
705  line.add_text(growth, format, false, text_margins);
706  }
707 
708  // Flag
709  std::unique_ptr<QPixmap> scaled_flag;
710  if (city_owner(pcity) != client_player()) {
711  scaled_flag = std::make_unique<QPixmap>(
712  get_city_flag_sprite(t, pcity)->scaledToHeight(
713  target_height, Qt::SmoothTransformation));
714  line.add_icon(scaled_flag.get());
715  }
716 
717  // Occupied indicator
719  unsigned long count = unit_list_size(pcity->tile->units);
720  count =
721  qBound(0UL, count,
722  static_cast<unsigned long>(citybar->occupancy.size() - 1));
723  line.add_icon(citybar->occupancy[count]);
724  } else {
725  if (pcity->client.occupied) {
726  line.add_icon(citybar->occupied);
727  } else {
728  line.add_icon(citybar->occupancy[0]);
729  }
730  }
731 
732  // Name
734  format.setFont(get_font(FONT_CITY_NAME));
735  format.setForeground(text_color);
736  line.add_text(name, format, false, text_margins);
737  }
738 
739  // Production
740  std::unique_ptr<QPixmap> production_pix;
741  std::unique_ptr<QPixmap> production_progress;
742  if (should_draw_productions) {
743  // Format
744  format.setFont(get_font(FONT_CITY_PROD));
745  format.setFontPointSize(format.fontPointSize() / 1.5);
746  format.setForeground(get_color(t, production_color));
747 
748  QMargins prod_margins = text_margins;
749  prod_margins.setLeft(0); // Already have the city name on the left
750 
751  // Text
752  int turns = city_production_turns_to_build(pcity, true);
753  if (turns < 1000) {
754  line.add_text(QString::number(turns), format, false, text_margins);
755  } else {
756  line.add_text(QStringLiteral("∞"), format, false, text_margins);
757  }
758 
759  // Progress bar
760  production_progress = std::make_unique<QPixmap>(
761  QSize(6, target_height) * painter.device()->devicePixelRatio());
762  production_progress->setDevicePixelRatio(
763  painter.device()->devicePixelRatio());
764 
765  QPainter p(production_progress.get());
766 
767  // Draw
768  if (pcity->surplus[O_SHIELD] < 0) {
769  production_progress->fill(Qt::red);
770  } else {
771  production_progress->fill(Qt::black);
772  }
773  int total = universal_build_shield_cost(pcity, &pcity->production);
774  double current = double(pcity->shield_stock) / total;
775  double next_turn =
776  qBound(0.0, current + double(pcity->surplus[O_SHIELD]) / total, 1.0);
777 
778  current *= target_height;
779  next_turn *= target_height;
780 
781  // Surplus (yellow)
782  if (next_turn > current) {
783  p.fillRect(QRectF(0, target_height, 6, -next_turn),
784  QColor(200, 200, 60));
785  }
786 
787  // Stock (blue)
788  p.fillRect(QRectF(0, target_height, 6, -current), Qt::blue);
789 
790  // Negative surplus (red)
791  if (next_turn < current) {
792  p.fillRect(QRectF(0, target_height - current, 6, current - next_turn),
793  Qt::red);
794  }
795 
796  // Add it
797  line.add_icon(production_progress.get());
798 
799  // Icon
800  const QPixmap *xsprite = nullptr;
801  const auto &target = pcity->production;
802  if (can_see_inside && (VUT_UTYPE == target.kind)) {
803  xsprite =
804  get_unittype_sprite(t, target.value.utype, direction8_invalid());
805  } else if (can_see_inside && (target.kind == VUT_IMPROVEMENT)) {
806  xsprite = get_building_sprite(t, target.value.building);
807  }
808  if (xsprite) {
809  production_pix = std::make_unique<QPixmap>(
810  xsprite->scaledToHeight(target_height, Qt::SmoothTransformation));
811  line.add_icon(production_pix.get());
812  }
813  }
814 
815  // Lay out and draw the first line
816  line.do_layout();
817 
818  double width = std::ceil(line.size().width());
819  double height = line.height() + 2;
820  double x = std::floor(position.x() - width / 2 - 1);
821  double y = std::floor(position.y());
822 
823  // Draw the background
824  if (city_owner(pcity) == client_player()) {
825  painter.setPen(QPen(Qt::black, 1));
826  painter.setBrush(QColor(0, 0, 0, 100));
827  } else {
828  owner_color.setAlpha(90);
829  painter.setPen(QPen(owner_color, 1));
830  painter.setBrush(QBrush(owner_color));
831  }
832  painter.drawRoundedRect(QRectF(x, y, width + 2, height - 1), 7, 7);
833 
834  // Draw the text
835  line.paint(painter, QPointF(x + 1, y + 1));
836 
837  // Trade line
838  if (should_draw_trade_routes) {
839  line_of_text trade_line;
840 
841  // Icon
842  trade_line.add_icon(citybar->trade);
843 
844  // Text
845  char trade_routes[32];
846  color_std trade_routes_color;
847  get_city_mapview_trade_routes(pcity, trade_routes, sizeof(trade_routes),
848  &trade_routes_color);
849 
850  format.setFont(get_font(FONT_CITY_PROD));
851  format.setForeground(get_color(t, trade_routes_color));
852  trade_line.add_text(trade_routes, format, false, text_margins);
853 
854  // Lay it out
855  trade_line.do_layout();
856 
857  double trade_width = std::ceil(trade_line.size().width());
858  double trade_x = std::floor(position.x() - trade_width / 2 - 1);
859  double trade_y = y + height;
860 
861  // Draw the background
862  if (city_owner(pcity) == client_player()) {
863  painter.setPen(QPen(Qt::black, 1));
864  painter.setBrush(QColor(0, 0, 0, 100));
865  } else {
866  owner_color.setAlpha(90);
867  painter.setPen(QPen(owner_color, 1));
868  painter.setBrush(QBrush(owner_color));
869  }
870  painter.drawRoundedRect(QRectF(trade_x, y + line.height() + 2,
871  trade_width + 2, trade_line.height() + 1),
872  7, 7);
873 
874  // Draw the text
875  trade_line.paint(painter, QPointF(trade_x + 1, trade_y + 1));
876 
877  x = std::min(x, trade_x);
878  width = std::max(width, trade_width);
879  height += trade_line.height() + 2;
880  }
881 
882  return QRect(x, y, std::ceil(width + 2), std::ceil(height));
883 }
QFont get_font(client_font font)
Returns given font.
Definition: canvas.cpp:57
@ FONT_CITY_PROD
Definition: canvas.h:21
@ FONT_CITY_NAME
Definition: canvas.h:20
int city_granary_size(int city_size)
Generalized formula used to calculate granary size.
Definition: city.cpp:2034
struct player * city_owner(const struct city *pcity)
Return the owner of the city.
Definition: city.cpp:1083
int city_production_turns_to_build(const struct city *pcity, bool include_shield_stock)
Calculates the turns which are needed to build the requested production in the city.
Definition: city.cpp:784
citizens city_size_get(const struct city *pcity)
Get the city size.
Definition: city.cpp:1101
Abstraction for city bars of various styles.
Definition: citybar.h:32
static void option_changed(option *opt)
Called by the option code when the option has changed.
Definition: citybar.cpp:286
static const QVector< QString > * available_vector(const option *)
Returns the list of all available city bar styles.
Definition: citybar.cpp:271
static void set_current(const QString &name)
Sets the current city bar style.
Definition: citybar.cpp:307
static QStringList available()
Returns the list of all available city bar styles.
Definition: citybar.cpp:261
static std::unique_ptr< citybar_painter > s_current
Pointer to the city bar painter currently in use.
Definition: citybar.h:66
static citybar_painter * current()
Returns the current painter (never null).
Definition: citybar.cpp:295
Helper class to create a line of text.
Definition: citybar.cpp:46
void paint(QPainter &p, const QPointF &top_left) const
Paints the line at the given position.
Definition: citybar.cpp:226
double ideal_width() const
Returns the ideal line width (the sum of the width of all blocks)
Definition: citybar.cpp:135
double height() const
Definition: citybar.cpp:70
void add_text(const QString &text, const QTextCharFormat &format, bool shadow=true, const QMargins &margins=QMargins())
Adds text to the line with the given format.
Definition: citybar.cpp:107
void set_text_shadow_brush(const QBrush &brush)
Definition: citybar.cpp:66
void add_icon(const QPixmap *icon, const QMargins &margins=QMargins())
Adds an icon to the line.
Definition: citybar.cpp:94
void do_layout(double width=0)
Lays out the line to fill the given width.
Definition: citybar.cpp:152
std::vector< block > m_blocks
Definition: citybar.cpp:78
QBrush m_shadow_brush
Definition: citybar.cpp:75
void add_spacer()
Adds a spacer to the line.
Definition: citybar.cpp:85
QSizeF m_size
Definition: citybar.cpp:77
QSizeF size() const
Definition: citybar.cpp:71
QRect paint(QPainter &painter, const QPointF &position, const city *pcity) const override
Draws the "polished" city bar.
Definition: citybar.cpp:603
QRect paint(QPainter &painter, const QPointF &position, const city *pcity) const override
Draws a simple city bar.
Definition: citybar.cpp:334
QRect paint(QPainter &painter, const QPointF &position, const city *pcity) const override
Draws the traditional city bar with a dark background, two lines of text and colored borders.
Definition: citybar.cpp:434
bool client_is_global_observer()
Returns whether client is global observer.
struct player * client_player()
Either controlling or observing.
struct civclient client
QColor color_best_contrast(const QColor &subject, const QColor *candidates, int ncandidates)
Find the colour from 'candidates' with the best perceptual contrast from 'subject'.
QColor get_color(const struct tileset *t, enum color_std stdcolor)
Return a pointer to the given "standard" color.
QColor get_player_color(const struct tileset *t, const struct player *pplayer)
Return the color of the player.
@ O_SHIELD
Definition: fc_types.h:86
@ O_FOOD
Definition: fc_types.h:85
#define _(String)
Definition: fcintl.h:50
#define N_(String)
Definition: fcintl.h:52
const char * name
Definition: inputfile.cpp:118
Constants used when drawing the button.
const char * option_str_get(const struct option *poption)
Returns the current value of this string option.
Definition: options.cpp:553
client_options * gui_options
Definition: options.cpp:74
bool can_player_see_units_in_city(const struct player *pplayer, const struct city *pcity)
Return TRUE iff the player can see units in the city.
Definition: player.cpp:1045
int universal_build_shield_cost(const struct city *pcity, const struct universal *target)
Return the number of shields it takes to build this universal.
#define ARRAY_SIZE(x)
Definition: shared.h:79
Definition: city.h:291
struct city::@15::@18 client
int surplus[O_LAST]
Definition: city.h:324
int food_stock
Definition: city.h:338
struct player * owner
Definition: city.h:294
struct universal production
Definition: city.h:368
citizens size
Definition: city.h:301
struct tile * tile
Definition: city.h:293
int shield_stock
Definition: city.h:339
QPixmap * trade
Definition: tilespec.h:190
QPixmap * occupied
Definition: tilespec.h:190
std::vector< QPixmap * > occupancy
Definition: tilespec.h:191
QPixmap * shields
Definition: tilespec.h:190
QPixmap * food
Definition: tilespec.h:190
QPixmap * background
Definition: tilespec.h:190
struct connection conn
Definition: client_main.h:89
bool draw_city_names
Definition: options.h:123
bool draw_city_productions
Definition: options.h:125
bool draw_city_trade_routes
Definition: options.h:127
bool draw_city_growth
Definition: options.h:124
char default_city_bar_style_name[512]
Definition: options.h:58
struct player * playing
Definition: connection.h:142
const QPixmap * icon
Definition: citybar.cpp:53
enum line_of_text::block::@118 mode
QTextCharFormat format
Definition: citybar.cpp:51
QMargins margins
Definition: citybar.cpp:54
The base class for options.
Definition: options.cpp:209
struct unit_list * units
Definition: tile.h:50
const QPixmap * get_city_flag_sprite(const struct tileset *t, const struct city *pcity)
Return the flag graphic to be used by the city.
Definition: tilespec.cpp:3052
const struct citybar_sprites * get_citybar_sprites(const struct tileset *t)
Return all the sprites used for city bar drawing.
Definition: tilespec.cpp:3523
const QPixmap * get_unittype_sprite(const struct tileset *t, const struct unit_type *punittype, enum direction8 facing, const QColor &replace)
Return the sprite for the unit type (the base "unit" sprite).
Definition: tilespec.cpp:3416
struct tileset * get_tileset()
Returns the tileset.
Definition: tilespec.cpp:326
const QPixmap * get_building_sprite(const struct tileset *t, const struct impr_type *pimprove)
Return the sprite for the building/improvement.
Definition: tilespec.cpp:3394
void get_city_mapview_production(const city *pcity, char *buffer, size_t buffer_len)
Find the mapview city production text for the given city, and place it into the buffer.
void get_city_mapview_name_and_growth(const city *pcity, char *name_buffer, size_t name_buffer_len, char *growth_buffer, size_t growth_buffer_len, enum color_std *growth_color, enum color_std *production_color)
Fill the two buffers which information about the city which is shown below it.
void get_city_mapview_trade_routes(const city *pcity, char *trade_routes_buffer, size_t trade_routes_buffer_len, enum color_std *pcolor)
Find the mapview city trade routes text for the given city, and place it into the buffer.
void update_map_canvas_visible()
Schedules an update of (only) the visible part of the map at the next unqueue_mapview_update().