#include #include #include #include #include #include #include #define GLAD_GL_IMPLEMENTATION #include #define GLFW_INCLUDE_NONE #include #define NK_IMPLEMENTATION #include "nuklear.h" #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include "mp.h" #include "continuouscolorscale.h" #include "divergentcolorscale.h" #include "numericrange.h" #include "scatterplot.h" #include "voronoisplat.h" #include "barchart.h" #include "colormap.h" // #include "transitioncontrol.h" #include "projectionhistory.h" #include "manipulationhandler.h" #include "mapscalehandler.h" // #include "selectionhandler.h" #include "brushinghandler.h" static const int RNG_SEED = 123; static const int WINDOW_WIDTH = 1200; static const int WINDOW_HEIGHT = 800; class Main { public: Main(Main const&) = delete; void operator=(Main const&) = delete; static Main &instance() { static Main instance; return instance; } bool saveData() const { bool ret = true; if (m_cp.n_elem > 0 && m_indicesSavePath.size() > 0) { ret = ret && m_cp.save(m_cpSavePath, arma::raw_ascii); } if (m_cpIndices.n_elem > 0 && m_cpSavePath.size() > 0) { ret = ret && m_cpIndices.save(m_indicesSavePath, arma::raw_ascii); } return ret; } bool loadDataset(const std::string &path) { return m_X.load(path, arma::raw_ascii); } void setIndicesSavePath(const std::string &path) { m_indicesSavePath = path; } void setCPSavePath(const std::string &path) { m_cpSavePath = path; } arma::mat X() const { return m_X; } void setSelectRPs() { // cpPlot->setAcceptedMouseButtons(Qt::NoButton); // cpPlot->setAcceptHoverEvents(false); // rpPlot->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); // rpPlot->setAcceptHoverEvents(true); } void setSelectCPs() { // rpPlot->setAcceptedMouseButtons(Qt::NoButton); // rpPlot->setAcceptHoverEvents(false); // cpPlot->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); // cpPlot->setAcceptHoverEvents(true); } enum ColorScaleType { ColorScaleCategorical, ColorScaleContinuous, ColorScaleDivergent, ColorScaleRainbow }; void setCPColorScale(ColorScaleType colorScaleType) { float min = 0.0f; float max = 1.0f; if (colorScaleCPs) { min = colorScaleCPs->min(); max = colorScaleCPs->max(); } colorScaleCPs = getColorScale(colorScaleType); colorScaleCPs->setExtents(min, max); cpPlot->setColorScale(colorScaleCPs); cpBarChart->setColorScale(colorScaleCPs); cpColormap->setColorScale(colorScaleCPs); // bundlePlot->setColorScale(colorScaleCPs); } void setRPColorScale(ColorScaleType colorScaleType) { float min = 0.0f; float max = 1.0f; // TODO: if weird behaviors are found, check this (was CPs) if (colorScaleRPs) { min = colorScaleRPs->min(); max = colorScaleRPs->max(); } colorScaleRPs = getColorScale(colorScaleType); colorScaleRPs->setExtents(min, max); rpPlot->setColorScale(colorScaleRPs); splat->setColorScale(colorScaleRPs); rpBarChart->setColorScale(colorScaleRPs); rpColormap->setColorScale(colorScaleRPs); } // Pointers to visual components whose values are set in the main() function // after components are instantiated by the QtQuick engine BarChart *cpBarChart, *rpBarChart; Colormap *cpColormap, *rpColormap; Scatterplot *cpPlot, *rpPlot; VoronoiSplat *splat; // LinePlot *bundlePlot; // Color scales in use std::shared_ptr colorScaleCPs, colorScaleRPs; // Object that controls manipulation history ProjectionHistory *projectionHistory; void undoManipulation() { projectionHistory->undo(); } void resetManipulation() { projectionHistory->reset(); } enum ObserverType { ObserverCurrent = ProjectionHistory::ObserverCurrent, ObserverDiffPrevious = ProjectionHistory::ObserverDiffPrevious, ObserverDiffFirst = ProjectionHistory::ObserverDiffFirst }; bool setObserverType(ObserverType observerType) { switch (observerType) { case ObserverCurrent: return projectionHistory->setType(ProjectionHistory::ObserverCurrent); case ObserverDiffPrevious: return projectionHistory->setType(ProjectionHistory::ObserverDiffPrevious); case ObserverDiffFirst: return projectionHistory->setType(ProjectionHistory::ObserverDiffFirst); } return false; } void setCPIndices(const arma::uvec &indices) { m_cpIndices = indices; m_rpIndices.set_size(m_X.n_rows - m_cpIndices.n_elem); NumericRange allIndices(0, m_X.n_rows); std::set_symmetric_difference(allIndices.cbegin(), allIndices.cend(), m_cpIndices.cbegin(), m_cpIndices.cend(), m_rpIndices.begin()); } void setCP(const arma::mat &cp) { if (cp.n_cols != 2 || cp.n_rows != m_cpIndices.n_elem) { return; } m_cp = cp; } void updateMap(const arma::mat &Y) { cpPlot->setXY(Y.rows(m_cpIndices)); const arma::mat ®ularPoints = Y.rows(m_rpIndices); rpPlot->setXY(regularPoints); splat->setSites(regularPoints); } private: Main() : cpPlot(0) , rpPlot(0) , splat(0) , cpBarChart(0) , rpBarChart(0) , cpColormap(0) , rpColormap(0) // , bundlePlot(0) , projectionHistory(0) { } std::shared_ptr getColorScale(ColorScaleType colorScaleType) { switch (colorScaleType) { case ColorScaleCategorical: return std::shared_ptr(new ColorScale{ // QColor("#1f77b4"), QColor("#ff7f0e"), QColor("#2ca02c"), Color(31, 119, 180), Color(255, 127, 14), Color(44, 160, 44), // QColor("#d62728"), QColor("#9467bd"), QColor("#8c564b"), Color(214, 39, 40), Color(148, 103, 189), Color(140, 86, 75), // QColor("#e377c2"), QColor("#17becf"), QColor("#7f7f7f"), Color(227, 119, 194), Color(23, 190, 207), Color(127, 127, 127), }); case ColorScaleContinuous: return std::shared_ptr( ContinuousColorScale::builtin( ContinuousColorScale::HeatedObjects, nullptr)); case ColorScaleDivergent: return std::shared_ptr( DivergentColorScale::builtin( DivergentColorScale::RedGrayBlue, nullptr)); case ColorScaleRainbow: // fall-through default: return std::shared_ptr( ContinuousColorScale::builtin( ContinuousColorScale::Rainbow, nullptr)); } } arma::mat m_X, m_cp; arma::uvec m_cpIndices, m_rpIndices; std::string m_indicesSavePath, m_cpSavePath; }; arma::uvec extractCPs(const arma::mat &X) { int numCPs = (int) (3 * sqrt(X.n_rows)); arma::uvec indices(X.n_rows); std::iota(indices.begin(), indices.end(), 0); indices = arma::shuffle(indices); return indices.subvec(0, numCPs-1); } arma::mat standardize(const arma::mat &X) { arma::mat stdX = X; for (arma::uword j = 0; j < X.n_cols; j++) { double sd = arma::stddev(stdX.col(j)); double mean = arma::mean(stdX.col(j)); stdX.col(j) = (stdX.col(j) - mean) / sd; } return stdX; } // GLFW callbacks static void glfw_errorCallback(int err, const char *description) { std::cerr << "glfw: Error: " << err << ": " << description << std::endl; } static void text_input(GLFWwindow *win, unsigned int codepoint) { nk_input_unicode((struct nk_context *) glfwGetWindowUserPointer(win), codepoint); } static void scroll_input(GLFWwindow *win, double _, double yoff) { (void) _; nk_input_scroll((struct nk_context *) glfwGetWindowUserPointer(win), nk_vec2(0, (float) yoff)); } int main(int argc, char *argv[]) { static GLFWwindow *win; int width = 0, height = 0; int display_width = 0, display_height = 0; // GUI // struct device device; // struct nk_font_atlas atlas; // struct media media; struct nk_context ctx; NK_UNUSED(argc); NK_UNUSED(argv); glfwSetErrorCallback(glfw_errorCallback); if (!glfwInit()) { std::cerr << "glfw: init failed" << std::endl; exit(1); } glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); #ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); #endif glfwWindowHint(GLFW_SCALE_TO_MONITOR, GL_TRUE); glfwWindowHint(GLFW_RED_BITS, 8); glfwWindowHint(GLFW_GREEN_BITS, 8); glfwWindowHint(GLFW_BLUE_BITS, 8); glfwWindowHint(GLFW_ALPHA_BITS, 8); glfwWindowHint(GLFW_SAMPLES, 8); win = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "pm", NULL, NULL); if (!win) { std::cerr << "glfw: failed to create window" << std::endl; glfwTerminate(); exit(1); } glfwMakeContextCurrent(win); glfwSetWindowUserPointer(win, &ctx); glfwSetCharCallback(win, text_input); glfwSetScrollCallback(win, scroll_input); glfwGetWindowSize(win, &width, &height); glfwGetFramebufferSize(win, &display_width, &display_height); GLFWmonitor* primary = glfwGetPrimaryMonitor(); float xscale, yscale; glfwGetMonitorContentScale(primary, &xscale, &yscale); std::cout << "glfw: Detected scaling: " << xscale << "x" << yscale << std::endl; const GLFWvidmode * mode = glfwGetVideoMode(primary); std::cout << "glfw: Detected resolution: " << mode->width << "x" << mode->height << std::endl; if (!gladLoadGL(glfwGetProcAddress)) { std::cerr << "GLAD load failed" << std::endl; glfwTerminate(); exit(1); } glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); Main &m = Main::instance(); if (!m.loadDataset("wdbc-std.tbl")) { std::cerr << "Could not load data." << std::endl; return 1; } const arma::mat &X = standardize(m.X()); arma::arma_rng::set_seed(RNG_SEED); arma::uvec cpIndices; arma::mat Ys; // Load/generate control point indices cpIndices = extractCPs(X); // Load/generate control points Ys.set_size(cpIndices.n_elem, 2); Ys.randn(); mp::forceScheme(mp::dist(X.rows(cpIndices)), Ys); if (cpIndices.n_elem != Ys.n_rows) { std::cerr << "The number of CP indices and the CP map do not match." << std::endl; return 1; } // TODO: maybe put this inside m->setCP() method; for now, will be outside // for more flexibility Ys = arma::normalise(Ys); // Sort indices (some operations become easier later) arma::uvec cpSortedIndices = arma::sort_index(cpIndices); cpIndices = cpIndices(cpSortedIndices); Ys = Ys.rows(cpSortedIndices); m.setCPIndices(cpIndices); m.setCP(Ys); // Keeps track of what happened to the visualization ProjectionHistory history(X, cpIndices); // Visual components Scatterplot cpPlot, rpPlot; VoronoiSplat splat; Colormap cpColormap, rpColormap; BarChart cpBarChart, rpBarChart; m.cpPlot = &cpPlot; m.rpPlot = &rpPlot; m.splat = &splat; auto setCP = std::bind(&Main::setCP, &m, std::placeholders::_1); cpPlot.xyChanged.connect(setCP); cpPlot.xyInteractivelyChanged.connect(setCP); // Keep both scatterplots and the splat scaled equally and // relative to the full plot MapScaleHandler mapScaleHandler; mapScaleHandler.scaleChanged.connect( std::bind(&Scatterplot::setScale, &cpPlot, std::placeholders::_1, std::placeholders::_2)); mapScaleHandler.scaleChanged.connect( std::bind(&Scatterplot::setScale, &rpPlot, std::placeholders::_1, std::placeholders::_2)); mapScaleHandler.scaleChanged.connect( std::bind(&VoronoiSplat::setScale, &splat, std::placeholders::_1, std::placeholders::_2)); history.currentMapChanged.connect( std::bind(&MapScaleHandler::scaleToMap, &mapScaleHandler, std::placeholders::_1)); // Update projection as the cp are modified (either directly in // the manipulationHandler object or interactively in cpPlot ManipulationHandler manipulationHandler(X, cpIndices); cpPlot.xyInteractivelyChanged.connect( std::bind(&ManipulationHandler::setCP, &manipulationHandler, std::placeholders::_1)); // Update history whenever a new projection is computed... manipulationHandler.mapChanged.connect( std::bind(&ProjectionHistory::addMap, &history, std::placeholders::_1)); // ... and update visual components whenever the history changes history.currentMapChanged.connect( std::bind(&Main::updateMap, &m, std::placeholders::_1)); // Linking between selections /* SelectionHandler cpSelectionHandler(cpIndices.n_elem); connect(m->cpPlot, &Scatterplot::selectionInteractivelyChanged, &cpSelectionHandler, &SelectionHandler::setSelection); connect(m->cpBarChart, &BarChart::selectionInteractivelyChanged, &cpSelectionHandler, &SelectionHandler::setSelection); connect(&cpSelectionHandler, &SelectionHandler::selectionChanged, m->cpPlot, &Scatterplot::setSelection); connect(&cpSelectionHandler, &SelectionHandler::selectionChanged, m->cpBarChart, &BarChart::setSelection); connect(&cpSelectionHandler, &SelectionHandler::selectionChanged, [m](const std::vector &cpSelection) { // given some CPs, see unexpected RPs *influenced by* them std::vector selectedCPIndices; for (int i = 0; i < cpSelection.size(); i++) { if (cpSelection[i]) { selectedCPIndices.push_back(i); } } if (selectedCPIndices.empty()) { return; } arma::uvec selectedCPs(selectedCPIndices.size()); std::copy(selectedCPIndices.begin(), selectedCPIndices.end(), selectedCPs.begin()); // Only the 10% largest values, filtered by the selected RPs const arma::mat &unreliability = m->projectionHistory->unreliability(); arma::uvec indicesLargest = arma::sort_index(unreliability.cols(selectedCPs), "descending"); arma::uword numLargest = indicesLargest.n_elem * 0.01f; indicesLargest = indicesLargest.subvec(0, numLargest-1); // m->bundlePlot->setValues(unreliability(indicesLargest)); const arma::uvec &cpIndices = m->projectionHistory->cpIndices(); arma::uvec CPs = cpIndices(selectedCPs(indicesLargest / unreliability.n_rows)); const arma::uvec &rpIndices = m->projectionHistory->rpIndices(); arma::uvec RPs = indicesLargest; RPs.transform([&unreliability](arma::uword v) { return v % unreliability.n_rows; }); RPs = rpIndices(RPs); arma::uvec indices(CPs.n_elem + RPs.n_elem); for (arma::uword i = 0; i < CPs.n_elem; i++) { indices[2*i + 0] = CPs(i); indices[2*i + 1] = RPs(i); } // m->bundlePlot->setLines(indices, m->projectionHistory->Y()); }); */ // Same as before, but now for regular points /* SelectionHandler rpSelectionHandler(X.n_rows - cpIndices.n_elem); rpPlot.selectionInteractivelyChanged.connect( std::bind(&SelectionHandler::setSelection, &rpSelectionHandler)); rpBarChart.selectionInteractivelyChanged.connect( std::bind(&SelectionHandler::setSelection, &rpSelectionHandler)); rpSelectionHandler.selectionChanged.connect( std::bind(&Scatterplot::setSelection, &rpPlot)); rpSelectionHandler.selectionChanged.connect( std::bind(&BarChart::setSelection, &rpBarChart)); // This still needs more tests QObject::connect(&rpSelectionHandler, &SelectionHandler::selectionChanged, [m](const std::vector &rpSelection) { // given some RPs, see unexpected CPs *influencing* them std::vector selectedRPIndices; for (int i = 0; i < rpSelection.size(); i++) { if (rpSelection[i]) { selectedRPIndices.push_back(i); } } if (selectedRPIndices.empty()) { overviewBundles(m); return; } arma::uvec selectedRPs(selectedRPIndices.size()); std::copy(selectedRPIndices.begin(), selectedRPIndices.end(), selectedRPs.begin()); // Only the 10% largest values, filtered by the selected RPs const arma::mat &unreliability = m->projectionHistory->unreliability(); arma::uvec indicesLargest = arma::sort_index(unreliability.rows(selectedRPs), "descending"); arma::uword numLargest = indicesLargest.n_elem * 0.01f; indicesLargest = indicesLargest.subvec(0, numLargest-1); m->bundlePlot->setValues(unreliability(indicesLargest)); const arma::uvec &cpIndices = m->projectionHistory->cpIndices(); arma::uvec CPs = cpIndices(indicesLargest / selectedRPs.n_elem); const arma::uvec &rpIndices = m->projectionHistory->rpIndices(); arma::uvec RPs = indicesLargest; RPs.transform([&selectedRPs](arma::uword v) { return v % selectedRPs.n_elem; }); RPs = rpIndices(selectedRPs(RPs)); arma::uvec indices(CPs.n_elem + RPs.n_elem); for (arma::uword i = 0; i < CPs.n_elem; i++) { indices[2*i + 0] = CPs(i); indices[2*i + 1] = RPs(i); } m->bundlePlot->setLines(indices, m->projectionHistory->Y()); }); */ // Brushing between each bar chart and respective scatterplot BrushingHandler cpBrushHandler; auto cpBrushHandler_brushItem = std::bind( &BrushingHandler::brushItem, &cpBrushHandler, std::placeholders::_1); cpPlot.itemInteractivelyBrushed.connect(cpBrushHandler_brushItem); cpBarChart.itemInteractivelyBrushed.connect(cpBrushHandler_brushItem); cpBrushHandler.itemBrushed.connect( std::bind(&Scatterplot::brushItem, &cpPlot, std::placeholders::_1)); cpBrushHandler.itemBrushed.connect( std::bind(&BarChart::brushItem, &cpBarChart, std::placeholders::_1)); BrushingHandler rpBrushHandler; auto rpBrushHandler_brushItem = std::bind( &BrushingHandler::brushItem, &rpBrushHandler, std::placeholders::_1); rpPlot.itemInteractivelyBrushed.connect(rpBrushHandler_brushItem); rpBarChart.itemInteractivelyBrushed.connect(rpBrushHandler_brushItem); rpBrushHandler.itemBrushed.connect( std::bind(&Scatterplot::brushItem, &rpPlot, std::placeholders::_1)); rpBrushHandler.itemBrushed.connect( std::bind(&BarChart::brushItem, &rpBarChart, std::placeholders::_1)); // Update visual components whenever values change history.rpValuesChanged.connect([&](const arma::vec &v, bool rescale) { if (!m.colorScaleRPs || v.n_elem == 0) { return; } if (rescale) { m.colorScaleRPs->setExtents(v.min(), v.max()); } else { m.colorScaleRPs->setExtents(std::min(m.colorScaleRPs->min(), (float) v.min()), std::max(m.colorScaleRPs->max(), (float) v.max())); } m.splat->setColorScale(m.colorScaleRPs); m.rpColormap->setColorScale(m.colorScaleRPs); }); history.cpValuesChanged.connect([&](const arma::vec &v, bool rescale) { if (!m.colorScaleCPs || v.n_elem == 0) { return; } m.colorScaleCPs->setExtents(v.min(), v.max()); m.cpColormap->setColorScale(m.colorScaleCPs); // Originally, we should have done this: // // history.cpValuesChanged.connect( // std::bind(&Scatterplot::setColorData, &cpPlot, std::placeholders::_1)); // // But this has problems with the number of arguments of // setColorData, so we just call it here cpPlot.setColorData(v); }); history.rpValuesChanged.connect( std::bind(&VoronoiSplat::setValues, &splat, std::placeholders::_1)); history.cpValuesChanged.connect( std::bind(&BarChart::setValues, &cpBarChart, std::placeholders::_1)); history.rpValuesChanged.connect( std::bind(&BarChart::setValues, &rpBarChart, std::placeholders::_1)); // ProjectionHistory takes special care of separate CP/RP selections // cpSelectionHandler.selectionChanged.connect( // std::bind(&ProjectionHistory::setCPSelection, &history)); // rpSelectionHandler.selectionChanged.connect( // std::bind(&ProjectionHistory::setRPSelection, &history)); // history.selectionChanged.connect( // std::bind(&LinePlot::selectionChanged, &bundlePlot)); // Connect projection components to rewinding mechanism // TransitionControl plotTC; // plotTC.tChanged.connect( // std::bind(&ProjectionHistory::setRewind, &history, std::placeholders::_1)); history.mapRewound.connect( std::bind(&Main::updateMap, &m, std::placeholders::_1)); history.cpValuesRewound.connect( std::bind(&Scatterplot::setColorData, &cpPlot, std::placeholders::_1)); history.rpValuesRewound.connect( std::bind(&VoronoiSplat::setValues, &splat, std::placeholders::_1)); history.rpValuesRewound.connect( std::bind(&BarChart::updateValues, &rpBarChart, std::placeholders::_1)); // General component set up // plotTC->setAcceptedMouseButtons(Qt::RightButton); // m->cpPlot->setDragEnabled(true); // m->cpBarChart->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); m.setSelectCPs(); // m->cpBarChart->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); // m->rpBarChart->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); m.setCPColorScale(Main::ColorScaleRainbow); m.setRPColorScale(Main::ColorScaleRainbow); // This sets the initial CP configuration, triggering all the necessary // signals to set up the helper objects and visual components manipulationHandler.setCP(Ys); while (!glfwWindowShouldClose(win)) { struct nk_vec2 scale; glfwGetWindowSize(win, &width, &height); glfwGetFramebufferSize(win, &display_width, &display_height); scale.x = static_cast(display_width) / static_cast(width); scale.y = static_cast(display_height) / static_cast(height); // Input glfwPollEvents(); { double x, y; nk_input_begin(&ctx); // glfwPollEvents(); nk_input_key(&ctx, NK_KEY_DEL, glfwGetKey(win, GLFW_KEY_DELETE) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_ENTER, glfwGetKey(win, GLFW_KEY_ENTER) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_TAB, glfwGetKey(win, GLFW_KEY_TAB) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_BACKSPACE, glfwGetKey(win, GLFW_KEY_BACKSPACE) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_LEFT, glfwGetKey(win, GLFW_KEY_LEFT) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_RIGHT, glfwGetKey(win, GLFW_KEY_RIGHT) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_UP, glfwGetKey(win, GLFW_KEY_UP) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_DOWN, glfwGetKey(win, GLFW_KEY_DOWN) == GLFW_PRESS); if (glfwGetKey(win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS) { nk_input_key(&ctx, NK_KEY_COPY, glfwGetKey(win, GLFW_KEY_C) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_PASTE, glfwGetKey(win, GLFW_KEY_P) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_CUT, glfwGetKey(win, GLFW_KEY_X) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_CUT, glfwGetKey(win, GLFW_KEY_E) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_SHIFT, 1); } else { nk_input_key(&ctx, NK_KEY_COPY, 0); nk_input_key(&ctx, NK_KEY_PASTE, 0); nk_input_key(&ctx, NK_KEY_CUT, 0); nk_input_key(&ctx, NK_KEY_SHIFT, 0); } glfwGetCursorPos(win, &x, &y); int int_x = static_cast(x); int int_y = static_cast(y); nk_input_motion(&ctx, int_x, int_y); nk_input_button(&ctx, NK_BUTTON_LEFT, int_x, int_y, glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS); nk_input_button(&ctx, NK_BUTTON_MIDDLE, int_x, int_y, glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS); nk_input_button(&ctx, NK_BUTTON_RIGHT, int_x, int_y, glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS); nk_input_end(&ctx); } glViewport(0, 0, display_width, display_height); glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.3f, 0.3f, 0.3f, 1.0f); // device_draw(&device, &ctx, width, height, scale, NK_ANTI_ALIASING_ON); glfwSwapBuffers(win); } nk_free(&ctx); glfwTerminate(); return 0; }