Many user interface additions. Now I report when the solver fails,

and in the case of a singular Jacobian, report which constraints
can be removed to fix it. Also a mechanism to hover and select
entities and constraints from the text window.

[git-p4: depot-paths = "//depot/solvespace/": change = 1746]
This commit is contained in:
Jonathan Westhues 2008-05-26 01:56:50 -08:00
parent 727ac126fb
commit b484c26493
12 changed files with 271 additions and 67 deletions

View File

@ -1,5 +1,7 @@
#include "solvespace.h"
const hConstraint Constraint::NO_CONSTRAINT = { 0 };
char *Constraint::DescriptionString(void) {
static char ret[1024];
sprintf(ret, "c%03x", h.v);

View File

@ -28,6 +28,7 @@ void Constraint::LineDrawOrGetDistance(Vector a, Vector b) {
double d = dogd.mp.DistanceToLine(ap, bp.Minus(ap), true);
dogd.dmin = min(dogd.dmin, d);
}
dogd.refp = (a.Plus(b)).ScaledBy(0.5);
}
double Constraint::EllipticalInterpolation(double rx, double ry, double theta) {
@ -56,6 +57,7 @@ void Constraint::DoLabel(Vector ref, Vector *labelPos, Vector gr, Vector gu) {
} else {
Point2d o = SS.GW.ProjectPoint(ref);
dogd.dmin = min(dogd.dmin, o.DistanceTo(dogd.mp) - 10);
dogd.refp = ref;
}
}
@ -197,6 +199,7 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) {
// same center; so if the point is visible, then this
// constraint cannot be selected. But that's okay.
dogd.dmin = min(dogd.dmin, pp.DistanceTo(dogd.mp) - 3);
dogd.refp = p;
}
break;
}
@ -449,7 +452,8 @@ s:
(type == AT_MIDPOINT) ? "M" : NULL)));
glPopMatrix();
} else {
Point2d ref = SS.GW.ProjectPoint(m.Plus(offset));
dogd.refp = m.Plus(offset);
Point2d ref = SS.GW.ProjectPoint(dogd.refp);
dogd.dmin = min(dogd.dmin, ref.DistanceTo(dogd.mp)-10);
}
} else {
@ -518,3 +522,12 @@ Vector Constraint::GetLabelPos(void) {
return p;
}
Vector Constraint::GetReferencePos(void) {
dogd.drawing = false;
dogd.refp = SS.GW.offset.ScaledBy(-1);
DrawOrGetDistance(NULL);
return dogd.refp;
}

View File

@ -522,6 +522,7 @@ void Entity::LineDrawOrGetDistance(Vector a, Vector b) {
double d = dogd.mp.DistanceToLine(ap, bp.Minus(ap), true);
dogd.dmin = min(dogd.dmin, d);
}
dogd.refp = (a.Plus(b)).ScaledBy(0.5);
}
void Entity::LineDrawOrGetDistanceOrEdge(Vector a, Vector b) {
@ -555,6 +556,16 @@ double Entity::GetDistance(Point2d mp) {
return dogd.dmin;
}
Vector Entity::GetReferencePos(void) {
dogd.drawing = false;
dogd.edges = NULL;
dogd.refp = SS.GW.offset.ScaledBy(-1);
DrawOrGetDistance(-1);
return dogd.refp;
}
void Entity::DrawOrGetDistance(int order) {
Group *g = SS.GetGroup(group);
// If an entity is invisible, then it doesn't get shown, and it doesn't
@ -615,7 +626,6 @@ void Entity::DrawOrGetDistance(int order) {
case NORMAL_IN_3D:
case NORMAL_IN_2D: {
if(order >= 0 && order != 2) break;
if(!SS.GW.showNormals) break;
int i;
for(i = 0; i < 2; i++) {
@ -630,6 +640,7 @@ void Entity::DrawOrGetDistance(int order) {
} else {
glxColor3d(0, 0.4, 0.4);
if(i > 0) break;
if(!SS.GW.showNormals) break;
}
Quaternion q = NormalGetNum();

View File

@ -320,7 +320,7 @@ void GraphicsWindow::MenuEdit(int id) {
SS.GW.ClearSuper();
// And regenerate to get rid of what it generates, plus anything
// that references it (since the regen code checks for that).
SS.GW.GeneratePerSolving();
SS.GenerateAll(SS.GW.solving == SOLVE_ALWAYS, 0, INT_MAX);
SS.GW.EnsureValidActives();
SS.TW.Show();
break;
@ -624,7 +624,9 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
default: oops();
}
SS.GW.GeneratePerSolving();
if(pending.operation != 0 && pending.operation != DRAGGING_CONSTRAINT) {
SS.GW.GeneratePerSolving();
}
havePainted = false;
}
@ -640,10 +642,34 @@ bool GraphicsWindow::Selection::IsEmpty(void) {
}
void GraphicsWindow::Selection::Clear(void) {
entity.v = constraint.v = 0;
emphasized = false;
}
void GraphicsWindow::Selection::Draw(void) {
if(entity.v) SS.GetEntity (entity )->Draw(-1);
if(constraint.v) SS.GetConstraint(constraint)->Draw();
Vector refp;
if(entity.v) {
Entity *e = SS.GetEntity(entity);
e->Draw(-1);
if(emphasized) refp = e->GetReferencePos();
}
if(constraint.v) {
Constraint *c = SS.GetConstraint(constraint);
c->Draw();
if(emphasized) refp = c->GetReferencePos();
}
if(emphasized && (constraint.v || entity.v)) {
double s = 0.501/SS.GW.scale;
Vector topLeft = SS.GW.projRight.ScaledBy(-SS.GW.width*s);
topLeft = topLeft.Plus(SS.GW.projUp.ScaledBy(SS.GW.height*s));
topLeft = topLeft.Minus(SS.GW.offset);
glLineWidth(40);
glColor4d(1.0, 1.0, 0, 0.2);
glBegin(GL_LINES);
glxVertex3v(topLeft);
glxVertex3v(refp);
glEnd();
glLineWidth(1);
}
}
void GraphicsWindow::ClearSuper(void) {
@ -998,7 +1024,9 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
break;
}
}
SS.GW.GeneratePerSolving();
if(pending.operation != 0 && pending.operation != DRAGGING_CONSTRAINT) {
SS.GW.GeneratePerSolving();
}
SS.TW.Show();
InvalidateGraphics();

View File

@ -98,6 +98,7 @@ void Group::MenuGroup(int id) {
Vector offsetf = (e->WorkplaneGetOffset()).ScaledBy(-1);
SS.GW.AnimateOnto(quatf, offsetf);
}
TextWindow::ScreenSelectGroup(0, g.h.v);
SS.TW.Show();
}
@ -517,7 +518,7 @@ void Group::Draw(void) {
glxFillMesh(&mesh);
glDisable(GL_LIGHTING);
// glxDebugMesh(&mesh);
glxDebugMesh(&mesh);
if(polyError.yes) {
glxColor4d(1, 0, 0, 0.2);
@ -534,7 +535,7 @@ void Group::Draw(void) {
glxWriteText("not closed contour!");
glPopMatrix();
} else {
glxColor4d(0, 1.0, 1.0, 0.1);
glxColor4d(0, 1.0, 1.0, 0.05);
glPolygonOffset(-1, -1);
glxFillPolygon(&poly);
glPolygonOffset(0, 0);

View File

@ -10,6 +10,7 @@ class hParam;
class Entity;
class Param;
class hConstraint;
class hEquation;
class Equation;
@ -88,6 +89,14 @@ public:
hGroup opB;
bool visible;
static const int SOLVED_OKAY = 0;
static const int DIDNT_CONVERGE = 10;
static const int SINGULAR_JACOBIAN = 11;
struct {
int how;
SList<hConstraint> remove;
} solved;
static const int WORKPLANE_BY_POINT_ORTHO = 6000;
static const int WORKPLANE_BY_LINE_SEGMENTS = 6001;
static const int EXTRUDE_ONE_SIDED = 7000;
@ -288,6 +297,7 @@ public:
Point2d mp;
double dmin;
SEdgeList *edges;
Vector refp;
} dogd; // state for drawing or getting distance (for hit testing)
void LineDrawOrGetDistance(Vector a, Vector b);
void LineDrawOrGetDistanceOrEdge(Vector a, Vector b);
@ -296,6 +306,7 @@ public:
void Draw(int order);
double GetDistance(Point2d mp);
void GenerateEdges(SEdgeList *el);
Vector GetReferencePos(void);
void AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index);
void GenerateEquations(IdList<Equation,hEquation> *l);
@ -328,6 +339,8 @@ public:
class Constraint {
public:
static const hConstraint NO_CONSTRAINT;
static const int USER_EQUATION = 10;
static const int POINTS_COINCIDENT = 20;
static const int PT_PT_DISTANCE = 30;
@ -382,6 +395,7 @@ public:
bool drawing;
Point2d mp;
double dmin;
Vector refp;
} dogd; // state for drawing or getting distance (for hit testing)
void LineDrawOrGetDistance(Vector a, Vector b);
void DrawOrGetDistance(Vector *labelPos);
@ -391,6 +405,7 @@ public:
double GetDistance(Point2d mp);
Vector GetLabelPos(void);
Vector GetReferencePos(void);
void Draw(void);
bool HasLabel(void);

View File

@ -14,8 +14,8 @@ void SolveSpace::Init(char *cmdLine) {
TW.Init();
GW.Init();
GenerateAll(true, 0, INT_MAX);
TW.Show();
GenerateAll(false, 0, INT_MAX);
}
bool SolveSpace::PruneOrphans(void) {
@ -127,7 +127,8 @@ void SolveSpace::GenerateAll(bool andSolve) {
int firstShown = INT_MAX, lastShown = 0;
// The references don't count, so start from group 1
for(i = 1; i < group.n; i++) {
if(group.elem[i].visible) {
Group *g = &(group.elem[i]);
if(g->visible || (g->solved.how != Group::SOLVED_OKAY)) {
firstShown = min(firstShown, i);
lastShown = max(lastShown, i);
}
@ -182,6 +183,7 @@ void SolveSpace::GenerateAll(bool andSolve, int first, int last) {
if(g->h.v == Group::HGROUP_REFERENCES.v) {
ForceReferences();
g->solved.how = Group::SOLVED_OKAY;
} else {
if(i >= first && i <= last) {
// The group falls inside the range, so really solve it,
@ -271,7 +273,7 @@ void SolveSpace::ForceReferences(void) {
}
}
bool SolveSpace::SolveGroup(hGroup hg) {
void SolveSpace::SolveGroup(hGroup hg) {
int i;
// Clear out the system to be solved.
sys.entity.Clear();
@ -293,26 +295,9 @@ bool SolveSpace::SolveGroup(hGroup hg) {
p->known = false;
p->val = GetParam(p->h)->val;
}
// And generate all the equations from constraints in this group
for(i = 0; i < constraint.n; i++) {
Constraint *c = &(constraint.elem[i]);
if(c->group.v != hg.v) continue;
c->Generate(&(sys.eq));
}
// And the equations from entities
for(i = 0; i < entity.n; i++) {
Entity *e = &(entity.elem[i]);
if(e->group.v != hg.v) continue;
e->GenerateEquations(&(sys.eq));
}
// And from the groups themselves
g->GenerateEquations(&(sys.eq));
bool r = sys.Solve();
sys.Solve(g);
FreeAllTemporary();
return r;
}
void SolveSpace::MenuFile(int id) {

View File

@ -162,12 +162,14 @@ public:
void WriteJacobian(int eqTag, int paramTag);
void EvalJacobian(void);
void WriteEquationsExceptFor(hConstraint hc, hGroup hg);
void FindWhichToRemoveToFixJacobian(Group *g);
void SolveBySubstitution(void);
static bool IsDragged(hParam p);
bool NewtonSolve(int tag);
bool Solve(void);
void Solve(Group *g);
};
@ -236,7 +238,7 @@ public:
void GenerateAll(bool andSolve);
void GenerateAll(bool andSolve, int first, int last);
bool SolveGroup(hGroup hg);
void SolveGroup(hGroup hg);
void ForceReferences(void);
// The system to be solved.

View File

@ -349,17 +349,60 @@ bool System::NewtonSolve(int tag) {
}
} while(iter++ < 50 && !converged);
if(converged) {
return true;
} else {
dbp("no convergence");
return false;
return converged;
}
void System::WriteEquationsExceptFor(hConstraint hc, hGroup hg) {
int i;
// Generate all the equations from constraints in this group
for(i = 0; i < SS.constraint.n; i++) {
Constraint *c = &(SS.constraint.elem[i]);
if(c->group.v != hg.v) continue;
if(c->h.v == hc.v) continue;
c->Generate(&eq);
}
// And the equations from entities
for(i = 0; i < SS.entity.n; i++) {
Entity *e = &(SS.entity.elem[i]);
if(e->group.v != hg.v) continue;
e->GenerateEquations(&eq);
}
// And from the groups themselves
(SS.GetGroup(hg))->GenerateEquations(&eq);
}
void System::FindWhichToRemoveToFixJacobian(Group *g) {
int i;
(g->solved.remove).Clear();
for(i = 0; i < SS.constraint.n; i++) {
Constraint *c = &(SS.constraint.elem[i]);
if(c->group.v != g->h.v) continue;
param.ClearTags();
eq.Clear();
WriteEquationsExceptFor(c->h, g->h);
eq.ClearTags();
WriteJacobian(0, 0);
EvalJacobian();
int rank = GaussJordan();
if(rank == mat.m) {
// We fixed it by removing this constraint
(g->solved.remove).Add(&(c->h));
}
}
}
bool System::Solve(void) {
void System::Solve(Group *g) {
g->solved.remove.Clear();
WriteEquationsExceptFor(Constraint::NO_CONSTRAINT, g->h);
int i, j = 0;
/*
dbp("%d equations", eq.n);
for(i = 0; i < eq.n; i++) {
@ -391,6 +434,12 @@ bool System::Solve(void) {
} */
int rank = GaussJordan();
if(rank != mat.m) {
FindWhichToRemoveToFixJacobian(g);
g->solved.how = Group::SINGULAR_JACOBIAN;
TextWindow::ReportHowGroupSolved(g->h);
return;
}
/* dbp("bound states:");
for(j = 0; j < mat.n; j++) {
@ -417,8 +466,13 @@ bool System::Solve(void) {
// The main param table keeps track of what was assumed.
pp->assumed = (p->tag == VAR_ASSUMED);
}
if(g->solved.how != Group::SOLVED_OKAY) {
g->solved.how = Group::SOLVED_OKAY;
TextWindow::ReportHowGroupSolved(g->h);
}
} else {
g->solved.how = Group::DIDNT_CONVERGE;
TextWindow::ReportHowGroupSolved(g->h);
}
return true;
}

View File

@ -5,15 +5,16 @@ const TextWindow::Color TextWindow::fgColors[] = {
{ 'd', RGB(255, 255, 255) },
{ 'l', RGB(100, 100, 255) },
{ 't', RGB(255, 200, 0) },
{ 'h', RGB(170, 0, 0) },
{ 'h', RGB( 90, 90, 90) },
{ 's', RGB( 40, 255, 40) },
{ 'm', RGB(200, 200, 0) },
{ 'r', RGB( 0, 0, 0) },
{ 'x', RGB(255, 20, 20) },
{ 0, 0 },
};
const TextWindow::Color TextWindow::bgColors[] = {
{ 'd', RGB( 0, 0, 0) },
{ 't', RGB( 40, 20, 40) },
{ 't', RGB( 34, 15, 15) },
{ 'a', RGB( 20, 20, 20) },
{ 'r', RGB(255, 255, 255) },
{ 0, 0 },
@ -59,7 +60,7 @@ void TextWindow::Printf(bool halfLine, char *fmt, ...) {
int fg = 'd', bg = 'd';
int link = NOT_A_LINK;
DWORD data = 0;
LinkFunction *f = NULL;
LinkFunction *f = NULL, *h = NULL;
c = 0;
while(*fmt) {
@ -91,6 +92,7 @@ void TextWindow::Printf(bool halfLine, char *fmt, ...) {
link = NOT_A_LINK;
data = 0;
f = NULL;
h = NULL;
break;
case 'F':
@ -121,6 +123,10 @@ void TextWindow::Printf(bool halfLine, char *fmt, ...) {
f = va_arg(vl, LinkFunction *);
break;
case 'h':
h = va_arg(vl, LinkFunction *);
break;
case 'D':
data = va_arg(vl, DWORD);
break;
@ -142,6 +148,7 @@ void TextWindow::Printf(bool halfLine, char *fmt, ...) {
meta[r][c].link = link;
meta[r][c].data = data;
meta[r][c].f = f;
meta[r][c].h = h;
c++;
}
@ -160,6 +167,9 @@ done:
void TextWindow::Show(void) {
if(!(SS.GW.pending.operation)) SS.GW.ClearPending();
SS.GW.GroupSelection();
#define gs (SS.GW.gs)
ShowHeader();
if(SS.GW.pending.description) {
@ -174,8 +184,7 @@ void TextWindow::Show(void) {
// fall through
case SCREEN_LIST_OF_GROUPS: ShowListOfGroups(); break;
case SCREEN_GROUP_INFO: ShowGroupInfo(); break;
case SCREEN_REQUEST_INFO: ShowRequestInfo(); break;
case SCREEN_CONSTRAINT_INFO: ShowConstraintInfo(); break;
case SCREEN_GROUP_SOLVE_INFO: ShowGroupSolveInfo(); break;
}
}
InvalidateText();
@ -297,18 +306,32 @@ void TextWindow::ScreenActivateGroup(int link, DWORD v) {
}
SS.GW.ClearSuper();
}
void TextWindow::ReportHowGroupSolved(hGroup hg) {
SS.TW.OneScreenForwardTo(SCREEN_GROUP_SOLVE_INFO);
SS.TW.shown->group.v = hg.v;
SS.TW.Show();
}
void TextWindow::ScreenHowGroupSolved(int link, DWORD v) {
if(SS.GW.activeGroup.v != v) {
ScreenActivateGroup(link, v);
}
SS.TW.OneScreenForwardTo(SCREEN_GROUP_SOLVE_INFO);
SS.TW.shown->group.v = v;
}
void TextWindow::ShowListOfGroups(void) {
Printf(true, "%Ftactive show group-name%E");
Printf(true, "%Ftactv show ok group-name%E");
int i;
for(i = 0; i < SS.group.n; i++) {
Group *g = &(SS.group.elem[i]);
char *s = g->DescriptionString();
bool active = (g->h.v == SS.GW.activeGroup.v);
bool shown = g->visible;
bool ok = (g->solved.how == Group::SOLVED_OKAY);
bool ref = (g->h.v == Group::HGROUP_REFERENCES.v);
Printf(false, "%Bp%Fd "
"%Fp%D%f%s%Ll%s%E%s "
Printf(false, "%Bp%Fd "
"%Fp%D%f%s%Ll%s%E%s "
"%Fp%D%f%Ll%s%E%s "
"%Fp%D%f%s%Ll%s%E "
"%Fl%Ll%D%f%s",
// Alternate between light and dark backgrounds, for readability
(i & 1) ? 'd' : 'a',
@ -321,6 +344,10 @@ void TextWindow::ShowListOfGroups(void) {
shown ? 's' : 'h', g->h.v, (&TextWindow::ScreenToggleGroupShown),
shown ? "yes" : "no",
shown ? "" : " ",
// Link to the errors, if a problem occured while solving
ok ? 's' : 'x', g->h.v, (&TextWindow::ScreenHowGroupSolved),
ok ? "ok" : "",
ok ? "" : "NO",
// Link to a screen that gives more details on the group
g->h.v, (&TextWindow::ScreenSelectGroup), s);
}
@ -332,13 +359,25 @@ void TextWindow::ShowListOfGroups(void) {
}
void TextWindow::ScreenHoverConstraint(int link, DWORD v) {
SS.GW.hover.Clear();
SS.GW.hover.constraint.v = v;
SS.GW.hover.emphasized = true;
}
void TextWindow::ScreenHoverRequest(int link, DWORD v) {
SS.GW.hover.Clear();
hRequest hr = { v };
SS.GW.hover.entity = hr.entity(0);
SS.GW.hover.emphasized = true;
}
void TextWindow::ScreenSelectConstraint(int link, DWORD v) {
SS.TW.OneScreenForwardTo(SCREEN_CONSTRAINT_INFO);
SS.TW.shown->constraint.v = v;
SS.GW.ClearSelection();
SS.GW.selection[0].constraint.v = v;
}
void TextWindow::ScreenSelectRequest(int link, DWORD v) {
SS.TW.OneScreenForwardTo(SCREEN_REQUEST_INFO);
SS.TW.shown->request.v = v;
hRequest hr = { v };
SS.GW.ClearSelection();
SS.GW.selection[0].entity = hr.entity(0);
}
void TextWindow::ScreenChangeExtrudeSides(int link, DWORD v) {
Group *g = SS.GetGroup(SS.TW.shown->group);
@ -396,9 +435,10 @@ void TextWindow::ShowGroupInfo(void) {
if(r->group.v == shown->group.v) {
char *s = r->DescriptionString();
Printf(false, "%Bp %Fl%Ll%D%f%s%E",
Printf(false, "%Bp %Fl%Ll%D%f%h%s%E",
(a & 1) ? 'd' : 'a',
r->h.v, (&TextWindow::ScreenSelectRequest), s);
r->h.v, (&TextWindow::ScreenSelectRequest),
&(TextWindow::ScreenHoverRequest), s);
a++;
}
}
@ -411,15 +451,46 @@ void TextWindow::ShowGroupInfo(void) {
if(c->group.v == shown->group.v) {
char *s = c->DescriptionString();
Printf(false, "%Bp %Fl%Ll%D%f%s%E",
Printf(false, "%Bp %Fl%Ll%D%f%h%s%E",
(a & 1) ? 'd' : 'a',
c->h.v, (&TextWindow::ScreenSelectConstraint), s);
c->h.v, (&TextWindow::ScreenSelectConstraint),
(&TextWindow::ScreenHoverConstraint), s);
a++;
}
}
if(a == 0) Printf(false, "%Ba (none)");
}
void TextWindow::ShowGroupSolveInfo(void) {
Group *g = SS.group.FindById(shown->group);
Printf(true, "%FtGROUP %E%s", g->DescriptionString());
switch(g->solved.how) {
case Group::SOLVED_OKAY:
Printf(true, " %Fsgroup solved okay%E");
break;
case Group::DIDNT_CONVERGE:
Printf(true, " %FxSOLVE FAILED!%Fd no convergence");
break;
case Group::SINGULAR_JACOBIAN: {
Printf(true, "%FxSOLVE FAILED!%Fd inconsistent system");
Printf(true, "remove any one of these to fix it");
for(int i = 0; i < g->solved.remove.n; i++) {
hConstraint hc = g->solved.remove.elem[i];
Constraint *c = SS.GetConstraint(hc);
Printf(false, "%Bp %Fl%Ll%D%f%h%s%E",
(i & 1) ? 'd' : 'a',
c->h.v, (&TextWindow::ScreenSelectConstraint),
(&TextWindow::ScreenHoverConstraint),
c->DescriptionString());
}
break;
}
}
}
void TextWindow::ShowRequestInfo(void) {
Request *r = SS.GetRequest(shown->request);

14
ui.h
View File

@ -27,6 +27,7 @@ public:
int link;
DWORD data;
LinkFunction *f;
LinkFunction *h;
} meta[MAX_ROWS][MAX_COLS];
int top[MAX_ROWS]; // in half-line units, or -1 for unused
@ -41,9 +42,7 @@ public:
// State for the screen that we are showing in the text window.
static const int SCREEN_LIST_OF_GROUPS = 0;
static const int SCREEN_GROUP_INFO = 1;
static const int SCREEN_REQUEST_INFO = 2;
static const int SCREEN_ENTIY_INFO = 3;
static const int SCREEN_CONSTRAINT_INFO = 4;
static const int SCREEN_GROUP_SOLVE_INFO = 2;
typedef struct {
int screen;
hGroup group;
@ -56,6 +55,8 @@ public:
int history;
ShownState *shown;
static void ReportHowGroupSolved(hGroup hg);
void ShowHeader(void);
// These are self-contained screens, that show some information about
// the sketch.
@ -64,15 +65,20 @@ public:
void ShowRequestInfo(void);
void ShowEntityInfo(void);
void ShowConstraintInfo(void);
void ShowGroupSolveInfo(void);
void OneScreenForwardTo(int screen);
static void ScreenSelectGroup(int link, DWORD v);
static void ScreenActivateGroup(int link, DWORD v);
static void ScreenToggleGroupShown(int link, DWORD v);
static void ScreenHowGroupSolved(int link, DWORD v);
static void ScreenShowGroupsSpecial(int link, DWORD v);
static void ScreenHoverConstraint(int link, DWORD v);
static void ScreenHoverRequest(int link, DWORD v);
static void ScreenSelectRequest(int link, DWORD v);
static void ScreenSelectConstraint(int link, DWORD v);
static void ScreenChangeExtrudeSides(int link, DWORD v);
static void ScreenChangeMeshCombine(int link, DWORD v);
@ -218,6 +224,8 @@ public:
public:
hEntity entity;
hConstraint constraint;
bool emphasized;
void Draw(void);

View File

@ -288,6 +288,9 @@ LRESULT CALLBACK TextWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
case WM_LBUTTONDOWN:
case WM_MOUSEMOVE: {
GraphicsWindow::Selection ps = SS.GW.hover;
SS.GW.hover.Clear();
int x = LOWORD(lParam);
int y = HIWORD(lParam);
@ -303,25 +306,30 @@ LRESULT CALLBACK TextWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
}
if(r >= SS.TW.rows) {
SetCursor(LoadCursor(NULL, IDC_ARROW));
break;
goto done;
}
#define META (SS.TW.meta[r][c])
if(msg == WM_MOUSEMOVE) {
if(SS.TW.meta[r][c].link) {
if(META.link) {
SetCursor(LoadCursor(NULL, IDC_HAND));
if(META.h) {
(META.h)(META.link, META.data);
}
} else {
SetCursor(LoadCursor(NULL, IDC_ARROW));
}
} else {
if(SS.TW.meta[r][c].link && SS.TW.meta[r][c].f) {
(SS.TW.meta[r][c].f)(
SS.TW.meta[r][c].link,
SS.TW.meta[r][c].data
);
if(META.link && META.f) {
(META.f)(META.link, META.data);
SS.TW.Show();
InvalidateGraphics();
}
}
done:
if(!ps.Equals(&(SS.GW.hover))) {
InvalidateGraphics();
}
break;
}
@ -361,6 +369,12 @@ static BOOL ProcessKeyDown(WPARAM wParam)
}
}
if(wParam == VK_BACK && !GraphicsEditControlIsVisible()) {
TextWindow::ScreenNavigation('b', 0);
SS.TW.Show();
return TRUE;
}
int c;
switch(wParam) {
case VK_OEM_PLUS: c = '+'; break;