Skip to content

Commit

Permalink
add classes for string and number input popups with a common base class
Browse files Browse the repository at this point in the history
  • Loading branch information
mqrause committed Nov 14, 2024
1 parent f89f500 commit d416005
Show file tree
Hide file tree
Showing 2 changed files with 445 additions and 0 deletions.
333 changes: 333 additions & 0 deletions src/input_popup.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,333 @@
#include "input_popup.h"

#include "cata_utility.h"
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
#include "ui.h"
#include "uistate.h"
#include "ui_manager.h"

input_popup::input_popup( const std::string &title, int width, ImGuiWindowFlags flags ) :
cataimgui::window( title, ImGuiWindowFlags_NoNavInputs | flags | ( title.empty() ?
ImGuiWindowFlags_NoTitleBar : 0 ) | ImGuiWindowFlags_AlwaysAutoResize ),
width( width )
{
ctxt = input_context( "STRING_INPUT", keyboard_mode::keychar );
ctxt.register_action( "TEXT.CONFIRM" );
ctxt.register_action( "TEXT.QUIT" );
ctxt.set_timeout( 10 );
}

void input_popup::set_description( const std::string &desc, const nc_color &default_color,
bool monofont )
{
description = desc;
description_default_color = default_color;
description_monofont = monofont;
}

void input_popup::set_label( const std::string &label, const nc_color &default_color )
{
this->label = label;
label_default_color = default_color;
}

void input_popup::set_max_input_length( int length )
{
// add 1 for \0, make sure it's smaller than the size of the text array
max_input_length = clamp( length + 1, 2, 255 );
}

static void register_input( input_context &ctxt, const callback_input &input )
{
if( input.description ) {
ctxt.register_action( input.action, *input.description );
} else {
ctxt.register_action( input.action );
}
}

void input_popup::add_callback( const callback_input &input,
const std::function<bool()> &callback_func )
{
if( !input.action.empty() ) {
register_input( ctxt, input );
}
callbacks.emplace_back( input, callback_func );
}

bool input_popup::cancelled()
{
return is_cancelled;
}

void input_popup::draw_controls()
{
if( !description.empty() ) {
if( description_monofont ) {
cataimgui::PushMonoFont();
}
// todo: you should not have to manually split text to print it as paragraphs
uint64_t offset = 0;
uint64_t pos = 0;
while( pos != std::string::npos ) {
pos = description.find( "\n", offset );
std::string str = description.substr( offset, pos - offset );
cataimgui::TextColoredParagraph( description_default_color, str, std::nullopt,
str_width_to_pixels( width ) );
ImGui::NewLine();
offset = pos + 1;
}
if( description_monofont ) {
ImGui::PopFont();
}
}

if( !label.empty() ) {
ImGui::AlignTextToFramePadding();
cataimgui::draw_colored_text( label, c_light_red );
ImGui::SameLine();
}

// make sure the cursor is in the input field when we regain focus
if( !ImGui::IsWindowFocused() ) {
set_focus = true;
}

if( set_focus && ImGui::IsWindowFocused() ) {
ImGui::SetKeyboardFocusHere();
set_focus = false;
}
draw_input_control();
}

bool input_popup::handle_custom_callbacks( const std::string &action )
{

input_event ev = ctxt.get_raw_input();
int key = ev.get_first_input();

bool next_loop = false;
for( const auto &cb : callbacks ) {
if( cb.first == callback_input( action, key ) && cb.second() ) {
next_loop = true;
break;
}
}

return next_loop;
}

string_input_popup_imgui::string_input_popup_imgui( const std::string &title, int width,
const std::string &old_input, ImGuiWindowFlags flags ) :
input_popup( title, width ),
old_input( old_input )
{
ctxt.register_action( "TEXT.UP" );
ctxt.register_action( "TEXT.DOWN" );

set_text( old_input );
}

void string_input_popup_imgui::draw_input_control()
{
ImGui::InputText( "##string_input", text.data(), max_input_length );
}

void string_input_popup_imgui::set_identifier( const std::string &ident )
{
identifier = ident;
}

void string_input_popup_imgui::set_text( const std::string &txt )
{
snprintf( text.data(), text.size(), "%s", txt.c_str() );
}

void string_input_popup_imgui::show_history()
{
if( identifier.empty() ) {
return;
}
std::vector<std::string> &hist = uistate.gethistory( identifier );
std::string txt( text.data() );
uilist hmenu;
hmenu.title = _( "d: delete history" );
hmenu.allow_anykey = true;
for( size_t h = 0; h < hist.size(); h++ ) {
hmenu.addentry( h, true, -2, hist[h] );
}
if( !text.empty() && ( hmenu.entries.empty() ||
hmenu.entries[hist.size() - 1].txt != txt ) ) {
hmenu.addentry( hist.size(), true, -2, txt );
}

if( !hmenu.entries.empty() ) {
hmenu.selected = hmenu.entries.size() - 1;

bool finished = false;
do {
hmenu.query();
if( hmenu.ret >= 0 && hmenu.entries[hmenu.ret].txt != txt ) {
set_text( hmenu.entries[hmenu.ret].txt );
if( static_cast<size_t>( hmenu.ret ) < hist.size() ) {
hist.erase( hist.begin() + hmenu.ret );
add_to_history( txt );
}
finished = true;
} else if( hmenu.ret == UILIST_UNBOUND && hmenu.ret_evt.get_first_input() == 'd' ) {
hist.clear();
finished = true;
} else if( hmenu.ret != UILIST_UNBOUND ) {
finished = true;
}
} while( !finished );
}
}

void string_input_popup_imgui::update_input_history( bool up )
{
if( identifier.empty() ) {
return;
}

std::vector<std::string> &hist = uistate.gethistory( identifier );

if( hist.empty() ) {
return;
}

if( hist.size() >= max_history_size ) {
hist.erase( hist.begin(), hist.begin() + ( hist.size() - max_history_size ) );
}

if( up ) {
if( history_index >= static_cast<int>( hist.size() ) ) {
return;
} else if( history_index == 0 ) {
session_input = text.data();

//avoid showing the same result twice (after reopen filter window without reset)
if( hist.size() > 1 && session_input == hist.back() ) {
history_index += 1;
}
}
} else {
if( history_index == 1 ) {
set_text( session_input );
//show initial string entered and 'return'
history_index = 0;
return;
} else if( history_index == 0 ) {
return;
}
}

history_index += up ? 1 : -1;
set_text( hist[hist.size() - history_index] );
}

void string_input_popup_imgui::add_to_history( const std::string &value ) const
{
if( !identifier.empty() && !value.empty() ) {
std::vector<std::string> &hist = uistate.gethistory( identifier );
if( hist.empty() || hist[hist.size() - 1] != value ) {
hist.push_back( value );
}
}
}

std::string string_input_popup_imgui::query()
{
is_cancelled = false;

while( true ) {
ui_manager::redraw_invalidated();

std::string action = ctxt.handle_input();
if( handle_custom_callbacks( action ) ) {
continue;
}

if( action == "TEXT.CONFIRM" ) {
std::string txt( text.data() );
add_to_history( txt );
return txt;
} else if( action == "TEXT.QUIT" ) {
break;
} else if( action == "TEXT.UP" ) {
if( is_uilist_history ) {
show_history();
} else {
update_input_history( true );
}
} else if( action == "TEXT.DOWN" && !is_uilist_history ) {
update_input_history( false );
}

// mouse click on x to close leads here
if( !get_is_open() ) {
break;
}
}

is_cancelled = true;
return old_input;
}

void string_input_popup_imgui::use_uilist_history( bool use_uilist )
{
is_uilist_history = use_uilist;
}

template<typename T>
number_input_popup<T>::number_input_popup( const std::string &title, int width, T old_value,
ImGuiWindowFlags flags ) :
input_popup( title, width ),
old_value( old_value ),
value( old_value )
{
// potentially register context keys
}

template<>
void number_input_popup<int>::draw_input_control()
{
ImGui::InputInt( "##number_input", &value );
}

template<>
void number_input_popup<float>::draw_input_control()
{
cataimgui::InputFloat( "##number_input", &value );
}

template<typename T>
T number_input_popup<T>::query()
{

while( true ) {
ui_manager::redraw_invalidated();
std::string action = ctxt.handle_input();

if( handle_custom_callbacks( action ) ) {
continue;
}

if( action == "TEXT.CONFIRM" ) {
return value;
} else if( action == "TEXT.QUIT" ) {
break;
}

// mouse click on x to close leads here
if( !get_is_open() ) {
break;
}
}

return old_value;
}

template class number_input_popup<int>;
template class number_input_popup<float>;
Loading

0 comments on commit d416005

Please sign in to comment.