/* * Asciiquarium - Native KDE Screensaver based on the Asciiquarium program * (c) Kirk Baucom , which you can find at * http://www.robobunny.com/projects/asciiquarium/ * * Ported to KDE by Maksim Orlovich and * Michael Pyne . * * Copyright (c) 2003 Kirk Baucom * Copyright (c) 2005 Maksim Orlovich * Copyright (c) 2005 Michael Pyne * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include "screen.h" #include "sprite.h" #include "aasaver.h" Screen::Screen(AASaver* widget): m_widget(widget) { QFontMetrics fm(KGlobalSettings::fixedFont()); // Compute cell geometries. m_cellW = fm.maxWidth(); m_cellH = fm.lineSpacing(); // Computer number of full cells that will fit. m_width = widget->width() / m_cellW; m_height = widget->height() / m_cellH; // Calculate offset needed to evenly distribute excess screen space. m_offX = (widget->width() - m_width * m_cellW) / 2; m_offY = (widget->height() - m_height * m_cellH) / 2; // Create double buffer. m_backBuffer = QPixmap(m_widget->size()); m_backBuffer.fill(black); // FIXME: handle resizing! // Setup animation timer. QTimer* timer = new QTimer(this); connect(timer, SIGNAL(timeout()), SLOT(doAnimate())); timer->start(msPerTick()); } int Screen::msPerTick() const { return 50; } Screen::~Screen() { } void Screen::updateSpan(int x, int y, const QPixmap &updatePixmap) { if (y < 0 || y >= m_height) return; QPoint upperLeft(m_offX + x * m_cellW, m_offY + y * m_cellH); bitBlt(&m_backBuffer, upperLeft, &updatePixmap, updatePixmap.rect(), Qt::CopyROP); m_widget->update(QRect(upperLeft, updatePixmap.size())); } void Screen::clearSpan(int x, int y, const QPixmap &clearPixmap) { if (y < 0 || y >= m_height) return; QPoint upperLeft(m_offX + x * m_cellW, m_offY + y * m_cellH); bitBlt(&m_backBuffer, upperLeft, &clearPixmap, clearPixmap.rect(), Qt::CopyROP); m_widget->update(QRect(upperLeft, clearPixmap.size())); } //Actually paints the region on the widget. void Screen::paint(QRegion r) { QPainter p(m_widget); QMemArray rects = r.rects(); for (int r = 0; r < rects.size(); ++r) { //Determine the grid locations described by the rect QRect bound = rects[r]; bitBlt(m_widget, bound.topLeft(), &m_backBuffer, bound, Qt::CopyROP); } //for rect in region }; /** * Utility type used to faciliate sorting of the Sprite list in order to * implement the Painter's Algorithm when painting the back buffer. */ struct ZKey { /** * Logical depth of sprite. Now 0 is farthest away from the eyes, unlike * with Sprite::depth(). */ int z; Sprite* addr; ZKey(): z(0), addr(0) {} ZKey(Sprite* spr): z(1000 - spr->depth()), addr(spr) {} bool operator<(const ZKey& other) const { if (z < other.z) return true; if (z > other.z) return false; return addr < other.addr; } }; void Screen::doAnimate() { //First, rebuild a new list of sprites, and build a dirty region QRegion dirtyRegion; QValueVector sprites; QValueVector colliders; // Look for sprites that can suffer a collision. for (unsigned pos = 0; pos < m_sprites.size(); ++pos) { if(m_sprites[pos]->canCollide()) colliders.append(m_sprites[pos]); } // Find collisions. // FIXME: Use transparent regions for accuracy. for (unsigned pos = 0; pos < colliders.size(); ++pos) for (unsigned sprite = 0; sprite < m_sprites.size(); ++sprite) { if(m_sprites[sprite] == colliders[pos]) continue; if(colliders[pos]->geom().intersects(m_sprites[sprite]->geom())) colliders[pos]->collision(m_sprites[sprite]); } //Retain all live existing sprites for (int pos = 0; pos < m_sprites.size(); ++pos) { Sprite* sprite = m_sprites[pos]; QRect oldRect = sprite->geom(); if (!sprite->isKilled()) { bool dirty = sprite->tickUpdate(); if (dirty) dirtyRegion |= oldRect | sprite->geom(); if (!sprite->isKilled()) sprites.append(sprite); } if (sprite->isKilled()) //note:may be made true by updateTick! { dirtyRegion |= oldRect; delete sprite; } } //Add new sprites. for (int pos = 0; pos < m_addedSprites.size(); ++pos) { dirtyRegion |= m_addedSprites[pos]->geom(); sprites.append(m_addedSprites[pos]); } m_addedSprites.clear(); m_sprites = sprites; //Compute the list of sprites affected. Note that this is //done iteratively until fixed point. QValueVector paintSprites; QValueVector remSprites; bool changed; do { changed = false; remSprites.clear(); for (int c = 0; c < sprites.size(); ++c) { Sprite* sprite = sprites[c]; if (dirtyRegion.intersect(sprite->geom()).isEmpty()) remSprites.append(sprite); //not to be painted thus far else { //This sprite is to be painted paintSprites.append(sprite); //make sure we repaint everything overlapping it dirtyRegion |= sprite->geom(); changed = true; } } sprites = remSprites; } while (changed); //Z-sort the items. QMap sorted; for (int pos = 0; pos < paintSprites.size(); ++pos) sorted[ZKey(paintSprites[pos])] = paintSprites[pos]; //Paint, in Z-order for (QMapIterator i = sorted.begin(); i != sorted.end(); ++i) i.data()->paint(); // Make sure black strip at edge is still present. if(!paintSprites.isEmpty()) { QPainter p(&m_backBuffer); p.fillRect(m_backBuffer.width() - m_offX, 0, m_offX, m_backBuffer.height(), Qt::black); } } #include "screen.moc" // vim: set et ts=8 sw=4: