#include #include #include #include #include "Core.h" #include #include #include #include #include "modules/Screen.h" #include "df/enabler.h" using std::string; using std::vector; using std::map; using std::ostringstream; using std::set; using namespace DFHack; using namespace df::enums; using df::global::enabler; #ifndef HAVE_NULLPTR #define nullptr 0L #endif #define COLOR_TITLE COLOR_BLUE #define COLOR_UNSELECTED COLOR_GREY #define COLOR_SELECTED COLOR_WHITE #define COLOR_HIGHLIGHTED COLOR_GREEN template static void for_each_(vector &v, Fn func) { for_each(v.begin(), v.end(), func); } template static void for_each_(map &v, Fn func) { for_each(v.begin(), v.end(), func); } template static void transform_(vector &src, vector &dst, Fn func) { transform(src.begin(), src.end(), back_inserter(dst), func); } typedef int8_t UIColor; void OutputString(UIColor color, int &x, int &y, const std::string &text, bool newline = false, int left_margin = 0, const UIColor bg_color = 0) { Screen::paintString(Screen::Pen(' ', color, bg_color), x, y, text); if (newline) { ++y; x = left_margin; } else x += text.length(); } void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false, int left_margin = 0, int8_t color = COLOR_WHITE) { OutputString(10, x, y, hotkey); string display(": "); display.append(text); OutputString(color, x, y, display, newline, left_margin); } void OutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, int left_margin = 0, int8_t color = COLOR_WHITE) { OutputHotkeyString(x, y, text, hotkey); OutputString(COLOR_WHITE, x, y, ": "); if (state) OutputString(COLOR_GREEN, x, y, "Enabled", newline, left_margin); else OutputString(COLOR_GREY, x, y, "Disabled", newline, left_margin); } const int ascii_to_enum_offset = interface_key::STRING_A048 - '0'; inline string int_to_string(const int n) { return static_cast( &(ostringstream() << n) )->str(); } static void set_to_limit(int &value, const int maximum, const int min = 0) { if (value < min) value = min; else if (value > maximum) value = maximum; } inline void paint_text(const UIColor color, const int &x, const int &y, const std::string &text, const UIColor background = 0) { Screen::paintString(Screen::Pen(' ', color, background), x, y, text); } static string pad_string(string text, const int size, const bool front = true, const bool trim = false) { if (text.length() > size) { if (trim && size > 10) { text = text.substr(0, size-3); text.append("..."); } return text; } string aligned(size - text.length(), ' '); if (front) { aligned.append(text); return aligned; } else { text.append(aligned); return text; } } /* * List classes */ template class ListEntry { public: T *elem; string text; bool selected; ListEntry(string text, T &elem) : elem(&elem), text(text), selected(false) { } }; template class ListColumn { public: int highlighted_index; int display_start_offset; int32_t bottom_margin, search_margin, left_margin; bool multiselect; bool allow_null; bool auto_select; bool force_sort; bool allow_search; bool feed_changed_highlight; ListColumn() { bottom_margin = 3; clear(); left_margin = 2; search_margin = 63; highlighted_index = 0; multiselect = false; allow_null = true; auto_select = false; force_sort = false; allow_search = true; feed_changed_highlight = false; } void clear() { list.clear(); display_list.clear(); display_start_offset = 0; max_item_width = 0; resize(); } void resize() { display_max_rows = gps->dimy - 4 - bottom_margin; } void add(ListEntry &entry) { list.push_back(entry); if (entry.text.length() > max_item_width) max_item_width = entry.text.length(); } void add(const string &text, T &elem) { list.push_back(ListEntry(text, elem)); if (text.length() > max_item_width) max_item_width = text.length(); } int fixWidth() { for (auto it = list.begin(); it != list.end(); it++) { it->text = pad_string(it->text, max_item_width, false); } return left_margin + max_item_width; } virtual void display_extras(const T &elem, int32_t &x, int32_t &y) const {} void display(const bool is_selected_column) const { int32_t y = 2; paint_text(COLOR_TITLE, left_margin, y, title); int last_index_able_to_display = display_start_offset + display_max_rows; for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++) { ++y; UIColor fg_color = (display_list[i]->selected) ? COLOR_SELECTED : COLOR_UNSELECTED; UIColor bg_color = (is_selected_column && i == highlighted_index) ? COLOR_HIGHLIGHTED : COLOR_BLACK; paint_text(fg_color, left_margin, y, display_list[i]->text, bg_color); int x = left_margin + display_list[i]->text.length() + 1; display_extras(*display_list[i]->elem, x, y); } if (is_selected_column && allow_search) { y = gps->dimy - bottom_margin; int32_t x = search_margin; OutputHotkeyString(x, y, "Search" ,"S"); OutputString(COLOR_WHITE, x, y, ": "); OutputString(COLOR_WHITE, x, y, search_string); OutputString(COLOR_LIGHTGREEN, x, y, "_"); } } void filterDisplay() { ListEntry *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL; display_list.clear(); search_string = toLower(search_string); for (size_t i = 0; i < list.size(); i++) { if (search_string.empty() || toLower(list[i].text).find(search_string) != string::npos) { ListEntry *entry = &list[i]; display_list.push_back(entry); if (entry == prev_selected) highlighted_index = display_list.size() - 1; } } changeHighlight(0); feed_changed_highlight = true; } void selectDefaultEntry() { for (size_t i = 0; i < display_list.size(); i++) { if (display_list[i]->selected) { highlighted_index = i; break; } } } void validateHighlight() { set_to_limit(highlighted_index, display_list.size() - 1); if (highlighted_index < display_start_offset) display_start_offset = highlighted_index; else if (highlighted_index >= display_start_offset + display_max_rows) display_start_offset = highlighted_index - display_max_rows + 1; if (auto_select || (!allow_null && list.size() == 1)) display_list[highlighted_index]->selected = true; feed_changed_highlight = true; } void changeHighlight(const int highlight_change, const int offset_shift = 0) { if (!initHighlightChange()) return; highlighted_index += highlight_change + offset_shift * display_max_rows; display_start_offset += offset_shift * display_max_rows; set_to_limit(display_start_offset, max(0, (int)(display_list.size())-display_max_rows)); validateHighlight(); } void setHighlight(const int index) { if (!initHighlightChange()) return; highlighted_index = index; validateHighlight(); } bool initHighlightChange() { if (display_list.size() == 0) return false; if (auto_select && !multiselect) { for (auto it = list.begin(); it != list.end(); it++) { it->selected = false; } } return true; } void toggleHighlighted() { if (auto_select) return; ListEntry *entry = display_list[highlighted_index]; if (!multiselect || !allow_null) { int selected_count = 0; for (size_t i = 0; i < list.size(); i++) { if (!multiselect && !entry->selected) list[i].selected = false; if (!allow_null && list[i].selected) selected_count++; } if (!allow_null && entry->selected && selected_count == 1) return; } entry->selected = !entry->selected; } vector getSelectedElems(bool only_one = false) { vector results; for (auto it = list.begin(); it != list.end(); it++) { if ((*it).selected) { results.push_back(it->elem); if (only_one) break; } } return results; } void clearSelection() { for_each_(list, [] (ListEntry &e) { e.selected = false; }); } void selectItem(const T* elem) { if (!elem) return; //clearSelection(); int i = 0; for (; i < display_list.size(); i++) { if (display_list[i]->elem == elem) { setHighlight(i); break; } } } void clearSearch() { search_string.clear(); filterDisplay(); } T* getFirstSelectedElem() { vector results = getSelectedElems(true); if (results.size() == 0) return nullptr; else return results[0]; } size_t getDisplayListSize() { return display_list.size(); } size_t getBaseListSize() { return list.size(); } bool feed(set *input) { feed_changed_highlight = false; if (input->count(interface_key::CURSOR_UP)) { changeHighlight(-1); } else if (input->count(interface_key::CURSOR_DOWN)) { changeHighlight(1); } else if (input->count(interface_key::STANDARDSCROLL_PAGEUP)) { changeHighlight(0, -1); } else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN)) { changeHighlight(0, 1); } else if (input->count(interface_key::SELECT) && !auto_select) { toggleHighlighted(); } else if (input->count(interface_key::CUSTOM_SHIFT_S)) { clearSearch(); } else if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1 && enabler->mouse_lbut) { return setHighlightByMouse(); } else if (allow_search) { // Search query typing mode always on df::interface_key last_token = *input->rbegin(); if (last_token >= interface_key::STRING_A096 && last_token <= interface_key::STRING_A123) { // Standard character search_string += last_token - ascii_to_enum_offset; filterDisplay(); } else if (last_token == interface_key::STRING_A000) { // Backspace if (search_string.length() > 0) { search_string.erase(search_string.length()-1); filterDisplay(); } } else { return false; } return true; } else { return false; } return true; } bool setHighlightByMouse() { if (gps->mouse_y >= 3 && gps->mouse_y < display_max_rows + 3 && gps->mouse_x >= left_margin && gps->mouse_x < left_margin + max_item_width) { int new_index = display_start_offset + gps->mouse_y - 3; if (new_index < display_list.size()) setHighlight(new_index); enabler->mouse_lbut = enabler->mouse_rbut = 0; return true; } return false; } void sort() { if (force_sort || list.size() < 100) std::sort(list.begin(), list.end(), [] (ListEntry const& a, ListEntry const& b) { return a.text.compare(b.text) < 0; }); filterDisplay(); } void setTitle(const string t) { title = t; if (title.length() > max_item_width) max_item_width = title.length(); } size_t getDisplayedListSize() { return display_list.size(); } private: vector> list; vector*> display_list; string search_string; string title; int display_max_rows; int max_item_width; };