You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
586 lines
14 KiB
586 lines
14 KiB
#include <tqbitmap.h>
|
|
#include <tqcheckbox.h>
|
|
#include <tqlabel.h>
|
|
#include <tqimage.h>
|
|
#include <tqpixmapcache.h>
|
|
#include <tqwhatsthis.h>
|
|
|
|
#include <kapplication.h>
|
|
#include <kcombobox.h>
|
|
#include <kconfig.h>
|
|
#include <knuminput.h>
|
|
#include <kpixmapeffect.h>
|
|
#include <kstandarddirs.h>
|
|
|
|
#include "slope.h"
|
|
|
|
Slope::Slope(TQRect rect, TQCanvas *canvas)
|
|
: TQCanvasRectangle(rect, canvas), type(KImageEffect::VerticalGradient), grade(4), reversed(false), color(TQColor("#327501"))
|
|
{
|
|
stuckOnGround = false;
|
|
showingInfo = false;
|
|
|
|
gradientKeys[KImageEffect::VerticalGradient] = "Vertical";
|
|
gradientKeys[KImageEffect::HorizontalGradient] = "Horizontal";
|
|
gradientKeys[KImageEffect::DiagonalGradient] = "Diagonal";
|
|
gradientKeys[KImageEffect::CrossDiagonalGradient] = "Opposite Diagonal";
|
|
gradientKeys[KImageEffect::EllipticGradient] = "Elliptic";
|
|
|
|
gradientI18nKeys[KImageEffect::VerticalGradient] = i18n("Vertical");
|
|
gradientI18nKeys[KImageEffect::HorizontalGradient] = i18n("Horizontal");
|
|
gradientI18nKeys[KImageEffect::DiagonalGradient] = i18n("Diagonal");
|
|
gradientI18nKeys[KImageEffect::CrossDiagonalGradient] = i18n("Opposite Diagonal");
|
|
gradientI18nKeys[KImageEffect::EllipticGradient] = i18n("Circular");
|
|
|
|
setZ(-50);
|
|
|
|
if (!TQPixmapCache::find("grass", grass))
|
|
{
|
|
grass.load(locate("appdata", "pics/grass.png"));
|
|
TQPixmapCache::insert("grass", grass);
|
|
}
|
|
|
|
point = new RectPoint(color.light(), this, canvas);
|
|
|
|
TQFont font(kapp->font());
|
|
font.setPixelSize(18);
|
|
text = new TQCanvasText(canvas);
|
|
text->setZ(99999.99);
|
|
text->setFont(font);
|
|
text->setColor(white);
|
|
|
|
editModeChanged(false);
|
|
hideInfo();
|
|
|
|
// this does updatePixmap
|
|
setGradient("Vertical");
|
|
}
|
|
|
|
bool Slope::terrainCollisions() const
|
|
{
|
|
// we are a terrain collision
|
|
return true;
|
|
}
|
|
|
|
void Slope::showInfo()
|
|
{
|
|
showingInfo = true;
|
|
Arrow *arrow = 0;
|
|
for (arrow = arrows.first(); arrow; arrow = arrows.next())
|
|
{
|
|
arrow->setZ(z() + .01);
|
|
arrow->setVisible(true);
|
|
}
|
|
text->tqsetVisible(true);
|
|
}
|
|
|
|
void Slope::hideInfo()
|
|
{
|
|
showingInfo = false;
|
|
Arrow *arrow = 0;
|
|
for (arrow = arrows.first(); arrow; arrow = arrows.next())
|
|
arrow->setVisible(false);
|
|
text->tqsetVisible(false);
|
|
}
|
|
|
|
void Slope::aboutToDie()
|
|
{
|
|
delete point;
|
|
clearArrows();
|
|
delete text;
|
|
}
|
|
|
|
void Slope::clearArrows()
|
|
{
|
|
Arrow *arrow = 0;
|
|
for (arrow = arrows.first(); arrow; arrow = arrows.next())
|
|
{
|
|
arrow->setVisible(false);
|
|
arrow->aboutToDie();
|
|
}
|
|
arrows.setAutoDelete(true);
|
|
arrows.clear();
|
|
arrows.setAutoDelete(false);
|
|
}
|
|
|
|
TQPtrList<TQCanvasItem> Slope::moveableItems() const
|
|
{
|
|
TQPtrList<TQCanvasItem> ret;
|
|
ret.append(point);
|
|
return ret;
|
|
}
|
|
|
|
void Slope::setGrade(double newGrade)
|
|
{
|
|
if (newGrade >= 0 && newGrade < 11)
|
|
{
|
|
grade = newGrade;
|
|
updatePixmap();
|
|
}
|
|
}
|
|
|
|
void Slope::setSize(int width, int height)
|
|
{
|
|
newSize(width, height);
|
|
}
|
|
|
|
void Slope::newSize(int width, int height)
|
|
{
|
|
if (type == KImageEffect::EllipticGradient)
|
|
{
|
|
TQCanvasRectangle::setSize(width, width);
|
|
// move point back to good spot
|
|
moveBy(0, 0);
|
|
|
|
if (game && game->isEditing())
|
|
game->updateHighlighter();
|
|
}
|
|
else
|
|
TQCanvasRectangle::setSize(width, height);
|
|
|
|
updatePixmap();
|
|
updateZ();
|
|
}
|
|
|
|
void Slope::moveBy(double dx, double dy)
|
|
{
|
|
TQCanvasRectangle::moveBy(dx, dy);
|
|
|
|
point->dontMove();
|
|
point->move(x() + width(), y() + height());
|
|
|
|
moveArrow();
|
|
updateZ();
|
|
}
|
|
|
|
void Slope::moveArrow()
|
|
{
|
|
int xavg = 0, yavg = 0;
|
|
TQPointArray r = areaPoints();
|
|
for (unsigned int i = 0; i < r.size(); ++i)
|
|
{
|
|
xavg += r[i].x();
|
|
yavg += r[i].y();
|
|
}
|
|
xavg /= r.size();
|
|
yavg /= r.size();
|
|
|
|
Arrow *arrow = 0;
|
|
for (arrow = arrows.first(); arrow; arrow = arrows.next())
|
|
arrow->move((double)xavg, (double)yavg);
|
|
|
|
if (showingInfo)
|
|
showInfo();
|
|
else
|
|
hideInfo();
|
|
|
|
text->move((double)xavg - text->boundingRect().width() / 2, (double)yavg - text->boundingRect().height() / 2);
|
|
}
|
|
|
|
void Slope::editModeChanged(bool changed)
|
|
{
|
|
point->tqsetVisible(changed);
|
|
moveBy(0, 0);
|
|
}
|
|
|
|
void Slope::updateZ(TQCanvasRectangle *vStrut)
|
|
{
|
|
const int area = (height() * width());
|
|
const int defaultz = -50;
|
|
|
|
double newZ = 0;
|
|
|
|
TQCanvasRectangle *rect = 0;
|
|
if (!stuckOnGround)
|
|
rect = vStrut? vStrut : onVStrut();
|
|
|
|
if (rect)
|
|
{
|
|
if (area > (rect->width() * rect->height()))
|
|
newZ = defaultz;
|
|
else
|
|
newZ = rect->z();
|
|
}
|
|
else
|
|
newZ = defaultz;
|
|
|
|
setZ(((double)1 / (area == 0? 1 : area)) + newZ);
|
|
}
|
|
|
|
void Slope::load(KConfig *cfg)
|
|
{
|
|
stuckOnGround = cfg->readBoolEntry("stuckOnGround", stuckOnGround);
|
|
grade = cfg->readDoubleNumEntry("grade", grade);
|
|
reversed = cfg->readBoolEntry("reversed", reversed);
|
|
|
|
// bypass updatePixmap which newSize normally does
|
|
TQCanvasRectangle::setSize(cfg->readNumEntry("width", width()), cfg->readNumEntry("height", height()));
|
|
updateZ();
|
|
|
|
TQString gradientType = cfg->readEntry("gradient", gradientKeys[type]);
|
|
setGradient(gradientType);
|
|
}
|
|
|
|
void Slope::save(KConfig *cfg)
|
|
{
|
|
cfg->writeEntry("reversed", reversed);
|
|
cfg->writeEntry("width", width());
|
|
cfg->writeEntry("height", height());
|
|
cfg->writeEntry("gradient", gradientKeys[type]);
|
|
cfg->writeEntry("grade", grade);
|
|
cfg->writeEntry("stuckOnGround", stuckOnGround);
|
|
}
|
|
|
|
void Slope::draw(TQPainter &painter)
|
|
{
|
|
painter.drawPixmap(x(), y(), pixmap);
|
|
}
|
|
|
|
TQPointArray Slope::areaPoints() const
|
|
{
|
|
switch (type)
|
|
{
|
|
case KImageEffect::CrossDiagonalGradient:
|
|
{
|
|
TQPointArray ret(3);
|
|
ret[0] = TQPoint((int)x(), (int)y());
|
|
ret[1] = TQPoint((int)x() + width(), (int)y() + height());
|
|
ret[2] = reversed? TQPoint((int)x() + width(), y()) : TQPoint((int)x(), (int)y() + height());
|
|
|
|
return ret;
|
|
}
|
|
|
|
case KImageEffect::DiagonalGradient:
|
|
{
|
|
TQPointArray ret(3);
|
|
ret[0] = TQPoint((int)x() + width(), (int)y());
|
|
ret[1] = TQPoint((int)x(), (int)y() + height());
|
|
ret[2] = !reversed? TQPoint((int)x() + width(), y() + height()) : TQPoint((int)x(), (int)y());
|
|
|
|
return ret;
|
|
}
|
|
|
|
case KImageEffect::EllipticGradient:
|
|
{
|
|
TQPointArray ret;
|
|
ret.makeEllipse((int)x(), (int)y(), width(), height());
|
|
return ret;
|
|
}
|
|
|
|
default:
|
|
return TQCanvasRectangle::areaPoints();
|
|
}
|
|
}
|
|
|
|
bool Slope::collision(Ball *ball, long int /*id*/)
|
|
{
|
|
if (grade <= 0)
|
|
return false;
|
|
|
|
double vx = ball->xVelocity();
|
|
double vy = ball->yVelocity();
|
|
double addto = 0.013 * grade;
|
|
|
|
const bool diag = type == KImageEffect::DiagonalGradient || type == KImageEffect::CrossDiagonalGradient;
|
|
const bool circle = type == KImageEffect::EllipticGradient;
|
|
|
|
double slopeAngle = 0;
|
|
|
|
if (diag)
|
|
slopeAngle = atan((double)width() / (double)height());
|
|
else if (circle)
|
|
{
|
|
const TQPoint start(x() + (int)width() / 2.0, y() + (int)height() / 2.0);
|
|
const TQPoint end((int)ball->x(), (int)ball->y());
|
|
|
|
Vector betweenVector(start, end);
|
|
const double factor = betweenVector.magnitude() / ((double)width() / 2.0);
|
|
slopeAngle = betweenVector.direction();
|
|
|
|
// this little bit by Daniel
|
|
addto *= factor * M_PI / 2;
|
|
addto = sin(addto);
|
|
}
|
|
|
|
switch (type)
|
|
{
|
|
case KImageEffect::HorizontalGradient:
|
|
reversed? vx += addto : vx -= addto;
|
|
break;
|
|
|
|
case KImageEffect::VerticalGradient:
|
|
reversed? vy += addto : vy -= addto;
|
|
break;
|
|
|
|
case KImageEffect::DiagonalGradient:
|
|
case KImageEffect::EllipticGradient:
|
|
reversed? vx += cos(slopeAngle) * addto : vx -= cos(slopeAngle) * addto;
|
|
reversed? vy += sin(slopeAngle) * addto : vy -= sin(slopeAngle) * addto;
|
|
break;
|
|
|
|
case KImageEffect::CrossDiagonalGradient:
|
|
reversed? vx -= cos(slopeAngle) * addto : vx += cos(slopeAngle) * addto;
|
|
reversed? vy += sin(slopeAngle) * addto : vy -= sin(slopeAngle) * addto;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ball->setVelocity(vx, vy);
|
|
// check if the ball is at the center of a pit or mound
|
|
// or has otherwise stopped.
|
|
if (vx == 0 && vy ==0)
|
|
ball->setState(Stopped);
|
|
else
|
|
ball->setState(Rolling);
|
|
|
|
// do NOT do terrain collisions
|
|
return false;
|
|
}
|
|
|
|
void Slope::setGradient(TQString text)
|
|
{
|
|
for (TQMap<KImageEffect::GradientType, TQString>::Iterator it = gradientKeys.begin(); it != gradientKeys.end(); ++it)
|
|
{
|
|
if (it.data() == text)
|
|
{
|
|
setType(it.key());
|
|
return;
|
|
}
|
|
}
|
|
|
|
// extra forgiveness ;-) (note it's i18n keys)
|
|
for (TQMap<KImageEffect::GradientType, TQString>::Iterator it = gradientI18nKeys.begin(); it != gradientI18nKeys.end(); ++it)
|
|
{
|
|
if (it.data() == text)
|
|
{
|
|
setType(it.key());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Slope::setType(KImageEffect::GradientType type)
|
|
{
|
|
this->type = type;
|
|
|
|
if (type == KImageEffect::EllipticGradient)
|
|
{
|
|
// calls updatePixmap
|
|
newSize(width(), height());
|
|
}
|
|
else
|
|
updatePixmap();
|
|
}
|
|
|
|
void Slope::updatePixmap()
|
|
{
|
|
// make a gradient, make grass that's bright or dim
|
|
// merge into this->pixmap. This is drawn in draw()
|
|
|
|
// we update the arrows in this function
|
|
clearArrows();
|
|
|
|
const bool diag = type == KImageEffect::DiagonalGradient || type == KImageEffect::CrossDiagonalGradient;
|
|
const bool circle = type == KImageEffect::EllipticGradient;
|
|
|
|
const TQColor darkColor = color.dark(100 + grade * (circle? 20 : 10));
|
|
const TQColor lightColor = diag || circle? color.light(110 + (diag? 5 : .5) * grade) : color;
|
|
// hack only for circles
|
|
const bool _reversed = circle? !reversed : reversed;
|
|
TQImage gradientImage = KImageEffect::gradient(TQSize(width(), height()), _reversed? darkColor : lightColor, _reversed? lightColor : darkColor, type);
|
|
|
|
TQPixmap qpixmap(width(), height());
|
|
TQPainter p(&qpixmap);
|
|
p.drawTiledPixmap(TQRect(0, 0, width(), height()), grass);
|
|
p.end();
|
|
|
|
const double length = sqrt(width() * width() + height() * height()) / 4;
|
|
|
|
if (circle)
|
|
{
|
|
const TQColor otherLightColor = color.light(110 + 15 * grade);
|
|
const TQColor otherDarkColor = darkColor.dark(110 + 20 * grade);
|
|
TQImage otherGradientImage = KImageEffect::gradient(TQSize(width(), height()), reversed? otherDarkColor : otherLightColor, reversed? otherLightColor : otherDarkColor, KImageEffect::DiagonalGradient);
|
|
|
|
TQImage grassImage(qpixmap.convertToImage());
|
|
|
|
TQImage finalGradientImage = KImageEffect::blend(otherGradientImage, gradientImage, .60);
|
|
pixmap.convertFromImage(KImageEffect::blend(grassImage, finalGradientImage, .40));
|
|
|
|
// make arrows
|
|
double angle = 0;
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
angle += M_PI / 2;
|
|
Arrow *arrow = new Arrow(canvas());
|
|
arrow->setLength(length);
|
|
arrow->setAngle(angle);
|
|
arrow->setReversed(reversed);
|
|
arrow->updateSelf();
|
|
arrows.append(arrow);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Arrow *arrow = new Arrow(canvas());
|
|
|
|
float ratio = 0;
|
|
float factor = 1;
|
|
|
|
double angle = 0;
|
|
|
|
switch (type)
|
|
{
|
|
case KImageEffect::HorizontalGradient:
|
|
angle = 0;
|
|
factor = .32;
|
|
break;
|
|
|
|
case KImageEffect::VerticalGradient:
|
|
angle = M_PI / 2;
|
|
factor = .32;
|
|
break;
|
|
|
|
case KImageEffect::DiagonalGradient:
|
|
angle = atan((double)width() / (double)height());
|
|
|
|
factor = .45;
|
|
break;
|
|
|
|
case KImageEffect::CrossDiagonalGradient:
|
|
angle = atan((double)width() / (double)height());
|
|
angle = M_PI - angle;
|
|
|
|
factor = .05;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
float factorPart = factor * 2;
|
|
// gradePart is out of 1
|
|
float gradePart = grade / 8.0;
|
|
|
|
ratio = factorPart * gradePart;
|
|
|
|
// reverse the reversed ones
|
|
if (reversed)
|
|
ratio *= -1;
|
|
else
|
|
angle += M_PI;
|
|
|
|
KPixmap kpixmap = qpixmap;
|
|
(void) KPixmapEffect::intensity(kpixmap, ratio);
|
|
|
|
TQImage grassImage(kpixmap.convertToImage());
|
|
|
|
// okay, now we have a grass image that's
|
|
// appropriately lit, and a gradient;
|
|
// lets blend..
|
|
pixmap.convertFromImage(KImageEffect::blend(gradientImage, grassImage, .42));
|
|
arrow->setAngle(angle);
|
|
arrow->setLength(length);
|
|
arrow->updateSelf();
|
|
|
|
arrows.append(arrow);
|
|
}
|
|
|
|
text->setText(TQString::number(grade));
|
|
|
|
if (diag || circle)
|
|
{
|
|
// make cleared bitmap
|
|
TQBitmap bitmap(pixmap.width(), pixmap.height(), true);
|
|
TQPainter bpainter(&bitmap);
|
|
bpainter.setBrush(color1);
|
|
TQPointArray r = areaPoints();
|
|
|
|
// shift all the points
|
|
for (unsigned int i = 0; i < r.count(); ++i)
|
|
{
|
|
TQPoint &p = r[i];
|
|
p.setX(p.x() - x());
|
|
p.setY(p.y() - y());
|
|
}
|
|
bpainter.drawPolygon(r);
|
|
|
|
// mask is drawn
|
|
pixmap.setMask(bitmap);
|
|
}
|
|
|
|
moveArrow();
|
|
update();
|
|
}
|
|
|
|
/////////////////////////
|
|
|
|
SlopeConfig::SlopeConfig(Slope *slope, TQWidget *parent)
|
|
: Config(parent)
|
|
{
|
|
this->slope = slope;
|
|
TQVBoxLayout *tqlayout = new TQVBoxLayout(this, marginHint(), spacingHint());
|
|
KComboBox *gradient = new KComboBox(this);
|
|
TQStringList items;
|
|
TQString curText;
|
|
for (TQMap<KImageEffect::GradientType, TQString>::Iterator it = slope->gradientI18nKeys.begin(); it != slope->gradientI18nKeys.end(); ++it)
|
|
{
|
|
if (it.key() == slope->curType())
|
|
curText = it.data();
|
|
items.append(it.data());
|
|
}
|
|
gradient->insertStringList(items);
|
|
gradient->setCurrentText(curText);
|
|
tqlayout->addWidget(gradient);
|
|
connect(gradient, TQT_SIGNAL(activated(const TQString &)), this, TQT_SLOT(setGradient(const TQString &)));
|
|
|
|
tqlayout->addStretch();
|
|
|
|
TQCheckBox *reversed = new TQCheckBox(i18n("Reverse direction"), this);
|
|
reversed->setChecked(slope->isReversed());
|
|
tqlayout->addWidget(reversed);
|
|
connect(reversed, TQT_SIGNAL(toggled(bool)), this, TQT_SLOT(setReversed(bool)));
|
|
|
|
TQHBoxLayout *htqlayout = new TQHBoxLayout(tqlayout, spacingHint());
|
|
htqlayout->addWidget(new TQLabel(i18n("Grade:"), this));
|
|
KDoubleNumInput *grade = new KDoubleNumInput(this);
|
|
grade->setRange(0, 8, 1, true);
|
|
grade->setValue(slope->curGrade());
|
|
htqlayout->addWidget(grade);
|
|
connect(grade, TQT_SIGNAL(valueChanged(double)), this, TQT_SLOT(gradeChanged(double)));
|
|
|
|
TQCheckBox *stuck = new TQCheckBox(i18n("Unmovable"), this);
|
|
TQWhatsThis::add(stuck, i18n("Whether or not this slope can be moved by other objects, like floaters."));
|
|
stuck->setChecked(slope->isStuckOnGround());
|
|
tqlayout->addWidget(stuck);
|
|
connect(stuck, TQT_SIGNAL(toggled(bool)), this, TQT_SLOT(setStuckOnGround(bool)));
|
|
}
|
|
|
|
void SlopeConfig::setGradient(const TQString &text)
|
|
{
|
|
slope->setGradient(text);
|
|
changed();
|
|
}
|
|
|
|
void SlopeConfig::setReversed(bool yes)
|
|
{
|
|
slope->setReversed(yes);
|
|
changed();
|
|
}
|
|
|
|
void SlopeConfig::setStuckOnGround(bool yes)
|
|
{
|
|
slope->setStuckOnGround(yes);
|
|
changed();
|
|
}
|
|
|
|
void SlopeConfig::gradeChanged(double newgrade)
|
|
{
|
|
slope->setGrade(newgrade);
|
|
changed();
|
|
}
|
|
|
|
#include "slope.moc"
|