From c79604f64d3ba58161706fc226d2ff166892af1a Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Tue, 3 Nov 2009 23:52:58 -0800 Subject: [PATCH] Add marquee selection, a view-aligned rectangle such that everything even partially within that rectangle gets selected when I release. Also make deselecting a point deselect all coincident points too; otherwise there now exist ways to select both coincident points, which meant that it was impossible to deselect the lower one. And fix text window to show selection info even if just constraints are selected, seems more consistent. [git-p4: depot-paths = "//depot/solvespace/": change = 2066] --- draw.cpp | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++- mouse.cpp | 47 ++++++++++++++++--- textwin.cpp | 6 +-- ui.h | 4 ++ wishlist.txt | 1 - 5 files changed, 171 insertions(+), 11 deletions(-) diff --git a/draw.cpp b/draw.cpp index 24dbebd..bebef54 100644 --- a/draw.cpp +++ b/draw.cpp @@ -118,6 +118,24 @@ void GraphicsWindow::ToggleSelectionStateOf(Selection *stog) { break; } } + // If two points are coincident, then it's impossible to hover one of + // them. But make sure to deselect both, to avoid mysterious seeming + // inability to deselect if the bottom one did somehow get selected. + if(wasSelected && stog->entity.v) { + Entity *e = SK.GetEntity(stog->entity); + if(e->IsPoint()) { + Vector ep = e->PointGetNum(); + for(s = selection.First(); s; s = selection.NextAfter(s)) { + if(!s->entity.v) continue; + if(s->entity.v == stog->entity.v) continue; + Entity *se = SK.GetEntity(s->entity); + if(!se->IsPoint()) continue; + if(ep.Equals(se->PointGetNum())) { + s->tag = 1; + } + } + } + } selection.RemoveTagged(); if(wasSelected) return; @@ -141,6 +159,61 @@ void GraphicsWindow::ToggleSelectionStateOf(Selection *stog) { selection.Add(stog); } +//----------------------------------------------------------------------------- +// Select everything that lies within the marquee view-aligned rectangle. For +// points, we test by the point location. For normals, we test by the normal's +// associated point. For anything else, we test by any piecewise linear edge. +//----------------------------------------------------------------------------- +void GraphicsWindow::SelectByMarquee(void) { + Point2d begin = ProjectPoint(orig.marqueePoint); + double xmin = min(orig.mouse.x, begin.x), + xmax = max(orig.mouse.x, begin.x), + ymin = min(orig.mouse.y, begin.y), + ymax = max(orig.mouse.y, begin.y); + + Entity *e; + for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { + if(e->group.v != SS.GW.activeGroup.v) continue; + if(e->IsFace() || e->IsDistance()) continue; + + if(e->IsPoint() || e->IsNormal()) { + Vector p = e->IsPoint() ? e->PointGetNum() : + SK.GetEntity(e->point[0])->PointGetNum(); + Point2d pp = ProjectPoint(p); + if(pp.x >= xmin && pp.x <= xmax && + pp.y >= ymin && pp.y <= ymax) + { + ToggleSelectionStateOf(e->h); + } + } else { + // Use the 3d bounding box test routines, to avoid duplication; + // so let our bounding square become a bounding box that certainly + // includes the z = 0 plane. + Vector ptMin = Vector::From(xmin, ymin, -1), + ptMax = Vector::From(xmax, ymax, 1); + SEdgeList sel; + ZERO(&sel); + e->GenerateEdges(&sel); + SEdge *se; + for(se = sel.l.First(); se; se = sel.l.NextAfter(se)) { + Point2d ppa = ProjectPoint(se->a), + ppb = ProjectPoint(se->b); + Vector ptA = Vector::From(ppa.x, ppa.y, 0), + ptB = Vector::From(ppb.x, ppb.y, 0); + if(Vector::BoundingBoxIntersectsLine(ptMax, ptMin, + ptA, ptB, true) || + !ptA.OutsideAndNotOn(ptMax, ptMin) || + !ptB.OutsideAndNotOn(ptMax, ptMin)) + { + ToggleSelectionStateOf(e->h); + break; + } + } + sel.Clear(); + } + } +} + //----------------------------------------------------------------------------- // Sort the selection according to various critieria: the entities and // constraints separately, counts of certain types of entities (circles, @@ -307,12 +380,28 @@ Vector GraphicsWindow::ProjectPoint4(Vector p, double *w) { return r; } +//----------------------------------------------------------------------------- +// Return a point in the plane parallel to the screen and through the offset, +// that projects onto the specified (x, y) coordinates. +//----------------------------------------------------------------------------- +Vector GraphicsWindow::UnProjectPoint(Point2d p) { + Vector orig = offset.ScaledBy(-1); + + // Note that we ignoring the effects of perspective. Since our returned + // point has the same component normal to the screen as the offset, it + // will have z = 0 after the rotation is applied, thus w = 1. So this is + // correct. + orig = orig.Plus(projRight.ScaledBy(p.x / scale)).Plus( + projUp. ScaledBy(p.y / scale)); + return orig; +} + void GraphicsWindow::NormalizeProjectionVectors(void) { Vector norm = projRight.Cross(projUp); projUp = norm.Cross(projRight); - projUp = projUp.ScaledBy(1/projUp.Magnitude()); - projRight = projRight.ScaledBy(1/projRight.Magnitude()); + projUp = projUp.WithMagnitude(1); + projRight = projRight.WithMagnitude(1); } Vector GraphicsWindow::VectorFromProjs(Vector rightUpForward) { @@ -557,6 +646,37 @@ nogrid:; glxUnlockColor(); + // If a marquee selection is in progress, then draw the selection + // rectangle, as an outline and a transparent fill. + if(pending.operation == DRAGGING_MARQUEE) { + Point2d begin = ProjectPoint(orig.marqueePoint); + double xmin = min(orig.mouse.x, begin.x), + xmax = max(orig.mouse.x, begin.x), + ymin = min(orig.mouse.y, begin.y), + ymax = max(orig.mouse.y, begin.y); + + Vector tl = UnProjectPoint(Point2d::From(xmin, ymin)), + tr = UnProjectPoint(Point2d::From(xmax, ymin)), + br = UnProjectPoint(Point2d::From(xmax, ymax)), + bl = UnProjectPoint(Point2d::From(xmin, ymax)); + + glLineWidth((GLfloat)1.3); + glxColorRGB(Style::Color(Style::HOVERED)); + glBegin(GL_LINE_LOOP); + glxVertex3v(tl); + glxVertex3v(tr); + glxVertex3v(br); + glxVertex3v(bl); + glEnd(); + glxColorRGBa(Style::Color(Style::HOVERED), 0.10); + glBegin(GL_QUADS); + glxVertex3v(tl); + glxVertex3v(tr); + glxVertex3v(br); + glxVertex3v(bl); + glEnd(); + } + // And finally the toolbar. if(SS.showToolbar) { ToolbarDraw(); diff --git a/mouse.cpp b/mouse.cpp index 9e9590b..f253f63 100644 --- a/mouse.cpp +++ b/mouse.cpp @@ -91,11 +91,14 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, } } - if(!leftDown && pending.operation == DRAGGING_POINTS) { + if(!leftDown && (pending.operation == DRAGGING_POINTS || + pending.operation == DRAGGING_MARQUEE)) + { ClearPending(); + InvalidateGraphics(); } - Point2d mp = { x, y }; + Point2d mp = Point2d::From(x, y); if(rightDown && orig.mouse.DistanceTo(mp) < 5 && !orig.startedMoving) { // Avoid accidentally panning (or rotating if shift is down) if the @@ -148,9 +151,11 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, // If we're currently not doing anything, then see if we should // start dragging something. if(leftDown && dm > 3) { - if(hover.entity.v) { + Entity *e = NULL; + if(hover.entity.v) e = SK.GetEntity(hover.entity); + if(e && e->type != Entity::WORKPLANE) { Entity *e = SK.GetEntity(hover.entity); - if(e->type == Entity::CIRCLE) { + if(e->type == Entity::CIRCLE && selection.n <= 1) { // Drag the radius. ClearSelection(); pending.circle = hover.entity; @@ -176,6 +181,22 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, // We just started a drag, so remember for the undo before // the drag changes anything. SS.UndoRemember(); + } else { + if(!hover.constraint.v) { + // That's just marquee selection, which should not cause + // an undo remember. + if(dm > 10) { + if(hover.entity.v) { + // Avoid accidentally selecting workplanes when + // starting drags. + ToggleSelectionStateOf(hover.entity); + hover.Clear(); + } + pending.operation = DRAGGING_MARQUEE; + orig.marqueePoint = + UnProjectPoint(orig.mouseOnButtonDown); + } + } } } else { // Otherwise, just hit test and give up; but don't hit test @@ -359,9 +380,18 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, break; } + case DRAGGING_MARQUEE: + orig.mouse = mp; + InvalidateGraphics(); + break; + default: oops(); } - if(pending.operation != 0 && pending.operation != DRAGGING_CONSTRAINT) { + + if(pending.operation != 0 && + pending.operation != DRAGGING_CONSTRAINT && + pending.operation != DRAGGING_MARQUEE) + { SS.GenerateAll(); } havePainted = false; @@ -950,6 +980,13 @@ void GraphicsWindow::MouseLeftUp(double mx, double my) { case DRAGGING_NORMAL: case DRAGGING_RADIUS: ClearPending(); + InvalidateGraphics(); + break; + + case DRAGGING_MARQUEE: + SelectByMarquee(); + ClearPending(); + InvalidateGraphics(); break; default: diff --git a/textwin.cpp b/textwin.cpp index 8ea289f..a75c6bc 100644 --- a/textwin.cpp +++ b/textwin.cpp @@ -216,7 +216,7 @@ void TextWindow::Show(void) { Printf(false, "%s", SS.GW.pending.description); Printf(true, "%Fl%f%Ll(cancel operation)%E", &TextWindow::ScreenUnselectAll); - } else if(gs.n > 0 || gs.stylables > 0) { + } else if(gs.n > 0 || gs.constraints > 0) { if(edit.meaning != EDIT_TTF_TEXT) HideTextEditControl(); ShowHeader(false); DescribeSelection(); @@ -519,7 +519,7 @@ void TextWindow::DescribeSelection(void) { double d = (p1.Minus(p0)).Dot(n0); Printf(true, " distance = %Fi%s", SS.MmToString(d)); } - } else if(gs.n == 0) { + } else if(gs.n == 0 && gs.stylables > 0) { Printf(true, "%FtSELECTED:%E comment text"); } else { int n = SS.GW.selection.n; @@ -527,7 +527,7 @@ void TextWindow::DescribeSelection(void) { } if(shown.screen == SCREEN_STYLE_INFO && - shown.style.v >= Style::FIRST_CUSTOM) + shown.style.v >= Style::FIRST_CUSTOM && gs.stylables > 0) { // If we are showing a screen for a particular style, then offer the // option to assign our selected entities to that style. diff --git a/ui.h b/ui.h index 036e749..6360288 100644 --- a/ui.h +++ b/ui.h @@ -327,6 +327,7 @@ public: Vector projUp; Point2d mouse; Point2d mouseOnButtonDown; + Vector marqueePoint; bool startedMoving; } orig; @@ -344,6 +345,7 @@ public: Point2d ProjectPoint(Vector p); Vector ProjectPoint3(Vector p); Vector ProjectPoint4(Vector p, double *w); + Vector UnProjectPoint(Point2d p); void AnimateOnto(Quaternion quatf, Vector offsetf); void AnimateOntoWorkplane(void); Vector VectorFromProjs(Vector rightUpForward); @@ -372,6 +374,7 @@ public: static const int DRAGGING_RADIUS = 0x0f000006; static const int DRAGGING_NORMAL = 0x0f000007; static const int DRAGGING_NEW_RADIUS = 0x0f000008; + static const int DRAGGING_MARQUEE = 0x0f000009; struct { int operation; @@ -452,6 +455,7 @@ public: void GroupSelection(void); void ToggleSelectionStateOf(hEntity he); void ToggleSelectionStateOf(Selection *s); + void SelectByMarquee(void); void ClearSuper(void); static const int CMNU_UNSELECT_ALL = 0x100; diff --git a/wishlist.txt b/wishlist.txt index a0ac937..8c32c60 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,5 +1,4 @@ -marquee selection copy and paste background image associative entities from solid model, as a special group