Text wrapping in TextScroller, InfoBox and InfoBlock respects variable-width fonts.
authorChristoph Sommer <mail@christoph-sommer.de>
Sun, 15 Apr 2007 15:41:27 +0000 (15:41 +0000)
committerChristoph Sommer <mail@christoph-sommer.de>
Sun, 15 Apr 2007 15:41:27 +0000 (15:41 +0000)
InfoBlock objects' height adapts to text height.

SVN-Revision: 4981

src/object/infoblock.cpp
src/object/infoblock.hpp
src/textscroller.cpp
src/textscroller.hpp
src/video/font.cpp
src/video/font.hpp

index e5957f3..dda84e6 100644 (file)
 #include "sector.hpp"
 #include "log.hpp"
 #include "object/player.hpp"
+#include "main.hpp"
 
 namespace {
   const float SCROLL_DELAY = 0.5;
   const float SCROLL_DISTANCE = 16;
+  const float WIDTH = 400;
+  const float HEIGHT = 200;
 }
 
 InfoBlock::InfoBlock(const lisp::Lisp& lisp)
-  : Block(sprite_manager->create("images/objects/bonus_block/infoblock.sprite")), shown_pct(0), dest_pct(0), slowdown_scroll(0)
+  : Block(sprite_manager->create("images/objects/bonus_block/infoblock.sprite")), shown_pct(0), dest_pct(0)
 {
   Vector pos;
   lisp.get("x", pos.x);
@@ -48,11 +51,18 @@ InfoBlock::InfoBlock(const lisp::Lisp& lisp)
   //stopped = false;
   //ringing = new AmbientSound(get_pos(), 0.5, 300, 1, "sounds/phone.wav");
   //Sector::current()->add_object(ringing);
-  infoBox.reset(new InfoBox(message));
+
+  // Split text string lines into a vector
+  lines = InfoBoxLine::split(message, 400);
+  lines_height = 0;
+  for(size_t i = 0; i < lines.size(); ++i) lines_height+=lines[i]->get_height();
 }
 
 InfoBlock::~InfoBlock()
 {
+  for(std::vector<InfoBoxLine*>::iterator i = lines.begin(); i != lines.end(); i++) {
+    delete *i;
+  }
 }
 
 void
@@ -101,6 +111,8 @@ InfoBlock::get_nearest_player()
 void
 InfoBlock::update(float delta)
 {
+  Block::update(delta);
+
   if (delta == 0) return;
 
   // hide message if player is too far away or above infoblock
@@ -112,16 +124,6 @@ InfoBlock::update(float delta)
       Vector dist = (p2 - p1);
       float d = dist.norm();
       if (d > 128 || dist.y < 0) dest_pct = 0;
-      slowdown_scroll += delta; 
-      if ( slowdown_scroll > SCROLL_DELAY ) {
-        slowdown_scroll = 0; 
-        if (dist.x > SCROLL_DISTANCE) {
-          infoBox->scrolldown();
-        }
-        if( dist.x < -SCROLL_DISTANCE) {
-          infoBox->scrollup();
-        }
-      }
     }
   }
 
@@ -137,13 +139,38 @@ InfoBlock::draw(DrawingContext& context)
 {
   Block::draw(context);
 
-  if (shown_pct > 0) {
-    context.push_transform();
-    context.set_translation(Vector(0, 0));
-    context.set_alpha(shown_pct);
-    infoBox->draw(context);
-    context.pop_transform();
+  if (shown_pct <= 0) return;
+
+  context.push_transform();
+  //context.set_translation(Vector(0, 0));
+  context.set_alpha(shown_pct);
+
+  //float x1 = SCREEN_WIDTH/2-200;
+  //float y1 = SCREEN_HEIGHT/2-200;
+  float border = 8;
+  float width = 400; // this is the text width only
+  float height = lines_height; // this is the text height only
+  float x1 = (get_bbox().p1.x + get_bbox().p2.x)/2 - width/2;
+  float x2 = (get_bbox().p1.x + get_bbox().p2.x)/2 + width/2;
+  float y1 = original_y - height;
+
+  // lines_height includes one ITEMS_SPACE too much, so the bottom border is reduced by 4px
+  context.draw_filled_rect(Vector(x1-border, y1-border), Vector(width+2*border, height+2*border-4), Color(0.6f, 0.7f, 0.8f, 0.5f), LAYER_GUI-50);
+
+  float y = y1;
+  for(size_t i = 0; i < lines.size(); ++i) {
+    if(y >= y1 + height) {
+      //log_warning << "Too many lines of text in InfoBlock" << std::endl;
+      //dest_pct = 0;
+      //shown_pct = 0;
+      break;
+    }
+
+    lines[i]->draw(context, Rect(x1, y, x2, y), LAYER_GUI-50+1);
+    y += lines[i]->get_height();
   }
+
+  context.pop_transform();
 }
 
 void
index f67b81a..21e2ae4 100644 (file)
@@ -42,12 +42,11 @@ protected:
   //bool stopped;
   float shown_pct; /**< Value in the range of 0..1, depending on how much of the infobox is currently shown */
   float dest_pct; /**< With each call to update(), shown_pct will slowly transition to this value */
-  std::auto_ptr<InfoBox> infoBox;
 
   Player* get_nearest_player();
 
-private:
-  float slowdown_scroll;
+  std::vector<InfoBoxLine*> lines; /**< lines of text (or images) to display */
+  float lines_height;
 };
 
 #endif
index 5cf82dc..74dc067 100644 (file)
@@ -70,7 +70,7 @@ TextScroller::TextScroller(const std::string& filename)
   }
 
   // Split text string lines into a vector
-  lines = InfoBoxLine::split(text, 40);
+  lines = InfoBoxLine::split(text, SCREEN_WIDTH - 2*LEFT_BORDER);
 
   // load background image
   background.reset(new Surface("images/background/" + background_file));
@@ -124,7 +124,7 @@ TextScroller::draw(DrawingContext& context)
 
   float y = SCREEN_HEIGHT - scroll;
   for(size_t i = 0; i < lines.size(); i++) {
-    lines[i]->draw(context, Vector(LEFT_BORDER, y), LAYER_GUI);
+    lines[i]->draw(context, Rect(LEFT_BORDER, y, SCREEN_WIDTH - 2*LEFT_BORDER, y), LAYER_GUI);
     y += lines[i]->get_height();
   }
 
@@ -138,7 +138,7 @@ InfoBox::InfoBox(const std::string& text)
   : firstline(0)
 {
   // Split text string lines into a vector
-  lines = InfoBoxLine::split(text, 23);
+  lines = InfoBoxLine::split(text, 400);
 
   try
   {
@@ -182,7 +182,7 @@ InfoBox::draw(DrawingContext& context)
       break;
     }
 
-    lines[i]->draw(context, Vector(x1, y), LAYER_GUI);
+    lines[i]->draw(context, Rect(x1, y, x1+width, y), LAYER_GUI);
     y += lines[i]->get_height();
   }
 
@@ -225,47 +225,78 @@ InfoBox::pagedown()
 {
 }
 
-InfoBoxLine::InfoBoxLine(char format_char, const std::string& text) : lineType(NORMAL), font(white_text), text(text), image(0)
-{
+namespace {
+Font* get_font_by_format_char(char format_char) {
   switch(format_char)
   {
     case ' ':
-      lineType = SMALL;
-      font = white_small_text;
+      return white_small_text;
       break;
     case '\t':
-      lineType = NORMAL;
-      font = white_text;
+      return white_text;
       break;
     case '-':
-      lineType = HEADING;
-      font = white_big_text;
+      return white_big_text;
       break;
     case '*':
-      lineType = REFERENCE;
-      font = blue_text;
+      return blue_text;
       break;
     case '#':
-      lineType = NORMAL_LEFT;
-      font = white_text;
+      return white_text;
       break;
     case '!':
-      lineType = IMAGE;
-      image = new Surface(text);
+      return 0;
       break;
     default:
+      return 0;
       log_warning << "Unknown format_char: '" << format_char << "'" << std::endl;
       break;
   }
 }
 
+InfoBoxLine::LineType get_linetype_by_format_char(char format_char) {
+  switch(format_char)
+  {
+    case ' ':
+      return InfoBoxLine::SMALL;
+      break;
+    case '\t':
+      return InfoBoxLine::NORMAL;
+      break;
+    case '-':
+      return InfoBoxLine::HEADING;
+      break;
+    case '*':
+      return InfoBoxLine::REFERENCE;
+      break;
+    case '#':
+      return InfoBoxLine::NORMAL_LEFT;
+      break;
+    case '!':
+      return InfoBoxLine::IMAGE;
+      break;
+    default:
+      return InfoBoxLine::SMALL;
+      log_warning << "Unknown format_char: '" << format_char << "'" << std::endl;
+      break;
+  }
+}
+}
+
+InfoBoxLine::InfoBoxLine(char format_char, const std::string& text) : lineType(NORMAL), font(white_text), text(text), image(0)
+{
+  font = get_font_by_format_char(format_char);
+  lineType = get_linetype_by_format_char(format_char);
+  if (lineType == IMAGE) image = new Surface(text);
+}
+
 InfoBoxLine::~InfoBoxLine()
 {
   delete image;
 }
 
 const std::vector<InfoBoxLine*>
-InfoBoxLine::split(const std::string& text, int line_length)
+InfoBoxLine::split(const std::string& text, float width)
 {
   std::vector<InfoBoxLine*> lines;
 
@@ -300,7 +331,10 @@ InfoBoxLine::split(const std::string& text, int line_length)
     // append wrapped parts of line into list
     std::string overflow;
     do {
-      lines.push_back(new InfoBoxLine(format_char, Font::wrap_to_chars(s, line_length, &overflow)));
+      Font* font = get_font_by_format_char(format_char);
+      std::string s2 = s;
+      if (font) s2 = font->wrap_to_width(s2, width, &overflow);
+      lines.push_back(new InfoBoxLine(format_char, s2));
       s = overflow;
     } while (s.length() > 0);
 
@@ -310,17 +344,18 @@ InfoBoxLine::split(const std::string& text, int line_length)
 }
 
 void
-InfoBoxLine::draw(DrawingContext& context, const Vector& position, int layer)
+InfoBoxLine::draw(DrawingContext& context, const Rect& bbox, int layer)
 {
+  Vector position = bbox.p1;
   switch (lineType) {
     case IMAGE:
-      context.draw_surface(image, Vector( (SCREEN_WIDTH - image->get_width()) / 2, position.y), layer);
+      context.draw_surface(image, Vector( (bbox.p1.x + bbox.p2.x - image->get_width()) / 2, position.y), layer);
       break;
     case NORMAL_LEFT:
       context.draw_text(font, text, Vector(position.x, position.y), ALIGN_LEFT, layer);
       break;
     default:
-      context.draw_text(font, text, Vector(SCREEN_WIDTH/2, position.y), ALIGN_CENTER, layer);
+      context.draw_text(font, text, Vector((bbox.p1.x + bbox.p2.x) / 2, position.y), ALIGN_CENTER, layer);
       break;
   }
 }
index 5198eb9..cc166dc 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "screen.hpp"
 #include "math/vector.hpp"
+#include "math/rect.hpp"
 
 class DrawingContext;
 class Surface;
@@ -37,21 +38,22 @@ class Font;
  */
 class InfoBoxLine
 {
-private:
+public:
   enum LineType { NORMAL, NORMAL_LEFT, SMALL, HEADING, REFERENCE, IMAGE};
-  LineType lineType;
-  Font* font;
-  std::string text;
-  Surface* image;
 
-public:
   InfoBoxLine(char format_char, const std::string& text);
   ~InfoBoxLine();
 
-  void draw(DrawingContext& context, const Vector& position, int layer);
+  void draw(DrawingContext& context, const Rect& bbox, int layer);
   float get_height();
 
-  static const std::vector<InfoBoxLine*> split(const std::string& text, int line_length);
+  static const std::vector<InfoBoxLine*> split(const std::string& text, float width);
+
+private:
+  InfoBoxLine::LineType lineType;
+  Font* font;
+  std::string text;
+  Surface* image;
 };
 
 /** This class is displaying a box with information text inside the game
index 7c6d7cf..f967892 100644 (file)
@@ -241,6 +241,30 @@ Font::wrap_to_chars(const std::string& s, int line_length, std::string* overflow
   return s;
 }
 
+std::string
+Font::wrap_to_width(const std::string& s, float width, std::string* overflow)
+{
+  // if text is already smaller, return full text
+  if (get_text_width(s) <= width) {
+    if (overflow) *overflow = "";
+    return s;
+  }
+
+  // if we can find a whitespace character to break at, return text up to this character
+  for (int i = s.length()-1; i >= 0; i--) {
+    std::string s2 = s.substr(0,i);
+    if (s[i] != ' ') continue;
+    if (get_text_width(s2) <= width) {
+      if (overflow) *overflow = s.substr(i+1);
+      return s.substr(0, i);
+    }
+  }
+  
+  // FIXME: hard-wrap at width, taking care of multibyte characters
+  if (overflow) *overflow = "";
+  return s;
+}
+
 void
 Font::draw(const std::string& text, const Vector& pos_, FontAlignment alignment,
            DrawingEffect drawing_effect, float alpha) const
index e3b8875..1cf38eb 100644 (file)
@@ -78,6 +78,11 @@ public:
    */
   static std::string wrap_to_chars(const std::string& text, int max_chars, std::string* overflow);
 
+  /**
+   * returns the given string, truncated (preferrably at whitespace) to be at most "width" pixels wide
+   */
+  std::string wrap_to_width(const std::string& text, float width, std::string* overflow);
+
   /** Draws the given text to the screen. Also needs the position.
    * Type of alignment, drawing effect and alpha are optional. */
   void draw(const std::string& text, const Vector& pos,