diff --git a/data/json/ui/sidebar.json b/data/json/ui/sidebar.json index d3bc1fe126ed4..c08b0a1950c7c 100644 --- a/data/json/ui/sidebar.json +++ b/data/json/ui/sidebar.json @@ -299,7 +299,7 @@ { "id": "stamina_graph", "type": "widget", - "label": "Stam", + "label": "Stamina", "var": "stamina", "style": "graph", "width": 10, @@ -425,6 +425,7 @@ "id": "hitpoint_graphs_top_layout", "type": "widget", "style": "layout", + "label": "HP Top", "arrange": "columns", "widgets": [ "hp_left_arm_graph", "hp_head_graph", "hp_right_arm_graph" ] }, @@ -432,25 +433,34 @@ "id": "hitpoint_graphs_bottom_layout", "type": "widget", "style": "layout", + "label": "HP Bottom", "arrange": "columns", "widgets": [ "hp_left_leg_graph", "hp_torso_graph", "hp_right_leg_graph" ] }, { - "id": "hitpoints_head_torso", + "id": "hitpoints_all_graphs_layout", + "type": "widget", + "style": "layout", + "label": "Hit Points", + "arrange": "rows", + "widgets": [ "hitpoints_top_layout", "hitpoints_bottom_layout" ] + }, + { + "id": "hitpoints_head_torso_layout", "type": "widget", "style": "layout", "arrange": "columns", "widgets": [ "hp_head_graph", "hp_torso_graph" ] }, { - "id": "hitpoints_arms", + "id": "hitpoints_arms_layout", "type": "widget", "style": "layout", "arrange": "columns", "widgets": [ "hp_left_arm_graph", "hp_right_arm_graph" ] }, { - "id": "hitpoints_legs", + "id": "hitpoints_legs_layout", "type": "widget", "style": "layout", "arrange": "columns", @@ -472,6 +482,14 @@ "arrange": "columns", "widgets": [ "hp_left_leg_num", "hp_torso_num", "hp_right_leg_num" ] }, + { + "id": "hitpoints_all_narrow_graphs_layout", + "type": "widget", + "style": "layout", + "label": "Hit Points", + "arrange": "rows", + "widgets": [ "hitpoints_head_torso_layout", "hitpoints_arms_layout", "hitpoints_legs_layout" ] + }, { "id": "encumbrance_top_layout", "type": "widget", @@ -504,6 +522,7 @@ "id": "stamina_fatigue_layout", "type": "widget", "style": "layout", + "label": "Stamina/Fatigue", "arrange": "columns", "widgets": [ "stamina_graph", "fatigue_graph" ] }, @@ -521,45 +540,83 @@ "arrange": "columns", "widgets": [ "wetness_left_leg_num", "wetness_torso_num", "wetness_right_leg_num" ] }, + { + "id": "speed_focus_layout", + "type": "widget", + "style": "layout", + "label": "Speed/Focus", + "arrange": "columns", + "widgets": [ "speed_num", "focus_num" ] + }, + { + "id": "weapon_style_layout", + "type": "widget", + "style": "layout", + "label": "Weapon/Style", + "arrange": "rows", + "widgets": [ "wielding_desc", "style_desc" ] + }, + { + "id": "needs_desc_layout", + "type": "widget", + "style": "layout", + "label": "Needs", + "arrange": "rows", + "widgets": [ "hunger_desc", "thirst_desc", "fatigue_desc", "pain_desc", "body_temp_desc" ] + }, { "id": "stamina_speed_layout", "type": "widget", "style": "layout", + "label": "Stamina/Speed", "arrange": "columns", "widgets": [ "stamina_graph_classic", "speed_num" ] }, { - "id": "focus_move_layout", + "id": "stamina_activity_weary_layout", + "type": "widget", + "style": "layout", + "label": "Stamina/Weariness", + "arrange": "rows", + "widgets": [ "stamina_graph", "activity_desc", "weariness_desc", "weary_malus_desc" ] + }, + { + "id": "mood_focus_layout", "type": "widget", "style": "layout", + "label": "Mood/Focus", "arrange": "columns", - "widgets": [ "focus_num", "move_num" ] + "widgets": [ "mood_desc", "focus_num" ] }, { "id": "stamina_fatigue_focus_layout", "type": "widget", "style": "layout", + "label": "Stamina/Fatigue/Focus", "arrange": "columns", "widgets": [ "stamina_graph_classic", "fatigue_graph", "focus_num" ] }, { - "id": "speed_move_layout", + "id": "move_speed_layout", "type": "widget", "style": "layout", + "label": "Move/Speed", "arrange": "columns", - "widgets": [ "speed_num", "move_num" ] + "widgets": [ "move_num", "speed_num" ] }, { - "id": "sound_speed_move_layout", + "id": "safe_sound_layout", "type": "widget", "style": "layout", + "label": "Safe/Sound", "arrange": "columns", - "widgets": [ "sound_num", "speed_num", "move_num" ] + "widgets": [ "safe_mode_desc", "sound_num" ] }, { "id": "sound_fatigue_focus_layout", "type": "widget", "style": "layout", + "label": "Sound/Fatigue/Focus", "arrange": "columns", "widgets": [ "sound_num", "fatigue_graph", "focus_num" ] }, @@ -567,6 +624,7 @@ "id": "stamina_speed_move_layout", "type": "widget", "style": "layout", + "label": "Stamina/Speed/Move", "arrange": "columns", "widgets": [ "stamina_graph_classic", "speed_num", "move_num" ] }, @@ -574,6 +632,7 @@ "id": "sound_focus_layout", "type": "widget", "style": "layout", + "label": "Sound/Focus", "arrange": "columns", "widgets": [ "sound_num", "focus_num" ] }, @@ -581,6 +640,7 @@ "id": "stats_layout", "type": "widget", "style": "layout", + "label": "Stats", "arrange": "columns", "widgets": [ "str_num", "dex_num", "int_num", "per_num" ] }, @@ -588,6 +648,7 @@ "id": "str_dex_layout", "type": "widget", "style": "layout", + "label": "Str/Dex", "arrange": "columns", "widgets": [ "str_num", "dex_num" ] }, @@ -595,9 +656,18 @@ "id": "int_per_layout", "type": "widget", "style": "layout", + "label": "Int/Per", "arrange": "columns", "widgets": [ "int_num", "per_num" ] }, + { + "id": "stats_narrow_layout", + "type": "widget", + "style": "layout", + "label": "Stats", + "arrange": "rows", + "widgets": [ "mood_focus_layout", "move_speed_layout", "str_dex_layout", "int_per_layout" ] + }, { "id": "activity_desc", "type": "widget", @@ -609,7 +679,7 @@ { "id": "body_temp_desc", "type": "widget", - "label": "Body Heat", + "label": "Heat", "style": "text", "var": "body_temp_text", "//": "Uses display::temp_text_color" @@ -740,7 +810,7 @@ { "id": "power_desc", "type": "widget", - "label": "Power", + "label": "Bionic Power", "style": "text", "var": "power_text" }, @@ -761,36 +831,43 @@ { "id": "safe_mode_desc", "type": "widget", - "label": "Safe Mode", + "label": "Safe", "style": "text", "var": "safe_mode_text" }, { - "id": "root_layout_wide", + "id": "light_moon_wind_temp_layout", "type": "widget", + "label": "Environment", "style": "layout", "arrange": "rows", - "widgets": [ - "hitpoint_graphs_top_layout", - "hitpoint_graphs_bottom_layout", - "sound_fatigue_focus_layout", - "stamina_speed_move_layout", - "stats_layout" - ] + "widgets": [ "lighting_desc", "moon_phase_desc", "wind_desc", "env_temp_desc" ] }, { - "id": "root_layout_narrow", + "id": "place_date_time_layout", "type": "widget", + "label": "Place/Date/Time", "style": "layout", "arrange": "rows", + "widgets": [ "place_desc", "date_desc", "time_desc" ] + }, + { + "id": "custom_sidebar", + "type": "widget", + "style": "sidebar", + "label": "custom", + "width": 36, "widgets": [ - "hitpoints_head_torso", - "hitpoints_arms", - "hitpoints_legs", - "stamina_speed_layout", - "focus_move_layout", - "str_dex_layout", - "int_per_layout" + "hitpoints_all_narrow_graphs_layout", + "stats_narrow_layout", + "stamina_activity_weary_layout", + "needs_desc_layout", + "safe_sound_layout", + "power_desc", + "rad_badge_desc", + "weapon_style_layout", + "place_date_time_layout", + "light_moon_wind_temp_layout" ] } ] diff --git a/data/mods/TEST_DATA/widgets.json b/data/mods/TEST_DATA/widgets.json index 9092a8105f649..e10ed0e315ea9 100644 --- a/data/mods/TEST_DATA/widgets.json +++ b/data/mods/TEST_DATA/widgets.json @@ -198,8 +198,30 @@ "id": "test_stat_panel", "type": "widget", "style": "layout", + "arrange": "columns", "widgets": [ "test_str_num", "test_dex_num", "test_int_num", "test_per_num" ] }, + { + "id": "test_2_column_layout", + "type": "widget", + "style": "layout", + "arrange": "columns", + "widgets": [ "test_move_num", "test_speed_num" ] + }, + { + "id": "test_3_column_layout", + "type": "widget", + "style": "layout", + "arrange": "columns", + "widgets": [ "test_move_num", "test_speed_num", "test_focus_num" ] + }, + { + "id": "test_4_column_layout", + "type": "widget", + "style": "layout", + "arrange": "columns", + "widgets": [ "test_move_num", "test_speed_num", "test_focus_num", "test_mana_num" ] + }, { "id": "test_text_widget", "type": "widget", diff --git a/doc/SIDEBAR_MOD.md b/doc/SIDEBAR_MOD.md index ae01a11401f27..68849333ccaf8 100644 --- a/doc/SIDEBAR_MOD.md +++ b/doc/SIDEBAR_MOD.md @@ -1,29 +1,39 @@ # Sidebar Modification - [Overview](#overview) -- [About widgets](#about-widgets) -- [Widget variables](#widget-variables) -- [Number widget](#number-widget) -- [Graph widget](#graph-widget) - - [fill](#fill) - - [var_max](#var-max) -- [Layout widget](#layout-widget) - - [Root layouts](#root-layouts) - +- [Widgets](#widgets) +- [Sidebar widgets](#sidebar-widgets) +- [Layout widgets](#layout-widgets) +- [Variable widgets](#variable-widgets) + - [Number widget](#number-widget) + - [Graph widget](#graph-widget) + - [fill](#fill) + - [var_max](#var-max) + - [Colors](#colors) ## Overview Some parts of the main CDDA sidebar are now moddable, meaning they are data-driven and can be customized simply by editing JSON files, without recompiling the game. -You can add the custom sidebar via the Sidebar Options menu `}` by enabling the "Custom" section. +You can add a custom sidebar section to your regular sidebar via the Sidebar Options menu `}` +by enabling the "Custom" section from the left-hand column for any of the regular sidebar layouts +(classic, labels, narrow etc.) + +You can also switch to an almost completely custom sidebar, by selecting "custom" from the +right-hand column of the sidebar options menu. This layout is built from the "custom_sidebar" widget +defined in `data/json/ui/sidebar.json`, with sections you can toggle or rearrange in-game according +to your preference. +In both cases, you can further customize your sidebar widgets by modifying (or modding) the JSON +that describes them. This document explains how they work. -## About widgets -Sidebar UI elements are defined in objects called widgets. A widget can display a variety of player -character attributes in numeric form, or as a bar graph of arbitrary width. A widget can also make a -layout of other widgets. +## Widgets + +All "custom" sidebar UI elements are defined in objects called widgets. A widget can display a +variety of player character attributes in numeric form, or as a bar graph of arbitrary width. A +widget can also make a layout of other widgets. Widget instances are defined by JSON data, with the main game sidebar widgets and layouts being in `data/json/ui/sidebar.json`. You may customize yours by editing this file, or by loading a mod that @@ -43,21 +53,113 @@ For example, here is a widget to display the player character's "Focus" attribut All widgets must have a unique "id", and "type": "widget". -Widgets have the following "style" options: +Each widget has a "style" field that may be: -- `number`: Display value as a plain integer number +- `number`: Show value as a plain integer number - `graph`: Show a bar graph of the value with colored text characters - `text`: Show text from a `*_text` variable -- `layout`: Special style; this widget will be a layout container for other widgets +- `layout`: Layout container for arranging other widgets in rows or columns +- `sidebar`: Special top-level widget for defining custom sidebars + +Let's start at the top, with the "sidebar" widget, composed of several "layout" widgets. + + +## Sidebar widget + +The highest-level widget is the "sidebar", which represents the entire display region on the right +(or left) edge of the screen. It includes a "width" in characters, a "label" displayed in the +sidebar options menu, and a list of "widgets", shown as sections that may be rearranged or +toggled from the sidebar options menu. + +These sub-widgets are typically [layout widgets](#layout-widgets), with other widgets arranged +inside them, but they could also be plain [variable widgets](#variable-widgets), used for showing +character attributes or other information. + +Here is how a simple sidebar definition might look in JSON: + +```json +{ + "id": "my_sidebar", + "style": "sidebar", + "width": 40, + "widgets": [ + "sound_focus_move_layout", + "stats_layout" + ] +} +``` -Non-layout widgets must define a "var" field, with the name of a predefined widget variable. +Each widget in the "sidebar" will be associated with a `panel_layout` instance in the code, which is +what allows them to be toggled and rearranged like the classic sidebar sections. +You may define any number of "sidebar" widgets, each with their own width, label, and collection of +sub-widgets and layouts. -## Widget variables +Sidebar widgets aside, there are two major types of widget: [variable widgets](#variable-widgets), +showing some piece of information (with a label); and [layout widgets](#layout-widgets), used for +arranging other widgets in rows or columns. + +We will look at layout widgets first, since they are easier to explain. + + +## Layout widgets + +Use widgets with "style": "layout" to arrange child widgets in sidebar panels, giving widget ids in +the "widgets" list field. + +The arrangement of child widgets is defined by the "arrange" field, which may be "columns" (default) +to array widgets horizontally, or "rows" to arrange them vertically, one widget per row. Widgets in +the same row will have their horizontal space split as equally as possible. + +```json +[ + { + "id": "sound_focus_move_layout", + "type": "widget", + "style": "layout", + "arrange": "columns", + "widgets": [ "sound_num", "focus_num", "move_num" ] + }, + { + "id": "stats_layout", + "type": "widget", + "style": "layout", + "arrange": "columns", + "widgets": [ "str_num", "dex_num", "int_num", "per_num" ] + }, + { + "id": "sound_focus_move_stats_layout", + "type": "widget", + "style": "layout", + "arrange": "rows", + "widgets": [ + "sound_focus_move_layout", + "stats_layout" + ] + } +] +``` -The "var" field of a widget tells what variable data gives the widget its value. Valid var names -are given by the `widget_var` enum defined in `widget.h`. In the widget's `show` method, these var -enums determine which avatar method(s) to get their values from. +The above might yield: + +``` +Sound: 8 Focus: 105 Move: 120 +Str: 8 Dex: 9 Int: 7 Per: 11 +``` + +Where do all these numeric widgets and their values come from? These are variable widgets, discussed +next. + + +## Variable widgets + +Variable widgets define a "var" field, with the name of a predefined widget variable. This tells the +widget what information it should show. Most of the time, these are attributes of the player +character, but they can also be attributes of the world, environment, or vehicle where they are. + +The "var" field of a display widget tells what variable data gives the widget its value. Valid var +names are given by the `widget_var` enum defined in `widget.h`. In the widget's `show` method, these +var enums determine which avatar method(s) to get their values from. Below are a few examples of vars and what they mean. See the `widget_var` list in `widget.h` for the definitive list of vars. @@ -126,7 +228,7 @@ define a "var_max" as a cutoff point; see the "Graph widget" section for more. You may also define "var_min" if it's relevant. By default this is 0. -## Number widget +### Number widget The simplest and usually most compact widget for displaying a value, "style": "number" appears as a label with an integer number. @@ -147,7 +249,7 @@ Focus: 100 The numeric value comes from the given "var", displayed as a decimal integer. -## Graph widget +### Graph widget The graph shows an arrangement of symbols. It has two important parameters: @@ -231,7 +333,7 @@ The method is specified with the "fill" field. This example uses the default "bu there is also a "pool" method, described in the next section. -### fill +#### fill With "bucket" fill, positions are filled like a row of buckets, using all symbols in the first position before beginning to fill the next position. This is like the classic 5-bar HP meter. @@ -290,7 +392,7 @@ Result: The total number of possible graphs is the same in each case, so both have the same resolution. -### var_max +#### var_max Using "graph" style widgets, usually you should provide a "var_max" value (integer) with the maximum typical value of "var" that will ever be rendered. @@ -304,62 +406,7 @@ up to 100 or 200 (like focus). If a var usually varies within a range `[low, hig "var_max" greater than `high` to be sure the normal variance is captured in the graph's range. -## Layout widget - -Use widgets with "style": "layout" to arrange child widgets in sidebar panels, giving widget ids in -the "widgets" list field. - -The arrangement of child widgets is defined by the "arrange" field, which may be "columns" (default) -to array widgets horizontally, or "rows" to arrange them vertically, one widget per row. Widgets in -the same row will have their horizontal space split as equally as possible. - -```json -[ - { - "id": "sound_focus_move_layout", - "type": "widget", - "style": "layout", - "arrange": "columns", - "widgets": [ "sound_num", "focus_num", "move_num" ] - }, - { - "id": "stats_layout", - "type": "widget", - "style": "layout", - "arrange": "columns", - "widgets": [ "str_num", "dex_num", "int_num", "per_num" ] - }, - { - "id": "root_layout", - "type": "widget", - "style": "layout", - "arrange": "rows", - "widgets": [ - "sound_focus_move_layout", - "stats_layout" - ] - } -] -``` - -The above might yield: - -``` -Sound: 8 Focus: 105 Move: 120 -Str: 8 Dex: 9 Int: 7 Per: 11 -``` - -### Root layouts - -There are two important "root layout" widgets defined in `data/json/ui/sidebar.json`: - -- Widget id "root_layout_wide" is used for "labels" and "classic" sidebars -- Widget id "root_layout_narrow" is used for "compact" and "labels narrow" sidebars - -Modify or override the root layout widget to define all sub-layouts or child widgets you want to see -in the custom section of your sidebar. - -## Colors +### Colors Widgets with "number" or "graph" style may define "colors", which will be used as a spectrum across the widget's values ("var_min" to "var_max"), applying the appropriate color at each level. @@ -398,4 +445,3 @@ yellow, light red, and red. Such coloration could be represented with "colors" l } ``` - diff --git a/src/avatar.h b/src/avatar.h index ce91fcf48f5ee..5a4973b7e7672 100644 --- a/src/avatar.h +++ b/src/avatar.h @@ -279,6 +279,10 @@ class avatar : public Character return mon_visible; } + const monster_visible_info &get_mon_visible() const { + return mon_visible; + } + struct daily_calories { int spent = 0; int gained = 0; diff --git a/src/game.cpp b/src/game.cpp index 61b1c30bda98b..830b074cc9250 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -429,7 +429,6 @@ void game::load_static_data() fullscreen = false; was_fullscreen = false; show_panel_adm = false; - panel_manager::get_manager().init(); // These functions do not load stuff from json. // The content they load/initialize is hardcoded into the program. @@ -677,8 +676,9 @@ void game::setup() load_core_data( ui ); } - load_world_modfiles( ui ); + // Panel manager needs JSON data to be loaded before init + panel_manager::get_manager().init(); m = map(); @@ -3362,25 +3362,30 @@ void game::draw_panels( bool force_draw ) int y = 0; const bool sidebar_right = get_option<std::string>( "SIDEBAR_POSITION" ) == "right"; int spacer = get_option<bool>( "SIDEBAR_SPACERS" ) ? 1 : 0; + // Total up height used by all panels, and see what is left over for log int log_height = 0; for( const window_panel &panel : mgr.get_current_layout().panels() ) { + // The panel with height -2 is the message log panel if( panel.get_height() != -2 && panel.toggle && panel.render() ) { log_height += panel.get_height() + spacer; } } log_height = std::max( TERMY - log_height, 3 ); + // Draw each panel having render() true for( const window_panel &panel : mgr.get_current_layout().panels() ) { if( panel.render() ) { // height clamped to window height. int h = std::min( panel.get_height(), TERMY - y ); + // The panel with height -2 is the message log panel if( h == -2 ) { h = log_height; } h += spacer; if( panel.toggle && panel.render() && h > 0 ) { if( panel.always_draw || draw_this_turn ) { - panel.draw( u, catacurses::newwin( h, panel.get_width(), - point( sidebar_right ? TERMX - panel.get_width() : 0, y ) ) ); + catacurses::window w = catacurses::newwin( h, panel.get_width(), + point( sidebar_right ? TERMX - panel.get_width() : 0, y ) ); + panel.draw( { u, w, panel.get_widget() } ); } if( show_panel_adm ) { const std::string panel_name = panel.get_name(); diff --git a/src/init.cpp b/src/init.cpp index 4b57451a76a55..ae6777233b799 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -624,6 +624,7 @@ void DynamicDataLoader::unload_data() vpart_category::reset(); weakpoints::reset(); weather_types::reset(); + widget::reset(); } void DynamicDataLoader::finalize_loaded_data() @@ -703,6 +704,7 @@ void DynamicDataLoader::finalize_loaded_data( loading_ui &ui ) { _( "Anatomies" ), &anatomy::finalize_all }, { _( "Mutations" ), &mutation_branch::finalize }, { _( "Achievements" ), &achievement::finalize }, + { _( "Widgets" ), &widget::finalize }, #if defined(TILES) { _( "Tileset" ), &load_tileset }, #endif diff --git a/src/panels.cpp b/src/panels.cpp index 9024e7525d2a2..a3fc9e8e708b9 100644 --- a/src/panels.cpp +++ b/src/panels.cpp @@ -85,13 +85,14 @@ static const trait_id trait_NOPAIN( "NOPAIN" ); // constructor window_panel::window_panel( - const std::function<void( avatar &, const catacurses::window & )> &draw_func, + const std::function<void( const draw_args & )> &draw_func, const std::string &id, const translation &nm, const int ht, const int wd, const bool default_toggle_, const std::function<bool()> &render_func, const bool force_draw ) : draw( draw_func ), render( render_func ), toggle( default_toggle_ ), always_draw( force_draw ), height( ht ), width( wd ), id( id ), name( nm ) { + wgt = widget_id::NULL_ID(); } // ==================================== @@ -106,26 +107,6 @@ static std::string trunc_ellipse( const std::string &input, unsigned int trunc ) return input; } -static void draw_rectangle( const catacurses::window &w, nc_color, point top_left, - point bottom_right ) -{ - // corners - mvwaddch( w, top_left, LINE_OXXO ); - mvwaddch( w, point( top_left.x, bottom_right.y ), LINE_XXOO ); - mvwaddch( w, point( bottom_right.x, top_left.y ), LINE_OOXX ); - mvwaddch( w, bottom_right, LINE_XOOX ); - - for( int i = 1; i < bottom_right.x; i++ ) { - mvwaddch( w, point( i, top_left.y ), LINE_OXOX ); - mvwaddch( w, point( i, bottom_right.y ), LINE_OXOX ); - } - - for( int i = 1; i < bottom_right.y; i++ ) { - mvwaddch( w, point( top_left.x, i ), LINE_XOXO ); - mvwaddch( w, point( bottom_right.x, i ), LINE_XOXO ); - } -} - std::pair<std::string, nc_color> display::str_text_color( const Character &p ) { nc_color clr; @@ -232,8 +213,17 @@ std::string window_panel::get_name() const return name.translated(); } -panel_layout::panel_layout( const translation &_name, - const std::vector<window_panel> &_panels ) +void window_panel::set_widget( const widget_id &w ) +{ + wgt = w; +} + +const widget_id &window_panel::get_widget() const +{ + return wgt; +} + +panel_layout::panel_layout( const translation &_name, const std::vector<window_panel> &_panels ) : _name( _name ), _panels( _panels ) { } @@ -855,7 +845,7 @@ std::pair<std::string, nc_color> display::safe_mode_text_color( const bool class // panels code // =============================== -static void draw_limb_health( avatar &u, const catacurses::window &w, bodypart_id bp ) +static void draw_limb_health( const avatar &u, const catacurses::window &w, bodypart_id bp ) { const bool no_feeling = u.has_trait( trait_NOPAIN ); static auto print_symbol_num = []( const catacurses::window & w, int num, const std::string & sym, @@ -912,8 +902,11 @@ static void draw_limb_health( avatar &u, const catacurses::window &w, bodypart_i } } -static void draw_limb2( avatar &u, const catacurses::window &w ) +static void draw_limb2( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); // print bodypart health int i = 0; @@ -1440,8 +1433,11 @@ std::pair<std::string, nc_color> display::rad_badge_text_color( const Character return std::make_pair( rad_text, rad_color ); } -static void draw_stats( avatar &u, const catacurses::window &w ) +static void draw_stats( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); nc_color stat_clr = display::str_text_color( u ).second; mvwprintz( w, point_zero, c_light_gray, _( "STR" ) ); @@ -1489,8 +1485,11 @@ std::pair<std::string, nc_color> display::move_mode_text_color( const Character return std::make_pair( mm_text, mm_color ); } -static void draw_stealth( avatar &u, const catacurses::window &w ) +static void draw_stealth( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); mvwprintz( w, point_zero, c_light_gray, _( "Speed" ) ); mvwprintz( w, point( 7, 0 ), value_color( u.get_speed() ), "%s", u.get_speed() ); @@ -1553,8 +1552,11 @@ static void draw_time_graphic( const catacurses::window &w ) wprintz( w, c_white, "]" ); } -static void draw_time( const avatar &u, const catacurses::window &w ) +static void draw_time( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); // display date mvwprintz( w, point_zero, c_light_gray, calendar::name_season( season_of_year( calendar::turn ) ) ); @@ -1578,8 +1580,11 @@ static void draw_time( const avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_needs_compact( const avatar &u, const catacurses::window &w ) +static void draw_needs_compact( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); auto hunger_pair = display::hunger_text_color( u ); @@ -1603,8 +1608,11 @@ static void draw_needs_compact( const avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_limb_narrow( avatar &u, const catacurses::window &w ) +static void draw_limb_narrow( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); int ny2 = 0; int i = 0; @@ -1648,8 +1656,11 @@ static void draw_limb_narrow( avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_limb_wide( avatar &u, const catacurses::window &w ) +static void draw_limb_wide( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); int i = 0; for( const bodypart_id &bp : @@ -1667,8 +1678,11 @@ static void draw_limb_wide( avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_char_narrow( avatar &u, const catacurses::window &w ) +static void draw_char_narrow( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); std::pair<std::string, nc_color> morale_pair = display::morale_face_color( u ); // NOLINTNEXTLINE(cata-use-named-point-constants) @@ -1708,8 +1722,12 @@ static void draw_char_narrow( avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_char_wide( avatar &u, const catacurses::window &w ) +static void draw_char_wide( const draw_args &args ) { + + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); std::pair<std::string, nc_color> morale_pair = display::morale_face_color( u ); // NOLINTNEXTLINE(cata-use-named-point-constants) @@ -1744,8 +1762,11 @@ static void draw_char_wide( avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_stat_narrow( avatar &u, const catacurses::window &w ) +static void draw_stat_narrow( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); // NOLINTNEXTLINE(cata-use-named-point-constants) @@ -1789,8 +1810,12 @@ static void draw_stat_narrow( avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_stat_wide( avatar &u, const catacurses::window &w ) +static void draw_stat_wide( const draw_args &args ) { + + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); mvwprintz( w, point_east, c_light_gray, _( "Str :" ) ); @@ -1832,8 +1857,11 @@ static void draw_stat_wide( avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_loc_labels( const avatar &u, const catacurses::window &w, bool minimap ) +static void draw_loc_labels( const draw_args &args, bool minimap ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); // display location const oter_id &cur_ter = overmap_buffer.ter( u.global_omt_location() ); @@ -1870,23 +1898,26 @@ static void draw_loc_labels( const avatar &u, const catacurses::window &w, bool wnoutrefresh( w ); } -static void draw_loc_narrow( const avatar &u, const catacurses::window &w ) +static void draw_loc_narrow( const draw_args &args ) { - draw_loc_labels( u, w, false ); + draw_loc_labels( args, false ); } -static void draw_loc_wide( const avatar &u, const catacurses::window &w ) +static void draw_loc_wide( const draw_args &args ) { - draw_loc_labels( u, w, false ); + draw_loc_labels( args, false ); } -static void draw_loc_wide_map( const avatar &u, const catacurses::window &w ) +static void draw_loc_wide_map( const draw_args &args ) { - draw_loc_labels( u, w, true ); + draw_loc_labels( args, true ); } -static void draw_moon_narrow( const avatar &u, const catacurses::window &w ) +static void draw_moon_narrow( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); // NOLINTNEXTLINE(cata-use-named-point-constants) mvwprintz( w, point( 1, 0 ), c_light_gray, _( "Moon : %s" ), display::get_moon() ); @@ -1895,8 +1926,11 @@ static void draw_moon_narrow( const avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_moon_wide( const avatar &u, const catacurses::window &w ) +static void draw_moon_wide( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); // NOLINTNEXTLINE(cata-use-named-point-constants) mvwprintz( w, point( 1, 0 ), c_light_gray, _( "Moon : %s" ), display::get_moon() ); @@ -1904,8 +1938,11 @@ static void draw_moon_wide( const avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_weapon_labels( const avatar &u, const catacurses::window &w ) +static void draw_weapon_labels( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); // NOLINTNEXTLINE(cata-use-named-point-constants) mvwprintz( w, point( 1, 0 ), c_light_gray, _( "Wield:" ) ); @@ -1916,8 +1953,11 @@ static void draw_weapon_labels( const avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_needs_narrow( const avatar &u, const catacurses::window &w ) +static void draw_needs_narrow( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); std::pair<std::string, nc_color> hunger_pair = display::hunger_text_color( u ); std::pair<std::string, nc_color> thirst_pair = display::thirst_text_color( u ); @@ -1939,8 +1979,11 @@ static void draw_needs_narrow( const avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_needs_labels( const avatar &u, const catacurses::window &w ) +static void draw_needs_labels( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); std::pair<std::string, nc_color> hunger_pair = display::hunger_text_color( u ); std::pair<std::string, nc_color> thirst_pair = display::thirst_text_color( u ); @@ -1966,8 +2009,11 @@ static void draw_needs_labels( const avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_needs_labels_alt( const avatar &u, const catacurses::window &w ) +static void draw_needs_labels_alt( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); std::pair<std::string, nc_color> hunger_pair = display::hunger_text_color( u ); std::pair<std::string, nc_color> thirst_pair = display::thirst_text_color( u ); @@ -1991,8 +2037,11 @@ static void draw_needs_labels_alt( const avatar &u, const catacurses::window &w wnoutrefresh( w ); } -static void draw_sound_labels( const avatar &u, const catacurses::window &w ) +static void draw_sound_labels( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); // NOLINTNEXTLINE(cata-use-named-point-constants) mvwprintz( w, point( 1, 0 ), c_light_gray, _( "Sound:" ) ); @@ -2004,8 +2053,11 @@ static void draw_sound_labels( const avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_sound_narrow( const avatar &u, const catacurses::window &w ) +static void draw_sound_narrow( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); // NOLINTNEXTLINE(cata-use-named-point-constants) mvwprintz( w, point( 1, 0 ), c_light_gray, _( "Sound:" ) ); @@ -2017,8 +2069,11 @@ static void draw_sound_narrow( const avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_env_compact( avatar &u, const catacurses::window &w ) +static void draw_env_compact( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); // Minimap to the left of text labels @@ -2077,8 +2132,11 @@ std::pair<std::string, nc_color> display::wind_text_color( const Character &u ) return std::make_pair( wind_text, get_wind_color( windpower ) ); } -static void render_wind( avatar &u, const catacurses::window &w, const std::string &formatstr ) +static void render_wind( const draw_args &args, const std::string &formatstr ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); mvwprintz( w, point_zero, c_light_gray, //~ translation should not exceed 5 console cells @@ -2089,18 +2147,21 @@ static void render_wind( avatar &u, const catacurses::window &w, const std::stri wnoutrefresh( w ); } -static void draw_wind( avatar &u, const catacurses::window &w ) +static void draw_wind( const draw_args &args ) { - render_wind( u, w, "%s: " ); + render_wind( args, "%s: " ); } -static void draw_wind_padding( avatar &u, const catacurses::window &w ) +static void draw_wind_padding( const draw_args &args ) { - render_wind( u, w, " %s: " ); + render_wind( args, " %s: " ); } -static void draw_health_classic( avatar &u, const catacurses::window &w ) +static void draw_health_classic( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + vehicle *veh = g->remoteveh(); if( veh == nullptr && u.in_vehicle ) { veh = veh_pointer_or_null( get_map().veh_at( u.pos() ) ); @@ -2195,8 +2256,11 @@ static void draw_health_classic( avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_armor_padding( const avatar &u, const catacurses::window &w ) +static void draw_armor_padding( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); nc_color color = c_light_gray; // NOLINTNEXTLINE(cata-use-named-point-constants) @@ -2222,8 +2286,11 @@ static void draw_armor_padding( const avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_armor( const avatar &u, const catacurses::window &w ) +static void draw_armor( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); nc_color color = c_light_gray; mvwprintz( w, point_zero, color, _( "Head :" ) ); @@ -2248,8 +2315,10 @@ static void draw_armor( const avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_messages( avatar &, const catacurses::window &w ) +static void draw_messages( const draw_args &args ) { + const catacurses::window &w = args._win; + werase( w ); int line = getmaxy( w ) - 2; int maxlength = getmaxx( w ); @@ -2257,8 +2326,10 @@ static void draw_messages( avatar &, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_messages_classic( avatar &, const catacurses::window &w ) +static void draw_messages_classic( const draw_args &args ) { + const catacurses::window &w = args._win; + werase( w ); int line = getmaxy( w ) - 2; int maxlength = getmaxx( w ); @@ -2267,8 +2338,10 @@ static void draw_messages_classic( avatar &, const catacurses::window &w ) } #if defined(TILES) -static void draw_mminimap( avatar &, const catacurses::window &w ) +static void draw_mminimap( const draw_args &args ) { + const catacurses::window &w = args._win; + werase( w ); g->draw_pixel_minimap( w ); wnoutrefresh( w ); @@ -2276,7 +2349,7 @@ static void draw_mminimap( avatar &, const catacurses::window &w ) #endif // Print monster info to the given window -void display::print_mon_info( avatar &u, const catacurses::window &w, int hor_padding, +void display::print_mon_info( const avatar &u, const catacurses::window &w, int hor_padding, bool compact ) { const monster_visible_info &mon_visible = u.get_mon_visible(); @@ -2435,85 +2508,63 @@ void display::print_mon_info( avatar &u, const catacurses::window &w, int hor_pa } } -static void draw_compass( avatar &u, const catacurses::window &w ) +static void draw_compass( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); display::print_mon_info( u, w ); wnoutrefresh( w ); } -static void draw_compass_compact( avatar &u, const catacurses::window &w ) +static void draw_compass_compact( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); display::print_mon_info( u, w, 0, true ); wnoutrefresh( w ); } -static void draw_compass_padding( avatar &u, const catacurses::window &w ) +static void draw_compass_padding( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); display::print_mon_info( u, w, 1 ); wnoutrefresh( w ); } -static void draw_compass_padding_compact( avatar &u, const catacurses::window &w ) +static void draw_compass_padding_compact( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); display::print_mon_info( u, w, 1, true ); wnoutrefresh( w ); } -static void draw_overmap_narrow( avatar &u, const catacurses::window &w ) +static void draw_overmap( const draw_args &args ) { - werase( w ); - const tripoint_abs_omt curs = u.global_omt_location(); - draw_rectangle( w, c_light_gray, point_zero, point( 31, 13 ) ); - // NOLINTNEXTLINE(cata-use-named-point-constants) - overmap_ui::draw_overmap_chunk( w, u, curs, point( 1, 1 ), 30, 12 ); - wnoutrefresh( w ); -} + const avatar &u = args._ava; + const catacurses::window &w = args._win; -static void draw_overmap_wide( avatar &u, const catacurses::window &w ) -{ werase( w ); const tripoint_abs_omt curs = u.global_omt_location(); - draw_rectangle( w, c_light_gray, point_zero, point( 43, 19 ) ); // NOLINTNEXTLINE(cata-use-named-point-constants) - overmap_ui::draw_overmap_chunk( w, u, curs, point( 1, 1 ), 42, 18 ); + overmap_ui::draw_overmap_chunk( w, u, curs, point_zero, getmaxx( w ) - 1, getmaxy( w ) - 1 ); wnoutrefresh( w ); } -// Custom moddable sidebar -static void draw_mod_sidebar( avatar &u, const catacurses::window &w, const std::string layout_name, - const int width ) +static void draw_veh_compact( const draw_args &args ) { - werase( w ); - - // Render each row of the root layout widget - widget root = widget_id( layout_name ).obj(); - int row_num = 0; - for( const widget_id &row_wid : root._widgets ) { - widget row_widget = row_wid.obj(); - trim_and_print( w, point( 1, row_num ), width - 1, c_light_gray, _( row_widget.layout( u, - width - 1 ) ) ); - row_num++; - } + const avatar &u = args._ava; + const catacurses::window &w = args._win; - wnoutrefresh( w ); -} - -static void draw_mod_sidebar_narrow( avatar &u, const catacurses::window &w ) -{ - draw_mod_sidebar( u, w, "root_layout_narrow", 31 ); -} - -static void draw_mod_sidebar_wide( avatar &u, const catacurses::window &w ) -{ - draw_mod_sidebar( u, w, "root_layout_wide", 43 ); -} - -static void draw_veh_compact( const avatar &u, const catacurses::window &w ) -{ werase( w ); // vehicle display @@ -2530,8 +2581,11 @@ static void draw_veh_compact( const avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_veh_padding( const avatar &u, const catacurses::window &w ) +static void draw_veh_padding( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); // vehicle display @@ -2548,8 +2602,11 @@ static void draw_veh_padding( const avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_ai_goal( const avatar &u, const catacurses::window &w ) +static void draw_ai_goal( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); behavior::tree needs; needs.add( &behavior__node_t_npc_needs.obj() ); @@ -2560,8 +2617,11 @@ static void draw_ai_goal( const avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_location_classic( const avatar &u, const catacurses::window &w ) +static void draw_location_classic( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); mvwprintz( w, point_zero, c_light_gray, _( "Location:" ) ); @@ -2571,8 +2631,10 @@ static void draw_location_classic( const avatar &u, const catacurses::window &w wnoutrefresh( w ); } -static void draw_weather_classic( avatar &, const catacurses::window &w ) +static void draw_weather_classic( const draw_args &args ) { + const catacurses::window &w = args._win; + werase( w ); if( get_map().get_abs_sub().z < 0 ) { @@ -2589,8 +2651,11 @@ static void draw_weather_classic( avatar &, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_lighting_classic( const avatar &u, const catacurses::window &w ) +static void draw_lighting_classic( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); const std::pair<std::string, nc_color> ll = get_light_level( @@ -2608,8 +2673,11 @@ static void draw_lighting_classic( const avatar &u, const catacurses::window &w wnoutrefresh( w ); } -static void draw_weapon_classic( const avatar &u, const catacurses::window &w ) +static void draw_weapon_classic( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); mvwprintz( w, point_zero, c_light_gray, _( "Weapon :" ) ); @@ -2626,8 +2694,11 @@ static void draw_weapon_classic( const avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_time_classic( const avatar &u, const catacurses::window &w ) +static void draw_time_classic( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); // display date @@ -2652,8 +2723,10 @@ static void draw_time_classic( const avatar &u, const catacurses::window &w ) wnoutrefresh( w ); } -static void draw_hint( const avatar &, const catacurses::window &w ) +static void draw_hint( const draw_args &args ) { + const catacurses::window &w = args._win; + werase( w ); std::string press = press_x( ACTION_TOGGLE_PANEL_ADM ); // NOLINTNEXTLINE(cata-use-named-point-constants) @@ -2692,29 +2765,41 @@ static void draw_weariness_partial( const avatar &u, const catacurses::window &w wnoutrefresh( w ); } -static void draw_weariness( const avatar &u, const catacurses::window &w ) +static void draw_weariness( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); draw_weariness_partial( u, w, point_zero, false ); wnoutrefresh( w ); } -static void draw_weariness_narrow( const avatar &u, const catacurses::window &w ) +static void draw_weariness_narrow( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); draw_weariness_partial( u, w, point_east, false ); wnoutrefresh( w ); } -static void draw_weariness_wide( const avatar &u, const catacurses::window &w ) +static void draw_weariness_wide( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); draw_weariness_partial( u, w, point_east, true ); wnoutrefresh( w ); } -static void draw_weariness_classic( const avatar &u, const catacurses::window &w ) +static void draw_weariness_classic( const draw_args &args ) { + const avatar &u = args._ava; + const catacurses::window &w = args._win; + werase( w ); std::pair<std::string, nc_color> weary = display::weariness_text_color( u ); @@ -2757,24 +2842,36 @@ static void print_mana( const Character &you, const catacurses::window &w, wnoutrefresh( w ); } -static void draw_mana_classic( const Character &you, const catacurses::window &w ) +static void draw_mana_classic( const draw_args &args ) { - print_mana( you, w, "%s: %s %s: %s", -8, -5, 20, -5 ); + const avatar &u = args._ava; + const catacurses::window &w = args._win; + + print_mana( u, w, "%s: %s %s: %s", -8, -5, 20, -5 ); } -static void draw_mana_compact( const Character &you, const catacurses::window &w ) +static void draw_mana_compact( const draw_args &args ) { - print_mana( you, w, "%s %s %s %s", 4, -5, 12, -5 ); + const avatar &u = args._ava; + const catacurses::window &w = args._win; + + print_mana( u, w, "%s %s %s %s", 4, -5, 12, -5 ); } -static void draw_mana_narrow( const Character &you, const catacurses::window &w ) +static void draw_mana_narrow( const draw_args &args ) { - print_mana( you, w, " %s: %s %s : %s", -5, -5, 9, -5 ); + const avatar &u = args._ava; + const catacurses::window &w = args._win; + + print_mana( u, w, " %s: %s %s : %s", -5, -5, 9, -5 ); } -static void draw_mana_wide( const Character &you, const catacurses::window &w ) +static void draw_mana_wide( const draw_args &args ) { - print_mana( you, w, " %s: %s %s : %s", -5, -5, 13, -5 ); + const avatar &u = args._ava; + const catacurses::window &w = args._win; + + print_mana( u, w, " %s: %s %s : %s", -5, -5, 13, -5 ); } // ============ @@ -2820,12 +2917,10 @@ static std::vector<window_panel> initialize_default_classic_panels() ret.emplace_back( window_panel( draw_compass_padding_compact, "Alt Compass", to_translation( "Alt Compass" ), 5, 44, false ) ); - ret.emplace_back( window_panel( draw_overmap_wide, "Overmap", to_translation( "Overmap" ), + ret.emplace_back( window_panel( draw_overmap, "Overmap", to_translation( "Overmap" ), 20, 44, false ) ); ret.emplace_back( window_panel( draw_messages_classic, "Log", to_translation( "Log" ), -2, 44, true ) ); - ret.emplace_back( window_panel( draw_mod_sidebar_wide, "Custom", to_translation( "Custom" ), - 8, 44, false ) ); #if defined(TILES) ret.emplace_back( window_panel( draw_mminimap, "Map", to_translation( "Map" ), -1, 44, true, default_render, true ) ); @@ -2866,10 +2961,8 @@ static std::vector<window_panel> initialize_default_compact_panels() ret.emplace_back( window_panel( draw_compass_compact, "Alt Compass", to_translation( "Alt Compass" ), 5, 32, true ) ); - ret.emplace_back( window_panel( draw_overmap_narrow, "Overmap", to_translation( "Overmap" ), + ret.emplace_back( window_panel( draw_overmap, "Overmap", to_translation( "Overmap" ), 14, 32, false ) ); - ret.emplace_back( window_panel( draw_mod_sidebar_narrow, "Custom", to_translation( "Custom" ), - 8, 32, false ) ); #if defined(TILES) ret.emplace_back( window_panel( draw_mminimap, "Map", to_translation( "Map" ), -1, 32, true, default_render, true ) ); @@ -2919,10 +3012,8 @@ static std::vector<window_panel> initialize_default_label_narrow_panels() ret.emplace_back( window_panel( draw_compass_padding_compact, "Alt Compass", to_translation( "Alt Compass" ), 5, 32, false ) ); - ret.emplace_back( window_panel( draw_overmap_narrow, "Overmap", to_translation( "Overmap" ), + ret.emplace_back( window_panel( draw_overmap, "Overmap", to_translation( "Overmap" ), 14, 32, false ) ); - ret.emplace_back( window_panel( draw_mod_sidebar_narrow, "Custom", to_translation( "Custom" ), - 8, 32, false ) ); #if defined(TILES) ret.emplace_back( window_panel( draw_mminimap, "Map", to_translation( "Map" ), -1, 32, true, default_render, true ) ); @@ -2976,10 +3067,8 @@ static std::vector<window_panel> initialize_default_label_panels() ret.emplace_back( window_panel( draw_compass_padding_compact, "Alt Compass", to_translation( "Alt Compass" ), 5, 44, false ) ); - ret.emplace_back( window_panel( draw_overmap_wide, "Overmap", to_translation( "Overmap" ), + ret.emplace_back( window_panel( draw_overmap, "Overmap", to_translation( "Overmap" ), 20, 44, false ) ); - ret.emplace_back( window_panel( draw_mod_sidebar_wide, "Custom", to_translation( "Custom" ), - 8, 44, false ) ); #if defined(TILES) ret.emplace_back( window_panel( draw_mminimap, "Map", to_translation( "Map" ), -1, 44, true, default_render, true ) ); @@ -2990,6 +3079,58 @@ static std::vector<window_panel> initialize_default_label_panels() return ret; } +// Message on how to use the custom sidebar panel and edit its JSON +static void draw_custom_hint( const draw_args &args ) +{ + const catacurses::window &w = args._win; + + werase( w ); + // NOLINTNEXTLINE(cata-use-named-point-constants) + mvwprintz( w, point( 1, 0 ), c_white, _( "Custom sidebar" ) ); + // NOLINTNEXTLINE(cata-use-named-point-constants) + mvwprintz( w, point( 1, 1 ), c_light_gray, + _( "Edit sidebar.json to adjust." ) ); + mvwprintz( w, point( 1, 2 ), c_light_gray, + _( "See SIDEBAR_MOD.md for help." ) ); + + wnoutrefresh( w ); +} + +// Initialize custom panels from a given "sidebar" style widget +static std::vector<window_panel> initialize_default_custom_panels( const widget &wgt ) +{ + std::vector<window_panel> ret; + + // Use defined width, or at least 16 + const int width = std::max( wgt._width, 16 ); + + // Show hint on configuration + ret.emplace_back( window_panel( draw_custom_hint, "Hint", to_translation( "Hint" ), + 3, width, true ) ); + + // Add window panel for each child widget + for( const widget_id &row_wid : wgt._widgets ) { + widget row_widget = row_wid.obj(); + ret.emplace_back( row_widget.get_window_panel( width ) ); + } + + // Add compass, message log, and map to fill remaining space + // TODO: Make these into proper widgets + ret.emplace_back( window_panel( draw_messages, "Log", to_translation( "Log" ), + -2, width, true ) ); +#if defined(TILES) + ret.emplace_back( window_panel( draw_mminimap, "Map", to_translation( "Map" ), + -1, width, true, default_render, true ) ); +#endif // TILES + ret.emplace_back( window_panel( draw_compass_padding_compact, "Compass", + to_translation( "Compass" ), + 5, width, true ) ); + ret.emplace_back( window_panel( draw_overmap, "Overmap", to_translation( "Overmap" ), + 7, width, false ) ); + + return ret; +} + static std::map<std::string, panel_layout> initialize_default_panel_layouts() { std::map<std::string, panel_layout> ret; @@ -3003,13 +3144,22 @@ static std::map<std::string, panel_layout> initialize_default_panel_layouts() ret.emplace( "labels", panel_layout( to_translation( "labels" ), initialize_default_label_panels() ) ); + // Add panel layout for each "sidebar" widget + for( const widget &wgt : widget::get_all() ) { + if( wgt._style == "sidebar" ) { + ret.emplace( wgt._label.translated(), + panel_layout( wgt._label, initialize_default_custom_panels( wgt ) ) ); + } + } + return ret; } panel_manager::panel_manager() { current_layout_id = "labels"; - layouts = initialize_default_panel_layouts(); + // Set empty layouts; these will be populated by load() + layouts = std::map<std::string, panel_layout>(); } panel_layout &panel_manager::get_current_layout() @@ -3046,6 +3196,7 @@ int panel_manager::get_width_left() void panel_manager::init() { + layouts = initialize_default_panel_layouts(); load(); update_offsets( get_current_layout().panels().begin()->get_width() ); } @@ -3152,7 +3303,7 @@ void panel_manager::show_adm() ctxt.register_action( "MOVE_PANEL" ); ctxt.register_action( "TOGGLE_PANEL" ); - const std::vector<int> column_widths = { 17, 37, 17 }; + const std::vector<int> column_widths = { 25, 37, 17 }; size_t current_col = 0; size_t current_row = 0; @@ -3174,7 +3325,7 @@ void panel_manager::show_adm() const int popup_height = 24; ui_adaptor ui; ui.on_screen_resize( [&]( ui_adaptor & ui ) { - w = catacurses::newwin( popup_height, 75, + w = catacurses::newwin( popup_height, 83, point( ( TERMX / 2 ) - 38, ( TERMY / 2 ) - 10 ) ); ui.position_from_window( w ); diff --git a/src/panels.h b/src/panels.h index 0847fbd557b20..c7d8f6845d97d 100644 --- a/src/panels.h +++ b/src/panels.h @@ -12,6 +12,7 @@ #include "color.h" #include "coordinates.h" #include "translations.h" +#include "widget.h" class JsonIn; class JsonOut; @@ -116,7 +117,7 @@ std::pair<std::string, nc_color> rad_badge_text_color( const Character &u ); std::string weight_string( const Character &u ); // Prints a list of nearby monsters -void print_mon_info( avatar &u, const catacurses::window &, int hor_padding = 0, +void print_mon_info( const avatar &u, const catacurses::window &, int hor_padding = 0, bool compact = false ); } // namespace display @@ -129,30 +130,57 @@ void draw_overmap_chunk( const catacurses::window &w_minimap, const avatar &you, bool default_render(); +// Arguments to pass into the static draw function (in window_panel::draw) +// Includes public avatar (_ava) and window (_win) references, and private widget reference +// passed to the constructor, accessible with get_widget(). +struct draw_args { + public: + const avatar &_ava; + const catacurses::window &_win; + + draw_args( const avatar &a, const catacurses::window &w, const widget_id &wgt ) : + _ava( a ), _win( w ), _wgt( wgt ) {} + + widget *get_widget() const { + return _wgt.is_null() ? nullptr : const_cast<widget *>( &*_wgt ); + } + private: + widget_id _wgt; +}; + +// A window_panel is a rectangular region or drawable area within the sidebar window. +// It corresponds to a section that the player may toggle or rearrange from the in-game sidebar options. +// It is associated with a draw function (taking draw_args with avatar and window), along with id and name. +// The height, width, and default toggle state must be provided to the constructor as well. class window_panel { public: - window_panel( const std::function<void( avatar &, const catacurses::window & )> &draw_func, + window_panel( const std::function<void( const draw_args & )> &draw_func, const std::string &id, const translation &nm, int ht, int wd, bool default_toggle_, const std::function<bool()> &render_func = default_render, bool force_draw = false ); - std::function<void( avatar &, const catacurses::window & )> draw; + std::function<void( const draw_args & )> draw; std::function<bool()> render; int get_height() const; int get_width() const; const std::string &get_id() const; std::string get_name() const; + void set_widget( const widget_id &w ); + const widget_id &get_widget() const; bool toggle; bool always_draw; private: int height; int width; + widget_id wgt; std::string id; translation name; }; +// A panel_layout is a collection of window_panels drawn in order from top to bottom. +// It is associated with the user-selectable layouts named "classic", "compact", "labels", etc. class panel_layout { public: @@ -167,6 +195,9 @@ class panel_layout std::vector<window_panel> _panels; }; +// The panel_manager allows the player choose their current panel layout and window panels. +// The player's selected panel_layout, enabled window_panels and what order they appear in, +// are saved to the PATH_INFO::panel_options() file, typically config/panel_options.json. class panel_manager { public: @@ -205,6 +236,7 @@ class panel_manager std::string current_layout_id; std::map<std::string, panel_layout> layouts; + friend widget; }; #endif // CATA_SRC_PANELS_H diff --git a/src/string_id_null_ids.cpp b/src/string_id_null_ids.cpp index 3c9486c521d9d..126a4a3b08d6a 100644 --- a/src/string_id_null_ids.cpp +++ b/src/string_id_null_ids.cpp @@ -21,7 +21,7 @@ MAKE_NULL_ID( harvest_list, "null" ) MAKE_NULL_ID( Item_spawn_data, "null" ) MAKE_NULL_ID( effect_type, "null" ) MAKE_NULL_ID( material_type, "null" ) - +MAKE_NULL_ID( widget, "null" ) MAKE_NULL_ID( monfaction, "" ) MAKE_NULL_ID( nested_mapgen, "null" ) MAKE_NULL_ID( overmap_land_use_code, "" ) diff --git a/src/widget.cpp b/src/widget.cpp index 537d5e23a08bb..ddef3a1be28bc 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -36,6 +36,11 @@ void widget::reset() widget_factory.reset(); } +const std::vector<widget> &widget::get_all() +{ + return widget_factory.get_all(); +} + // Convert widget "var" enums to string equivalents namespace io { @@ -183,6 +188,11 @@ void widget::load( const JsonObject &jo, const std::string & ) } } +void widget::finalize() +{ + // Nothing to do? +} + int widget::get_var_max( const avatar &ava ) { // Some vars (like HP) have an inherent maximum, used unless the widget overrides it @@ -329,6 +339,70 @@ std::string widget::show( const avatar &ava ) } } +// Drawing function, provided as a callback to the window_panel constructor. +// Handles rendering a widget's content into a window panel. +static void custom_draw_func( const draw_args &args ) +{ + const avatar &u = args._ava; + const catacurses::window &w = args._win; + widget *wgt = args.get_widget(); + + // Get full window width + const int width = catacurses::getmaxx( w ); + // Leave 1 character space for margin on left and right + const int margin = 1; + const int widt = width - 2 * margin; + + // Quit if there is nothing to draw or no space to draw it + if( wgt == nullptr || width <= 0 ) { + return; + } + + werase( w ); + if( wgt->_style == "sidebar" ) { + } else if( wgt->_style == "layout" ) { + if( wgt->_arrange == "rows" ) { + // Layout widgets in rows + // FIXME: Be able to handle rows that are themselves more than one line! + // Could this be done in the layout() function somehow (by returning newlines?) + int row_num = 0; + for( const widget_id &row_wid : wgt->_widgets ) { + widget row_widget = row_wid.obj(); + trim_and_print( w, point( margin, row_num ), widt, c_light_gray, row_widget.layout( u, + widt ) ); + row_num++; + } + } else { + // Layout widgets in columns + // For now, this is the default when calling layout() + // So, just layout self on a single line + trim_and_print( w, point( margin, 0 ), widt, c_light_gray, _( wgt->layout( u, widt ) ) ); + } + } else { + // No layout, just a widget - simply layout self on a single line + trim_and_print( w, point( margin, 0 ), widt, c_light_gray, _( wgt->layout( u, widt ) ) ); + } + wnoutrefresh( w ); +} + +window_panel widget::get_window_panel( const int width, const int req_height ) +{ + // Width is fixed, but height may vary depending on child widgets + int height = req_height; + + // For layout with rows, height will be number of rows + // (assuming each row is only 1 line) + if( _style == "layout" && _arrange == "rows" ) { + height = _widgets.size(); + } + // Minimap and log do not have a predetermined height + // (or they should allow caller to customize height) + + window_panel win( custom_draw_func, _label.translated(), _label, height, width, true ); + win.set_widget( this->id ); + return win; +} + bool widget::uses_text_function() { switch( _var ) { @@ -575,9 +649,22 @@ std::string widget::layout( const avatar &ava, const unsigned int max_width ) { std::string ret; if( _style == "layout" ) { - // Divide max_width equally among all widgets - int child_width = max_width / _widgets.size(); - int remainder = max_width % _widgets.size(); + // Widgets with "rows" arrangement must be laid out from window_panel + if( _arrange == "rows" ) { + debugmsg( "widget layout called with rows" ); + } + const int num_widgets = _widgets.size(); + if( num_widgets == 0 ) { + debugmsg( "widget layout has no widgets" ); + } + // Number of spaces between columns + const int col_padding = 2; + // Subtract column padding to get space available for widgets + const int avail_width = max_width - col_padding * ( num_widgets - 1 ); + // Divide available width equally among all widgets + const int child_width = avail_width / num_widgets; + // Keep remainder to distribute + int remainder = avail_width % num_widgets; for( const widget_id &wid : _widgets ) { widget cur_child = wid.obj(); int cur_width = child_width; @@ -586,11 +673,11 @@ std::string widget::layout( const avatar &ava, const unsigned int max_width ) cur_width += 1; remainder -= 1; } - // Allow 2 spaces of padding after each column, except last column (full-justified) + // Layout child in this column + ret += string_format( "%s", cur_child.layout( ava, cur_width ) ); + // Add column padding until we reach the last column if( wid != _widgets.back() ) { - ret += string_format( "%s ", cur_child.layout( ava, cur_width - 2 ) ); - } else { - ret += string_format( "%s", cur_child.layout( ava, cur_width ) ); + ret += std::string( col_padding, ' ' ); } } } else { diff --git a/src/widget.h b/src/widget.h index 57e097ec427d8..5c8fe7f6fdb08 100644 --- a/src/widget.h +++ b/src/widget.h @@ -9,6 +9,7 @@ //#include "cata_variant.h" #include "enum_traits.h" #include "generic_factory.h" +#include "panels.h" #include "string_id.h" #include "translations.h" #include "type_id.h" @@ -79,12 +80,16 @@ class JsonObject; template<typename T> class generic_factory; +// Forward declaration, due to codependency on panels.h +class window_panel; + // A widget is a UI element displaying information from the underlying value of a widget_var. // It may be loaded from a JSON object having "type": "widget". class widget { private: friend class generic_factory<widget>; + friend void custom_draw_fn( avatar &u, const catacurses::window &w, const widget &wgt ); widget_id id; bool was_loaded = false; @@ -125,8 +130,12 @@ class widget // Load JSON data for a widget (uses generic factory widget_factory) static void load_widget( const JsonObject &jo, const std::string &src ); void load( const JsonObject &jo, const std::string &src ); + // Finalize anything that must wait until all widgets are loaded + static void finalize(); // Reset to defaults using generic widget_factory static void reset(); + // Get all widget instances from the factory + static const std::vector<widget> &get_all(); // Layout this widget within max_width, including child widgets. Calling layout on a regular // (non-layout style) widget is the same as show(), but will pad with spaces inside the @@ -134,6 +143,8 @@ class widget std::string layout( const avatar &ava, unsigned int max_width = 0 ); // Display labeled widget, with value (number, graph, or string) from an avatar std::string show( const avatar &ava ); + // Return a window_panel for rendering this widget at given width (and possibly height) + window_panel get_window_panel( const int width, const int req_height = 1 ); // Return a colorized string for a _var associated with a description function std::string color_text_function_string( const avatar &ava ); // Return true if the current _var is one which uses a description function diff --git a/tests/widget_test.cpp b/tests/widget_test.cpp index 2e15749826767..0044ebda34bc7 100644 --- a/tests/widget_test.cpp +++ b/tests/widget_test.cpp @@ -7,6 +7,9 @@ static const itype_id itype_rad_badge( "rad_badge" ); // test widgets defined in data/json/sidebar.json and data/mods/TEST_DATA/widgets.json +static const widget_id widget_test_2_column_layout( "test_2_column_layout" ); +static const widget_id widget_test_3_column_layout( "test_3_column_layout" ); +static const widget_id widget_test_4_column_layout( "test_4_column_layout" ); static const widget_id widget_test_bp_wetness_head_num( "test_bp_wetness_head_num" ); static const widget_id widget_test_bp_wetness_torso_num( "test_bp_wetness_torso_num" ); static const widget_id widget_test_bucket_graph( "test_bucket_graph" ); @@ -393,20 +396,120 @@ TEST_CASE( "radiation badge widget", "[widget][radiation]" ) CHECK( rads_w.layout( ava ) == "RADIATION: <color_c_pink> black </color>" ); } -TEST_CASE( "layout widgets", "[widget][layout]" ) +// Widgets with "layout" style can combine other widgets in columns or rows. +// +// Using "arrange": "columns", width is divided as equally as possible among widgets. +// With C columns, (C-1)*2 characters are allotted for space between columns (__): +// +// C=2: FIRST__SECOND +// C=3: FIRST__SECOND__THIRD +// C=4: FIRST__SECOND__THIRD__FOURTH +// +// So total width available to each column is: +// +// (W - (C-1)*2) / C +// +// At 24 width, 2 columns, each column gets (24 - (2-1)*2) / 2 == 11 characters. +// At 36 width, 2 columns, each column gets (36 - (2-1)*2) / 2 == 17 characters. +// At 36 width, 3 columns, each column gets (36 - (3-1)*2) / 3 == 10 characters. +// +// This test case calls layout() at different widths for 2-, 3-, and 4-column layouts, +// to verify and demonstrate how the space is distributed among widgets in columns. +// +TEST_CASE( "layout widgets in columns", "[widget][layout][columns]" ) { - widget stats_w = widget_test_stat_panel.obj(); + widget stat_w = widget_test_stat_panel.obj(); + widget two_w = widget_test_2_column_layout.obj(); + widget three_w = widget_test_3_column_layout.obj(); + widget four_w = widget_test_4_column_layout.obj(); avatar &ava = get_avatar(); clear_avatar(); - CHECK( stats_w.layout( ava, 32 ) == - string_format( "STR: 8 DEX: 8 INT: 8 PER: 8" ) ); - CHECK( stats_w.layout( ava, 38 ) == - string_format( "STR: 8 DEX: 8 INT: 8 PER: 8" ) ); - CHECK( stats_w.layout( ava, 40 ) == - string_format( "STR: 8 DEX: 8 INT: 8 PER: 8" ) ); - CHECK( stats_w.layout( ava, 42 ) == - string_format( "STR: 8 DEX: 8 INT: 8 PER: 8" ) ); + ava.str_max = 8; + ava.dex_max = 8; + ava.int_max = 8; + ava.per_max = 8; + ava.movecounter = 50; + ava.set_focus( 120 ); + ava.set_speed_base( 100 ); + ava.magic->set_mana( 1000 ); + + // Two columns + // string ruler: 123456789012345678901234567890123456 + CHECK( two_w.layout( ava, 24 ) == "MOVE: 50 SPEED: 100" ); + CHECK( two_w.layout( ava, 25 ) == "MOVE: 50 SPEED: 100" ); + CHECK( two_w.layout( ava, 26 ) == "MOVE: 50 SPEED: 100" ); + CHECK( two_w.layout( ava, 27 ) == "MOVE: 50 SPEED: 100" ); + CHECK( two_w.layout( ava, 28 ) == "MOVE: 50 SPEED: 100" ); + CHECK( two_w.layout( ava, 29 ) == "MOVE: 50 SPEED: 100" ); + CHECK( two_w.layout( ava, 30 ) == "MOVE: 50 SPEED: 100" ); + CHECK( two_w.layout( ava, 31 ) == "MOVE: 50 SPEED: 100" ); + CHECK( two_w.layout( ava, 32 ) == "MOVE: 50 SPEED: 100" ); + CHECK( two_w.layout( ava, 33 ) == "MOVE: 50 SPEED: 100" ); + CHECK( two_w.layout( ava, 34 ) == "MOVE: 50 SPEED: 100" ); + CHECK( two_w.layout( ava, 35 ) == "MOVE: 50 SPEED: 100" ); + CHECK( two_w.layout( ava, 36 ) == "MOVE: 50 SPEED: 100" ); + // string ruler: 123456789012345678901234567890123456 + + // Three columns + // string ruler: 1234567890123456789012345678901234567890 + CHECK( three_w.layout( ava, 36 ) == "MOVE: 50 SPEED: 100 FOCUS: 120" ); + CHECK( three_w.layout( ava, 37 ) == "MOVE: 50 SPEED: 100 FOCUS: 120" ); + CHECK( three_w.layout( ava, 38 ) == "MOVE: 50 SPEED: 100 FOCUS: 120" ); + CHECK( three_w.layout( ava, 39 ) == "MOVE: 50 SPEED: 100 FOCUS: 120" ); + CHECK( three_w.layout( ava, 40 ) == "MOVE: 50 SPEED: 100 FOCUS: 120" ); + CHECK( three_w.layout( ava, 41 ) == "MOVE: 50 SPEED: 100 FOCUS: 120" ); + CHECK( three_w.layout( ava, 42 ) == "MOVE: 50 SPEED: 100 FOCUS: 120" ); + CHECK( three_w.layout( ava, 43 ) == "MOVE: 50 SPEED: 100 FOCUS: 120" ); + CHECK( three_w.layout( ava, 44 ) == "MOVE: 50 SPEED: 100 FOCUS: 120" ); + CHECK( three_w.layout( ava, 45 ) == "MOVE: 50 SPEED: 100 FOCUS: 120" ); + CHECK( three_w.layout( ava, 46 ) == "MOVE: 50 SPEED: 100 FOCUS: 120" ); + // string ruler: 1234567890123456789012345678901234567890123456 + + // Four columns + // string ruler: 123456789012345678901234567890123456789012 + CHECK( stat_w.layout( ava, 32 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( stat_w.layout( ava, 33 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( stat_w.layout( ava, 34 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( stat_w.layout( ava, 35 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( stat_w.layout( ava, 36 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( stat_w.layout( ava, 37 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( stat_w.layout( ava, 38 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( stat_w.layout( ava, 39 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( stat_w.layout( ava, 40 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( stat_w.layout( ava, 41 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( stat_w.layout( ava, 42 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( stat_w.layout( ava, 43 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( stat_w.layout( ava, 44 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( stat_w.layout( ava, 45 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( stat_w.layout( ava, 46 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + // string ruler: 1234567890123456789012345678901234567890123456 + + // Column alignment + // Layout keeps labels vertically aligned for layouts with the same number of widgets + // string ruler: 123456789012345678901234567890123456789012345678 + CHECK( stat_w.layout( ava, 48 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( four_w.layout( ava, 48 ) == "MOVE: 50 SPEED: 100 FOCUS: 120 MANA: 1000" ); + + // string ruler: 1234567890123456789012345678901234567890123456789012 + CHECK( stat_w.layout( ava, 52 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( four_w.layout( ava, 52 ) == "MOVE: 50 SPEED: 100 FOCUS: 120 MANA: 1000" ); + + // string ruler: 12345678901234567890123456789012345678901234567890123456 + CHECK( stat_w.layout( ava, 56 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( four_w.layout( ava, 56 ) == "MOVE: 50 SPEED: 100 FOCUS: 120 MANA: 1000" ); + + // string ruler: 123456789012345678901234567890123456789012345678901234567890 + CHECK( stat_w.layout( ava, 60 ) == "STR: 8 DEX: 8 INT: 8 PER: 8" ); + CHECK( four_w.layout( ava, 60 ) == "MOVE: 50 SPEED: 100 FOCUS: 120 MANA: 1000" ); + + // TODO: Consider re-distributing space so values are closer to labels, like this: + // 48 width + // "STR: 8 DEX: 8 INT: 8 PER: 8 " + // "MOVE: 0 SPEED: 100 FOCUS: 100 MANA: 1000 " + // 60 width + // "STR: 8 DEX: 8 INT: 8 PER: 8 " + // "MOVE: 0 SPEED: 100 FOCUS: 100 MANA: 1000 " }