diff --git a/src/popup.cpp b/src/popup.cpp index b05ed4ff36717..64e55affb9760 100644 --- a/src/popup.cpp +++ b/src/popup.cpp @@ -4,11 +4,14 @@ #include #include +#include "options.h" #include "cached_options.h" #include "catacharset.h" #include "input_context.h" #include "output.h" #include "ui_manager.h" +#include "ui.h" +#if !defined(__ANDROID__) #include "cata_imgui.h" #include "imgui/imgui.h" @@ -131,6 +134,7 @@ void query_popup_impl::on_resized() } } } +#endif query_popup::query_popup() : cur( 0 ), default_text_color( c_white ), anykey( false ), cancel( false ), @@ -258,12 +262,254 @@ std::vector> query_popup::fold_query( void query_popup::invalidate_ui() const { - std::shared_ptr ui = p_impl.lock(); + std::shared_ptr legacy_ui = adaptor.lock(); + if( legacy_ui ) { + if( win ) { + win = {}; + folded_msg.clear(); + buttons.clear(); + } + legacy_ui->mark_resize(); + } +#if !defined(__ANDROID__) + std::shared_ptr imgui_ui = p_impl.lock(); + if( imgui_ui ) { + imgui_ui->mark_resized(); + } +#endif +} + +static constexpr int border_width = 1; + +void query_popup::init() const +{ + constexpr int horz_padding = 2; + constexpr int vert_padding = 1; + const int max_line_width = FULL_SCREEN_WIDTH - border_width * 2; + + // Fold message text + folded_msg = foldstring( text, max_line_width ); + + // Fold query buttons + const auto &folded_query = fold_query( category, pref_kbd_mode, options, max_line_width, + horz_padding ); + + // Calculate size of message part + int msg_width = 0; + int msg_height = folded_msg.size(); + + for( const auto &line : folded_msg ) { + msg_width = std::max( msg_width, utf8_width( line, true ) ); + } + + // Calculate width with query buttons + for( const auto &line : folded_query ) { + if( !line.empty() ) { + int button_width = 0; + for( const auto &opt : line ) { + button_width += utf8_width( opt, true ); + } + msg_width = std::max( msg_width, button_width + + horz_padding * static_cast( line.size() - 1 ) ); + } + } + msg_width = std::min( msg_width, max_line_width ); + + // Calculate height with query buttons & button positions + buttons.clear(); + if( !folded_query.empty() ) { + msg_height += vert_padding; + for( const auto &line : folded_query ) { + if( !line.empty() ) { + int button_width = 0; + for( const auto &opt : line ) { + button_width += utf8_width( opt, true ); + } + // Right align. + // TODO: multi-line buttons + int button_x = std::max( 0, msg_width - button_width - + horz_padding * static_cast( line.size() - 1 ) ); + for( const auto &opt : line ) { + buttons.emplace_back( opt, point( button_x, msg_height ) ); + button_x += utf8_width( opt, true ) + horz_padding; + } + msg_height += 1 + vert_padding; + } + } + msg_height -= vert_padding; + } + + // Calculate window size + const int win_width = std::min( TERMX, + fullscr ? FULL_SCREEN_WIDTH : msg_width + border_width * 2 ); + const int win_height = std::min( TERMY, + fullscr ? FULL_SCREEN_HEIGHT : msg_height + border_width * 2 ); + const point win_pos( ( TERMX - win_width ) / 2, ontop ? 0 : ( TERMY - win_height ) / 2 ); + win = catacurses::newwin( win_height, win_width, win_pos ); + + std::shared_ptr ui = adaptor.lock(); if( ui ) { - ui->mark_resized(); + ui->position_from_window( win ); } } +void query_popup::show() const +{ + if( !win ) { + init(); + } + + werase( win ); + draw_border( win ); + + for( size_t line = 0; line < folded_msg.size(); ++line ) { + nc_color col = default_text_color; + print_colored_text( win, point( border_width, border_width + line ), col, col, + folded_msg[line] ); + } + + for( size_t ind = 0; ind < buttons.size(); ++ind ) { + const query_popup::button &btn = buttons[ind]; + nc_color col = c_white; + std::string text = colorize( btn.text, col ); + if( ind == cur ) { + text = hilite_string( text ); + } + print_colored_text( win, btn.pos + point( border_width, border_width ), + col, col, text ); + } + + wnoutrefresh( win ); +} + +std::shared_ptr query_popup::create_or_get_adaptor() +{ + std::shared_ptr ui = adaptor.lock(); + if( !ui ) { + adaptor = ui = std::make_shared(); + ui->on_redraw( [this]( const ui_adaptor & ) { + show(); + } ); + ui->on_screen_resize( [this]( ui_adaptor & ) { + init(); + } ); + ui->mark_resize(); + } + return ui; +} + +query_popup::result query_popup::query_once_legacy() +{ + if( !anykey && !cancel && options.empty() ) { + return { false, "ERROR", {} }; + } + + if( test_mode ) { + return { false, "ERROR", {} }; + } + + std::shared_ptr ui = create_or_get_adaptor(); + + ui_manager::redraw(); + + input_context ctxt( category, pref_kbd_mode ); + if( cancel || !options.empty() ) { + ctxt.register_action( "HELP_KEYBINDINGS" ); + } + if( !options.empty() ) { + ctxt.register_leftright(); + ctxt.register_action( "CONFIRM" ); + for( const query_popup::query_option &opt : options ) { + ctxt.register_action( opt.action ); + } + // Mouse movement and button + ctxt.register_action( "SELECT" ); + ctxt.register_action( "MOUSE_MOVE" ); + } + if( anykey ) { + ctxt.register_action( "ANY_INPUT" ); + // Mouse movement, button, and wheel + ctxt.register_action( "COORDINATE" ); + } + if( cancel ) { + ctxt.register_action( "QUIT" ); + } + + result res; + // Assign outside construction of `res` to ensure execution order + res.wait_input = !anykey; + do { + res.action = ctxt.handle_input(); + res.evt = ctxt.get_raw_input(); + + // If we're tracking mouse movement + if( !options.empty() && ( res.action == "MOUSE_MOVE" || res.action == "SELECT" ) ) { + std::optional coord = ctxt.get_coordinates_text( win ); + for( size_t i = 0; i < buttons.size(); i++ ) { + if( coord.has_value() && buttons[i].contains( coord.value() ) ) { + if( i != cur ) { + // Mouse-over new button, switch selection + cur = i; + ui_manager::redraw(); + } + if( res.action == "SELECT" ) { + // Left-click to confirm selection + res.action = "CONFIRM"; + } + } + } + } + } while( + // Always ignore mouse movement + ( res.evt.type == input_event_t::mouse && + res.evt.get_first_input() == static_cast( MouseInput::Move ) ) || + // Ignore window losing focus in SDL + ( res.evt.type == input_event_t::keyboard_char && res.evt.sequence.empty() ) + ); + + if( cancel && res.action == "QUIT" ) { + res.wait_input = false; + } else if( res.action == "LEFT" || res.action == "RIGHT" ) { + cur = inc_clamp_wrap( cur, res.action == "RIGHT", options.size() ); + } else if( res.action == "CONFIRM" ) { + if( cur < options.size() ) { + res.wait_input = false; + res.action = options[cur].action; + } + } else if( res.action == "HELP_KEYBINDINGS" ) { + // Keybindings may have changed, regenerate the UI + init(); + } else { + for( size_t ind = 0; ind < options.size(); ++ind ) { + if( res.action == options[ind].action ) { + cur = ind; + if( options[ind].filter( res.evt ) ) { + res.wait_input = false; + break; + } + } + } + } + + return res; +} + +query_popup::result query_popup::query_once() +{ +#if defined(__ANDROID__) + return query_once_legacy(); +#else + options_manager &opts = get_options(); + options_manager::cOpt &use_imgui_opt = opts.get_option( "USE_IMGUI" ); + if( get_options().has_option( "USE_IMGUI" ) && get_option( "USE_IMGUI" ) ) { + return query_once_imgui(); + } else { + return query_once_legacy(); + } +#endif +} + +#if !defined(__ANDROID__) std::shared_ptr query_popup::create_or_get_impl() { std::shared_ptr impl = p_impl.lock(); @@ -275,8 +521,9 @@ std::shared_ptr query_popup::create_or_get_impl() } return impl; } +#endif -query_popup::result query_popup::query_once() +query_popup::result query_popup::query_once_imgui() { if( !anykey && !cancel && options.empty() ) { return { false, "ERROR", {} }; @@ -370,13 +617,41 @@ query_popup::result query_popup::query_once() return res; } + query_popup::result query_popup::query() +{ +#if defined(__ANDROID__) + return query_legacy(); +#else + options_manager &opts = get_options(); + options_manager::cOpt &use_imgui_opt = opts.get_option( "USE_IMGUI" ); + if( get_options().has_option( "USE_IMGUI" ) && get_option( "USE_IMGUI" ) ) { + return query_imgui(); + } else { + return query_legacy(); + } +#endif +} + +query_popup::result query_popup::query_imgui() { std::shared_ptr ui = create_or_get_impl(); result res; do { - res = query_once(); + res = query_once_imgui(); + } while( res.wait_input ); + return res; +} + + +query_popup::result query_popup::query_legacy() +{ + std::shared_ptr ui = create_or_get_adaptor(); + + result res; + do { + res = query_once_legacy(); } while( res.wait_input ); return res; } @@ -426,6 +701,16 @@ bool query_popup::button::contains( const point &p ) const static_popup::static_popup() { - ui = create_or_get_impl(); - ui_manager::redraw(); +#if defined(__ANDROID__) + ui = create_or_get_adaptor(); +#else + options_manager &opts = get_options(); + options_manager::cOpt &use_imgui_opt = opts.get_option( "USE_IMGUI" ); + if( get_options().has_option( "USE_IMGUI" ) && get_option( "USE_IMGUI" ) ) { + ui_imgui = create_or_get_impl(); + ui_manager::redraw(); + } else { + ui = create_or_get_adaptor(); + } +#endif } diff --git a/src/popup.h b/src/popup.h index 59a7c548c1296..dfda725911d60 100644 --- a/src/popup.h +++ b/src/popup.h @@ -13,7 +13,9 @@ #include "input_enums.h" class ui_adaptor; +#if !defined(__ANDROID__) class query_popup_impl; +#endif /** * UI class for displaying messages or querying player input with popups. @@ -35,7 +37,9 @@ class query_popup_impl; class query_popup { +#if !defined(__ANDROID__) friend class query_popup_impl; +#endif public: /** * Query result returned by `query_once` and `query`. @@ -179,6 +183,12 @@ class query_popup */ query_popup &preferred_keyboard_mode( keyboard_mode mode ); + /** + * Draw the UI. An input context should be provided using `context()` + * for this function to properly generate option text. + **/ + void show() const; + /** * Query once and return the result. In order for this method to return * valid results, the popup must either have at least one option, or @@ -195,8 +205,16 @@ class query_popup * Create or get a ui_adaptor on the UI stack to handle redrawing and * resizing of the popup. */ + std::shared_ptr create_or_get_adaptor(); +#if !defined(__ANDROID__) std::shared_ptr create_or_get_impl(); + result query_imgui(); + result query_once_imgui(); +#endif + result query_legacy(); + result query_once_legacy(); + private: struct query_option { query_option( const std::string &action, @@ -226,9 +244,11 @@ class query_popup int width; }; + std::weak_ptr adaptor; std::weak_ptr p_impl; // UI caches + mutable catacurses::window win; mutable std::vector folded_msg; mutable std::vector