cscg22-gearboy

CSCG 2022 Challenge 'Gearboy'
git clone https://git.sinitax.com/sinitax/cscg22-gearboy
Log | Files | Refs | sfeed.txt

ImGuiFileBrowser.cpp (47137B)


      1#include "ImGuiFileBrowser.h"
      2#ifndef IMGUI_DEFINE_MATH_OPERATORS
      3#define IMGUI_DEFINE_MATH_OPERATORS
      4#endif
      5#include "../imgui/imgui_internal.h"
      6
      7#include <iostream>
      8#include <functional>
      9#include <climits>
     10#include <string.h>
     11#include <sstream>
     12#include <cwchar>
     13#include <cctype>
     14#include <algorithm>
     15#include <cmath>
     16#if defined (WIN32) || defined (_WIN32) || defined (__WIN32)
     17#define OSWIN
     18#ifndef NOMINMAX
     19    #define NOMINMAX
     20#endif
     21#include "Dirent/dirent.h"
     22#include <windows.h>
     23#else
     24#include <dirent.h>
     25#endif // defined (WIN32) || defined (_WIN32)
     26
     27namespace imgui_addons
     28{
     29    ImGuiFileBrowser::ImGuiFileBrowser()
     30    {
     31        filter_mode = FilterMode_Files | FilterMode_Dirs;
     32
     33        show_inputbar_combobox = false;
     34        validate_file = false;
     35        show_hidden = false;
     36        is_dir = false;
     37        filter_dirty = true;
     38        is_appearing = true;
     39
     40        col_items_limit = 12;
     41        selected_idx = -1;
     42        selected_ext_idx = 0;
     43        ext_box_width = -1.0f;
     44        col_width = 280.0f;
     45        min_size = ImVec2(500,300);
     46
     47        invfile_modal_id = "Invalid File!";
     48        repfile_modal_id = "Replace File?";
     49        selected_fn = "";
     50        selected_path = "";
     51
     52        #ifdef OSWIN
     53        current_path = "./";
     54        #else
     55        initCurrentPath();
     56        #endif
     57    }
     58
     59    ImGuiFileBrowser::~ImGuiFileBrowser()
     60    {
     61
     62    }
     63
     64    void ImGuiFileBrowser::clearFileList()
     65    {
     66        //Clear pointer references to subdirs and subfiles
     67        filtered_dirs.clear();
     68        filtered_files.clear();
     69        inputcb_filter_files.clear();
     70
     71        //Now clear subdirs and subfiles
     72        subdirs.clear();
     73        subfiles.clear();
     74        filter_dirty = true;
     75        selected_idx = -1;
     76    }
     77
     78    void ImGuiFileBrowser::closeDialog()
     79    {
     80        if (is_open_)
     81            *is_open_ = false;
     82        valid_types = "";
     83        valid_exts.clear();
     84        selected_ext_idx = 0;
     85        selected_idx = -1;
     86
     87        input_fn[0] = '\0';  //Hide any text in Input bar for the next time save dialog is opened.
     88        filter.Clear();     //Clear Filter for the next time open dialog is called.
     89
     90        show_inputbar_combobox = false;
     91        validate_file = false;
     92        show_hidden = false;
     93        is_dir = false;
     94        filter_dirty = true;
     95        is_appearing = true;
     96
     97        //Clear pointer references to subdirs and subfiles
     98        filtered_dirs.clear();
     99        filtered_files.clear();
    100        inputcb_filter_files.clear();
    101
    102        //Now clear subdirs and subfiles
    103        subdirs.clear();
    104        subfiles.clear();
    105
    106        ImGui::CloseCurrentPopup();
    107    }
    108
    109    bool ImGuiFileBrowser::showFileDialog(const std::string& label, const DialogMode mode, const ImVec2& sz_xy, const std::string& valid_types, bool* is_open)
    110    {
    111        is_open_ = is_open;
    112        dialog_mode = mode;
    113        ImGuiIO& io = ImGui::GetIO();
    114        max_size.x = io.DisplaySize.x;
    115        max_size.y = io.DisplaySize.y;
    116        ImGui::SetNextWindowSizeConstraints(min_size, max_size);
    117        ImGui::SetNextWindowPos(io.DisplaySize * 0.5f, ImGuiCond_Appearing, ImVec2(0.5f,0.5f));
    118        ImGui::SetNextWindowSize(ImVec2(std::max(sz_xy.x, min_size.x), std::max(sz_xy.y, min_size.y)), ImGuiCond_Appearing);
    119
    120        //Set Proper Filter Mode.
    121        if(mode == DialogMode::SELECT)
    122            filter_mode = FilterMode_Dirs;
    123        else
    124            filter_mode = FilterMode_Files | FilterMode_Dirs;
    125
    126        if (ImGui::BeginPopupModal(label.c_str(), nullptr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse))
    127        {
    128            bool show_error = false;
    129            if (is_open_)
    130                *is_open_ = true;
    131
    132            // If this is the initial run, read current directory and load data once.
    133            if(is_appearing)
    134            {
    135                selected_fn.clear();
    136                selected_path.clear();
    137                if(mode != DialogMode::SELECT)
    138                {
    139                    this->valid_types = valid_types;
    140                    setValidExtTypes(valid_types);
    141                }
    142
    143                /* If current path is empty (can happen on Windows if user closes dialog while inside MyComputer.
    144                 * Since this is a virtual folder, path would be empty) load the drives on Windows else initialize the current path on Unix.
    145                 */
    146                if(current_path.empty())
    147                {
    148                    #ifdef OSWIN
    149                    show_error |= !(loadWindowsDrives());
    150                    #else
    151                    initCurrentPath();
    152                    show_error |= !(readDIR(current_path));
    153                    #endif // OSWIN
    154                }
    155                else
    156                    show_error |= !(readDIR(current_path));
    157                is_appearing = false;
    158            }
    159
    160            show_error |= renderNavAndSearchBarRegion();
    161            show_error |= renderFileListRegion();
    162            show_error |= renderButtonsAndCheckboxRegion();
    163
    164            if (*is_open)
    165                show_error |= renderInputTextAndExtRegion();
    166
    167            if(validate_file)
    168            {
    169                validate_file = false;
    170                bool check = validateFile();
    171
    172                if(!check && dialog_mode == DialogMode::OPEN)
    173                {
    174                    ImGui::OpenPopup(invfile_modal_id.c_str());
    175                    selected_fn.clear();
    176                    selected_path.clear();
    177                }
    178
    179                else if(!check && dialog_mode == DialogMode::SAVE)
    180                    ImGui::OpenPopup(repfile_modal_id.c_str());
    181
    182                else if(!check && dialog_mode == DialogMode::SELECT)
    183                {
    184                    selected_fn.clear();
    185                    selected_path.clear();
    186                    show_error = true;
    187                    error_title = "Invalid Directory!";
    188                    error_msg = "Invalid Directory Selected. Please make sure the directory exists.";
    189                }
    190
    191                //If selected file passes through validation check, set path to the file and close file dialog
    192                if(check)
    193                {
    194                    selected_path = current_path + selected_fn;
    195
    196                    //Add a trailing "/" to emphasize its a directory not a file. If you want just the dir name it's accessible through "selected_fn"
    197                    if(dialog_mode == DialogMode::SELECT)
    198                        selected_path += "/";
    199                    closeDialog();
    200                }
    201            }
    202
    203            // We don't need to check as the modals will only be shown if OpenPopup is called
    204            showInvalidFileModal();
    205            if(showReplaceFileModal())
    206                closeDialog();
    207
    208            //Show Error Modal if there was an error opening any directory
    209            if(show_error)
    210                ImGui::OpenPopup(error_title.c_str());
    211            showErrorModal();
    212
    213            ImGui::EndPopup();
    214            return (!selected_fn.empty() && !selected_path.empty());
    215        }
    216        else
    217            return false;
    218    }
    219
    220    bool ImGuiFileBrowser::renderNavAndSearchBarRegion()
    221    {
    222        ImGuiStyle& style = ImGui::GetStyle();
    223        bool show_error = false;
    224        float frame_height = ImGui::GetFrameHeight();
    225        float list_item_height = GImGui->FontSize + style.ItemSpacing.y;
    226
    227        ImVec2 pw_content_size = ImGui::GetWindowSize() - style.WindowPadding * 2.0;
    228        ImVec2 sw_size = ImVec2(ImGui::CalcTextSize("Random").x + 140, style.WindowPadding.y * 2.0f + frame_height);
    229        ImVec2 sw_content_size = sw_size - style.WindowPadding * 2.0;
    230        ImVec2 nw_size = ImVec2(pw_content_size.x - style.ItemSpacing.x - sw_size.x, sw_size.y);
    231
    232
    233        ImGui::BeginChild("##NavigationWindow", nw_size, true, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoScrollbar);
    234
    235        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.882f, 0.745f, 0.078f,1.0f));
    236        for(std::size_t i = 0; i < current_dirlist.size(); i++)
    237        {
    238            if( ImGui::Button(current_dirlist[i].c_str()) )
    239            {
    240                //If last button clicked, nothing happens
    241                if(i != current_dirlist.size() - 1)
    242                    show_error |= !(onNavigationButtonClick((int)i));
    243            }
    244
    245            //Draw Arrow Buttons
    246            if(i != current_dirlist.size() - 1)
    247            {
    248                ImGui::SameLine(0,0);
    249                float next_label_width = ImGui::CalcTextSize(current_dirlist[i+1].c_str()).x;
    250
    251                if(i+1 < current_dirlist.size() - 1)
    252                    next_label_width += frame_height + ImGui::CalcTextSize(">>").x;
    253
    254                if(ImGui::GetCursorPosX() + next_label_width >= (nw_size.x - style.WindowPadding.x * 3.0))
    255                {
    256                    ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.01f));
    257                    ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f,1.0f));
    258
    259                    //Render a drop down of navigation items on button press
    260                    if(ImGui::Button(">>"))
    261                        ImGui::OpenPopup("##NavBarDropboxPopup");
    262                    if(ImGui::BeginPopup("##NavBarDropboxPopup"))
    263                    {
    264                        ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.125f, 0.125f, 0.125f, 1.0f));
    265                        if(ImGui::ListBoxHeader("##NavBarDropBox", ImVec2(0, list_item_height* 5)))
    266                        {
    267                            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.882f, 0.745f, 0.078f,1.0f));
    268                            for(std::size_t j = i+1; j < current_dirlist.size(); j++)
    269                            {
    270                                if(ImGui::Selectable(current_dirlist[j].c_str(), false) && j != current_dirlist.size() - 1)
    271                                {
    272                                    show_error |= !(onNavigationButtonClick((int)j));
    273                                    ImGui::CloseCurrentPopup();
    274                                }
    275                            }
    276                            ImGui::PopStyleColor();
    277                            ImGui::ListBoxFooter();
    278                        }
    279                        ImGui::PopStyleColor();
    280                        ImGui::EndPopup();
    281                    }
    282                    ImGui::PopStyleColor(2);
    283                    break;
    284                }
    285                else
    286                {
    287                    ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.01f));
    288                    ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f,1.0f));
    289                    ImGui::ArrowButtonEx("##Right", ImGuiDir_Right, ImVec2(frame_height, frame_height), ImGuiButtonFlags_Disabled);
    290                    ImGui::SameLine(0,0);
    291                    ImGui::PopStyleColor(2);
    292                }
    293            }
    294        }
    295        ImGui::PopStyleColor();
    296        ImGui::EndChild();
    297
    298        ImGui::SameLine();
    299        ImGui::BeginChild("##SearchWindow", sw_size, true, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoScrollbar);
    300
    301        //Render Search/Filter bar
    302        float marker_width = ImGui::CalcTextSize("(?)").x + style.ItemSpacing.x;
    303        if(filter.Draw("##SearchBar", sw_content_size.x - marker_width) || filter_dirty )
    304            filterFiles(filter_mode);
    305
    306        //If filter bar was focused clear selection
    307        if(ImGui::GetFocusID() == ImGui::GetID("##SearchBar"))
    308            selected_idx = -1;
    309
    310        ImGui::SameLine();
    311        showHelpMarker("Filter (inc, -exc)");
    312
    313        ImGui::EndChild();
    314        return show_error;
    315    }
    316
    317    bool ImGuiFileBrowser::renderFileListRegion()
    318    {
    319        ImGuiStyle& style = ImGui::GetStyle();
    320        ImVec2 pw_size = ImGui::GetWindowSize();
    321        bool show_error = false;
    322        float list_item_height = ImGui::CalcTextSize("").y + style.ItemSpacing.y;
    323        float input_bar_ypos = pw_size.y - ImGui::GetFrameHeightWithSpacing() * 2.5f - style.WindowPadding.y;
    324        float window_height = input_bar_ypos - ImGui::GetCursorPosY() - style.ItemSpacing.y;
    325        float window_content_height = window_height - style.WindowPadding.y * 2.0f;
    326        float min_content_size = pw_size.x - style.WindowPadding.x * 4.0f;
    327
    328        if(window_content_height <= 0.0f)
    329            return show_error;
    330
    331        //Reinitialize the limit on number of selectables in one column based on height
    332        col_items_limit = (int)std::max(1.0f, window_content_height/list_item_height);
    333        int num_cols = (int)std::max(1.0f, std::ceil(static_cast<float>(filtered_dirs.size() + filtered_files.size()) / col_items_limit));
    334        
    335        //Limitation by ImGUI in 1.75. If columns are greater than 64 readjust the limit on items per column and recalculate number of columns
    336        if(num_cols > 64)
    337        {
    338            int exceed_items_amount = (num_cols - 64) * col_items_limit;
    339            col_items_limit += (int)std::ceil(exceed_items_amount/64.0);
    340            num_cols = (int)std::max(1.0f, std::ceil(static_cast<float>(filtered_dirs.size() + filtered_files.size()) / col_items_limit));
    341        }
    342        
    343        float content_width = num_cols * col_width;
    344        if(content_width < min_content_size)
    345            content_width = 0;
    346
    347        ImGui::SetNextWindowContentSize(ImVec2(content_width, 0));
    348        ImGui::BeginChild("##ScrollingRegion", ImVec2(0, window_height), true, ImGuiWindowFlags_HorizontalScrollbar);
    349        ImGui::Columns(num_cols);
    350
    351        //Output directories in yellow
    352        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.882f, 0.745f, 0.078f,1.0f));
    353        int items = 0;
    354        for (std::size_t i = 0; i < filtered_dirs.size(); i++)
    355        {
    356            if(!filtered_dirs[i].is_hidden || show_hidden)
    357            {
    358                items++;
    359                if(ImGui::Selectable(filtered_dirs[i].name.c_str(), selected_idx == (int)i && is_dir, ImGuiSelectableFlags_AllowDoubleClick))
    360                {
    361                    selected_idx = (int)i;
    362                    is_dir = true;
    363
    364                    // If dialog mode is SELECT then copy the selected dir name to the input text bar
    365                    if(dialog_mode == DialogMode::SELECT)
    366                        strcpy(input_fn, filtered_dirs[i].name.c_str());
    367
    368                    if(ImGui::IsMouseDoubleClicked(0))
    369                    {
    370                        show_error |= !(onDirClick((int)i));
    371                        break;
    372                    }
    373                }
    374                if( (items) % col_items_limit == 0)
    375                    ImGui::NextColumn();
    376            }
    377        }
    378        ImGui::PopStyleColor(1);
    379
    380        //Output files
    381        for (std::size_t i = 0; i < filtered_files.size(); i++)
    382        {
    383            if(!filtered_files[i].is_hidden || show_hidden)
    384            {
    385                items++;
    386                if(ImGui::Selectable(filtered_files[i].name.c_str(), selected_idx == (int)i && !is_dir, ImGuiSelectableFlags_AllowDoubleClick))
    387                {
    388                    // unused: int len = filtered_files[i]->name.length();
    389                    selected_idx = (int)i;
    390                    is_dir = false;
    391
    392                    // If dialog mode is OPEN/SAVE then copy the selected file name to the input text bar
    393                    strcpy(input_fn, filtered_files[i].name.c_str());
    394
    395                    if(ImGui::IsMouseDoubleClicked(0))
    396                    {
    397                        selected_fn = filtered_files[i].name;
    398                        validate_file = true;
    399                    }
    400                }
    401                if( (items) % col_items_limit == 0)
    402                    ImGui::NextColumn();
    403            }
    404        }
    405        ImGui::Columns(1);
    406        ImGui::EndChild();
    407
    408        return show_error;
    409    }
    410
    411    bool ImGuiFileBrowser::renderInputTextAndExtRegion()
    412    {
    413        std::string label = (dialog_mode == DialogMode::SAVE) ? "Save As:" : "Open:";
    414        ImGuiStyle& style = ImGui::GetStyle();
    415        // unused: ImGuiIO& io = ImGui::GetIO();
    416
    417        ImVec2 pw_pos = ImGui::GetWindowPos();
    418        ImVec2 pw_content_sz = ImGui::GetWindowSize() - style.WindowPadding * 2.0;
    419        ImVec2 cursor_pos = ImGui::GetCursorPos();
    420
    421        if(ext_box_width < 0.0)
    422            ext_box_width = ImGui::CalcTextSize(".abc").x + 100;
    423        float label_width = ImGui::CalcTextSize(label.c_str()).x + style.ItemSpacing.x;
    424        float frame_height_spacing = ImGui::GetFrameHeightWithSpacing();
    425        float input_bar_width = pw_content_sz.x - label_width;
    426        if(dialog_mode != DialogMode::SELECT)
    427            input_bar_width -= (ext_box_width + style.ItemSpacing.x);
    428
    429        bool show_error = false;
    430        ImGui::SetCursorPosY(pw_content_sz.y - frame_height_spacing * 2.0f);
    431
    432        //Render Input Text Bar label
    433        ImGui::Text("%s", label.c_str());
    434        ImGui::SameLine();
    435
    436        //Render Input Text Bar
    437        input_combobox_pos = ImVec2(pw_pos + ImGui::GetCursorPos());
    438        input_combobox_sz = ImVec2(input_bar_width, 0);
    439        ImGui::PushItemWidth(input_bar_width);
    440        if(ImGui::InputTextWithHint("##FileNameInput", "Type a name...", &input_fn[0], 256, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll))
    441        {
    442            if(strlen(input_fn) > 0)
    443            {
    444                selected_fn = std::string(input_fn);
    445                validate_file = true;
    446            }
    447        }
    448        ImGui::PopItemWidth();
    449
    450        //If input bar was focused clear selection
    451        if(ImGui::IsItemEdited())
    452            selected_idx = -1;
    453
    454        // If Input Bar is edited show a list of files or dirs matching the input text.
    455        if(ImGui::IsItemEdited() || ImGui::IsItemActivated())
    456        {
    457            //If dialog_mode is OPEN/SAVE then filter from list of files..
    458            if(dialog_mode == DialogMode::OPEN || dialog_mode == DialogMode::SAVE)
    459            {
    460                inputcb_filter_files.clear();
    461                for(std::size_t i = 0; i < subfiles.size(); i++)
    462                {
    463                    if(ImStristr(subfiles[i].name.c_str(), nullptr, input_fn, nullptr) != nullptr)
    464                        inputcb_filter_files.push_back(std::ref(subfiles[i].name));
    465                }
    466            }
    467
    468            //If dialog_mode == SELECT then filter from list of directories
    469            else if(dialog_mode == DialogMode::SELECT)
    470            {
    471                inputcb_filter_files.clear();
    472                for(std::size_t i = 0; i < subdirs.size(); i++)
    473                {
    474                    if(ImStristr(subdirs[i].name.c_str(), nullptr, input_fn, nullptr) != nullptr)
    475                        inputcb_filter_files.push_back(std::ref(subdirs[i].name));
    476                }
    477            }
    478
    479            //If filtered list has any items show dropdown
    480            if(inputcb_filter_files.size() > 0)
    481                show_inputbar_combobox = true;
    482            else
    483                show_inputbar_combobox = false;
    484        }
    485
    486        //Render Extensions and File Types DropDown
    487        if(dialog_mode != DialogMode::SELECT)
    488        {
    489            ImGui::SameLine();
    490            renderExtBox();
    491        }
    492
    493        //Render a Drop Down of files/dirs (depending on mode) that have matching characters as the input text only.
    494        show_error |= renderInputComboBox();
    495
    496        ImGui::SetCursorPos(cursor_pos);
    497        return show_error;
    498    }
    499
    500    bool ImGuiFileBrowser::renderButtonsAndCheckboxRegion()
    501    {
    502        ImVec2 pw_size = ImGui::GetWindowSize();
    503        ImGuiStyle& style = ImGui::GetStyle();
    504        bool show_error = false;
    505        float frame_height = ImGui::GetFrameHeight();
    506        float frame_height_spacing = ImGui::GetFrameHeightWithSpacing();
    507        float opensave_btn_width = getButtonSize("Open").x;     // Since both Open/Save are 4 characters long, width gonna be same.
    508        float selcan_btn_width = getButtonSize("Cancel").x;     // Since both Cacnel/Select have same number of characters, so same width.
    509        float buttons_xpos;
    510
    511        if (dialog_mode == DialogMode::SELECT)
    512            buttons_xpos = pw_size.x - opensave_btn_width - (2.0f * selcan_btn_width) - ( 2.0f * style.ItemSpacing.x) - style.WindowPadding.x;
    513        else
    514            buttons_xpos = pw_size.x - opensave_btn_width - selcan_btn_width - style.ItemSpacing.x - style.WindowPadding.x;
    515
    516        ImGui::SetCursorPosY(pw_size.y - frame_height_spacing - style.WindowPadding.y);
    517
    518        //Render Checkbox
    519        float label_width = ImGui::CalcTextSize("Show Hidden Files and Folders").x + ImGui::GetCursorPosX() + frame_height;
    520        bool show_marker = (label_width >= buttons_xpos);
    521        ImGui::Checkbox( (show_marker) ? "##showHiddenFiles" : "Show Hidden Files and Folders", &show_hidden);
    522        if(show_marker)
    523        {
    524            ImGui::SameLine();
    525            showHelpMarker("Show Hidden Files and Folders");
    526        }
    527
    528        //Render an Open Button (in OPEN/SELECT dialog_mode) or Open/Save depending on what's selected in SAVE dialog_mode
    529        ImGui::SameLine();
    530        ImGui::SetCursorPosX(buttons_xpos);
    531        if(dialog_mode == DialogMode::SAVE)
    532        {
    533            // If directory selected and Input Text Bar doesn't have focus, render Open Button
    534            if(selected_idx != -1 && is_dir && ImGui::GetFocusID() != ImGui::GetID("##FileNameInput"))
    535            {
    536                if (ImGui::Button("Open"))
    537                    show_error |= !(onDirClick(selected_idx));
    538            }
    539            else if (ImGui::Button("Save") && strlen(input_fn) > 0)
    540            {
    541                selected_fn = std::string(input_fn);
    542                validate_file = true;
    543            }
    544        }
    545        else
    546        {
    547            if (ImGui::Button("Open"))
    548            {
    549                //It's possible for both to be true at once (user selected directory but input bar has some text. In this case we chose to open the directory instead of opening the file.
    550                //Also note that we don't need to access the selected file through "selected_idx" since the if a file is selected, input bar will get populated with that name.
    551                if(selected_idx >= 0 && is_dir)
    552                    show_error |= !(onDirClick(selected_idx));
    553                else if(strlen(input_fn) > 0)
    554                {
    555                    selected_fn = std::string(input_fn);
    556                    validate_file = true;
    557                }
    558            }
    559
    560            //Render Select Button if in SELECT Mode
    561            if(dialog_mode == DialogMode::SELECT)
    562            {
    563                //Render Select Button
    564                ImGui::SameLine();
    565                if (ImGui::Button("Select"))
    566                {
    567                    if(strlen(input_fn) > 0)
    568                    {
    569                        selected_fn = std::string(input_fn);
    570                        validate_file = true;
    571                    }
    572                }
    573            }
    574        }
    575
    576        //Render Cancel Button
    577        ImGui::SameLine();
    578        if (ImGui::Button("Cancel"))
    579            closeDialog();
    580
    581        return show_error;
    582    }
    583
    584    bool ImGuiFileBrowser::renderInputComboBox()
    585    {
    586        bool show_error = false;
    587        ImGuiStyle& style = ImGui::GetStyle();
    588        ImGuiID input_id =  ImGui::GetID("##FileNameInput");
    589        ImGuiID focus_scope_id = ImGui::GetID("##InputBarComboBoxListScope");
    590        float frame_height = ImGui::GetFrameHeight();
    591
    592        input_combobox_sz.y = std::min((inputcb_filter_files.size() + 1) * frame_height + style.WindowPadding.y *  2.0f, 
    593                                        8 * ImGui::GetFrameHeight() + style.WindowPadding.y *  2.0f);
    594        
    595        if(show_inputbar_combobox && ( ImGui::GetFocusScopeID() == focus_scope_id || ImGui::GetCurrentContext()->ActiveIdIsAlive == input_id  ))
    596        {
    597            ImGuiWindowFlags popupFlags = ImGuiWindowFlags_NoTitleBar           |
    598                                          ImGuiWindowFlags_NoResize             |
    599                                          ImGuiWindowFlags_NoMove               |
    600                                          ImGuiWindowFlags_NoFocusOnAppearing   |
    601                                          ImGuiWindowFlags_NoScrollbar          |
    602                                          ImGuiWindowFlags_NoSavedSettings;
    603
    604
    605            ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.1f, 0.1f, 0.1f, 1.0f));
    606            ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.125f, 0.125f, 0.125f, 1.0f));           
    607            ImGui::SetNextWindowBgAlpha(1.0);
    608            ImGui::SetNextWindowPos(input_combobox_pos + ImVec2(0, ImGui::GetFrameHeightWithSpacing()));
    609            ImGui::PushClipRect(ImVec2(0,0), ImGui::GetIO().DisplaySize, false);
    610            
    611            ImGui::BeginChild("##InputBarComboBox", input_combobox_sz, true, popupFlags);
    612
    613            ImVec2 listbox_size = input_combobox_sz - ImGui::GetStyle().WindowPadding * 2.0f;
    614            if(ImGui::ListBoxHeader("##InputBarComboBoxList", listbox_size))
    615            {
    616                ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f,1.0f));
    617                ImGui::PushFocusScope(focus_scope_id);
    618                for(auto& element : inputcb_filter_files)
    619                {
    620                    if(ImGui::Selectable(element.get().c_str(), false, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick))
    621                    {
    622                        if(element.get().size() > 256)
    623                        {
    624                            error_title = "Error!";
    625                            error_msg = "Selected File Name is longer than 256 characters.";
    626                            show_error = true;
    627                        }
    628                        else
    629                        {
    630                            strcpy(input_fn, element.get().c_str());
    631                            show_inputbar_combobox = false;
    632                        }
    633                    }
    634                }
    635                ImGui::PopFocusScope();
    636                ImGui::PopStyleColor(1);
    637                ImGui::ListBoxFooter();
    638            }
    639            ImGui::EndChild();
    640            ImGui::PopStyleColor(2);
    641            ImGui::PopClipRect();
    642        }
    643        return show_error;
    644    }
    645
    646    void ImGuiFileBrowser::renderExtBox()
    647    {
    648        ImGui::PushItemWidth(ext_box_width);
    649        if(ImGui::BeginCombo("##FileTypes", valid_exts[selected_ext_idx].c_str()))
    650        {
    651            for(std::size_t i = 0; i < valid_exts.size(); i++)
    652            {
    653                if(ImGui::Selectable(valid_exts[i].c_str(), selected_ext_idx == (int)i))
    654                {
    655                    selected_ext_idx = (int)i;
    656                    if(dialog_mode == DialogMode::SAVE)
    657                    {
    658                        std::string name(input_fn);
    659                        size_t idx = name.find_last_of(".");
    660                        if(idx == std::string::npos)
    661                            idx = strlen(input_fn);
    662                        for(std::size_t j = 0; j < valid_exts[selected_ext_idx].size(); j++)
    663                            input_fn[idx++] = valid_exts[selected_ext_idx][j];
    664                        input_fn[idx++] = '\0';
    665                    }
    666                    else
    667                        filterFiles(FilterMode_Files);
    668                }
    669            }
    670            ImGui::EndCombo();
    671        }
    672        ext = valid_exts[selected_ext_idx];
    673        ImGui::PopItemWidth();
    674    }
    675
    676    bool ImGuiFileBrowser::onNavigationButtonClick(int idx)
    677    {
    678        std::string new_path(current_path);
    679
    680        //First Button corresponds to virtual folder Computer which lists all logical drives (hard disks and removables) and "/" on Unix
    681        if(idx == 0)
    682        {
    683            #ifdef OSWIN
    684            if(!loadWindowsDrives())
    685                return false;
    686            current_path.clear();
    687            current_dirlist.clear();
    688            current_dirlist.push_back("Computer");
    689            return true;
    690            #else
    691            new_path = "/";
    692            #endif // OSWIN
    693        }
    694        else
    695        {
    696            #ifdef OSWIN
    697            //Clicked on a drive letter?
    698            if(idx == 1)
    699                new_path = current_path.substr(0, 3);
    700            else
    701                new_path = current_path.substr(0, current_path.find("/" + current_dirlist[idx]) + current_dirlist[idx].length() + 2 );
    702            #else
    703            //find returns 0 based indices and substr takes length of chars thus + 1. Another +1 to include trailing "/"
    704            new_path = current_path.substr(0, current_path.find("/" + current_dirlist[idx]) + current_dirlist[idx].length() + 2 );
    705            #endif
    706        }
    707
    708        if(readDIR(new_path))
    709        {
    710            current_dirlist.erase(current_dirlist.begin()+idx+1, current_dirlist.end());
    711            current_path = new_path;
    712            return true;
    713        }
    714        else
    715            return false;
    716    }
    717
    718    bool ImGuiFileBrowser::onDirClick(int idx)
    719    {
    720        std::string name;
    721        std::string new_path(current_path);
    722        bool drives_shown = false;
    723
    724        #ifdef OSWIN
    725        drives_shown = (current_dirlist.size() == 1 && current_dirlist.back() == "Computer");
    726        #endif // OSWIN
    727
    728        name = filtered_dirs[idx].name;
    729
    730        if(name == "..")
    731        {
    732            new_path.pop_back(); // Remove trailing '/'
    733            new_path = new_path.substr(0, new_path.find_last_of('/') + 1); // Also include a trailing '/'
    734        }
    735        else
    736        {
    737            //Remember we displayed drives on Windows as *Local/Removable Disk: X* hence we need last char only
    738            if(drives_shown)
    739                name = std::string(1, name.back()) + ":";
    740            new_path += name + "/";
    741        }
    742
    743        if(readDIR(new_path))
    744        {
    745            if(name == "..")
    746                current_dirlist.pop_back();
    747            else
    748                current_dirlist.push_back(name);
    749
    750             current_path = new_path;
    751             return true;
    752        }
    753        else
    754           return false;
    755    }
    756
    757    bool ImGuiFileBrowser::readDIR(std::string pathdir)
    758    {
    759        DIR* dir;
    760        struct dirent *ent;
    761
    762        /* If the current directory doesn't exist, and we are opening the dialog for the first time, reset to defaults to avoid looping of showing error modal.
    763         * An example case is when user closes the dialog in a folder. Then deletes the folder outside. On reopening the dialog the current path (previous) would be invalid.
    764         */
    765        dir = opendir(pathdir.c_str());
    766        if(dir == nullptr && is_appearing)
    767        {
    768            current_dirlist.clear();
    769            #ifdef OSWIN
    770            current_path = pathdir = "./";
    771            #else
    772            initCurrentPath();
    773            pathdir = current_path;
    774            #endif // OSWIN
    775
    776            dir = opendir(pathdir.c_str());
    777        }
    778
    779        if (dir != nullptr)
    780        {
    781            #ifdef OSWIN
    782            // If we are on Windows and current path is relative then get absolute path from dirent structure
    783            if(current_dirlist.empty() && pathdir == "./")
    784            {
    785                const wchar_t* absolute_path = dir->wdirp->patt;
    786                std::string current_directory = wStringToString(absolute_path);
    787                std::replace(current_directory.begin(), current_directory.end(), '\\', '/');
    788
    789                //Remove trailing "*" returned by ** dir->wdirp->patt **
    790                current_directory.pop_back();
    791                current_path = current_directory;
    792
    793                //Create a vector of each directory in the file path for the filepath bar. Not Necessary for linux as starting directory is "/"
    794                parsePathTabs(current_path);
    795            }
    796            #endif // OSWIN
    797
    798            // store all the files and directories within directory and clear previous entries
    799            clearFileList();
    800            while ((ent = readdir (dir)) != nullptr)
    801            {
    802                bool is_hidden = false;
    803                std::string name(ent->d_name);
    804
    805                //Ignore current directory
    806                if(name == ".")
    807                    continue;
    808
    809                //Somehow there is a '..' present in root directory in linux.
    810                #ifndef OSWIN
    811                if(name == ".." && pathdir == "/")
    812                    continue;
    813                #endif // OSWIN
    814
    815                if(name != "..")
    816                {
    817                    #ifdef OSWIN
    818                    std::string dir = pathdir + std::string(ent->d_name);
    819                    // IF system file skip it...
    820                    if (FILE_ATTRIBUTE_SYSTEM & GetFileAttributesA(dir.c_str()))
    821                        continue;
    822                    if (FILE_ATTRIBUTE_HIDDEN & GetFileAttributesA(dir.c_str()))
    823                        is_hidden = true;
    824                    #else
    825                    if(name[0] == '.')
    826                        is_hidden = true;
    827                    #endif // OSWIN
    828                }
    829                //Store directories and files in separate vectors
    830                if(ent->d_type == DT_DIR)
    831                    subdirs.push_back(Info(name, is_hidden));
    832                else if(ent->d_type == DT_REG && dialog_mode != DialogMode::SELECT)
    833                    subfiles.push_back(Info(name, is_hidden));
    834
    835                #ifndef OSWIN
    836                else if(ent->d_type == DT_LNK)
    837                {
    838                    subdirs.push_back(Info(name, is_hidden));
    839                }
    840                #endif // OSWIN
    841            }
    842            closedir (dir);
    843            std::sort(subdirs.begin(), subdirs.end(), alphaSortComparator);
    844            std::sort(subfiles.begin(), subfiles.end(), alphaSortComparator);
    845
    846            //Initialize Filtered dirs and files
    847            filterFiles(filter_mode);
    848        }
    849        else
    850        {
    851            error_title = "Error!";
    852            error_msg = "Error opening directory! Make sure the directory exists and you have the proper rights to access the directory.";
    853            return false;
    854        }
    855        return true;
    856    }
    857
    858    void ImGuiFileBrowser::filterFiles(int filter_mode)
    859    {
    860        filter_dirty = false;
    861        if((filter_mode & FilterMode_Dirs) != 0)
    862        {
    863            filtered_dirs.clear();
    864            for (size_t i = 0; i < subdirs.size(); ++i)
    865            {
    866                if(filter.PassFilter(subdirs[i].name.c_str()))
    867                    filtered_dirs.push_back(subdirs[i]);
    868            }
    869        }
    870        if((filter_mode & FilterMode_Files) != 0)
    871        {
    872            filtered_files.clear();
    873            for (size_t i = 0; i < subfiles.size(); ++i)
    874            {
    875                if(valid_exts[selected_ext_idx] == "*.*")
    876                {
    877                    if(filter.PassFilter(subfiles[i].name.c_str()))
    878                        filtered_files.push_back(subfiles[i]);
    879                }
    880                else
    881                {
    882                    //if(filter.PassFilter(subfiles[i].name.c_str()) && (ImStristr(subfiles[i].name.c_str(), nullptr, valid_exts[selected_ext_idx].c_str(), nullptr)) != nullptr)
    883
    884                    int last = (int)subfiles[i].name.find_last_of(".");
    885                    
    886                    if (last >= 0 )
    887                    {
    888                        std::string extension = subfiles[i].name.substr(last);
    889                        std::transform(extension.begin(), extension.end(), extension.begin(),
    890    [](unsigned char c){ return std::tolower(c); });
    891
    892                        if(filter.PassFilter(subfiles[i].name.c_str()) && (extension == valid_exts[selected_ext_idx]))
    893                            filtered_files.push_back(subfiles[i]);
    894                    }
    895                }
    896            }
    897        }
    898    }
    899
    900    void ImGuiFileBrowser::showHelpMarker(std::string desc)
    901    {
    902        ImGui::TextDisabled("(?)");
    903        if (ImGui::IsItemHovered())
    904        {
    905            ImGui::BeginTooltip();
    906            ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
    907            ImGui::TextUnformatted(desc.c_str());
    908            ImGui::PopTextWrapPos();
    909            ImGui::EndTooltip();
    910        }
    911    }
    912
    913    void ImGuiFileBrowser::showErrorModal()
    914    {
    915        ImVec2 window_size(260, 0);
    916        ImGui::SetNextWindowSize(window_size);
    917
    918        if (ImGui::BeginPopupModal(error_title.c_str(), nullptr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoResize))
    919        {
    920            ImGui::TextWrapped("%s", error_msg.c_str());
    921
    922            ImGui::Separator();
    923            ImGui::SetCursorPosX(window_size.x/2.0f - getButtonSize("OK").x/2.0f);
    924            if (ImGui::Button("OK", getButtonSize("OK")))
    925                ImGui::CloseCurrentPopup();
    926            ImGui::EndPopup();
    927        }
    928    }
    929
    930    bool ImGuiFileBrowser::showReplaceFileModal()
    931    {
    932        ImVec2 window_size(250, 0);
    933        ImGui::SetNextWindowSize(window_size);
    934        bool ret_val = false;
    935        if (ImGui::BeginPopupModal(repfile_modal_id.c_str(), nullptr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoResize))
    936        {
    937            // unused: float frame_height = ImGui::GetFrameHeightWithSpacing();
    938
    939            std::string text = "A file with the following filename already exists. Are you sure you want to replace the existing file?";
    940            ImGui::TextWrapped("%s", text.c_str());
    941
    942            ImGui::Separator();
    943
    944            float buttons_width = getButtonSize("Yes").x + getButtonSize("No").x + ImGui::GetStyle().ItemSpacing.x;
    945            ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetWindowWidth()/2.0f - buttons_width/2.0f - ImGui::GetStyle().WindowPadding.x);
    946
    947            if (ImGui::Button("Yes", getButtonSize("Yes")))
    948            {
    949                selected_path = current_path + selected_fn;
    950                ImGui::CloseCurrentPopup();
    951                ret_val = true;
    952            }
    953
    954            ImGui::SameLine();
    955            if (ImGui::Button("No", getButtonSize("No")))
    956            {
    957                selected_fn.clear();
    958                selected_path.clear();
    959                ImGui::CloseCurrentPopup();
    960                ret_val = false;
    961            }
    962            ImGui::EndPopup();
    963        }
    964        return ret_val;
    965    }
    966
    967    void ImGuiFileBrowser::showInvalidFileModal()
    968    {
    969        // unused: ImGuiStyle& style = ImGui::GetStyle();
    970        std::string text = "Selected file either doesn't exist or is not supported. Please select a file with the following extensions...";
    971        // unused: ImVec2 text_size = ImGui::CalcTextSize(text.c_str(), nullptr, true, 350 - style.WindowPadding.x * 2.0);
    972        ImVec2 button_size = getButtonSize("OK");
    973
    974        float frame_height = ImGui::GetFrameHeightWithSpacing();
    975        float cw_content_height = valid_exts.size() * frame_height;
    976        float cw_height = std::min(4.0f * frame_height, cw_content_height);
    977        ImVec2 window_size(350, 0);
    978        ImGui::SetNextWindowSize(window_size);
    979
    980        if (ImGui::BeginPopupModal(invfile_modal_id.c_str(), nullptr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoResize))
    981        {
    982
    983            ImGui::TextWrapped("%s", text.c_str());
    984            ImGui::BeginChild("##SupportedExts", ImVec2(0, cw_height), true);
    985            for(std::size_t i = 0; i < valid_exts.size(); i++)
    986                ImGui::BulletText("%s", valid_exts[i].c_str());
    987            ImGui::EndChild();
    988
    989            ImGui::SetCursorPosX(window_size.x/2.0f - button_size.x/2.0f);
    990            if (ImGui::Button("OK", button_size))
    991                ImGui::CloseCurrentPopup();
    992            ImGui::EndPopup();
    993        }
    994    }
    995
    996    void ImGuiFileBrowser::setValidExtTypes(const std::string& valid_types_string)
    997    {
    998        /* Initialize a list of files extensions that are valid.
    999         * If the user chooses a file that doesn't match the extensions in the
   1000         * list, we will show an error modal...
   1001         */
   1002        std::string max_str = "";
   1003        valid_exts.clear();
   1004        std::string extension = "";
   1005        std::istringstream iss(valid_types_string);
   1006        while(std::getline(iss, extension, ','))
   1007        {
   1008            if(!extension.empty())
   1009            {
   1010                if(max_str.size() < extension.size())
   1011                    max_str = extension;
   1012                std::transform(extension.begin(), extension.end(), extension.begin(),
   1013    [](unsigned char c){ return std::tolower(c); });
   1014                valid_exts.push_back(extension);
   1015            }
   1016        }
   1017        float min_width = ImGui::CalcTextSize(".abc").x + 100;
   1018        ext_box_width = std::max(min_width, ImGui::CalcTextSize(max_str.c_str()).x);
   1019    }
   1020
   1021    bool ImGuiFileBrowser::validateFile()
   1022    {
   1023        bool match = false;
   1024
   1025        //If there is an item selected, check if the selected file name (the input filename, in other words) matches the selection.
   1026        if(selected_idx >= 0)
   1027        {
   1028            if(dialog_mode == DialogMode::SELECT)
   1029                match = (filtered_dirs[selected_idx].name == selected_fn);
   1030            else
   1031                match = (filtered_files[selected_idx].name == selected_fn);
   1032        }
   1033
   1034        //If the input filename doesn't match we need to explicitly find the input filename..
   1035        if(!match)
   1036        {
   1037            if(dialog_mode == DialogMode::SELECT)
   1038            {
   1039                for(std::size_t i = 0; i < subdirs.size(); i++)
   1040                {
   1041                    if(subdirs[i].name == selected_fn)
   1042                    {
   1043                        match = true;
   1044                        break;
   1045                    }
   1046                }
   1047
   1048            }
   1049            else
   1050            {
   1051                for(std::size_t i = 0; i < subfiles.size(); i++)
   1052                {
   1053                    if(subfiles[i].name == selected_fn)
   1054                    {
   1055                        match = true;
   1056                        break;
   1057                    }
   1058                }
   1059            }
   1060        }
   1061
   1062        // If file doesn't match, return true on SAVE mode (since file doesn't exist, hence can be saved directly) and return false on other modes (since file doesn't exist so cant open/select)
   1063        if(!match)
   1064            return (dialog_mode == DialogMode::SAVE);
   1065
   1066        // If file matches, return false on SAVE, we need to show a replace file modal
   1067        if(dialog_mode == DialogMode::SAVE)
   1068            return false;
   1069        // Return true on SELECT, no need to validate extensions
   1070        else if(dialog_mode == DialogMode::SELECT)
   1071            return true;
   1072        else
   1073        {
   1074            // If list of extensions has all types, no need to validate.
   1075            for(auto ext : valid_exts)
   1076            {
   1077                if(ext == "*.*")
   1078                    return true;
   1079            }
   1080            int idx = (int)selected_fn.find_last_of('.');
   1081            std::string file_ext = idx == (int)std::string::npos ? "" : selected_fn.substr(idx, selected_fn.length() - idx);
   1082            std::transform(file_ext.begin(), file_ext.end(), file_ext.begin(),
   1083    [](unsigned char c){ return std::tolower(c); });
   1084            return (std::find(valid_exts.begin(), valid_exts.end(), file_ext) != valid_exts.end());
   1085        }
   1086    }
   1087
   1088    ImVec2 ImGuiFileBrowser::getButtonSize(std::string button_text)
   1089    {
   1090        return (ImGui::CalcTextSize(button_text.c_str()) + ImGui::GetStyle().FramePadding * 2.0);
   1091    }
   1092
   1093    void ImGuiFileBrowser::parsePathTabs(std::string path)
   1094    {
   1095        std::string path_element = "";
   1096        std::string root = "";
   1097
   1098        #ifdef OSWIN
   1099        current_dirlist.push_back("Computer");
   1100        #else
   1101        if(path[0] == '/')
   1102            current_dirlist.push_back("/");
   1103        #endif //OSWIN
   1104
   1105        std::istringstream iss(path);
   1106        while(std::getline(iss, path_element, '/'))
   1107        {
   1108            if(!path_element.empty())
   1109                current_dirlist.push_back(path_element);
   1110        }
   1111    }
   1112
   1113    std::string ImGuiFileBrowser::wStringToString(const wchar_t* wchar_arr)
   1114    {
   1115        std::mbstate_t state = std::mbstate_t();
   1116
   1117         //MinGW bug (patched in mingw-w64), wcsrtombs doesn't ignore length parameter when dest = nullptr. Hence the large number.
   1118        size_t len = 1 + std::wcsrtombs(nullptr, &(wchar_arr), 600000, &state);
   1119
   1120        char* char_arr = new char[len];
   1121        std::wcsrtombs(char_arr, &wchar_arr, len, &state);
   1122
   1123        std::string ret_val(char_arr);
   1124
   1125        delete[] char_arr;
   1126        return ret_val;
   1127    }
   1128
   1129    bool ImGuiFileBrowser::alphaSortComparator(const Info& a, const Info& b)
   1130    {
   1131        return a.name < b.name;
   1132    }
   1133
   1134    //Windows Exclusive function
   1135    #ifdef OSWIN
   1136    bool ImGuiFileBrowser::loadWindowsDrives()
   1137    {
   1138        DWORD len = GetLogicalDriveStringsA(0,nullptr);
   1139        char* drives = new char[len];
   1140        if(!GetLogicalDriveStringsA(len,drives))
   1141        {
   1142            delete[] drives;
   1143            return false;
   1144        }
   1145
   1146        clearFileList();
   1147        char* temp = drives;
   1148        for(char *drv = nullptr; *temp != '\0'; temp++)
   1149        {
   1150            drv = temp;
   1151            if(DRIVE_REMOVABLE == GetDriveTypeA(drv))
   1152                subdirs.push_back({"Removable Disk: " + std::string(1,drv[0]), false});
   1153            else if(DRIVE_FIXED == GetDriveTypeA(drv))
   1154                subdirs.push_back({"Local Disk: " + std::string(1,drv[0]), false});
   1155            //Go to nullptr character
   1156            while(*(++temp));
   1157        }
   1158        delete[] drives;
   1159        return true;
   1160    }
   1161    #endif
   1162
   1163    //Unix only
   1164    #ifndef OSWIN
   1165    void ImGuiFileBrowser::initCurrentPath()
   1166    {
   1167        bool path_max_def = false;
   1168
   1169        #ifdef PATH_MAX
   1170        path_max_def = true;
   1171        #endif // PATH_MAX
   1172
   1173        char* buffer = nullptr;
   1174
   1175        //If PATH_MAX is defined deal with memory using new/delete. Else fallback to malloc'ed memory from `realpath()`
   1176        if(path_max_def)
   1177            buffer = new char[PATH_MAX];
   1178
   1179        char* real_path = realpath("./", buffer);
   1180        if (real_path == nullptr)
   1181        {
   1182            current_path = "/";
   1183            current_dirlist.push_back("/");
   1184        }
   1185        else
   1186        {
   1187            current_path = std::string(real_path);
   1188            current_path += "/";
   1189            parsePathTabs(current_path);
   1190        }
   1191
   1192        if(path_max_def)
   1193            delete[] buffer;
   1194        else
   1195            free(real_path);
   1196    }
   1197    #endif // OSWIN
   1198}