Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions gui-js/libs/shared/src/lib/backend/minsky.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,7 @@ export class Group extends Item {
async portY(a1: number): Promise<number> {return this.$callMethod('portY',a1);}
async ports(a1: number): Promise<object> {return this.$callMethod('ports',a1);}
async portsSize(): Promise<number> {return this.$callMethod('portsSize');}
async positionEdgeVariables(): Promise<void> {return this.$callMethod('positionEdgeVariables');}
async px(...args: number[]): Promise<number> {return this.$callMethod('px',...args);}
async py(...args: number[]): Promise<number> {return this.$callMethod('py',...args);}
async pz(...args: number[]): Promise<number> {return this.$callMethod('pz',...args);}
Expand Down Expand Up @@ -2030,6 +2031,7 @@ export class Selection extends CppClass {
async portY(a1: number): Promise<number> {return this.$callMethod('portY',a1);}
async ports(a1: number): Promise<object> {return this.$callMethod('ports',a1);}
async portsSize(): Promise<number> {return this.$callMethod('portsSize');}
async positionEdgeVariables(): Promise<void> {return this.$callMethod('positionEdgeVariables');}
async randomLayout(): Promise<void> {return this.$callMethod('randomLayout');}
async relZoom(...args: number[]): Promise<number> {return this.$callMethod('relZoom',...args);}
async removeDisplayPlot(): Promise<void> {return this.$callMethod('removeDisplayPlot');}
Expand Down
21 changes: 18 additions & 3 deletions model/canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -426,9 +426,24 @@ namespace minsky
return false;
});
if (!item)
item=model->findAny
(&Group::groups, [&](const GroupPtr& i)
{return i->visible() && i->clickType(x,y)!=ClickType::outside;});
{
// Check if clicking on a group's edge I/O variable
model->recursiveDo(&Group::groups,
[&](Groups&, Groups::const_iterator i)
{
if ((*i)->visible())
if (auto selected=(*i)->select(x,y))
{
item=selected;
return true;
}
return false;
});
if (!item)
item=model->findAny
(&Group::groups, [&](const GroupPtr& i)
{return i->visible() && i->clickType(x,y)!=ClickType::outside;});
}
return item;
}

Expand Down
64 changes: 62 additions & 2 deletions model/group.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
using namespace std;
using namespace ecolab::cairo;

#include <algorithm>

// size of the top and bottom margins of the group icon
static const int topMargin=10;

Expand Down Expand Up @@ -912,10 +914,10 @@ namespace minsky
auto z=zoomFactor();
const double w=0.5*iWidth()*z, h=0.5*iHeight()*z;
if (onResizeHandle(x,y)) return ClickType::onResize;
if (displayContents() && inIORegion(x,y)==IORegion::none)
return ClickType::outside;
if (auto item=select(x,y))
return item->clickType(x,y);
if (displayContents() && inIORegion(x,y)==IORegion::none)
return ClickType::outside;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change looks fine, and a priori looks like it would fix the problem, however the problem still exists.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're correct - the initial fix in Group::clickType was incomplete. The issue is that edge I/O variables have visible() returning false, so Canvas::itemAt skips them and returns the group instead. Even though clickType properly delegates to the variable, itemFocus gets set to the group.

The complete fix required two changes:

  1. Group::clickType - check select() before early return (commit 99655fe)
  2. Canvas::itemAt - explicitly check edge I/O variables before falling back to group selection (commit eba73d6)

Now itemAt returns the edge variable directly, ensuring itemFocus is set correctly for wire dragging.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still doesn't work

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found the root cause - edge variables are only moved to their edge positions during draw(), so their stored x/y values don't reflect their rendered positions. When select() checked for hits, it was checking the wrong positions.

Fixed in commit 0f4dc7c by rewriting Group::select() to calculate where each edge variable would be positioned (using the same logic as draw1edge) and checking if the click is within those calculated positions. This should now properly detect clicks on edge I/O variables.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found the root cause - edge variables are only moved to their edge positions during draw(), so their stored x/y values don't reflect their rendered positions. When select() checked for hits, it was checking the wrong positions.

Fixed in commit 0f4dc7c by rewriting Group::select() to calculate where each edge variable would be positioned (using the same logic as draw1edge) and checking if the click is within those calculated positions. This should now properly detect clicks on edge I/O variables.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still doesn't work

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue was that edge variables AND their ports are only positioned during draw(). Fixed in commit e1d19f2 by creating positionEdgeVariables() which positions both variables and ports before hit testing. This ensures:

  • Variables are at correct edge positions
  • Ports are positioned relative to variables
  • Port click detection works for wire dragging

The method uses the same positioning logic as draw1edge() and positions ports using the same calculation as Variable::draw().

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still doesn't work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need more information to diagnose the issue. Could you please provide:

  1. What specific behavior occurs when you try to drag from the output wire circle? (e.g., does the group drag, does nothing happen, does it crash?)
  2. Are you testing with a group that has displayContents() == true (zoomed to show internal contents)?
  3. Does the output variable show a wire circle on hover?
  4. Any error messages or logs?

This will help me identify what's still failing in the wire dragging flow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need more information to diagnose the issue. Could you please provide:

  1. What specific behavior occurs when you try to drag from the output wire circle? (e.g., does the group drag, does nothing happen, does it crash?)
  2. Are you testing with a group that has displayContents() == true (zoomed to show internal contents)?
  3. Does the output variable show a wire circle on hover?
  4. Any error messages or logs?

This will help me identify what's still failing in the wire dragging flow.

if ((abs(x-this->x())<w && abs(y-this->y())<h+topMargin*z)) // check also if (x,y) is within top and bottom margins of group. for feature 88
return ClickType::onItem;
return ClickType::outside;
Expand Down Expand Up @@ -1180,14 +1182,72 @@ namespace minsky
return rotFactor;
}

void Group::positionEdgeVariables() const
{
// Position edge variables at their correct edge locations
// This is needed for hit testing since ports are positioned relative to variables
float left, right;
margins(left, right);
const float z = zoomFactor();
const Rotate r(rotation(), 0, 0);

auto positionEdge = [&](const vector<VariablePtr>& vars, float edgeX) {
float top = 0, bottom = 0;
for (size_t i = 0; i < vars.size(); ++i)
{
auto& v = vars[i];
float edgeY = 0;
auto vz = v->zoomFactor();
auto t = v->bb.top() * vz, b = v->bb.bottom() * vz;
if (i > 0) edgeY = i % 2 ? top - b : bottom - t;

// Move variable to edge position (same as in draw1edge)
v->moveTo(r.x(edgeX, edgeY) + this->x(), r.y(edgeX, edgeY) + this->y());
v->rotation(rotation());

// Also position ports (simplified version of what happens in Variable::draw)
// This ensures port hit testing works correctly
const RenderVariable rv(*v);
const double w = std::max(rv.width(), 0.5f * v->iWidth());
const double angle = v->rotation() * M_PI / 180.0;
const double sa = sin(angle), ca = cos(angle);
const double x0 = vz * w, y0 = 0, x1 = -vz * w + 2, y1 = 0;
if (v->portsSize() > 0)
v->ports(0).lock()->moveTo(v->x() + (x0 * ca - y0 * sa), v->y() + (y0 * ca + x0 * sa));
if (v->portsSize() > 1)
v->ports(1).lock()->moveTo(v->x() + (x1 * ca - y1 * sa), v->y() + (y1 * ca + x1 * sa));

if (i == 0)
{
bottom = b;
top = t;
}
else if (i % 2)
top -= (b - t);
else
bottom += (b - t);
}
};

positionEdge(inVariables, -0.5 * (iWidth() * z - left));
positionEdge(outVariables, 0.5 * (iWidth() * z - right));
}

ItemPtr Group::select(float x, float y) const
{
// Position edge variables at their correct locations for accurate hit testing
positionEdgeVariables();

// Check input variables
for (auto& v: inVariables)
if (RenderVariable(*v).inImage(x,y))
return v;

// Check output variables
for (auto& v: outVariables)
if (RenderVariable(*v).inImage(x,y))
return v;

return nullptr;
}

Expand Down
2 changes: 2 additions & 0 deletions model/group.h
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,8 @@ namespace minsky

/// scaling factor to allow a rotated icon to fit on the bitmap
float rotFactor() const;

void positionEdgeVariables() const;

/// returns the variable if point (x,y) is within a
/// I/O variable icon, null otherwise, indicating that the Group
Expand Down
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading