diff --git a/Makefile b/Makefile index 65972e6..fa4da9f 100644 --- a/Makefile +++ b/Makefile @@ -97,5 +97,5 @@ toolbar.cpp: $(OBJDIR)/icons.h glhelper.cpp: bitmapfont.table font.table $(OBJDIR)/icons.h: icons/* png2c.pl - perl png2c.pl > $(OBJDIR)/icons.h + perl png2c.pl $(OBJDIR)/icons.h $(OBJDIR)/icons-proto.h diff --git a/clipboard.cpp b/clipboard.cpp index 4965eb1..cbf40fd 100644 --- a/clipboard.cpp +++ b/clipboard.cpp @@ -359,7 +359,7 @@ void TextWindow::ShowPasteTransformed(void) { Printf(true, "%Ba %FtREPEAT%E %d time%s %Fl%Lt%f[change]%E", shown.paste.times, (shown.paste.times == 1) ? "" : "s", &ScreenChangePasteTransformed); - Printf(false, "%Bd %FtROTATE%E %@° %Fl%Lr%f[change]%E", + Printf(false, "%Bd %FtROTATE%E %@ degrees %Fl%Lr%f[change]%E", shown.paste.theta*180/PI, &ScreenChangePasteTransformed); Printf(false, "%Ba %FtABOUT PT%E (%s, %s, %s) %Fl%Lo%f[use selected]%E", diff --git a/draw.cpp b/draw.cpp index 2d88218..56e2ee8 100644 --- a/draw.cpp +++ b/draw.cpp @@ -454,11 +454,13 @@ Vector GraphicsWindow::VectorFromProjs(Vector rightUpForward) { return r; } -void GraphicsWindow::Paint(int w, int h) { +void GraphicsWindow::Paint(void) { int i; havePainted = true; - width = w; height = h; + int w, h; + GetGraphicsWindowSize(&w, &h); + width = w; height = h; glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); diff --git a/export.cpp b/export.cpp index e9abc60..b89513b 100644 --- a/export.cpp +++ b/export.cpp @@ -716,7 +716,7 @@ void SolveSpace::ExportAsPngTo(char *filename) { // so repaint the scene. And hide the toolbar too. int prevShowToolbar = SS.showToolbar; SS.showToolbar = false; - SS.GW.Paint(w, h); + SS.GW.Paint(); SS.showToolbar = prevShowToolbar; FILE *f = fopen(filename, "wb"); diff --git a/generate.cpp b/generate.cpp index 74d5b45..2edec89 100644 --- a/generate.cpp +++ b/generate.cpp @@ -200,20 +200,10 @@ void SolveSpace::GenerateAll(int first, int last, bool andFindFree) { double left = 80, top = -20, width = 240, height = 24; glColor3d(0.9, 0.8, 0.8); - glBegin(GL_QUADS); - glVertex2d(left, top); - glVertex2d(left+width, top); - glVertex2d(left+width, top-height); - glVertex2d(left, top-height); - glEnd(); + glxAxisAlignedQuad(left, left+width, top, top-height); glLineWidth(1); glColor3d(0.0, 0.0, 0.0); - glBegin(GL_LINE_LOOP); - glVertex2d(left, top); - glVertex2d(left+width, top); - glVertex2d(left+width, top-height); - glVertex2d(left, top-height); - glEnd(); + glxAxisAlignedLineLoop(left, left+width, top, top-height); glxCreateBitmapFont(); glColor3d(0, 0, 0); diff --git a/glhelper.cpp b/glhelper.cpp index f8cbf18..dd3b613 100644 --- a/glhelper.cpp +++ b/glhelper.cpp @@ -10,7 +10,8 @@ static bool ColorLocked; static bool DepthOffsetLocked; #define FONT_SCALE(h) ((h)/22.0) -double glxStrWidth(char *str, double h) { +double glxStrWidth(char *str, double h) +{ int w = 0; for(; *str; str++) { int c = *str; @@ -21,7 +22,8 @@ double glxStrWidth(char *str, double h) { } return w*FONT_SCALE(h)/SS.GW.scale; } -double glxStrHeight(double h) { +double glxStrHeight(double h) +{ // The characters have height ~22, as they appear in the table. return 22.0*FONT_SCALE(h)/SS.GW.scale; } @@ -99,6 +101,26 @@ void glxVertex3v(Vector u) glVertex3f((GLfloat)u.x, (GLfloat)u.y, (GLfloat)u.z); } +void glxAxisAlignedQuad(double l, double r, double t, double b) +{ + glBegin(GL_QUADS); + glVertex2d(l, t); + glVertex2d(l, b); + glVertex2d(r, b); + glVertex2d(r, t); + glEnd(); +} + +void glxAxisAlignedLineLoop(double l, double r, double t, double b) +{ + glBegin(GL_LINE_LOOP); + glVertex2d(l, t); + glVertex2d(l, b); + glVertex2d(r, b); + glVertex2d(r, t); + glEnd(); +} + static void FatLineEndcap(Vector p, Vector u, Vector v) { // A table of cos and sin of (pi*i/10 + pi/2), as i goes from 0 to 10 @@ -123,7 +145,8 @@ static void FatLineEndcap(Vector p, Vector u, Vector v) glEnd(); } -void glxFatLine(Vector a, Vector b, double width) { +void glxFatLine(Vector a, Vector b, double width) +{ // The half-width of the line we're drawing. double hw = width / 2; Vector ab = b.Minus(a); @@ -277,10 +300,10 @@ void glxFillMesh(int specColor, SMesh *m, DWORD h, DWORD s1, DWORD s2) glEnd(); } -static void GLX_CALLBACK Vertex(Vector *p) { +static void GLX_CALLBACK Vertex(Vector *p) +{ glxVertex3v(*p); } - void glxFillPolygon(SPolygon *p) { GLUtesselator *gt = gluNewTess(); @@ -434,7 +457,8 @@ void glxMarkPolygonNormal(SPolygon *p) glEnable(GL_LIGHTING); } -void glxDepthRangeOffset(int units) { +void glxDepthRangeOffset(int units) +{ if(!DepthOffsetLocked) { // The size of this step depends on the resolution of the Z buffer; for // a 16-bit buffer, this should be fine. @@ -443,7 +467,8 @@ void glxDepthRangeOffset(int units) { } } -void glxDepthRangeLockToFront(bool yes) { +void glxDepthRangeLockToFront(bool yes) +{ if(yes) { DepthOffsetLocked = true; glDepthRange(0, 0); @@ -453,7 +478,8 @@ void glxDepthRangeLockToFront(bool yes) { } } -void glxCreateBitmapFont(void) { +void glxCreateBitmapFont(void) +{ glBindTexture(GL_TEXTURE_2D, TEXTURE_BITMAP_FONT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); @@ -467,7 +493,8 @@ void glxCreateBitmapFont(void) { FontTexture); } -void glxBitmapCharQuad(char c, double x, double y) { +void glxBitmapCharQuad(char c, double x, double y) +{ int w = SS.TW.CHAR_WIDTH, h = SS.TW.CHAR_HEIGHT; @@ -491,7 +518,8 @@ void glxBitmapCharQuad(char c, double x, double y) { } } -void glxBitmapText(char *str, Vector p) { +void glxBitmapText(char *str, Vector p) +{ glEnable(GL_TEXTURE_2D); glBegin(GL_QUADS); while(*str) { @@ -504,3 +532,45 @@ void glxBitmapText(char *str, Vector p) { glDisable(GL_TEXTURE_2D); } +void glxDrawPixelsWithTexture(BYTE *data, int w, int h) +{ +#define MAX_DIM 32 + static BYTE Texture[MAX_DIM*MAX_DIM*3]; + int i, j; + if(w > MAX_DIM || h > MAX_DIM) oops(); + + for(i = 0; i < w; i++) { + for(j = 0; j < h; j++) { + Texture[(j*MAX_DIM + i)*3 + 0] = data[(j*w + i)*3 + 0]; + Texture[(j*MAX_DIM + i)*3 + 1] = data[(j*w + i)*3 + 1]; + Texture[(j*MAX_DIM + i)*3 + 2] = data[(j*w + i)*3 + 2]; + } + } + + glBindTexture(GL_TEXTURE_2D, TEXTURE_DRAW_PIXELS); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, MAX_DIM, MAX_DIM, 0, + GL_RGB, GL_UNSIGNED_BYTE, Texture); + + glEnable(GL_TEXTURE_2D); + glBegin(GL_QUADS); + glTexCoord2d(0, 0); + glVertex2d(0, h); + + glTexCoord2d(((double)w)/MAX_DIM, 0); + glVertex2d(w, h); + + glTexCoord2d(((double)w)/MAX_DIM, ((double)h)/MAX_DIM); + glVertex2d(w, 0); + + glTexCoord2d(0, ((double)h)/MAX_DIM); + glVertex2d(0, 0); + glEnd(); + glDisable(GL_TEXTURE_2D); +} + diff --git a/graphicswin.cpp b/graphicswin.cpp index 9b2d263..9bc33fb 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -52,13 +52,14 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, "Zoom &Out\t-", MNU_ZOOM_OUT, '-', mView }, { 1, "Zoom To &Fit\tF", MNU_ZOOM_TO_FIT, 'F', mView }, { 1, NULL, 0, NULL }, -{ 1, "Show Snap &Grid\t>", MNU_SHOW_GRID, '.'|S, mView }, -{ 1, "Force &Parallel Projection\t`", MNU_PARALLEL_PROJ, '`', mView }, -{ 1, NULL, 0, NULL }, +{ 1, "Align View to &Workplane\tW", MNU_ONTO_WORKPLANE, 'W', mView }, { 1, "Nearest &Ortho View\tF2", MNU_NEAREST_ORTHO, F(2), mView }, { 1, "Nearest &Isometric View\tF3", MNU_NEAREST_ISO, F(3), mView }, { 1, "&Center View At Point\tF4", MNU_CENTER_VIEW, F(4), mView }, { 1, NULL, 0, NULL }, +{ 1, "Show Snap &Grid\t>", MNU_SHOW_GRID, '.'|S, mView }, +{ 1, "Force &Parallel Projection\t`", MNU_PARALLEL_PROJ, '`', mView }, +{ 1, NULL, 0, NULL }, { 1, "Show Text &Window\tTab", MNU_SHOW_TEXT_WND, '\t', mView }, { 1, "Show &Toolbar", MNU_SHOW_TOOLBAR, 0, mView }, { 1, NULL, 0, NULL }, @@ -81,7 +82,7 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { {11, "Import Recent", MNU_GROUP_RECENT, 0, mGrp }, { 0, "&Sketch", 0, NULL }, -{ 1, "In &Workplane\tW", MNU_SEL_WORKPLANE, 'W', mReq }, +{ 1, "In &Workplane\t2", MNU_SEL_WORKPLANE, '2', mReq }, { 1, "Anywhere In &3d\t3", MNU_FREE_IN_3D, '3', mReq }, { 1, NULL, 0, NULL }, { 1, "Datum &Point\tP", MNU_DATUM_POINT, 'P', mReq }, @@ -373,6 +374,16 @@ void GraphicsWindow::MenuView(int id) { InvalidateGraphics(); break; + case MNU_ONTO_WORKPLANE: + if(!SS.GW.LockedInWorkplane()) { + Error("No workplane is active."); + break; + } + SS.GW.AnimateOntoWorkplane(); + SS.GW.ClearSuper(); + SS.later.showTW = true; + break; + case MNU_NEAREST_ORTHO: case MNU_NEAREST_ISO: { static const Vector ortho[3] = { @@ -813,11 +824,15 @@ void GraphicsWindow::MenuRequest(int id) { } else if(g->type == Group::DRAWING_WORKPLANE) { // The group's default workplane g->activeWorkplane = g->h.entity(0); + Message("No workplane selected. Activating default workplane " + "for this group."); } if(!SS.GW.LockedInWorkplane()) { - Error("Select workplane (e.g., the XY plane) " - "before locking on."); + Error("No workplane is selected, and the active group does " + "not have a default workplane. Try selecting a " + "workplane, or activating a sketch-in-new-workplane " + "group."); break; } // Align the view with the selected workplane @@ -892,17 +907,16 @@ void GraphicsWindow::ClearSuper(void) { EnsureValidActives(); } -void GraphicsWindow::ToggleBool(int link, DWORD v) { - bool *vb = (bool *)v; - *vb = !*vb; +void GraphicsWindow::ToggleBool(bool *v) { + *v = !*v; // The faces are shown as special stippling on the shaded triangle mesh, // so not meaningful to show them and hide the shaded. - if(!SS.GW.showShaded) SS.GW.showFaces = false; + if(!showShaded) showFaces = false; // We might need to regenerate the mesh and edge list, since the edges // wouldn't have been generated if they were previously hidden. - if(SS.GW.showEdges) (SK.GetGroup(SS.GW.activeGroup))->displayDirty = true; + if(showEdges) (SK.GetGroup(activeGroup))->displayDirty = true; SS.GenerateAll(); InvalidateGraphics(); diff --git a/icons/constraint.png b/icons/constraint.png new file mode 100644 index 0000000..a0e1889 Binary files /dev/null and b/icons/constraint.png differ diff --git a/icons/edges.png b/icons/edges.png new file mode 100644 index 0000000..d89c046 Binary files /dev/null and b/icons/edges.png differ diff --git a/icons/faces.png b/icons/faces.png new file mode 100644 index 0000000..8fd4e36 Binary files /dev/null and b/icons/faces.png differ diff --git a/icons/hidden-lines.png b/icons/hidden-lines.png new file mode 100644 index 0000000..cf43afb Binary files /dev/null and b/icons/hidden-lines.png differ diff --git a/icons/mesh.png b/icons/mesh.png new file mode 100644 index 0000000..dde867b Binary files /dev/null and b/icons/mesh.png differ diff --git a/icons/normal.png b/icons/normal.png new file mode 100644 index 0000000..c88c250 Binary files /dev/null and b/icons/normal.png differ diff --git a/icons/shaded.png b/icons/shaded.png new file mode 100644 index 0000000..5fb452d Binary files /dev/null and b/icons/shaded.png differ diff --git a/icons/workplane.png b/icons/workplane.png new file mode 100644 index 0000000..c451453 Binary files /dev/null and b/icons/workplane.png differ diff --git a/png2c.pl b/png2c.pl index fa3a3a1..3552e45 100644 --- a/png2c.pl +++ b/png2c.pl @@ -2,6 +2,10 @@ use GD; +my ($out, $proto) = @ARGV; +open(OUT, ">$out") or die "$out: $!"; +open(PROTO, ">$proto") or die "$proto: $!"; + for $file () { $file =~ m#.*/(.*)\.png#; @@ -17,7 +21,8 @@ for $file () { ($width, $height) = $img->getBounds(); die "$file: $width, $height" if ($width != 24) or ($height != 24); - print "unsigned char $base\[24*24*3] = {\n"; + print PROTO "extern unsigned char $base\[24*24*3\];"; + print OUT "unsigned char $base\[24*24*3] = {\n"; for($y = 0; $y < 24; $y++) { for($x = 0; $x < 24; $x++) { @@ -26,10 +31,10 @@ for $file () { if($r + $g + $b < 11) { ($r, $g, $b) = (30, 30, 30); } - printf " 0x%02x, 0x%02x, 0x%02x,\n", $r, $g, $b; + printf OUT " 0x%02x, 0x%02x, 0x%02x,\n", $r, $g, $b; } } - print "};\n\n"; + print OUT "};\n\n"; } diff --git a/solvespace.cpp b/solvespace.cpp index db90236..5cf13f6 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -63,7 +63,7 @@ void SolveSpace::Init(char *cmdLine) { // View units viewUnits = (Unit)CnfThawDWORD((DWORD)UNIT_MM, "ViewUnits"); // Camera tangent (determines perspective) - cameraTangent = CnfThawFloat(0.0f, "CameraTangent"); + cameraTangent = CnfThawFloat(0.3f, "CameraTangent"); // Grid spacing gridSpacing = CnfThawFloat(5.0f, "GridSpacing"); // Export scale factor @@ -114,6 +114,10 @@ void SolveSpace::Init(char *cmdLine) { // configuration file, but we will automatically load those as we need // them. + // The factory default settings include a non-zero perspective factor, + // but we'll default to that off. + forceParallelProj = true; + // Start with either an empty file, or the file specified on the // command line. NewFile(); diff --git a/solvespace.h b/solvespace.h index d7e7157..e4c5316 100644 --- a/solvespace.h +++ b/solvespace.h @@ -144,6 +144,7 @@ void InvalidateText(void); void InvalidateGraphics(void); void PaintGraphics(void); void GetGraphicsWindowSize(int *w, int *h); +void GetTextWindowSize(int *w, int *h); SDWORD GetMilliseconds(void); SQWORD GetUnixTime(void); @@ -197,6 +198,8 @@ typedef IdList ParamList; // Utility functions that are provided in the platform-independent code. void glxVertex3v(Vector u); +void glxAxisAlignedQuad(double l, double r, double t, double b); +void glxAxisAlignedLineLoop(double l, double r, double t, double b); #define DEFAULT_TEXT_HEIGHT (11.5) #define GLX_CALLBACK __stdcall typedef void GLX_CALLBACK glxCallbackFptr(void); @@ -221,11 +224,13 @@ void glxColorRGB(DWORD rgb); void glxColorRGBa(DWORD rgb, double a); void glxDepthRangeOffset(int units); void glxDepthRangeLockToFront(bool yes); +void glxDrawPixelsWithTexture(BYTE *data, int w, int h); void glxCreateBitmapFont(void); void glxBitmapText(char *str, Vector p); void glxBitmapCharQuad(char c, double x, double y); #define TEXTURE_BACKGROUND_IMG 10 #define TEXTURE_BITMAP_FONT 20 +#define TEXTURE_DRAW_PIXELS 30 #define arraylen(x) (sizeof((x))/sizeof((x)[0])) diff --git a/textscreens.cpp b/textscreens.cpp index 8ec5c96..6af619c 100644 --- a/textscreens.cpp +++ b/textscreens.cpp @@ -10,42 +10,26 @@ void TextWindow::ScreenHome(int link, DWORD v) { void TextWindow::ShowHeader(bool withNav) { ClearScreen(); - char *cd = SS.GW.LockedInWorkplane() ? - SK.GetEntity(SS.GW.ActiveWorkplane())->DescriptionString() : - "free in 3d"; + char cd[1024], cd2[1024]; + if(SS.GW.LockedInWorkplane()) { + sprintf(cd, "in plane: "); + strcpy(cd2, SK.GetEntity(SS.GW.ActiveWorkplane())->DescriptionString()); + } else { + sprintf(cd, "drawing / constraining in 3d"); + strcpy(cd2, ""); + } // Navigation buttons if(withNav) { - Printf(false, " %Fl%Lh%fhome%E %Bt%Ft wrkpl:%Fd %s", - (&TextWindow::ScreenHome), - cd); + Printf(false, " %Fl%Lh%fhome%E %Ft%s%E%s", + (&TextWindow::ScreenHome), cd, cd2); } else { - Printf(false, " %Bt%Ft wrkpl:%Fd %s", cd); + Printf(false, " %Ft%s%E%s", cd, cd2); } -#define hs(b) ((b) ? 's' : 'h') - Printf(false, "%Bt%Ftshow: " - "%Fp%Ll%D%fwrkpls%E " - "%Fp%Ll%D%fnormals%E " - "%Fp%Ll%D%fpoints%E " - "%Fp%Ll%D%fconstraints%E ", - hs(SS.GW.showWorkplanes), (DWORD)&(SS.GW.showWorkplanes), &(SS.GW.ToggleBool), - hs(SS.GW.showNormals), (DWORD)&(SS.GW.showNormals), &(SS.GW.ToggleBool), - hs(SS.GW.showPoints), (DWORD)&(SS.GW.showPoints), &(SS.GW.ToggleBool), -hs(SS.GW.showConstraints), (DWORD)(&SS.GW.showConstraints), &(SS.GW.ToggleBool) - ); - Printf(false, "%Bt%Ft " - "%Fp%Ll%D%fshaded%E " - "%Fp%Ll%D%fedges%E " - "%Fp%Ll%D%fmesh%E " - "%Fp%Ll%D%ffaces%E " - "%Fp%Ll%D%fhidden-lns%E", -hs(SS.GW.showShaded), (DWORD)(&SS.GW.showShaded), &(SS.GW.ToggleBool), -hs(SS.GW.showEdges), (DWORD)(&SS.GW.showEdges), &(SS.GW.ToggleBool), -hs(SS.GW.showMesh), (DWORD)(&SS.GW.showMesh), &(SS.GW.ToggleBool), -hs(SS.GW.showFaces), (DWORD)(&SS.GW.showFaces), &(SS.GW.ToggleBool), -hs(SS.GW.showHdnLines), (DWORD)(&SS.GW.showHdnLines), &(SS.GW.ToggleBool) - ); + // Leave space for the icons that are painted here. + Printf(false, ""); + Printf(false, ""); } //----------------------------------------------------------------------------- diff --git a/textwin.cpp b/textwin.cpp index ed9c591..ae34c98 100644 --- a/textwin.cpp +++ b/textwin.cpp @@ -1,4 +1,5 @@ #include "solvespace.h" +#include "obj/icons-proto.h" #include const TextWindow::Color TextWindow::fgColors[] = { @@ -22,6 +23,22 @@ const TextWindow::Color TextWindow::bgColors[] = { { 0, 0 }, }; +bool TextWindow::SPACER = false; +TextWindow::HideShowIcon TextWindow::hideShowIcons[] = { + { &(SS.GW.showWorkplanes), Icon_workplane, "workplanes from inactive groups"}, + { &(SS.GW.showNormals), Icon_normal, "normals" }, + { &(SS.GW.showPoints), Icon_point, "points" }, + { &(SS.GW.showConstraints), Icon_constraint, "constraints and dimensions" }, + { &(SS.GW.showFaces), Icon_faces, "XXX - special cased" }, + { &SPACER, 0 }, + { &(SS.GW.showShaded), Icon_shaded, "shaded view of solid model" }, + { &(SS.GW.showEdges), Icon_edges, "edges of solid model" }, + { &(SS.GW.showMesh), Icon_mesh, "triangle mesh of solid model" }, + { &SPACER, 0 }, + { &(SS.GW.showHdnLines), Icon_hidden_lines, "hidden lines" }, + { 0, 0 }, +}; + void TextWindow::MakeColorTable(const Color *in, float *out) { int i; for(i = 0; in[i].c != 0; i++) { @@ -259,7 +276,140 @@ void TextWindow::Show(void) { InvalidateText(); } -void TextWindow::Paint(int width, int height) { +void TextWindow::TimerCallback(void) +{ + tooltippedIcon = hoveredIcon; + InvalidateText(); +} + +void TextWindow::DrawOrHitTestIcons(int how, double mx, double my) +{ + int width, height; + GetTextWindowSize(&width, &height); + + int x = 20, y = 33 + LINE_HEIGHT; + y -= scrollPos*(LINE_HEIGHT/2); + + double grey = 30.0/255; + double top = y - 28, bot = y + 4; + glColor4d(grey, grey, grey, 1.0); + glxAxisAlignedQuad(0, width, top, bot); + + HideShowIcon *oldHovered = hoveredIcon; + if(how != PAINT) { + hoveredIcon = NULL; + } + + HideShowIcon *hsi; + for(hsi = &(hideShowIcons[0]); hsi->var; hsi++) { + if(hsi->var == &SPACER) { + // Draw a darker-grey spacer in between the groups of icons. + if(how == PAINT) { + int l = x, r = l + 4, + t = y, b = t - 24; + glColor4d(0.17, 0.17, 0.17, 1); + glxAxisAlignedQuad(l, r, t, b); + } + x += 12; + continue; + } + + if(how == PAINT) { + glPushMatrix(); + glTranslated(x, y-24, 0); + // Only thing that matters about the color is the alpha, + // should be one for no transparency + glColor3d(0, 0, 0); + glxDrawPixelsWithTexture(hsi->icon, 24, 24); + glPopMatrix(); + + if(hsi == hoveredIcon) { + glColor4d(1, 1, 0, 0.3); + glxAxisAlignedQuad(x - 2, x + 26, y + 2, y - 26); + } + if(!*(hsi->var)) { + glColor4d(1, 0, 0, 0.6); + glLineWidth(2); + int s = 0, f = 24; + glBegin(GL_LINES); + glVertex2d(x+s, y-s); + glVertex2d(x+f, y-f); + glVertex2d(x+s, y-f); + glVertex2d(x+f, y-s); + glEnd(); + } + } else { + if(mx > x - 2 && mx < x + 26 && + my < y + 2 && my > y - 26) + { + // The mouse is hovered over this icon, so do the tooltip + // stuff. + if(hsi != tooltippedIcon) { + oldMousePos = Point2d::From(mx, my); + } + if(hsi != oldHovered || how == CLICK) { + SetTimerFor(1000); + } + hoveredIcon = hsi; + if(how == CLICK) { + SS.GW.ToggleBool(hsi->var); + } + } + } + + x += 32; + } + + if(how != PAINT && hoveredIcon != oldHovered) { + InvalidateText(); + } + + if(tooltippedIcon) { + if(how == PAINT) { + char str[1024]; + + if(tooltippedIcon->icon == Icon_faces) { + if(SS.GW.showFaces) { + strcpy(str, "Don't select faces with mouse"); + } else { + strcpy(str, "Select faces with mouse"); + } + } else { + sprintf(str, "%s %s", *(tooltippedIcon->var) ? "Hide" : "Show", + tooltippedIcon->tip); + } + + double ox = oldMousePos.x, oy = oldMousePos.y - LINE_HEIGHT; + int tw = (strlen(str) + 1)*CHAR_WIDTH; + ox = min(ox, (width - 25) - tw); + oy = max(oy, 5); + + glxCreateBitmapFont(); + glLineWidth(1); + glColor4d(1.0, 1.0, 0.6, 1.0); + glxAxisAlignedQuad(ox, ox+tw, oy, oy+LINE_HEIGHT); + glColor4d(0.0, 0.0, 0.0, 1.0); + glxAxisAlignedLineLoop(ox, ox+tw, oy, oy+LINE_HEIGHT); + + glColor4d(0, 0, 0, 1); + glxBitmapText(str, Vector::From(ox+5, oy-3+LINE_HEIGHT, 0)); + } else { + if(!hoveredIcon || + (hoveredIcon != tooltippedIcon)) + { + tooltippedIcon = NULL; + InvalidateGraphics(); + } + // And if we're hovered, then we've set a timer that will cause + // us to show the tool tip later. + } + } +} + +void TextWindow::Paint(void) { + int width, height; + GetTextWindowSize(&width, &height); + // We would like things pixel-exact, to avoid shimmering. glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); @@ -276,12 +426,12 @@ void TextWindow::Paint(int width, int height) { halfRows = height / (LINE_HEIGHT/2); - int bottom = SS.TW.top[SS.TW.rows-1] + 2; + int bottom = top[rows-1] + 2; scrollPos = min(scrollPos, bottom - halfRows); scrollPos = max(scrollPos, 0); // Let's set up the scroll bar first - MoveTextScrollbarTo(scrollPos, SS.TW.top[SS.TW.rows - 1] + 1, halfRows); + MoveTextScrollbarTo(scrollPos, top[rows - 1] + 1, halfRows); // Create the bitmap font that we're going to use. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -289,33 +439,31 @@ void TextWindow::Paint(int width, int height) { // Now paint the window. int r, c, a; - for(a = 0; a < 3; a++) { + for(a = 0; a < 2; a++) { if(a == 0) { glBegin(GL_QUADS); } else if(a == 1) { glEnable(GL_TEXTURE_2D); glxCreateBitmapFont(); glBegin(GL_QUADS); - } else { - glBegin(GL_LINES); } - for(r = 0; r < SS.TW.rows; r++) { - int top = SS.TW.top[r]; - if(top < (scrollPos-1)) continue; - if(top > scrollPos+halfRows) break; + for(r = 0; r < rows; r++) { + int ltop = top[r]; + if(ltop < (scrollPos-1)) continue; + if(ltop > scrollPos+halfRows) break; - for(c = 0; c < min((width/CHAR_WIDTH)+1, SS.TW.MAX_COLS); c++) { + for(c = 0; c < min((width/CHAR_WIDTH)+1, MAX_COLS); c++) { int x = LEFT_MARGIN + c*CHAR_WIDTH; - int y = (top-scrollPos)*(LINE_HEIGHT/2) + 2; + int y = (ltop-scrollPos)*(LINE_HEIGHT/2) + 4; - int fg = SS.TW.meta[r][c].fg; - int bg = SS.TW.meta[r][c].bg; + int fg = meta[r][c].fg; + int bg = meta[r][c].bg; // On the first pass, all the background quads; on the next // pass, all the foreground (i.e., font) quads. if(a == 0) { - int bh = LINE_HEIGHT, adj = 0; + int bh = LINE_HEIGHT, adj = -2; if(bg & 0x80000000) { glColor3f(REDf(bg), GREENf(bg), BLUEf(bg)); bh = CHAR_HEIGHT; @@ -328,23 +476,45 @@ void TextWindow::Paint(int width, int height) { // Move the quad down a bit, so that the descenders // still have the correct background. y += adj; - glBegin(GL_QUADS); - glVertex2d(x, y); - glVertex2d(x + CHAR_WIDTH, y); - glVertex2d(x + CHAR_WIDTH, y + bh); - glVertex2d(x, y + bh); - glEnd(); + glxAxisAlignedQuad(x, x + CHAR_WIDTH, y, y + bh); y -= adj; } } else if(a == 1) { glColor3fv(&(fgColorTable[fg*3])); - glxBitmapCharQuad(SS.TW.text[r][c], x, y + CHAR_HEIGHT); - } else { - if(SS.TW.meta[r][c].link && SS.TW.meta[r][c].link != 'n') { - glColor3fv(&(fgColorTable[fg*3])); - y += CHAR_HEIGHT + 1; - glVertex2d(x, y); - glVertex2d(x + CHAR_WIDTH, y); + glxBitmapCharQuad(text[r][c], x, y + CHAR_HEIGHT); + + // If this is a link and it's hovered, then draw the + // underline. + if(meta[r][c].link && meta[r][c].link != 'n' && + (r == hoveredRow && c == hoveredCol)) + { + int cs = c, cf = c; + while(cs >= 0 && meta[r][cs].link && + meta[r][cs].f == meta[r][c].f && + meta[r][cs].data == meta[r][c].data) + { + cs--; + } + cs++; + + while( meta[r][cf].link && + meta[r][cf].f == meta[r][c].f && + meta[r][cf].data == meta[r][c].data) + { + cf++; + } + glEnd(); + + glDisable(GL_TEXTURE_2D); + glLineWidth(1); + glBegin(GL_LINES); + int yp = y + CHAR_HEIGHT; + glVertex2d(LEFT_MARGIN + cs*CHAR_WIDTH, yp); + glVertex2d(LEFT_MARGIN + cf*CHAR_WIDTH, yp); + glEnd(); + + glEnable(GL_TEXTURE_2D); + glBegin(GL_QUADS); } } } @@ -353,6 +523,9 @@ void TextWindow::Paint(int width, int height) { glEnd(); glDisable(GL_TEXTURE_2D); } + + // The header has some icons that are drawn separately from the text + DrawOrHitTestIcons(PAINT, 0, 0); } void TextWindow::MouseEvent(bool leftClick, double x, double y) { @@ -366,29 +539,39 @@ void TextWindow::MouseEvent(bool leftClick, double x, double y) { return; } + DrawOrHitTestIcons(leftClick ? CLICK : HOVER, x, y); + GraphicsWindow::Selection ps = SS.GW.hover; SS.GW.hover.Clear(); + int prevHoveredRow = hoveredRow, + prevHoveredCol = hoveredCol; + hoveredRow = 0; + hoveredCol = 0; + // Find the corresponding character in the text buffer int c = (int)((x - LEFT_MARGIN) / CHAR_WIDTH); int hh = (LINE_HEIGHT)/2; y += scrollPos*hh; int r; - for(r = 0; r < SS.TW.rows; r++) { - if(y >= SS.TW.top[r]*hh && y <= (SS.TW.top[r]+2)*hh) { + for(r = 0; r < rows; r++) { + if(y >= top[r]*hh && y <= (top[r]+2)*hh) { break; } } - if(r >= SS.TW.rows) { + if(r >= rows) { SetMousePointerToHand(false); goto done; } -#define META (SS.TW.meta[r][c]) + hoveredRow = r; + hoveredCol = c; + +#define META (meta[r][c]) if(leftClick) { if(META.link && META.f) { (META.f)(META.link, META.data); - SS.TW.Show(); + Show(); InvalidateGraphics(); } } else { @@ -403,19 +586,30 @@ void TextWindow::MouseEvent(bool leftClick, double x, double y) { } done: - if(!ps.Equals(&(SS.GW.hover))) { + if((!ps.Equals(&(SS.GW.hover))) || + prevHoveredRow != hoveredRow || + prevHoveredCol != hoveredCol) + { InvalidateGraphics(); + InvalidateText(); } } +void TextWindow::MouseLeave(void) { + tooltippedIcon = NULL; + hoveredRow = 0; + hoveredCol = 0; + InvalidateText(); +} + void TextWindow::ScrollbarEvent(int newPos) { - int bottom = SS.TW.top[SS.TW.rows-1] + 2; + int bottom = top[rows-1] + 2; newPos = min(newPos, bottom - halfRows); newPos = max(newPos, 0); if(newPos != scrollPos) { scrollPos = newPos; - MoveTextScrollbarTo(scrollPos, SS.TW.top[SS.TW.rows - 1] + 1, halfRows); + MoveTextScrollbarTo(scrollPos, top[rows - 1] + 1, halfRows); if(TextEditControlIsVisible()) { extern int TextEditControlCol, TextEditControlHalfRow; diff --git a/toolbar.cpp b/toolbar.cpp index b5e826a..9eee4d6 100644 --- a/toolbar.cpp +++ b/toolbar.cpp @@ -39,8 +39,8 @@ static const struct { { Icon_assemble, GraphicsWindow::MNU_GROUP_IMPORT, "New group importing / assembling file" }, { SPACER }, - { Icon_in3d, GraphicsWindow::MNU_FREE_IN_3D, "Sketch / constrain in 3d" }, - { Icon_ontoworkplane, GraphicsWindow::MNU_SEL_WORKPLANE, "Sketch / constrain in workplane" }, + { Icon_in3d, GraphicsWindow::MNU_NEAREST_ISO, "Nearest isometric view" }, + { Icon_ontoworkplane, GraphicsWindow::MNU_ONTO_WORKPLANE, "Align view to active workplane" }, { NULL }, }; @@ -122,12 +122,7 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, double c = 30.0/255; glColor4d(c, c, c, 1.0); - glBegin(GL_QUADS); - glVertex2d(aleft, atop); - glVertex2d(aleft, abot); - glVertex2d(aright, abot); - glVertex2d(aright, atop); - glEnd(); + glxAxisAlignedQuad(aleft, aright, atop, abot); } struct { @@ -151,12 +146,7 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, glColor4d(0.17, 0.17, 0.17, 1); x += 16; y += 24; - glBegin(GL_QUADS); - glVertex2d(x+divw, y+divh); - glVertex2d(x+divw, y-divh); - glVertex2d(x-divw, y-divh); - glVertex2d(x-divw, y+divh); - glEnd(); + glxAxisAlignedQuad(x+divw, x-divw, y+divh, y-divh); x -= 16; y -= 24; } @@ -172,12 +162,7 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, // Highlight the hovered or pending item. glColor4d(1, 1, 0, 0.3); int boxhw = 15; - glBegin(GL_QUADS); - glVertex2d(x+boxhw, y+boxhw); - glVertex2d(x+boxhw, y-boxhw); - glVertex2d(x-boxhw, y-boxhw); - glVertex2d(x-boxhw, y+boxhw); - glEnd(); + glxAxisAlignedQuad(x+boxhw, x-boxhw, y+boxhw, y-boxhw); } if(toolbarTooltipped == Toolbar[i].menu) { @@ -218,13 +203,16 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, if(toolbarTooltipped == SS.GW.menu[i].id) { int accel = SS.GW.menu[i].accel; int ac = accel & 0xff; + + char *s = str+strlen(str); if(isalnum(ac) || ac == '[') { - char *s = str+strlen(str); if(accel & 0x100) { sprintf(s, " (Shift+%c)", ac); } else if((accel & ~0xff) == 0) { sprintf(s, " (%c)", ac); } + } else if(ac == 0xf3) { + sprintf(s, " (F3)"); } break; } @@ -236,19 +224,9 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, double ox = toolbarMouseX + 3, oy = toolbarMouseY + 3; glLineWidth(1); glColor4d(1.0, 1.0, 0.6, 1.0); - glBegin(GL_QUADS); - glVertex2d(ox, oy); - glVertex2d(ox+tw, oy); - glVertex2d(ox+tw, oy+th); - glVertex2d(ox, oy+th); - glEnd(); + glxAxisAlignedQuad(ox, ox+tw, oy, oy+th); glColor4d(0.0, 0.0, 0.0, 1.0); - glBegin(GL_LINE_LOOP); - glVertex2d(ox, oy); - glVertex2d(ox+tw, oy); - glVertex2d(ox+tw, oy+th); - glVertex2d(ox, oy+th); - glEnd(); + glxAxisAlignedLineLoop(ox, ox+tw, oy, oy+th); glColor4d(0, 0, 0, 1); glPushMatrix(); diff --git a/ui.h b/ui.h index efd4d58..6b82819 100644 --- a/ui.h +++ b/ui.h @@ -31,7 +31,7 @@ public: static const int CHAR_WIDTH = 9; static const int CHAR_HEIGHT = 16; static const int LINE_HEIGHT = 20; - static const int LEFT_MARGIN = 4; + static const int LEFT_MARGIN = 6; int scrollPos; // The scrollbar position, in half-row units int halfRows; // The height of our window, in half-row units @@ -47,15 +47,35 @@ public: LinkFunction *f; LinkFunction *h; } meta[MAX_ROWS][MAX_COLS]; - int top[MAX_ROWS]; // in half-line units, or -1 for unused + int hoveredRow, hoveredCol; + + int top[MAX_ROWS]; // in half-line units, or -1 for unused int rows; + // The row of icons at the top of the text window, to hide/show things + typedef struct { + bool *var; + BYTE *icon; + char *tip; + } HideShowIcon; + static HideShowIcon hideShowIcons[]; + static bool SPACER; + // These are called by the platform-specific code. - void Paint(int w, int h); + void Paint(void); void MouseEvent(bool leftDown, double x, double y); void MouseScroll(double x, double y, int delta); + void MouseLeave(void); void ScrollbarEvent(int newPos); + + static const int PAINT = 0; + static const int HOVER = 1; + static const int CLICK = 2; + void DrawOrHitTestIcons(int how, double mx, double my); + void TimerCallback(void); + Point2d oldMousePos; + HideShowIcon *hoveredIcon, *tooltippedIcon; void Init(void); void MakeColorTable(const Color *in, float *out); @@ -282,6 +302,7 @@ public: MNU_ZOOM_TO_FIT, MNU_SHOW_GRID, MNU_PARALLEL_PROJ, + MNU_ONTO_WORKPLANE, MNU_NEAREST_ORTHO, MNU_NEAREST_ISO, MNU_CENTER_VIEW, @@ -567,7 +588,7 @@ public: bool showFaces; bool showMesh; bool showHdnLines; - static void ToggleBool(int link, DWORD v); + void ToggleBool(bool *v); bool showSnapGrid; @@ -578,7 +599,7 @@ public: void UpdateDraggedPoint(hEntity hp, double mx, double my); // These are called by the platform-specific code. - void Paint(int w, int h); + void Paint(void); void MouseMoved(double x, double y, bool leftDown, bool middleDown, bool rightDown, bool shiftDown, bool ctrlDown); void MouseLeftDown(double x, double y); diff --git a/win32/w32main.cpp b/win32/w32main.cpp index 4a7fc5b..0c6dc8a 100644 --- a/win32/w32main.cpp +++ b/win32/w32main.cpp @@ -229,6 +229,7 @@ void CALLBACK TimerCallback(HWND hwnd, UINT msg, UINT_PTR id, DWORD time) // The timer is periodic, so needs to be killed explicitly. KillTimer(GraphicsWnd, 1); SS.GW.TimerCallback(); + SS.TW.TimerCallback(); } void SetTimerFor(int milliseconds) { @@ -246,6 +247,10 @@ void GetGraphicsWindowSize(int *w, int *h) { GetWindowSize(GraphicsWnd, w, h); } +void GetTextWindowSize(int *w, int *h) +{ + GetWindowSize(TextWnd, w, h); +} void OpenWebsite(char *url) { ShellExecute(GraphicsWnd, "open", url, NULL, NULL, SW_SHOWNORMAL); @@ -291,9 +296,7 @@ static void PaintTextWnd(HDC hdc) { wglMakeCurrent(GetDC(TextWnd), TextGl); - int w, h; - GetWindowSize(TextWnd, &w, &h); - SS.TW.Paint(w, h); + SS.TW.Paint(); SwapBuffers(GetDC(TextWnd)); // Leave the graphics window context active, except when we're painting @@ -439,8 +442,21 @@ LRESULT CALLBACK TextWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) break; } + case WM_MOUSELEAVE: + SS.TW.MouseLeave(); + break; + case WM_LBUTTONDOWN: case WM_MOUSEMOVE: { + // We need this in order to get the WM_MOUSELEAVE + TRACKMOUSEEVENT tme; + ZERO(&tme); + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = TextWnd; + TrackMouseEvent(&tme); + + // And process the actual message int x = LOWORD(lParam); int y = HIWORD(lParam); SS.TW.MouseEvent(msg == WM_LBUTTONDOWN, x, y); @@ -595,9 +611,7 @@ static void CreateGlContext(HWND hwnd, HGLRC *glrc) void PaintGraphics(void) { - int w, h; - GetWindowSize(GraphicsWnd, &w, &h); - SS.GW.Paint(w, h); + SS.GW.Paint(); SwapBuffers(GetDC(GraphicsWnd)); } void InvalidateGraphics(void) @@ -1021,7 +1035,7 @@ static void CreateMainWindows(void) // We get the desired Alt+Tab behaviour by specifying that the text // window is a child of the graphics window. TextWnd = CreateWindowEx(0, - "TextWnd", "SolveSpace (Text Window)", WS_THICKFRAME | WS_CLIPCHILDREN, + "TextWnd", "SolveSpace - Browser", WS_THICKFRAME | WS_CLIPCHILDREN, 650, 500, 420, 300, GraphicsWnd, (HMENU)NULL, Instance, NULL); if(!TextWnd) oops(); diff --git a/wishlist.txt b/wishlist.txt index 5bc2f33..d7bf20e 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,9 +1,8 @@ -replace show/hide links with icons add checked/unchecked checkbox and radio button fix bug with rotation in plane where green line stays displayed lock point where dragged constraint -remove toolbar icons for sketch in 3d, add View -> Align to Workplane expose transformed point stuff in library, and email McNeel +assign default name for import groups based on filename ----- rounding, as a special group