Solvespace
1574 строки · 59.5 Кб
1//-----------------------------------------------------------------------------
2// Anything relating to mouse, keyboard, or 6-DOF mouse input.
3//
4// Copyright 2008-2013 Jonathan Westhues.
5//-----------------------------------------------------------------------------
6#include "solvespace.h"7
8void GraphicsWindow::UpdateDraggedPoint(hEntity hp, double mx, double my) {9Entity *p = SK.GetEntity(hp);10Vector pos = p->PointGetNum();11UpdateDraggedNum(&pos, mx, my);12p->PointForceTo(pos);13
14SS.ScheduleShowTW();15}
16
17void GraphicsWindow::UpdateDraggedNum(Vector *pos, double mx, double my) {18*pos = pos->Plus(projRight.ScaledBy((mx - orig.mouse.x)/scale));19*pos = pos->Plus(projUp.ScaledBy((my - orig.mouse.y)/scale));20}
21
22void GraphicsWindow::AddPointToDraggedList(hEntity hp) {23Entity *p = SK.GetEntity(hp);24// If an entity and its points are both selected, then its points could25// end up in the list twice. This would be bad, because it would move26// twice as far as the mouse pointer...27List<hEntity> *lhe = &(pending.points);28for(hEntity *hee = lhe->First(); hee; hee = lhe->NextAfter(hee)) {29if(*hee == hp) {30// Exact same point.31return;32}33Entity *pe = SK.GetEntity(*hee);34if(pe->type == p->type &&35pe->type != Entity::Type::POINT_IN_2D &&36pe->type != Entity::Type::POINT_IN_3D &&37pe->group == p->group)38{39// Transform-type point, from the same group. So it handles the40// same unknowns.41return;42}43}44pending.points.Add(&hp);45}
46
47void GraphicsWindow::StartDraggingByEntity(hEntity he) {48Entity *e = SK.GetEntity(he);49if(e->IsPoint()) {50AddPointToDraggedList(e->h);51} else if(e->type == Entity::Type::LINE_SEGMENT ||52e->type == Entity::Type::ARC_OF_CIRCLE ||53e->type == Entity::Type::CUBIC ||54e->type == Entity::Type::CUBIC_PERIODIC ||55e->type == Entity::Type::CIRCLE ||56e->type == Entity::Type::TTF_TEXT ||57e->type == Entity::Type::IMAGE)58{59int pts;60EntReqTable::GetEntityInfo(e->type, e->extraPoints,61NULL, &pts, NULL, NULL);62for(int i = 0; i < pts; i++) {63AddPointToDraggedList(e->point[i]);64}65}66}
67
68void GraphicsWindow::StartDraggingBySelection() {69List<Selection> *ls = &(selection);70for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) {71if(!s->entity.v) continue;72
73StartDraggingByEntity(s->entity);74}75// The user might select a point, and then click it again to start76// dragging; but the point just got unselected by that click. So drag77// the hovered item too, and they'll always have it.78if(hover.entity.v) {79hEntity dragEntity = ChooseFromHoverToDrag().entity;80if(dragEntity != Entity::NO_ENTITY) {81StartDraggingByEntity(dragEntity);82}83}84}
85
86void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,87bool middleDown, bool rightDown, bool shiftDown, bool ctrlDown)88{
89if(window->IsEditorVisible()) return;90if(context.active) return;91
92SS.extraLine.draw = false;93
94if(!orig.mouseDown) {95// If someone drags the mouse into our window with the left button96// already depressed, then we don't have our starting point; so97// don't try.98leftDown = false;99}100
101if(rightDown) {102middleDown = true;103shiftDown = !shiftDown;104}105
106// Not passing right-button and middle-button drags to the toolbar avoids107// some cosmetic issues with trackpad pans/rotates implemented with108// simulated right-button drag events causing spurious hover events.109if(SS.showToolbar && !middleDown) {110if(ToolbarMouseMoved((int)x, (int)y)) {111hover.Clear();112return;113}114}115
116if(!leftDown && (pending.operation == Pending::DRAGGING_POINTS ||117pending.operation == Pending::DRAGGING_MARQUEE))118{119ClearPending();120Invalidate();121}122
123Point2d mp = Point2d::From(x, y);124currentMousePosition = mp;125
126if(rightDown && orig.mouse.DistanceTo(mp) < 5 && !orig.startedMoving) {127// Avoid accidentally panning (or rotating if shift is down) if the128// user wants a context menu.129return;130}131orig.startedMoving = true;132
133// If the middle button is down, then mouse movement is used to pan and134// rotate our view. This wins over everything else.135if(middleDown) {136hover.Clear();137
138double dx = (x - orig.mouse.x) / scale;139double dy = (y - orig.mouse.y) / scale;140
141if(!(shiftDown || ctrlDown)) {142double sign = SS.cameraNav ? -1.0 : 1.0;143double s = 0.3*(PI/180)*scale*sign; // degrees per pixel144if(SS.turntableNav) { // lock the Z to vertical145projRight = orig.projRight.RotatedAbout(Vector::From(0, 0, 1), -s * dx);146projUp = orig.projUp.RotatedAbout(147Vector::From(orig.projRight.x, orig.projRight.y, orig.projRight.y), s * dy);148} else {149projRight = orig.projRight.RotatedAbout(orig.projUp, -s * dx);150projUp = orig.projUp.RotatedAbout(orig.projRight, s * dy);151}152
153NormalizeProjectionVectors();154} else if(ctrlDown) {155double theta = atan2(orig.mouse.y, orig.mouse.x);156theta -= atan2(y, x);157SS.extraLine.draw = true;158SS.extraLine.ptA = UnProjectPoint(Point2d::From(0, 0));159SS.extraLine.ptB = UnProjectPoint(mp);160
161Vector normal = orig.projRight.Cross(orig.projUp);162projRight = orig.projRight.RotatedAbout(normal, theta);163projUp = orig.projUp.RotatedAbout(normal, theta);164
165NormalizeProjectionVectors();166} else {167offset.x = orig.offset.x + dx*projRight.x + dy*projUp.x;168offset.y = orig.offset.y + dx*projRight.y + dy*projUp.y;169offset.z = orig.offset.z + dx*projRight.z + dy*projUp.z;170}171
172orig.projRight = projRight;173orig.projUp = projUp;174orig.offset = offset;175orig.mouse.x = x;176orig.mouse.y = y;177
178if(SS.TW.shown.screen == TextWindow::Screen::EDIT_VIEW) {179if(havePainted) {180SS.ScheduleShowTW();181}182}183Invalidate();184havePainted = false;185return;186}187
188if(pending.operation == Pending::NONE) {189double dm = orig.mouse.DistanceTo(mp);190// If we're currently not doing anything, then see if we should191// start dragging something.192if(leftDown && dm > 3) {193Entity *e = NULL;194hEntity dragEntity = ChooseFromHoverToDrag().entity;195if(dragEntity.v) e = SK.GetEntity(dragEntity);196if(e && e->type != Entity::Type::WORKPLANE) {197if(!hoverWasSelectedOnMousedown) {198// The user clicked an unselected entity, which199// means they're dragging just the hovered thing,200// not the full selection. So clear all the selection201// except that entity.202ClearSelection();203MakeSelected(dragEntity);204}205if(e->type == Entity::Type::CIRCLE && selection.n <= 1) {206// Drag the radius.207pending.circle = dragEntity;208pending.operation = Pending::DRAGGING_RADIUS;209} else if(e->IsNormal()) {210pending.normal = dragEntity;211pending.operation = Pending::DRAGGING_NORMAL;212} else {213StartDraggingBySelection();214hover.Clear();215pending.operation = Pending::DRAGGING_POINTS;216}217} else if(hover.constraint.v &&218SK.GetConstraint(hover.constraint)->HasLabel())219{220ClearSelection();221pending.constraint = hover.constraint;222pending.operation = Pending::DRAGGING_CONSTRAINT;223}224if(pending.operation != Pending::NONE) {225// We just started a drag, so remember for the undo before226// the drag changes anything.227SS.UndoRemember();228} else {229if(!hover.constraint.v) {230// That's just marquee selection, which should not cause231// an undo remember.232if(dm > 10) {233if(hover.entity.v) {234// Avoid accidentally selecting workplanes when235// starting drags.236MakeUnselected(hover.entity, /*coincidentPointTrick=*/false);237hover.Clear();238}239pending.operation = Pending::DRAGGING_MARQUEE;240orig.marqueePoint =241UnProjectPoint(orig.mouseOnButtonDown);242}243}244}245} else {246// Otherwise, just hit test and give up; but don't hit test247// if the mouse is down, because then the user could hover248// a point, mouse down (thus selecting it), and drag, in an249// effort to drag the point, but instead hover a different250// entity before we move far enough to start the drag.251if(!leftDown) {252// Hit testing can potentially take a lot of time.253// If we haven't painted since last time we highlighted254// something, don't hit test again, since this just causes255// a lag.256if(!havePainted) return;257HitTestMakeSelection(mp);258}259}260return;261}262
263// If the user has started an operation from the menu, but not264// completed it, then just do the selection.265if(pending.operation == Pending::COMMAND) {266HitTestMakeSelection(mp);267return;268}269
270if(pending.operation == Pending::DRAGGING_POINTS && ctrlDown) {271SS.extraLine.ptA = UnProjectPoint(orig.mouseOnButtonDown);272SS.extraLine.ptB = UnProjectPoint(mp);273SS.extraLine.draw = true;274}275
276// We're currently dragging something; so do that. But if we haven't277// painted since the last time we solved, do nothing, because there's278// no sense solving a frame and not displaying it.279if(!havePainted) {280return;281}282
283havePainted = false;284switch(pending.operation) {285case Pending::DRAGGING_CONSTRAINT: {286Constraint *c = SK.constraint.FindById(pending.constraint);287UpdateDraggedNum(&(c->disp.offset), x, y);288orig.mouse = mp;289Invalidate();290return;291}292
293case Pending::DRAGGING_NEW_LINE_POINT:294if(!ctrlDown) {295SS.GW.pending.hasSuggestion =296SS.GW.SuggestLineConstraint(SS.GW.pending.request, &SS.GW.pending.suggestion);297} else {298SS.GW.pending.hasSuggestion = false;299}300// fallthrough301case Pending::DRAGGING_NEW_POINT:302UpdateDraggedPoint(pending.point, x, y);303HitTestMakeSelection(mp);304SS.MarkGroupDirtyByEntity(pending.point);305orig.mouse = mp;306break;307
308case Pending::DRAGGING_POINTS:309if(shiftDown || ctrlDown) {310// Edit the rotation associated with a POINT_N_ROT_TRANS,311// either within (ctrlDown) or out of (shiftDown) the plane312// of the screen. So first get the rotation to apply, in qt.313Quaternion qt;314if(ctrlDown) {315double d = mp.DistanceTo(orig.mouseOnButtonDown);316if(d < 25) {317// Don't start dragging the position about the normal318// until we're a little ways out, to get a reasonable319// reference pos320qt = Quaternion::IDENTITY;321} else {322double theta = atan2(orig.mouse.y-orig.mouseOnButtonDown.y,323orig.mouse.x-orig.mouseOnButtonDown.x);324theta -= atan2(y-orig.mouseOnButtonDown.y,325x-orig.mouseOnButtonDown.x);326
327Vector gn = projRight.Cross(projUp);328qt = Quaternion::From(gn, -theta);329}330} else {331double dx = -(x - orig.mouse.x);332double dy = -(y - orig.mouse.y);333double s = 0.3*(PI/180); // degrees per pixel334qt = Quaternion::From(projUp, -s*dx).Times(335Quaternion::From(projRight, s*dy));336}337
338// Now apply this rotation to the points being dragged.339List<hEntity> *lhe = &(pending.points);340for(hEntity *he = lhe->First(); he; he = lhe->NextAfter(he)) {341Entity *e = SK.GetEntity(*he);342if(e->type != Entity::Type::POINT_N_ROT_TRANS) {343if(ctrlDown) {344Vector p = e->PointGetNum();345p = p.Minus(SS.extraLine.ptA);346p = qt.Rotate(p);347p = p.Plus(SS.extraLine.ptA);348e->PointForceTo(p);349} else {350UpdateDraggedPoint(*he, x, y);351}352} else {353Quaternion q = e->PointGetQuaternion();354Vector p = e->PointGetNum();355q = qt.Times(q);356e->PointForceQuaternionTo(q);357// Let's rotate about the selected point; so fix up the358// translation so that that point didn't move.359e->PointForceTo(p);360}361SS.MarkGroupDirtyByEntity(e->h);362}363} else {364List<hEntity> *lhe = &(pending.points);365for(hEntity *he = lhe->First(); he; he = lhe->NextAfter(he)) {366UpdateDraggedPoint(*he, x, y);367SS.MarkGroupDirtyByEntity(*he);368}369}370orig.mouse = mp;371break;372
373case Pending::DRAGGING_NEW_CUBIC_POINT: {374UpdateDraggedPoint(pending.point, x, y);375HitTestMakeSelection(mp);376
377hRequest hr = pending.point.request();378if(pending.point == hr.entity(4)) {379// The very first segment; dragging final point drags both380// tangent points.381Vector p0 = SK.GetEntity(hr.entity(1))->PointGetNum(),382p3 = SK.GetEntity(hr.entity(4))->PointGetNum(),383p1 = p0.ScaledBy(2.0/3).Plus(p3.ScaledBy(1.0/3)),384p2 = p0.ScaledBy(1.0/3).Plus(p3.ScaledBy(2.0/3));385SK.GetEntity(hr.entity(1+1))->PointForceTo(p1);386SK.GetEntity(hr.entity(1+2))->PointForceTo(p2);387} else {388// A subsequent segment; dragging point drags only final389// tangent point.390int i = SK.GetEntity(hr.entity(0))->extraPoints;391Vector pn = SK.GetEntity(hr.entity(4+i))->PointGetNum(),392pnm2 = SK.GetEntity(hr.entity(2+i))->PointGetNum(),393pnm1 = (pn.Plus(pnm2)).ScaledBy(0.5);394SK.GetEntity(hr.entity(3+i))->PointForceTo(pnm1);395}396
397orig.mouse = mp;398SS.MarkGroupDirtyByEntity(pending.point);399break;400}401case Pending::DRAGGING_NEW_ARC_POINT: {402UpdateDraggedPoint(pending.point, x, y);403HitTestMakeSelection(mp);404
405hRequest hr = pending.point.request();406Vector ona = SK.GetEntity(hr.entity(2))->PointGetNum();407Vector onb = SK.GetEntity(hr.entity(3))->PointGetNum();408Vector center = (ona.Plus(onb)).ScaledBy(0.5);409
410SK.GetEntity(hr.entity(1))->PointForceTo(center);411
412orig.mouse = mp;413SS.MarkGroupDirtyByEntity(pending.point);414break;415}416case Pending::DRAGGING_NEW_RADIUS:417case Pending::DRAGGING_RADIUS: {418Entity *circle = SK.GetEntity(pending.circle);419Vector center = SK.GetEntity(circle->point[0])->PointGetNum();420Point2d c2 = ProjectPoint(center);421double r = c2.DistanceTo(mp)/scale;422SK.GetEntity(circle->distance)->DistanceForceTo(r);423
424SS.MarkGroupDirtyByEntity(pending.circle);425SS.ScheduleShowTW();426break;427}428
429case Pending::DRAGGING_NORMAL: {430Entity *normal = SK.GetEntity(pending.normal);431Vector p = SK.GetEntity(normal->point[0])->PointGetNum();432Point2d p2 = ProjectPoint(p);433
434Quaternion q = normal->NormalGetNum();435Vector u = q.RotationU(), v = q.RotationV();436
437if(ctrlDown) {438double theta = atan2(orig.mouse.y-p2.y, orig.mouse.x-p2.x);439theta -= atan2(y-p2.y, x-p2.x);440
441Vector normal = projRight.Cross(projUp);442u = u.RotatedAbout(normal, -theta);443v = v.RotatedAbout(normal, -theta);444} else {445double dx = -(x - orig.mouse.x);446double dy = -(y - orig.mouse.y);447double s = 0.3*(PI/180); // degrees per pixel448u = u.RotatedAbout(projUp, -s*dx);449u = u.RotatedAbout(projRight, s*dy);450v = v.RotatedAbout(projUp, -s*dx);451v = v.RotatedAbout(projRight, s*dy);452}453orig.mouse = mp;454normal->NormalForceTo(Quaternion::From(u, v));455
456SS.MarkGroupDirtyByEntity(pending.normal);457break;458}459
460case Pending::DRAGGING_MARQUEE:461orig.mouse = mp;462Invalidate();463return;464
465case Pending::NONE:466case Pending::COMMAND:467ssassert(false, "Unexpected pending operation");468}469}
470
471void GraphicsWindow::ClearPending(bool scheduleShowTW) {472pending.points.Clear();473pending.requests.Clear();474pending = {};475if(scheduleShowTW) {476SS.ScheduleShowTW();477}478}
479
480bool GraphicsWindow::IsFromPending(hRequest r) {481for(auto &req : pending.requests) {482if(req == r) return true;483}484return false;485}
486
487void GraphicsWindow::AddToPending(hRequest r) {488pending.requests.Add(&r);489}
490
491void GraphicsWindow::ReplacePending(hRequest before, hRequest after) {492for(auto &req : pending.requests) {493if(req == before) {494req = after;495}496}497}
498
499void GraphicsWindow::MouseMiddleOrRightDown(double x, double y) {500if(window->IsEditorVisible()) return;501
502orig.offset = offset;503orig.projUp = projUp;504orig.projRight = projRight;505orig.mouse.x = x;506orig.mouse.y = y;507orig.startedMoving = false;508}
509
510void GraphicsWindow::MouseRightUp(double x, double y) {511SS.extraLine.draw = false;512Invalidate();513
514// Don't show a context menu if the user is right-clicking the toolbar,515// or if they are finishing a pan.516if(ToolbarMouseMoved((int)x, (int)y)) return;517if(orig.startedMoving) return;518
519if(context.active) return;520
521if(pending.operation == Pending::DRAGGING_NEW_LINE_POINT && pending.hasSuggestion) {522Constraint::TryConstrain(SS.GW.pending.suggestion,523Entity::NO_ENTITY, Entity::NO_ENTITY, pending.request.entity(0));524}525
526if(pending.operation == Pending::DRAGGING_NEW_LINE_POINT ||527pending.operation == Pending::DRAGGING_NEW_CUBIC_POINT ||528pending.operation == Pending::DRAGGING_NEW_ARC_POINT ||529pending.operation == Pending::DRAGGING_NEW_RADIUS ||530pending.operation == Pending::DRAGGING_NEW_POINT531)532{533// Special case; use a right click to stop drawing lines, since534// a left click would draw another one. This is quicker and more535// intuitive than hitting escape. Likewise for other entities536// for consistency.537ClearPending();538return;539}540
541// The current mouse location542Vector v = offset.ScaledBy(-1);543v = v.Plus(projRight.ScaledBy(x/scale));544v = v.Plus(projUp.ScaledBy(y/scale));545
546Platform::MenuRef menu = Platform::CreateMenu();547context.active = true;548
549if(!hover.IsEmpty()) {550MakeSelected(&hover);551SS.ScheduleShowTW();552}553GroupSelection();554
555bool itemsSelected = (gs.n > 0 || gs.constraints > 0);556if(itemsSelected) {557if(gs.stylables > 0) {558Platform::MenuRef styleMenu = menu->AddSubMenu(_("Assign to Style"));559
560bool empty = true;561for(const Style &s : SK.style) {562if(s.h.v < Style::FIRST_CUSTOM) continue;563
564uint32_t v = s.h.v;565
566styleMenu->AddItem(s.DescriptionString(), [v]() {567Style::AssignSelectionToStyle(v);568});569empty = false;570}571
572if(!empty) styleMenu->AddSeparator();573
574styleMenu->AddItem(_("No Style"), []() {575Style::AssignSelectionToStyle(0);576});577styleMenu->AddItem(_("Newly Created Custom Style..."), [this]() {578uint32_t vs = Style::CreateCustomStyle();579Style::AssignSelectionToStyle(vs);580ForceTextWindowShown();581});582}583if(gs.n + gs.constraints == 1) {584menu->AddItem(_("Group Info"), [this]() {585hGroup hg;586if(gs.entities == 1) {587hg = SK.GetEntity(gs.entity[0])->group;588} else if(gs.points == 1) {589hg = SK.GetEntity(gs.point[0])->group;590} else if(gs.constraints == 1) {591hg = SK.GetConstraint(gs.constraint[0])->group;592} else {593return;594}595ClearSelection();596
597SS.TW.GoToScreen(TextWindow::Screen::GROUP_INFO);598SS.TW.shown.group = hg;599SS.ScheduleShowTW();600ForceTextWindowShown();601});602}603if(gs.n + gs.constraints == 1 && gs.stylables == 1) {604menu->AddItem(_("Style Info"), [this]() {605hStyle hs;606if(gs.entities == 1) {607hs = Style::ForEntity(gs.entity[0]);608} else if(gs.points == 1) {609hs = Style::ForEntity(gs.point[0]);610} else if(gs.constraints == 1) {611hs = SK.GetConstraint(gs.constraint[0])->GetStyle();612} else {613return;614}615ClearSelection();616
617SS.TW.GoToScreen(TextWindow::Screen::STYLE_INFO);618SS.TW.shown.style = hs;619SS.ScheduleShowTW();620ForceTextWindowShown();621});622}623if(gs.withEndpoints > 0) {624menu->AddItem(_("Select Edge Chain"),625[]() { MenuEdit(Command::SELECT_CHAIN); });626}627if(gs.constraints == 1 && gs.n == 0) {628Constraint *c = SK.GetConstraint(gs.constraint[0]);629if(c->HasLabel() && c->type != Constraint::Type::COMMENT) {630menu->AddItem(_("Toggle Reference Dimension"),631[]() { Constraint::MenuConstrain(Command::REFERENCE); });632}633if(c->type == Constraint::Type::ANGLE ||634c->type == Constraint::Type::EQUAL_ANGLE)635{636menu->AddItem(_("Other Supplementary Angle"),637[]() { Constraint::MenuConstrain(Command::OTHER_ANGLE); });638}639}640if(gs.constraintLabels > 0 || gs.points > 0) {641menu->AddItem(_("Snap to Grid"),642[]() { MenuEdit(Command::SNAP_TO_GRID); });643}644
645if(gs.points == 1 && gs.point[0].isFromRequest()) {646Request *r = SK.GetRequest(gs.point[0].request());647int index = r->IndexOfPoint(gs.point[0]);648if((r->type == Request::Type::CUBIC && (index > 1 && index < r->extraPoints + 2)) ||649r->type == Request::Type::CUBIC_PERIODIC) {650menu->AddItem(_("Remove Spline Point"), [this, r]() {651int index = r->IndexOfPoint(gs.point[0]);652ssassert(r->extraPoints != 0,653"Expected a bezier with interior control points");654
655SS.UndoRemember();656Entity *e = SK.GetEntity(r->h.entity(0));657
658// First, fix point-coincident constraints involving this point.659// Then, remove all other constraints, since they would otherwise660// jump to an adjacent one and mess up the bezier after generation.661FixConstraintsForPointBeingDeleted(e->point[index]);662RemoveConstraintsForPointBeingDeleted(e->point[index]);663
664for(int i = index; i < MAX_POINTS_IN_ENTITY - 1; i++) {665if(e->point[i + 1].v == 0) break;666Entity *p0 = SK.GetEntity(e->point[i]);667Entity *p1 = SK.GetEntity(e->point[i + 1]);668ReplacePointInConstraints(p1->h, p0->h);669p0->PointForceTo(p1->PointGetNum());670}671r->extraPoints--;672SS.MarkGroupDirtyByEntity(gs.point[0]);673ClearSelection();674});675}676}677if(gs.entities == 1 && gs.entity[0].isFromRequest()) {678Request *r = SK.GetRequest(gs.entity[0].request());679if(r->type == Request::Type::CUBIC || r->type == Request::Type::CUBIC_PERIODIC) {680Entity *e = SK.GetEntity(gs.entity[0]);681int addAfterPoint = e->GetPositionOfPoint(GetCamera(), Point2d::From(x, y));682ssassert(addAfterPoint != -1, "Expected a nearest bezier point to be located");683// Skip derivative point.684if(r->type == Request::Type::CUBIC) addAfterPoint++;685menu->AddItem(_("Add Spline Point"), [this, r, addAfterPoint, v]() {686int pointCount = r->extraPoints +687((r->type == Request::Type::CUBIC_PERIODIC) ? 3 : 4);688if(pointCount >= MAX_POINTS_IN_ENTITY) {689Error(_("Cannot add spline point: maximum number of points reached."));690return;691}692
693SS.UndoRemember();694r->extraPoints++;695SS.MarkGroupDirtyByEntity(gs.entity[0]);696SS.GenerateAll(SolveSpaceUI::Generate::REGEN);697
698Entity *e = SK.GetEntity(r->h.entity(0));699for(int i = MAX_POINTS_IN_ENTITY; i > addAfterPoint + 1; i--) {700Entity *p0 = SK.entity.FindByIdNoOops(e->point[i]);701if(p0 == NULL) continue;702Entity *p1 = SK.GetEntity(e->point[i - 1]);703ReplacePointInConstraints(p1->h, p0->h);704p0->PointForceTo(p1->PointGetNum());705}706Entity *p = SK.GetEntity(e->point[addAfterPoint + 1]);707p->PointForceTo(v);708SS.MarkGroupDirtyByEntity(gs.entity[0]);709ClearSelection();710});711}712}713if(gs.entities == gs.n) {714menu->AddItem(_("Toggle Construction"),715[]() { MenuRequest(Command::CONSTRUCTION); });716}717
718if(gs.points == 1) {719Entity *p = SK.GetEntity(gs.point[0]);720Constraint *c = nullptr;721IdList<Constraint,hConstraint> *lc = &(SK.constraint);722for(Constraint &ci : *lc) {723if(ci.type != Constraint::Type::POINTS_COINCIDENT) continue;724if(ci.ptA == p->h || ci.ptB == p->h) {725c = &ci;726break;727}728}729if(c) {730menu->AddItem(_("Delete Point-Coincident Constraint"), [this, p]() {731if(!p->IsPoint()) return;732
733SS.UndoRemember();734SK.constraint.ClearTags();735for(Constraint &c : SK.constraint) {736if(c.type != Constraint::Type::POINTS_COINCIDENT) continue;737if(c.ptA == p->h || c.ptB == p->h) {738c.tag = 1;739}740}741SK.constraint.RemoveTagged();742ClearSelection();743});744}745}746menu->AddSeparator();747if(LockedInWorkplane()) {748menu->AddItem(_("Cut"),749[]() { MenuClipboard(Command::CUT); });750menu->AddItem(_("Copy"),751[]() { MenuClipboard(Command::COPY); });752}753} else {754menu->AddItem(_("Select All"),755[]() { MenuEdit(Command::SELECT_ALL); });756}757
758if((!SS.clipboard.r.IsEmpty() || !SS.clipboard.c.IsEmpty()) && LockedInWorkplane()) {759menu->AddItem(_("Paste"),760[]() { MenuClipboard(Command::PASTE); });761menu->AddItem(_("Paste Transformed..."),762[]() { MenuClipboard(Command::PASTE_TRANSFORM); });763}764
765if(itemsSelected) {766menu->AddItem(_("Delete"),767[]() { MenuClipboard(Command::DELETE); });768menu->AddSeparator();769menu->AddItem(_("Unselect All"),770[]() { MenuEdit(Command::UNSELECT_ALL); });771}772// If only one item is selected, then it must be the one that we just773// selected from the hovered item; in which case unselect all and hovered774// are equivalent.775if(!hover.IsEmpty() && selection.n > 1) {776menu->AddItem(_("Unselect Hovered"), [this] {777if(!hover.IsEmpty()) {778MakeUnselected(&hover, /*coincidentPointTrick=*/true);779}780});781}782
783if(itemsSelected) {784menu->AddSeparator();785menu->AddItem(_("Zoom to Fit"),786[]() { MenuView(Command::ZOOM_TO_FIT); });787}788
789menu->PopUp();790
791context.active = false;792SS.ScheduleShowTW();793}
794
795hRequest GraphicsWindow::AddRequest(Request::Type type) {796return AddRequest(type, /*rememberForUndo=*/true);797}
798hRequest GraphicsWindow::AddRequest(Request::Type type, bool rememberForUndo) {799if(rememberForUndo) SS.UndoRemember();800
801Request r = {};802r.group = activeGroup;803Group *g = SK.GetGroup(activeGroup);804if(g->type == Group::Type::DRAWING_3D || g->type == Group::Type::DRAWING_WORKPLANE) {805r.construction = false;806} else {807r.construction = true;808}809r.workplane = ActiveWorkplane();810r.type = type;811SK.request.AddAndAssignId(&r);812
813// We must regenerate the parameters, so that the code that tries to814// place this request's entities where the mouse is can do so. But815// we mustn't try to solve until reasonable values have been supplied816// for these new parameters, or else we'll get a numerical blowup.817r.Generate(&SK.entity, &SK.param);818SS.MarkGroupDirty(r.group);819return r.h;820}
821
822Vector GraphicsWindow::SnapToEntityByScreenPoint(Point2d pp, hEntity he) {823Entity *e = SK.GetEntity(he);824if(e->IsPoint()) return e->PointGetNum();825SEdgeList *edges = e->GetOrGenerateEdges();826
827double minD = -1.0f;828double k = 0.0;829const SEdge *edge = NULL;830for(const auto &e : edges->l) {831Point2d p0 = ProjectPoint(e.a);832Point2d p1 = ProjectPoint(e.b);833Point2d dir = p1.Minus(p0);834double d = pp.DistanceToLine(p0, dir, /*asSegment=*/true);835if(minD > 0.0 && d > minD) continue;836minD = d;837k = pp.Minus(p0).Dot(dir) / dir.Dot(dir);838edge = &e;839}840if(edge == NULL) return UnProjectPoint(pp);841return edge->a.Plus(edge->b.Minus(edge->a).ScaledBy(k));842}
843
844bool GraphicsWindow::ConstrainPointByHovered(hEntity pt, const Point2d *projected) {845if(!hover.entity.v) return false;846
847Entity *point = SK.GetEntity(pt);848Entity *e = SK.GetEntity(hover.entity);849if(e->IsPoint()) {850point->PointForceTo(e->PointGetNum());851Constraint::ConstrainCoincident(e->h, pt);852return true;853}854if(e->IsCircle()) {855if(projected != NULL) {856Vector snapPos = SnapToEntityByScreenPoint(*projected, e->h);857point->PointForceTo(snapPos);858}859Constraint::Constrain(Constraint::Type::PT_ON_CIRCLE,860pt, Entity::NO_ENTITY, e->h);861return true;862}863if(e->type == Entity::Type::LINE_SEGMENT) {864if(projected != NULL) {865Vector snapPos = SnapToEntityByScreenPoint(*projected, e->h);866point->PointForceTo(snapPos);867}868Constraint::Constrain(Constraint::Type::PT_ON_LINE,869pt, Entity::NO_ENTITY, e->h);870return true;871}872
873return false;874}
875
876bool GraphicsWindow::MouseEvent(Platform::MouseEvent event) {877using Platform::MouseEvent;878double width, height;879window->GetContentSize(&width, &height);880
881event.x = event.x - width / 2;882event.y = height / 2 - event.y;883
884switch(event.type) {885case MouseEvent::Type::MOTION:886this->MouseMoved(event.x, event.y,887event.button == MouseEvent::Button::LEFT,888event.button == MouseEvent::Button::MIDDLE,889event.button == MouseEvent::Button::RIGHT,890event.shiftDown,891event.controlDown);892break;893
894case MouseEvent::Type::PRESS:895if(event.button == MouseEvent::Button::LEFT) {896this->MouseLeftDown(event.x, event.y, event.shiftDown, event.controlDown);897} else if(event.button == MouseEvent::Button::MIDDLE ||898event.button == MouseEvent::Button::RIGHT) {899this->MouseMiddleOrRightDown(event.x, event.y);900}901break;902
903case MouseEvent::Type::DBL_PRESS:904if(event.button == MouseEvent::Button::LEFT) {905this->MouseLeftDoubleClick(event.x, event.y);906}907break;908
909case MouseEvent::Type::RELEASE:910if(event.button == MouseEvent::Button::LEFT) {911this->MouseLeftUp(event.x, event.y, event.shiftDown, event.controlDown);912} else if(event.button == MouseEvent::Button::RIGHT) {913this->MouseRightUp(event.x, event.y);914}915break;916
917case MouseEvent::Type::SCROLL_VERT:918this->MouseScroll(event.shiftDown ? event.scrollDelta / 10 : event.scrollDelta);919break;920
921case MouseEvent::Type::LEAVE:922this->MouseLeave();923break;924}925
926return true;927}
928
929void GraphicsWindow::MouseLeftDown(double mx, double my, bool shiftDown, bool ctrlDown) {930orig.mouseDown = true;931
932if(window->IsEditorVisible()) {933orig.mouse = Point2d::From(mx, my);934orig.mouseOnButtonDown = orig.mouse;935window->HideEditor();936return;937}938SS.TW.HideEditControl();939
940if(SS.showToolbar) {941if(ToolbarMouseDown((int)mx, (int)my)) return;942}943
944// This will be clobbered by MouseMoved below.945bool hasConstraintSuggestion = pending.hasSuggestion;946Constraint::Type constraintSuggestion = pending.suggestion;947
948// Make sure the hover is up to date.949MouseMoved(mx, my, /*leftDown=*/false, /*middleDown=*/false, /*rightDown=*/false,950/*shiftDown=*/false, /*ctrlDown=*/false);951orig.mouse.x = mx;952orig.mouse.y = my;953orig.mouseOnButtonDown = orig.mouse;954Point2d mouse = Point2d::From(mx, my);955
956// The current mouse location957Vector v = offset.ScaledBy(-1);958v = v.Plus(projRight.ScaledBy(mx/scale));959v = v.Plus(projUp.ScaledBy(my/scale));960
961hRequest hr = {};962hConstraint hc = {};963switch(pending.operation) {964case Pending::COMMAND:965switch(pending.command) {966case Command::DATUM_POINT:967hr = AddRequest(Request::Type::DATUM_POINT);968SK.GetEntity(hr.entity(0))->PointForceTo(v);969ConstrainPointByHovered(hr.entity(0), &mouse);970
971ClearSuper();972break;973
974case Command::LINE_SEGMENT:975case Command::CONSTR_SEGMENT:976hr = AddRequest(Request::Type::LINE_SEGMENT);977SK.GetRequest(hr)->construction = (pending.command == Command::CONSTR_SEGMENT);978SK.GetEntity(hr.entity(1))->PointForceTo(v);979ConstrainPointByHovered(hr.entity(1), &mouse);980
981ClearSuper();982AddToPending(hr);983
984pending.operation = Pending::DRAGGING_NEW_LINE_POINT;985pending.request = hr;986pending.point = hr.entity(2);987pending.description = _("click next point of line, or press Esc");988SK.GetEntity(pending.point)->PointForceTo(v);989break;990
991case Command::RECTANGLE: {992if(!SS.GW.LockedInWorkplane()) {993Error(_("Can't draw rectangle in 3d; first, activate a workplane "994"with Sketch -> In Workplane."));995ClearSuper();996break;997}998hRequest lns[4];999int i;1000SS.UndoRemember();1001for(i = 0; i < 4; i++) {1002lns[i] = AddRequest(Request::Type::LINE_SEGMENT, /*rememberForUndo=*/false);1003AddToPending(lns[i]);1004}1005for(i = 0; i < 4; i++) {1006Constraint::ConstrainCoincident(1007lns[i].entity(1), lns[(i+1)%4].entity(2));1008SK.GetEntity(lns[i].entity(1))->PointForceTo(v);1009SK.GetEntity(lns[i].entity(2))->PointForceTo(v);1010}1011for(i = 0; i < 4; i++) {1012Constraint::Constrain(1013(i % 2) ? Constraint::Type::HORIZONTAL : Constraint::Type::VERTICAL,1014Entity::NO_ENTITY, Entity::NO_ENTITY,1015lns[i].entity(0));1016}1017if(ConstrainPointByHovered(lns[2].entity(1), &mouse)) {1018Vector pos = SK.GetEntity(lns[2].entity(1))->PointGetNum();1019for(i = 0; i < 4; i++) {1020SK.GetEntity(lns[i].entity(1))->PointForceTo(pos);1021SK.GetEntity(lns[i].entity(2))->PointForceTo(pos);1022}1023}1024
1025pending.operation = Pending::DRAGGING_NEW_POINT;1026pending.point = lns[1].entity(2);1027pending.description = _("click to place other corner of rectangle");1028hr = lns[0];1029break;1030}1031case Command::CIRCLE:1032hr = AddRequest(Request::Type::CIRCLE);1033// Centered where we clicked1034SK.GetEntity(hr.entity(1))->PointForceTo(v);1035// Normal to the screen1036SK.GetEntity(hr.entity(32))->NormalForceTo(1037Quaternion::From(SS.GW.projRight, SS.GW.projUp));1038// Initial radius zero1039SK.GetEntity(hr.entity(64))->DistanceForceTo(0);1040
1041ConstrainPointByHovered(hr.entity(1), &mouse);1042
1043ClearSuper();1044AddToPending(hr);1045
1046pending.operation = Pending::DRAGGING_NEW_RADIUS;1047pending.circle = hr.entity(0);1048pending.description = _("click to set radius");1049break;1050
1051case Command::ARC: {1052if(!SS.GW.LockedInWorkplane()) {1053Error(_("Can't draw arc in 3d; first, activate a workplane "1054"with Sketch -> In Workplane."));1055ClearPending();1056break;1057}1058hr = AddRequest(Request::Type::ARC_OF_CIRCLE);1059// This fudge factor stops us from immediately failing to solve1060// because of the arc's implicit (equal radius) tangent.1061Vector adj = SS.GW.projRight.WithMagnitude(2/SS.GW.scale);1062SK.GetEntity(hr.entity(1))->PointForceTo(v.Minus(adj));1063SK.GetEntity(hr.entity(2))->PointForceTo(v);1064SK.GetEntity(hr.entity(3))->PointForceTo(v);1065ConstrainPointByHovered(hr.entity(2), &mouse);1066
1067ClearSuper();1068AddToPending(hr);1069
1070pending.operation = Pending::DRAGGING_NEW_ARC_POINT;1071pending.point = hr.entity(3);1072pending.description = _("click to place point");1073break;1074}1075case Command::CUBIC:1076hr = AddRequest(Request::Type::CUBIC);1077SK.GetEntity(hr.entity(1))->PointForceTo(v);1078SK.GetEntity(hr.entity(2))->PointForceTo(v);1079SK.GetEntity(hr.entity(3))->PointForceTo(v);1080SK.GetEntity(hr.entity(4))->PointForceTo(v);1081ConstrainPointByHovered(hr.entity(1), &mouse);1082
1083ClearSuper();1084AddToPending(hr);1085
1086pending.operation = Pending::DRAGGING_NEW_CUBIC_POINT;1087pending.point = hr.entity(4);1088pending.description = _("click next point of cubic, or press Esc");1089break;1090
1091case Command::WORKPLANE:1092if(LockedInWorkplane()) {1093Error(_("Sketching in a workplane already; sketch in 3d before "1094"creating new workplane."));1095ClearSuper();1096break;1097}1098hr = AddRequest(Request::Type::WORKPLANE);1099SK.GetEntity(hr.entity(1))->PointForceTo(v);1100SK.GetEntity(hr.entity(32))->NormalForceTo(1101Quaternion::From(SS.GW.projRight, SS.GW.projUp));1102ConstrainPointByHovered(hr.entity(1), &mouse);1103
1104ClearSuper();1105break;1106
1107case Command::TTF_TEXT: {1108if(!SS.GW.LockedInWorkplane()) {1109Error(_("Can't draw text in 3d; first, activate a workplane "1110"with Sketch -> In Workplane."));1111ClearSuper();1112break;1113}1114hr = AddRequest(Request::Type::TTF_TEXT);1115AddToPending(hr);1116Request *r = SK.GetRequest(hr);1117r->str = "Abc";1118r->font = Platform::embeddedFont;1119
1120for(int i = 1; i <= 4; i++) {1121SK.GetEntity(hr.entity(i))->PointForceTo(v);1122}1123
1124pending.operation = Pending::DRAGGING_NEW_POINT;1125pending.point = hr.entity(3);1126pending.description = _("click to place bottom right of text");1127break;1128}1129
1130case Command::IMAGE: {1131if(!SS.GW.LockedInWorkplane()) {1132Error(_("Can't draw image in 3d; first, activate a workplane "1133"with Sketch -> In Workplane."));1134ClearSuper();1135break;1136}1137hr = AddRequest(Request::Type::IMAGE);1138AddToPending(hr);1139Request *r = SK.GetRequest(hr);1140r->file = pending.filename;1141r->construction = true;1142
1143for(int i = 1; i <= 4; i++) {1144SK.GetEntity(hr.entity(i))->PointForceTo(v);1145}1146
1147pending.operation = Pending::DRAGGING_NEW_POINT;1148pending.point = hr.entity(3);1149pending.description = "click to place bottom right of image";1150break;1151}1152
1153case Command::COMMENT: {1154ClearSuper();1155Constraint c = {};1156c.group = SS.GW.activeGroup;1157c.workplane = SS.GW.ActiveWorkplane();1158c.type = Constraint::Type::COMMENT;1159c.disp.offset = v;1160c.comment = _("NEW COMMENT -- DOUBLE-CLICK TO EDIT");1161hc = Constraint::AddConstraint(&c);1162break;1163}1164default: ssassert(false, "Unexpected pending menu id");1165}1166break;1167
1168case Pending::DRAGGING_RADIUS:1169ClearPending();1170break;1171
1172case Pending::DRAGGING_NEW_POINT:1173case Pending::DRAGGING_NEW_ARC_POINT:1174ConstrainPointByHovered(pending.point, &mouse);1175ClearPending();1176break;1177
1178case Pending::DRAGGING_NEW_CUBIC_POINT: {1179hRequest hr = pending.point.request();1180Request *r = SK.GetRequest(hr);1181
1182if(hover.entity == hr.entity(1) && r->extraPoints >= 2) {1183// They want the endpoints coincident, which means a periodic1184// spline instead.1185r->type = Request::Type::CUBIC_PERIODIC;1186// Remove the off-curve control points, which are no longer1187// needed here; so move [2,ep+1] down, skipping first pt.1188int i;1189for(i = 2; i <= r->extraPoints+1; i++) {1190SK.GetEntity(hr.entity((i-1)+1))->PointForceTo(1191SK.GetEntity(hr.entity(i+1))->PointGetNum());1192}1193// and move ep+3 down by two, skipping both1194SK.GetEntity(hr.entity((r->extraPoints+1)+1))->PointForceTo(1195SK.GetEntity(hr.entity((r->extraPoints+3)+1))->PointGetNum());1196r->extraPoints -= 2;1197// And we're done.1198SS.MarkGroupDirty(r->group);1199ClearPending();1200break;1201}1202
1203if(ConstrainPointByHovered(pending.point, &mouse)) {1204ClearPending();1205break;1206}1207
1208Entity e;1209if(r->extraPoints >= (int)arraylen(e.point) - 4) {1210ClearPending();1211break;1212}1213
1214(SK.GetRequest(hr)->extraPoints)++;1215SS.GenerateAll(SolveSpaceUI::Generate::REGEN);1216
1217int ep = r->extraPoints;1218Vector last = SK.GetEntity(hr.entity(3+ep))->PointGetNum();1219
1220SK.GetEntity(hr.entity(2+ep))->PointForceTo(last);1221SK.GetEntity(hr.entity(3+ep))->PointForceTo(v);1222SK.GetEntity(hr.entity(4+ep))->PointForceTo(v);1223pending.point = hr.entity(4+ep);1224break;1225}1226
1227case Pending::DRAGGING_NEW_LINE_POINT: {1228if(hover.entity.v) {1229Entity *e = SK.GetEntity(hover.entity);1230if(e->IsPoint()) {1231hRequest hrl = pending.point.request();1232Entity *sp = SK.GetEntity(hrl.entity(1));1233if(( e->PointGetNum()).Equals(1234(sp->PointGetNum())))1235{1236// If we constrained by the hovered point, then we1237// would create a zero-length line segment. That's1238// not good, so just stop drawing.1239ClearPending();1240break;1241}1242}1243}1244
1245bool doneDragging = ConstrainPointByHovered(pending.point, &mouse);1246
1247// Constrain the line segment horizontal or vertical if close enough1248if(hasConstraintSuggestion) {1249Constraint::TryConstrain(constraintSuggestion,1250Entity::NO_ENTITY, Entity::NO_ENTITY, pending.request.entity(0));1251}1252
1253if(doneDragging) {1254ClearPending();1255break;1256}1257
1258// Create a new line segment, so that we continue drawing.1259hRequest hr = AddRequest(Request::Type::LINE_SEGMENT);1260ReplacePending(pending.request, hr);1261SK.GetRequest(hr)->construction = SK.GetRequest(pending.request)->construction;1262// Displace the second point of the new line segment slightly,1263// to avoid creating zero-length edge warnings.1264SK.GetEntity(hr.entity(2))->PointForceTo(1265v.Plus(projRight.ScaledBy(0.5/scale)));1266
1267// Constrain the line segments to share an endpoint1268Constraint::ConstrainCoincident(pending.point, hr.entity(1));1269Vector pendingPos = SK.GetEntity(pending.point)->PointGetNum();1270SK.GetEntity(hr.entity(1))->PointForceTo(pendingPos);1271
1272// And drag an endpoint of the new line segment1273pending.operation = Pending::DRAGGING_NEW_LINE_POINT;1274pending.request = hr;1275pending.point = hr.entity(2);1276pending.description = _("click next point of line, or press Esc");1277
1278break;1279}1280
1281case Pending::NONE:1282default:1283ClearPending();1284if(!hover.IsEmpty()) {1285if(!ctrlDown) {1286hoverWasSelectedOnMousedown = IsSelected(&hover);1287MakeSelected(&hover);1288} else {1289MakeUnselected(&hover, /*coincidentPointTrick=*/true);1290}1291}1292break;1293}1294
1295// Activate group with newly created request/constraint1296Group *g = NULL;1297if(hr.v != 0) {1298g = SK.GetGroup(SK.GetRequest(hr)->group);1299}1300if(hc.v != 0) {1301g = SK.GetGroup(SK.GetConstraint(hc)->group);1302}1303if(g != NULL) {1304g->visible = true;1305}1306
1307SS.ScheduleShowTW();1308Invalidate();1309}
1310
1311void GraphicsWindow::MouseLeftUp(double mx, double my, bool shiftDown, bool ctrlDown) {1312orig.mouseDown = false;1313
1314switch(pending.operation) {1315case Pending::DRAGGING_POINTS:1316case Pending::DRAGGING_CONSTRAINT:1317case Pending::DRAGGING_NORMAL:1318case Pending::DRAGGING_RADIUS:1319if(!hoverWasSelectedOnMousedown) {1320// And then clear the selection again, since they1321// probably didn't want that selected if they just1322// were dragging it.1323ClearSelection();1324}1325hoverWasSelectedOnMousedown = false;1326SS.extraLine.draw = false;1327ClearPending();1328Invalidate();1329break;1330
1331case Pending::DRAGGING_MARQUEE:1332SelectByMarquee();1333ClearPending();1334Invalidate();1335break;1336
1337case Pending::NONE:1338if(hover.IsEmpty() && !ctrlDown) {1339ClearSelection();1340}1341break;1342
1343default:1344break; // do nothing1345}1346}
1347
1348void GraphicsWindow::EditConstraint(hConstraint constraint) {1349constraintBeingEdited = constraint;1350ClearSuper();1351
1352Constraint *c = SK.GetConstraint(constraintBeingEdited);1353if(!c->HasLabel()) {1354// Not meaningful to edit a constraint without a dimension1355return;1356}1357if(c->reference) {1358// Not meaningful to edit a reference dimension1359return;1360}1361
1362Vector p3 = c->GetLabelPos(GetCamera());1363Point2d p2 = ProjectPoint(p3);1364
1365std::string editValue;1366std::string editPlaceholder;1367switch(c->type) {1368case Constraint::Type::COMMENT:1369editValue = c->comment;1370editPlaceholder = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";1371break;1372
1373default: {1374double value = fabs(c->valA);1375
1376// If displayed as radius, also edit as radius.1377if(c->type == Constraint::Type::DIAMETER && c->other)1378value /= 2;1379
1380// Try showing value with default number of digits after decimal first.1381if(c->type == Constraint::Type::LENGTH_RATIO || c->type == Constraint::Type::ARC_ARC_LEN_RATIO || c->type == Constraint::Type::ARC_LINE_LEN_RATIO) {1382editValue = ssprintf("%.3f", value);1383} else if(c->type == Constraint::Type::ANGLE) {1384editValue = SS.DegreeToString(value);1385} else {1386editValue = SS.MmToString(value, true);1387value /= SS.MmPerUnit();1388}1389// If that's not enough to represent it exactly, show the value with as many1390// digits after decimal as required, up to 10.1391int digits = 0;1392while(fabs(std::stod(editValue) - value) > 1e-10) {1393editValue = ssprintf("%.*f", digits, value);1394digits++;1395}1396editPlaceholder = "10.000000";1397break;1398}1399}1400
1401double width, height;1402window->GetContentSize(&width, &height);1403hStyle hs = c->disp.style;1404if(hs.v == 0) hs.v = Style::CONSTRAINT;1405double capHeight = Style::TextHeight(hs);1406double fontHeight = VectorFont::Builtin()->GetHeight(capHeight);1407double editMinWidth = VectorFont::Builtin()->GetWidth(capHeight, editPlaceholder);1408window->ShowEditor(p2.x + width / 2, height / 2 - p2.y,1409fontHeight, editMinWidth,1410/*isMonospace=*/false, editValue);1411}
1412
1413void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) {1414if(window->IsEditorVisible()) return;1415SS.TW.HideEditControl();1416
1417if(hover.constraint.v) {1418EditConstraint(hover.constraint);1419}1420}
1421
1422void GraphicsWindow::EditControlDone(const std::string &s) {1423window->HideEditor();1424window->Invalidate();1425
1426Constraint *c = SK.GetConstraint(constraintBeingEdited);1427
1428if(c->type == Constraint::Type::COMMENT) {1429SS.UndoRemember();1430c->comment = s;1431return;1432}1433
1434if(Expr *e = Expr::From(s, true)) {1435SS.UndoRemember();1436
1437switch(c->type) {1438case Constraint::Type::PROJ_PT_DISTANCE:1439case Constraint::Type::PT_LINE_DISTANCE:1440case Constraint::Type::PT_FACE_DISTANCE:1441case Constraint::Type::PT_PLANE_DISTANCE:1442case Constraint::Type::LENGTH_DIFFERENCE:1443case Constraint::Type::ARC_ARC_DIFFERENCE:1444case Constraint::Type::ARC_LINE_DIFFERENCE: {1445// The sign is not displayed to the user, but this is a signed1446// distance internally. To flip the sign, the user enters a1447// negative distance.1448bool wasNeg = (c->valA < 0);1449if(wasNeg) {1450c->valA = -SS.ExprToMm(e);1451} else {1452c->valA = SS.ExprToMm(e);1453}1454break;1455}1456case Constraint::Type::ANGLE:1457case Constraint::Type::LENGTH_RATIO:1458case Constraint::Type::ARC_ARC_LEN_RATIO:1459case Constraint::Type::ARC_LINE_LEN_RATIO:1460// These don't get the units conversion for distance, and1461// they're always positive1462c->valA = fabs(e->Eval());1463break;1464
1465case Constraint::Type::DIAMETER:1466c->valA = fabs(SS.ExprToMm(e));1467
1468// If displayed and edited as radius, convert back1469// to diameter1470if(c->other)1471c->valA *= 2;1472break;1473
1474default:1475// These are always positive, and they get the units conversion.1476c->valA = fabs(SS.ExprToMm(e));1477break;1478}1479SS.MarkGroupDirty(c->group);1480}1481}
1482
1483void GraphicsWindow::MouseScroll(double zoomMultiplyer) {1484// To support smooth scrolling where scroll wheel events come in increments1485// smaller (or larger) than 1 we do:1486// scale *= exp(ln(1.2) * zoomMultiplyer);1487// to ensure that the same total scroll delta always results in the same1488// total zoom irrespective of in how many increments the zoom was applied.1489// For example if we scroll a total delta of a+b in two events vs. one then1490// scale * e^a * e^b == scale * e^(a+b)1491// while1492// scale * a * b != scale * (a+b)1493// So this constant is ln(1.2) = 0.1823216 to make the default zoom 1.2x1494ZoomToMouse(zoomMultiplyer);1495}
1496
1497void GraphicsWindow::MouseLeave() {1498// Un-hover everything when the mouse leaves our window, unless there's1499// currently a context menu shown.1500if(!context.active) {1501hover.Clear();1502toolbarHovered = Command::NONE;1503Invalidate();1504}1505SS.extraLine.draw = false;1506}
1507
1508void GraphicsWindow::SixDofEvent(Platform::SixDofEvent event) {1509if(event.type == Platform::SixDofEvent::Type::RELEASE) {1510ZoomToFit(/*includingInvisibles=*/false, /*useSelection=*/true);1511Invalidate();1512return;1513}1514
1515if(!havePainted) return;1516Vector out = projRight.Cross(projUp);1517
1518// rotation vector is axis of rotation, and its magnitude is angle1519Vector aa = Vector::From(event.rotationX, event.rotationY, event.rotationZ);1520// but it's given with respect to screen projection frame1521aa = aa.ScaleOutOfCsys(projRight, projUp, out);1522double aam = aa.Magnitude();1523if(aam > 0.0) aa = aa.WithMagnitude(1);1524
1525// This can either transform our view, or transform a linked part.1526GroupSelection();1527Entity *e = NULL;1528Group *g = NULL;1529if(gs.points == 1 && gs.n == 1) e = SK.GetEntity(gs.point [0]);1530if(gs.entities == 1 && gs.n == 1) e = SK.GetEntity(gs.entity[0]);1531if(e) g = SK.GetGroup(e->group);1532if(g && g->type == Group::Type::LINKED && !event.shiftDown) {1533// Apply the transformation to a linked part. Gain down the Z1534// axis, since it's hard to see what you're doing on that one since1535// it's normal to the screen.1536Vector t = projRight.ScaledBy(event.translationX/scale).Plus(1537projUp .ScaledBy(event.translationY/scale).Plus(1538out .ScaledBy(0.1*event.translationZ/scale)));1539Quaternion q = Quaternion::From(aa, aam);1540
1541// If we go five seconds without SpaceNavigator input, or if we've1542// switched groups, then consider that a new action and save an undo1543// point.1544int64_t now = GetMilliseconds();1545if(now - last6DofTime > 5000 ||1546last6DofGroup != g->h)1547{1548SS.UndoRemember();1549}1550
1551g->TransformImportedBy(t, q);1552
1553last6DofTime = now;1554last6DofGroup = g->h;1555SS.MarkGroupDirty(g->h);1556} else {1557// Apply the transformation to the view of the everything. The1558// x and y components are translation; but z component is scale,1559// not translation, or else it would do nothing in a parallel1560// projection1561offset = offset.Plus(projRight.ScaledBy(event.translationX/scale));1562offset = offset.Plus(projUp.ScaledBy(event.translationY/scale));1563scale *= exp(0.001*event.translationZ);1564
1565if(aam > 0.0) {1566projRight = projRight.RotatedAbout(aa, -aam);1567projUp = projUp. RotatedAbout(aa, -aam);1568NormalizeProjectionVectors();1569}1570}1571
1572havePainted = false;1573Invalidate();1574}
1575