support multi-line PGEntries

This commit is contained in:
David Rose 2001-07-11 18:46:17 +00:00
parent 969f4db907
commit 5285800a36
5 changed files with 194 additions and 19 deletions

View File

@ -105,10 +105,16 @@ get_max_chars() const {
// number of characters; also set_max_chars().
//
// If this is 0, there is no limit.
//
// If _num_lines is more than 1, rather than being a
// fixed width on the whole entry, this becomes instead
// the wordwrap width (and the width limit on the entry
// is essentially _max_width * _num_lines).
////////////////////////////////////////////////////////////////////
INLINE void PGEntry::
set_max_width(float max_width) {
_max_width = max_width;
_text_geom_stale = true;
}
////////////////////////////////////////////////////////////////////
@ -123,6 +129,31 @@ get_max_width() const {
return _max_width;
}
////////////////////////////////////////////////////////////////////
// Function: PGEntry::set_num_lines
// Access: Published
// Description: Sets the number of lines of text the PGEntry will
// use. This only has meaning if _max_width is not 0;
// _max_width indicates the wordwrap width of each line.
////////////////////////////////////////////////////////////////////
INLINE void PGEntry::
set_num_lines(int num_lines) {
nassertv(num_lines >= 1);
_num_lines = num_lines;
_text_geom_stale = true;
}
////////////////////////////////////////////////////////////////////
// Function: PGEntry::get_num_lines
// Access: Published
// Description: Returns the number of lines of text the PGEntry will
// use, if _max_width is not 0. See set_num_lines().
////////////////////////////////////////////////////////////////////
INLINE int PGEntry::
get_num_lines() const {
return _num_lines;
}
////////////////////////////////////////////////////////////////////
// Function: PGEntry::set_blink_rate
// Access: Published

View File

@ -46,6 +46,7 @@ PGEntry(const string &name) : PGItem(name)
_cursor_stale = true;
_max_chars = 0;
_max_width = 0.0;
_num_lines = 1;
_last_text_def = (TextNode *)NULL;
_text_geom_stale = true;
_blink_start = 0.0;
@ -220,14 +221,64 @@ press(const MouseWatcherParameter &param) {
_text.substr(0, _cursor_position) + key +
_text.substr(_cursor_position);
// Check the width.
bool too_wide = false;
if (get_max_width() > 0.0) {
// Check the length.
bool too_long = false;
if (_max_width > 0.0) {
TextNode *text_node = get_text_def(S_focus);
too_wide = (text_node->calc_width(new_text) > get_max_width());
if (_num_lines <= 1) {
// If we have only one line, we can check the length
// by simply measuring the width of the text.
too_long = (text_node->calc_width(new_text) > _max_width);
} else {
// If we have multiple lines, we have to check the
// length by wordwrapping it and counting up the
// number of lines.
string ww_text = text_node->wordwrap_to(new_text, _max_width, true);
int num_lines = 1;
size_t last_line_start = 0;
for (size_t p = 0;
p < ww_text.length() && !too_long;
++p) {
if (ww_text[p] == '\n') {
last_line_start = p + 1;
num_lines++;
too_long = (num_lines > _num_lines);
}
}
if (!too_long) {
// We must also ensure that the last line is not too
// long (it might be, because of additional
// whitespace on the end).
string last_line = ww_text.substr(last_line_start);
float last_line_width = text_node->calc_width(last_line);
if (num_lines == _num_lines) {
// Mainly we only care about this if we're on
// the very last line.
too_long = (last_line_width > _max_width);
} else {
// If we're not on the very last line, the width
// is still important, just so we don't allow an
// infinite number of spaces to accumulate.
// However, we must allow at least *one* space
// on the end of a line.
if (_text.length() >= 1 &&
_text[_text.length() - 1] == ' ') {
if (last_line_width > _max_width) {
// In this case, however, it's not exactly
// an overflow; we just want to reject the
// space.
return;
}
}
}
}
}
}
if (too_wide) {
if (too_long) {
overflow(param);
} else {
@ -280,20 +331,22 @@ overflow(const MouseWatcherParameter &param) {
// Access: Published
// Description: Sets up the entry for normal use. The width is the
// maximum width of characters that will be typed, and
// determines the size of the entry, based on the
// TextNode in effect.
// num_lines is the integer number of lines of text of
// the entry. Both of these together determine the size
// of the entry, based on the TextNode in effect.
////////////////////////////////////////////////////////////////////
void PGEntry::
setup(float width) {
setup(float width, int num_lines) {
set_text(string());
_cursor_position = 0;
set_max_chars(0);
set_max_width(width);
set_num_lines(num_lines);
TextNode *text_node = get_text_def(S_focus);
float height = text_node->get_line_height();
float line_height = text_node->get_line_height();
LVecBase4f frame(0.0, width, -0.3 * height, height);
LVecBase4f frame(0.0, width, -0.3 * line_height - (line_height * (num_lines - 1)), line_height);
switch (text_node->get_align()) {
case TM_ALIGN_LEFT:
// The default case.
@ -329,8 +382,8 @@ setup(float width) {
clear_cursor_def();
LineSegs ls;
ls.set_color(0.0, 0.0, 0.0, 1.0);
ls.move_to(0.0, 0.0, -0.15 * height);
ls.draw_to(0.0, 0.0, 0.85 * height);
ls.move_to(0.0, 0.0, -0.15 * line_height);
ls.draw_to(0.0, 0.0, 0.85 * line_height);
new RenderRelation(get_cursor_def(), ls.create());
// An underscore cursor would work too.
@ -437,7 +490,53 @@ update_text() {
if (_text_geom_stale || node != _last_text_def) {
// We need to regenerate.
_last_text_def = node;
_last_text_def->set_text(_text);
if (_max_width > 0.0 && _num_lines > 1) {
// Fold the text into multiple lines.
string ww_text = _last_text_def->wordwrap_to(_text, _max_width, true);
// And chop the lines up into pieces.
_ww_lines.clear();
size_t p = 0;
size_t q = ww_text.find('\n');
while (q != string::npos) {
_ww_lines.push_back(WWLine());
WWLine &line = _ww_lines.back();
line._str = ww_text.substr(p, q - p);
// Get the left edge of the text at this line.
line._left = 0.0;
if (_last_text_def->get_align() != TM_ALIGN_LEFT) {
_last_text_def->set_text(line._str);
line._left = _last_text_def->get_left();
}
p = q + 1;
q = ww_text.find('\n', p);
}
_ww_lines.push_back(WWLine());
WWLine &line = _ww_lines.back();
line._str = ww_text.substr(p);
// Get the left edge of the text at this line.
line._left = 0.0;
if (_last_text_def->get_align() != TM_ALIGN_LEFT) {
_last_text_def->set_text(line._str);
line._left = _last_text_def->get_left();
}
_last_text_def->set_text(ww_text);
} else {
// Only one line.
_ww_lines.clear();
_ww_lines.push_back(WWLine());
WWLine &line = _ww_lines.back();
line._str = _text;
_last_text_def->set_text(_text);
line._left = _last_text_def->get_left();
}
if (_current_text_arc != (NodeRelation *)NULL) {
remove_arc(_current_text_arc);
@ -445,7 +544,6 @@ update_text() {
PT_Node text = _last_text_def->generate();
_current_text_arc = new RenderRelation(_text_render_root, text);
_text_geom_stale = false;
_text_left = _last_text_def->get_left();
_cursor_stale = true;
}
}
@ -465,10 +563,23 @@ update_cursor() {
_cursor_position = min(_cursor_position, (int)_text.length());
float width =
_last_text_def->calc_width(_text.substr(0, _cursor_position));
// Determine the row and column of the cursor.
int row = 0;
int column = _cursor_position;
while (row + 1 < (int)_ww_lines.size() &&
column > (int)_ww_lines[row]._str.length()) {
column -= _ww_lines[row]._str.length();
row++;
}
LVecBase3f trans(_text_left + width, 0.0, 0.0);
nassertv(row >= 0 && row < (int)_ww_lines.size());
nassertv(column >= 0 && column <= (int)_ww_lines[row]._str.length());
float width =
_last_text_def->calc_width(_ww_lines[row]._str.substr(0, column));
float line_height = _last_text_def->get_line_height();
LVecBase3f trans(_ww_lines[row]._left + width, 0.0, -line_height * row);
LMatrix4f pos = LMatrix4f::translate_mat(trans);
_cursor_def->set_transition(new TransformTransition(pos));

View File

@ -60,7 +60,7 @@ PUBLISHED:
S_inactive
};
void setup(float width);
void setup(float width, int num_lines);
INLINE void set_text(const string &text);
INLINE const string &get_text() const;
@ -72,6 +72,8 @@ PUBLISHED:
INLINE int get_max_chars() const;
INLINE void set_max_width(float max_width);
INLINE float get_max_width() const;
INLINE void set_num_lines(int num_lines);
INLINE int get_num_lines() const;
INLINE void set_blink_rate(float blink_rate);
INLINE float get_blink_rate() const;
@ -104,6 +106,7 @@ private:
int _max_chars;
float _max_width;
int _num_lines;
typedef pvector< PT(TextNode) > TextDefs;
TextDefs _text_defs;
@ -116,9 +119,19 @@ private:
// is generated.
NodeRelation *_current_text_arc;
TextNode *_last_text_def;
float _text_left;
bool _text_geom_stale;
// This is a list of each row of text in the entry, after it has
// been wordwrapped by update_text(). It's used by update_cursor()
// to compute the correct cursor position.
class WWLine {
public:
string _str;
float _left;
};
typedef pvector<WWLine> WWLines;
WWLines _ww_lines;
// This is the arc above the node that represents the cursor
// geometry. It is also attached to the above node, and is
// transformed around and/or hidden according to the cursor's

View File

@ -160,6 +160,24 @@ get_id() const {
return _region->get_name();
}
////////////////////////////////////////////////////////////////////
// Function: PGItem::set_id
// Access: Published
// Description: Set the unique ID assigned to this PGItem. It is the
// user's responsibility to ensure that this ID is
// unique.
//
// Normally, this should not need to be called, as the
// PGItem will assign itself an ID when it is created,
// but this function allows the user to decide to
// redefine the ID to be something possibly more
// meaningful.
////////////////////////////////////////////////////////////////////
INLINE void PGItem::
set_id(const string &id) {
_region->set_name(id);
}
////////////////////////////////////////////////////////////////////
// Function: PGItem::get_enter_prefix
// Access: Published, Static

View File

@ -102,6 +102,8 @@ PUBLISHED:
void set_frame_style(int state, const PGFrameStyle &style);
INLINE const string &get_id() const;
INLINE void set_id(const string &id);
INLINE static string get_enter_prefix();
INLINE static string get_exit_prefix();
INLINE static string get_press_prefix();