diff --git a/draw.cpp b/draw.cpp index 24dbebdb..bebef54e 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 9e9590b3..f253f639 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 8ea289fd..a75c6bcd 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 036e7490..63602885 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 a0ac9370..8c32c60d 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