diff --git a/confscreen.cpp b/confscreen.cpp index 3d5a65da..386ea80b 100644 --- a/confscreen.cpp +++ b/confscreen.cpp @@ -21,12 +21,8 @@ void TextWindow::ScreenChangeLightIntensity(int link, DWORD v) { } void TextWindow::ScreenChangeColor(int link, DWORD v) { - char str[1024]; - sprintf(str, "%.2f, %.2f, %.2f", - REDf(SS.modelColor[v]), - GREENf(SS.modelColor[v]), - BLUEf(SS.modelColor[v])); - SS.TW.ShowEditControl(9+2*v, 13, str); + SS.TW.ShowEditControlWithColorPicker(9+2*v, 13, SS.modelColor[v]); + SS.TW.edit.meaning = EDIT_COLOR; SS.TW.edit.i = v; } diff --git a/dsc.h b/dsc.h index 2298d0a6..81833776 100644 --- a/dsc.h +++ b/dsc.h @@ -90,6 +90,7 @@ public: double DivPivoting(Vector delta); Vector ClosestOrtho(void); void MakeMaxMin(Vector *maxv, Vector *minv); + Vector ClampWithin(double minv, double maxv); static bool BoundingBoxesDisjoint(Vector amax, Vector amin, Vector bmax, Vector bmin); static bool BoundingBoxIntersectsLine(Vector amax, Vector amin, diff --git a/solvespace.h b/solvespace.h index 60d89ee3..a9931181 100644 --- a/solvespace.h +++ b/solvespace.h @@ -231,6 +231,8 @@ void glxBitmapCharQuad(char c, double x, double y); #define TEXTURE_BACKGROUND_IMG 10 #define TEXTURE_BITMAP_FONT 20 #define TEXTURE_DRAW_PIXELS 30 +#define TEXTURE_COLOR_PICKER_2D 40 +#define TEXTURE_COLOR_PICKER_1D 50 #define arraylen(x) (sizeof((x))/sizeof((x)[0])) diff --git a/style.cpp b/style.cpp index 17392536..18e4fd80 100644 --- a/style.cpp +++ b/style.cpp @@ -324,9 +324,7 @@ void TextWindow::ScreenCreateCustomStyle(int link, DWORD v) { void TextWindow::ScreenChangeBackgroundColor(int link, DWORD v) { DWORD rgb = SS.backgroundColor; - char str[300]; - sprintf(str, "%.2f, %.2f, %.2f", REDf(rgb), GREENf(rgb), BLUEf(rgb)); - SS.TW.ShowEditControl(v, 3, str); + SS.TW.ShowEditControlWithColorPicker(v, 3, rgb); SS.TW.edit.meaning = EDIT_BACKGROUND_COLOR; } @@ -536,9 +534,7 @@ void TextWindow::ScreenChangeStyleColor(int link, DWORD v) { } else { oops(); } - char str[300]; - sprintf(str, "%.2f, %.2f, %.2f", REDf(rgb), GREENf(rgb), BLUEf(rgb)); - SS.TW.ShowEditControl(row, col, str); + SS.TW.ShowEditControlWithColorPicker(row, col, rgb); SS.TW.edit.style = hs; SS.TW.edit.meaning = em; } diff --git a/textwin.cpp b/textwin.cpp index 3b5f4a10..36b63a7d 100644 --- a/textwin.cpp +++ b/textwin.cpp @@ -67,6 +67,7 @@ void TextWindow::ClearSuper(void) { } void TextWindow::HideEditControl(void) { + editControl.colorPicker.show = false; HideTextEditControl(); } @@ -80,6 +81,21 @@ void TextWindow::ShowEditControl(int halfRow, int col, char *s) { ShowTextEditControl(x - 3, y + 2, s); } +void TextWindow::ShowEditControlWithColorPicker(int halfRow, int col, DWORD rgb) +{ + char str[1024]; + sprintf(str, "%.2f, %.2f, %.2f", REDf(rgb), GREENf(rgb), BLUEf(rgb)); + + SS.later.showTW = true; + + editControl.colorPicker.show = true; + editControl.colorPicker.rgb = rgb; + editControl.colorPicker.h = 0; + editControl.colorPicker.s = 0; + editControl.colorPicker.v = 1; + ShowEditControl(halfRow, col, str); +} + void TextWindow::ClearScreen(void) { int i, j; for(i = 0; i < MAX_ROWS; i++) { @@ -295,6 +311,17 @@ void TextWindow::Show(void) { } } Printf(false, ""); + + // Make sure there's room for the color picker + if(editControl.colorPicker.show) { + int pickerHeight = 25; + int halfRow = editControl.halfRow; + if(top[rows-1] - halfRow < pickerHeight && rows < MAX_ROWS) { + rows++; + top[rows-1] = halfRow + pickerHeight; + } + } + InvalidateText(); } @@ -430,6 +457,330 @@ void TextWindow::DrawOrHitTestIcons(int how, double mx, double my) } } +//---------------------------------------------------------------------------- +// Given (x, y, z) = (h, s, v) in [0,6), [0,1], [0,1], return (x, y, z) = +// (r, g, b) all in [0, 1]. +//---------------------------------------------------------------------------- +Vector TextWindow::HsvToRgb(Vector hsv) { + if(hsv.x >= 6) hsv.x -= 6; + + Vector rgb; + double hmod2 = hsv.x; + while(hmod2 >= 2) hmod2 -= 2; + double x = (1 - fabs(hmod2 - 1)); + if(hsv.x < 1) { + rgb = Vector::From(1, x, 0); + } else if(hsv.x < 2) { + rgb = Vector::From(x, 1, 0); + } else if(hsv.x < 3) { + rgb = Vector::From(0, 1, x); + } else if(hsv.x < 4) { + rgb = Vector::From(0, x, 1); + } else if(hsv.x < 5) { + rgb = Vector::From(x, 0, 1); + } else { + rgb = Vector::From(1, 0, x); + } + double c = hsv.y*hsv.z; + double m = 1 - hsv.z; + rgb = rgb.ScaledBy(c); + rgb = rgb.Plus(Vector::From(m, m, m)); + + return rgb; +} + +BYTE *TextWindow::HsvPattern2d(void) { + static BYTE Texture[256*256*3]; + static bool Init; + + if(!Init) { + int i, j, p; + p = 0; + for(i = 0; i < 256; i++) { + for(j = 0; j < 256; j++) { + Vector hsv = Vector::From(6.0*i/255.0, 1.0*j/255.0, 1); + Vector rgb = HsvToRgb(hsv); + rgb = rgb.ScaledBy(255); + Texture[p++] = (BYTE)rgb.x; + Texture[p++] = (BYTE)rgb.y; + Texture[p++] = (BYTE)rgb.z; + } + } + Init = true; + } + return Texture; +} + +BYTE *TextWindow::HsvPattern1d(double h, double s) { + static BYTE Texture[256*4]; + + int i, p; + p = 0; + for(i = 0; i < 256; i++) { + Vector hsv = Vector::From(6*h, s, 1.0*(255 - i)/255.0); + Vector rgb = HsvToRgb(hsv); + rgb = rgb.ScaledBy(255); + Texture[p++] = (BYTE)rgb.x; + Texture[p++] = (BYTE)rgb.y; + Texture[p++] = (BYTE)rgb.z; + // Needs a padding byte, to make things four-aligned + p++; + } + return Texture; +} + +void TextWindow::ColorPickerDone(void) { + char str[1024]; + DWORD rgb = editControl.colorPicker.rgb; + sprintf(str, "%.2f, %.2f, %.3f", REDf(rgb), GREENf(rgb), BLUEf(rgb)); + EditControlDone(str); +} + +bool TextWindow::DrawOrHitTestColorPicker(int how, bool leftDown, + double x, double y) +{ + bool mousePointerAsHand = false; + + if(how == HOVER && !leftDown) { + editControl.colorPicker.picker1dActive = false; + editControl.colorPicker.picker2dActive = false; + } + + if(!editControl.colorPicker.show) return false; + if(how == CLICK || (how == HOVER && leftDown)) InvalidateText(); + + static const DWORD BaseColor[12] = { + RGB(255, 0, 0), + RGB( 0, 255, 0), + RGB( 0, 0, 255), + + RGB( 0, 255, 255), + RGB(255, 0, 255), + RGB(255, 255, 0), + + RGB(255, 127, 0), + RGB(255, 0, 127), + RGB( 0, 255, 127), + RGB(127, 255, 0), + RGB(127, 0, 255), + RGB( 0, 127, 255), + }; + + int width, height; + GetTextWindowSize(&width, &height); + + int px = LEFT_MARGIN + CHAR_WIDTH*editControl.col; + int py = (editControl.halfRow - SS.TW.scrollPos)*(LINE_HEIGHT/2); + + py += LINE_HEIGHT + 5; + + static const int WIDTH = 16, HEIGHT = 12; + static const int PITCH = 18, SIZE = 15; + + px = min(px, width - (WIDTH*PITCH + 40)); + + int pxm = px + WIDTH*PITCH + 11, + pym = py + HEIGHT*PITCH + 7; + + int bw = 6; + if(how == PAINT) { + glColor4d(0.2, 0.2, 0.2, 1); + glxAxisAlignedQuad(px, pxm+bw, py, pym+bw); + glColor4d(0.0, 0.0, 0.0, 1); + glxAxisAlignedQuad(px+(bw/2), pxm+(bw/2), py+(bw/2), pym+(bw/2)); + } else { + if(x < px || x > pxm+(bw/2) || + y < py || y > pym+(bw/2)) + { + return false; + } + } + px += (bw/2); + py += (bw/2); + + int i, j; + for(i = 0; i < WIDTH/2; i++) { + for(j = 0; j < HEIGHT; j++) { + Vector rgb; + DWORD d; + if(i == 0 && j < 8) { + d = SS.modelColor[j]; + rgb = Vector::From(REDf(d), GREENf(d), BLUEf(d)); + } else if(i == 0) { + double a = (j - 8.0)/3.0; + rgb = Vector::From(a, a, a); + } else { + d = BaseColor[j]; + rgb = Vector::From(REDf(d), GREENf(d), BLUEf(d)); + if(i >= 2 && i <= 4) { + double a = (i == 2) ? 0.2 : (i == 3) ? 0.3 : 0.4; + rgb = rgb.Plus(Vector::From(a, a, a)); + } + if(i >= 5 && i <= 7) { + double a = (i == 5) ? 0.7 : (i == 6) ? 0.4 : 0.18; + rgb = rgb.ScaledBy(a); + } + } + + rgb = rgb.ClampWithin(0, 1); + int sx = px + 5 + PITCH*(i + 8) + 4, sy = py + 5 + PITCH*j; + + if(how == PAINT) { + glColor4d(CO(rgb), 1); + glxAxisAlignedQuad(sx, sx+SIZE, sy, sy+SIZE); + } else if(how == CLICK) { + if(x >= sx && x <= sx+SIZE && y >= sy && y <= sy+SIZE) { + editControl.colorPicker.rgb = RGBf(rgb.x, rgb.y, rgb.z); + ColorPickerDone(); + } + } else if(how == HOVER) { + if(x >= sx && x <= sx+SIZE && y >= sy && y <= sy+SIZE) { + mousePointerAsHand = true; + } + } + } + } + + int hxm, hym; + int hx = px + 5, hy = py + 5; + hxm = hx + PITCH*7 + SIZE; + hym = hy + PITCH*2 + SIZE; + if(how == PAINT) { + glxColorRGB(editControl.colorPicker.rgb); + glxAxisAlignedQuad(hx, hxm, hy, hym); + } else if(how == CLICK) { + if(x >= hx && x <= hxm && y >= hy && y <= hym) { + ColorPickerDone(); + } + } else if(how == HOVER) { + if(x >= hx && x <= hxm && y >= hy && y <= hym) { + mousePointerAsHand = true; + } + } + + hy += PITCH*3; + + hxm = hx + PITCH*7 + SIZE; + hym = hy + PITCH*1 + SIZE; + // The one-dimensional thing to pick the color's value + if(how == PAINT) { + glBindTexture(GL_TEXTURE_2D, TEXTURE_COLOR_PICKER_1D); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + 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, 1, 256, 0, + GL_RGB, GL_UNSIGNED_BYTE, + HsvPattern1d(editControl.colorPicker.h, + editControl.colorPicker.s)); + + glEnable(GL_TEXTURE_2D); + glBegin(GL_QUADS); + glTexCoord2d(0, 0); + glVertex2d(hx, hy); + + glTexCoord2d(1, 0); + glVertex2d(hx, hym); + + glTexCoord2d(1, 1); + glVertex2d(hxm, hym); + + glTexCoord2d(0, 1); + glVertex2d(hxm, hy); + glEnd(); + glDisable(GL_TEXTURE_2D); + + double cx = hx+(hxm-hx)*(1 - editControl.colorPicker.v); + glColor4d(0, 0, 0, 1); + glLineWidth(1); + glBegin(GL_LINES); + glVertex2d(cx, hy); + glVertex2d(cx, hym); + glEnd(); + glEnd(); + } else if(how == CLICK || + (how == HOVER && leftDown && editControl.colorPicker.picker1dActive)) + { + if(x >= hx && x <= hxm && y >= hy && y <= hym) { + editControl.colorPicker.v = 1 - (x - hx)/(hxm - hx); + + Vector rgb = HsvToRgb(Vector::From( + 6*editControl.colorPicker.h, + editControl.colorPicker.s, + editControl.colorPicker.v)); + editControl.colorPicker.rgb = RGBf(rgb.x, rgb.y, rgb.z); + + editControl.colorPicker.picker1dActive = true; + } + } + // and advance our vertical position + hy += PITCH*2; + + hxm = hx + PITCH*7 + SIZE; + hym = hy + PITCH*6 + SIZE; + // Two-dimensional thing to pick a color by hue and saturation + if(how == PAINT) { + glBindTexture(GL_TEXTURE_2D, TEXTURE_COLOR_PICKER_2D); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + 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, 256, 256, 0, + GL_RGB, GL_UNSIGNED_BYTE, HsvPattern2d()); + + glEnable(GL_TEXTURE_2D); + glBegin(GL_QUADS); + glTexCoord2d(0, 0); + glVertex2d(hx, hy); + + glTexCoord2d(1, 0); + glVertex2d(hx, hym); + + glTexCoord2d(1, 1); + glVertex2d(hxm, hym); + + glTexCoord2d(0, 1); + glVertex2d(hxm, hy); + glEnd(); + glDisable(GL_TEXTURE_2D); + + glColor4d(1, 1, 1, 1); + glLineWidth(1); + double cx = hx+(hxm-hx)*editControl.colorPicker.h, + cy = hy+(hym-hy)*editControl.colorPicker.s; + glBegin(GL_LINES); + glVertex2d(cx - 5, cy); + glVertex2d(cx + 4, cy); + glVertex2d(cx, cy - 5); + glVertex2d(cx, cy + 4); + glEnd(); + } else if(how == CLICK || + (how == HOVER && leftDown && editControl.colorPicker.picker2dActive)) + { + if(x >= hx && x <= hxm && y >= hy && y <= hym) { + double h = (x - hx)/(hxm - hx), + s = (y - hy)/(hym - hy); + editControl.colorPicker.h = h; + editControl.colorPicker.s = s; + + Vector rgb = HsvToRgb(Vector::From( + 6*editControl.colorPicker.h, + editControl.colorPicker.s, + editControl.colorPicker.v)); + editControl.colorPicker.rgb = RGBf(rgb.x, rgb.y, rgb.z); + + editControl.colorPicker.picker2dActive = true; + } + } + + SetMousePointerToHand(mousePointerAsHand); + return true; +} + void TextWindow::Paint(void) { int width, height; GetTextWindowSize(&width, &height); @@ -446,7 +797,8 @@ void TextWindow::Paint(void) { glTranslated(-1, 1, 0); glScaled(2.0/width, -2.0/height, 1); - glTranslated(0, 0, 0); + // Make things round consistently, avoiding exact integer boundary + glTranslated(-0.1, -0.1, 0); halfRows = height / (LINE_HEIGHT/2); @@ -580,10 +932,18 @@ void TextWindow::Paint(void) { // The header has some icons that are drawn separately from the text DrawOrHitTestIcons(PAINT, 0, 0); + + // And we may show a color picker for certain editable fields + DrawOrHitTestColorPicker(PAINT, false, 0, 0); } -void TextWindow::MouseEvent(bool leftClick, double x, double y) { +void TextWindow::MouseEvent(bool leftClick, bool leftDown, double x, double y) { if(TextEditControlIsVisible() || GraphicsEditControlIsVisible()) { + if(DrawOrHitTestColorPicker(leftClick ? CLICK : HOVER, leftDown, x, y)) + { + return; + } + if(leftClick) { HideEditControl(); HideGraphicsEditControl(); diff --git a/ui.h b/ui.h index 8880e170..43a43c06 100644 --- a/ui.h +++ b/ui.h @@ -69,7 +69,7 @@ public: // These are called by the platform-specific code. void Paint(void); - void MouseEvent(bool leftDown, double x, double y); + void MouseEvent(bool isClick, bool leftDown, double x, double y); void MouseScroll(double x, double y, int delta); void MouseLeave(void); void ScrollbarEvent(int newPos); @@ -81,6 +81,12 @@ public: void TimerCallback(void); Point2d oldMousePos; HideShowIcon *hoveredIcon, *tooltippedIcon; + + Vector HsvToRgb(Vector hsv); + BYTE *HsvPattern2d(void); + BYTE *HsvPattern1d(double h, double s); + void ColorPickerDone(void); + bool DrawOrHitTestColorPicker(int how, bool leftDown, double x, double y); void Init(void); void MakeColorTable(const Color *in, float *out); @@ -181,11 +187,18 @@ public: int halfRow; int col; - bool showColorPicker; + struct { + DWORD rgb; + double h, s, v; + bool show; + bool picker1dActive; + bool picker2dActive; + } colorPicker; } editControl; void HideEditControl(void); void ShowEditControl(int halfRow, int col, char *s); + void ShowEditControlWithColorPicker(int halfRow, int col, DWORD rgb); void ClearSuper(void); diff --git a/util.cpp b/util.cpp index 1d4b4732..d1bcbdb3 100644 --- a/util.cpp +++ b/util.cpp @@ -736,6 +736,20 @@ Vector Vector::ClosestOrtho(void) { } } +Vector Vector::ClampWithin(double minv, double maxv) { + Vector ret = *this; + + if(ret.x < minv) ret.x = minv; + if(ret.y < minv) ret.y = minv; + if(ret.z < minv) ret.z = minv; + + if(ret.x > maxv) ret.x = maxv; + if(ret.y > maxv) ret.y = maxv; + if(ret.z > maxv) ret.z = maxv; + + return ret; +} + void Vector::MakeMaxMin(Vector *maxv, Vector *minv) { maxv->x = max(maxv->x, x); maxv->y = max(maxv->y, y); diff --git a/win32/w32main.cpp b/win32/w32main.cpp index f584d8ae..5ce9da43 100644 --- a/win32/w32main.cpp +++ b/win32/w32main.cpp @@ -458,7 +458,7 @@ LRESULT CALLBACK TextWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) // And process the actual message int x = LOWORD(lParam); int y = HIWORD(lParam); - SS.TW.MouseEvent(msg == WM_LBUTTONDOWN, x, y); + SS.TW.MouseEvent(msg == WM_LBUTTONDOWN, wParam & MK_LBUTTON, x, y); break; } diff --git a/wishlist.txt b/wishlist.txt index bf89f1b6..d268303a 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,5 +1,6 @@ O(n*log(n)) assembly of edges into contours -good color picker +make group model color use new color picker +fix anti-aliased edge bug with filled contours crude DXF, HPGL import a request to import a plane thing make export assemble only contours in same group