Added input-history feature to TextInputForm. Control via up/down arrows.
This commit is contained in:
@@ -27,18 +27,32 @@ namespace JUI {
|
||||
|
||||
class TextInputForm : public TextRect, public Clickable {
|
||||
public:
|
||||
#pragma region Events
|
||||
Event<> OnSelect;
|
||||
Event<> OnDeselect;
|
||||
Event<std::string> OnReturn;
|
||||
#pragma endregion
|
||||
public:
|
||||
#pragma region Constructors
|
||||
TextInputForm();
|
||||
explicit TextInputForm(Widget* parent);
|
||||
#pragma endregion
|
||||
public:
|
||||
#pragma region Methods
|
||||
void Update(float elapsed) override;
|
||||
void InnerDraw() override;
|
||||
void Draw() override;
|
||||
/// @return The number of characters the cursor is actually moved to the left by.
|
||||
int MoveCursorLeft(int chars = 1);
|
||||
/// @note The amount of
|
||||
/// @return The number of characters the cursor is actually moved to the right by.
|
||||
int MoveCursorRight(int chars = 1);
|
||||
#pragma region Getters
|
||||
|
||||
void MoveCursorLeft();
|
||||
#pragma endregion
|
||||
#pragma region Setters
|
||||
|
||||
void MoveCursorRight();
|
||||
#pragma endregion
|
||||
|
||||
/// Returns the maximum position of the cursor, which is determined by the input buffer's length.
|
||||
[[nodiscard]] unsigned int CursorMaxPosition() const;
|
||||
@@ -64,6 +78,11 @@ namespace JUI {
|
||||
void PushKeyToCurrentPlaceInInputBuffer(const Key &key);
|
||||
|
||||
bool ObserveKeyInput(Key key, bool pressed) override;
|
||||
|
||||
void ShowNextHistory();
|
||||
|
||||
void ShowPrevHistory();
|
||||
|
||||
bool ObserveMouseInput(MouseButton btn, bool pressed) override;
|
||||
bool ObserveMouseMovement(const Vector2 &latest_known_pos) override;
|
||||
[[nodiscard]] std::string GetAutocompleteText() const;
|
||||
@@ -103,22 +122,50 @@ namespace JUI {
|
||||
void SetSelectionColor(const Color4& color);
|
||||
bool HasSelection() const;
|
||||
std::string GetSelectedText() const;
|
||||
|
||||
[[nodiscard]] bool KeepInputHistory() const;
|
||||
|
||||
void KeepInputHistory(bool value);
|
||||
|
||||
std::string InputHistory(int index) const;
|
||||
|
||||
#pragma endregion
|
||||
protected:
|
||||
#pragma region Properties
|
||||
bool keep_input_history = false;
|
||||
bool clear_text_on_return = true;
|
||||
bool drop_focus_on_return = false;
|
||||
bool focused = false;
|
||||
bool selection_enabled;
|
||||
bool selection_active;
|
||||
int selection_start_index;
|
||||
int selection_end_index;
|
||||
unsigned int cursor_position = 0;
|
||||
std::string input_buffer;
|
||||
|
||||
float cursor_blink_time = 0.f;
|
||||
Color4 autocomplete_color = Style::InputForm::AutocompleteTextColor;
|
||||
std::string autocomplete_text = "Hello World";
|
||||
bool hide_autocomplete_on_select = true;
|
||||
bool autocomplete_text_enabled = true;
|
||||
std::set<std::string> blacklist;
|
||||
bool selection_enabled;
|
||||
#pragma endregion
|
||||
#pragma region Working Variables
|
||||
std::vector<std::string> history;
|
||||
int history_index = -1;
|
||||
bool selection_active;
|
||||
int selection_start_index;
|
||||
int selection_end_index;
|
||||
unsigned int cursor_position = 0;
|
||||
std::string input_buffer;
|
||||
std::string saved_input_buffer;
|
||||
|
||||
|
||||
/// Tracks the time (in seconds) since the TextInputForm was last opened.
|
||||
/// @note This is used to circumvent a behavioral bug caused by how the input code is structured:
|
||||
/// 1. User clicks a button that opens an InputForm.
|
||||
/// 2. Grab InputForm Focus.
|
||||
/// 3. User releases the button.
|
||||
/// 4. The input form interprets this as "I am focused, but something else was just clicked".
|
||||
/// 5. The input form drops its focus instantly, and it appears as if it was never focused.
|
||||
float time_focused = 0.f;
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
};
|
||||
}
|
@@ -31,20 +31,18 @@ namespace JUI {
|
||||
|
||||
bool TextInputForm::ObserveMouseInput(MouseButton btn, bool pressed) {
|
||||
|
||||
if (pressed && btn == MouseButton::Left) {
|
||||
if (!IsMouseInside() && focused && time_focused > 1.f) {
|
||||
OnDeselect.Invoke();
|
||||
focused = false;
|
||||
}
|
||||
|
||||
if (IsMouseInside()) {
|
||||
|
||||
if (IsMouseInside() && pressed && btn == MouseButton::Left) {
|
||||
if (!focused) {
|
||||
OnSelect.Invoke();
|
||||
}
|
||||
focused = true;
|
||||
return true;
|
||||
} else {
|
||||
if (focused)
|
||||
OnDeselect.Invoke();
|
||||
focused = false;
|
||||
}
|
||||
|
||||
}
|
||||
return Widget::ObserveMouseInput(btn, pressed);
|
||||
}
|
||||
@@ -87,6 +85,7 @@ namespace JUI {
|
||||
|
||||
// TODO: Make cursor actually blink
|
||||
if (focused) {
|
||||
time_focused += elapsed;
|
||||
cursor_blink_time += elapsed;
|
||||
|
||||
if (cursor_blink_time > 1.f)
|
||||
@@ -115,12 +114,27 @@ namespace JUI {
|
||||
return s;
|
||||
}
|
||||
|
||||
void TextInputForm::MoveCursorLeft() {
|
||||
if (cursor_position > 0)
|
||||
cursor_position--;
|
||||
int TextInputForm::MoveCursorLeft(int chars) {
|
||||
if (cursor_position <= 0) return 0; // TODO: This may be redundant?
|
||||
|
||||
int available_moves = Math::Min<int>(chars, cursor_position);
|
||||
|
||||
cursor_position -= available_moves;
|
||||
|
||||
return available_moves;
|
||||
}
|
||||
|
||||
void TextInputForm::MoveCursorRight() {
|
||||
int TextInputForm::MoveCursorRight(int chars) {
|
||||
if (cursor_position >= CursorMaxPosition()) return 0;
|
||||
|
||||
int chars_until_end = CursorMaxPosition() - cursor_position;
|
||||
|
||||
int available_moves = Math::Min<int>(chars, chars_until_end);
|
||||
|
||||
cursor_position += available_moves;
|
||||
|
||||
return available_moves;
|
||||
|
||||
if (cursor_position < CursorMaxPosition()-1)
|
||||
cursor_position++;
|
||||
}
|
||||
@@ -137,6 +151,15 @@ namespace JUI {
|
||||
void TextInputForm::SendInput(bool clear_input) {
|
||||
OnReturn.Invoke(input_buffer);
|
||||
|
||||
if (keep_input_history) {
|
||||
if (history.size() == 0 || history[0] != input_buffer) {
|
||||
history.insert(history.begin(), input_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
history_index = -1;
|
||||
saved_input_buffer = "";
|
||||
|
||||
if (clear_input) {
|
||||
input_buffer = "";
|
||||
cursor_position = 0;
|
||||
@@ -159,11 +182,15 @@ namespace JUI {
|
||||
if (cursor_position > 0) {
|
||||
input_buffer = input_buffer.erase(cursor_position-1, 1);
|
||||
cursor_position--;
|
||||
|
||||
saved_input_buffer = input_buffer;
|
||||
}
|
||||
}
|
||||
void TextInputForm::PushStringToCurrentPlaceInInputBuffer(const std::string& snippet) {
|
||||
input_buffer = input_buffer.insert(cursor_position, snippet);
|
||||
cursor_position += snippet.length();
|
||||
|
||||
saved_input_buffer = input_buffer;
|
||||
}
|
||||
void TextInputForm::PushKeyToCurrentPlaceInInputBuffer(const Key& key) {
|
||||
std::string insertion = lowercase(key.Mnemonic);
|
||||
@@ -194,12 +221,30 @@ namespace JUI {
|
||||
}
|
||||
|
||||
if (key == Keys::LeftArrow) {
|
||||
MoveCursorLeft();
|
||||
int qty = 1;
|
||||
// TODO: Move Left until next space.
|
||||
if (InputService::IsKeyDown(Keys::LeftShift)) qty = 5;
|
||||
MoveCursorLeft(qty);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == Keys::RightArrow) {
|
||||
MoveCursorRight();
|
||||
int qty = 1;
|
||||
// TODO: Move Right until next space.
|
||||
if (InputService::IsKeyDown(Keys::LeftShift)) qty = 5;
|
||||
MoveCursorRight(qty);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == Keys::DownArrow) {
|
||||
if (keep_input_history)
|
||||
ShowNextHistory();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == Keys::UpArrow) {
|
||||
if (keep_input_history)
|
||||
ShowPrevHistory();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -218,11 +263,45 @@ namespace JUI {
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextInputForm::ShowNextHistory() {
|
||||
if (keep_input_history) {
|
||||
|
||||
if (history_index > -1)
|
||||
history_index--;
|
||||
|
||||
std::string result = "";
|
||||
if (history_index == -1)
|
||||
SetInputBuffer(saved_input_buffer);
|
||||
else
|
||||
SetInputBuffer(history[history_index]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void TextInputForm::ShowPrevHistory() {
|
||||
if (keep_input_history) {
|
||||
if (history_index == -1 && !history.empty() || history.size()-1 > history_index) {
|
||||
history_index++;
|
||||
}
|
||||
|
||||
std::string result = "";
|
||||
if (history_index == -1)
|
||||
SetInputBuffer(saved_input_buffer);
|
||||
else
|
||||
SetInputBuffer(history[history_index]);
|
||||
}
|
||||
}
|
||||
|
||||
bool TextInputForm::HasFocus() const { return focused; }
|
||||
|
||||
void TextInputForm::SetFocused(bool focused) { this->focused = focused;}
|
||||
void TextInputForm::SetFocused(bool focused) {
|
||||
this->focused = focused;
|
||||
if (focused) time_focused = 0;
|
||||
}
|
||||
|
||||
void TextInputForm::GrabFocus() { SetFocused(true); }
|
||||
void TextInputForm::GrabFocus() {
|
||||
SetFocused(true);
|
||||
}
|
||||
|
||||
void TextInputForm::DropFocus() { SetFocused(false); }
|
||||
|
||||
@@ -250,4 +329,13 @@ namespace JUI {
|
||||
blacklist.insert(value);
|
||||
}
|
||||
|
||||
bool TextInputForm::KeepInputHistory() const { return keep_input_history; }
|
||||
|
||||
void TextInputForm::KeepInputHistory(bool value) {
|
||||
keep_input_history = value;
|
||||
}
|
||||
|
||||
std::string TextInputForm::InputHistory(int index) const {
|
||||
return history[index];
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user