Repository: ossia/score
Pull request #1748: feature/add multicursor and pathgenerator widgets
Author: login: D3mh4
Created: 2025-03-26T17:24:50Z | Merged: 2025-04-02T17:13:42Z

- add multi-cursor manager plugin
- import multi-cursor plugin



Diff:
diff --git a/3rdparty/avendish b/3rdparty/avendish
index b9aea013b3..40040c255c 160000
--- a/3rdparty/avendish
+++ b/3rdparty/avendish
@@ -1 +1 @@
-Subproject commit b9aea013b385045b0b75acda38186bddadd9d976
+Subproject commit 40040c255c5d69b1060cb04b1d33e8794fd32d6c
diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt
index b0e00d7e3c..afa2efbd25 100755
--- a/src/lib/CMakeLists.txt
+++ b/src/lib/CMakeLists.txt
@@ -286,6 +286,8 @@ set(HEADERS
     "${CMAKE_CURRENT_SOURCE_DIR}/score/graphics/widgets/QGraphicsLogKnob.hpp"
     "${CMAKE_CURRENT_SOURCE_DIR}/score/graphics/widgets/QGraphicsLogSlider.hpp"
     "${CMAKE_CURRENT_SOURCE_DIR}/score/graphics/widgets/QGraphicsMultiSlider.hpp"
+    "${CMAKE_CURRENT_SOURCE_DIR}/score/graphics/widgets/QGraphicsMultiSliderXY.hpp"
+    "${CMAKE_CURRENT_SOURCE_DIR}/score/graphics/widgets/QGraphicsPathGeneratorXY.hpp"
     "${CMAKE_CURRENT_SOURCE_DIR}/score/graphics/widgets/QGraphicsNoteChooser.hpp"
     "${CMAKE_CURRENT_SOURCE_DIR}/score/graphics/widgets/QGraphicsPixmapButton.hpp"
     "${CMAKE_CURRENT_SOURCE_DIR}/score/graphics/widgets/QGraphicsPixmapEnum.hpp"
@@ -472,6 +474,8 @@ set(SRCS
 "${CMAKE_CURRENT_SOURCE_DIR}/score/graphics/widgets/QGraphicsLogKnob.cpp"
 "${CMAKE_CURRENT_SOURCE_DIR}/score/graphics/widgets/QGraphicsLogSlider.cpp"
 "${CMAKE_CURRENT_SOURCE_DIR}/score/graphics/widgets/QGraphicsMultiSlider.cpp"
+"${CMAKE_CURRENT_SOURCE_DIR}/score/graphics/widgets/QGraphicsMultiSliderXY.cpp"
+"${CMAKE_CURRENT_SOURCE_DIR}/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp"
 "${CMAKE_CURRENT_SOURCE_DIR}/score/graphics/widgets/QGraphicsNoteChooser.cpp"
 "${CMAKE_CURRENT_SOURCE_DIR}/score/graphics/widgets/QGraphicsPixmapButton.cpp"
 "${CMAKE_CURRENT_SOURCE_DIR}/score/graphics/widgets/QGraphicsPixmapEnum.cpp"
diff --git a/src/lib/score/graphics/GraphicWidgets.hpp b/src/lib/score/graphics/GraphicWidgets.hpp
index 1e32be50c2..952d42f845 100644
--- a/src/lib/score/graphics/GraphicWidgets.hpp
+++ b/src/lib/score/graphics/GraphicWidgets.hpp
@@ -11,6 +11,8 @@
 #include <score/graphics/widgets/QGraphicsLogKnob.hpp>
 #include <score/graphics/widgets/QGraphicsLogSlider.hpp>
 #include <score/graphics/widgets/QGraphicsMultiSlider.hpp>
+#include <score/graphics/widgets/QGraphicsMultiSliderXY.hpp>
+#include <score/graphics/widgets/QGraphicsPathGeneratorXY.hpp>
 #include <score/graphics/widgets/QGraphicsNoteChooser.hpp>
 #include <score/graphics/widgets/QGraphicsPixmapButton.hpp>
 #include <score/graphics/widgets/QGraphicsPixmapEnum.hpp>
diff --git a/src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp b/src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
new file mode 100644
index 0000000000..107174e0b0
--- /dev/null
+++ b/src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
@@ -0,0 +1,165 @@
+#include <score/graphics/DefaultGraphicsSliderImpl.hpp>
+#include <score/graphics/widgets/QGraphicsMultiSliderXY.hpp>
+#include <score/model/Skin.hpp>
+#include <score/serialization/StringConstants.hpp>
+#include <score/tools/Debug.hpp>
+
+#include <ossia/detail/math.hpp>
+#include <ossia/network/domain/domain_functions.hpp>
+#include <ossia/network/value/value_conversion.hpp>
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+
+#include <wobjectimpl.h>
+W_OBJECT_IMPL(score::QGraphicsMultiSliderXY);
+
+namespace score
+{
+score::QGraphicsMultiSliderXY::QGraphicsMultiSliderXY(QGraphicsItem* parent)
+{
+  auto& skin = score::Skin::instance();
+  setCursor(skin.CursorPointingHand);
+  this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
+}
+
+void score::QGraphicsMultiSliderXY::setPoint(const QPointF& r)
+{
+  SCORE_TODO;
+}
+
+void score::QGraphicsMultiSliderXY::paint(
+    QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+
+  tab = m_value.get<std::vector<ossia::value>>();
+
+  auto& skin = score::Skin::instance();
+
+  // Draw the background
+  painter->fillRect(boundingRect(), skin.Dark);
+
+  // Draw cursors
+  for (const ossia::value& v : tab)
+  {
+    auto& cursor = v.get<ossia::vec2f>();
+    painter->fillRect(
+        QRectF((cursor[0] - cursorSize.x / 2) * width(),
+               (1-cursor[1] - cursorSize.y / 2) * height(),
+               cursorSize.x * width(),
+               cursorSize.y * height()),
+        skin.Base2);
+  }
+}
+
+ossia::value score::QGraphicsMultiSliderXY::value() const
+{
+  return m_value;
+}
+
+void score::QGraphicsMultiSliderXY::setRange(const ossia::value& min, const ossia::value& max)
+{
+  this->min = ossia::convert<float>(min);
+  this->max = ossia::convert<float>(max);
+}
+
+void score::QGraphicsMultiSliderXY::setRange(const ossia::domain& dom)
+{
+  auto [min, max] = ossia::get_float_minmax(dom);
+
+  if (min)
+    this->min = *min;
+  if (max)
+    this->max = *max;
+}
+
+void score::QGraphicsMultiSliderXY::setValue(ossia::value v)
+{
+  prepareGeometryChange();
+  m_value = std::move(v);
+  update();
+}
+
+void score::QGraphicsMultiSliderXY::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
+{
+  if(m_grab && ossia::valid_index(selectedCursor, tab))  // The cursor’s position is updated as the mouse moves, constrained within the widget’s bounds.
+  {
+    tab[selectedCursor] = ossia::vec2f{(float)std::clamp((event->pos().x() / width()), 0., 1.),
+                                       (float)std::clamp(1-(event->pos().y() / height()), 0., 1.)};
+    m_value = std::vector<ossia::value>(tab.begin(), tab.end());
+    sliderMoved();
+    update();
+  }
+}
+
+void score::QGraphicsMultiSliderXY::mousePressEvent(QGraphicsSceneMouseEvent* event)
+{
+  float x = event->pos().x();
+  float y = event->pos().y();
+
+  // If the left mouse button is pressed on an existing cursor, that cursor can be moved.
+  if (event->button() & Qt::LeftButton)
+  {
+    for (int v = 0; v < std::ssize(tab) ; v++)
+    {
+      auto& point = tab[v].get<ossia::vec2f>();
+      if ((point[0] - 0.02f) * width() <= (float)x &&
+         (float)x <= (point[0] + 0.02f) * width() &&
+         (1-point[1] - 0.02f) * height() <= (float)y &&
+         (float)y <= (1-point[1] + 0.02f) * height())
+      {
+        m_grab = true;
+        selectedCursor = v;
+        mouseMoveEvent(event);
+        return;
+      }
+    }
+    // Else if the press occurs at an empty area, a new cursor will be created at that position.
+    tab.push_back(ossia::vec2f{(float)(x / width()), 1-(float)(y / width())});
+    m_value = std::vector<ossia::value>(tab.begin(), tab.end());
+    m_grab = true;
+    selectedCursor = std::ssize(tab) - 1;
+    mouseMoveEvent(event);
+    return;
+  }
+  else if (event->button() & Qt::RightButton) //If the right mouse button is pressed over a cursor, the cursor is deleted.
+  {
+    m_grab = false;
+    for (int v = 0; v < std::ssize(tab); v++)
+    {
+      auto& point = tab[v].get<ossia::vec2f>();
+      if ((point[0] - 0.02f) * width() <= (float)x &&
+         (float)x <= (point[0] + 0.02f) * width() &&
+         (1-point[1] - 0.02f) * height() <= (float)y &&
+         (float)y <= (1-point[1] + 0.02f) * height())
+      {
+        tab.erase(tab.begin() + v);
+        selectedCursor=-1;
+        m_value = std::vector<ossia::value>(tab.begin(), tab.end());
+        sliderMoved();
+        return;
+      }
+    }
+    return;
+  }
+  update();
+}
+
+void score::QGraphicsMultiSliderXY::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
+{
+  if ((event->button() & Qt::LeftButton) && m_grab)
+  {
+    mouseMoveEvent(event);
+    m_grab = false;
+    selectedCursor=-1;
+  }
+
+  update();
+  sliderReleased();
+}
+
+QRectF score::QGraphicsMultiSliderXY::boundingRect() const
+{
+  return QRectF(0, 0, 400, 400);
+}
+}
diff --git a/src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.hpp b/src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.hpp
new file mode 100644
index 0000000000..188880ac30
--- /dev/null
+++ b/src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.hpp
@@ -0,0 +1,78 @@
+#pragma once
+#include <score/graphics/widgets/Constants.hpp>
+
+#include <ossia-qt/value_metatypes.hpp>
+
+#include <QGraphicsItem>
+#include <QObject>
+
+#include <score_lib_base_export.h>
+
+#include <verdigris>
+
+#include <halp/value_types.hpp>
+#include <halp/custom_widgets.hpp>
+
+namespace ossia
+{
+
+struct domain;
+}
+namespace score
+{
+template <typename T>
+struct GridWidget;
+struct RightClickImpl;
+class SCORE_LIB_BASE_EXPORT QGraphicsMultiSliderXY final
+    : public QObject
+    , public QGraphicsItem
+{
+  W_OBJECT(QGraphicsMultiSliderXY)
+  SCORE_GRAPHICS_ITEM_TYPE(110)
+public:
+  template <typename T>
+  friend struct GridWidget;
+
+  std::vector<ossia::value> tab;
+
+  static constexpr double width() { return 400.; }
+  static constexpr double height() { return 400.; }
+
+  halp::xy_type<float> cursorSize{0.04, 0.04};
+
+  int selectedCursor;
+  bool isSelected;
+
+  double min{0.}, max{1.};
+
+  int m_grab{-1};
+  ossia::value m_value{};
+  ossia::value m_execValue{};
+  bool m_hasExec{};
+  bool moving = false;
+  RightClickImpl* impl{};
+
+  QGraphicsMultiSliderXY(QGraphicsItem* parent);
+
+  void setPoint(const QPointF& r);
+  void setValue(ossia::value v);
+  ossia::value value() const;
+  //void setExecutionValue(double v);
+  //void resetExecution();
+
+  void setRange(const ossia::value& min, const ossia::value& max);
+  void setRange(const ossia::domain& dom);
+
+public:
+  void sliderMoved() E_SIGNAL(SCORE_LIB_BASE_EXPORT, sliderMoved)
+  void sliderReleased() E_SIGNAL(SCORE_LIB_BASE_EXPORT, sliderReleased)
+
+private:
+  void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
+  void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
+  void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;
+  QRectF boundingRect() const override;
+  void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+      override;
+};
+}
diff --git a/src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp b/src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp
new file mode 100644
index 0000000000..97220aada0
--- /dev/null
+++ b/src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp
@@ -0,0 +1,196 @@
+#include <score/graphics/DefaultGraphicsSliderImpl.hpp>
+#include <score/graphics/widgets/QGraphicsPathGeneratorXY.hpp>
+#include <score/serialization/StringConstants.hpp>
+#include <score/tools/Debug.hpp>
+#include <score/model/Skin.hpp>
+
+#include <ossia/detail/math.hpp>
+#include <ossia/network/domain/domain_functions.hpp>
+#include <ossia/network/value/value_conversion.hpp>
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+#include <utility>
+
+#include <wobjectimpl.h>
+W_OBJECT_IMPL(score::QGraphicsPathGeneratorXY);
+
+namespace score
+{
+score::QGraphicsPathGeneratorXY::QGraphicsPathGeneratorXY(QGraphicsItem* parent)
+{
+  auto& skin = score::Skin::instance();
+  setCursor(skin.CursorPointingHand);
+  this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
+}
+
+void score::QGraphicsPathGeneratorXY::paint(
+    QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+  tab = m_value.get<std::vector<ossia::value>>();
+  auto& skin = score::Skin::instance();
+
+  // Draw the background
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(skin.Dark.color()));
+
+  // Draw cursors
+  for (size_t s = 0; s < std::ssize(tab); s++) {
+    auto& source = tab[s].get<std::vector<ossia::value>>();
+    bool isSelectedSource = (s == selectedSource);
+
+    for (const auto& sub_element : tab[s].get<std::vector<ossia::value>>()) {
+      const ossia::vec2f cursorXY = sub_element.get<ossia::vec2f>();
+      bool isStartPoint = (sub_element == source[0]);
+
+      QRectF cursorRect(
+          (cursorXY[0] - cursorSize.x / 2) * width(),
+          (1 - cursorXY[1] - cursorSize.y / 2) * height(),
+          cursorSize.x * width(),
+          cursorSize.y * height()
+          );
+
+      QColor cursorColor = isSelectedSource
+                               ? (isStartPoint ? skin.Base3.color() : skin.Warn3.color())
+                               : (isStartPoint ? skin.Base3.color().darker(300) : skin.Warn3.color().darker(250));
+
+      painter->fillRect(cursorRect, cursorColor);
+    }
+
+   // Draw path if selected
+    if (isSelectedSource) {
+      const auto& startCursor = source[0].get<ossia::vec2f>();
+      const auto& endCursor = source[1].get<ossia::vec2f>();
+      QPointF startPoint(
+          startCursor[0] * width(),
+          (1 - startCursor[1]) * height()
+          );
+      QPointF endPoint(
+          endCursor[0] * width(),
+          (1 - endCursor[1]) * height()
+          );
+
+      painter->drawLine(startPoint, endPoint);
+    }
+  }
+}
+
+ossia::value score::QGraphicsPathGeneratorXY::value() const
+{
+  return m_value;
+}
+
+void score::QGraphicsPathGeneratorXY::setRange(const ossia::value& min, const ossia::value& max)
+{
+  this->min = ossia::convert<float>(min);
+  this->max = ossia::convert<float>(max);
+}
+
+void score::QGraphicsPathGeneratorXY::setRange(const ossia::domain& dom)
+{
+  auto [min, max] = ossia::get_float_minmax(dom);
+
+  if (min)
+    this->min = *min;
+  if (max)
+    this->max = *max;
+}
+
+void score::QGraphicsPathGeneratorXY::setValue(ossia::value v)
+{
+  prepareGeometryChange();
+  m_value = std::move(v);
+  update();
+}
+
+void score::QGraphicsPathGeneratorXY::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
+{
+  if (m_grab && ossia::valid_index(selectedSource, tab)) // The cursor’s position is updated as the mouse moves, constrained within the widget’s bounds.
+  {
+    tab[selectedSource].get<std::vector<ossia::value>>()[selectedCursor] =
+                                  ossia::vec2f{
+                                       (float)std::clamp((event->pos().x() / width()), 0., 1.),
+                                       (float)std::clamp(1-(event->pos().y() / height()), 0., 1.)};
+
+    m_value = std::vector<ossia::value>(tab.begin(), tab.end());
+    sliderMoved();
+    update();
+  }
+}
+
+void score::QGraphicsPathGeneratorXY::mousePressEvent(QGraphicsSceneMouseEvent* event)
+{
+  float x = event->pos().x();
+  float y = event->pos().y();
+
+  // If the left mouse button is pressed on an existing cursor, that cursor can be moved.
+  if (event->button() & Qt::LeftButton)
+  {
+    for (int sourceIndex = 0; sourceIndex < std::ssize(tab); sourceIndex++){
+      for (int cursorIndex = 0; cursorIndex < std::ssize(tab[sourceIndex].get<std::vector<ossia::value>>()); cursorIndex++)
+    {
+      const auto& CursorXY = tab[sourceIndex].get<std::vector<ossia::value>>()[cursorIndex].get<ossia::vec2f>();
+      if ((CursorXY[0] - 0.02f) * width() <= (float)x &&
+         (float)x <= (CursorXY[0] + 0.02f) * width() &&
+         (1-CursorXY[1] - 0.02f) * height() <= (float)y &&
+         (float)y <= (1-CursorXY[1] + 0.02f) * height())
+      {
+        m_grab = true;
+        selectedSource = sourceIndex;
+        selectedCursor = cursorIndex;
+        mouseMoveEvent(event);
+        return;
+      }
+    }
+    }
+    // Else if the press occurs at an empty area, a new cursor will be created at that position.
+    tab.push_back(std::vector<ossia::value>{ossia::vec2f{(float)(x / width()), 1-(float)(y / width())}, ossia::vec2f{(float)(x / width()), 1-(float)(y / width())}});
+
+    m_value = std::vector<ossia::value>(tab.begin(), tab.end());
+    m_grab = true;
+    selectedSource = tab.size() - 1;
+    selectedCursor = 1;
+    mouseMoveEvent(event);
+    return;
+  }
+  else if (event->button() & Qt::RightButton) //If the right mouse button is pressed over a cursor, the cursor is deleted.
+  {
+    m_grab = false;
+    for (int v = 0; v < std::ssize(tab); v++){
+      for (int c = 0; c < std::ssize(tab[v].get<std::vector<ossia::value>>()); c++)
+    {
+      const auto& CursorXY = tab[v].get<std::vector<ossia::value>>()[c].get<ossia::vec2f>();
+      if ((CursorXY[0] - 0.02f) * width() <= (float)x &&
+         (float)x <= (CursorXY[0] + 0.02f) * width() &&
+         (1-CursorXY[1] - 0.02f) * height() <= (float)y &&
+         (float)y <= (1-CursorXY[1] + 0.02f) * height())
+      {
+        tab.erase(tab.begin() + v);
+        selectedSource--;
+        m_value = std::vector<ossia::value>(tab.begin(), tab.end());
+        sliderMoved();
+        return;
+      }}
+
+    }
+    return;
+  }
+  update();
+}
+
+void score::QGraphicsPathGeneratorXY::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
+{
+  if ((event->button() & Qt::LeftButton) && m_grab)
+  {
+    m_grab = false;
+    mouseMoveEvent(event);
+  }
+
+  update();
+  sliderReleased();
+}
+
+QRectF score::QGraphicsPathGeneratorXY::boundingRect() const
+{
+  return QRectF(0, 0, 400, 400);
+}
+}
diff --git a/src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.hpp b/src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.hpp
new file mode 100644
index 0000000000..786e1f2a11
--- /dev/null
+++ b/src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.hpp
@@ -0,0 +1,79 @@
+#pragma once
+#include <score/graphics/widgets/Constants.hpp>
+
+#include <ossia-qt/value_metatypes.hpp>
+
+#include <QGraphicsItem>
+#include <QObject>
+
+#include <score_lib_base_export.h>
+
+#include <verdigris>
+
+#include <halp/value_types.hpp>
+#include <halp/custom_widgets.hpp>
+
+namespace ossia
+{
+
+struct domain;
+}
+namespace score
+{
+template <typename T>
+struct GridWidget;
+struct RightClickImpl;
+class SCORE_LIB_BASE_EXPORT QGraphicsPathGeneratorXY final
+    : public QObject
+    , public QGraphicsItem
+{
+  W_OBJECT(QGraphicsPathGeneratorXY)
+  SCORE_GRAPHICS_ITEM_TYPE(110)
+public:
+  template <typename T>
+  friend struct GridWidget;
+
+  std::vector<ossia::value> tab;
+
+  static constexpr double width() { return 400.; }
+  static constexpr double height() { return 400.; }
+
+  halp::xy_type<float> cursorSize{0.04, 0.04};
+
+  int selectedCursor;
+  int selectedSource;
+  bool isSelected;
+
+  double min{0.}, max{1.};
+
+  int m_grab{-1};
+  ossia::value m_value{};
+  ossia::value m_execValue{};
+  bool m_hasExec{};
+  bool moving = false;
+  RightClickImpl* impl{};
+
+  QGraphicsPathGeneratorXY(QGraphicsItem* parent);
+
+  void setPoint(const QPointF& r);
+  void setValue(ossia::value v);
+  ossia::value value() const;
+  //void setExecutionValue(double v);
+  //void resetExecution();
+
+  void setRange(const ossia::value& min, const ossia::value& max);
+  void setRange(const ossia::domain& dom);
+
+public:
+  void sliderMoved() E_SIGNAL(SCORE_LIB_BASE_EXPORT, sliderMoved)
+  void sliderReleased() E_SIGNAL(SCORE_LIB_BASE_EXPORT, sliderReleased)
+
+private:
+  void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
+  void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
+  void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;
+  QRectF boundingRect() const override;
+  void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+      override;
+};
+}
diff --git a/src/plugins/score-lib-process/Process/Dataflow/ControlWidgets.hpp b/src/plugins/score-lib-process/Process/Dataflow/ControlWidgets.hpp
index 0c05fdb5d7..2d9ec10733 100644
--- a/src/plugins/score-lib-process/Process/Dataflow/ControlWidgets.hpp
+++ b/src/plugins/score-lib-process/Process/Dataflow/ControlWidgets.hpp
@@ -1571,6 +1571,97 @@ struct MultiSlider
   }
 };
 
+struct MultiSliderXY
+{
+  static Process::PortItemLayout layout() noexcept
+  {
+    return Process::DefaultControlLayouts::slider();
+  }
+
+  template <typename T>
+  static auto make_widget(
+      T& inlet, const score::DocumentContext& ctx, QWidget* parent, QObject* context)
+  {
+    SCORE_TODO;
+    return nullptr; // TODO
+  }
+
+  template <typename T, typename Control_T>
+  static QGraphicsItem* make_item(
+      const T& slider, Control_T& inlet, const score::DocumentContext& ctx,
+      QGraphicsItem* parent, QObject* context)
+  {
+    auto sl = new score::QGraphicsMultiSliderXY{nullptr};
+    initWidgetProperties(inlet, *sl);
+    sl->setValue(inlet.value());
+    sl->setRange(inlet.domain());
+
+    QObject::connect(
+        sl, &score::QGraphicsMultiSliderXY::sliderMoved, context, [=, &inlet, &ctx] {
+      sl->moving = true;
+      ctx.dispatcher.submit<SetControlValue<Control_T>>(inlet, sl->value());
+    });
+    QObject::connect(
+        sl, &score::QGraphicsMultiSliderXY::sliderReleased, context, [&ctx, sl]() {
+      ctx.dispatcher.commit();
+      sl->moving = false;
+    });
+
+    QObject::connect(&inlet, &Control_T::valueChanged, sl, [=](const ossia::value& val) {
+      if(!sl->moving)
+        sl->setValue(std::move(val));
+    });
+
+    return sl;
+  }
+};
+
+struct PathGeneratorXY
+{
+  static Process::PortItemLayout layout() noexcept
+  {
+    return Process::DefaultControlLayouts::slider();
+  }
+
+  template <typename T>
+  static auto make_widget(
+      T& inlet, const score::DocumentContext& ctx, QWidget* parent, QObject* context)
+  {
+    SCORE_TODO;
+    return nullptr; // TODO
+  }
+
+  template <typename T, typename Control_T>
+  static QGraphicsItem* make_item(
+      const T& slider, Control_T& inlet, const score::DocumentContext& ctx,
+      QGraphicsItem* parent, QObject* context)
+  {
+    auto sl = new score::QGraphicsPathGeneratorXY{nullptr};
+    initWidgetProperties(inlet, *sl);
+    sl->setValue(inlet.value());
+    sl->setRange(inlet.domain());
+
+    QObject::connect(
+        sl, &score::QGraphicsPathGeneratorXY::sliderMoved, context, [=, &inlet, &ctx] {
+      sl->moving = true;
+      ctx.dispatcher.submit<SetControlValue<Control_T>>(inlet, sl->value());
+    });
+    QObject::connect(
+        sl, &score::QGraphicsPathGeneratorXY::sliderReleased, context, [&ctx, sl]() {
+      ctx.dispatcher.commit();
+      sl->moving = false;
+    });
+
+    QObject::connect(&inlet, &Control_T::valueChanged, sl, [=](const ossia::value& val) {
+      if(!sl->moving)
+        sl->setValue(std::move(val));
+    });
+
+    return sl;
+  }
+};
+
+
 /// Outlets
 using Bargraph = FloatDisplay;
 }
diff --git a/src/plugins/score-lib-process/Process/Dataflow/WidgetInlets.cpp b/src/plugins/score-lib-process/Process/Dataflow/WidgetInlets.cpp
index 6a730a854b..0c79eb36d9 100644
--- a/src/plugins/score-lib-process/Process/Dataflow/WidgetInlets.cpp
+++ b/src/plugins/score-lib-process/Process/Dataflow/WidgetInlets.cpp
@@ -742,6 +742,70 @@ void MultiSlider::setupExecution(ossia::inlet& inl, QObject* exec_context) const
   port.domain = domain().get();
 }
 
+
+MultiSliderXY::MultiSliderXY(
+    ossia::value init, const QString& name, Id<Port> id, QObject* parent)
+    : ControlInlet{id, parent}
+{
+  hidden = true;
+  setValue(std::move(init));
+  setInit(value());
+  setName(name);
+  setDomain(ossia::make_domain(0., 1.));
+}
+
+MultiSliderXY::~MultiSliderXY() { }
+
+ossia::value MultiSliderXY::getMin() const noexcept
+{
+  return domain().get().get_min();
+}
+
+ossia::value MultiSliderXY::getMax() const noexcept
+{
+  return domain().get().get_max();
+}
+
+void MultiSliderXY::setupExecution(ossia::inlet& inl, QObject* exec_context) const noexcept
+{
+  auto& port = **safe_cast<ossia::value_inlet*>(&inl);
+  port.type = ossia::val_type::LIST;
+  port.domain = domain().get();
+}
+
+
+
+PathGeneratorXY::PathGeneratorXY(
+    ossia::value init, const QString& name, Id<Port> id, QObject* parent)
+    : ControlInlet{id, parent}
+{
+  hidden = true;
+  setValue(std::move(init));
+  setInit(value());
+  setName(name);
+  setDomain(ossia::make_domain(0., 1.));
+}
+
+PathGeneratorXY::~PathGeneratorXY() { }
+
+ossia::value PathGeneratorXY::getMin() const noexcept
+{
+  return domain().get().get_min();
+}
+
+ossia::value PathGeneratorXY::getMax() const noexcept
+{
+  return domain().get().get_max();
+}
+
+void PathGeneratorXY::setupExecution(ossia::inlet& inl, QObject* exec_context) const noexcept
+{
+  auto& port = **safe_cast<ossia::value_inlet*>(&inl);
+  port.type = ossia::val_type::LIST;
+  port.domain = domain().get();
+}
+
+
 Bargraph::Bargraph(
     float min, float max, float init, const QString& name, Id<Port> id, QObject* parent)
     : ControlOutlet{id, parent}
@@ -1415,6 +1479,53 @@ JSONWriter::write(Process::MultiSlider& p)
 {
 }
 
+template <>
+SCORE_LIB_PROCESS_EXPORT void
+DataStreamReader::read(const Process::MultiSliderXY& p)
+{
+  read((const Process::ControlInlet&)p);
+}
+template <>
+SCORE_LIB_PROCESS_EXPORT void
+DataStreamWriter::write(Process::MultiSliderXY& p)
+{
+}
+template <>
+SCORE_LIB_PROCESS_EXPORT void
+JSONReader::read(const Process::MultiSliderXY& p)
+{
+  read((const Process::ControlInlet&)p);
+}
+template <>
+SCORE_LIB_PROCESS_EXPORT void
+JSONWriter::write(Process::MultiSliderXY& p)
+{
+}
+
+
+template <>
+SCORE_LIB_PROCESS_EXPORT void
+DataStreamReader::read(const Process::PathGeneratorXY& p)
+{
+  read((const Process::ControlInlet&)p);
+}
+template <>
+SCORE_LIB_PROCESS_EXPORT void
+DataStreamWriter::write(Process::PathGeneratorXY& p)
+{
+}
+template <>
+SCORE_LIB_PROCESS_EXPORT void
+JSONReader::read(const Process::PathGeneratorXY& p)
+{
+  read((const Process::ControlInlet&)p);
+}
+template <>
+SCORE_LIB_PROCESS_EXPORT void
+JSONWriter::write(Process::PathGeneratorXY& p)
+{
+}
+
 template <>
 SCORE_LIB_PROCESS_EXPORT void
 DataStreamReader::read(const Process::Bargraph& p)
diff --git a/src/plugins/score-lib-process/Process/Dataflow/WidgetInlets.hpp b/src/plugins/score-lib-process/Process/Dataflow/WidgetInlets.hpp
index 42572dcf2a..c8fe2beb03 100644
--- a/src/plugins/score-lib-process/Process/Dataflow/WidgetInlets.hpp
+++ b/src/plugins/score-lib-process/Process/Dataflow/WidgetInlets.hpp
@@ -35,8 +35,13 @@ struct XYZSlider;
 struct XYSpinboxes;
 struct XYZSpinboxes;
 struct MultiSlider;
+struct MultiSliderXY;
+struct PathGeneratorXY;
 struct Bargraph;
 } // namespace Process
+
+
+
 UUID_METADATA(
     SCORE_LIB_PROCESS_EXPORT, Process::Port, Process::FloatSlider,
     "af2b4fc3-aecb-4c15-a5aa-1c573a239925")
@@ -133,7 +138,12 @@ UUID_METADATA(
 UUID_METADATA(
     SCORE_LIB_PROCESS_EXPORT, Process::Port, Process::MultiSlider,
     "25de6d71-1554-4fe1-bf3f-9cbf12bdadeb")
-
+UUID_METADATA(
+    SCORE_LIB_PROCESS_EXPORT, Process::Port, Process::MultiSliderXY,
+    "ce12611f-f3b0-4f99-b3c0-3f0b25a38aa1")
+UUID_METADATA(
+    SCORE_LIB_PROCESS_EXPORT, Process::Port, Process::PathGeneratorXY,
+    "b60d0059-733b-4b57-a1c1-65fa140d3b8a")
 UUID_METADATA(
     SCORE_LIB_PROCESS_EXPORT, Process::Port, Process::Bargraph,
     "f6d740ce-acc0-44c0-932a-0a03345af84f")
@@ -576,6 +586,35 @@ struct SCORE_LIB_PROCESS_EXPORT MultiSlider : public Process::ControlInlet
   using Process::ControlInlet::ControlInlet;
 };
 
+
+struct SCORE_LIB_PROCESS_EXPORT MultiSliderXY : public Process::ControlInlet
+{
+  MODEL_METADATA_IMPL(MultiSliderXY)
+  MultiSliderXY(
+      ossia::value init, const QString& name, Id<Process::Port> id, QObject* parent);
+  ~MultiSliderXY();
+
+  ossia::value getMin() const noexcept;
+  ossia::value getMax() const noexcept;
+  void setupExecution(ossia::inlet& inl, QObject* exec_context) const noexcept override;
+
+  using Process::ControlInlet::ControlInlet;
+};
+
+struct SCORE_LIB_PROCESS_EXPORT PathGeneratorXY : public Process::ControlInlet
+{
+  MODEL_METADATA_IMPL(PathGeneratorXY)
+  PathGeneratorXY(
+      ossia::value init, const QString& name, Id<Process::Port> id, QObject* parent);
+  ~PathGeneratorXY();
+
+  ossia::value getMin() const noexcept;
+  ossia::value getMax() const noexcept;
+  void setupExecution(ossia::inlet& inl, QObject* exec_context) const noexcept override;
+
+  using Process::ControlInlet::ControlInlet;
+};
+
 // Outlets
 
 struct SCORE_LIB_PROCESS_EXPORT Bargraph : public Process::ControlOutlet
diff --git a/src/plugins/score-plugin-avnd/Crousti/Concepts.hpp b/src/plugins/score-plugin-avnd/Crousti/Concepts.hpp
index 96c4b2f19e..f89fe06f2f 100644
--- a/src/plugins/score-plugin-avnd/Crousti/Concepts.hpp
+++ b/src/plugins/score-plugin-avnd/Crousti/Concepts.hpp
@@ -1,4 +1,5 @@
 #pragma once
+#include "Engine/Node/CommonWidgets.hpp"
 #include <Process/Dataflow/WidgetInlets.hpp>
 #include <Process/ProcessFlags.hpp>
 #include <Process/ProcessMetadata.hpp>
@@ -229,11 +230,19 @@ make_control_in(avnd::field_index<N>, Id<Process::Port>&& id, QObject* parent)
   }
   else if constexpr(widg.widget == avnd::widget_type::multi_slider)
   {
-    constexpr auto c = avnd::get_range<T>();
-    auto [start, end] = c.init;
     std::vector<ossia::value> init;
     return new Process::MultiSlider{init, qname, id, parent};
   }
+  else if constexpr(widg.widget == avnd::widget_type::multi_slider_xy)
+  {
+    std::vector<ossia::value> init;
+    return new Process::MultiSliderXY{init, qname, id, parent};
+  }
+  else if constexpr(widg.widget == avnd::widget_type::path_generator_xy)
+  {
+    std::vector<ossia::value> init;
+    return new Process::PathGeneratorXY{init, qname, id, parent};
+  }
   else if constexpr(widg.widget == avnd::widget_type::spinbox)
   {
     static constexpr auto c = avnd::get_range<T>();
diff --git a/src/plugins/score-plugin-dataflow/score_plugin_dataflow.cpp b/src/plugins/score-plugin-dataflow/score_plugin_dataflow.cpp
index 5fcb6b2de4..75f1c7a5bd 100644
--- a/src/plugins/score-plugin-dataflow/score_plugin_dataflow.cpp
+++ b/src/plugins/score-plugin-dataflow/score_plugin_dataflow.cpp
@@ -76,6 +76,8 @@ std::vector<score::InterfaceBase*> score_plugin_dataflow::factories(
          Dataflow::WidgetInletFactory<
              Process::XYZSpinboxes, WidgetFactory::XYZSpinboxes>,
          Dataflow::WidgetInletFactory<Process::MultiSlider, WidgetFactory::MultiSlider>,
+         Dataflow::WidgetInletFactory<Process::MultiSliderXY, WidgetFactory::MultiSliderXY>,
+         Dataflow::WidgetInletFactory<Process::PathGeneratorXY, WidgetFactory::PathGeneratorXY>,
          Dataflow::WidgetInletFactory<
              Dataflow::CurveInlet, WidgetFactory::CurveInletItems>,
          Dataflow::WidgetOutletFactory<Process::Bargraph, WidgetFactory::Bargraph>>>(
diff --git a/src/plugins/score-plugin-engine/Engine/Node/CommonWidgets.hpp b/src/plugins/score-plugin-engine/Engine/Node/CommonWidgets.hpp
index 7f3d36a933..3a2ee43b69 100644
--- a/src/plugins/score-plugin-engine/Engine/Node/CommonWidgets.hpp
+++ b/src/plugins/score-plugin-engine/Engine/Node/CommonWidgets.hpp
@@ -63,6 +63,14 @@ enum Waveform
   Noise3
 };
 
+enum Path
+{
+  Linear,
+  Circle,
+  Spiral
+
+};
+
 #if 0
 static constexpr auto WaveformChooser()
 {
diff --git a/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolFactory.cpp b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolFactory.cpp
index 88af8d4ce4..be14667321 100644
--- a/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolFactory.cpp
+++ b/src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolFactory.cpp
@@ -15,6 +15,7 @@
 #include <QDialogButtonBox>
 #include <QFormLayout>
 #include <QObject>
+#include <QUrl>
 
 namespace Protocols
 {


Reviews:
user: login: jcelerier
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-26T17:59:21Z
user: login: jcelerier
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T14:22:53Z
user: login: D3mh4
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T17:49:22Z
user: login: jcelerier
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T18:14:24Z
user: login: D3mh4
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T20:20:44Z
user: login: D3mh4
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T20:21:38Z
user: login: jcelerier
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T20:21:56Z
user: login: jcelerier
body: good job!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-02T17:13:17Z

Review comments:
diff_hunk: @@ -0,0 +1,187 @@
+#include <score/graphics/DefaultGraphicsSliderImpl.hpp>
+#include <score/graphics/widgets/QGraphicsPathGeneratorXY.hpp>
+#include <score/model/Skin.hpp>
+#include <score/serialization/StringConstants.hpp>
+#include <score/tools/Debug.hpp>
+
+#include <ossia/detail/math.hpp>
+#include <ossia/network/domain/domain_functions.hpp>
+#include <ossia/network/value/value_conversion.hpp>
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+
+#include <wobjectimpl.h>
+W_OBJECT_IMPL(score::QGraphicsPathGeneratorXY);
+
+namespace score
+{
+score::QGraphicsPathGeneratorXY::QGraphicsPathGeneratorXY(QGraphicsItem* parent)
+{
+  auto& skin = score::Skin::instance();
+  setCursor(skin.CursorPointingHand);
+  this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
+}
+
+void score::QGraphicsPathGeneratorXY::setPoint(const QPointF& r)
+{
+  SCORE_TODO;
path: src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp
user: login: jcelerier
body: I think this one needs to be implemented too! :)
created_at: 2025-03-26T17:49:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,164 @@
+#include <score/graphics/DefaultGraphicsSliderImpl.hpp>
+#include <score/graphics/widgets/QGraphicsMultiSliderXY.hpp>
+#include <score/model/Skin.hpp>
+#include <score/serialization/StringConstants.hpp>
+#include <score/tools/Debug.hpp>
+
+#include <ossia/detail/math.hpp>
+#include <ossia/network/domain/domain_functions.hpp>
+#include <ossia/network/value/value_conversion.hpp>
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+
+#include <wobjectimpl.h>
+W_OBJECT_IMPL(score::QGraphicsMultiSliderXY);
+
+namespace score
+{
+score::QGraphicsMultiSliderXY::QGraphicsMultiSliderXY(QGraphicsItem* parent)
+{
+  auto& skin = score::Skin::instance();
+  setCursor(skin.CursorPointingHand);
+  this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
+}
+
+void score::QGraphicsMultiSliderXY::setPoint(const QPointF& r)
+{
+  SCORE_TODO;
path: src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
user: login: jcelerier
body: TODO
created_at: 2025-03-26T17:49:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,164 @@
+#include <score/graphics/DefaultGraphicsSliderImpl.hpp>
+#include <score/graphics/widgets/QGraphicsMultiSliderXY.hpp>
+#include <score/model/Skin.hpp>
+#include <score/serialization/StringConstants.hpp>
+#include <score/tools/Debug.hpp>
+
+#include <ossia/detail/math.hpp>
+#include <ossia/network/domain/domain_functions.hpp>
+#include <ossia/network/value/value_conversion.hpp>
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+
+#include <wobjectimpl.h>
+W_OBJECT_IMPL(score::QGraphicsMultiSliderXY);
+
+namespace score
+{
+score::QGraphicsMultiSliderXY::QGraphicsMultiSliderXY(QGraphicsItem* parent)
+{
+  auto& skin = score::Skin::instance();
+  setCursor(skin.CursorPointingHand);
+  this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
+}
+
+void score::QGraphicsMultiSliderXY::setPoint(const QPointF& r)
+{
+  SCORE_TODO;
+}
+
+void score::QGraphicsMultiSliderXY::paint(
+    QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+
+  tab = m_value.get<std::vector<ossia::value>>();
+
+
+  // Draw the background
+  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
path: src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
user: login: jcelerier
body: Instead of hardcoding colors, could you use some from the skin ? 
see examples here: 

https://github.com/ossia/score/blob/master/src/lib/score/graphics/widgets/QGraphicsRangeSlider.cpp#L150-L155
created_at: 2025-03-26T17:51:09Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,164 @@
+#include <score/graphics/DefaultGraphicsSliderImpl.hpp>
+#include <score/graphics/widgets/QGraphicsMultiSliderXY.hpp>
+#include <score/model/Skin.hpp>
+#include <score/serialization/StringConstants.hpp>
+#include <score/tools/Debug.hpp>
+
+#include <ossia/detail/math.hpp>
+#include <ossia/network/domain/domain_functions.hpp>
+#include <ossia/network/value/value_conversion.hpp>
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+
+#include <wobjectimpl.h>
+W_OBJECT_IMPL(score::QGraphicsMultiSliderXY);
+
+namespace score
+{
+score::QGraphicsMultiSliderXY::QGraphicsMultiSliderXY(QGraphicsItem* parent)
+{
+  auto& skin = score::Skin::instance();
+  setCursor(skin.CursorPointingHand);
+  this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
+}
+
+void score::QGraphicsMultiSliderXY::setPoint(const QPointF& r)
+{
+  SCORE_TODO;
+}
+
+void score::QGraphicsMultiSliderXY::paint(
+    QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+
+  tab = m_value.get<std::vector<ossia::value>>();
+
+
+  // Draw the background
+  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(qRgba(10, 10, 10, 255)));
+
+  // Draw cursors
+  for (ossia::value& v : tab)
path: src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
user: login: jcelerier
body: `const ossia::value& v` since we aren't going to modify it
created_at: 2025-03-26T17:51:32Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,164 @@
+#include <score/graphics/DefaultGraphicsSliderImpl.hpp>
+#include <score/graphics/widgets/QGraphicsMultiSliderXY.hpp>
+#include <score/model/Skin.hpp>
+#include <score/serialization/StringConstants.hpp>
+#include <score/tools/Debug.hpp>
+
+#include <ossia/detail/math.hpp>
+#include <ossia/network/domain/domain_functions.hpp>
+#include <ossia/network/value/value_conversion.hpp>
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+
+#include <wobjectimpl.h>
+W_OBJECT_IMPL(score::QGraphicsMultiSliderXY);
+
+namespace score
+{
+score::QGraphicsMultiSliderXY::QGraphicsMultiSliderXY(QGraphicsItem* parent)
+{
+  auto& skin = score::Skin::instance();
+  setCursor(skin.CursorPointingHand);
+  this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
+}
+
+void score::QGraphicsMultiSliderXY::setPoint(const QPointF& r)
+{
+  SCORE_TODO;
+}
+
+void score::QGraphicsMultiSliderXY::paint(
+    QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+
+  tab = m_value.get<std::vector<ossia::value>>();
path: src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
user: login: jcelerier
body: Are we 100% sure it's always going to be a std::vector<ossia::value> ? 
created_at: 2025-03-26T17:51:59Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,164 @@
+#include <score/graphics/DefaultGraphicsSliderImpl.hpp>
+#include <score/graphics/widgets/QGraphicsMultiSliderXY.hpp>
+#include <score/model/Skin.hpp>
+#include <score/serialization/StringConstants.hpp>
+#include <score/tools/Debug.hpp>
+
+#include <ossia/detail/math.hpp>
+#include <ossia/network/domain/domain_functions.hpp>
+#include <ossia/network/value/value_conversion.hpp>
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+
+#include <wobjectimpl.h>
+W_OBJECT_IMPL(score::QGraphicsMultiSliderXY);
+
+namespace score
+{
+score::QGraphicsMultiSliderXY::QGraphicsMultiSliderXY(QGraphicsItem* parent)
+{
+  auto& skin = score::Skin::instance();
+  setCursor(skin.CursorPointingHand);
+  this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
+}
+
+void score::QGraphicsMultiSliderXY::setPoint(const QPointF& r)
+{
+  SCORE_TODO;
+}
+
+void score::QGraphicsMultiSliderXY::paint(
+    QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+
+  tab = m_value.get<std::vector<ossia::value>>();
+
+
+  // Draw the background
+  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(qRgba(10, 10, 10, 255)));
+
+  // Draw cursors
+  for (ossia::value& v : tab)
+  {
+
+    painter->fillRect(
+        QRectF((v.get<ossia::vec2f>()[0] - cursorSize.x / 2) * width(),
path: src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
user: login: jcelerier
body: you can do the get<ossia::vec2f>() in a variable before like we saw in the othe patch
created_at: 2025-03-26T17:52:19Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,164 @@
+#include <score/graphics/DefaultGraphicsSliderImpl.hpp>
+#include <score/graphics/widgets/QGraphicsMultiSliderXY.hpp>
+#include <score/model/Skin.hpp>
+#include <score/serialization/StringConstants.hpp>
+#include <score/tools/Debug.hpp>
+
+#include <ossia/detail/math.hpp>
+#include <ossia/network/domain/domain_functions.hpp>
+#include <ossia/network/value/value_conversion.hpp>
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+
+#include <wobjectimpl.h>
+W_OBJECT_IMPL(score::QGraphicsMultiSliderXY);
+
+namespace score
+{
+score::QGraphicsMultiSliderXY::QGraphicsMultiSliderXY(QGraphicsItem* parent)
+{
+  auto& skin = score::Skin::instance();
+  setCursor(skin.CursorPointingHand);
+  this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
+}
+
+void score::QGraphicsMultiSliderXY::setPoint(const QPointF& r)
+{
+  SCORE_TODO;
+}
+
+void score::QGraphicsMultiSliderXY::paint(
+    QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+
+  tab = m_value.get<std::vector<ossia::value>>();
+
+
+  // Draw the background
+  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(qRgba(10, 10, 10, 255)));
+
+  // Draw cursors
+  for (ossia::value& v : tab)
+  {
+
+    painter->fillRect(
+        QRectF((v.get<ossia::vec2f>()[0] - cursorSize.x / 2) * width(),
+               (v.get<ossia::vec2f>()[1] - cursorSize.y / 2) * height(),
+               cursorSize.x * width(),
+               cursorSize.y * height()),
+        QColor(qRgba(0, 0, 255, 255)));
+  }
+}
+
+ossia::value score::QGraphicsMultiSliderXY::value() const
+{
+  return m_value;
+}
+
+void score::QGraphicsMultiSliderXY::setRange(const ossia::value& min, const ossia::value& max)
+{
+  this->min = ossia::convert<float>(min);
+  this->max = ossia::convert<float>(max);
+}
+
+void score::QGraphicsMultiSliderXY::setRange(const ossia::domain& dom)
+{
+  auto [min, max] = ossia::get_float_minmax(dom);
+
+  if (min)
+    this->min = *min;
+  if (max)
+    this->max = *max;
+}
+
+void score::QGraphicsMultiSliderXY::setValue(ossia::value v)
+{
+  prepareGeometryChange();
+  m_value = v;
path: src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
user: login: jcelerier
body: std::move(v)
created_at: 2025-03-26T17:52:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,164 @@
+#include <score/graphics/DefaultGraphicsSliderImpl.hpp>
+#include <score/graphics/widgets/QGraphicsMultiSliderXY.hpp>
+#include <score/model/Skin.hpp>
+#include <score/serialization/StringConstants.hpp>
+#include <score/tools/Debug.hpp>
+
+#include <ossia/detail/math.hpp>
+#include <ossia/network/domain/domain_functions.hpp>
+#include <ossia/network/value/value_conversion.hpp>
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+
+#include <wobjectimpl.h>
+W_OBJECT_IMPL(score::QGraphicsMultiSliderXY);
+
+namespace score
+{
+score::QGraphicsMultiSliderXY::QGraphicsMultiSliderXY(QGraphicsItem* parent)
+{
+  auto& skin = score::Skin::instance();
+  setCursor(skin.CursorPointingHand);
+  this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
+}
+
+void score::QGraphicsMultiSliderXY::setPoint(const QPointF& r)
+{
+  SCORE_TODO;
+}
+
+void score::QGraphicsMultiSliderXY::paint(
+    QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+
+  tab = m_value.get<std::vector<ossia::value>>();
+
+
+  // Draw the background
+  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(qRgba(10, 10, 10, 255)));
+
+  // Draw cursors
+  for (ossia::value& v : tab)
+  {
+
+    painter->fillRect(
+        QRectF((v.get<ossia::vec2f>()[0] - cursorSize.x / 2) * width(),
+               (v.get<ossia::vec2f>()[1] - cursorSize.y / 2) * height(),
+               cursorSize.x * width(),
+               cursorSize.y * height()),
+        QColor(qRgba(0, 0, 255, 255)));
+  }
+}
+
+ossia::value score::QGraphicsMultiSliderXY::value() const
+{
+  return m_value;
+}
+
+void score::QGraphicsMultiSliderXY::setRange(const ossia::value& min, const ossia::value& max)
+{
+  this->min = ossia::convert<float>(min);
+  this->max = ossia::convert<float>(max);
+}
+
+void score::QGraphicsMultiSliderXY::setRange(const ossia::domain& dom)
+{
+  auto [min, max] = ossia::get_float_minmax(dom);
+
+  if (min)
+    this->min = *min;
+  if (max)
+    this->max = *max;
+}
+
+void score::QGraphicsMultiSliderXY::setValue(ossia::value v)
+{
+  prepareGeometryChange();
+  m_value = v;
+  update();
+}
+
+void score::QGraphicsMultiSliderXY::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
+{
+  if (m_grab) // The cursor’s position is updated as the mouse moves, constrained within the widget’s bounds.
+  {
+    tab[selectedCursor] = ossia::vec2f{(float)std::clamp((event->pos().x() / width()), 0., 1.),
+                                       (float)std::clamp((event->pos().y() / height()), 0., 1.)};
+    m_value = std::vector<ossia::value>(tab.begin(), tab.end());
+    sliderMoved();
+    update();
+  }
+}
+
+void score::QGraphicsMultiSliderXY::mousePressEvent(QGraphicsSceneMouseEvent* event)
+{
path: src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
user: login: jcelerier
body: just in case I'd reset selectedCursor to -1 here
created_at: 2025-03-26T17:53:29Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,164 @@
+#include <score/graphics/DefaultGraphicsSliderImpl.hpp>
+#include <score/graphics/widgets/QGraphicsMultiSliderXY.hpp>
+#include <score/model/Skin.hpp>
+#include <score/serialization/StringConstants.hpp>
+#include <score/tools/Debug.hpp>
+
+#include <ossia/detail/math.hpp>
+#include <ossia/network/domain/domain_functions.hpp>
+#include <ossia/network/value/value_conversion.hpp>
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+
+#include <wobjectimpl.h>
+W_OBJECT_IMPL(score::QGraphicsMultiSliderXY);
+
+namespace score
+{
+score::QGraphicsMultiSliderXY::QGraphicsMultiSliderXY(QGraphicsItem* parent)
+{
+  auto& skin = score::Skin::instance();
+  setCursor(skin.CursorPointingHand);
+  this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
+}
+
+void score::QGraphicsMultiSliderXY::setPoint(const QPointF& r)
+{
+  SCORE_TODO;
+}
+
+void score::QGraphicsMultiSliderXY::paint(
+    QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+
+  tab = m_value.get<std::vector<ossia::value>>();
+
+
+  // Draw the background
+  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(qRgba(10, 10, 10, 255)));
+
+  // Draw cursors
+  for (ossia::value& v : tab)
+  {
+
+    painter->fillRect(
+        QRectF((v.get<ossia::vec2f>()[0] - cursorSize.x / 2) * width(),
+               (v.get<ossia::vec2f>()[1] - cursorSize.y / 2) * height(),
+               cursorSize.x * width(),
+               cursorSize.y * height()),
+        QColor(qRgba(0, 0, 255, 255)));
+  }
+}
+
+ossia::value score::QGraphicsMultiSliderXY::value() const
+{
+  return m_value;
+}
+
+void score::QGraphicsMultiSliderXY::setRange(const ossia::value& min, const ossia::value& max)
+{
+  this->min = ossia::convert<float>(min);
+  this->max = ossia::convert<float>(max);
+}
+
+void score::QGraphicsMultiSliderXY::setRange(const ossia::domain& dom)
+{
+  auto [min, max] = ossia::get_float_minmax(dom);
+
+  if (min)
+    this->min = *min;
+  if (max)
+    this->max = *max;
+}
+
+void score::QGraphicsMultiSliderXY::setValue(ossia::value v)
+{
+  prepareGeometryChange();
+  m_value = v;
+  update();
+}
+
+void score::QGraphicsMultiSliderXY::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
+{
+  if (m_grab) // The cursor’s position is updated as the mouse moves, constrained within the widget’s bounds.
path: src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
user: login: jcelerier
body: `if(m_grab && ossia::valid_index(selectedCursor, tab)) ` for safety
created_at: 2025-03-26T17:55:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,164 @@
+#include <score/graphics/DefaultGraphicsSliderImpl.hpp>
+#include <score/graphics/widgets/QGraphicsMultiSliderXY.hpp>
+#include <score/model/Skin.hpp>
+#include <score/serialization/StringConstants.hpp>
+#include <score/tools/Debug.hpp>
+
+#include <ossia/detail/math.hpp>
+#include <ossia/network/domain/domain_functions.hpp>
+#include <ossia/network/value/value_conversion.hpp>
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+
+#include <wobjectimpl.h>
+W_OBJECT_IMPL(score::QGraphicsMultiSliderXY);
+
+namespace score
+{
+score::QGraphicsMultiSliderXY::QGraphicsMultiSliderXY(QGraphicsItem* parent)
+{
+  auto& skin = score::Skin::instance();
+  setCursor(skin.CursorPointingHand);
+  this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
+}
+
+void score::QGraphicsMultiSliderXY::setPoint(const QPointF& r)
+{
+  SCORE_TODO;
+}
+
+void score::QGraphicsMultiSliderXY::paint(
+    QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+
+  tab = m_value.get<std::vector<ossia::value>>();
+
+
+  // Draw the background
+  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(qRgba(10, 10, 10, 255)));
+
+  // Draw cursors
+  for (ossia::value& v : tab)
+  {
+
+    painter->fillRect(
+        QRectF((v.get<ossia::vec2f>()[0] - cursorSize.x / 2) * width(),
+               (v.get<ossia::vec2f>()[1] - cursorSize.y / 2) * height(),
+               cursorSize.x * width(),
+               cursorSize.y * height()),
+        QColor(qRgba(0, 0, 255, 255)));
+  }
+}
+
+ossia::value score::QGraphicsMultiSliderXY::value() const
+{
+  return m_value;
+}
+
+void score::QGraphicsMultiSliderXY::setRange(const ossia::value& min, const ossia::value& max)
+{
+  this->min = ossia::convert<float>(min);
+  this->max = ossia::convert<float>(max);
+}
+
+void score::QGraphicsMultiSliderXY::setRange(const ossia::domain& dom)
+{
+  auto [min, max] = ossia::get_float_minmax(dom);
+
+  if (min)
+    this->min = *min;
+  if (max)
+    this->max = *max;
+}
+
+void score::QGraphicsMultiSliderXY::setValue(ossia::value v)
+{
+  prepareGeometryChange();
+  m_value = v;
+  update();
+}
+
+void score::QGraphicsMultiSliderXY::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
+{
+  if (m_grab) // The cursor’s position is updated as the mouse moves, constrained within the widget’s bounds.
+  {
+    tab[selectedCursor] = ossia::vec2f{(float)std::clamp((event->pos().x() / width()), 0., 1.),
+                                       (float)std::clamp((event->pos().y() / height()), 0., 1.)};
+    m_value = std::vector<ossia::value>(tab.begin(), tab.end());
+    sliderMoved();
+    update();
+  }
+}
+
+void score::QGraphicsMultiSliderXY::mousePressEvent(QGraphicsSceneMouseEvent* event)
+{
+  float x = event->pos().x();
+  float y = event->pos().y();
+
+  // If the left mouse button is pressed on an existing cursor, that cursor can be moved.
+  if (event->button() & Qt::LeftButton)
+  {
+    for (int v = 0; v < tab.size(); v++)
+    {
+      if ((tab[v].get<ossia::vec2f>()[0] - 0.02f) * width() <= (float)x &&
path: src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
user: login: jcelerier
body: `auto& point = tab[v].get<ossia::vec2f>();` before the `if` would make this code much clearer
created_at: 2025-03-26T17:56:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,164 @@
+#include <score/graphics/DefaultGraphicsSliderImpl.hpp>
+#include <score/graphics/widgets/QGraphicsMultiSliderXY.hpp>
+#include <score/model/Skin.hpp>
+#include <score/serialization/StringConstants.hpp>
+#include <score/tools/Debug.hpp>
+
+#include <ossia/detail/math.hpp>
+#include <ossia/network/domain/domain_functions.hpp>
+#include <ossia/network/value/value_conversion.hpp>
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+
+#include <wobjectimpl.h>
+W_OBJECT_IMPL(score::QGraphicsMultiSliderXY);
+
+namespace score
+{
+score::QGraphicsMultiSliderXY::QGraphicsMultiSliderXY(QGraphicsItem* parent)
+{
+  auto& skin = score::Skin::instance();
+  setCursor(skin.CursorPointingHand);
+  this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
+}
+
+void score::QGraphicsMultiSliderXY::setPoint(const QPointF& r)
+{
+  SCORE_TODO;
+}
+
+void score::QGraphicsMultiSliderXY::paint(
+    QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+
+  tab = m_value.get<std::vector<ossia::value>>();
+
+
+  // Draw the background
+  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(qRgba(10, 10, 10, 255)));
+
+  // Draw cursors
+  for (ossia::value& v : tab)
+  {
+
+    painter->fillRect(
+        QRectF((v.get<ossia::vec2f>()[0] - cursorSize.x / 2) * width(),
+               (v.get<ossia::vec2f>()[1] - cursorSize.y / 2) * height(),
+               cursorSize.x * width(),
+               cursorSize.y * height()),
+        QColor(qRgba(0, 0, 255, 255)));
+  }
+}
+
+ossia::value score::QGraphicsMultiSliderXY::value() const
+{
+  return m_value;
+}
+
+void score::QGraphicsMultiSliderXY::setRange(const ossia::value& min, const ossia::value& max)
+{
+  this->min = ossia::convert<float>(min);
+  this->max = ossia::convert<float>(max);
+}
+
+void score::QGraphicsMultiSliderXY::setRange(const ossia::domain& dom)
+{
+  auto [min, max] = ossia::get_float_minmax(dom);
+
+  if (min)
+    this->min = *min;
+  if (max)
+    this->max = *max;
+}
+
+void score::QGraphicsMultiSliderXY::setValue(ossia::value v)
+{
+  prepareGeometryChange();
+  m_value = v;
+  update();
+}
+
+void score::QGraphicsMultiSliderXY::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
+{
+  if (m_grab) // The cursor’s position is updated as the mouse moves, constrained within the widget’s bounds.
+  {
+    tab[selectedCursor] = ossia::vec2f{(float)std::clamp((event->pos().x() / width()), 0., 1.),
+                                       (float)std::clamp((event->pos().y() / height()), 0., 1.)};
+    m_value = std::vector<ossia::value>(tab.begin(), tab.end());
+    sliderMoved();
+    update();
+  }
+}
+
+void score::QGraphicsMultiSliderXY::mousePressEvent(QGraphicsSceneMouseEvent* event)
+{
+  float x = event->pos().x();
+  float y = event->pos().y();
+
+  // If the left mouse button is pressed on an existing cursor, that cursor can be moved.
+  if (event->button() & Qt::LeftButton)
+  {
+    for (int v = 0; v < tab.size(); v++)
+    {
+      if ((tab[v].get<ossia::vec2f>()[0] - 0.02f) * width() <= (float)x &&
+         (float)x <= (tab[v].get<ossia::vec2f>()[0] + 0.02f) * width() &&
+         (tab[v].get<ossia::vec2f>()[1] - 0.02f) * height() <= (float)y &&
+         (float)y <= (tab[v].get<ossia::vec2f>()[1] + 0.02f) * height())
+      {
+        m_grab = true;
+        selectedCursor = v;
+        mouseMoveEvent(event);
+        return;
+      }
+    }
+    // Else if the press occurs at an empty area, a new cursor will be created at that position.
+    tab.push_back(ossia::vec2f{(float)(x / width()), (float)(y / width())});
+    m_value = std::vector<ossia::value>(tab.begin(), tab.end());
+    m_grab = true;
+    selectedCursor = tab.size() - 1;
+    mouseMoveEvent(event);
+    return;
+  }
+  else if (event->button() & Qt::RightButton) //If the right mouse button is pressed over a cursor, the cursor is deleted.
+  {
+    m_grab = false;
+    for (int v = 0; v < tab.size(); v++)
+    {
+      if ((tab[v].get<ossia::vec2f>()[0] - 0.02f) * width() <= (float)x &&
path: src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
user: login: jcelerier
body: same
created_at: 2025-03-26T17:56:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,164 @@
+#include <score/graphics/DefaultGraphicsSliderImpl.hpp>
+#include <score/graphics/widgets/QGraphicsMultiSliderXY.hpp>
+#include <score/model/Skin.hpp>
+#include <score/serialization/StringConstants.hpp>
+#include <score/tools/Debug.hpp>
+
+#include <ossia/detail/math.hpp>
+#include <ossia/network/domain/domain_functions.hpp>
+#include <ossia/network/value/value_conversion.hpp>
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+
+#include <wobjectimpl.h>
+W_OBJECT_IMPL(score::QGraphicsMultiSliderXY);
+
+namespace score
+{
+score::QGraphicsMultiSliderXY::QGraphicsMultiSliderXY(QGraphicsItem* parent)
+{
+  auto& skin = score::Skin::instance();
+  setCursor(skin.CursorPointingHand);
+  this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
+}
+
+void score::QGraphicsMultiSliderXY::setPoint(const QPointF& r)
+{
+  SCORE_TODO;
+}
+
+void score::QGraphicsMultiSliderXY::paint(
+    QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+
+  tab = m_value.get<std::vector<ossia::value>>();
+
+
+  // Draw the background
+  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(qRgba(10, 10, 10, 255)));
+
+  // Draw cursors
+  for (ossia::value& v : tab)
+  {
+
+    painter->fillRect(
+        QRectF((v.get<ossia::vec2f>()[0] - cursorSize.x / 2) * width(),
+               (v.get<ossia::vec2f>()[1] - cursorSize.y / 2) * height(),
+               cursorSize.x * width(),
+               cursorSize.y * height()),
+        QColor(qRgba(0, 0, 255, 255)));
+  }
+}
+
+ossia::value score::QGraphicsMultiSliderXY::value() const
+{
+  return m_value;
+}
+
+void score::QGraphicsMultiSliderXY::setRange(const ossia::value& min, const ossia::value& max)
+{
+  this->min = ossia::convert<float>(min);
+  this->max = ossia::convert<float>(max);
+}
+
+void score::QGraphicsMultiSliderXY::setRange(const ossia::domain& dom)
+{
+  auto [min, max] = ossia::get_float_minmax(dom);
+
+  if (min)
+    this->min = *min;
+  if (max)
+    this->max = *max;
+}
+
+void score::QGraphicsMultiSliderXY::setValue(ossia::value v)
+{
+  prepareGeometryChange();
+  m_value = v;
+  update();
+}
+
+void score::QGraphicsMultiSliderXY::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
+{
+  if (m_grab) // The cursor’s position is updated as the mouse moves, constrained within the widget’s bounds.
+  {
+    tab[selectedCursor] = ossia::vec2f{(float)std::clamp((event->pos().x() / width()), 0., 1.),
+                                       (float)std::clamp((event->pos().y() / height()), 0., 1.)};
+    m_value = std::vector<ossia::value>(tab.begin(), tab.end());
+    sliderMoved();
+    update();
+  }
+}
+
+void score::QGraphicsMultiSliderXY::mousePressEvent(QGraphicsSceneMouseEvent* event)
+{
+  float x = event->pos().x();
+  float y = event->pos().y();
+
+  // If the left mouse button is pressed on an existing cursor, that cursor can be moved.
+  if (event->button() & Qt::LeftButton)
+  {
+    for (int v = 0; v < tab.size(); v++)
path: src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
user: login: jcelerier
body: std::ssize(tab) instead of tab.size() otherwise we get unsigned / signed comparison warnings
created_at: 2025-03-26T17:56:56Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,187 @@
+#include <score/graphics/DefaultGraphicsSliderImpl.hpp>
+#include <score/graphics/widgets/QGraphicsPathGeneratorXY.hpp>
+#include <score/model/Skin.hpp>
+#include <score/serialization/StringConstants.hpp>
+#include <score/tools/Debug.hpp>
+
+#include <ossia/detail/math.hpp>
+#include <ossia/network/domain/domain_functions.hpp>
+#include <ossia/network/value/value_conversion.hpp>
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+
+#include <wobjectimpl.h>
+W_OBJECT_IMPL(score::QGraphicsPathGeneratorXY);
+
+namespace score
+{
+score::QGraphicsPathGeneratorXY::QGraphicsPathGeneratorXY(QGraphicsItem* parent)
+{
+  auto& skin = score::Skin::instance();
+  setCursor(skin.CursorPointingHand);
+  this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
+}
+
+void score::QGraphicsPathGeneratorXY::setPoint(const QPointF& r)
+{
+  SCORE_TODO;
+}
+
+void score::QGraphicsPathGeneratorXY::paint(
+    QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+
+  tab = m_value.get<std::vector<ossia::value>>();
+
+
+  // Draw the background
+  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(qRgba(10, 10, 10, 255)));
+
+  // Draw cursors
+    for (size_t v = 0; v < tab.size(); v++){
+      for (size_t c = 0; c < tab[v].get<std::vector<ossia::value>>().size(); c++)
path: src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp
user: login: jcelerier
body: do we know for sure tab[v] is a std::vector<ossia::value> ? 
created_at: 2025-03-26T17:57:41Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,187 @@
+#include <score/graphics/DefaultGraphicsSliderImpl.hpp>
+#include <score/graphics/widgets/QGraphicsPathGeneratorXY.hpp>
+#include <score/model/Skin.hpp>
+#include <score/serialization/StringConstants.hpp>
+#include <score/tools/Debug.hpp>
+
+#include <ossia/detail/math.hpp>
+#include <ossia/network/domain/domain_functions.hpp>
+#include <ossia/network/value/value_conversion.hpp>
+
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+
+#include <wobjectimpl.h>
+W_OBJECT_IMPL(score::QGraphicsPathGeneratorXY);
+
+namespace score
+{
+score::QGraphicsPathGeneratorXY::QGraphicsPathGeneratorXY(QGraphicsItem* parent)
+{
+  auto& skin = score::Skin::instance();
+  setCursor(skin.CursorPointingHand);
+  this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
+}
+
+void score::QGraphicsPathGeneratorXY::setPoint(const QPointF& r)
+{
+  SCORE_TODO;
+}
+
+void score::QGraphicsPathGeneratorXY::paint(
+    QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+
+  tab = m_value.get<std::vector<ossia::value>>();
+
+
+  // Draw the background
+  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(qRgba(10, 10, 10, 255)));
+
+  // Draw cursors
+    for (size_t v = 0; v < tab.size(); v++){
+      for (size_t c = 0; c < tab[v].get<std::vector<ossia::value>>().size(); c++)
+      {
+
+    ossia::vec2f& s = tab[v].get<std::vector<ossia::value>>()[c].get<ossia::vec2f>();
+
+    painter->fillRect(
+        QRectF((s[0] - cursorSize.x / 2) * width(),
+               (s[1] - cursorSize.y / 2) * height(),
+               cursorSize.x * width(),
+               cursorSize.y * height()),
+        v == selectedSource ? QColor(c == 0 ? qRgba(0, 255, 0, 255) : qRgba(255, 0, 0, 255)):QColor(qRgba(100, 100, 100, 255)));
+
+    }
+      if(v==selectedSource){
+
+        painter->drawLine(
+            QPointF((tab[v].get<std::vector<ossia::value>>()[0].get<ossia::vec2f>()[0]*width()),
path: src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp
user: login: jcelerier
body: intermediate variables
created_at: 2025-03-26T17:57:57Z
author_association: MEMBER
diff_hunk: @@ -63,6 +63,14 @@ enum Waveform
   Noise3
 };
 
+enum Path
path: src/plugins/score-plugin-engine/Engine/Node/CommonWidgets.hpp
user: login: jcelerier
body: Is this used anywhere ? if not it can be removed
created_at: 2025-03-26T17:58:55Z
author_association: MEMBER
diff_hunk: @@ -15,6 +15,7 @@
 #include <QDialogButtonBox>
 #include <QFormLayout>
 #include <QObject>
+#include <QUrl>
path: src/plugins/score-plugin-protocols/Protocols/SimpleIO/SimpleIOProtocolFactory.cpp
user: login: jcelerier
body: I think this has been fixed in score for quite some time now
created_at: 2025-03-26T17:59:16Z
author_association: MEMBER
diff_hunk: @@ -34,21 +34,21 @@ void score::QGraphicsMultiSliderXY::paint(
 
   tab = m_value.get<std::vector<ossia::value>>();
 
+  auto& skin = score::Skin::instance();
 
   // Draw the background
-  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
-  painter->fillRect(QRectF(0, 0, width(), height()), QColor(qRgba(10, 10, 10, 255)));
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(skin.Dark.color()));
 
   // Draw cursors
-  for (ossia::value& v : tab)
+  for (const ossia::value& v : tab)
   {
-
+    auto& Cursor = v.get<ossia::vec2f>();
path: src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
user: login: jcelerier
body: cursor without a capital
created_at: 2025-04-01T14:15:52Z
author_association: MEMBER
diff_hunk: @@ -34,21 +34,21 @@ void score::QGraphicsMultiSliderXY::paint(
 
   tab = m_value.get<std::vector<ossia::value>>();
 
+  auto& skin = score::Skin::instance();
 
   // Draw the background
-  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
-  painter->fillRect(QRectF(0, 0, width(), height()), QColor(qRgba(10, 10, 10, 255)));
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(skin.Dark.color()));
 
   // Draw cursors
-  for (ossia::value& v : tab)
+  for (const ossia::value& v : tab)
   {
-
+    auto& Cursor = v.get<ossia::vec2f>();
     painter->fillRect(
-        QRectF((v.get<ossia::vec2f>()[0] - cursorSize.x / 2) * width(),
-               (v.get<ossia::vec2f>()[1] - cursorSize.y / 2) * height(),
+        QRectF((Cursor[0] - cursorSize.x / 2) * width(),
+               (1-Cursor[1] - cursorSize.y / 2) * height(),
                cursorSize.x * width(),
                cursorSize.y * height()),
-        QColor(qRgba(0, 0, 255, 255)));
+        QColor(skin.Base2.color()));
path: src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
user: login: jcelerier
body: devrait pas y avoir besoin du QColor(...), juste passer directement skin.Base2.color()
created_at: 2025-04-01T14:16:16Z
author_association: MEMBER
diff_hunk: @@ -144,13 +147,12 @@ void score::QGraphicsMultiSliderXY::mousePressEvent(QGraphicsSceneMouseEvent* ev
 
 void score::QGraphicsMultiSliderXY::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
 {
-  float x = event->pos().x();
-  float y = event->pos().y();
-
   if ((event->button() & Qt::LeftButton) && m_grab)
   {
-    m_grab = true;
-    mouseMoveEvent(event);
+    m_grab = false;
+    selectedCursor=-1;
+    //mouseMoveEvent(event);
path: src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
user: login: jcelerier
body: est-ce que le mouseMoveEvent posait problème ici ? 
created_at: 2025-04-01T14:16:57Z
author_association: MEMBER
diff_hunk: @@ -23,44 +24,37 @@ score::QGraphicsPathGeneratorXY::QGraphicsPathGeneratorXY(QGraphicsItem* parent)
   this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
 }
 
-void score::QGraphicsPathGeneratorXY::setPoint(const QPointF& r)
-{
-  SCORE_TODO;
-}
-
 void score::QGraphicsPathGeneratorXY::paint(
     QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
 {
-
   tab = m_value.get<std::vector<ossia::value>>();
-
+  auto& skin = score::Skin::instance();
 
   // Draw the background
-  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
-  painter->fillRect(QRectF(0, 0, width(), height()), QColor(qRgba(10, 10, 10, 255)));
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(skin.Dark.color()));
path: src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp
user: login: jcelerier
body: - `QRectF(0, 0, width(), height())` est équivalent à `boundingRect()`
-  pareil, pas besoin de construire un QColor explicite


created_at: 2025-04-01T14:17:32Z
author_association: MEMBER
diff_hunk: @@ -23,44 +24,37 @@ score::QGraphicsPathGeneratorXY::QGraphicsPathGeneratorXY(QGraphicsItem* parent)
   this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
 }
 
-void score::QGraphicsPathGeneratorXY::setPoint(const QPointF& r)
-{
-  SCORE_TODO;
-}
-
 void score::QGraphicsPathGeneratorXY::paint(
     QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
 {
-
   tab = m_value.get<std::vector<ossia::value>>();
-
+  auto& skin = score::Skin::instance();
 
   // Draw the background
-  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
-  painter->fillRect(QRectF(0, 0, width(), height()), QColor(qRgba(10, 10, 10, 255)));
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(skin.Dark.color()));
 
   // Draw cursors
-    for (size_t v = 0; v < tab.size(); v++){
-      for (size_t c = 0; c < tab[v].get<std::vector<ossia::value>>().size(); c++)
+    for (size_t s = 0; s < std::ssize(tab); s++){
+      for (size_t c = 0; c < std::ssize(tab[s].get<std::vector<ossia::value>>()); c++)
       {
 
-    ossia::vec2f& s = tab[v].get<std::vector<ossia::value>>()[c].get<ossia::vec2f>();
+    ossia::vec2f& cursorXY = tab[s].get<std::vector<ossia::value>>()[c].get<ossia::vec2f>();
 
     painter->fillRect(
-        QRectF((s[0] - cursorSize.x / 2) * width(),
-               (s[1] - cursorSize.y / 2) * height(),
+        QRectF((cursorXY[0] - cursorSize.x / 2) * width(),
path: src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp
user: login: jcelerier
body: je recommande de mettre les QRectF et QColor construits dans des variables séparées pour rendre le code plus clair
created_at: 2025-04-01T14:18:09Z
author_association: MEMBER
diff_hunk: @@ -23,44 +24,37 @@ score::QGraphicsPathGeneratorXY::QGraphicsPathGeneratorXY(QGraphicsItem* parent)
   this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
 }
 
-void score::QGraphicsPathGeneratorXY::setPoint(const QPointF& r)
-{
-  SCORE_TODO;
-}
-
 void score::QGraphicsPathGeneratorXY::paint(
     QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
 {
-
   tab = m_value.get<std::vector<ossia::value>>();
-
+  auto& skin = score::Skin::instance();
 
   // Draw the background
-  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
-  painter->fillRect(QRectF(0, 0, width(), height()), QColor(qRgba(10, 10, 10, 255)));
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(skin.Dark.color()));
 
   // Draw cursors
-    for (size_t v = 0; v < tab.size(); v++){
-      for (size_t c = 0; c < tab[v].get<std::vector<ossia::value>>().size(); c++)
+    for (size_t s = 0; s < std::ssize(tab); s++){
+      for (size_t c = 0; c < std::ssize(tab[s].get<std::vector<ossia::value>>()); c++)
path: src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp
user: login: jcelerier
body: est-ce que pour cette boucle for on voudrait pas juste: 

```C++
for (size_t s = 0; s < std::ssize(tab); s++)
{
  for (const auto& sub_element : tab[s].get<std::vector<ossia::value>>()) {
     const ossia::vec2f cursorXY = sub_element.get<ossia::vec2f>();
     ...
  }
} 
```
created_at: 2025-04-01T14:20:42Z
author_association: MEMBER
diff_hunk: @@ -23,44 +24,37 @@ score::QGraphicsPathGeneratorXY::QGraphicsPathGeneratorXY(QGraphicsItem* parent)
   this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
 }
 
-void score::QGraphicsPathGeneratorXY::setPoint(const QPointF& r)
-{
-  SCORE_TODO;
-}
-
 void score::QGraphicsPathGeneratorXY::paint(
     QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
 {
-
   tab = m_value.get<std::vector<ossia::value>>();
-
+  auto& skin = score::Skin::instance();
 
   // Draw the background
-  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
-  painter->fillRect(QRectF(0, 0, width(), height()), QColor(qRgba(10, 10, 10, 255)));
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(skin.Dark.color()));
 
   // Draw cursors
-    for (size_t v = 0; v < tab.size(); v++){
-      for (size_t c = 0; c < tab[v].get<std::vector<ossia::value>>().size(); c++)
+    for (size_t s = 0; s < std::ssize(tab); s++){
+      for (size_t c = 0; c < std::ssize(tab[s].get<std::vector<ossia::value>>()); c++)
path: src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp
user: login: jcelerier
body: ce qui évite d'introduire la variable c qu'on utilise pas par la suite
created_at: 2025-04-01T14:21:02Z
author_association: MEMBER
diff_hunk: @@ -23,44 +24,37 @@ score::QGraphicsPathGeneratorXY::QGraphicsPathGeneratorXY(QGraphicsItem* parent)
   this->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
 }
 
-void score::QGraphicsPathGeneratorXY::setPoint(const QPointF& r)
-{
-  SCORE_TODO;
-}
-
 void score::QGraphicsPathGeneratorXY::paint(
     QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
 {
-
   tab = m_value.get<std::vector<ossia::value>>();
-
+  auto& skin = score::Skin::instance();
 
   // Draw the background
-  painter->setBrush(QColor(qRgba(10, 10, 10, 255)));
-  painter->fillRect(QRectF(0, 0, width(), height()), QColor(qRgba(10, 10, 10, 255)));
+  painter->fillRect(QRectF(0, 0, width(), height()), QColor(skin.Dark.color()));
 
   // Draw cursors
-    for (size_t v = 0; v < tab.size(); v++){
-      for (size_t c = 0; c < tab[v].get<std::vector<ossia::value>>().size(); c++)
+    for (size_t s = 0; s < std::ssize(tab); s++){
+      for (size_t c = 0; c < std::ssize(tab[s].get<std::vector<ossia::value>>()); c++)
       {
 
-    ossia::vec2f& s = tab[v].get<std::vector<ossia::value>>()[c].get<ossia::vec2f>();
+    ossia::vec2f& cursorXY = tab[s].get<std::vector<ossia::value>>()[c].get<ossia::vec2f>();
 
     painter->fillRect(
-        QRectF((s[0] - cursorSize.x / 2) * width(),
-               (s[1] - cursorSize.y / 2) * height(),
+        QRectF((cursorXY[0] - cursorSize.x / 2) * width(),
+               (1-cursorXY[1] - cursorSize.y / 2) * height(),
                cursorSize.x * width(),
                cursorSize.y * height()),
-        v == selectedSource ? QColor(c == 0 ? qRgba(0, 255, 0, 255) : qRgba(255, 0, 0, 255)):QColor(qRgba(100, 100, 100, 255)));
+        s == selectedSource ? QColor(c == 0 ? skin.Base3.color(): skin.Warn3.color()):QColor(skin.DarkGray.color()));
 
     }
-      if(v==selectedSource){
-
+      if(s==selectedSource){
+        auto& point = tab[s].get<std::vector<ossia::value>>();
         painter->drawLine(
-            QPointF((tab[v].get<std::vector<ossia::value>>()[0].get<ossia::vec2f>()[0]*width()),
-                    tab[v].get<std::vector<ossia::value>>()[0].get<ossia::vec2f>()[1]*height()),
-            QPointF((tab[v].get<std::vector<ossia::value>>()[1].get<ossia::vec2f>()[0]*width()),
-                    tab[v].get<std::vector<ossia::value>>()[1].get<ossia::vec2f>()[1]*height()));
+            QPointF((point[0].get<ossia::vec2f>()[0]*width()),
path: src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp
user: login: jcelerier
body: je mettrais `point[0].get<ossia::vec2f>()` en variable séparée pour la clarté du code
created_at: 2025-04-01T14:21:29Z
author_association: MEMBER
diff_hunk: @@ -115,13 +111,13 @@ void score::QGraphicsPathGeneratorXY::mousePressEvent(QGraphicsSceneMouseEvent*
   // If the left mouse button is pressed on an existing cursor, that cursor can be moved.
   if (event->button() & Qt::LeftButton)
   {
-    for (int v = 0; v < tab.size(); v++){
-      for (int c = 0; c < tab[v].get<std::vector<ossia::value>>().size(); c++)
+    for (int v = 0; v < std::ssize(tab); v++){
path: src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp
user: login: jcelerier
body: même commentaire que tantôt
created_at: 2025-04-01T14:22:04Z
author_association: MEMBER
diff_hunk: @@ -115,13 +111,13 @@ void score::QGraphicsPathGeneratorXY::mousePressEvent(QGraphicsSceneMouseEvent*
   // If the left mouse button is pressed on an existing cursor, that cursor can be moved.
   if (event->button() & Qt::LeftButton)
   {
-    for (int v = 0; v < tab.size(); v++){
-      for (int c = 0; c < tab[v].get<std::vector<ossia::value>>().size(); c++)
+    for (int v = 0; v < std::ssize(tab); v++){
+      for (int c = 0; c < std::ssize(tab[v].get<std::vector<ossia::value>>()); c++)
     {
       if ((tab[v].get<std::vector<ossia::value>>()[c].get<ossia::vec2f>()[0] - 0.02f) * width() <= (float)x &&
path: src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp
user: login: jcelerier
body: faire passer `tab[v].get<std::vector<ossia::value>>()[c].get<ossia::vec2f>()` dans une variable
created_at: 2025-04-01T14:22:33Z
author_association: MEMBER
diff_hunk: @@ -144,13 +140,13 @@ void score::QGraphicsPathGeneratorXY::mousePressEvent(QGraphicsSceneMouseEvent*
   else if (event->button() & Qt::RightButton) //If the right mouse button is pressed over a cursor, the cursor is deleted.
   {
     m_grab = false;
-    for (int v = 0; v < tab.size(); v++){
-    for (int c = 0; c < tab[v].get<std::vector<ossia::value>>().size(); c++)
+    for (int v = 0; v < std::ssize(tab); v++){
path: src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp
user: login: jcelerier
body: same comments as above
created_at: 2025-04-01T14:22:49Z
author_association: MEMBER
diff_hunk: @@ -144,13 +147,12 @@ void score::QGraphicsMultiSliderXY::mousePressEvent(QGraphicsSceneMouseEvent* ev
 
 void score::QGraphicsMultiSliderXY::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
 {
-  float x = event->pos().x();
-  float y = event->pos().y();
-
   if ((event->button() & Qt::LeftButton) && m_grab)
   {
-    m_grab = true;
-    mouseMoveEvent(event);
+    m_grab = false;
+    selectedCursor=-1;
+    //mouseMoveEvent(event);
path: src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
user: login: D3mh4
body: La fonction est inutile ici, car elle a pour conditions m_grab et selectedCursor. Elle ne s'exécutera donc pas. Et il n'y a aucun intérêt à déplacer le curseur lorsque le clic est relâché.
created_at: 2025-04-01T17:49:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -144,13 +147,12 @@ void score::QGraphicsMultiSliderXY::mousePressEvent(QGraphicsSceneMouseEvent* ev
 
 void score::QGraphicsMultiSliderXY::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
 {
-  float x = event->pos().x();
-  float y = event->pos().y();
-
   if ((event->button() & Qt::LeftButton) && m_grab)
   {
-    m_grab = true;
-    mouseMoveEvent(event);
+    m_grab = false;
+    selectedCursor=-1;
+    //mouseMoveEvent(event);
path: src/lib/score/graphics/widgets/QGraphicsMultiSliderXY.cpp
user: login: jcelerier
body: en fait il y en a un: parfois l'évenement de mouseRelease a pas la même position que le dernier mouseMove si jamais la souris était en train de bouger rapidement. Du coup quand on relache le dernier truc est quelques pixels à côté de là où il devrait être. Je ferais: 

1. mouseMoveEvent
2. m_grab = falsse; selectedCursor = -1; 
created_at: 2025-04-01T18:14:24Z
author_association: MEMBER
diff_hunk: @@ -115,13 +111,13 @@ void score::QGraphicsPathGeneratorXY::mousePressEvent(QGraphicsSceneMouseEvent*
   // If the left mouse button is pressed on an existing cursor, that cursor can be moved.
   if (event->button() & Qt::LeftButton)
   {
-    for (int v = 0; v < tab.size(); v++){
-      for (int c = 0; c < tab[v].get<std::vector<ossia::value>>().size(); c++)
+    for (int v = 0; v < std::ssize(tab); v++){
path: src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp
user: login: D3mh4
body: les variables v et c me permettent de stocker dans des variables int l'Index de la source et le curseur sélectionnées.

`        selectedSource = v;
        selectedCursor = c;`
created_at: 2025-04-01T20:20:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -115,13 +111,13 @@ void score::QGraphicsPathGeneratorXY::mousePressEvent(QGraphicsSceneMouseEvent*
   // If the left mouse button is pressed on an existing cursor, that cursor can be moved.
   if (event->button() & Qt::LeftButton)
   {
-    for (int v = 0; v < tab.size(); v++){
-      for (int c = 0; c < tab[v].get<std::vector<ossia::value>>().size(); c++)
+    for (int v = 0; v < std::ssize(tab); v++){
path: src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp
user: login: D3mh4
body: je peux modifier les noms pour que ce soit mieux lisible
created_at: 2025-04-01T20:21:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -115,13 +111,13 @@ void score::QGraphicsPathGeneratorXY::mousePressEvent(QGraphicsSceneMouseEvent*
   // If the left mouse button is pressed on an existing cursor, that cursor can be moved.
   if (event->button() & Qt::LeftButton)
   {
-    for (int v = 0; v < tab.size(); v++){
-      for (int c = 0; c < tab[v].get<std::vector<ossia::value>>().size(); c++)
+    for (int v = 0; v < std::ssize(tab); v++){
path: src/lib/score/graphics/widgets/QGraphicsPathGeneratorXY.cpp
user: login: jcelerier
body: ahh bien vu, oublions ce commentaire alors !
created_at: 2025-04-01T20:21:56Z
author_association: MEMBER

PR comments:
user: login: jcelerier
created_at: 2025-03-26T17:27:24Z
body: I think the multicursors plugin can be removed as it went into GBAP
author_association: MEMBER
user: login: jcelerier
created_at: 2025-04-01T20:22:14Z
body: une fois réglé il faudra juste fixer le pb de build
author_association: MEMBER
user: login: jcelerier
created_at: 2025-04-01T21:08:16Z
body: relaunching a build, let's see if it gives something! 
author_association: MEMBER
user: login: jcelerier
created_at: 2025-04-01T21:08:50Z
body: I'm setting it as "Merge when ready" so if the build succeeds it will be merged automatically, does that work with you ?

author_association: MEMBER