/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include "ultima/shared/std/string.h"
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/conf/configuration.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/fonts/font_manager.h"
#include "ultima/nuvie/fonts/font.h"
#include "ultima/nuvie/screen/game_palette.h"
#include "ultima/nuvie/gui/gui.h"
#include "ultima/nuvie/gui/widgets/msg_scroll.h"
#include "ultima/nuvie/core/events.h"
#include "ultima/nuvie/core/game.h"
#include "ultima/nuvie/core/effect.h"
#include "ultima/nuvie/keybinding/keys.h"
namespace Ultima {
namespace Nuvie {
// MsgText Class
MsgText::MsgText() : font(nullptr), color(0) {
}
MsgText::MsgText(const Std::string &new_string, Font *f) : font(f), color(0) {
s.assign(new_string);
if (font) {
color = font->getDefaultColor();
}
}
MsgText::~MsgText() {
}
void MsgText::append(const Std::string &new_string) {
s.append(new_string);
}
void MsgText::copy(MsgText *msg_text) {
s.assign(msg_text->s);
font = msg_text->font;
color = msg_text->color;
}
uint32 MsgText::length() {
return (uint32)s.size();
}
uint16 MsgText::getDisplayWidth() {
return font->getStringWidth(s.c_str());
}
MsgLine::~MsgLine() {
for (MsgText *token : text) {
delete token;
}
}
void MsgLine::append(MsgText *new_text) {
MsgText *msg_text = nullptr;
if (text.size() > 0)
msg_text = text.back();
if (msg_text && msg_text->font == new_text->font && msg_text->color == new_text->color && new_text->s.size() == 1 && new_text->s[0] != ' ')
msg_text->s.append(new_text->s);
else {
msg_text = new MsgText();
msg_text->copy(new_text);
text.push_back(msg_text);
}
total_length += new_text->s.size();
return;
}
// remove the last char in the line.
void MsgLine::remove_char() {
MsgText *msg_text;
if (total_length == 0)
return;
msg_text = text.back();
msg_text->s.deleteLastChar();
if (msg_text->s.empty()) {
text.pop_back();
delete msg_text;
}
total_length--;
return;
}
uint32 MsgLine::length() {
return total_length;
}
// gets the MsgText object that contains the text at line position pos
MsgText *MsgLine::get_text_at_pos(uint16 pos) {
uint16 i = 0;
if (pos > total_length)
return nullptr;
for (auto *t : text) {
if (i + t->s.size() >= pos)
return t;
i += t->s.size();
}
return nullptr;
}
uint16 MsgLine::get_display_width() {
uint16 len = 0;
for (MsgText *token : text) {
len += token->font->getStringWidth(token->s.c_str());
}
return len;
}
// MsgScroll Class
void MsgScroll::init(const Configuration *cfg, Font *f) {
font = f;
config = cfg;
config->value("config/GameType", game_type);
input_char = 0;
yes_no_only = false;
aye_nay_only = false;
numbers_only = false;
scroll_updated = false;
discard_whitespace = true;
page_break = false;
show_cursor = true;
talking = false;
autobreak = false;
just_finished_page_break = false;
using_target_cursor = false;
callback_target = nullptr;
callback_user_data = nullptr;
scrollback_height = MSGSCROLL_SCROLLBACK_HEIGHT;
capitalise_next_letter = false;
font_color = FONT_COLOR_U6_NORMAL;
font_highlight_color = FONT_COLOR_U6_HIGHLIGHT;
if (game_type != NUVIE_GAME_U6) {
font_color = FONT_COLOR_WOU_NORMAL;
font_highlight_color = FONT_COLOR_WOU_HIGHLIGHT;
}
}
MsgScroll::MsgScroll(const Configuration *cfg, Font *f) : GUI_Widget(nullptr, 0, 0, 0, 0),
input_mode(false), permit_input(nullptr), just_displayed_prompt(false),
permit_inputescape(false), screen_x(0), screen_y(0), keyword_highlight(false) {
uint16 x, y;
init(cfg, f);
switch (game_type) {
case NUVIE_GAME_MD :
scroll_width = MSGSCROLL_MD_WIDTH;
scroll_height = MSGSCROLL_MD_HEIGHT;
x = 184;
y = 128;
break;
case NUVIE_GAME_SE :
scroll_width = MSGSCROLL_SE_WIDTH;
scroll_height = MSGSCROLL_SE_HEIGHT;
x = 184;
y = 128;
break;
case NUVIE_GAME_U6 :
default :
scroll_width = MSGSCROLL_U6_WIDTH;
scroll_height = MSGSCROLL_U6_HEIGHT;
x = 176;
y = 112;
break;
}
if (Game::get_game()->is_original_plus())
x += Game::get_game()->get_game_width() - 320;
uint16 x_off = Game::get_game()->get_game_x_offset();
uint16 y_off = Game::get_game()->get_game_y_offset();
GUI_Widget::Init(nullptr, x + x_off, y + y_off, scroll_width * 8, scroll_height * 8);
cursor_char = 0;
cursor_x = 0;
cursor_y = scroll_height - 1;
line_count = 0;
cursor_wait = 0;
display_pos = 0;
bg_color = Game::get_game()->get_palette()->get_bg_color();
capitalise_next_letter = false;
left_margin = 0;
add_new_line();
}
MsgScroll::~MsgScroll() {
// delete the scroll buffer
for (MsgLine *line : msg_buf)
delete line;
// delete the holding buffer
for (MsgText *token : holding_buffer)
delete token;
}
bool MsgScroll::init(const char *player_name) {
Std::string prompt_string;
prompt_string.append(player_name);
if (game_type == NUVIE_GAME_U6) {
prompt_string.append(":\n");
}
prompt_string.append(">");
if (set_prompt(prompt_string.c_str(), font) == false)
return false;
set_input_mode(false);
return true;
}
void MsgScroll::set_scroll_dimensions(uint16 w, uint16 h) {
scroll_width = w;
scroll_height = h;
cursor_char = 0;
cursor_x = 0;
cursor_y = scroll_height - 1;
line_count = 0;
cursor_wait = 0;
display_pos = 0;
}
int MsgScroll::print_internal(const Std::string *format, ...) {
va_list ap;
int printed = 0;
static size_t bufsize = 128; // will be resized if needed
static char *buffer = (char *) malloc(bufsize); // static so we don't have to reallocate all the time.
while (1) {
if (buffer == nullptr) {
DEBUG(0, LEVEL_ALERT, "MsgScroll::printf: Couldn't allocate %d bytes for buffer\n", bufsize);
/* try to shrink the buffer to at least have a change next time,
* but if we're low on memory probably have worse issues...
*/
bufsize >>= 1;
buffer = (char *) malloc(bufsize); // no need to check now.
/*
* We don't retry, or if we need e.g. 4 bytes for the format and
* there's only 3 available, we'd grow and shrink forever.
*/
return printed;
}
/* try formatting */
va_start(ap, format);
printed = vsnprintf(buffer, bufsize, format->c_str(), ap);
va_end(ap);
if (printed < 0) {
DEBUG(0, LEVEL_ERROR, "MsgScroll::printf: vsnprintf returned < 0: either output error or glibc < 2.1\n");
free(buffer);
bufsize *= 2; // In case of an output error, we'll just keep doubling until the malloc fails.
buffer = (char *) malloc(bufsize); // if this fails, will be caught later on
/* try again */
continue;
} else if ((size_t)printed >= bufsize) {
DEBUG(0, LEVEL_DEBUGGING, "MsgScroll::printf: needed buffer of %d bytes, only had %d bytes.\n", printed + 1, bufsize);
bufsize = printed + 1; //this should be enough
free(buffer);
buffer = (char *) malloc(bufsize); // if this fails, will be caught later on
/* try again */
continue;
}
/* if we're here, formatting probably worked. We can stop looping */
break;
}
/* use the string */
display_string(buffer);
return printed;
}
void MsgScroll::display_fmt_string(const char *format, ...) {
char buf[1024];
memset(buf, 0, 1024);
va_list args;
va_start(args, format);
vsnprintf(buf, 1024, format, args);
va_end(args);
display_string(buf);
}
void MsgScroll::display_string(const Std::string &s, uint16 length, uint8 lang_num) {
}
void MsgScroll::display_string(const Std::string &s, bool include_on_map_window) {
display_string(s, font, include_on_map_window);
}
void MsgScroll::display_string(const Std::string &s, uint8 color, bool include_on_map_window) {
display_string(s, font, color, include_on_map_window);
}
void MsgScroll::display_string(const Std::string &s, Font *f, bool include_on_map_window) {
display_string(s, f, font_color, include_on_map_window);
}
void MsgScroll::display_string(const Std::string &s, Font *f, uint8 color, bool include_on_map_window) {
MsgText *msg_text;
if (s.empty())
return;
if (f == nullptr)
f = font;
msg_text = new MsgText(s, f);
msg_text->color = color;
//::debug(1, "%s", msg_text->s.c_str());
holding_buffer.push_back(msg_text);
process_holding_buffer();
}
// process text tokens till we either run out or hit a page break.
void MsgScroll::process_holding_buffer() {
MsgText *token;
if (!page_break) {
token = holding_buffer_get_token();
for (; token != nullptr && !page_break;) {
parse_token(token);
delete token;
scroll_updated = true;
if (!page_break)
token = holding_buffer_get_token();
}
}
}
MsgText *MsgScroll::holding_buffer_get_token() {
MsgText *input, *token;
int i;
if (holding_buffer.empty())
return nullptr;
input = holding_buffer.front();
if (input->font == nullptr) {
line_count = 0;
holding_buffer.pop_front();
delete input;
return nullptr;
}
i = input->s.findFirstOf(" \t\n*<>`", 0);
if (i == 0) i++;
if (i == -1)
i = input->s.size();
if (i > 0) {
token = new MsgText(input->s.substr(0, i), font); // FIX maybe a format flag. // input->font);
token->color = input->color;
input->s.erase(0, i);
if (input->s.empty()) {
holding_buffer.pop_front();
delete input;
}
return token;
}
return nullptr;
}
bool MsgScroll::can_fit_token_on_msgline(MsgLine *msg_line, MsgText *token) {
if (msg_line->total_length + token->length() > scroll_width) {
return false; //token doesn't fit on the current line.
}
return true;
}
bool MsgScroll::parse_token(MsgText *token) {
MsgLine *msg_line = nullptr;
if (!(token && token->s.size()))
return true;
if (!msg_buf.empty())
msg_line = msg_buf.back(); // retrieve the last line from the scroll buffer.
switch (token->s[0]) {
case '\n' :
add_new_line();
break;
case '\t' :
set_autobreak(false);
break;
case '`' :
capitalise_next_letter = true;
break;
case '<' :
set_font(NUVIE_FONT_GARG); // runic / gargoyle font
break;
case '>' :
if (is_garg_font()) {
set_font(NUVIE_FONT_NORMAL); // english font
break;
}
// falls through
// ...if we haven't already seen a '<' char
default :
if (msg_line) {
if (can_fit_token_on_msgline(msg_line, token) == false) { // the token is to big for the current line
msg_line = add_new_line();
}
// This adds extra newlines. (SB-X)
// if(msg_line->total_length + token->size() == scroll_width) //we add a new line but write to the old line.
// add_new_line();
if (msg_line->total_length == 0 && token->s[0] == ' ' && discard_whitespace) // discard whitespace at the start of a line.
return true;
}
if (token->s[0] == '*') {
if (just_finished_page_break == false) //we don't want to add another break if we've only just completed an autobreak
set_page_break();
} else {
if (capitalise_next_letter) {
token->s[0] = toupper(token->s[0]);
capitalise_next_letter = false;
}
if (msg_line == nullptr) {
msg_line = add_new_line();
}
add_token(token);
if (msg_line->total_length == scroll_width // add another line for cursor.
&& (talking || Game::get_game()->get_event()->get_mode() == INPUT_MODE
|| Game::get_game()->get_event()->get_mode() == KEYINPUT_MODE)) {
msg_line = add_new_line();
}
}
break;
}
if (msg_buf.size() > scroll_height)
display_pos = msg_buf.size() - scroll_height;
just_finished_page_break = false;
just_displayed_prompt = false;
return true;
}
void MsgScroll::add_token(MsgText *token) {
msg_buf.back()->append(token);
}
bool MsgScroll::remove_char() {
MsgLine *msg_line;
msg_line = msg_buf.back(); // retrieve the last line from the scroll buffer.
msg_line->remove_char();
if (msg_line->total_length == 0) { // remove empty line from scroll buffer
msg_buf.pop_back();
delete msg_line;
}
return true;
}
void MsgScroll::set_font(uint8 font_type) {
font = Game::get_game()->get_font_manager()->get_font(font_type); // 0 = normal english; 1 = runic / gargoyle font
}
bool MsgScroll::is_garg_font() {
return (font == Game::get_game()->get_font_manager()->get_font(NUVIE_FONT_GARG));
}
void MsgScroll::clear_scroll() {
for (MsgLine *line : msg_buf) {
delete line;
}
msg_buf.clear();
line_count = 0;
display_pos = 0;
scroll_updated = true;
add_new_line();
}
void MsgScroll::delete_front_line() {
MsgLine *msg_line_front = msg_buf.front();
msg_buf.pop_front();
delete msg_line_front;
}
MsgLine *MsgScroll::add_new_line() {
MsgLine *msg_line = new MsgLine();
msg_buf.push_back(msg_line);
line_count++;
if (msg_buf.size() > scrollback_height) {
delete_front_line();
}
if (autobreak && line_count > scroll_height - 1)
set_page_break();
return msg_line;
}
bool MsgScroll::set_prompt(const char *new_prompt, Font *f) {
prompt.s.assign(new_prompt);
prompt.font = f;
return true;
}
void MsgScroll::display_prompt() {
if (!talking && !just_displayed_prompt) {
//line_count = 0;
display_string(prompt.s, prompt.font, MSGSCROLL_NO_MAP_DISPLAY);
//line_count = 0;
clear_page_break();
just_displayed_prompt = true;
}
}
void MsgScroll::display_converse_prompt() {
display_string("\nyou say:", MSGSCROLL_NO_MAP_DISPLAY);
}
void MsgScroll::set_keyword_highlight(bool state) {
keyword_highlight = state;
}
void MsgScroll::set_permitted_input(const char *allowed) {
permit_input = allowed;
if (permit_input) {
if (strcmp(permit_input, "yn") == 0)
yes_no_only = true;
else if (strncmp(permit_input, "0123456789", strlen(permit_input)) == 0)
numbers_only = true;
else if (game_type == NUVIE_GAME_U6 && strcmp(permit_input, "ayn") == 0) // Heftimus npc 47
aye_nay_only = true;
}
}
void MsgScroll::clear_permitted_input() {
permit_input = nullptr;
yes_no_only = false;
numbers_only = false;
aye_nay_only = false;
}
void MsgScroll::set_input_mode(bool state, const char *allowed, bool can_escape, bool use_target_cursor, bool set_numbers_only_to_true) {
bool do_callback = false;
input_mode = state;
clear_permitted_input();
permit_inputescape = can_escape;
using_target_cursor = use_target_cursor;
if (set_numbers_only_to_true)
numbers_only = true;
line_count = 0;
clear_page_break();
if (input_mode == true) {
if (allowed && strlen(allowed))
set_permitted_input(allowed);
//FIXME SDL2 SDL_EnableUNICODE(1); // allow character translation
input_buf.erase(0, input_buf.size());
} else {
//FIXME SDL2 SDL_EnableUNICODE(0); // reduce translation overhead when not needed
if (callback_target)
do_callback = true; // **DELAY until end-of-method so callback can set_input_mode() again**
}
Game::get_game()->get_gui()->lock_input((input_mode && !using_target_cursor) ? this : nullptr);
// send whatever input was collected to target that requested it
if (do_callback) {
CallBack *requestor = callback_target; // copy to temp
char *user_data = callback_user_data;
cancel_input_request(); // clear originals (callback may request again)
Std::string input_str = input_buf;
requestor->set_user_data(user_data); // use temp requestor/user_data
requestor->callback(MSGSCROLL_CB_TEXT_READY, this, &input_str);
}
}
void MsgScroll::move_scroll_down() {
if (msg_buf.size() > scroll_height && display_pos < msg_buf.size() - scroll_height) {
display_pos++;
scroll_updated = true;
}
}
void MsgScroll::move_scroll_up() {
if (display_pos > 0) {
display_pos--;
scroll_updated = true;
}
}
void MsgScroll::page_up() {
uint8 i = 0;
for (; display_pos > 0 && i < scroll_height; i++)
display_pos--;
if (i > 0)
scroll_updated = true;
}
void MsgScroll::page_down() {
uint8 i = 0;
for (; msg_buf.size() > scroll_height && i < scroll_height
&& display_pos < msg_buf.size() - scroll_height ; i++)
display_pos++;
if (i > 0)
scroll_updated = true;
}
void MsgScroll::process_page_break() {
page_break = false;
just_finished_page_break = true;
if (!input_mode)
Game::get_game()->get_gui()->unlock_input();
process_holding_buffer(); // Process any text in the holding buffer.
just_displayed_prompt = true;
}
/* Take input from the main event handler and do something with it
* if necessary.
*/
GUI_status MsgScroll::KeyDown(const Common::KeyState &keyState) {
Common::KeyState key = keyState;
char ascii = get_ascii_char_from_keysym(key);
if (page_break == false && input_mode == false)
return GUI_PASS;
bool is_printable = Common::isPrint(ascii);
KeyBinder *keybinder = Game::get_game()->get_keybinder();
ActionType a = keybinder->get_ActionType(key);
ActionKeyType action_key_type = keybinder->GetActionKeyType(a);
if (using_target_cursor && !is_printable && action_key_type <= DO_ACTION_KEY) // directional keys, toggle_cursor, and do_action
return GUI_PASS;
if (!input_mode || !is_printable) {
switch (action_key_type) {
case WEST_KEY:
key.keycode = Common::KEYCODE_LEFT;
break;
case EAST_KEY:
key.keycode = Common::KEYCODE_RIGHT;
break;
case SOUTH_KEY:
key.keycode = Common::KEYCODE_DOWN;
break;
case NORTH_KEY:
key.keycode = Common::KEYCODE_UP;
break;
case CANCEL_ACTION_KEY:
key.keycode = Common::KEYCODE_ESCAPE;
break;
case DO_ACTION_KEY:
key.keycode = Common::KEYCODE_RETURN;
break;
case MSGSCROLL_UP_KEY:
key.keycode = Common::KEYCODE_PAGEUP;
break;
case MSGSCROLL_DOWN_KEY:
key.keycode = Common::KEYCODE_PAGEDOWN;
break;
default:
if (keybinder->handle_always_available_keys(a)) return GUI_YUM;
break;
}
}
switch (key.keycode) {
case Common::KEYCODE_UP :
if (input_mode) break; //will select printable ascii
move_scroll_up();
return GUI_YUM;
case Common::KEYCODE_DOWN:
if (input_mode) break; //will select printable ascii
move_scroll_down();
return GUI_YUM;
case Common::KEYCODE_PAGEUP:
if (Game::get_game()->is_new_style())
move_scroll_up();
else page_up();
return GUI_YUM;
case Common::KEYCODE_PAGEDOWN:
if (Game::get_game()->is_new_style())
move_scroll_down();
else page_down();
return GUI_YUM;
default :
break;
}
if (page_break) {
process_page_break();
return GUI_YUM;
}
switch (key.keycode) {
case Common::KEYCODE_ESCAPE:
if (permit_inputescape) {
// reset input buffer
permit_input = nullptr;
if (input_mode)
set_input_mode(false);
}
return GUI_YUM;
case Common::KEYCODE_KP_ENTER:
case Common::KEYCODE_RETURN:
if (permit_inputescape || input_char != 0) { // input_char should only be permit_input
if (input_char != 0)
input_buf_add_char(get_char_from_input_char());
if (input_mode)
set_input_mode(false);
}
return GUI_YUM;
case Common::KEYCODE_RIGHT:
if (input_char != 0 && permit_input == nullptr)
input_buf_add_char(get_char_from_input_char());
break;
case Common::KEYCODE_DOWN:
increase_input_char();
break;
case Common::KEYCODE_UP:
decrease_input_char();
break;
case Common::KEYCODE_LEFT :
case Common::KEYCODE_BACKSPACE :
if (input_mode) {
if (input_char != 0)
input_char = 0;
else
input_buf_remove_char();
}
break;
case Common::KEYCODE_LSHIFT :
return GUI_YUM;
case Common::KEYCODE_RSHIFT :
return GUI_YUM;
default: // alphanumeric characters
if (input_mode && is_printable) {
if (permit_input == nullptr) {
if (!numbers_only || Common::isDigit(ascii)) {
if (input_char != 0)
input_buf_add_char(get_char_from_input_char());
input_buf_add_char(ascii);
}
} else if (strchr(permit_input, ascii) || strchr(permit_input, tolower(ascii))) {
input_buf_add_char(toupper(ascii));
set_input_mode(false);
}
}
break;
}
return GUI_YUM;
}
GUI_status MsgScroll::MouseWheel(sint32 x, sint32 y) {
if (page_break) { // any click == scroll-to-end
process_page_break();
return GUI_YUM;
}
Game *game = Game::get_game();
if (game->is_new_style()) {
if (!input_mode)
return GUI_PASS;
if (y > 0)
move_scroll_up();
if (y < 0)
move_scroll_down();
} else {
if (y > 0)
page_up();
if (y < 0)
page_down();
}
return GUI_YUM;
}
GUI_status MsgScroll::MouseUp(int x, int y, Events::MouseButton button) {
uint16 i;
Std::string token_str;
if (page_break) { // any click == scroll-to-end
process_page_break();
return GUI_YUM;
}
if (button == 1) { // left click == select word
if (input_mode) {
token_str = get_token_string_at_pos(x, y);
if (permit_input != nullptr && !token_str.empty()) {
if (strchr(permit_input, token_str[0])
|| strchr(permit_input, tolower(token_str[0]))) {
input_buf_add_char(token_str[0]);
set_input_mode(false);
}
return GUI_YUM;
}
for (i = 0; i < token_str.size(); i++) {
if (Common::isAlnum(token_str[i]))
input_buf_add_char(token_str[i]);
}
} else if (!Game::get_game()->is_new_style())
Game::get_game()->get_event()->cancelAction();
} else if (button == 3) { // right click == send input
if (input_mode) {
if (permit_inputescape) {
set_input_mode(false);
return GUI_YUM;
}
} else if (!Game::get_game()->is_new_style())
Game::get_game()->get_event()->cancelAction();
}
return GUI_PASS;
}
Std::string MsgScroll::get_token_string_at_pos(uint16 x, uint16 y) {
uint16 i;
sint32 buf_x, buf_y;
MsgText *token = nullptr;
Std::list::iterator iter;
buf_x = (x - area.left) / 8;
buf_y = (y - area.top) / 8;
if (buf_x < 0 || buf_x >= scroll_width || // click not in MsgScroll area.
buf_y < 0 || buf_y >= scroll_height)
return "";
if (msg_buf.size() <= scroll_height) {
if ((sint32)msg_buf.size() < buf_y + 1)
return "";
} else {
buf_y = display_pos + buf_y;
}
for (i = 0, iter = msg_buf.begin(); i < buf_y && iter != msg_buf.end();) {
iter++;
i++;
}
if (iter != msg_buf.end()) {
token = (*iter)->get_text_at_pos(buf_x);
if (token) {
DEBUG(0, LEVEL_DEBUGGING, "Token at (%d,%d) = %s\n", buf_x, buf_y, token->s.c_str());
return token->s;
}
}
return "";
}
void MsgScroll::Display(bool full_redraw) {
uint16 i;
Std::list::iterator iter;
MsgLine *msg_line = nullptr;
if (scroll_updated || full_redraw || Game::get_game()->is_original_plus_full_map()) {
screen->fill(bg_color, area.left, area.top, area.width(), area.height()); //clear whole scroll
iter = msg_buf.begin();
for (i = 0; i < display_pos; i++)
iter++;
for (i = 0; i < scroll_height && iter != msg_buf.end(); i++, iter++) {
msg_line = *iter;
drawLine(screen, msg_line, i);
}
scroll_updated = false;
screen->update(area.left, area.top, area.width(), area.height());
cursor_y = i - 1;
if (msg_line) {
cursor_x = msg_line->total_length;
if (cursor_x == scroll_width) { // don't draw the cursor outside the scroll (SB-X)
if (cursor_y + 1 < scroll_height)
cursor_y++;
cursor_x = 0;
}
} else
cursor_x = area.left;
} else {
clearCursor(area.left + 8 * cursor_x, area.top + cursor_y * 8);
}
if (show_cursor && (msg_buf.size() <= scroll_height || display_pos == msg_buf.size() - scroll_height)) {
drawCursor(area.left + left_margin + 8 * cursor_x, area.top + cursor_y * 8);
}
}
inline void MsgScroll::drawLine(Screen *theScreen, MsgLine *msg_line, uint16 line_y) {
uint16 total_length = 0;
for (MsgText *token : msg_line->text) {
token->font->drawString(theScreen, token->s.c_str(), area.left + left_margin + total_length * 8, area.top + line_y * 8, token->color, font_highlight_color); //FIX for hardcoded font height
total_length += token->s.size();
}
}
void MsgScroll::clearCursor(uint16 x, uint16 y) {
screen->fill(bg_color, x, y, 8, 8);
}
void MsgScroll::drawCursor(uint16 x, uint16 y) {
uint8 cursor_color = input_mode ? get_input_font_color() : font_color;
if (input_char != 0) { // show letter selected by arrow keys
font->drawChar(screen, get_char_from_input_char(), x, y, cursor_color);
screen->update(x, y, 8, 8);
return;
}
if (page_break) {
if (cursor_wait <= 2) // flash arrow
font->drawChar(screen, 1, x, y, cursor_color); // down arrow
} else
font->drawChar(screen, cursor_char + 5, x, y, cursor_color); //spinning ankh
screen->update(x, y, 8, 8);
if (cursor_wait == MSGSCROLL_CURSOR_DELAY) {
cursor_char = (cursor_char + 1) % 4;
cursor_wait = 0;
} else
cursor_wait++;
}
void MsgScroll::set_page_break() {
line_count = 1;
page_break = true;
if (!input_mode) {
Game::get_game()->get_gui()->lock_input(this);
}
return;
}
bool MsgScroll::input_buf_add_char(char c) {
MsgText token;
input_char = 0;
if (permit_input != nullptr)
input_buf_remove_char();
input_buf.push_back(c);
scroll_updated = true;
// Add char to scroll buffer
token.s.assign(&c, 1);
token.font = font;
token.color = get_input_font_color();
parse_token(&token);
return true;
}
bool MsgScroll::input_buf_remove_char() {
if (!input_buf.empty()) {
input_buf.deleteLastChar();
scroll_updated = true;
remove_char();
return true;
}
return false;
}
bool MsgScroll::has_input() {
if (input_mode == false) //we only have input ready after the user presses enter.
return true;
return false;
}
Std::string MsgScroll::get_input() {
// MsgScroll sets input_mode to false when it receives Common::KEYCODE_ENTER
Std::string s;
if (input_mode == false) {
s.assign(input_buf);
}
//::debug(1, "%s", s.c_str());
return s;
}
void MsgScroll::clear_page_break() {
MsgText *msg_text = new MsgText("", nullptr);
holding_buffer.push_back(msg_text);
process_holding_buffer();
}
/* Set callback & callback_user_data so that a message will be sent to the
* caller when input has been gathered. */
void MsgScroll::request_input(CallBack *caller, void *user_data) {
callback_target = caller;
callback_user_data = (char *)user_data;
}
// 0 is no char, 1 - 26 is alpha, 27 is space, 28 - 37 is numbers
void MsgScroll::increase_input_char() {
if (permit_input != nullptr && strcmp(permit_input, "\n") == 0) // blame hacky PauseEffect
return;
if (yes_no_only)
input_char = input_char == 25 ? 14 : 25;
else if (aye_nay_only)
input_char = input_char == 1 ? 14 : 1;
else if (numbers_only)
input_char = (input_char == 0 || input_char == 37) ? 28 : input_char + 1;
else
input_char = (input_char + 1) % 38;
if (permit_input != nullptr && !strchr(permit_input, get_char_from_input_char())) // might only be needed for the teleport cheat menu
increase_input_char();
}
void MsgScroll::decrease_input_char() {
if (permit_input != nullptr && strcmp(permit_input, "\n") == 0) // blame hacky PauseEffect
return;
if (yes_no_only)
input_char = input_char == 25 ? 14 : 25;
else if (numbers_only)
input_char = (input_char == 0 || input_char == 28) ? 37 : input_char - 1;
else if (aye_nay_only)
input_char = input_char == 1 ? 14 : 1;
else
input_char = input_char == 0 ? 37 : input_char - 1;
if (permit_input != nullptr && !strchr(permit_input, get_char_from_input_char())) // might only be needed for the teleport cheat menu
decrease_input_char();
}
uint8 MsgScroll::get_char_from_input_char() {
if (input_char > 27)
return (input_char - 28 + Common::KEYCODE_0);
else if (input_char == 27)
return Common::KEYCODE_SPACE;
else
return (input_char + Common::KEYCODE_a - 1);
}
} // End of namespace Nuvie
} // End of namespace Ultima