← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/fh1_width_and_mapobject_messages into lp:widelands

 

GunChleoc has proposed merging lp:~widelands-dev/widelands/fh1_width_and_mapobject_messages into lp:widelands.

Commit message:
The new font renderer now sets the width properly and supports player color for images.
- Added width property to img tag
- Buildings and ships now send their messages with the new font renderer.
- Messages try to render with the new font renderer first, then fall back to the old font renderer for layouting messages that haven't been converted yet (and from savegames).

Requested reviews:
  Widelands Developers (widelands-dev)

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/fh1_width_and_mapobject_messages/+merge/318189

Ships and buildings now send their messages with the new font renderer. Status messages and loading old games should still work.

Waresdisplay also needs testing.
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/fh1_width_and_mapobject_messages into lp:widelands.
=== added file 'data/tribes/buildings/productionsites/barbarians/shipyard/representative_image_pc.png'
Binary files data/tribes/buildings/productionsites/barbarians/shipyard/representative_image_pc.png	1970-01-01 00:00:00 +0000 and data/tribes/buildings/productionsites/barbarians/shipyard/representative_image_pc.png	2017-02-24 09:24:55 +0000 differ
=== added file 'data/tribes/buildings/warehouses/barbarians/warehouse/representative_image_pc.png'
Binary files data/tribes/buildings/warehouses/barbarians/warehouse/representative_image_pc.png	1970-01-01 00:00:00 +0000 and data/tribes/buildings/warehouses/barbarians/warehouse/representative_image_pc.png	2017-02-24 09:24:55 +0000 differ
=== modified file 'src/graphic/text/rt_parse.cc'
--- src/graphic/text/rt_parse.cc	2017-02-20 12:10:39 +0000
+++ src/graphic/text/rt_parse.cc	2017-02-24 09:24:55 +0000
@@ -204,6 +204,8 @@
 		TagConstraint tc;
 		tc.allowed_attrs.insert("src");
 		tc.allowed_attrs.insert("ref");
+		tc.allowed_attrs.insert("color");
+		tc.allowed_attrs.insert("width");
 		tc.text_allowed = false;
 		tc.has_closing_tag = false;
 		tag_constraints_["img"] = tc;

=== modified file 'src/graphic/text/rt_render.cc'
--- src/graphic/text/rt_render.cc	2017-02-23 21:46:48 +0000
+++ src/graphic/text/rt_render.cc	2017-02-24 09:24:55 +0000
@@ -26,7 +26,7 @@
 #include <vector>
 
 #include <SDL.h>
-#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string.hpp>
 #include <boost/format.hpp>
 
 #include "base/i18n.h"
@@ -39,6 +39,7 @@
 #include "graphic/graphic.h"
 #include "graphic/image_cache.h"
 #include "graphic/image_io.h"
+#include "graphic/playercolor.h"
 #include "graphic/text/bidi.h"
 #include "graphic/text/font_io.h"
 #include "graphic/text/font_set.h"
@@ -48,6 +49,7 @@
 #include "graphic/text_layout.h"
 #include "graphic/texture.h"
 #include "io/filesystem/filesystem_exceptions.h"
+#include "io/filesystem/layered_filesystem.h"
 
 using namespace std;
 
@@ -63,6 +65,8 @@
 	uint8_t left, top, right, bottom;
 };
 
+enum class WidthUnit { kAbsolute, kPercent, kShrink, kFill };
+
 struct NodeStyle {
 	UI::FontSet const* fontset;
 	string font_face;
@@ -658,6 +662,8 @@
 public:
 	DivTagRenderNode(NodeStyle& ns)
 	   : RenderNode(ns),
+	     desired_width_(0),
+	     desired_width_unit_(WidthUnit::kShrink),
 	     background_color_(0, 0, 0),
 	     is_background_color_set_(false),
 	     background_image_(nullptr) {
@@ -679,6 +685,14 @@
 		return height();
 	}
 
+	uint16_t desired_width() const {
+		return desired_width_;
+	}
+
+	WidthUnit desired_width_unit() const {
+		return desired_width_unit_;
+	}
+
 	Texture* render(TextureCache* texture_cache) override {
 		if (width() > g_gr->max_texture_size() || height() > g_gr->max_texture_size()) {
 			const std::string error_message =
@@ -742,6 +756,10 @@
 		h_ = inner_h;
 		margin_ = margin;
 	}
+	void set_desired_width(int w, WidthUnit unit) {
+		desired_width_ = w;
+		desired_width_unit_ = unit;
+	}
 	void set_background(RGBColor clr) {
 		background_color_ = clr;
 		is_background_color_set_ = true;
@@ -758,6 +776,8 @@
 	}
 
 private:
+	uint16_t desired_width_;
+	WidthUnit desired_width_unit_;
 	uint16_t w_, h_;
 	vector<RenderNode*> nodes_to_render_;
 	Borders margin_;
@@ -769,28 +789,41 @@
 
 class ImgRenderNode : public RenderNode {
 public:
-	ImgRenderNode(NodeStyle& ns, const Image& image) : RenderNode(ns), image_(image) {
+	ImgRenderNode(NodeStyle& ns,
+	              const std::string& image_filename,
+	              double scale,
+	              const RGBColor& color)
+	   : RenderNode(ns), image_(g_gr->images().get(image_filename)), scale_(scale), color_(color) {
+		if (color_.hex_value() != "000000") {
+			std::string pc_image_filename = image_filename;
+			boost::replace_all(pc_image_filename, ".png", "_pc.png");
+			if (g_fs->file_exists(pc_image_filename)) {
+				image_ = playercolor_image(&color_, image_, g_gr->images().get(pc_image_filename));
+			}
+		}
 	}
 
 	uint16_t width() override {
-		return image_.width();
+		return scale_ * image_->width();
 	}
 	uint16_t height() override {
-		return image_.height();
+		return scale_ * image_->height();
 	}
 	uint16_t hotspot_y() override {
-		return image_.height();
+		return scale_ * image_->height();
 	}
 	Texture* render(TextureCache* texture_cache) override;
 
 private:
-	const Image& image_;
+	const Image* image_;
+	const double scale_;
+	const RGBColor color_;
 };
 
 Texture* ImgRenderNode::render(TextureCache* /* texture_cache */) {
-	Texture* rv = new Texture(image_.width(), image_.height());
-	rv->blit(Rectf(0, 0, image_.width(), image_.height()), image_,
-	         Rectf(0, 0, image_.width(), image_.height()), 1., BlendMode::Copy);
+	Texture* rv = new Texture(width(), height());
+	rv->blit(Rectf(0, 0, width(), height()), *image_, Rectf(0, 0, image_->width(), image_->height()),
+	         1., BlendMode::Copy);
 	return rv;
 }
 // End: Helper Stuff
@@ -1031,7 +1064,27 @@
 
 	void enter() override {
 		const AttrMap& a = tag_.attrs();
-		render_node_ = new ImgRenderNode(nodestyle_, *image_cache_->get(a["src"].get_string()));
+		RGBColor color;
+		const std::string image_filename = a["src"].get_string();
+		double scale = 1.0;
+
+		if (a.has("color")) {
+			color = a["color"].get_color();
+		}
+		if (a.has("width")) {
+			int width = a["width"].get_int();
+			if (width > renderer_style_.overall_width) {
+				log("WARNING: Font renderer: Specified image width of %d exceeds the overall available "
+				    "width of %d. Setting width to %d.\n",
+				    width, renderer_style_.overall_width, renderer_style_.overall_width);
+				width = renderer_style_.overall_width;
+			}
+			const int image_width = image_cache_->get(image_filename)->width();
+			if (width < image_width) {
+				scale = double(width) / image_width;
+			}
+		}
+		render_node_ = new ImgRenderNode(nodestyle_, image_filename, scale, color);
 	}
 	void emit_nodes(vector<RenderNode*>& nodes) override {
 		nodes.push_back(render_node_);
@@ -1198,6 +1251,18 @@
 			}
 		}
 
+		switch (render_node_->desired_width_unit()) {
+		case WidthUnit::kPercent:
+			w_ = render_node_->desired_width() * renderer_style_.overall_width / 100;
+			renderer_style_.remaining_width -= w_;
+			break;
+		case WidthUnit::kFill:
+			w_ = renderer_style_.remaining_width;
+			renderer_style_.remaining_width = 0;
+			break;
+		default:;  // Do nothing
+		}
+
 		// Layout takes ownership of subnodes
 		Layout layout(subnodes);
 		vector<RenderNode*> nodes_to_render;
@@ -1205,6 +1270,9 @@
 		uint16_t extra_width = 0;
 		if (w_ < INFINITE_WIDTH && w_ > max_line_width) {
 			extra_width = w_ - max_line_width;
+		} else if (render_node_->desired_width_unit() == WidthUnit::kShrink) {
+			w_ = max_line_width;
+			renderer_style_.remaining_width -= w_;
 		}
 
 		// Collect all tags from children
@@ -1225,9 +1293,7 @@
 			w_ = max_line_width;
 		}
 
-		if (renderer_style_.remaining_width >= w_) {
-			renderer_style_.remaining_width -= w_;
-		} else {
+		if (renderer_style_.remaining_width < w_) {
 			renderer_style_.remaining_width = renderer_style_.overall_width;
 		}
 
@@ -1242,18 +1308,29 @@
 	virtual void handle_unique_attributes() {
 		const AttrMap& a = tag_.attrs();
 		if (a.has("width")) {
+			shrink_to_fit_ = false;
+			w_ = INFINITE_WIDTH;
 			std::string width_string = a["width"].get_string();
 			if (width_string == "*") {
-				w_ = renderer_style_.remaining_width;
+				render_node_->set_desired_width(INFINITE_WIDTH, WidthUnit::kFill);
 			} else if (boost::algorithm::ends_with(width_string, "%")) {
 				width_string = width_string.substr(0, width_string.length() - 1);
-				uint8_t new_width_percent = strtol(width_string.c_str(), nullptr, 10);
-				w_ = floor(renderer_style_.overall_width * new_width_percent / 100);
-				w_ = std::min(w_, renderer_style_.remaining_width);
+				uint8_t width_percent = strtol(width_string.c_str(), nullptr, 10);
+				if (width_percent > 100) {
+					log("WARNING: Font renderer: Do not use width > 100%%\n");
+					width_percent = 100;
+				}
+				render_node_->set_desired_width(width_percent, WidthUnit::kPercent);
 			} else {
 				w_ = a["width"].get_int();
+				if (w_ > renderer_style_.overall_width) {
+					log("WARNING: Font renderer: Specified width of %d exceeds the overall available "
+					    "width of %d. Setting width to %d.\n",
+					    w_, renderer_style_.overall_width, renderer_style_.overall_width);
+					w_ = renderer_style_.overall_width;
+				}
+				render_node_->set_desired_width(w_, WidthUnit::kAbsolute);
 			}
-			shrink_to_fit_ = false;
 		}
 		if (a.has("float")) {
 			const string s = a["float"].get_string();

=== modified file 'src/graphic/text_constants.h'
--- src/graphic/text_constants.h	2017-01-25 18:55:59 +0000
+++ src/graphic/text_constants.h	2017-02-24 09:24:55 +0000
@@ -25,6 +25,7 @@
 /// Font Sizes
 #define UI_FONT_SIZE_BIG 22
 #define UI_FONT_SIZE_SMALL 14
+#define UI_FONT_SIZE_MESSAGE 12
 #define UI_FONT_SIZE_ULTRASMALL 10
 constexpr int kMinimumFontSize = 6;
 

=== modified file 'src/graphic/text_layout.cc'
--- src/graphic/text_layout.cc	2017-02-18 14:03:02 +0000
+++ src/graphic/text_layout.cc	2017-02-24 09:24:55 +0000
@@ -50,6 +50,14 @@
 	   ->height();
 }
 
+bool is_paragraph(const std::string& text) {
+	return boost::starts_with(text, "<p");
+}
+
+bool is_div(const std::string& text) {
+	return boost::starts_with(text, "<div");
+}
+
 std::string richtext_escape(const std::string& given_text) {
 	std::string text = given_text;
 	boost::replace_all(text, "&", "&amp;");  // Must be performed first
@@ -140,6 +148,13 @@
 	return f.str();
 }
 
+std::string as_message(const std::string& heading, const std::string& body) {
+	return ((boost::format(
+	            "<rt><p><font size=18 bold=1 color=D1D1D1>%s<br></font></p><vspace gap=6>%s</rt>") %
+				heading % (is_paragraph(body) || is_div(body) ? body : "<p>" + body + "</p>"))
+	           .str());
+}
+
 const Image* autofit_ui_text(const std::string& text, int width, RGBColor color, int fontsize) {
 	const Image* result = UI::g_fh1->render(as_uifont(richtext_escape(text), fontsize, color));
 	if (width > 0) {  // Autofit

=== modified file 'src/graphic/text_layout.h'
--- src/graphic/text_layout.h	2017-01-25 18:55:59 +0000
+++ src/graphic/text_layout.h	2017-02-24 09:24:55 +0000
@@ -90,6 +90,7 @@
 std::string as_tooltip(const std::string&);
 std::string as_waresinfo(const std::string&);
 std::string as_game_tip(const std::string&);
+std::string as_message(const std::string& heading, const std::string& body);
 
 /**
   * Render 'text' as ui_font. If 'width' > 0 and the rendered image is too

=== modified file 'src/logic/map_objects/tribes/building.cc'
--- src/logic/map_objects/tribes/building.cc	2017-01-25 18:55:59 +0000
+++ src/logic/map_objects/tribes/building.cc	2017-02-24 09:24:55 +0000
@@ -727,8 +727,10 @@
  * It will have the building's coordinates, and display a picture of the
  * building in its description.
  *
- * \param msgsender a computer-readable description of why the message was sent
- * \param title user-visible title of the message
+ * \param msgtype a computer-readable description of why the message was sent
+ * \param title short title to be displayed in message listings
+ * \param icon_filename the filename to be used for the icon in message listings
+ * \param heading long title to be displayed within the message
  * \param description user-visible message body, will be placed in an
  *   appropriate rich-text paragraph
  * \param link_to_building_lifetime if true, the message will be deleted when this
@@ -746,26 +748,13 @@
                             bool link_to_building_lifetime,
                             uint32_t throttle_time,
                             uint32_t throttle_radius) {
-	// TODO(sirver): add support into the font renderer to get to representative
-	// animations of buildings so that the messages can still be displayed, even
-	// after reload.
 	const std::string& img = descr().representative_image_filename();
-	std::string rt_description;
-	rt_description.reserve(strlen("<rt image=") + img.size() + 1 +
-	                       strlen("<p font-size=14 font-face=serif>") + description.size() +
-	                       strlen("</p></rt>"));
-	rt_description = "<rt image=";
-	rt_description += img;
-	{
-		std::string::iterator it = rt_description.end() - 1;
-		for (; it != rt_description.begin() && *it != '?'; --it) {
-		}
-		for (; *it == '?'; --it)
-			*it = '0';
-	}
-	rt_description = (boost::format("%s><p font-face=serif font-size=14>%s</p></rt>") %
-	                  rt_description % description)
-	                    .str();
+	const int width = descr().representative_image()->width();
+	const std::string rt_description =
+		(boost::format("<div padding_r=10><p><img width=%d src=%s color=%s></p></div>"
+							"<div width=*><p><font size=%d>%s</font></p></div>") %
+	    width % img % owner().get_playercolor().hex_value() % UI_FONT_SIZE_MESSAGE % description)
+	      .str();
 
 	Message* msg =
 	   new Message(msgtype, game.get_gametime(), title, icon_filename, heading, rt_description,

=== modified file 'src/logic/map_objects/tribes/ship.cc'
--- src/logic/map_objects/tribes/ship.cc	2017-02-21 20:38:33 +0000
+++ src/logic/map_objects/tribes/ship.cc	2017-02-24 09:24:55 +0000
@@ -1033,25 +1033,22 @@
  * It will have the ship's coordinates, and display a picture in its description.
  *
  * \param msgsender a computer-readable description of why the message was sent
- * \param title user-visible title of the message
+ * \param title short title to be displayed in message listings
+ * \param heading long title to be displayed within the message
  * \param description user-visible message body, will be placed in an appropriate rich-text
  *paragraph
- * \param picture picture name relative to the data/ directory
+ * \param picture the filename to be used for the icon in message listings
  */
 void Ship::send_message(Game& game,
                         const std::string& title,
                         const std::string& heading,
                         const std::string& description,
                         const std::string& picture) {
-	std::string rt_description;
-	if (picture.size() > 3) {
-		rt_description = "<rt image=";
-		rt_description += picture;
-		rt_description += "><p font-size=14 font-face=serif>";
-	} else
-		rt_description = "<rt><p font-size=14 font-face=serif>";
-	rt_description += description;
-	rt_description += "</p></rt>";
+	const std::string rt_description =
+		(boost::format("<div padding_r=10><p><img src=%s></p></div>"
+							"<div width=*><p><font size=%d>%s</font></p></div>") %
+	    picture % UI_FONT_SIZE_MESSAGE % description)
+	      .str();
 
 	Message* msg = new Message(Message::Type::kSeafaring, game.get_gametime(), title, picture,
 	                           heading, rt_description, get_position(), serial_);

=== modified file 'src/logic/map_objects/tribes/soldier.cc'
--- src/logic/map_objects/tribes/soldier.cc	2017-01-31 07:18:19 +0000
+++ src/logic/map_objects/tribes/soldier.cc	2017-02-24 09:24:55 +0000
@@ -1348,13 +1348,13 @@
 					   game, *new Message(
 					            Message::Type::kGameLogic, game.get_gametime(), descr().descname(),
 					            "images/ui_basic/menu_help.png", _("Logic error"),
-					            (boost::format("<rt><p font-size=12>%s</p></rt>") % messagetext).str(),
+					            messagetext,
 					            get_position(), serial_));
 					opponent.owner().add_message(
 					   game, *new Message(
 					            Message::Type::kGameLogic, game.get_gametime(), descr().descname(),
 					            "images/ui_basic/menu_help.png", _("Logic error"),
-					            (boost::format("<rt><p font-size=12>%s</p></rt>") % messagetext).str(),
+					            messagetext,
 					            opponent.get_position(), serial_));
 					game.game_controller()->set_desired_speed(0);
 					return pop_task(game);

=== modified file 'src/logic/map_objects/tribes/worker.cc'
--- src/logic/map_objects/tribes/worker.cc	2017-02-11 12:38:17 +0000
+++ src/logic/map_objects/tribes/worker.cc	2017-02-24 09:24:55 +0000
@@ -1681,7 +1681,7 @@
 		owner().add_message(
 		   game, *new Message(Message::Type::kGameLogic, game.get_gametime(), _("Worker"),
 		                      "images/ui_basic/menu_help.png", _("Worker got lost!"),
-		                      (boost::format("<rt><p font-size=12>%s</p></rt>") % message).str(),
+		                      message,
 		                      get_position()),
 		   serial_);
 		set_location(nullptr);

=== modified file 'src/ui_basic/multilinetextarea.h'
--- src/ui_basic/multilinetextarea.h	2017-01-25 18:55:59 +0000
+++ src/ui_basic/multilinetextarea.h	2017-02-24 09:24:55 +0000
@@ -68,8 +68,8 @@
 	void set_color(RGBColor fg) {
 		color_ = fg;
 	}
-	void force_new_renderer() {
-		force_new_renderer_ = true;
+	void force_new_renderer(bool force = true) {
+		force_new_renderer_ = force;
 	}
 
 	// Drawing and event handlers

=== modified file 'src/wui/game_message_menu.cc'
--- src/wui/game_message_menu.cc	2017-01-25 18:55:59 +0000
+++ src/wui/game_message_menu.cc	2017-02-24 09:24:55 +0000
@@ -326,12 +326,19 @@
 				   game.get_gametime(), player.player_number(), id));
 			}
 			centerviewbtn_->set_enabled(message->position());
-
-			message_body.set_text(
-			   (boost::format("<rt><p font-size=18 font-weight=bold font-color=D1D1D1>%s<br></p>"
-			                  "<p font-size=8> <br></p></rt>%s") %
-			    message->heading() % message->body())
-			      .str());
+			// TODO(GunChleoc): Programming by exception is ugly, but we need try/catch here for saveloading.
+			// Revisit this when we delete the old font renderer.
+			try {
+				message_body.force_new_renderer();
+				message_body.set_text(as_message(message->heading(), message->body()));
+			} catch (const std::exception&) {
+				message_body.force_new_renderer(false);
+				message_body.set_text(
+								(boost::format("<rt><p font-size=18 font-weight=bold font-color=D1D1D1>%s<br></p>"
+													"<p font-size=8> <br></p></rt>%s") %
+								 message->heading() % message->body())
+									.str());
+			}
 			return;
 		}
 	}

=== modified file 'src/wui/waresdisplay.cc'
--- src/wui/waresdisplay.cc	2017-02-20 12:10:39 +0000
+++ src/wui/waresdisplay.cc	2017-02-24 09:24:55 +0000
@@ -431,7 +431,7 @@
 				       tribe.get_ware_descr(c->first)->icon_filename() +
 				       "\"></p></div><div width=26 background=000000><p><font size=9>" +
 				       boost::lexical_cast<std::string>(static_cast<int32_t>(c->second)) +
-				       "</font></p></div></p></div>";
+						 "</font></p></div></p></div>";
 			}
 	return ret;
 }


Follow ups