#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace hex::plugin::builtin { namespace { template std::vector patternToArray(pl::ptrn::Pattern *pattern){ const auto bytes = pattern->getBytes(); std::vector result; result.resize(bytes.size() / sizeof(T)); for (size_t i = 0; i < result.size(); i++) std::memcpy(&result[i], &bytes[i * sizeof(T)], sizeof(T)); return result; } } namespace { void drawLinePlotVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span arguments) { static std::vector values; auto dataPattern = arguments[0].toPattern(); if (ImPlot::BeginPlot("##plot", ImVec2(400, 250), ImPlotFlags_CanvasOnly)) { ImPlot::SetupAxes("X", "Y", ImPlotAxisFlags_AutoFit, ImPlotAxisFlags_AutoFit); if (shouldReset) { values.clear(); values = sampleData(patternToArray(dataPattern.get()), ImPlot::GetPlotSize().x * 4); } ImPlot::PlotLine("##line", values.data(), values.size()); ImPlot::EndPlot(); } } void drawScatterPlotVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span arguments) { static std::vector xValues, yValues; auto xPattern = arguments[0].toPattern(); auto yPattern = arguments[1].toPattern(); if (ImPlot::BeginPlot("##plot", ImVec2(400, 250), ImPlotFlags_CanvasOnly)) { ImPlot::SetupAxes("X", "Y", ImPlotAxisFlags_AutoFit, ImPlotAxisFlags_AutoFit); if (shouldReset) { xValues.clear(); yValues.clear(); xValues = sampleData(patternToArray(xPattern.get()), ImPlot::GetPlotSize().x * 4); yValues = sampleData(patternToArray(yPattern.get()), ImPlot::GetPlotSize().x * 4); } ImPlot::PlotScatter("##scatter", xValues.data(), yValues.data(), xValues.size()); ImPlot::EndPlot(); } } void drawImageVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span arguments) { static ImGuiExt::Texture texture; static float scale = 1.0F; if (shouldReset) { auto pattern = arguments[0].toPattern(); auto data = pattern->getBytes(); texture = ImGuiExt::Texture(data.data(), data.size()); scale = 200_scaled / texture.getSize().x; } if (texture.isValid()) ImGui::Image(texture, texture.getSize() * scale); if (ImGui::IsWindowHovered()) { auto scrollDelta = ImGui::GetIO().MouseWheel; if (scrollDelta != 0.0F) { scale += scrollDelta * 0.1F; scale = std::clamp(scale, 0.1F, 10.0F); } } } void drawBitmapVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span arguments) { static ImGuiExt::Texture texture; static float scale = 1.0F; if (shouldReset) { auto pattern = arguments[0].toPattern(); auto width = arguments[1].toUnsigned(); auto height = arguments[2].toUnsigned(); auto data = pattern->getBytes(); texture = ImGuiExt::Texture(data.data(), data.size(), width, height); } if (texture.isValid()) ImGui::Image(texture, texture.getSize() * scale); if (ImGui::IsWindowHovered()) { auto scrollDelta = ImGui::GetIO().MouseWheel; if (scrollDelta != 0.0F) { scale += scrollDelta * 0.1F; scale = std::clamp(scale, 0.1F, 10.0F); } } } void drawDisassemblyVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span arguments) { struct Disassembly { u64 address; std::vector bytes; std::string instruction; }; static std::vector disassembly; if (shouldReset) { auto pattern = arguments[0].toPattern(); auto baseAddress = arguments[1].toUnsigned(); auto architecture = arguments[2].toUnsigned(); auto mode = arguments[3].toUnsigned(); disassembly.clear(); csh capstone; if (cs_open(static_cast(architecture), static_cast(mode), &capstone) == CS_ERR_OK) { cs_option(capstone, CS_OPT_SKIPDATA, CS_OPT_ON); auto data = pattern->getBytes(); cs_insn *instructions = nullptr; size_t instructionCount = cs_disasm(capstone, data.data(), data.size(), baseAddress, 0, &instructions); for (size_t i = 0; i < instructionCount; i++) { disassembly.push_back({ instructions[i].address, { instructions[i].bytes, instructions[i].bytes + instructions[i].size }, hex::format("{} {}", instructions[i].mnemonic, instructions[i].op_str) }); } cs_free(instructions, instructionCount); cs_close(&capstone); } } if (ImGui::BeginTable("##disassembly", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY, scaled(ImVec2(0, 300)))) { ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableSetupColumn("hex.builtin.common.address"_lang); ImGui::TableSetupColumn("hex.builtin.common.bytes"_lang); ImGui::TableSetupColumn("hex.builtin.common.instruction"_lang); ImGui::TableHeadersRow(); for (auto &entry : disassembly) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGuiExt::TextFormatted("0x{0:08X}", entry.address); ImGui::TableNextColumn(); std::string bytes; for (auto byte : entry.bytes) bytes += hex::format("{0:02X} ", byte); ImGui::TextUnformatted(bytes.c_str()); ImGui::TableNextColumn(); ImGui::TextUnformatted(entry.instruction.c_str()); } ImGui::EndTable(); } } void draw3DVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span arguments) { auto verticesPattern = arguments[0].toPattern(); auto indicesPattern = arguments[1].toPattern(); static ImGuiExt::Texture texture; static gl::Vector translation; static gl::Vector rotation = { { 1.0F, -1.0F, 0.0F } }; static float scaling = 0.1F; static std::vector vertices, normals; static std::vector indices; static gl::Shader shader; static gl::VertexArray vertexArray; static gl::Buffer vertexBuffer, normalBuffer; static gl::Buffer indexBuffer; { auto dragDelta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Middle); rotation[0] += -dragDelta.y * 0.0075F; rotation[1] += -dragDelta.x * 0.0075F; ImGui::ResetMouseDragDelta(ImGuiMouseButton_Middle); dragDelta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right); translation[0] += -dragDelta.x * 0.1F; translation[1] += -dragDelta.y * 0.1F; ImGui::ResetMouseDragDelta(ImGuiMouseButton_Right); auto scrollDelta = ImGui::GetIO().MouseWheel; scaling += scrollDelta * 0.01F; if (scaling < 0.01F) scaling = 0.01F; } if (shouldReset) { vertices = patternToArray(verticesPattern.get()); indices = patternToArray(indicesPattern.get()); normals.clear(); normals.resize(vertices.size()); for (u32 i = 0; i < normals.size(); i += 9) { auto v1 = gl::Vector({ vertices[i] , vertices[i + 1], vertices[i + 2] }); auto v2 = gl::Vector({ vertices[i + 3], vertices[i + 4], vertices[i + 5] }); auto v3 = gl::Vector({ vertices[i + 6], vertices[i + 7], vertices[i + 8] }); auto normal = ((v2 - v1).cross(v3 - v1)).normalize(); normals[i] = normal[0]; normals[i + 1] = normal[1]; normals[i + 2] = normal[2]; normals[i + 3] = normal[0]; normals[i + 4] = normal[1]; normals[i + 5] = normal[2]; normals[i + 6] = normal[0]; normals[i + 7] = normal[1]; normals[i + 8] = normal[2]; } shader = gl::Shader(romfs::get("shaders/default/vertex.glsl").string(), romfs::get("shaders/default/fragment.glsl").string()); vertexArray = gl::VertexArray(); vertexBuffer = {}; normalBuffer = {}; indexBuffer = {}; vertexArray.bind(); vertexBuffer = gl::Buffer(gl::BufferType::Vertex, vertices); normalBuffer = gl::Buffer(gl::BufferType::Vertex, normals); indexBuffer = gl::Buffer(gl::BufferType::Index, indices); vertexArray.addBuffer(0, vertexBuffer); vertexArray.addBuffer(1, normalBuffer); if (!indices.empty()) vertexArray.addBuffer(2, indexBuffer); vertexBuffer.unbind(); normalBuffer.unbind(); indexBuffer.unbind(); vertexArray.unbind(); } { gl::FrameBuffer frameBuffer; gl::Texture renderTexture(400_scaled, 400_scaled); frameBuffer.attachTexture(renderTexture); frameBuffer.bind(); glEnable(GL_DEPTH_TEST); shader.bind(); shader.setUniform("scale", scaling); shader.setUniform("rotation", rotation); shader.setUniform("translation", translation); vertexArray.bind(); glViewport(0, 0, renderTexture.getWidth(), renderTexture.getHeight()); glClearColor(0.00F, 0.00F, 0.00F, 0.00f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (indices.empty()) vertexBuffer.draw(); else indexBuffer.draw(); vertexArray.unbind(); shader.unbind(); frameBuffer.unbind(); texture = ImGuiExt::Texture(renderTexture.release(), renderTexture.getWidth(), renderTexture.getHeight()); } auto textureSize = texture.getSize(); if (ImGui::BeginTable("##3DVisualizer", 2, ImGuiTableFlags_SizingFixedFit)) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); if (ImGui::BeginChild("##image", textureSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { ImGui::Image(texture, textureSize, ImVec2(0, 1), ImVec2(1, 0)); } ImGui::EndChild(); ImGui::PopStyleVar(); ImGui::TableNextColumn(); ImGui::TextUnformatted("hex.builtin.pl_visualizer.3d.rotation"_lang); ImGui::VSliderFloat("##X", ImVec2(18_scaled, textureSize.y), &rotation.data()[0], 0, std::numbers::pi * 2, "", ImGuiSliderFlags_AlwaysClamp); ImGui::SameLine(); ImGui::VSliderFloat("##Y", ImVec2(18_scaled, textureSize.y), &rotation.data()[1], 0, std::numbers::pi * 2, "", ImGuiSliderFlags_AlwaysClamp); ImGui::SameLine(); ImGui::VSliderFloat("##Z", ImVec2(18_scaled, textureSize.y), &rotation.data()[2], 0, std::numbers::pi * 2, "", ImGuiSliderFlags_AlwaysClamp); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextUnformatted("hex.builtin.pl_visualizer.3d.scale"_lang); ImGui::SameLine(); ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); ImGui::SliderFloat("##Scale", &scaling, 0.0001F, 0.2F, ""); ImGui::PopItemWidth(); for (u8 i = 0; i < 3; i++) { while (rotation.data()[i] > std::numbers::pi * 2) rotation.data()[i] -= std::numbers::pi * 2; while (rotation.data()[i] < 0) rotation.data()[i] += std::numbers::pi * 2; } ImGui::TableNextColumn(); if (ImGui::Button("hex.builtin.common.reset"_lang, ImVec2(ImGui::GetContentRegionAvail().x, 0))) { translation = gl::Vector({ 0.0F, 0.0F, 0.0F }); rotation = gl::Vector({ 0.0F, 0.0F, 0.0F }); scaling = 0.1F; } ImGui::EndTable(); } } void drawSoundVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span arguments) { auto wavePattern = arguments[0].toPattern(); auto channels = arguments[1].toUnsigned(); auto sampleRate = arguments[2].toUnsigned(); static std::vector waveData, sampledData; static ma_device audioDevice; static ma_device_config deviceConfig; static bool shouldStop = false; static u64 index = 0; static TaskHolder resetTask; if (sampleRate == 0) throw std::logic_error(hex::format("Invalid sample rate: {}", sampleRate)); else if (channels == 0) throw std::logic_error(hex::format("Invalid channel count: {}", channels)); if (shouldReset) { waveData.clear(); resetTask = TaskManager::createTask("Visualizing...", TaskManager::NoProgress, [=](Task &) { ma_device_stop(&audioDevice); waveData = patternToArray(wavePattern.get()); sampledData = sampleData(waveData, 300_scaled * 4); index = 0; deviceConfig = ma_device_config_init(ma_device_type_playback); deviceConfig.playback.format = ma_format_s16; deviceConfig.playback.channels = channels; deviceConfig.sampleRate = sampleRate; deviceConfig.pUserData = &waveData; deviceConfig.dataCallback = [](ma_device *device, void *pOutput, const void *, ma_uint32 frameCount) { if (index >= waveData.size()) { index = 0; shouldStop = true; return; } ma_copy_pcm_frames(pOutput, waveData.data() + index, frameCount, device->playback.format, device->playback.channels); index += frameCount; }; ma_device_init(nullptr, &deviceConfig, &audioDevice); }); } ImGui::BeginDisabled(resetTask.isRunning()); ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, ImVec2(0, 0)); if (ImPlot::BeginPlot("##amplitude_plot", scaled(ImVec2(300, 80)), ImPlotFlags_CanvasOnly | ImPlotFlags_NoFrame | ImPlotFlags_NoInputs)) { ImPlot::SetupAxes("##time", "##amplitude", ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_NoMenus, ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_NoMenus); ImPlot::SetupAxesLimits(0, waveData.size(), std::numeric_limits::min(), std::numeric_limits::max(), ImGuiCond_Always); double dragPos = index; if (ImPlot::DragLineX(1, &dragPos, ImGui::GetStyleColorVec4(ImGuiCol_Text))) { if (dragPos < 0) dragPos = 0; if (dragPos >= waveData.size()) dragPos = waveData.size() - 1; index = dragPos; } ImPlot::PlotLine("##audio", sampledData.data(), sampledData.size()); ImPlot::EndPlot(); } ImPlot::PopStyleVar(); { const u64 min = 0, max = waveData.size(); ImGui::PushItemWidth(300_scaled); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); ImGui::SliderScalar("##index", ImGuiDataType_U64, &index, &min, &max, ""); ImGui::PopStyleVar(); ImGui::PopItemWidth(); } if (shouldStop) { shouldStop = false; ma_device_stop(&audioDevice); } bool playing = ma_device_is_started(&audioDevice); if (ImGuiExt::IconButton(playing ? ICON_VS_DEBUG_PAUSE : ICON_VS_PLAY, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_ToolbarGreen))) { if (playing) ma_device_stop(&audioDevice); else ma_device_start(&audioDevice); } ImGui::SameLine(); if (ImGuiExt::IconButton(ICON_VS_DEBUG_STOP, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_ToolbarRed))) { index = 0; ma_device_stop(&audioDevice); } ImGui::EndDisabled(); ImGui::SameLine(); if (resetTask.isRunning()) ImGuiExt::TextSpinner(""); else ImGuiExt::TextFormatted("{:02d}:{:02d} / {:02d}:{:02d}", (index / sampleRate) / 60, (index / sampleRate) % 60, (waveData.size() / sampleRate) / 60, (waveData.size() / sampleRate) % 60); } void drawChunkBasedEntropyVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span arguments) { // Variable used to store the result to avoid having to recalculate the result at each frame static DiagramChunkBasedEntropyAnalysis analyzer; // Compute data if (shouldReset) { auto pattern = arguments[0].toPattern(); auto chunkSize = arguments[1].toUnsigned(); analyzer.process(pattern->getBytes(), chunkSize); } // Show results analyzer.draw(ImVec2(400, 250), ImPlotFlags_CanvasOnly); } void drawHexVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span arguments) { static ui::HexEditor editor; static std::unique_ptr dataProvider; if (shouldReset) { auto pattern = arguments[0].toPattern(); std::vector data; dataProvider = std::make_unique(); try { data = pattern->getBytes(); } catch (const std::exception &) { dataProvider->resize(0); throw; } dataProvider->resize(data.size()); dataProvider->writeRaw(0x00, data.data(), data.size()); dataProvider->setReadOnly(true); editor.setProvider(dataProvider.get()); } if (ImGui::BeginChild("##editor", scaled(ImVec2(600, 400)), false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { editor.draw(); ImGui::EndChild(); } } void drawCoordinateVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span arguments) { static ImVec2 coordinate; static double latitude, longitude; static std::string address; static std::mutex addressMutex; static TaskHolder addressTask; static auto mapTexture = ImGuiExt::Texture(romfs::get("assets/common/map.jpg").span()); static ImVec2 mapSize = scaled(ImVec2(500, 500 / mapTexture.getAspectRatio())); if (shouldReset) { std::scoped_lock lock(addressMutex); address.clear(); latitude = arguments[0].toFloatingPoint(); longitude = arguments[1].toFloatingPoint(); // Convert latitude and longitude to X/Y coordinates on the image coordinate.x = float((longitude + 180) / 360 * mapSize.x); coordinate.y = float((-latitude + 90) / 180 * mapSize.y); } const auto startPos = ImGui::GetWindowPos() + ImGui::GetCursorPos(); // Draw background image ImGui::Image(mapTexture, mapSize); // Draw Longitude / Latitude text below image ImGui::PushTextWrapPos(startPos.x + mapSize.x); ImGuiExt::TextFormattedWrapped("{}: {:.0f}° {:.0f}' {:.4f}\" {} | {}: {:.0f}° {:.0f}' {:.4f}\" {}", "hex.builtin.pl_visualizer.coordinates.latitude"_lang, std::floor(std::abs(latitude)), std::floor(std::abs(latitude - std::floor(latitude)) * 60), (std::abs(latitude - std::floor(latitude)) * 60 - std::floor(std::abs(latitude - std::floor(latitude)) * 60)) * 60, latitude >= 0 ? "N" : "S", "hex.builtin.pl_visualizer.coordinates.longitude"_lang, std::floor(std::abs(longitude)), std::floor(std::abs(longitude - std::floor(longitude)) * 60), (std::abs(longitude - std::floor(longitude)) * 60 - std::floor(std::abs(longitude - std::floor(longitude)) * 60)) * 60, longitude >= 0 ? "E" : "W" ); ImGui::PopTextWrapPos(); if (addressTask.isRunning()) { ImGuiExt::TextSpinner("hex.builtin.pl_visualizer.coordinates.querying"_lang); } else if (address.empty()) { if (ImGuiExt::DimmedButton("hex.builtin.pl_visualizer.coordinates.query"_lang)) { addressTask = TaskManager::createBackgroundTask("hex.builtin.pl_visualizer.coordinates.querying"_lang, [lat = latitude, lon = longitude](auto &) { constexpr static auto ApiURL = "https://geocode.maps.co/reverse?lat={}&lon={}&format=jsonv2"; HttpRequest request("GET", hex::format(ApiURL, lat, lon)); auto response = request.execute().get(); if (!response.isSuccess()) return; try { auto json = nlohmann::json::parse(response.getData()); auto jsonAddr = json["address"]; std::scoped_lock lock(addressMutex); if (jsonAddr.contains("village")) { address = hex::format("{} {}, {} {}", jsonAddr["village"].get(), jsonAddr["county"].get(), jsonAddr["state"].get(), jsonAddr["country"].get()); } else if (jsonAddr.contains("city")) { address = hex::format("{}, {} {}, {} {}", jsonAddr["road"].get(), jsonAddr["quarter"].get(), jsonAddr["city"].get(), jsonAddr["state"].get(), jsonAddr["country"].get()); } } catch (std::exception &) { address = std::string("hex.builtin.pl_visualizer.coordinates.querying_no_address"_lang); } }); } } else { ImGui::PushTextWrapPos(startPos.x + mapSize.x); ImGuiExt::TextFormattedWrapped("{}", address); ImGui::PopTextWrapPos(); } // Draw crosshair pointing to the coordinates { constexpr static u32 CrossHairColor = 0xFF00D0D0; constexpr static u32 BorderColor = 0xFF000000; auto drawList = ImGui::GetWindowDrawList(); drawList->AddLine(startPos + ImVec2(coordinate.x, 0), startPos + ImVec2(coordinate.x, mapSize.y), CrossHairColor, 2_scaled); drawList->AddLine(startPos + ImVec2(0, coordinate.y), startPos + ImVec2(mapSize.x, coordinate.y), CrossHairColor, 2_scaled); drawList->AddCircleFilled(startPos + coordinate, 5, CrossHairColor); drawList->AddCircle(startPos + coordinate, 5, BorderColor); } } void drawTimestampVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool, std::span arguments) { time_t timestamp = arguments[0].toUnsigned(); auto tm = fmt::gmtime(timestamp); auto date = std::chrono::year_month_day(std::chrono::year(tm.tm_year + 1900), std::chrono::month(tm.tm_mon + 1), std::chrono::day(tm.tm_mday)); auto lastMonthDay = std::chrono::year_month_day_last(date.year(), date.month() / std::chrono::last); auto firstWeekDay = std::chrono::weekday(std::chrono::year_month_day(date.year(), date.month(), std::chrono::day(1))); const auto scale = 1_scaled * (ImHexApi::System::getFontSize() / ImHexApi::System::DefaultFontSize); // Draw calendar if (ImGui::BeginTable("##month_table", 2)) { ImGui::TableNextRow(); ImGui::TableNextColumn(); // Draw centered month name and year ImGuiExt::TextFormattedCenteredHorizontal("{:%B %Y}", tm); if (ImGui::BeginTable("##days_table", 7, ImGuiTableFlags_Borders | ImGuiTableFlags_NoHostExtendX, ImVec2(160, 120) * scale)) { constexpr static auto ColumnFlags = ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoReorder | ImGuiTableColumnFlags_NoHide; ImGui::TableSetupColumn("M", ColumnFlags); ImGui::TableSetupColumn("T", ColumnFlags); ImGui::TableSetupColumn("W", ColumnFlags); ImGui::TableSetupColumn("T", ColumnFlags); ImGui::TableSetupColumn("F", ColumnFlags); ImGui::TableSetupColumn("S", ColumnFlags); ImGui::TableSetupColumn("S", ColumnFlags); ImGui::TableHeadersRow(); ImGui::TableNextRow(); // Skip days before the first day of the month for (u8 i = 0; i < firstWeekDay.c_encoding() - 1; ++i) ImGui::TableNextColumn(); // Draw days for (u8 i = 1; i <= u32(lastMonthDay.day()); ++i) { ImGui::TableNextColumn(); ImGuiExt::TextFormatted("{:02}", i); if (std::chrono::day(i) == date.day()) ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ImGuiExt::GetCustomColorU32(ImGuiCustomCol_ToolbarRed)); if (std::chrono::weekday(std::chrono::year_month_day(date.year(), date.month(), std::chrono::day(i))) == std::chrono::Sunday) ImGui::TableNextRow(); } ImGui::EndTable(); } ImGui::TableNextColumn(); // Draw analog clock const auto size = ImVec2(120, 120) * scale; if (ImGui::BeginChild("##clock", size + ImVec2(0, ImGui::GetTextLineHeightWithSpacing()))) { // Draw centered digital hour, minute and seconds ImGuiExt::TextFormattedCenteredHorizontal("{:%H:%M:%S}", tm); auto drawList = ImGui::GetWindowDrawList(); const auto center = ImGui::GetWindowPos() + ImVec2(0, ImGui::GetTextLineHeightWithSpacing()) + size / 2; // Draw clock face drawList->AddCircle(center, size.x / 2, ImGui::GetColorU32(ImGuiCol_TextDisabled), 0); auto sectionPos = [](float i) { return ImVec2(std::sin(-i * 30.0F * std::numbers::pi / 180.0F + std::numbers::pi / 2), std::cos(-i * 30.0F * std::numbers::pi / 180.0F + std::numbers::pi / 2)); }; // Draw clock sections and numbers for (u8 i = 0; i < 12; ++i) { auto text = hex::format("{}", (((i + 2) % 12) + 1)); drawList->AddLine(center + sectionPos(i) * size / 2.2, center + sectionPos(i) * size / 2, ImGui::GetColorU32(ImGuiCol_TextDisabled), 1_scaled); drawList->AddText(center + sectionPos(i) * size / 3 - ImGui::CalcTextSize(text.c_str()) / 2, ImGui::GetColorU32(ImGuiCol_Text), text.c_str()); } // Draw hour hand drawList->AddLine(center, center + sectionPos((tm.tm_hour + 9) % 12 + float(tm.tm_min) / 60.0) * size / 3.5, ImGui::GetColorU32(ImGuiCol_TextDisabled), 3_scaled); // Draw minute hand drawList->AddLine(center, center + sectionPos((float(tm.tm_min) / 5.0F) - 3) * size / 2.5, ImGui::GetColorU32(ImGuiCol_TextDisabled), 3_scaled); // Draw second hand drawList->AddLine(center, center + sectionPos((float(tm.tm_sec) / 5.0F) - 3) * size / 2.5, ImGuiExt::GetCustomColorU32(ImGuiCustomCol_ToolbarRed), 2_scaled); } ImGui::EndChild(); ImGui::EndTable(); } } } void registerPatternLanguageVisualizers() { using ParamCount = pl::api::FunctionParameterCount; ContentRegistry::PatternLanguage::addVisualizer("line_plot", drawLinePlotVisualizer, ParamCount::exactly(1)); ContentRegistry::PatternLanguage::addVisualizer("scatter_plot", drawScatterPlotVisualizer, ParamCount::exactly(2)); ContentRegistry::PatternLanguage::addVisualizer("image", drawImageVisualizer, ParamCount::exactly(1)); ContentRegistry::PatternLanguage::addVisualizer("bitmap", drawBitmapVisualizer, ParamCount::exactly(3)); ContentRegistry::PatternLanguage::addVisualizer("disassembler", drawDisassemblyVisualizer, ParamCount::exactly(4)); ContentRegistry::PatternLanguage::addVisualizer("3d", draw3DVisualizer, ParamCount::exactly(2)); ContentRegistry::PatternLanguage::addVisualizer("sound", drawSoundVisualizer, ParamCount::exactly(3)); ContentRegistry::PatternLanguage::addVisualizer("chunk_entropy", drawChunkBasedEntropyVisualizer, ParamCount::exactly(2)); ContentRegistry::PatternLanguage::addVisualizer("hex_viewer", drawHexVisualizer, ParamCount::exactly(1)); ContentRegistry::PatternLanguage::addVisualizer("coordinates", drawCoordinateVisualizer, ParamCount::exactly(2)); ContentRegistry::PatternLanguage::addVisualizer("timestamp", drawTimestampVisualizer, ParamCount::exactly(1)); } }