implement text overlay support

This commit is contained in:
ZappoMan 2014-02-15 21:13:44 -08:00
parent 0a9f9a7c7a
commit ef11865d24
12 changed files with 382 additions and 122 deletions

View file

@ -44,6 +44,19 @@ for (s = 0; s < numberOfSwatches; s++) {
});
}
var text = Overlays.addOverlay("text", {
x: 200,
y: 100,
width: 150,
height: 50,
backgroundColor: { red: 0, green: 0, blue: 0},
textColor: { red: 255, green: 0, blue: 0},
topMargin: 4,
leftMargin: 4,
alpha: 0.7,
text: "Here is some text.\nAnd a second line."
});
var toolA = Overlays.addOverlay("image", {
x: 100,
y: 100,
@ -97,6 +110,7 @@ function scriptEnding() {
}
Overlays.deleteOverlay(thumb);
Overlays.deleteOverlay(slider);
Overlays.deleteOverlay(text);
}
Script.scriptEnding.connect(scriptEnding);
@ -132,10 +146,14 @@ function mouseMoveEvent(event) {
}
}
function mousePressEvent(event) {
var clickedText = false;
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
if (clickedOverlay == thumb) {
movingSlider = true;
thumbClickOffsetX = event.x - thumbX;
} else if (clickedOverlay == text) {
Overlays.editOverlay(text, { text: "you clicked here:\n " + event.x + "," + event.y } );
clickedText = true;
} else {
for (s = 0; s < numberOfSwatches; s++) {
if (clickedOverlay == swatches[s]) {
@ -145,6 +163,9 @@ function mousePressEvent(event) {
}
}
}
if (!clickedText) {
Overlays.editOverlay(text, { text: "you didn't click here" } );
}
}
function mouseReleaseEvent(event) {

View file

@ -19,15 +19,6 @@
#include <glm/gtc/quaternion.hpp>
#include <QSettings>
// the standard sans serif font family
#define SANS_FONT_FAMILY "Helvetica"
// the standard mono font family
#define MONO_FONT_FAMILY "Courier"
// the Inconsolata font family
#define INCONSOLATA_FONT_FAMILY "Inconsolata"
void eulerToOrthonormals(glm::vec3 * angles, glm::vec3 * fwd, glm::vec3 * left, glm::vec3 * up);
float azimuth_to(glm::vec3 head_pos, glm::vec3 source_pos);

View file

@ -14,23 +14,13 @@
#include <SharedUtil.h>
ImageOverlay::ImageOverlay() :
_parent(NULL),
_textureID(0),
_alpha(DEFAULT_ALPHA),
_backgroundColor(DEFAULT_BACKGROUND_COLOR),
_visible(true),
_renderImage(false),
_textureBound(false),
_wantClipFromImage(false)
{
}
void ImageOverlay::init(QGLWidget* parent) {
qDebug() << "ImageOverlay::init() parent=" << parent;
_parent = parent;
}
ImageOverlay::~ImageOverlay() {
if (_parent && _textureID) {
// do we need to call this?
@ -114,44 +104,9 @@ void ImageOverlay::render() {
}
}
// TODO: handle only setting the included values...
void ImageOverlay::setProperties(const QScriptValue& properties) {
//qDebug() << "ImageOverlay::setProperties()... properties=" << &properties;
QScriptValue bounds = properties.property("bounds");
if (bounds.isValid()) {
QRect boundsRect;
boundsRect.setX(bounds.property("x").toVariant().toInt());
boundsRect.setY(bounds.property("y").toVariant().toInt());
boundsRect.setWidth(bounds.property("width").toVariant().toInt());
boundsRect.setHeight(bounds.property("height").toVariant().toInt());
setBounds(boundsRect);
} else {
QRect oldBounds = getBounds();
QRect newBounds = oldBounds;
if (properties.property("x").isValid()) {
newBounds.setX(properties.property("x").toVariant().toInt());
} else {
newBounds.setX(oldBounds.x());
}
if (properties.property("y").isValid()) {
newBounds.setY(properties.property("y").toVariant().toInt());
} else {
newBounds.setY(oldBounds.y());
}
if (properties.property("width").isValid()) {
newBounds.setWidth(properties.property("width").toVariant().toInt());
} else {
newBounds.setWidth(oldBounds.width());
}
if (properties.property("height").isValid()) {
newBounds.setHeight(properties.property("height").toVariant().toInt());
} else {
newBounds.setHeight(oldBounds.height());
}
setBounds(newBounds);
//qDebug() << "set bounds to " << getBounds();
}
Overlay::setProperties(properties);
QScriptValue subImageBounds = properties.property("subImage");
if (subImageBounds.isValid()) {
QRect oldSubImageRect = _fromImage;
@ -183,26 +138,6 @@ void ImageOverlay::setProperties(const QScriptValue& properties) {
if (imageURL.isValid()) {
setImageURL(imageURL.toVariant().toString());
}
QScriptValue color = properties.property("backgroundColor");
if (color.isValid()) {
QScriptValue red = color.property("red");
QScriptValue green = color.property("green");
QScriptValue blue = color.property("blue");
if (red.isValid() && green.isValid() && blue.isValid()) {
_backgroundColor.red = red.toVariant().toInt();
_backgroundColor.green = green.toVariant().toInt();
_backgroundColor.blue = blue.toVariant().toInt();
}
}
if (properties.property("alpha").isValid()) {
setAlpha(properties.property("alpha").toVariant().toFloat());
}
if (properties.property("visible").isValid()) {
setVisible(properties.property("visible").toVariant().toBool());
}
}

View file

@ -22,43 +22,24 @@
#include <SharedUtil.h>
const xColor DEFAULT_BACKGROUND_COLOR = { 255, 255, 255 };
const float DEFAULT_ALPHA = 0.7f;
#include "Overlay.h"
class ImageOverlay : QObject {
class ImageOverlay : public Overlay {
Q_OBJECT
public:
ImageOverlay();
~ImageOverlay();
void init(QGLWidget* parent);
void render();
virtual void render();
//public slots:
// getters
bool getVisible() const { return _visible; }
int getX() const { return _bounds.x(); }
int getY() const { return _bounds.y(); }
int getWidth() const { return _bounds.width(); }
int getHeight() const { return _bounds.height(); }
const QRect& getBounds() const { return _bounds; }
const QRect& getClipFromSource() const { return _fromImage; }
const xColor& getBackgroundColor() const { return _backgroundColor; }
float getAlpha() const { return _alpha; }
const QUrl& getImageURL() const { return _imageURL; }
// setters
void setVisible(bool visible) { _visible = visible; }
void setX(int x) { _bounds.setX(x); }
void setY(int y) { _bounds.setY(y); }
void setWidth(int width) { _bounds.setWidth(width); }
void setHeight(int height) { _bounds.setHeight(height); }
void setBounds(const QRect& bounds) { _bounds = bounds; }
void setClipFromSource(const QRect& bounds) { _fromImage = bounds; _wantClipFromImage = true; }
void setBackgroundColor(const xColor& color) { _backgroundColor = color; }
void setAlpha(float alpha) { _alpha = alpha; }
void setImageURL(const QUrl& url);
void setProperties(const QScriptValue& properties);
virtual void setProperties(const QScriptValue& properties);
private slots:
void replyFinished(QNetworkReply* reply); // we actually want to hide this...
@ -66,14 +47,9 @@ private slots:
private:
QUrl _imageURL;
QGLWidget* _parent;
QImage _textureImage;
GLuint _textureID;
QRect _bounds; // where on the screen to draw
QRect _fromImage; // where from in the image to sample
float _alpha;
xColor _backgroundColor;
bool _visible; // should the overlay be drawn at all
bool _renderImage; // is there an image associated with this overlay, or is it just a colored rectangle
bool _textureBound; // has the texture been bound
bool _wantClipFromImage;

View file

@ -0,0 +1,88 @@
//
// Overlay.cpp
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#include "Overlay.h"
#include <QSvgRenderer>
#include <QPainter>
#include <QGLWidget>
#include <SharedUtil.h>
Overlay::Overlay() :
_parent(NULL),
_alpha(DEFAULT_ALPHA),
_backgroundColor(DEFAULT_BACKGROUND_COLOR),
_visible(true)
{
}
void Overlay::init(QGLWidget* parent) {
_parent = parent;
}
Overlay::~Overlay() {
}
void Overlay::setProperties(const QScriptValue& properties) {
QScriptValue bounds = properties.property("bounds");
if (bounds.isValid()) {
QRect boundsRect;
boundsRect.setX(bounds.property("x").toVariant().toInt());
boundsRect.setY(bounds.property("y").toVariant().toInt());
boundsRect.setWidth(bounds.property("width").toVariant().toInt());
boundsRect.setHeight(bounds.property("height").toVariant().toInt());
setBounds(boundsRect);
} else {
QRect oldBounds = getBounds();
QRect newBounds = oldBounds;
if (properties.property("x").isValid()) {
newBounds.setX(properties.property("x").toVariant().toInt());
} else {
newBounds.setX(oldBounds.x());
}
if (properties.property("y").isValid()) {
newBounds.setY(properties.property("y").toVariant().toInt());
} else {
newBounds.setY(oldBounds.y());
}
if (properties.property("width").isValid()) {
newBounds.setWidth(properties.property("width").toVariant().toInt());
} else {
newBounds.setWidth(oldBounds.width());
}
if (properties.property("height").isValid()) {
newBounds.setHeight(properties.property("height").toVariant().toInt());
} else {
newBounds.setHeight(oldBounds.height());
}
setBounds(newBounds);
//qDebug() << "set bounds to " << getBounds();
}
QScriptValue color = properties.property("backgroundColor");
if (color.isValid()) {
QScriptValue red = color.property("red");
QScriptValue green = color.property("green");
QScriptValue blue = color.property("blue");
if (red.isValid() && green.isValid() && blue.isValid()) {
_backgroundColor.red = red.toVariant().toInt();
_backgroundColor.green = green.toVariant().toInt();
_backgroundColor.blue = blue.toVariant().toInt();
}
}
if (properties.property("alpha").isValid()) {
setAlpha(properties.property("alpha").toVariant().toFloat());
}
if (properties.property("visible").isValid()) {
setVisible(properties.property("visible").toVariant().toBool());
}
}

View file

@ -0,0 +1,64 @@
//
// Overlay.h
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__Overlay__
#define __interface__Overlay__
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <QRect>
#include <QScriptValue>
#include <QString>
#include <SharedUtil.h> // for xColor
const xColor DEFAULT_BACKGROUND_COLOR = { 255, 255, 255 };
const float DEFAULT_ALPHA = 0.7f;
class Overlay : public QObject {
Q_OBJECT
public:
Overlay();
~Overlay();
void init(QGLWidget* parent);
virtual void render() = 0;
// getters
bool getVisible() const { return _visible; }
int getX() const { return _bounds.x(); }
int getY() const { return _bounds.y(); }
int getWidth() const { return _bounds.width(); }
int getHeight() const { return _bounds.height(); }
const QRect& getBounds() const { return _bounds; }
const xColor& getBackgroundColor() const { return _backgroundColor; }
float getAlpha() const { return _alpha; }
// setters
void setVisible(bool visible) { _visible = visible; }
void setX(int x) { _bounds.setX(x); }
void setY(int y) { _bounds.setY(y); }
void setWidth(int width) { _bounds.setWidth(width); }
void setHeight(int height) { _bounds.setHeight(height); }
void setBounds(const QRect& bounds) { _bounds = bounds; }
void setBackgroundColor(const xColor& color) { _backgroundColor = color; }
void setAlpha(float alpha) { _alpha = alpha; }
virtual void setProperties(const QScriptValue& properties);
protected:
QGLWidget* _parent;
QRect _bounds; // where on the screen to draw
float _alpha;
xColor _backgroundColor;
bool _visible; // should the overlay be drawn at all
};
#endif /* defined(__interface__Overlay__) */

View file

@ -7,6 +7,9 @@
#include "Overlays.h"
#include "ImageOverlay.h"
#include "TextOverlay.h"
unsigned int Overlays::_nextOverlayID = 1;
@ -21,7 +24,7 @@ void Overlays::init(QGLWidget* parent) {
}
void Overlays::render() {
foreach(ImageOverlay* thisOverlay, _imageOverlays) {
foreach(Overlay* thisOverlay, _overlays) {
thisOverlay->render();
}
}
@ -36,36 +39,44 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope
ImageOverlay* thisOverlay = new ImageOverlay();
thisOverlay->init(_parent);
thisOverlay->setProperties(properties);
_imageOverlays[thisID] = thisOverlay;
_overlays[thisID] = thisOverlay;
} else if (type == "text") {
thisID = _nextOverlayID;
_nextOverlayID++;
TextOverlay* thisOverlay = new TextOverlay();
thisOverlay->init(_parent);
thisOverlay->setProperties(properties);
_overlays[thisID] = thisOverlay;
}
return thisID;
}
// TODO: make multi-threaded safe
bool Overlays::editOverlay(unsigned int id, const QScriptValue& properties) {
if (!_imageOverlays.contains(id)) {
if (!_overlays.contains(id)) {
return false;
}
ImageOverlay* thisOverlay = _imageOverlays[id];
Overlay* thisOverlay = _overlays[id];
thisOverlay->setProperties(properties);
return true;
}
// TODO: make multi-threaded safe
void Overlays::deleteOverlay(unsigned int id) {
if (_imageOverlays.contains(id)) {
_imageOverlays.erase(_imageOverlays.find(id));
if (_overlays.contains(id)) {
_overlays.erase(_overlays.find(id));
}
}
unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) {
QMapIterator<unsigned int, ImageOverlay*> i(_imageOverlays);
QMapIterator<unsigned int, Overlay*> i(_overlays);
i.toBack();
while (i.hasPrevious()) {
i.previous();
unsigned int thisID = i.key();
ImageOverlay* thisOverlay = i.value();
if (thisOverlay->getBounds().contains(point.x, point.y, false)) {
Overlay* thisOverlay = i.value();
if (thisOverlay->getVisible() && thisOverlay->getBounds().contains(point.x, point.y, false)) {
return thisID;
}
}

View file

@ -10,7 +10,7 @@
#include <QScriptValue>
#include "ImageOverlay.h"
#include "Overlay.h"
class Overlays : public QObject {
Q_OBJECT
@ -35,7 +35,7 @@ public slots:
unsigned int getOverlayAtPoint(const glm::vec2& point);
private:
QMap<unsigned int, ImageOverlay*> _imageOverlays;
QMap<unsigned int, Overlay*> _overlays;
static unsigned int _nextOverlayID;
QGLWidget* _parent;
};

View file

@ -0,0 +1,80 @@
//
// TextOverlay.cpp
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#include <QGLWidget>
#include <SharedUtil.h>
#include "TextOverlay.h"
#include "TextRenderer.h"
TextOverlay::TextOverlay() :
_leftMargin(DEFAULT_MARGIN),
_topMargin(DEFAULT_MARGIN)
{
}
TextOverlay::~TextOverlay() {
}
void TextOverlay::render() {
if (!_visible) {
return; // do nothing if we're not visible
}
const float MAX_COLOR = 255;
glColor4f(_backgroundColor.red / MAX_COLOR, _backgroundColor.green / MAX_COLOR, _backgroundColor.blue / MAX_COLOR, _alpha);
glBegin(GL_QUADS);
glVertex2f(_bounds.left(), _bounds.top());
glVertex2f(_bounds.right(), _bounds.top());
glVertex2f(_bounds.right(), _bounds.bottom());
glVertex2f(_bounds.left(), _bounds.bottom());
glEnd();
//TextRenderer(const char* family, int pointSize = -1, int weight = -1, bool italic = false,
// EffectType effect = NO_EFFECT, int effectThickness = 1);
TextRenderer textRenderer(SANS_FONT_FAMILY, 11, 50);
const int leftAdjust = -1; // required to make text render relative to left edge of bounds
const int topAdjust = -2; // required to make text render relative to top edge of bounds
int x = _bounds.left() + _leftMargin + leftAdjust;
int y = _bounds.top() + _topMargin + topAdjust;
glColor3f(1.0f, 1.0f, 1.0f);
QStringList lines = _text.split("\n");
int lineOffset = 0;
foreach(QString thisLine, lines) {
if (lineOffset == 0) {
lineOffset = textRenderer.calculateHeight(qPrintable(thisLine));
}
lineOffset += textRenderer.draw(x, y + lineOffset, qPrintable(thisLine));
const int lineGap = 2;
lineOffset += lineGap;
}
}
void TextOverlay::setProperties(const QScriptValue& properties) {
Overlay::setProperties(properties);
QScriptValue text = properties.property("text");
if (text.isValid()) {
setText(text.toVariant().toString());
}
if (properties.property("leftMargin").isValid()) {
setLeftMargin(properties.property("leftMargin").toVariant().toInt());
}
if (properties.property("topMargin").isValid()) {
setTopMargin(properties.property("topMargin").toVariant().toInt());
}
}

View file

@ -0,0 +1,58 @@
//
// TextOverlay.h
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__TextOverlay__
#define __interface__TextOverlay__
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <QImage>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QRect>
#include <QScriptValue>
#include <QString>
#include <QUrl>
#include <SharedUtil.h>
#include "Overlay.h"
const int DEFAULT_MARGIN = 10;
class TextOverlay : public Overlay {
Q_OBJECT
public:
TextOverlay();
~TextOverlay();
virtual void render();
// getters
const QString& getText() const { return _text; }
int getLeftMargin() const { return _leftMargin; }
int getTopMargin() const { return _topMargin; }
// setters
void setText(const QString& text) { _text = text; }
void setLeftMargin(int margin) { _leftMargin = margin; }
void setTopMargin(int margin) { _topMargin = margin; }
virtual void setProperties(const QScriptValue& properties);
private:
QString _text;
int _leftMargin;
int _topMargin;
};
#endif /* defined(__interface__TextOverlay__) */

View file

@ -8,6 +8,8 @@
#include <QFont>
#include <QPaintEngine>
#include <QtDebug>
#include <QString>
#include <QStringList>
#include "InterfaceConfig.h"
#include "TextRenderer.h"
@ -30,10 +32,25 @@ TextRenderer::~TextRenderer() {
glDeleteTextures(_allTextureIDs.size(), _allTextureIDs.constData());
}
void TextRenderer::draw(int x, int y, const char* str) {
int TextRenderer::calculateHeight(const char* str) {
int maxHeight = 0;
for (const char* ch = str; *ch != 0; ch++) {
const Glyph& glyph = getGlyph(*ch);
if (glyph.textureID() == 0) {
continue;
}
if (glyph.bounds().height() > maxHeight) {
maxHeight = glyph.bounds().height();
}
}
return maxHeight;
}
int TextRenderer::draw(int x, int y, const char* str) {
glEnable(GL_TEXTURE_2D);
int maxHeight = 0;
for (const char* ch = str; *ch != 0; ch++) {
const Glyph& glyph = getGlyph(*ch);
if (glyph.textureID() == 0) {
@ -41,19 +58,23 @@ void TextRenderer::draw(int x, int y, const char* str) {
continue;
}
if (glyph.bounds().height() > maxHeight) {
maxHeight = glyph.bounds().height();
}
glBindTexture(GL_TEXTURE_2D, glyph.textureID());
int left = x + glyph.bounds().x();
int right = x + glyph.bounds().x() + glyph.bounds().width();
int bottom = y + glyph.bounds().y();
int top = y + glyph.bounds().y() + glyph.bounds().height();
float scale = 1.0 / IMAGE_SIZE;
float ls = glyph.location().x() * scale;
float rs = (glyph.location().x() + glyph.bounds().width()) * scale;
float bt = glyph.location().y() * scale;
float tt = (glyph.location().y() + glyph.bounds().height()) * scale;
glBegin(GL_QUADS);
glTexCoord2f(ls, bt);
glVertex2f(left, bottom);
@ -64,12 +85,13 @@ void TextRenderer::draw(int x, int y, const char* str) {
glTexCoord2f(ls, tt);
glVertex2f(left, top);
glEnd();
x += glyph.width();
}
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
return maxHeight;
}
int TextRenderer::computeWidth(char ch)

View file

@ -20,6 +20,16 @@
// a special "character" that renders as a solid block
const char SOLID_BLOCK_CHAR = 127;
// the standard sans serif font family
#define SANS_FONT_FAMILY "Helvetica"
// the standard mono font family
#define MONO_FONT_FAMILY "Courier"
// the Inconsolata font family
#define INCONSOLATA_FONT_FAMILY "Inconsolata"
class Glyph;
class TextRenderer {
@ -33,7 +43,11 @@ public:
const QFontMetrics& metrics() const { return _metrics; }
void draw(int x, int y, const char* str);
// returns the height of the tallest character
int calculateHeight(const char* str);
// also returns the height of the tallest character
int draw(int x, int y, const char* str);
int computeWidth(char ch);
int computeWidth(const char* str);