diff --git a/CMakeLists.txt b/CMakeLists.txt index 0628afa4..6b67bd83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,8 @@ src/glmesh.cpp src/loader.cpp src/main.cpp src/mesh.cpp -src/window.cpp) +src/window.cpp +src/shaderlightprefs.cpp) #set project headers. set(Project_Headers src/app.h @@ -42,7 +43,8 @@ src/canvas.h src/glmesh.h src/loader.h src/mesh.h -src/window.h) +src/window.h +src/shaderlightprefs.h) #set project resources and icon resource set(Project_Resources qt/qt.qrc gl/gl.qrc) @@ -52,7 +54,7 @@ set(Icon_Resource exe/fstl.rc) set(OpenGL_GL_PREFERENCE GLVND) #find required packages. -find_package(Qt5 5.14 REQUIRED COMPONENTS Core Gui Widgets OpenGL) +find_package(Qt5 5.12 REQUIRED COMPONENTS Core Gui Widgets OpenGL) find_package(OpenGL REQUIRED) find_package(Threads REQUIRED) diff --git a/gl/calc_altitudes.glsl b/gl/calc_altitudes.glsl new file mode 100644 index 00000000..721bed1f --- /dev/null +++ b/gl/calc_altitudes.glsl @@ -0,0 +1,64 @@ +#version 330 + +layout (triangles) in; +layout (triangle_strip, max_vertices = 3) out; + +out vec3 ec_pos; +noperspective out vec3 altitude; + +uniform vec2 portSize; + +void main() { + vec4 p0 = gl_in[0].gl_Position; + vec4 p1 = gl_in[1].gl_Position; + vec4 p2 = gl_in[2].gl_Position; + + vec2 p0_f = p0.xy/p0.w; + vec2 p1_f = p1.xy/p1.w; + vec2 p2_f = p2.xy/p2.w; + + // Altitude calculation : + // vp0p1 is the p0p1 vector + // vp0p2 is the p0p2 vector + // det(vp0p1,vp0p2) is the area of the parallelogram defined by the two vectors vp0p1 and vp0p2 + // h0 is the altitude from p0 in the triangle (p0 p1 p2) + // h0 multiplied by p1p2 which is the length of vp0p1-vp0p2 is also the area of the parallelogram defined by the two vectors vp0p1 and vp0p2 + // this leads to h0 + // + // portSize is used to have an altitude in pixel + // + + // Calculate h0 altitude from p0 + vec2 vp0p1 = portSize*(p1_f-p0_f); + vec2 vp0p2 = portSize*(p2_f-p0_f); + float h0 = abs(determinant(mat2(vp0p1,vp0p2))) / length(vp0p1-vp0p2); + // release values + gl_Position = p0; + ec_pos = gl_Position.xyz; + altitude = vec3(h0*p0.w, 0.0, 0.0); + EmitVertex(); + + // calculate h1 altitude from p1 + vec2 vp1p0 = portSize*(p0_f-p1_f); + vec2 vp1p2 = portSize*(p2_f-p1_f); + float h1 = abs(determinant(mat2(vp1p0,vp1p2))) / length(vp1p0-vp1p2); + // release values + gl_Position = p1; + ec_pos = gl_Position.xyz; + altitude = vec3(0.0, h1*p1.w, 0.0); + EmitVertex(); + + // calculate h2 altitude from p2 + vec2 vp2p0 = portSize*(p0_f-p2_f); + vec2 vp2p1 = portSize*(p1_f-p2_f); + float h2 = abs(determinant(mat2(vp2p0,vp2p1))) / length(vp2p0-vp2p1); + // release values + gl_Position = p2; + ec_pos = gl_Position.xyz; + altitude = vec3(0.0, 0.0, h2*p2.w); + EmitVertex(); + + EndPrimitive(); + +} + diff --git a/gl/gl.qrc b/gl/gl.qrc index 6e8baa90..6d919e69 100644 --- a/gl/gl.qrc +++ b/gl/gl.qrc @@ -1,13 +1,16 @@ - + mesh.frag mesh.vert mesh_wireframe.frag mesh_surfaceangle.frag + mesh_light.frag quad.frag quad.vert colored_lines.frag colored_lines.vert sphere.stl + calc_altitudes.glsl + mesh_light_120.frag diff --git a/gl/mesh_light.frag b/gl/mesh_light.frag new file mode 100644 index 00000000..d2ceaa2b --- /dev/null +++ b/gl/mesh_light.frag @@ -0,0 +1,33 @@ +#version 330 + +uniform float zoom; +uniform vec4 ambient_light_color; +uniform vec4 directive_light_color; +uniform vec3 directive_light_direction; +uniform bool useWire; +uniform vec3 wireColor; +uniform float wireWidth; + +in vec3 ec_pos; +noperspective in vec3 altitude; + +void main() { + // Normalize light direction + vec3 dir = normalize(directive_light_direction); + + // normal vector + vec3 ec_normal = normalize(cross(dFdx(ec_pos), dFdy(ec_pos))); + ec_normal.z *= zoom; + ec_normal = normalize(ec_normal); + + + vec3 color = ambient_light_color.w * ambient_light_color.xyz + directive_light_color.w * dot(ec_normal,dir) * directive_light_color.xyz; + + if (useWire) { + float d = min(min(altitude.x, altitude.y),altitude.z); + float mixVal = smoothstep(wireWidth-1.0, wireWidth+1.0,d); + color = mix(wireColor,color,mixVal); + } + + gl_FragColor = vec4(color, 1.0); +} diff --git a/gl/mesh_light_120.frag b/gl/mesh_light_120.frag new file mode 100644 index 00000000..4c671470 --- /dev/null +++ b/gl/mesh_light_120.frag @@ -0,0 +1,23 @@ +#version 120 + +uniform float zoom; +uniform vec4 ambient_light_color; +uniform vec4 directive_light_color; +uniform vec3 directive_light_direction; + +varying vec3 ec_pos; + +void main() { + // Normalize light direction + vec3 dir = normalize(directive_light_direction); + + // normal vector + vec3 ec_normal = normalize(cross(dFdx(ec_pos), dFdy(ec_pos))); + ec_normal.z *= zoom; + ec_normal = normalize(ec_normal); + + + vec3 color = ambient_light_color.w * ambient_light_color.xyz + directive_light_color.w * dot(ec_normal,dir) * directive_light_color.xyz; + + gl_FragColor = vec4(color, 1.0); +} diff --git a/qt/icons/auto_refresh.png b/qt/icons/auto_refresh.png new file mode 100644 index 00000000..34826986 Binary files /dev/null and b/qt/icons/auto_refresh.png differ diff --git a/qt/icons/axes.png b/qt/icons/axes.png new file mode 100644 index 00000000..e4400735 Binary files /dev/null and b/qt/icons/axes.png differ diff --git a/qt/icons/document-open.png b/qt/icons/document-open.png new file mode 100644 index 00000000..c94a7edf Binary files /dev/null and b/qt/icons/document-open.png differ diff --git a/qt/icons/exit.png b/qt/icons/exit.png new file mode 100644 index 00000000..fb0a1a6b Binary files /dev/null and b/qt/icons/exit.png differ diff --git a/qt/icons/invert_zoom.png b/qt/icons/invert_zoom.png new file mode 100644 index 00000000..7136b95c Binary files /dev/null and b/qt/icons/invert_zoom.png differ diff --git a/qt/icons/orthographic.png b/qt/icons/orthographic.png new file mode 100644 index 00000000..64cbb7d7 Binary files /dev/null and b/qt/icons/orthographic.png differ diff --git a/qt/icons/perspective.png b/qt/icons/perspective.png new file mode 100644 index 00000000..442f9bf0 Binary files /dev/null and b/qt/icons/perspective.png differ diff --git a/qt/icons/preferences-system.png b/qt/icons/preferences-system.png new file mode 100644 index 00000000..97c48025 Binary files /dev/null and b/qt/icons/preferences-system.png differ diff --git a/qt/icons/reset_rotation_on_load.png b/qt/icons/reset_rotation_on_load.png new file mode 100644 index 00000000..1f17a007 Binary files /dev/null and b/qt/icons/reset_rotation_on_load.png differ diff --git a/qt/icons/resolution_1_32.png b/qt/icons/resolution_1_32.png new file mode 100644 index 00000000..7d655cc9 Binary files /dev/null and b/qt/icons/resolution_1_32.png differ diff --git a/qt/icons/screenshot.png b/qt/icons/screenshot.png new file mode 100644 index 00000000..97f380e3 Binary files /dev/null and b/qt/icons/screenshot.png differ diff --git a/qt/icons/sphere_shader1.png b/qt/icons/sphere_shader1.png new file mode 100644 index 00000000..84931c37 Binary files /dev/null and b/qt/icons/sphere_shader1.png differ diff --git a/qt/icons/sphere_shader2.png b/qt/icons/sphere_shader2.png new file mode 100644 index 00000000..258b3598 Binary files /dev/null and b/qt/icons/sphere_shader2.png differ diff --git a/qt/icons/sphere_shader3.png b/qt/icons/sphere_shader3.png new file mode 100644 index 00000000..ca05cd45 Binary files /dev/null and b/qt/icons/sphere_shader3.png differ diff --git a/qt/icons/sphere_shader4.png b/qt/icons/sphere_shader4.png new file mode 100644 index 00000000..31b3e787 Binary files /dev/null and b/qt/icons/sphere_shader4.png differ diff --git a/qt/icons/view-fullscreen.png b/qt/icons/view-fullscreen.png new file mode 100644 index 00000000..bc059924 Binary files /dev/null and b/qt/icons/view-fullscreen.png differ diff --git a/qt/icons/view-refresh.png b/qt/icons/view-refresh.png new file mode 100644 index 00000000..72f6df20 Binary files /dev/null and b/qt/icons/view-refresh.png differ diff --git a/qt/qt.qrc b/qt/qt.qrc index f2b1266f..2c9179de 100644 --- a/qt/qt.qrc +++ b/qt/qt.qrc @@ -2,5 +2,22 @@ style.qss icons/fstl_64x64.png + icons/sphere_shader1.png + icons/sphere_shader2.png + icons/sphere_shader3.png + icons/sphere_shader4.png + icons/document-open.png + icons/exit.png + icons/screenshot.png + icons/view-fullscreen.png + icons/view-refresh.png + icons/preferences-system.png + icons/axes.png + icons/orthographic.png + icons/perspective.png + icons/reset_rotation_on_load.png + icons/invert_zoom.png + icons/auto_refresh.png + icons/resolution_1_32.png diff --git a/src/canvas.cpp b/src/canvas.cpp index 0f6037fb..0349660d 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -11,6 +11,24 @@ const float Canvas::P_PERSPECTIVE = 0.25f; const float Canvas::P_ORTHOGRAPHIC = 0.0f; +const QString Canvas::AMBIENT_COLOR = "ambientColor"; +const QString Canvas::AMBIENT_FACTOR = "ambientFactor"; +const QString Canvas::DIRECTIVE_COLOR = "directiveColor"; +const QString Canvas::DIRECTIVE_FACTOR = "directiveFactor"; +const QString Canvas::CURRENT_LIGHT_DIRECTION = "currentLightDirection"; +const QString Canvas::USE_WIRE = "useWire"; +const QString Canvas::WIRE_WIDTH = "wireWidth"; +const QString Canvas::WIRE_COLOR = "wireColor"; + +const QColor Canvas::defaultAmbientColor = QColor::fromRgbF(0.22,0.8,1.0); +const QColor Canvas::defaultDirectiveColor = QColor(255,255,255); +const double Canvas::defaultAmbientFactor = 0.67; +const double Canvas::defaultDirectiveFactor = 0.5; +const int Canvas::defaultCurrentLightDirection = 1; +const bool Canvas::defaultUseWire = false; +const double Canvas::defaultWireWidth = 1.0; +const QColor Canvas::defaultWireColor = QColor(255,128,0); + Canvas::Canvas(const QSurfaceFormat& format, QWidget *parent) : QOpenGLWidget(parent), mesh(nullptr), scale(1), zoom(1), @@ -24,7 +42,42 @@ Canvas::Canvas(const QSurfaceFormat& format, QWidget *parent) currentTransform = QMatrix4x4(); resetTransform(); + fallbackGlsl = false; + QSettings settings; + ambientColor = settings.value(AMBIENT_COLOR,defaultAmbientColor).value(); + directiveColor = settings.value(DIRECTIVE_COLOR,defaultDirectiveColor).value(); + ambientFactor = settings.value(AMBIENT_FACTOR,defaultAmbientFactor).value(); + directiveFactor = settings.value(DIRECTIVE_FACTOR,defaultDirectiveFactor).value(); + useWire = settings.value(USE_WIRE,defaultUseWire).value(); + wireWidth = settings.value(WIRE_WIDTH,defaultWireWidth).value(); + wireColor = settings.value(WIRE_COLOR,defaultWireColor).value(); + + // Fill direction list + // Fill in directions + nameDir.clear(); + listDir.clear(); + QList xname, yname, zname; + xname << "right " << " " << "left "; + yname << "top " << " " << "bottom "; + zname << "rear " << " " << "front "; + for (int i=-1; i<2 ; i++) { + for (int j=-1; j<2; j++) { + for (int k=-1; k<2; k++) { + QString current = xname.at(i+1) + yname.at(j+1) + zname.at(k+1); + if (!(i==0 && j==0 && k==0)) { + nameDir << current.simplified(); + listDir << QVector3D((double)i,(double)j,(double)k); + } + } + } + } + currentLightDirection = settings.value(CURRENT_LIGHT_DIRECTION,defaultCurrentLightDirection).value(); + if (currentLightDirection < 0 || currentLightDirection >= nameDir.length()) { + currentLightDirection = defaultCurrentLightDirection; + } + anim.setDuration(100); + } Canvas::~Canvas() @@ -134,6 +187,8 @@ void Canvas::initializeGL() { initializeOpenGLFunctions(); + fallbackGlsl = false; + mesh_vertshader = new QOpenGLShader(QOpenGLShader::Vertex); mesh_vertshader->compileSourceFile(":/gl/mesh.vert"); mesh_shader.addShader(mesh_vertshader); @@ -145,6 +200,18 @@ void Canvas::initializeGL() mesh_surfaceangle_shader.addShader(mesh_vertshader); mesh_surfaceangle_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_surfaceangle.frag"); mesh_surfaceangle_shader.link(); + mesh_meshlight_shader.addShader(mesh_vertshader); + bool loadSuccess330 = mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Geometry, ":/gl/calc_altitudes.glsl") && + mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_light.frag"); + if (!loadSuccess330) { + // fallback to 120 + fallbackGlsl = true; + mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_light_120.frag"); + qDebug() << "Cannot load a shader using glsl version 330, fall back to another using version 120"; + qDebug() << "Adding wireframe on top of meshlight shader will be disabled."; + } + emit fallbackGlslUpdated(fallbackGlsl); + mesh_meshlight_shader.link(); backdrop = new Backdrop(); axis = new Axis(); @@ -166,6 +233,17 @@ void Canvas::paintGL() float textHeight = painter.fontInfo().pointSize(); if (drawAxes) painter.drawText(QRect(10, textHeight, width(), height()), meshInfo); painter.drawText(10, height() - textHeight, status); + + if (drawAxes) { + QString sWidth = QString("GL Width = %1").arg(width()); + QString sHeight = QString("GL Height = %1").arg(height()); + int sWidthLength = painter.fontMetrics().horizontalAdvance(sWidth); + int sHeightLength = painter.fontMetrics().horizontalAdvance(sHeight); + int origin = std::min(sWidthLength,sHeightLength); + painter.drawText(width() - origin - 10, textHeight + 10, sWidth); + painter.drawText(width() - origin - 10, 2* textHeight + 10, sHeight); + } + } void Canvas::draw_mesh() @@ -182,10 +260,14 @@ void Canvas::draw_mesh() { selected_mesh_shader = &mesh_shader; } - else + else if (drawMode == surfaceangle) { selected_mesh_shader = &mesh_surfaceangle_shader; } + else if (drawMode == meshlight) + { + selected_mesh_shader = &mesh_meshlight_shader; + } glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } @@ -202,6 +284,34 @@ void Canvas::draw_mesh() // Compensate for z-flattening when zooming glUniform1f(selected_mesh_shader->uniformLocation("zoom"), 1/zoom); + // specific meshlight arguments + if (drawMode == meshlight) { + // Ambient Light Color, followed by the ambient light coefficient to use + //glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),0.22f, 0.8f, 1.0f, 0.67f); + glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),ambientColor.redF(), ambientColor.greenF(), ambientColor.blueF(), ambientFactor); + // Directive Light Color, followed by the directive light coefficient to use + //glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.5f); + glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),directiveColor.redF(),directiveColor.greenF(),directiveColor.blueF(),directiveFactor); + + // Directive Light Direction + // dir 1,0,0 Light from the left + // dir -1,0,0 Light from the right + // dir 0,1,0 Light from bottom + // dir 0,-1,0 Light from top + // dir 0,0,1 Light from viewer (front) + // dir 0,0,-1 Light from behind + // + // -1,-1,0 Light from top right + //glUniform3f(selected_mesh_shader->uniformLocation("directive_light_direction"),-1.0f,-1.0f,0.0f); + glUniform3f(selected_mesh_shader->uniformLocation("directive_light_direction"),listDir.at(currentLightDirection).x(), listDir.at(currentLightDirection).y(), listDir.at(currentLightDirection).z()); + if (!fallbackGlsl) { + glUniform1i(selected_mesh_shader->uniformLocation("useWire"),useWire); + glUniform1f(selected_mesh_shader->uniformLocation("wireWidth"),wireWidth); + glUniform2f(selected_mesh_shader->uniformLocation("portSize"),(float)this->width(),(float)this->height()); + glUniform3f(selected_mesh_shader->uniformLocation("wireColor"),wireColor.redF(),wireColor.greenF(),wireColor.blueF()); + } + } + // Find and enable the attribute location for vertex position const GLuint vp = selected_mesh_shader->attributeLocation("vertex_position"); glEnableVertexAttribArray(vp); @@ -351,7 +461,13 @@ void Canvas::wheelEvent(QWheelEvent *event) { // Find GL position before the zoom operation // (to zoom about mouse cursor) +// event->pos() obsolete since introduction of event->position() in 5.14 +// but we still want to be able compile with 5.12 which is the minimum requirement in CmakeLists.txt +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + auto p = event->pos(); +#else auto p = event->position(); +#endif QVector3D v(1 - p.x() / (0.5*width()), p.y() / (0.5*height()) - 1, 0); QVector3D a = transform_matrix().inverted() * @@ -385,3 +501,117 @@ void Canvas::resizeGL(int width, int height) { glViewport(0, 0, width, height); } + +QColor Canvas::getAmbientColor() { + return ambientColor; +} + +void Canvas::setAmbientColor(QColor c) { + ambientColor = c; + QSettings settings; + settings.setValue(AMBIENT_COLOR,c); +} + +double Canvas::getAmbientFactor() { + return (float) ambientFactor; +} + +void Canvas::setAmbientFactor(double f) { + ambientFactor = (float) f; + QSettings settings; + settings.setValue(AMBIENT_FACTOR,f); +} + +void Canvas::resetAmbientColor() { + setAmbientColor(defaultAmbientColor); + setAmbientFactor(defaultAmbientFactor); +} + +QColor Canvas::getDirectiveColor() { + return directiveColor; +} + +void Canvas::setDirectiveColor(QColor c) { + directiveColor = c; + QSettings settings; + settings.setValue(DIRECTIVE_COLOR,c); +} + +double Canvas::getDirectiveFactor() { + return (float) directiveFactor; +} + +void Canvas::setDirectiveFactor(double f) { + directiveFactor = (float) f; + QSettings settings; + settings.setValue(DIRECTIVE_FACTOR,f); +} + +void Canvas::resetDirectiveColor() { + setDirectiveColor(defaultDirectiveColor); + setDirectiveFactor(defaultDirectiveFactor); +} + +QList Canvas::getNameDir() { + return nameDir; +} + +int Canvas::getCurrentLightDirection() { + return currentLightDirection; +} + +void Canvas::setCurrentLightDirection(int ind) { + currentLightDirection = ind; + QSettings settings; + settings.setValue(CURRENT_LIGHT_DIRECTION,currentLightDirection); +} + +void Canvas::resetCurrentLightDirection() { + setCurrentLightDirection(defaultCurrentLightDirection); +} + +bool Canvas::getUseWire() { + return useWire; +} + +void Canvas::setUseWire(bool b) { + useWire = b; + QSettings settings; + settings.setValue(USE_WIRE,useWire); +} + +void Canvas::resetUseWire() { + setUseWire(defaultUseWire); +} + +double Canvas::getWireWidth() { + return (double) wireWidth; +} + +void Canvas::setWireWidth(double w) { + wireWidth = (float) w; + QSettings settings; + settings.setValue(WIRE_WIDTH,w); +} + +void Canvas::resetWireWidth() { + setWireWidth(defaultWireWidth); +} + +QColor Canvas::getWireColor() { + return wireColor; +} + +void Canvas::setWireColor(QColor c) { + wireColor = c; + QSettings settings; + settings.setValue(WIRE_COLOR,wireColor); +} + +void Canvas::resetWireColor() { + setWireColor(defaultWireColor); +} + +bool Canvas::isFallbackGlsl() { + return fallbackGlsl; +} diff --git a/src/canvas.h b/src/canvas.h index e065db65..a5994363 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -10,7 +10,7 @@ class Mesh; class Backdrop; class Axis; -enum DrawMode {shaded, wireframe, surfaceangle, DRAWMODECOUNT}; +enum DrawMode {shaded, wireframe, surfaceangle, meshlight, DRAWMODECOUNT}; class Canvas : public QOpenGLWidget, protected QOpenGLFunctions { @@ -29,6 +29,37 @@ class Canvas : public QOpenGLWidget, protected QOpenGLFunctions void set_drawMode(enum DrawMode mode); void setResetTransformOnLoad(bool d); + QColor getAmbientColor(); + void setAmbientColor(QColor c); + double getAmbientFactor(); + void setAmbientFactor(double f); + void resetAmbientColor(); + + QColor getDirectiveColor(); + void setDirectiveColor(QColor c); + double getDirectiveFactor(); + void setDirectiveFactor(double f); + void resetDirectiveColor(); + + QList getNameDir(); + int getCurrentLightDirection(); + void setCurrentLightDirection(int ind); + void resetCurrentLightDirection(); + + bool getUseWire(); + void setUseWire(bool b); + void resetUseWire(); + + double getWireWidth(); + void setWireWidth(double w); + void resetWireWidth(); + + QColor getWireColor(); + void setWireColor(QColor c); + void resetWireColor(); + + bool isFallbackGlsl(); + public slots: void set_status(const QString& s); void clear_status(); @@ -47,6 +78,9 @@ public slots: void set_perspective(float p); void view_anim(float v); +signals: + void fallbackGlslUpdated(bool b); + private: void draw_mesh(); @@ -62,6 +96,38 @@ public slots: QOpenGLShaderProgram mesh_shader; QOpenGLShaderProgram mesh_wireframe_shader; QOpenGLShaderProgram mesh_surfaceangle_shader; + QOpenGLShaderProgram mesh_meshlight_shader; + + QColor ambientColor; + QColor directiveColor; + float ambientFactor; + float directiveFactor; + QList nameDir; + QList listDir; + int currentLightDirection; + bool useWire; + float wireWidth; + QColor wireColor; + bool fallbackGlsl; + + const static QColor defaultAmbientColor; + const static QColor defaultDirectiveColor; + const static double defaultAmbientFactor; + const static double defaultDirectiveFactor; + const static int defaultCurrentLightDirection; + const static bool defaultUseWire; + const static double defaultWireWidth; + const static QColor defaultWireColor; + + const static QString AMBIENT_COLOR; + const static QString AMBIENT_FACTOR; + const static QString DIRECTIVE_COLOR; + const static QString DIRECTIVE_FACTOR; + const static QString CURRENT_LIGHT_DIRECTION; + const static QString USE_WIRE; + const static QString WIRE_WIDTH; + const static QString WIRE_COLOR; + GLMesh* mesh; Backdrop* backdrop; diff --git a/src/main.cpp b/src/main.cpp index 4ec87227..14561ebc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,10 +4,14 @@ int main(int argc, char *argv[]) { + // Force C locale to force decimal point + QLocale::setDefault(QLocale::c()); + QCoreApplication::setOrganizationName("fstl-app"); QCoreApplication::setOrganizationDomain("https://github.com/fstl-app/fstl"); QCoreApplication::setApplicationName("fstl"); QCoreApplication::setApplicationVersion(FSTL_VERSION); App a(argc, argv); + return a.exec(); } diff --git a/src/shaderlightprefs.cpp b/src/shaderlightprefs.cpp new file mode 100644 index 00000000..bb4cc38d --- /dev/null +++ b/src/shaderlightprefs.cpp @@ -0,0 +1,272 @@ +#include "shaderlightprefs.h" +#include "canvas.h" +#include + +const QString ShaderLightPrefs::PREFS_GEOM = "shaderPrefsGeometry"; + +ShaderLightPrefs::ShaderLightPrefs(QWidget *parent, Canvas *_canvas) : QDialog(parent) +{ + canvas = _canvas; + + QVBoxLayout* prefsLayout = new QVBoxLayout; + this->setLayout(prefsLayout); + + QLabel* title = new QLabel("Shader preferences"); + QFont boldFont = QApplication::font(); + boldFont.setWeight(QFont::Bold); + title->setFont(boldFont); + title->setAlignment(Qt::AlignCenter); + prefsLayout->addWidget(title); + + QWidget* middleWidget = new QWidget; + QGridLayout* middleLayout = new QGridLayout; + middleWidget->setLayout(middleLayout); + this->layout()->addWidget(middleWidget); + + // labels + middleLayout->addWidget(new QLabel("Ambient Color"),0,0); + middleLayout->addWidget(new QLabel("Directive Color"),1,0); + middleLayout->addWidget(new QLabel("Direction"),2,0); + + QPixmap dummy(20, 20); + + dummy.fill(canvas->getAmbientColor()); + buttonAmbientColor = new QPushButton; + buttonAmbientColor->setIcon(QIcon(dummy)); + middleLayout->addWidget(buttonAmbientColor,0,1); + buttonAmbientColor->setFocusPolicy(Qt::NoFocus); + connect(buttonAmbientColor,SIGNAL(clicked(bool)),this,SLOT(buttonAmbientColorClicked())); + + editAmbientFactor = new QLineEdit; + editAmbientFactor->setValidator(new QDoubleValidator); + editAmbientFactor->setText(QString("%1").arg(canvas->getAmbientFactor())); + middleLayout->addWidget(editAmbientFactor,0,2); + connect(editAmbientFactor,SIGNAL(editingFinished()),this,SLOT(editAmbientFactorFinished())); + + QPushButton* buttonResetAmbientColor = new QPushButton("Reset"); + middleLayout->addWidget(buttonResetAmbientColor,0,3); + buttonResetAmbientColor->setFocusPolicy(Qt::NoFocus); + connect(buttonResetAmbientColor,SIGNAL(clicked(bool)),this,SLOT(resetAmbientColorClicked())); + + + dummy.fill(canvas->getDirectiveColor()); + buttonDirectiveColor = new QPushButton; + buttonDirectiveColor->setIcon(QIcon(dummy)); + middleLayout->addWidget(buttonDirectiveColor,1,1); + buttonDirectiveColor->setFocusPolicy(Qt::NoFocus); + connect(buttonDirectiveColor,SIGNAL(clicked(bool)),this,SLOT(buttonDirectiveColorClicked())); + + editDirectiveFactor = new QLineEdit; + editDirectiveFactor->setValidator(new QDoubleValidator); + editDirectiveFactor->setText(QString("%1").arg(canvas->getDirectiveFactor())); + middleLayout->addWidget(editDirectiveFactor,1,2); + connect(editDirectiveFactor,SIGNAL(editingFinished()),this,SLOT(editDirectiveFactorFinished())); + + QPushButton* buttonResetDirectiveColor = new QPushButton("Reset"); + middleLayout->addWidget(buttonResetDirectiveColor,1,3); + buttonResetDirectiveColor->setFocusPolicy(Qt::NoFocus); + connect(buttonResetDirectiveColor,SIGNAL(clicked(bool)),this,SLOT(resetDirectiveColorClicked())); + + // Fill in directions + + comboDirections = new QComboBox; + comboDirections->setFocusPolicy(Qt::NoFocus); + middleLayout->addWidget(comboDirections,2,1,1,2); + comboDirections->addItems(canvas->getNameDir()); + comboDirections->setCurrentIndex(canvas->getCurrentLightDirection()); + connect(comboDirections,SIGNAL(currentIndexChanged(int)),this,SLOT(comboDirectionsChanged(int))); + + QPushButton* buttonResetDirection = new QPushButton("Reset"); + middleLayout->addWidget(buttonResetDirection,2,3); + buttonResetDirection->setFocusPolicy(Qt::NoFocus); + connect(buttonResetDirection,SIGNAL(clicked(bool)),this,SLOT(resetDirection())); + + + groupWireFrame = new QFrame; + QGridLayout* groupWireFrameLayout = new QGridLayout; + groupWireFrame->setLayout(groupWireFrameLayout); + + checkboxUseWireFrame = new QCheckBox("Add wireframe"); + checkboxUseWireFrame->setChecked(canvas->getUseWire()); + groupWireFrameLayout->addWidget(checkboxUseWireFrame,0,0); + checkboxUseWireFrame->setFocusPolicy(Qt::NoFocus); + connect(checkboxUseWireFrame,SIGNAL(stateChanged(int)),this,SLOT(checkboxUseWireFrameChanged())); + + QLabel* labelWireColor = new QLabel("Wire Color"); + groupWireFrameLayout->addWidget(labelWireColor,1,0); + dummy.fill(canvas->getWireColor()); + buttonWireColor = new QPushButton; + buttonWireColor->setIcon(QIcon(dummy)); + groupWireFrameLayout->addWidget(buttonWireColor,1,1); + buttonWireColor->setFocusPolicy(Qt::NoFocus); + QPushButton* buttonResetWireColor = new QPushButton("Reset"); + buttonResetWireColor->setFocusPolicy(Qt::NoFocus); + groupWireFrameLayout->addWidget(buttonResetWireColor,1,3); + connect(buttonWireColor,SIGNAL(clicked(bool)),this,SLOT(buttonWireColorClicked())); + connect(buttonResetWireColor,SIGNAL(clicked(bool)),this,SLOT(resetWireColorClicked())); + + labelWireWidth = new QLabel(QString("Wire Width : %1").arg((int)canvas->getWireWidth())); + groupWireFrameLayout->addWidget(labelWireWidth,2,0); + sliderWireWidth = new QSlider(Qt::Horizontal); + sliderWireWidth->setFocusPolicy(Qt::NoFocus); + sliderWireWidth->setRange(1,10); + sliderWireWidth->setTickPosition(QSlider::TicksBelow); + sliderWireWidth->setSingleStep(1); + sliderWireWidth->setPageStep(1); + sliderWireWidth->setValue((int)canvas->getWireWidth()); + groupWireFrameLayout->addWidget(sliderWireWidth,2,1,1,2); + connect(sliderWireWidth,SIGNAL(valueChanged(int)),this,SLOT(sliderWireWidthChanged())); + QPushButton* buttonResetLineWidth = new QPushButton("Reset"); + buttonResetLineWidth->setFocusPolicy(Qt::NoFocus); + groupWireFrameLayout->addWidget(buttonResetLineWidth,2,3); + connect(buttonResetLineWidth,SIGNAL(clicked(bool)),this,SLOT(resetWireWidthClicked())); + + middleLayout->addWidget(groupWireFrame,3,0,3,4); + + // Ok button + QWidget* boxButton = new QWidget; + QHBoxLayout* boxButtonLayout = new QHBoxLayout; + boxButton->setLayout(boxButtonLayout); + QFrame *spacerL = new QFrame; + spacerL->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding)); + QPushButton* okButton = new QPushButton("Ok"); + boxButtonLayout->addWidget(spacerL); + boxButtonLayout->addWidget(okButton); + this->layout()->addWidget(boxButton); + okButton->setFocusPolicy(Qt::NoFocus); + connect(okButton,SIGNAL(clicked(bool)),this,SLOT(okButtonClicked())); + + QSettings settings; + if (!settings.value(PREFS_GEOM).isNull()) { + restoreGeometry(settings.value(PREFS_GEOM).toByteArray()); + } + + connect(canvas,SIGNAL(fallbackGlslUpdated(bool)),this,SLOT(onFallbackGlslUpdated(bool))); +} + +void ShaderLightPrefs::buttonAmbientColorClicked() { + QColor newColor = QColorDialog::getColor(canvas->getAmbientColor(), this, QString("Choose color"),QColorDialog::DontUseNativeDialog); + if (newColor.isValid() == true) + { + canvas->setAmbientColor(newColor); + QPixmap dummy(20, 20); + dummy.fill(canvas->getAmbientColor()); + buttonAmbientColor->setIcon(QIcon(dummy)); + canvas->update(); + } +} + +void ShaderLightPrefs::editAmbientFactorFinished() { + canvas->setAmbientFactor(editAmbientFactor->text().toDouble()); + canvas->update(); +} + +void ShaderLightPrefs::resetAmbientColorClicked() { + canvas->resetAmbientColor(); + QPixmap dummy(20, 20); + dummy.fill(canvas->getAmbientColor()); + buttonAmbientColor->setIcon(QIcon(dummy)); + editAmbientFactor->setText(QString("%1").arg(canvas->getAmbientFactor())); + canvas->update(); +} + +void ShaderLightPrefs::buttonDirectiveColorClicked() { + QColor newColor = QColorDialog::getColor(canvas->getDirectiveColor(), this, QString("Choose color"),QColorDialog::DontUseNativeDialog); + if (newColor.isValid() == true) + { + canvas->setDirectiveColor(newColor); + QPixmap dummy(20, 20); + dummy.fill(canvas->getDirectiveColor()); + buttonDirectiveColor->setIcon(QIcon(dummy)); + canvas->update(); + } +} + +void ShaderLightPrefs::editDirectiveFactorFinished() { + canvas->setDirectiveFactor(editDirectiveFactor->text().toDouble()); + canvas->update(); +} + +void ShaderLightPrefs::resetDirectiveColorClicked() { + canvas->resetDirectiveColor(); + QPixmap dummy(20, 20); + dummy.fill(canvas->getDirectiveColor()); + buttonDirectiveColor->setIcon(QIcon(dummy)); + editDirectiveFactor->setText(QString("%1").arg(canvas->getDirectiveFactor())); + canvas->update(); +} + +void ShaderLightPrefs::okButtonClicked() { + this->close(); +} + +void ShaderLightPrefs::comboDirectionsChanged(int ind) { + canvas->setCurrentLightDirection(ind); + canvas->update(); +} + +void ShaderLightPrefs::resetDirection() { + canvas->resetCurrentLightDirection(); + comboDirections->setCurrentIndex(canvas->getCurrentLightDirection()); + canvas->update(); +} + +void ShaderLightPrefs::resizeEvent(QResizeEvent *event) +{ + QSettings().setValue(PREFS_GEOM, saveGeometry()); + QWidget::resizeEvent(event); +} + +void ShaderLightPrefs::moveEvent(QMoveEvent *event) +{ + QSettings().setValue(PREFS_GEOM, saveGeometry()); + QWidget::moveEvent(event); +} + +void ShaderLightPrefs::checkboxUseWireFrameChanged() { + bool state = checkboxUseWireFrame->isChecked(); + canvas->setUseWire(state); + canvas->update(); +} + +void ShaderLightPrefs::buttonWireColorClicked() { + QColor newColor = QColorDialog::getColor(canvas->getWireColor(), this, QString("Choose color"),QColorDialog::DontUseNativeDialog); + if (newColor.isValid() == true) + { + canvas->setWireColor(newColor); + QPixmap dummy(20, 20); + dummy.fill(canvas->getWireColor()); + buttonWireColor->setIcon(QIcon(dummy)); + canvas->update(); + } +} + +void ShaderLightPrefs::resetWireColorClicked() { + canvas->resetWireColor(); + QPixmap dummy(20, 20); + dummy.fill(canvas->getWireColor()); + buttonWireColor->setIcon(QIcon(dummy)); + canvas->update(); +} + +void ShaderLightPrefs::sliderWireWidthChanged() { + int lw = sliderWireWidth->value(); + canvas->setWireWidth((double) lw); + labelWireWidth->setText(QString("Wire Width : %1").arg(lw)); + canvas->update(); +} + +void ShaderLightPrefs::resetWireWidthClicked() { + canvas->resetWireWidth(); + sliderWireWidth->setValue((int)canvas->getWireWidth()); +} + +void ShaderLightPrefs::onFallbackGlslUpdated(bool b) { + groupWireFrame->setDisabled(b); +} + +void ShaderLightPrefs::toggleUseWire() { + // toggle if enable, no sense to do so otherwise + if (checkboxUseWireFrame->isEnabled()) + checkboxUseWireFrame->toggle(); +} diff --git a/src/shaderlightprefs.h b/src/shaderlightprefs.h new file mode 100644 index 00000000..0e6b79ee --- /dev/null +++ b/src/shaderlightprefs.h @@ -0,0 +1,63 @@ +#ifndef SHADERLIGHTPREFS_H +#define SHADERLIGHTPREFS_H + +#include + +class Canvas; +class QLabel; +class QLineEdit; +class QComboBox; +class QCheckBox; +class QSlider; +class QFrame; + +class ShaderLightPrefs : public QDialog +{ + Q_OBJECT +public: + ShaderLightPrefs(QWidget* parent, Canvas* _canvas); + void toggleUseWire(); + +protected: + void resizeEvent(QResizeEvent *event) override; + void moveEvent(QMoveEvent *event) override; + +private slots: + void buttonAmbientColorClicked(); + void editAmbientFactorFinished(); + void resetAmbientColorClicked(); + + void buttonDirectiveColorClicked(); + void editDirectiveFactorFinished(); + void resetDirectiveColorClicked(); + + void comboDirectionsChanged(int ind); + void resetDirection(); + + void checkboxUseWireFrameChanged(); + void buttonWireColorClicked(); + void resetWireColorClicked(); + void sliderWireWidthChanged(); + void resetWireWidthClicked(); + + void okButtonClicked(); + void onFallbackGlslUpdated(bool b); + +private: + Canvas* canvas; + QPushButton* buttonAmbientColor; + QLineEdit* editAmbientFactor; + QPushButton* buttonDirectiveColor; + QLineEdit* editDirectiveFactor; + QComboBox* comboDirections; + + QFrame* groupWireFrame; + QCheckBox* checkboxUseWireFrame; + QPushButton* buttonWireColor; + QLabel* labelWireWidth; + QSlider* sliderWireWidth; + + const static QString PREFS_GEOM; +}; + +#endif // SHADERLIGHTPREFS_H diff --git a/src/window.cpp b/src/window.cpp index db9e99a0..40e0d8cd 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -3,6 +3,7 @@ #include "window.h" #include "canvas.h" #include "loader.h" +#include "shaderlightprefs.h" #include #include @@ -15,6 +16,16 @@ const QString Window::PROJECTION_KEY = "projection"; const QString Window::DRAW_MODE_KEY = "drawMode"; const QString Window::WINDOW_GEOM_KEY = "windowGeometry"; const QString Window::RESET_TRANSFORM_ON_LOAD_KEY = "resetTransformOnLoad"; +const QString Window::HIDE_MENU_BAR = "hideMenuBar"; + +const QKeySequence Window::shortcutOpen = Qt::Key_O; +const QKeySequence Window::shortcutReload = Qt::Key_R; +const QKeySequence Window::shortcutScreenshot = Qt::Key_S; +const QKeySequence Window::shortcutQuit = Qt::Key_Q; +const QKeySequence Window::shortcutDrawModeSettings = Qt::Key_P; +const QKeySequence Window::shortcutDrawAxes = Qt::Key_A; +const QKeySequence Window::shortcutHideMenuBar = Qt::Key_M; +const QKeySequence Window::shortcutFullscreen = Qt::Key_F; Window::Window(QWidget *parent) : QMainWindow(parent), @@ -26,6 +37,8 @@ Window::Window(QWidget *parent) : shaded_action(new QAction("Shaded", this)), wireframe_action(new QAction("Wireframe", this)), surfaceangle_action(new QAction("Surface Angle", this)), + meshlight_action(new QAction("Shaded ambient and directive light source", this)), + drawModePrefs_action(new QAction("Draw Mode Settings")), axes_action(new QAction("Draw Axes", this)), invert_zoom_action(new QAction("Invert Zoom", this)), reload_action(new QAction("Reload", this)), @@ -34,6 +47,7 @@ Window::Window(QWidget *parent) : hide_menuBar_action(new QAction("Hide Menu Bar", this)), fullscreen_action(new QAction("Toggle Fullscreen",this)), resetTransformOnLoadAction(new QAction("Reset rotation on load",this)), + setGLSizeAction(new QAction("Set Viewport Size",this)), recent_files(new QMenu("Open recent", this)), recent_files_group(new QActionGroup(this)), recent_files_clear_action(new QAction("Clear recent files", this)), @@ -54,25 +68,38 @@ Window::Window(QWidget *parent) : canvas = new Canvas(format, this); setCentralWidget(canvas); + canvas->update(); + + meshlightprefs = new ShaderLightPrefs(this, canvas); + + QObject::connect(drawModePrefs_action, &QAction::triggered,this,&Window::on_drawModePrefs); QObject::connect(watcher, &QFileSystemWatcher::fileChanged, this, &Window::on_watched_change); - open_action->setShortcut(QKeySequence::Open); + //open_action->setShortcut(QKeySequence::Open); + open_action->setShortcut(shortcutOpen); + open_action->setIcon(QIcon(":/qt/icons/document-open.png")); QObject::connect(open_action, &QAction::triggered, this, &Window::on_open); this->addAction(open_action); - quit_action->setShortcut(QKeySequence::Quit); + //quit_action->setShortcut(QKeySequence::Quit); + quit_action->setShortcut(shortcutQuit); + quit_action->setIcon(QIcon(":/qt/icons/exit.png")); QObject::connect(quit_action, &QAction::triggered, this, &Window::close); this->addAction(quit_action); autoreload_action->setCheckable(true); + autoreload_action->setIcon(QIcon(":/qt/icons/auto_refresh.png")); QObject::connect(autoreload_action, &QAction::triggered, this, &Window::on_autoreload_triggered); - reload_action->setShortcut(QKeySequence::Refresh); + //reload_action->setShortcut(QKeySequence::Refresh); + reload_action->setShortcut(shortcutReload); + reload_action->setIcon(QIcon(":/qt/icons/view-refresh.png")); + this->addAction(reload_action); reload_action->setEnabled(false); QObject::connect(reload_action, &QAction::triggered, this, &Window::on_reload); @@ -86,6 +113,9 @@ Window::Window(QWidget *parent) : this, &Window::on_load_recent); save_screenshot_action->setCheckable(false); + save_screenshot_action->setShortcut(shortcutScreenshot); + save_screenshot_action->setIcon(QIcon(":/qt/icons/screenshot.png")); + this->addAction(save_screenshot_action); QObject::connect(save_screenshot_action, &QAction::triggered, this, &Window::on_save_screenshot); @@ -101,9 +131,11 @@ Window::Window(QWidget *parent) : file_menu->addAction(quit_action); auto view_menu = menuBar()->addMenu("View"); - auto projection_menu = view_menu->addMenu("Projection"); + projection_menu = view_menu->addMenu("Projection"); projection_menu->addAction(perspective_action); + perspective_action->setIcon(QIcon(":/qt/icons/perspective.png")); projection_menu->addAction(orthographic_action); + orthographic_action->setIcon(QIcon(":/qt/icons/orthographic.png")); auto projections = new QActionGroup(projection_menu); for (auto p : {perspective_action, orthographic_action}) { @@ -114,12 +146,17 @@ Window::Window(QWidget *parent) : QObject::connect(projections, &QActionGroup::triggered, this, &Window::on_projection); - auto draw_menu = view_menu->addMenu("Draw Mode"); + draw_menu = view_menu->addMenu("Draw Mode"); draw_menu->addAction(shaded_action); draw_menu->addAction(wireframe_action); draw_menu->addAction(surfaceangle_action); + draw_menu->addAction(meshlight_action); + shaded_action->setIcon(QIcon(":/qt/icons/sphere_shader1.png")); + wireframe_action->setIcon(QIcon(":/qt/icons/sphere_shader2.png")); + surfaceangle_action->setIcon(QIcon(":/qt/icons/sphere_shader3.png")); + meshlight_action->setIcon(QIcon(":/qt/icons/sphere_shader4.png")); auto drawModes = new QActionGroup(draw_menu); - for (auto p : {shaded_action, wireframe_action, surfaceangle_action}) + for (auto p : {shaded_action, wireframe_action, surfaceangle_action, meshlight_action}) { drawModes->addAction(p); p->setCheckable(true); @@ -127,39 +164,155 @@ Window::Window(QWidget *parent) : drawModes->setExclusive(true); QObject::connect(drawModes, &QActionGroup::triggered, this, &Window::on_drawMode); + view_menu->addAction(drawModePrefs_action); + drawModePrefs_action->setShortcut(shortcutDrawModeSettings); + drawModePrefs_action->setIcon(QIcon(":/qt/icons/preferences-system.png")); + this->addAction(drawModePrefs_action); + drawModePrefs_action->setDisabled(true); view_menu->addAction(axes_action); axes_action->setCheckable(true); - QObject::connect(axes_action, &QAction::triggered, + axes_action->setShortcut(shortcutDrawAxes); + axes_action->setIcon(QIcon(":/qt/icons/axes.png")); + this->addAction(axes_action); + QObject::connect(axes_action, &QAction::toggled, this, &Window::on_drawAxes); view_menu->addAction(invert_zoom_action); invert_zoom_action->setCheckable(true); + invert_zoom_action->setIcon(QIcon(":/qt/icons/invert_zoom.png")); QObject::connect(invert_zoom_action, &QAction::triggered, this, &Window::on_invertZoom); view_menu->addAction(resetTransformOnLoadAction); resetTransformOnLoadAction->setCheckable(true); + resetTransformOnLoadAction->setIcon(QIcon(":/qt/icons/reset_rotation_on_load.png")); QObject::connect(resetTransformOnLoadAction, &QAction::triggered, this, &Window::on_resetTransformOnLoad); view_menu->addAction(hide_menuBar_action); - hide_menuBar_action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C); + hide_menuBar_action->setShortcut(shortcutHideMenuBar); hide_menuBar_action->setCheckable(true); QObject::connect(hide_menuBar_action, &QAction::toggled, this, &Window::on_hide_menuBar); + // To have the shortcut work without the menu this->addAction(hide_menuBar_action); view_menu->addAction(fullscreen_action); - fullscreen_action->setShortcut(Qt::Key_F11); + fullscreen_action->setShortcut(shortcutFullscreen); + fullscreen_action->setIcon(QIcon(":/qt/icons/view-fullscreen.png")); fullscreen_action->setCheckable(true); QObject::connect(fullscreen_action, &QAction::toggled, this, &Window::on_fullscreen); this->addAction(fullscreen_action); + QMenu *resolutionMenu = view_menu->addMenu("Set Viewport Size"); + resolutionMenu->setIcon(QIcon(":/qt/icons/resolution_1_32.png")); + resolutionMenu->menuAction()->setIconVisibleInMenu(true); + QActionGroup* groupResolution = new QActionGroup(resolutionMenu); + + QAction *quatreTiers = new QAction("-- 4:3",this); + quatreTiers->setDisabled(true); + resolutionMenu->addAction(quatreTiers); + QAction *setResolution0Action = new QAction("640 x 480 (VGA)",this); + resolutionMenu->addAction(setResolution0Action); + groupResolution->addAction(setResolution0Action); + + QAction *setResolution1Action = new QAction("768 x 576 (PAL)",this); + resolutionMenu->addAction(setResolution1Action); + groupResolution->addAction(setResolution1Action); + + QAction *setResolution2Action = new QAction("800 x 600 (SVGA)",this); + resolutionMenu->addAction(setResolution2Action); + groupResolution->addAction(setResolution2Action); + + QAction *setResolution3Action = new QAction("1024 x 768 (XGA)",this); + resolutionMenu->addAction(setResolution3Action); + groupResolution->addAction(setResolution3Action); + + QAction *seizeNeuf = new QAction("-- 16:9",this); + seizeNeuf->setDisabled(true); + resolutionMenu->addAction(seizeNeuf); + + QAction *setResolution4Action = new QAction("800 x 480 (WVGA)",this); + resolutionMenu->addAction(setResolution4Action); + groupResolution->addAction(setResolution4Action); + + QAction *setResolution5Action = new QAction("1024 x 576 (16:9 PAL)",this); + resolutionMenu->addAction(setResolution5Action); + groupResolution->addAction(setResolution5Action); + + QAction *setResolution6Action = new QAction("1280 x 720 (HD720)",this); + resolutionMenu->addAction(setResolution6Action); + groupResolution->addAction(setResolution6Action); + + resolutionMenu->addSeparator(); + +// QAction *setCustomResolutionAction = new QAction("Custom Resolution",this); +// //connect(setCustomResolutionAction,SIGNAL(triggered(bool)),this,SLOT(setCustomResolution())); +// resolutionMenu->addAction(setCustomResolutionAction); + + connect(groupResolution,SIGNAL(triggered(QAction*)),this,SLOT(setViewportSize(QAction*))); auto help_menu = menuBar()->addMenu("Help"); help_menu->addAction(about_action); + // Toolbar + // First group + windowToolBar = new QToolBar; + windowToolBar->addAction(quit_action); + windowToolBar->addAction(open_action); + windowToolBar->addAction(reload_action); + windowToolBar->addAction(autoreload_action); + + // preferences button here + windowToolBar->addSeparator(); + + // Second group + projectionButton = new QToolButton; + projectionButton->setPopupMode(QToolButton::InstantPopup); + projectionButton->setMenu(projection_menu); + projectionButton->setFocusPolicy(Qt::NoFocus); // we do not want the button to have keyboard focus + windowToolBar->addWidget(projectionButton); + + shaderButton = new QToolButton; + shaderButton->setPopupMode(QToolButton::InstantPopup); + shaderButton->setMenu(draw_menu); + shaderButton->setFocusPolicy(Qt::NoFocus); // we do not want the button to have keyboard focus + windowToolBar->addWidget(shaderButton); + windowToolBar->addAction(drawModePrefs_action); + + windowToolBar->addAction(axes_action); + windowToolBar->addAction(invert_zoom_action); + windowToolBar->addAction(resetTransformOnLoadAction); + + windowToolBar->addSeparator(); + // Third group + + QToolButton* viewportSizeButton = new QToolButton; + viewportSizeButton->setPopupMode(QToolButton::InstantPopup); + viewportSizeButton->setMenu(resolutionMenu); + viewportSizeButton->setIcon(resolutionMenu->icon()); + viewportSizeButton->setToolTip(resolutionMenu->title()); + viewportSizeButton->setFocusPolicy(Qt::NoFocus); // we do not want the button to have keyboard focus + windowToolBar->addWidget(viewportSizeButton); + + windowToolBar->addAction(save_screenshot_action); + windowToolBar->addAction(fullscreen_action); + + + + + // reset view here + // select views here + // slect shader here + // select gl size here + + + + + + + this->addToolBar(windowToolBar); load_persist_settings(); } @@ -180,13 +333,16 @@ void Window::load_persist_settings(){ axes_action->setChecked(draw_axes); QString projection = settings.value(PROJECTION_KEY, "perspective").toString(); + QAction* currentProjection; if(projection == "perspective"){ canvas->view_perspective(Canvas::P_PERSPECTIVE, false); - perspective_action->setChecked(true); + currentProjection = perspective_action; }else{ canvas->view_perspective(Canvas::P_ORTHOGRAPHIC, false); - orthographic_action->setChecked(true); + currentProjection = orthographic_action; } + currentProjection->setChecked(true); + on_projection(currentProjection); DrawMode draw_mode = (DrawMode)settings.value(DRAW_MODE_KEY, DRAWMODECOUNT).toInt(); @@ -194,12 +350,35 @@ void Window::load_persist_settings(){ { draw_mode = shaded; } - canvas->set_drawMode(draw_mode); - QAction* (dm_acts[]) = {shaded_action, wireframe_action, surfaceangle_action}; + dm_acts = {shaded_action, wireframe_action, surfaceangle_action, meshlight_action}; dm_acts[draw_mode]->setChecked(true); + on_drawMode(dm_acts[draw_mode]); + + // menu bar + bool hideMenu = settings.value(HIDE_MENU_BAR, false).toBool(); + hide_menuBar_action->blockSignals(true); + hide_menuBar_action->setChecked(hideMenu); + on_hide_menuBar(); + hide_menuBar_action->blockSignals(false); resize(600, 400); restoreGeometry(settings.value(WINDOW_GEOM_KEY).toByteArray()); + if (this->isFullScreen()) { + fullscreen_action->blockSignals(true); + fullscreen_action->setChecked(true); + fullscreen_action->blockSignals(false); + } + } + +void Window::on_drawModePrefs() { + // For now only one draw mode has settings + // when settings for other draw mode will be available + // we will need to check the current mode + if (meshlightprefs->isVisible()) { + meshlightprefs->hide(); + } else { + meshlightprefs->show(); + } } void Window::on_open() @@ -290,25 +469,42 @@ void Window::on_projection(QAction* proj) canvas->view_perspective(Canvas::P_ORTHOGRAPHIC, true); QSettings().setValue(PROJECTION_KEY, "orthographic"); } + projection_menu->setIcon(proj->icon()); + projectionButton->setIcon(proj->icon()); + projectionButton->setToolTip(QString("%1 : %2").arg(projection_menu->title()).arg(proj->toolTip())); } void Window::on_drawMode(QAction* act) { + // On mode change hide prefs first + meshlightprefs->hide(); + DrawMode mode; if (act == shaded_action) { + drawModePrefs_action->setEnabled(false); mode = shaded; } else if (act == wireframe_action) { + drawModePrefs_action->setEnabled(false); mode = wireframe; } - else + else if (act == surfaceangle_action) { + drawModePrefs_action->setEnabled(false); mode = surfaceangle; } + else if (act == meshlight_action) + { + drawModePrefs_action->setEnabled(true); + mode = meshlight; + } canvas->set_drawMode(mode); QSettings().setValue(DRAW_MODE_KEY, mode); + draw_menu->setIcon(act->icon()); + shaderButton->setIcon(act->icon()); + shaderButton->setToolTip(QString("%1 : %2").arg(draw_menu->title()).arg(act->toolTip())); } void Window::on_drawAxes(bool d) @@ -399,6 +595,9 @@ void Window::on_save_screenshot() void Window::on_hide_menuBar() { menuBar()->setVisible(!hide_menuBar_action->isChecked()); + windowToolBar->setVisible(!hide_menuBar_action->isChecked()); + QSettings settings; + settings.setValue(HIDE_MENU_BAR,hide_menuBar_action->isChecked()); } void Window::rebuild_recent_files() @@ -642,11 +841,19 @@ void Window::keyPressEvent(QKeyEvent* event) { load_next(); return; - } - else if (event->key() == Qt::Key_Escape) - { - hide_menuBar_action->setChecked(false); + } else if (event->key() == Qt::Key_Up) { + cycleShader(true); + return; + } else if (event->key() == Qt::Key_Down) { + cycleShader(false); + return; + } else if (event->key() == Qt::Key_Escape && !menuBar()->isVisible()) { // this is if user did not noticed the hide menu key + hide_menuBar_action->toggle(); return; + } else if (event->key() == Qt::Key_W) { + if (dm_acts.at(getCurrentShader()) == meshlight_action) { + meshlightprefs->toggleUseWire(); + } } QMainWindow::keyPressEvent(event); @@ -659,3 +866,46 @@ void Window::on_fullscreen() { this->showNormal(); } } + +int Window::getCurrentShader() { + int shadeNumber = dm_acts.size(); + int current = 0; + for (int i=0; iisChecked()) { + current = i; + break; + } + } + return current; +} + +void Window::cycleShader(bool up) { + int current = getCurrentShader(); + int updown = up ? 1 : -1; + int nextS = (current + updown) % dm_acts.size(); + nextS = nextS < 0 ? dm_acts.size() - 1 : nextS; + dm_acts.at(nextS)->setChecked(true); + on_drawMode(dm_acts.at(nextS)); +} + + +// Resize the widget giving the canvas dimension +// Useful for screenshot of given size. +void Window::setCanvasSize(int w, int h) { + if (this->isFullScreen()) { + fullscreen_action->toggle(); + } + int dw = this->size().width() - canvas->size().width(); + int dh = this->size().height() - canvas->size().height(); + this->resize(w + dw, h + dh); +} + +void Window::setViewportSize(QAction* act) { + QString t = act->text(); + QRegExp rx = QRegExp("^\\s*(\\d+).+(\\d+).*"); + rx.indexIn(t); + QStringList desc = rx.capturedTexts(); + int w = desc.at(1).toInt(); + int h = desc.at(2).toInt(); + setCanvasSize(w, h); +} diff --git a/src/window.h b/src/window.h index 3efbeadd..31e5c130 100644 --- a/src/window.h +++ b/src/window.h @@ -5,8 +5,10 @@ #include #include #include +#include class Canvas; +class ShaderLightPrefs; class Window : public QMainWindow { @@ -16,6 +18,9 @@ class Window : public QMainWindow bool load_stl(const QString& filename, bool is_reload=false); bool load_prev(void); bool load_next(void); + int getCurrentShader(); + void cycleShader(bool); + void setCanvasSize(int w, int h); protected: void dragEnterEvent(QDragEnterEvent* event) override; @@ -51,6 +56,8 @@ private slots: void on_save_screenshot(); void on_fullscreen(); void on_hide_menuBar(); + void on_drawModePrefs(); + void setViewportSize(QAction* act); private: void rebuild_recent_files(); @@ -67,6 +74,8 @@ private slots: QAction* const shaded_action; QAction* const wireframe_action; QAction* const surfaceangle_action; + QAction* const meshlight_action; + QAction* const drawModePrefs_action; QAction* const axes_action; QAction* const invert_zoom_action; QAction* const reload_action; @@ -75,8 +84,15 @@ private slots: QAction* const hide_menuBar_action; QAction* const fullscreen_action; QAction* const resetTransformOnLoadAction; + QAction* const setGLSizeAction; QMenu* const recent_files; + QMenu* draw_menu; + QToolButton* shaderButton; + QToolBar* windowToolBar; + QMenu* projection_menu; + QToolButton* projectionButton; + QActionGroup* const recent_files_group; QAction* const recent_files_clear_action; const static int MAX_RECENT_FILES=8; @@ -88,6 +104,17 @@ private slots: const static QString DRAW_MODE_KEY; const static QString WINDOW_GEOM_KEY; const static QString RESET_TRANSFORM_ON_LOAD_KEY; + const static QString HIDE_MENU_BAR; + + + const static QKeySequence shortcutOpen; + const static QKeySequence shortcutReload; + const static QKeySequence shortcutScreenshot; + const static QKeySequence shortcutQuit; + const static QKeySequence shortcutDrawModeSettings; + const static QKeySequence shortcutDrawAxes; + const static QKeySequence shortcutHideMenuBar; + const static QKeySequence shortcutFullscreen; QString current_file; QString lookup_folder; @@ -96,6 +123,9 @@ private slots: QFileSystemWatcher* watcher; Canvas* canvas; + + ShaderLightPrefs* meshlightprefs; + QList dm_acts; }; #endif // WINDOW_H diff --git a/xdg/README.txt b/xdg/README.txt new file mode 100644 index 00000000..7aad2b0f --- /dev/null +++ b/xdg/README.txt @@ -0,0 +1,23 @@ +Linux : +----------- +desktop file and application icons installation. +This tells the system that fstl knows to open stl files and allow stl to +be launched using windows key. + +Install : +./xdg_install.sh fstl + +Uninstall : +./xdg_uninstall.sh fstl + +if runned as regular user this will install locally in : + $HOME/.local/share/mime/ + $HOME/.local/share/applications/ + $HOME/.local/share/icons/ + +if runned as root this will install system-wide in : + /usr/share/mime + /usr/share/applications + /usr/share/icons + +Third script xdg_package_install.sh is to be used when building deb or rpm package. diff --git a/xdg/fstlapp-fstl.desktop b/xdg/fstlapp-fstl.desktop new file mode 100644 index 00000000..b4e93c5b --- /dev/null +++ b/xdg/fstlapp-fstl.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=fstl +GenericName=Fast STL Viewer +Exec=fstl %U +Terminal=false +Icon=fstlapp-fstl +Type=Application +MimeType=model/stl; +Categories=Utility; diff --git a/xdg/icons/fstlapp-fstl_128x128.png b/xdg/icons/fstlapp-fstl_128x128.png new file mode 100644 index 00000000..0c312003 Binary files /dev/null and b/xdg/icons/fstlapp-fstl_128x128.png differ diff --git a/xdg/icons/fstlapp-fstl_16x16.png b/xdg/icons/fstlapp-fstl_16x16.png new file mode 100644 index 00000000..d53992e2 Binary files /dev/null and b/xdg/icons/fstlapp-fstl_16x16.png differ diff --git a/xdg/icons/fstlapp-fstl_22x22.png b/xdg/icons/fstlapp-fstl_22x22.png new file mode 100644 index 00000000..40e760eb Binary files /dev/null and b/xdg/icons/fstlapp-fstl_22x22.png differ diff --git a/xdg/icons/fstlapp-fstl_256x256.png b/xdg/icons/fstlapp-fstl_256x256.png new file mode 100644 index 00000000..4235dc58 Binary files /dev/null and b/xdg/icons/fstlapp-fstl_256x256.png differ diff --git a/xdg/icons/fstlapp-fstl_32x32.png b/xdg/icons/fstlapp-fstl_32x32.png new file mode 100644 index 00000000..8bab67ea Binary files /dev/null and b/xdg/icons/fstlapp-fstl_32x32.png differ diff --git a/xdg/icons/fstlapp-fstl_48x48.png b/xdg/icons/fstlapp-fstl_48x48.png new file mode 100644 index 00000000..851d8bd4 Binary files /dev/null and b/xdg/icons/fstlapp-fstl_48x48.png differ diff --git a/xdg/icons/fstlapp-fstl_64x64.png b/xdg/icons/fstlapp-fstl_64x64.png new file mode 100644 index 00000000..0f83e6bf Binary files /dev/null and b/xdg/icons/fstlapp-fstl_64x64.png differ diff --git a/xdg/xdg_install.sh b/xdg/xdg_install.sh new file mode 100755 index 00000000..b9d59b20 --- /dev/null +++ b/xdg/xdg_install.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# This script will install mimetypes, icons and desktop file, +# it takes a name in argument +# +# if runned as regular user this will install locally in : +# $HOME/.local/share/mime/ +# $HOME/.local/share/applications/ +# $HOME/.local/share/icons/ +# +# if runned as root this will install system-wide in : +# /usr/share/mime +# /usr/share/applications +# /usr/share/icons + +if [ $# != 1 ]; then + echo "You must provide an application name" + exit 1 +fi + +name=$1 + +# echo "Installing mimetypes" +# xdg-mime install fstlapp-$name-mimetypes.xml + +echo "Installing desktop file" +xdg-desktop-menu install fstlapp-$name.desktop + +echo "Installing apps icons" +iclist="fstlapp-$name" +for im in $iclist +do + xdg-icon-resource install --theme hicolor --context apps --size 16 icons/${im}_16x16.png $im + xdg-icon-resource install --theme hicolor --context apps --size 22 icons/${im}_22x22.png $im + xdg-icon-resource install --theme hicolor --context apps --size 32 icons/${im}_32x32.png $im + xdg-icon-resource install --theme hicolor --context apps --size 48 icons/${im}_48x48.png $im + xdg-icon-resource install --theme hicolor --context apps --size 64 icons/${im}_64x64.png $im + xdg-icon-resource install --theme hicolor --context apps --size 128 icons/${im}_128x128.png $im + xdg-icon-resource install --theme hicolor --context apps --size 256 icons/${im}_256x256.png $im +done + +# echo "Installing mimetypes icons" +# iclist="`cat fstlapp-$name-mimetypes.xml | grep "icon name" | sed 's/^.*"\(.*\)".*$/\1/'`" +# for im in $iclist +# do +# xdg-icon-resource install --theme hicolor --context mimetypes --size 16 icons/${im}_16x16.png $im +# xdg-icon-resource install --theme hicolor --context mimetypes --size 22 icons/${im}_22x22.png $im +# xdg-icon-resource install --theme hicolor --context mimetypes --size 32 icons/${im}_32x32.png $im +# xdg-icon-resource install --theme hicolor --context mimetypes --size 48 icons/${im}_48x48.png $im +# xdg-icon-resource install --theme hicolor --context mimetypes --size 64 icons/${im}_64x64.png $im +# done + diff --git a/xdg/xdg_package_install.sh b/xdg/xdg_package_install.sh new file mode 100755 index 00000000..7e19a420 --- /dev/null +++ b/xdg/xdg_package_install.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +# For a package installation (rpm or deb), we must proceed a different way +# This script takes two arguments, the first one is the installation +# prefix and the second is the name + +if [ $# != 2 ]; then + echo "You must provide two arguments" + exit 1 +fi + +base=$1 +name=$2 + +# echo "Drop mimetypes file in /usr/share/mime/packages/" +# mkdir -p $base/usr/share/mime/packages/ +# cp fstlapp-$name-mimetypes.xml $base/usr/share/mime/packages/ + +echo "Drop desktop file in /usr/share/applications/" +mkdir -p $base/usr/share/applications/ +cp fstlapp-$name.desktop $base/usr/share/applications/ + +slist="16 22 32 48 64 128 256" +echo "Installing apps icons" +iclist="fstlapp-$name" +for im in $iclist +do + for s in $slist + do + mkdir -p $base/usr/share/icons/hicolor/${s}x${s}/apps + cp icons/${im}_${s}x${s}.png $base/usr/share/icons/hicolor/${s}x${s}/apps/$im.png + done +done + +# echo "Installing mimetypes icons" +# iclist="`cat fstlapp-$name-mimetypes.xml | grep "icon name" | sed 's/^.*"\(.*\)".*$/\1/'`" +# for im in $iclist +# do +# for s in $slist +# do +# mkdir -p $base/usr/share/icons/hicolor/${s}x${s}/mimetypes +# cp icons/${im}_${s}x${s}.png $base/usr/share/icons/hicolor/${s}x${s}/mimetypes/$im.png +# done +# done + +# +# Put this in the post installation and post uninstallation scripts +# +#echo "Updating mime database" +#update-mime-database /usr/share/mim +# +#echo "Updating desktop database" +#update-desktop-database diff --git a/xdg/xdg_uninstall.sh b/xdg/xdg_uninstall.sh new file mode 100755 index 00000000..1dd7ef4f --- /dev/null +++ b/xdg/xdg_uninstall.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# This script will uninstall mimetypes, icons and desktop file +# +# if runned as regular user this will uninstall locally from : +# $HOME/.local/share/mime/ +# $HOME/.local/share/applications/ +# $HOME/.local/share/icons/ +# +# if runned as root this will uninstall system-wide from : +# /usr/share/mime +# /usr/share/applications +# /usr/share/icons + +if [ $# != 1 ]; then + echo "You must provide a name" + exit 1 +fi + +name=$1 + +# echo "Uninstalling mimetypes" +# xdg-mime uninstall fstlapp-$name-mimetypes.xml + +echo "Uninstalling desktop file" +xdg-desktop-menu uninstall fstlapp-$name.desktop + +echo "Uninstalling apps icons" +iclist="fstlapp-$name" +for im in $iclist +do + xdg-icon-resource uninstall --theme hicolor --context apps --size 16 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 22 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 32 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 48 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 64 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 128 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 256 $im +done + +# echo "Uninstalling mimetypes icons" +# iclist="`cat fstlapp-$name-mimetypes.xml | grep "icon name" | sed 's/^.*"\(.*\)".*$/\1/'`" +# for im in $iclist +# do +# xdg-icon-resource uninstall --theme hicolor --context mimetypes --size 16 $im +# xdg-icon-resource uninstall --theme hicolor --context mimetypes --size 22 $im +# xdg-icon-resource uninstall --theme hicolor --context mimetypes --size 32 $im +# xdg-icon-resource uninstall --theme hicolor --context mimetypes --size 48 $im +# xdg-icon-resource uninstall --theme hicolor --context mimetypes --size 64 $im +# done