diff --git a/app/app.pro b/app/app.pro index e92f201f66..806a970d50 100644 --- a/app/app.pro +++ b/app/app.pro @@ -60,7 +60,9 @@ HEADERS += \ src/spinslider.h \ src/doubleprogressdialog.h \ src/colorslider.h \ - src/checkupdatesdialog.h + src/checkupdatesdialog.h \ + src/consolewindow.h \ + src/asciipreviewdialog.h SOURCES += \ src/main.cpp \ @@ -89,7 +91,9 @@ SOURCES += \ src/spinslider.cpp \ src/doubleprogressdialog.cpp \ src/colorslider.cpp \ - src/checkupdatesdialog.cpp + src/checkupdatesdialog.cpp \ + src/consolewindow.cpp \ + src/asciipreviewdialog.cpp FORMS += \ ui/mainwindow2.ui \ @@ -111,7 +115,9 @@ FORMS += \ ui/timelinepage.ui \ ui/filespage.ui \ ui/toolspage.ui \ - ui/toolboxwidget.ui + ui/toolboxwidget.ui \ + ui/consolewindow.ui \ + ui/asciipreviewdialog.ui diff --git a/app/data/app.qrc b/app/data/app.qrc index 78158697ef..0aeeff4323 100644 --- a/app/data/app.qrc +++ b/app/data/app.qrc @@ -56,6 +56,7 @@ icons/new/arrow-horizontal.png icons/new/arrow-selectmove.png icons/new/arrow-vertical.png + audio/electric-city.mp3 icons/onion-blue.png diff --git a/app/data/audio/electric-city.mp3 b/app/data/audio/electric-city.mp3 new file mode 100644 index 0000000000..53bd07f3bb Binary files /dev/null and b/app/data/audio/electric-city.mp3 differ diff --git a/app/src/asciipreviewdialog.cpp b/app/src/asciipreviewdialog.cpp new file mode 100644 index 0000000000..c77e0fbf30 --- /dev/null +++ b/app/src/asciipreviewdialog.cpp @@ -0,0 +1,30 @@ +#include "asciipreviewdialog.h" +#include "ui_asciipreviewdialog.h" + +#include + +AsciiPreviewDialog::AsciiPreviewDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::AsciiPreviewDialog), + mMetrics(QFont("Courier New", 13)) +{ + ui->setupUi(this); + + ui->verticalLayout->setSizeConstraint(QLayout::SetFixedSize); +} + +AsciiPreviewDialog::~AsciiPreviewDialog() +{ + delete ui; +} + +void AsciiPreviewDialog::setText(QString s) +{ + ui->textDisplay->setText(s); + qDebug() << mMetrics.size(0, s); +} + +void AsciiPreviewDialog::setPageNumber(int n) +{ + setWindowTitle(tr("Previewing Page #%1").arg(n)); +} diff --git a/app/src/asciipreviewdialog.h b/app/src/asciipreviewdialog.h new file mode 100644 index 0000000000..3d8513c733 --- /dev/null +++ b/app/src/asciipreviewdialog.h @@ -0,0 +1,26 @@ +#ifndef ASCIIPREVIEWDIALOG_H +#define ASCIIPREVIEWDIALOG_H + +#include + +namespace Ui { +class AsciiPreviewDialog; +} + +class AsciiPreviewDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AsciiPreviewDialog(QWidget *parent = nullptr); + ~AsciiPreviewDialog(); + + void setText(QString s); + void setPageNumber(int n); + +private: + Ui::AsciiPreviewDialog *ui; + QFontMetrics mMetrics; +}; + +#endif // ASCIIPREVIEWDIALOG_H diff --git a/app/src/consolewindow.cpp b/app/src/consolewindow.cpp new file mode 100644 index 0000000000..434f99ddda --- /dev/null +++ b/app/src/consolewindow.cpp @@ -0,0 +1,681 @@ +#include "consolewindow.h" +#include "ui_consolewindow.h" + +#include +#include +#include + +#include "mainwindow2.h" +#include "ui_mainwindow2.h" +#include "editor.h" +#include "toolmanager.h" +#include "pointerevent.h" +#include "viewmanager.h" +#include "playbackmanager.h" +#include "colormanager.h" +#include "layermanager.h" +#include "layercamera.h" +#include "layerbitmap.h" +#include "asciiimage.h" +#include "asciipreviewdialog.h" + +ConsoleWindow::ConsoleWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::ConsoleWindow) +{ + ui->setupUi(this); + + // Actions + connect(ui->prompt, &QLineEdit::returnPressed, this, &ConsoleWindow::runCommand); + ui->prompt->installEventFilter(this); + + // Styling + ui->prompt->setAttribute(Qt::WA_MacShowFocusRect, 0); + ui->prompt->setFocus(); + + mMainWindow = new MainWindow2(this); + mMainWindow->mEditor->color()->setColor(Qt::black); + LayerManager *lm = mMainWindow->mEditor->layers(); + mCamLayer = lm->createCameraLayer("ASCII Camera"); + mCamLayer->setViewRect(QRect(mMainWindow->mEditor->view()->mapScreenToCanvas(QPoint(0, 0)).toPoint(), QSize(50, 50))); + LayerCamera *curCam; + while ((curCam = static_cast(lm->getLastCameraLayer())) != mCamLayer) + { + lm->deleteLayer(lm->getIndex(curCam)); + } + mDrawingLayer = lm->createBitmapLayer("ASCII Bitmap"); + lm->setCurrentLayer(mDrawingLayer); + + mPreviewDialog = new AsciiPreviewDialog(this); + connect(mMainWindow->mEditor, &Editor::currentFrameChanged, this, &ConsoleWindow::frameChanged); + + // Play music + QMediaPlaylist *playlist = new QMediaPlaylist(); + playlist->addMedia(QUrl("qrc:/audio/electric-city.mp3")); + playlist->setPlaybackMode(QMediaPlaylist::Loop); + mSpeaker = new QMediaPlayer(); + mSpeaker->setPlaylist(playlist); + mSpeaker->play(); +} + +ConsoleWindow::~ConsoleWindow() +{ + delete ui; +} + +bool ConsoleWindow::eventFilter(QObject *watched, QEvent *event) +{ + if (watched == ui->prompt) + { + if (event->type() == QEvent::FocusOut) + { + // Keep focus on prompt + ui->prompt->setFocus(); + } + } + return false; +} + +void ConsoleWindow::runCommand() +{ + // Handle exiting the splash screen + if (mIsOnSplash) + { + // Remove title + ui->title->hide(); + // Remove logo + ui->console->clear(); + + // Clear prompt + ui->prompt->setText(""); + // Make prompt editable + ui->prompt->setReadOnly(false); + + // Show help text + printLook(""); + print(tr("Hint: If you are unsure of what to do, type HELP below and hit enter.")); + + // Prevent this code from being run again + mIsOnSplash = false; + return; + } + + // Get command + QString command = ui->prompt->text().trimmed(); + // Clean command for robustness + command = command.toLower().replace(QRegularExpression("[^A-Za-z 0-9\\.\\-]"), ""); + // Echo to output + ui->console->appendPlainText("> " + command); + // Clear prompt + ui->prompt->setText(""); + + // Take action! + + if (command == tr("help")) + { + printHelp(); + } + else if (command == tr("look")) + { + printLook(""); + } + else if (command.startsWith(tr("look "))) + { + printLook(command.mid(tr("look ").size())); + } + else if (command == tr("close") || command == tr("quit")) + { + // Prevents sound glitching + mSpeaker->pause(); + + // Quit application + close(); + } + else if ((QStringList() << tr("equip") << tr("pick") << tr("pickup") << tr("pick up") << tr("grab")).contains(command)) + { + print(tr("What do you want to %1? To specify, use the action: %2 ").arg(command, command.toUpper())); + } + else if (command.startsWith(tr("equip ")) || command.startsWith(tr("pick ")) || command.startsWith(tr("pickup ")) || command.startsWith(tr("grab "))) + { + if (command.startsWith(tr("pick up "))) + { + printEquip(tr("pick up"), command.mid(tr("pick up ").size()).split(' ')); + } + else + { + printEquip(command.left(command.indexOf(' ')), command.mid(command.indexOf(' ') + 1).split(' ')); + } + } + else if (command == "version") + { + print("Pencil2D ASCII Edition v0.1"); + } + else if (command == tr("press")) + { + print(tr("Press needs x and y coordinates of where to start pressing. Use PRESS ")); + } + else if (command.startsWith(tr("press "))) + { + doPress(command.mid(tr("press ").size()).split(' ')); + } + else if (command == tr("move") || command == tr("go to")) + { + print(tr("Move needs x and y coordinates of where to move to. Use MOVE ")); + } + else if (command.startsWith(tr("move "))) + { + doMove(command.mid(tr("move ").size()).split(' ')); + } + else if (command.startsWith(tr("go to "))) + { + doMove(command.mid(tr("go to ").size()).split(' ')); + } + else if (command == tr("release")) { + doRelease(); + } + else if (command == tr("unplug")) + { + print("What do you want to unplug? To specify, use the action: UNPLUG "); + } + else if (command.startsWith(tr("unplug "))) + { + if (command == tr("unplug speaker")) + { + doPlugSpeaker(false); + } + else + { + print(tr("Cannot unplug %1").arg(command.mid(tr("unplug ").size()))); + } + } + else if (command == tr("plug in")) + { + print("What do you want to plug in? To specify, use the action: PLUG IN "); + } + else if (command.startsWith(tr("plug in "))) + { + if (command == tr("plug in speaker")) + { + doPlugSpeaker(true); + } + else + { + print(tr("Cannot plug in %1").arg(command.mid(tr("plug in ").size()))); + } + } + else if (command == "iddqd") + { + // A debug command to display the main window + if (mMainWindow->isVisible()) + { + mMainWindow->hide(); + } + else + { + mMainWindow->show(); + } + } + else if (command == tr("view") || command == tr("render")) + { + printPaper(RENDER_SIZE); + } + else if (command.startsWith("view ") || command.startsWith(tr("render "))) + { + printPaper(command.mid(command.indexOf(' ') + 1).split(" ")); + } + else if (command.startsWith(tr("page"))) + { + doPage(command.mid(tr("page").size()).trimmed()); + } + else if (command == tr("flip") || command == tr("play")) + { + doPlay(); + } + else if (command == tr("stop") || command == tr("pause")) + { + doStop(); + } + else if (command == tr("open")) + { + doOpen(); + } + else if (command == tr("save")) + { + doSave(); + } + else + { + print(tr("I do not understand that.")); + } +} + +void ConsoleWindow::frameChanged(int index) +{ + Q_UNUSED(index); + if (!mPreviewDialog->isVisible()) return; + printPaper(); +} + +void ConsoleWindow::frameUpdate() +{ + if (!mPreviewDialog->isVisible()) return; + printPaper(); +} + +void ConsoleWindow::print(QString s) +{ + ui->console->appendPlainText(s); +} + +void ConsoleWindow::printHelp() +{ + print("There's no shame in asking for help! In case you are not familiar with the typical format of text-based games, here is a very brief overview. You type actions into the bar at the bottom and when you hit enter the result of those actions will be displayed here. Usually actions use a simple VERB NOUN form. For example, LOOK MONEY. Capitalization is used throughout to help you find important nouns and verbs to use. LOOK is the most important verb as it helps you understand what objects are of interest and what you can do with them. Use LOOK by itself to generally look around for things."); +} + +void ConsoleWindow::printLook(QString arg) +{ + arg = arg.trimmed(); + if (arg.isEmpty()) + { + QString baseMessage = tr("Ye find yeself in yon animation studio. The room is small and dark with no obvious exits. The only source of illumination is a LAMP on a DRAWING TABLE in the center of the room. PAPER and various TOOLS are neatly organized on top of the table. A WASTEBIN and a BOOK are beside the table. %1"); + if (mSpeaker->state() == QMediaPlayer::PlayingState) + { + print(baseMessage.arg(tr("You can hear music playing from a SPEAKER behind you."))); + } + else + { + print(baseMessage.arg(tr("A SPEAKER lays silent behind you."))); + } + } + else if (arg == tr("corner") || arg == tr("corners")) + { + print(tr("There are no corners, you are in a round room. I didn't mention this earlier because there was no point.")); + } + else if (arg == tr("money")) + { + print(tr("That was just an example! You don't actually have any money, and it doesn't do you any good anyway without something to buy.")); + } + else if ((QStringList() << tr("self") << tr("me") << tr("i") << tr("myself")).contains(arg)) + { + print(tr("You look down at your body. You've got hands and feet and all the usually stuff in between. There's no mirror in the room to see what your head looks like, but you're probably quite good looking.")); + } + else if (arg == tr("lamp")) + { + if(!mHasLookedLamp) + { + print(tr("It's just an ordinary lamp.")); + mHasLookedLamp = true; + } + else + { + print(tr("It's still just an ordinary lamp, but you swear it has moved since last time you've looked at it.")); + } + } + else if (arg == tr("speaker")) + { + if (mSpeaker->state() == QMediaPlayer::PlayingState) + { + print(tr("This small speaker is playing some chill 8-bit tunes. There is a small plaque with the words \"Electric City by Jazzcat, 8-bit Remix by Dippy\" engraved on it. It is plugged into the wall, so you should be able to turn it off by UNPLUGging it.")); + } + else + { + print(tr("This small speaker was playing some music until you UNPLUGged it. Now it's so silent. Its sole purpose in life was to play music, and now you have deprived it of that. I hope your happy with yourself. If you want to make things right you better PLUG IN it again.")); + } + } + else if (arg == tr("tools") || arg == tr("items")) + { + print(tr("There are various drawing implements lined up around the edge of the desk. You can see PENCILs, ERASERs, PENs, and BRUSHes. You could probably PICK UP one of them and use it. For example you could PICK UP PEN 2 for the 2 sized pen.")); + } + else if ((QStringList() << tr("desk") << tr("table") << tr("drawing desk") << tr("drawing table")).contains(arg)) + { + print(tr("There is a large desk in the center of the room. On it are various objects including PAPER and TOOLS. To the left is a WASTEBIN and to the right a BOOK.")); + } + else if (arg == tr("paper") || arg == tr("pages")) + { + print(tr("There is a seemingly infinte number of papers on the desk. You could probably use some of the TOOLS to draw on them. You also have the urge to FLIP through them. If you want to take a closer look at the top paper, you can VIEW it.")); + } + else if (arg == tr("book") || arg == tr("spellbook")) + { + print(tr("You pick up the book an skim through it. It appears to be a spellbook written in some mystic language. The only words you even recognize are OPEN and SAVE. Perhaps they are some kind of incantations you can use.")); + } + else if ((QStringList() << tr("wastebin") << tr("garbage bin") << tr("garbage") << tr("trash bin") << tr("trash")).contains(arg)) + { + QString baseMessage = tr("There is a wastebin to the side of the desk. You could probably use it to DISCARD the current PAPER if you wanted. %1"); + if (mDiscardedPaper == 0) + { + print(baseMessage.arg(tr("It is currently empty."))); + } + else if (mDiscardedPaper <= 10) + { + print(baseMessage.arg(tr("There are already a few papers in it."))); + } + else if (mDiscardedPaper <= 25) + { + print(tr("You've DISCARDed an aweful lot of PAPERs into this wastebin. It is in danger of overflowing.")); + } + else + { + print(tr("You've DISCARDed so many PAPERs into this wastebin that it is now overflowing onto the floor. What a mess!")); + } + } + else + { + print(tr("I don't know where to look for that.")); + } +} + +void ConsoleWindow::printEquip(QString term, QStringList args) +{ + if (args.size() > 2) + { + print(tr("Too many words, %1 only supports and (in that order).").arg(term)); + } + + // List of tools supported by Pencil2D ASCII Version + QHash allowableTools; + allowableTools["pencil"] = ToolType::PENCIL; + allowableTools["eraser"] = ToolType::ERASER; + allowableTools["pen"] = ToolType::PEN; + allowableTools["brush"] = ToolType::BRUSH; + + if (allowableTools.contains(args[0])) + { + if (args.size() == 2) + { + bool ok = true; + float s = args[1].toFloat(&ok); + if (!ok) + { + print(tr("%1 is not a valid size, ignoring. Please use a number next time").arg(args[1])); + } + else + { + mMainWindow->mEditor->tools()->getTool(allowableTools[args[0]])->setWidth(s); + } + } + mMainWindow->mEditor->tools()->setCurrentTool(allowableTools[args[0]]); + print(tr("You %1 the %2 with size %3. To use it, you have to PRESS , MOVE zero or more times, and then RELEASE. It is also helpful to VIEW what you are doing.").arg(term, args[0], QString::number(mMainWindow->mEditor->tools()->getTool(allowableTools[args[0]])->properties.width))); + } + else + { + print(tr("You can't %1 the %2.").arg(term, args[0])); + } +} + +void ConsoleWindow::printPaper(QSize renderSize) +{ + if (renderSize.isEmpty()) + { + renderSize = mCurrentRenderSize; + } + mCurrentRenderSize = renderSize; + + // Render image + + QSize cameraSize = mCamLayer->getViewSize(); + int currentFrame = mMainWindow->mEditor->currentFrame(); + QImage imageToExport(renderSize, QImage::Format_ARGB32_Premultiplied); + + QColor bgColor = Qt::white; + bgColor.setAlpha(0); + imageToExport.fill(bgColor); + + QPainter painter(&imageToExport); + painter.setWorldTransform(mCamLayer->getViewAtFrame(currentFrame)); + painter.setWindow(QRect(QPoint(0, 0), cameraSize)); + + mMainWindow->mEditor->object()->paintImage(painter, currentFrame, false, true); + if (mIsDrawing) + { + mMainWindow->ui->scribbleArea->mBufferImg->paintImage(painter); + } + + // Convert image to ASCII + + QString output = AsciiImage::convert(imageToExport); + + // Display image in preview dialog + + mPreviewDialog->setText(output); + mPreviewDialog->setPageNumber(currentFrame); + mPreviewDialog->show(); +} + +void ConsoleWindow::printPaper(QStringList args) +{ + // Check for exactly two arguments (x, y) + if (args.size() != 2) + { + print(tr("You must specify either no arugments or a width and height to view . Use RENDER ")); + return; + } + + // Try to parse arguments into numbers + qreal w, h; + bool ok, succeeded = true; + w = args[0].toDouble(&ok); + succeeded &= ok; + h = args[1].toDouble(&ok); + succeeded &= ok; + if (!succeeded) + { + print(tr("Could not understand sizes %1 and %2. Please use numerical sizes.").arg(args[0], args[1])); + return; + } + + printPaper(QSize(w,h)); +} + +void ConsoleWindow::doPress(QStringList args) +{ + // Check to make sure we haven't already pressed + if (mIsDrawing) + { + print(tr("Can't press because you are already drawing. Use RELEASE first to finish drawing the previous stroke.")); + return; + } + + // Check for exactly two arguments (x, y) + if (args.size() != 2) + { + print(tr("Press needs x and y coordinates of where to start pressing. Use PRESS ")); + return; + } + + // Try to parse arguments into numbers + float x, y; + bool ok, succeeded = true; + x = args[0].toFloat(&ok); + succeeded &= ok; + y = args[1].toFloat(&ok); + succeeded &= ok; + if (!succeeded) + { + print(tr("Could not understand positions %1 and %2. Please use numerical positions.").arg(args[0], args[1])); + return; + } + + mCurrentPos = QPointF(static_cast(x), static_cast(y)); + mIsDrawing = true; + + QMouseEvent *e = new QMouseEvent(QEvent::MouseButtonPress, mCurrentPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + mMainWindow->ui->scribbleArea->mousePressEvent(e); + frameUpdate(); +} + +void ConsoleWindow::doMove(QStringList args) +{ + // Check to make sure we've already pressed + if (!mIsDrawing) + { + print(tr("Can't move because you are not currently drawing. Use PRESS first.")); + return; + } + + // Check for exactly two arguments (x, y) + if (args.size() != 2) + { + print(tr("Move needs x and y coordinates of where to move to. Use MOVE ")); + return; + } + + // Try to parse arguments into numbers + float x, y; + bool ok, succeeded = true; + x = args[0].toFloat(&ok); + succeeded &= ok; + y = args[1].toFloat(&ok); + succeeded &= ok; + if (!succeeded) + { + print(tr("Could not understand positions %1 and %2. Please use numerical positions.").arg(args[0], args[1])); + return; + } + + mCurrentPos = QPointF(static_cast(x), static_cast(y)); + + QMouseEvent *e = new QMouseEvent(QEvent::MouseMove, mCurrentPos, Qt::NoButton, Qt::LeftButton, Qt::NoModifier); + mMainWindow->ui->scribbleArea->mouseMoveEvent(e); + frameUpdate(); +} + +void ConsoleWindow::doRelease() +{ + // Check to make sure we've already pressed + if (!mIsDrawing) + { + print(tr("Can't release because you are not currently drawing. Use PRESS first.")); + return; + } + + QMouseEvent *e = new QMouseEvent(QEvent::MouseButtonRelease, mCurrentPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + mMainWindow->ui->scribbleArea->mouseReleaseEvent(e); + mIsDrawing = false; + frameUpdate(); +} + +void ConsoleWindow::doPlugSpeaker(bool shouldPlay) +{ + if (shouldPlay) + { + if (mSpeaker->state() == QMediaPlayer::PlayingState) + { + print(tr("The speaker is already plugged in!")); + } + else + { + mSpeaker->play(); + print(tr("Plugged in speaker :)")); + } + } + else + { + if(mSpeaker->state() != QMediaPlayer::PlayingState) + { + print(tr("The speaker is already unplugged!")); + } + else + { + mSpeaker->pause(); + print(tr("Unplugged speaker ;(")); + } + } +} + +void ConsoleWindow::doPage(QString arg) +{ + if (arg.isEmpty()) + { + print(tr("The current page is %1. You can use PAGE PREVIOUS, PAGE NEXT, or PAGE to navigate through your pile of PAPERS.").arg(mMainWindow->mEditor->currentFrame())); + } + else if (arg == tr("next") || arg == tr("forward") || arg == tr("forwards")) + { + mMainWindow->mEditor->scrubForward(); + print(tr("You put the current PAPER off to the side to VIEW the next one. It is page #%1").arg(mMainWindow->mEditor->currentFrame())); + } + else if (arg == tr("previous") || arg == tr("back") || arg == tr("backward") || arg == tr("backwards")) + { + mMainWindow->mEditor->scrubBackward(); + print(tr("You put the previous PAPER over the current paper to VIEW it. It is page #%1").arg(mMainWindow->mEditor->currentFrame())); + } + else + { + bool ok = true; + int n = arg.toInt(&ok); + if (!ok || n <= 0) + { + print(tr("%1 is not a page number. All page numbers are positive whole numbers.").arg(arg)); + } + else + { + mMainWindow->mEditor->scrubTo(n); + print(tr("You rummage through your papers until you find page %1. You put in on top so you can VIEW it.").arg(n)); + } + } +} + +void ConsoleWindow::doPlay() +{ + if (!mMainWindow->mEditor->playback()->isPlaying()) + { + mMainWindow->mEditor->playback()->play(); + + print(tr("You pick up the papers and start to flip them between your fingers. As you VIEW them, the still drawings come to life! At any time you can STOP.")); + } + else + { + print(tr("You're already flipping through the papers.")); + } +} + +void ConsoleWindow::doStop() +{ + if (mMainWindow->mEditor->playback()->isPlaying()) + { + mMainWindow->mEditor->playback()->stop(); + print(tr("You stop flipping the papers. That was fun.")); + } + else + { + print(tr("You can't stop what you don't start!")); + } +} + +void ConsoleWindow::doOpen() +{ + mMainWindow->openDocument(); + mMainWindow->mEditor->color()->setColor(Qt::black); + LayerManager *lm = mMainWindow->mEditor->layers(); + mCamLayer = (LayerCamera *) lm->findLayerByName("ASCII Camera", Layer::CAMERA); + if (mCamLayer == nullptr) + { + mCamLayer = lm->createCameraLayer("ASCII Camera"); + mCamLayer->setViewRect(QRect(mMainWindow->mEditor->view()->mapScreenToCanvas(QPoint(0, 0)).toPoint(), QSize(50, 50))); + } + else + { + QSize camSize = mCamLayer->getViewSize(); + mMainWindow->mEditor->view()->translate(camSize.width(), camSize.height()); + } + LayerCamera *curCam; + mDrawingLayer = (LayerBitmap*) lm->findLayerByName("ASCII Bitmap", Layer::BITMAP); + if (mDrawingLayer == nullptr) + { + mDrawingLayer = lm->createBitmapLayer("ASCII Bitmap"); + } + lm->setCurrentLayer(mDrawingLayer); +} + +void ConsoleWindow::doSave() +{ + bool success = mMainWindow->saveAsNewDocument(); + + if (success) + { + print(tr("You magically command the papers to save. You're not sure what happend, but you feel like it worked.")); + } + else + { + print(tr("You magically command the papers to save, but nothing happens.")); + } +} diff --git a/app/src/consolewindow.h b/app/src/consolewindow.h new file mode 100644 index 0000000000..68631dd562 --- /dev/null +++ b/app/src/consolewindow.h @@ -0,0 +1,67 @@ +#ifndef CONSOLEWINDOW_H +#define CONSOLEWINDOW_H + +#include + +namespace Ui { +class ConsoleWindow; +} + +class MainWindow2; +class QMediaPlayer; +class LayerCamera; +class LayerBitmap; +class AsciiPreviewDialog; + +class ConsoleWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit ConsoleWindow(QWidget *parent = nullptr); + ~ConsoleWindow() override; + + void print(QString s); + void printHelp(); + void printLook(QString arg); + void printEquip(QString term, QStringList args); + void printPaper(QStringList args); + void printPaper(QSize renderSize=QSize()); + + void doPress(QStringList args); + void doMove(QStringList args); + void doRelease(); + void doPlugSpeaker(bool shouldPlay); + void doPage(QString arg); + void doPlay(); + void doStop(); + void doOpen(); + void doSave(); + +protected: + bool eventFilter(QObject *object, QEvent *event) override; + +private slots: + void runCommand(); + void frameChanged(int index); + void frameUpdate(); + +private: + Ui::ConsoleWindow *ui; + + const QSize RENDER_SIZE{100, 50}; + + MainWindow2 *mMainWindow; + QMediaPlayer *mSpeaker; + bool mIsOnSplash = true; + bool mHasLookedLamp = false; + int mDiscardedPaper = 0; + QPointF mCurrentPos; + bool mIsDrawing = false; + LayerCamera *mCamLayer; + LayerBitmap *mDrawingLayer; + AsciiPreviewDialog *mPreviewDialog; + QSize mCurrentRenderSize; +}; + +#endif // CONSOLEWINDOW_H diff --git a/app/src/main.cpp b/app/src/main.cpp index 1c38967518..a3fb7a65cc 100644 --- a/app/src/main.cpp +++ b/app/src/main.cpp @@ -25,6 +25,7 @@ GNU General Public License for more details. #include "editor.h" #include "mainwindow2.h" +#include "consolewindow.h" #include "pencilapplication.h" #include "layermanager.h" #include "layercamera.h" @@ -311,6 +312,15 @@ int handleArguments(PencilApplication& app) return 0; } +int runConsole(PencilApplication& app) +{ + Q_UNUSED(app); + ConsoleWindow consoleWindow; + PlatformHandler::configurePlatformSpecificSettings(); + consoleWindow.show(); + return PencilApplication::exec(); +} + int main(int argc, char* argv[]) { Q_INIT_RESOURCE(core_lib); @@ -331,5 +341,5 @@ int main(int argc, char* argv[]) installTranslator(app); - return handleArguments(app); + return runConsole(app); } diff --git a/app/src/mainwindow2.cpp b/app/src/mainwindow2.cpp index 10375dae2c..560279ccdb 100644 --- a/app/src/mainwindow2.cpp +++ b/app/src/mainwindow2.cpp @@ -484,7 +484,7 @@ void MainWindow2::openDocument() bool MainWindow2::saveAsNewDocument() { - FileDialog fileDialog(this); + FileDialog fileDialog(nullptr); QString fileName = fileDialog.saveFile(FileType::ANIMATION); if (fileName.isEmpty()) { @@ -622,7 +622,8 @@ bool MainWindow2::openObject(QString strFilePath, bool checkForChanges) bool MainWindow2::saveObject(QString strSavedFileName) { - QProgressDialog progress(tr("Saving document..."), tr("Abort"), 0, 100, this); + // Remove progress dialog, does not play well when the main window is hidden + QProgressDialog progress(tr("Saving document..."), tr("Abort"), 0, 100, nullptr); progress.setWindowModality(Qt::WindowModal); progress.show(); diff --git a/app/src/mainwindow2.h b/app/src/mainwindow2.h index cfa5485395..9bb792dfdc 100644 --- a/app/src/mainwindow2.h +++ b/app/src/mainwindow2.h @@ -54,6 +54,7 @@ class MainWindow2 : public QMainWindow { Q_OBJECT + friend class ConsoleWindow; public: explicit MainWindow2(QWidget* parent = nullptr); ~MainWindow2() override; diff --git a/app/ui/asciipreviewdialog.ui b/app/ui/asciipreviewdialog.ui new file mode 100644 index 0000000000..51b0491938 --- /dev/null +++ b/app/ui/asciipreviewdialog.ui @@ -0,0 +1,42 @@ + + + AsciiPreviewDialog + + + + 0 + 0 + 102 + 100 + + + + Dialog + + + background:black; + + + + + + + Courier New + + + + color: limegreen; + + + Loading... + + + Qt::AlignCenter + + + + + + + + diff --git a/app/ui/consolewindow.ui b/app/ui/consolewindow.ui new file mode 100644 index 0000000000..9c4183cf3a --- /dev/null +++ b/app/ui/consolewindow.ui @@ -0,0 +1,171 @@ + + + ConsoleWindow + + + + 0 + 0 + 900 + 950 + + + + + Courier New + + + + Pencil2D ASCII Edition + + + color:limegreen;background:black; + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Courier New + 26 + + + + padding:10px 0; + + + <html><head/><body><div style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Pencil2D ASCII Edition: A Text-Based Animation Adventure</div></body></html> + + + Qt::AlignCenter + + + + + + + + 0 + 1 + + + + + Courier New + 18 + + + + border:none;background:transparent;border-bottom:2px solid limegreen; + + + true + + + + + N8888888D + OZ$$$ZZZZZZZZZZZ$Z$$Z + $ZZZZZZZZZZZZZZZZZZZZZZZ$Z$N + 8$Z$ZZZZZZZZ$$$$$$$$$ZZZZZZZZ$ZZ ..NWWN.. + ZZZZZZZZZ$77777777777777777$ZZZWW8+==:::::=+Z + 8$ZZZZZZ7777777777777777777777OWW=======+++++=:++ + ZZZZZZ$777777777777IIIII77777WW+:::===++++++++++++?7 + 8$ZZZZZ777777777???????????+NW=:+77:,,,,~+++++++++????? + OZZZZ$$7777777??+?????????DWD77?,:,,,,7+....+++++???????? + 8$ZZZZ7777777???????????IWW:::::,,,7?,...?=...~???????????? + N$ZZZZ777777???????????WW?IIIII$$:.,..=7....I..:,?????????II$ + $ZZZZ777777?????????NW$I?IIIIIIIIII7....,7,,:=7:::?????IIIIII + $ZZZZ777777???????OWWI?IIIIIIIIIIIIII??. .7:::~7~~~IIIIIIIIIIZ + NZZZ$$77777??????WW?IIIIIIIIIIIIIIIIII?+Z::::7:~~??~~~IIIIII777O + ZZZZZ77777????WW7IIIIIIIIIIIIIIIIIII+++II$?~~~7~~~7~~~IIII77777O + ZZZZ$77777?NWNIIIIIIIIIIIIIIIIIII+++7IIIIIII~~~7~~~7~~=7777777OO + DZZZZ$777$WW?IIIIIIIIIIIIIIIIII?++IIIIIIIIII7=~~~7~~I+==77777OOON + 8ZZZZ777$W++++=I$IIIIIIIIIIII++?IIIIIIIIIIII7$~~~7~~~7==OOOOOOOO + 8ZZZ$77OW++====~~~~OI?IIII+++7IIIIIIIIIIIIIII7=~~=+==7=$OOOOOO8 + 8ZZZ$7OW+====~~~~::::ZI?++77IIIIIIIIIIIIII7777O$I+II$Z$$OOOOOO + 8ZZZ$OW====~~~::::,,,::IIIIIIIIIIIIIIIII777777O$$$Z$$O$$OOOON + DZZZOW~~~~~~::::,,,:::::Z7IIIIIIIIII7I777777778$$$Z$$Z$Z88 + ZZZW~~~~::::,,,,::::~~~~7IIIIIIIII777777777778$$$$$O$$WWN + Z8W~~::::,,,::::::~~~~~==7IIII777777777777777O$$Z$$DWWZ$Z + W~:::,,,,:::::~~~~~====+?7II777777777777777$$$$ZWWOZZZZD + :::,,,:::::~~~~~=====++++$77777777777777777O$WWN77ZZZZ$ + NNNN::::::~~~~=====+++++??Z7777777777777777ZWW7777ZZZZZ + N???+DD~~~~~=====+++++??????77777777777777WW$77777ZZZZ$8 + +?????IN~~=====+++++????IIII77777777777WW$7777777ZZZZZZ + N???????ONN===+++++????IIIIII777777777WWD?77777777$ZZZZZ + N????++? NDNO++++????IIIIIIIIIZ77777ZWN?I77777777ZZZZ$$8 + N+?+?ZNNNDDDDN+?????IIIIIIIIII?777$WW77777777777$ZZZZZZ + NNNNNNDDDDDDDDN???IIIIIIIIIIIIIZ7WWO77777777777ZZZZZZZZ + NDDDDDDDDDDDNNOIIIIIII7II7IIIIDWD7777777777$ZZZZZZZ$Z + NNNND8OOZZZZ$$$$$$$$ZZZZZZZZ$Z$O + $ZZZZZZZZZZZZZZZZZZZZZZZ$Z$N + 8$$ZZZZZZZZZZZZZZZZ$O + N8888888D + + + + + + + 0 + + + + + + Courier New + 18 + + + + background:rgba(0,255,0,0.25); + + + > + + + + + + + + Courier New + 18 + + + + border:none;background:rgba(0,255,0,0.25); + + + Press enter to begin + + + true + + + + + + + + + + + diff --git a/core_lib/core_lib.pro b/core_lib/core_lib.pro index ca24ea2405..0081ee6e63 100644 --- a/core_lib/core_lib.pro +++ b/core_lib/core_lib.pro @@ -98,7 +98,8 @@ HEADERS += \ src/activeframepool.h \ src/external/platformhandler.h \ src/external/macosx/macosxnative.h \ - src/util/pointerevent.h + src/util/pointerevent.h \ + src/util/asciiimage.h SOURCES += src/graphics/bitmap/bitmapimage.cpp \ @@ -161,7 +162,8 @@ SOURCES += src/graphics/bitmap/bitmapimage.cpp \ src/miniz.cpp \ src/qminiz.cpp \ src/activeframepool.cpp \ - src/util/pointerevent.cpp + src/util/pointerevent.cpp \ + src/util/asciiimage.cpp FORMS += \ ui/camerapropertiesdialog.ui diff --git a/core_lib/src/interface/editor.cpp b/core_lib/src/interface/editor.cpp index 81fd4c71ba..9077f6d0b0 100644 --- a/core_lib/src/interface/editor.cpp +++ b/core_lib/src/interface/editor.cpp @@ -878,7 +878,7 @@ void Editor::scrubTo(int frame) int oldFrame = mFrame; mFrame = frame; - Q_EMIT currentFrameChanged(oldFrame); + //Q_EMIT currentFrameChanged(oldFrame); Q_EMIT currentFrameChanged(frame); // FIXME: should not emit Timeline update here. diff --git a/core_lib/src/interface/scribblearea.cpp b/core_lib/src/interface/scribblearea.cpp index 2a9c769a41..d1083dfb05 100644 --- a/core_lib/src/interface/scribblearea.cpp +++ b/core_lib/src/interface/scribblearea.cpp @@ -503,6 +503,7 @@ void ScribbleArea::pointerPressEvent(PointerEvent* event) if (event->button() == Qt::LeftButton) { + qDebug() << "Calling pointerPressEvent on tool"; currentTool()->pointerPressEvent(event); } } @@ -530,6 +531,7 @@ void ScribbleArea::pointerMoveEvent(PointerEvent* event) event->accept(); return; } + qDebug() << "Calling pointerMoveEvent on tool"; currentTool()->pointerMoveEvent(event); } @@ -550,6 +552,7 @@ void ScribbleArea::pointerReleaseEvent(PointerEvent* event) } //qDebug() << "release event"; + qDebug() << "Calling pointerReleaseEvent on tool"; currentTool()->pointerReleaseEvent(event); // ---- last check (at the very bottom of mouseRelease) ---- diff --git a/core_lib/src/interface/scribblearea.h b/core_lib/src/interface/scribblearea.h index 800e362e20..2fcadd8bfa 100644 --- a/core_lib/src/interface/scribblearea.h +++ b/core_lib/src/interface/scribblearea.h @@ -162,7 +162,7 @@ public slots: void showLayerNotVisibleWarning(); -protected: +public: void tabletEvent(QTabletEvent*) override; void wheelEvent(QWheelEvent*) override; void mousePressEvent(QMouseEvent*) override; diff --git a/core_lib/src/managers/layermanager.h b/core_lib/src/managers/layermanager.h index 886818536a..0fe082aca7 100644 --- a/core_lib/src/managers/layermanager.h +++ b/core_lib/src/managers/layermanager.h @@ -69,6 +69,8 @@ class LayerManager : public BaseManager int animationLength(bool includeSounds = true); void notifyAnimationLengthChanged(); + int getIndex(Layer*) const; + Q_SIGNALS: void currentLayerChanged(int index); void layerCountChanged(int count); @@ -76,8 +78,6 @@ class LayerManager : public BaseManager void layerDeleted(int index); private: - int getIndex(Layer*) const; - int mLastCameraLayerIdx = 0; }; diff --git a/core_lib/src/managers/viewmanager.cpp b/core_lib/src/managers/viewmanager.cpp index ebcfc2c9b9..41d8ef8042 100644 --- a/core_lib/src/managers/viewmanager.cpp +++ b/core_lib/src/managers/viewmanager.cpp @@ -307,7 +307,8 @@ void ViewManager::flipVertical(bool b) void ViewManager::setCanvasSize(QSize size) { mCanvasSize = size; - mCentre = QTransform::fromTranslate(mCanvasSize.width() / 2.f, mCanvasSize.height() / 2.f); + // Don't center on resize because the camera must stay in the top left + //mCentre = QTransform::fromTranslate(mCanvasSize.width() / 2.f, mCanvasSize.height() / 2.f); updateViewTransforms(); Q_EMIT viewChanged(); diff --git a/core_lib/src/structure/layercamera.cpp b/core_lib/src/structure/layercamera.cpp index afe7aa46d5..c933b78a01 100644 --- a/core_lib/src/structure/layercamera.cpp +++ b/core_lib/src/structure/layercamera.cpp @@ -215,6 +215,11 @@ QSize LayerCamera::getViewSize() return viewRect.size(); } +void LayerCamera::setViewRect(QRect rect) +{ + viewRect = rect; +} + void LayerCamera::loadImageAtFrame( int frameNumber, qreal dx, qreal dy, qreal rotate, qreal scale) { if ( keyExists( frameNumber ) ) diff --git a/core_lib/src/structure/layercamera.h b/core_lib/src/structure/layercamera.h index c2768c78f4..7437bb9bd4 100644 --- a/core_lib/src/structure/layercamera.h +++ b/core_lib/src/structure/layercamera.h @@ -66,6 +66,8 @@ class LayerCamera : public Layer QRect getViewRect(); QSize getViewSize(); + void setViewRect(QRect rect); + signals: void resolutionChanged(); diff --git a/core_lib/src/tool/strokemanager.cpp b/core_lib/src/tool/strokemanager.cpp index 3c83db4cad..ff26e6cc72 100644 --- a/core_lib/src/tool/strokemanager.cpp +++ b/core_lib/src/tool/strokemanager.cpp @@ -74,18 +74,10 @@ void StrokeManager::pointerPressEvent(PointerEvent* event) void StrokeManager::pointerMoveEvent(PointerEvent* event) { - // only applied to drawing tools. - if (mStabilizerLevel != -1) - { - smoothMousePos(event->posF()); - } - else - { - // No smoothing - mLastPixel = mCurrentPixel; - mCurrentPixel = event->posF(); - mLastInterpolated = mCurrentPixel; - } + // No smoothing + mLastPixel = mCurrentPixel; + mCurrentPixel = event->posF(); + mLastInterpolated = mCurrentPixel; if(event->isTabletEvent()) { setPressure(event->pressure()); @@ -230,6 +222,9 @@ QList StrokeManager::interpolateStroke() { // is nan initially QList result; + // Disable stroke interpolation + noInpolOp(result); + return result; if (mStabilizerLevel == StabilizationLevel::SIMPLE) { diff --git a/core_lib/src/util/asciiimage.cpp b/core_lib/src/util/asciiimage.cpp new file mode 100644 index 0000000000..9ec991bae1 --- /dev/null +++ b/core_lib/src/util/asciiimage.cpp @@ -0,0 +1,41 @@ +#include "asciiimage.h" + +#include +#include +#include +#include + +const QList AsciiImage::ASCII_LIST = {'#','#','X','X','x','x','x','+','+','+','=','=','=','-','-','-',';',';',',',',','.','.','.'}; + +AsciiImage::AsciiImage() +{ + +} + +QString AsciiImage::convert(QImage img) +{ + QString output = ""; + + for(int y = 0; y < img.height(); y++) + { + QRgb *rgbLine = (QRgb *) img.scanLine(y); + for(int x = 0; x < img.width(); x++) + { + output.append(getPixel(rgbLine[x], qAlpha(rgbLine[x]))); + } + output.append('\n'); + } + return output; +} + +QChar AsciiImage::getPixel(QColor c, ushort alpha) +{ + if(!c.isValid() || alpha == 0) + { + return QChar::Nbsp; + } + + // Algorithm and mappings from https://github.com/zachwill/asciifi/blob/master/static/js/application.js + return ASCII_LIST[qMin(qFloor(c.valueF() * ASCII_LIST.size()), ASCII_LIST.size()-1)]; +} + diff --git a/core_lib/src/util/asciiimage.h b/core_lib/src/util/asciiimage.h new file mode 100644 index 0000000000..ce86e52e71 --- /dev/null +++ b/core_lib/src/util/asciiimage.h @@ -0,0 +1,22 @@ +#ifndef ASCIIIMAGE_H +#define ASCIIIMAGE_H + +#include + +class QImage; +class QColor; +class QChar; + +class AsciiImage +{ +public: + AsciiImage(); + + static QString convert(QImage img); + static QChar getPixel(QColor c, ushort alpha); + +private: + static const QList ASCII_LIST; +}; + +#endif // ASCIIIMAGE_H diff --git a/tests/src/test_viewmanager.cpp b/tests/src/test_viewmanager.cpp index b8c0d985c7..25d5c6b29c 100644 --- a/tests/src/test_viewmanager.cpp +++ b/tests/src/test_viewmanager.cpp @@ -302,8 +302,6 @@ TEST_CASE("ViewManager: canvas size") QTransform t = v.getView(); - REQUIRE(t.dx() == 50.0); // should be half of the canvas width - REQUIRE(t.dy() == 100.0); // should be half of the canvas height REQUIRE(t.isRotating() == false); REQUIRE(t.isScaling() == false); } @@ -318,8 +316,6 @@ TEST_CASE("ViewManager: canvas size") QTransform t = v.getView(); - REQUIRE(t.dx() == 240.0 + 200.0); // should be half of the canvas width + translation x offset - REQUIRE(t.dy() == 180.0 + 200.0); // should be half of the canvas height + translation y offset REQUIRE(t.isRotating() == false); REQUIRE(t.isScaling() == false); }