void Messages::display_messages() { WINDOW_PTR w_ptr {newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH, (TERMY > FULL_SCREEN_HEIGHT) ? (TERMY - FULL_SCREEN_HEIGHT) / 2 : 0, (TERMX > FULL_SCREEN_WIDTH) ? (TERMX - FULL_SCREEN_WIDTH) / 2 : 0)}; WINDOW *const w = w_ptr.get(); input_context ctxt("MESSAGE_LOG"); ctxt.register_action("UP", _("Scroll up")); ctxt.register_action("DOWN", _("Scroll down")); ctxt.register_action("QUIT"); ctxt.register_action("HELP_KEYBINDINGS"); int offset = 0; const int maxlength = FULL_SCREEN_WIDTH - 2 - 1; const int bottom = FULL_SCREEN_HEIGHT - 2; const int msg_count = size(); for (;;) { werase(w); draw_border(w); mvwprintz(w, bottom + 1, 32, c_red, _("Press %s to return"), ctxt.get_desc("QUIT").c_str()); draw_scrollbar(w, offset, bottom, msg_count, 1); int line = 1; int lasttime = -1; for( int i = offset; i < msg_count; ++i ) { if (line > bottom) { break; } const game_message &m = player_messages.impl_->history(i); const nc_color col = msgtype_to_color( m.type, false ); const calendar timepassed = calendar::turn - m.timestamp_in_turns; if (timepassed.get_turn() > lasttime) { mvwprintz(w, line++, 3, c_ltblue, _("%s ago:"), timepassed.textify_period().c_str()); lasttime = timepassed.get_turn(); } nc_color col_out = col; for( const std::string &folded : foldstring(m.get_with_count(), maxlength) ) { if (line > bottom) { break; } print_colored_text( w, line++, 1, col_out, col, folded ); } } if (offset + 1 < msg_count) { mvwprintz(w, bottom + 1, 5, c_magenta, "vvv"); } if (offset > 0) { mvwprintz(w, bottom + 1, maxlength - 3, c_magenta, "^^^"); } wrefresh(w); const std::string &action = ctxt.handle_input(); if (action == "DOWN" && offset + 1 < msg_count) { offset++; } else if (action == "UP" && offset > 0) { offset--; } else if (action == "QUIT") { break; } } player_messages.impl_->curmes = calendar::turn.get_turn(); }
void Messages::dialog::show() { werase( w ); draw_border( w, border_color ); scrollbar() .offset_x( 0 ) .offset_y( border_width ) .content_size( folded_filtered.size() ) .viewport_pos( offset ) .viewport_size( max_lines ) .apply( w ); // Range of window lines to print size_t line_from = 0, line_to; if( offset < folded_filtered.size() ) { line_to = std::min( max_lines, folded_filtered.size() - offset ); } else { line_to = 0; } if( !log_from_top ) { // Always print from new to old std::swap( line_from, line_to ); } std::string prev_time_str; bool printing_range = false; for( size_t line = line_from; line != line_to; ) { // Decrement here if printing from bottom to get the correct line number if( !log_from_top ) { --line; } const size_t folded_ind = offset + line; const size_t msg_ind = folded_all[folded_filtered[folded_ind]].first; const game_message &msg = player_messages.history( msg_ind ); nc_color col = msgtype_to_color( msg.type, false ); // Print current line print_colored_text( w, border_width + line, border_width + time_width + padding_width, col, col, folded_all[folded_filtered[folded_ind]].second ); // Generate aligned time string const time_point msg_time = msg.timestamp_in_turns; const std::string time_str = to_string_clipped( calendar::turn - msg_time, clipped_align::right ); if( time_str != prev_time_str ) { // Time changed, print time string prev_time_str = time_str; right_print( w, border_width + line, border_width + msg_width + padding_width, time_color, time_str ); printing_range = false; } else { // Print line brackets to mark ranges of time if( printing_range ) { const size_t last_line = log_from_top ? line - 1 : line + 1; wattron( w, bracket_color ); mvwaddch( w, border_width + last_line, border_width + time_width - 1, LINE_XOXO ); wattroff( w, bracket_color ); } wattron( w, bracket_color ); mvwaddch( w, border_width + line, border_width + time_width - 1, log_from_top ? LINE_XXOO : LINE_OXXO ); wattroff( w, bracket_color ); printing_range = true; } // Decrement for !log_from_top is done at the beginning if( log_from_top ) { ++line; } } if( filtering ) { wrefresh( w ); // Print the help text werase( w_filter_help ); draw_border( w_filter_help, border_color ); for( size_t line = 0; line < help_text.size(); ++line ) { nc_color col = filter_help_color; print_colored_text( w_filter_help, border_width + line, border_width, col, col, help_text[line] ); } mvwprintz( w_filter_help, w_fh_height - 1, border_width, border_color, "< " ); mvwprintz( w_filter_help, w_fh_height - 1, w_fh_width - border_width - 2, border_color, " >" ); wrefresh( w_filter_help ); // This line is preventing this method from being const filter.query( false, true ); // Draw only } else { if( filter_str.empty() ) { mvwprintz( w, w_height - 1, border_width, border_color, _( "< Press %s to filter, %s to reset >" ), ctxt.get_desc( "FILTER" ), ctxt.get_desc( "RESET_FILTER" ) ); } else { mvwprintz( w, w_height - 1, border_width, border_color, "< %s >", filter_str ); mvwprintz( w, w_height - 1, border_width + 2, filter_color, "%s", filter_str ); } wrefresh( w ); } }
void Messages::display_messages() { catacurses::window w = catacurses::newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH, ( TERMY > FULL_SCREEN_HEIGHT ) ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0, ( TERMX > FULL_SCREEN_WIDTH ) ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0 ); input_context ctxt( "MESSAGE_LOG" ); ctxt.register_action( "UP", _( "Scroll up" ) ); ctxt.register_action( "DOWN", _( "Scroll down" ) ); ctxt.register_action( "QUIT" ); ctxt.register_action( "HELP_KEYBINDINGS" ); /* Right-Aligning Time Epochs For Readability * ========================================== * Given display_messages(); right-aligns epochs, we must declare one quick variable first: * max_padlength refers to the length of the LONGEST possible unit of time returned by to_string_clipped() any language has to offer. * This variable is, for now, set to '10', which seems most reasonable. * * The reason right-aligned epochs don't use a "shortened version" (e.g. only showing the first letter) boils down to: * 1. The first letter of every time unit being unique is a property that might not carry across to other languages. * 2. Languages where displayed characters change depending on the characters preceding/following it will become unclear. * 3. Some polymorphic languages might not be able to appropriately convey meaning with one character (FRS is not a linguist- correct her on this if needed.) * * This right padlength is then incorporated into a so-called 'epoch_format' which, in turn, will be used to format the correct epoch. * If an external language introduces time units longer than 10 characters in size, consider altering these variables. * The game (likely) shan't segfault, though the text may appear a bit messed up if these variables aren't set properly. */ const int max_padlength = 10; /* Dealing With Screen Extremities * =============================== * 'maxlength' corresponds to the most extreme length a log message may be before foldstring() wraps it around to two or more lines. * The numbers subtracted from FULL_SCREEN_WIDTH are - in order: * '-2' the characters reserved for the borders of the box, both on the left and right side. * '-1' the leftmost guide character that's drawn on screen. * '-4' the padded three-digit number each epoch starts with. * '-max_padlength' the characters of space that are allocated to time units (e.g. "years", "minutes", etc.) * * 'bottom' works much like 'maxlength', but instead it refers to the amount of lines that the message box may hold. */ const int maxlength = FULL_SCREEN_WIDTH - 2 - 1 - 4 - max_padlength; const int bottom = FULL_SCREEN_HEIGHT - 2; const int msg_count = size(); /* Dealing With Scroll Direction * ============================= * Much like how the sidebar can have variable scroll direction, so will the message box. * To properly differentiate the two methods of displaying text, we will label them NEWEST-TOP, and OLDEST-TOP. This labeling should be self explanatory. * * Note that 'offset' tracks only our current position in the list; it shan't at all affect the manner in which the messages are drawn. * Messages are always drawn top-to-bottom. If NEWEST-TOP is used, then the top line (line=1) corresponds to the newest message. The one further down the second-newest, etc. * If the OLDEST-TOP method is used, then the top line (line=1) corresponds to the oldest message, and the bottom one to the newest. * The 'for (;;)' block below is nearly completely method-agnostic, save for the `player_messages.impl_->history(i)` call. * * In case of NEWEST-TOP, the 'i' variable easily enough corresponds to the newest message. * In case of OLDEST-TOP, the 'i' variable must be flipped- meaning the highest value of 'i' returns the result for the lowest value of 'i', etc. * To achieve this, the 'flip_message' variable is set to either the value of 'msg_count', or '0'. This variable is then subtracted from 'i' in each call to player_messages.impl_->history(); * * 'offset' refers to the corresponding message that will be displayed at the very TOP of the message box window. * NEWEST-TOP: 'offset' starts simply at '0' - the very top of the window. * OLDEST-TOP: 'offset' is set to the maximum value it could possibly be. That is- 'msg_count-bottom'. This way, the screen starts with the scrollbar all the way down. * 'retrieve_history' refers to the line that should be displayed- this is either 'i' if it's NEWEST-TOP, or a flipped version of 'i' if it's OLDEST-TOP. */ int offset = log_from_top ? 0 : ( msg_count - bottom ); const int flip = log_from_top ? 0 : msg_count - 1; for( ;; ) { werase( w ); draw_border( w ); mvwprintz( w, bottom + 1, 32, c_red, _( "Press %s to return" ), ctxt.get_desc( "QUIT" ).c_str() ); draw_scrollbar( w, offset, bottom, msg_count, 1, 0, c_white, true ); int line = 1; int lasttime = -1; for( int i = offset; i < msg_count; ++i ) { const int retrieve_history = abs( i - flip ); if( line > bottom ) { break; // This statement makes it so that no non-existent messages are printed (which usually results in a segfault) } else if( retrieve_history >= msg_count ) { continue; } const game_message &m = player_messages.impl_->history( retrieve_history ); const calendar timepassed = calendar::turn - m.timestamp_in_turns; std::string long_ago = to_string_clipped( time_duration::from_turns( timepassed ) ); nc_color col = msgtype_to_color( m.type, false ); // Here we separate the unit and amount from one another so that they can be properly padded when they're drawn on the screen. // Note that the very first character of 'unit' is often a space (except for languages where the time unit directly follows the number.) const auto amount_len = long_ago.find_first_not_of( "0123456789" ); std::string amount = long_ago.substr( 0, amount_len ); std::string unit = long_ago.substr( amount_len ); if( timepassed.get_turn() != lasttime ) { right_print( w, line, 2, c_light_blue, string_format( _( "%-3s%-10s" ), amount.c_str(), unit.c_str() ) ); lasttime = timepassed.get_turn(); } nc_color col_out = col; for( const std::string &folded : foldstring( m.get_with_count(), maxlength ) ) { if( line > bottom ) { break; } print_colored_text( w, line, 2, col_out, col, folded ); // So-called special "markers"- alternating '=' and '-'s at the edges of te message window so players can properly make sense of which message belongs to which time interval. // The '+offset%4' in the calculation makes it so that the markings scroll along with the messages. // On lines divisible by 4, draw a dark gray '-' at both horizontal extremes of the window. if( ( line + offset % 4 ) % 4 == 0 ) { mvwprintz( w, line, 1, c_dark_gray, "-" ); mvwprintz( w, line, FULL_SCREEN_WIDTH - 2, c_dark_gray, "-" ); // On lines divisible by 2 (but not 4), draw a light gray '=' at the horizontal extremes of the window. } else if( ( line + offset % 4 ) % 2 == 0 ) { mvwprintz( w, line, 1, c_light_gray, "=" ); mvwprintz( w, line, FULL_SCREEN_WIDTH - 2, c_light_gray, "=" ); } // Only now are we done with this line: line++; } } if( offset < msg_count - bottom ) { mvwprintz( w, bottom + 1, 5, c_magenta, "vvv" ); } if( offset > 0 ) { mvwprintz( w, bottom + 1, FULL_SCREEN_WIDTH - 8, c_magenta, "^^^" ); } wrefresh( w ); const std::string &action = ctxt.handle_input(); if( action == "DOWN" && offset < msg_count - bottom ) { offset++; } else if( action == "UP" && offset > 0 ) { offset--; } else if( action == "QUIT" ) { break; } } player_messages.impl_->curmes = calendar::turn.get_turn(); }