diff --git a/simulation/halsim_gui/src/main/native/cpp/AddressableLEDGui.cpp b/simulation/halsim_gui/src/main/native/cpp/AddressableLEDGui.cpp index 554da2c894..606eac5cc6 100644 --- a/simulation/halsim_gui/src/main/native/cpp/AddressableLEDGui.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/AddressableLEDGui.cpp @@ -23,8 +23,14 @@ using namespace halsimgui; -static constexpr int kDefaultColumns = 10; -static std::vector numColumns; +namespace { +struct LEDDisplaySettings { + int numColumns = 10; + LEDConfig config; +}; +} // namespace + +static std::vector displaySettings; // read/write columns setting to ini file static void* AddressableLEDReadOpen(ImGuiContext* ctx, @@ -33,15 +39,15 @@ static void* AddressableLEDReadOpen(ImGuiContext* ctx, int num; if (wpi::StringRef{name}.getAsInteger(10, num)) return nullptr; if (num < 0) return nullptr; - if (num >= static_cast(numColumns.size())) - numColumns.resize(num + 1, kDefaultColumns); - return &numColumns[num]; + if (num >= static_cast(displaySettings.size())) + displaySettings.resize(num + 1); + return &displaySettings[num]; } static void AddressableLEDReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* lineStr) { - int* cols = static_cast(entry); + auto* settings = static_cast(entry); // format: columns=# wpi::StringRef line{lineStr}; auto [name, value] = line.split('='); @@ -50,24 +56,41 @@ static void AddressableLEDReadLine(ImGuiContext* ctx, if (name == "columns") { int num; if (value.getAsInteger(10, num)) return; - *cols = num; + settings->numColumns = num; + } else if (name == "serpentine") { + int num; + if (value.getAsInteger(10, num)) return; + settings->config.serpentine = num != 0; + } else if (name == "order") { + int num; + if (value.getAsInteger(10, num)) return; + settings->config.order = static_cast(num); + } else if (name == "start") { + int num; + if (value.getAsInteger(10, num)) return; + settings->config.start = static_cast(num); } } static void AddressableLEDWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* out_buf) { - for (size_t i = 0; i < numColumns.size(); ++i) { - out_buf->appendf("[AddressableLED][%d]\ncolumns=%d\n\n", - static_cast(i), numColumns[i]); + for (size_t i = 0; i < displaySettings.size(); ++i) { + out_buf->appendf( + "[AddressableLED][%d]\ncolumns=%d\nserpentine=%d\norder=%d\n" + "start=%d\n\n", + static_cast(i), displaySettings[i].numColumns, + displaySettings[i].config.serpentine ? 1 : 0, + static_cast(displaySettings[i].config.order), + static_cast(displaySettings[i].config.start)); } } static void DisplayAddressableLEDs() { bool hasAny = false; static const int numLED = HAL_GetNumAddressableLEDs(); - if (numLED > static_cast(numColumns.size())) - numColumns.resize(numLED, kDefaultColumns); + if (numLED > static_cast(displaySettings.size())) + displaySettings.resize(numLED); for (int i = 0; i < numLED; ++i) { if (!HALSIM_GetAddressableLEDInitialized(i)) continue; @@ -82,8 +105,22 @@ static void DisplayAddressableLEDs() { ImGui::PushItemWidth(ImGui::GetFontSize() * 6); ImGui::LabelText("Length", "%d", length); ImGui::LabelText("Running", "%s", running ? "Yes" : "No"); - ImGui::InputInt("Columns", &numColumns[i]); - if (numColumns[i] < 1) numColumns[i] = 1; + ImGui::InputInt("Columns", &displaySettings[i].numColumns); + { + static const char* options[] = {"Row Major", "Column Major"}; + int val = displaySettings[i].config.order; + if (ImGui::Combo("Order", &val, options, 2)) + displaySettings[i].config.order = static_cast(val); + } + { + static const char* options[] = {"Upper Left", "Lower Left", "Upper Right", + "Lower Right"}; + int val = displaySettings[i].config.start; + if (ImGui::Combo("Start", &val, options, 4)) + displaySettings[i].config.start = static_cast(val); + } + ImGui::Checkbox("Serpentine", &displaySettings[i].config.serpentine); + if (displaySettings[i].numColumns < 1) displaySettings[i].numColumns = 1; ImGui::PopItemWidth(); // show as LED indicators @@ -100,7 +137,8 @@ static void DisplayAddressableLEDs() { } } - DrawLEDs(values, length, numColumns[i], colors); + DrawLEDs(values, length, displaySettings[i].numColumns, colors, 0, 0, + displaySettings[i].config); } if (!hasAny) ImGui::Text("No addressable LEDs"); } diff --git a/simulation/halsim_gui/src/main/native/cpp/ExtraGuiWidgets.cpp b/simulation/halsim_gui/src/main/native/cpp/ExtraGuiWidgets.cpp index a698d74d2e..3b1ba2825a 100644 --- a/simulation/halsim_gui/src/main/native/cpp/ExtraGuiWidgets.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/ExtraGuiWidgets.cpp @@ -10,30 +10,87 @@ namespace halsimgui { void DrawLEDs(const int* values, int numValues, int cols, const ImU32* colors, - float size, float spacing) { - if (numValues == 0) return; + float size, float spacing, const LEDConfig& config) { + if (numValues == 0 || cols < 1) return; if (size == 0) size = ImGui::GetFontSize() / 2.0; if (spacing == 0) spacing = ImGui::GetFontSize() / 3.0; + int rows = (numValues + cols - 1) / cols; + float inc = size + spacing; + ImDrawList* drawList = ImGui::GetWindowDrawList(); const ImVec2 p = ImGui::GetCursorScreenPos(); - float x = p.x + size / 2, y = p.y + size / 2; - int rows = 1; - for (int i = 0; i < numValues; ++i) { - if (i >= (rows * cols)) { - ++rows; - x = p.x + size / 2; - y += size + spacing; - } - if (values[i] > 0) - drawList->AddRectFilled(ImVec2(x, y), ImVec2(x + size, y + size), - colors[values[i] - 1]); - else if (values[i] < 0) - drawList->AddRect(ImVec2(x, y), ImVec2(x + size, y + size), - colors[-values[i] - 1], 0.0f, 0, 1.0); - x += size + spacing; + + float ystart, yinc; + if (config.start & 1) { + // lower + ystart = p.y + size / 2 + inc * (rows - 1); + yinc = -inc; + } else { + // upper + ystart = p.y + size / 2; + yinc = inc; } - ImGui::Dummy(ImVec2((size + spacing) * cols, (size + spacing) * rows)); + + float xstart, xinc; + if (config.start & 2) { + // right + xstart = p.x + size / 2 + inc * (cols - 1); + xinc = -inc; + } else { + // left + xstart = p.x + size / 2; + xinc = inc; + } + + float x = xstart, y = ystart; + if (config.order == LEDConfig::RowMajor) { + // row major + int row = 1; + for (int i = 0; i < numValues; ++i) { + if (i >= (row * cols)) { + ++row; + if (config.serpentine) { + x -= xinc; + xinc = -xinc; + } else { + x = xstart; + } + y += yinc; + } + if (values[i] > 0) + drawList->AddRectFilled(ImVec2(x, y), ImVec2(x + size, y + size), + colors[values[i] - 1]); + else if (values[i] < 0) + drawList->AddRect(ImVec2(x, y), ImVec2(x + size, y + size), + colors[-values[i] - 1], 0.0f, 0, 1.0); + x += xinc; + } + } else { + // column major + int col = 1; + for (int i = 0; i < numValues; ++i) { + if (i >= (col * rows)) { + ++col; + if (config.serpentine) { + y -= yinc; + yinc = -yinc; + } else { + y = ystart; + } + x += xinc; + } + if (values[i] > 0) + drawList->AddRectFilled(ImVec2(x, y), ImVec2(x + size, y + size), + colors[values[i] - 1]); + else if (values[i] < 0) + drawList->AddRect(ImVec2(x, y), ImVec2(x + size, y + size), + colors[-values[i] - 1], 0.0f, 0, 1.0); + y += yinc; + } + } + + ImGui::Dummy(ImVec2(inc * cols, inc * rows)); } } // namespace halsimgui diff --git a/simulation/halsim_gui/src/main/native/include/ExtraGuiWidgets.h b/simulation/halsim_gui/src/main/native/include/ExtraGuiWidgets.h index 99764cd67c..7374cac95b 100644 --- a/simulation/halsim_gui/src/main/native/include/ExtraGuiWidgets.h +++ b/simulation/halsim_gui/src/main/native/include/ExtraGuiWidgets.h @@ -11,6 +11,33 @@ namespace halsimgui { +/** + * DrawLEDs() configuration for 2D arrays. + */ +struct LEDConfig { + /** + * Whether the major order is serpentined (e.g. the first row goes left to + * right, the second row right to left, the third row left to right, and so + * on). + */ + bool serpentine = false; + + /** + * The input array order (row-major or column-major). + */ + enum Order { RowMajor = 0, ColumnMajor } order = RowMajor; + + /** + * The starting location of the array (0 location). + */ + enum Start { + UpperLeft = 0, + LowerLeft, + UpperRight, + LowerRight + } start = UpperLeft; +}; + /** * Draw a 2D array of LEDs. * @@ -27,8 +54,10 @@ namespace halsimgui { * if 0, defaults to 1/2 of font size * @param spacing spacing between each LED (both horizontal and vertical); * if 0, defaults to 1/3 of font size + * @param config 2D array configuration */ void DrawLEDs(const int* values, int numValues, int cols, const ImU32* colors, - float size = 0.0f, float spacing = 0.0f); + float size = 0.0f, float spacing = 0.0f, + const LEDConfig& config = LEDConfig{}); } // namespace halsimgui