diff --git a/Makefile b/Makefile index 2b0c212c8..35a1e3606 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ endif ifdef CLANG_WARNINGS CXXFLAGS+= -Weverything -Wno-c++17-extensions -Wno-c++98-compat -Wno-c++98-compat-pedantic \ -Wno-double-promotion -Wno-switch-enum -Wno-conversion -Wno-covered-switch-default \ - -Wno-inconsistent-missing-destructor-override \ + -Wno-inconsistent-missing-destructor-override -Wno-float-equal \ -Wno-exit-time-destructors -Wno-global-constructors -Wno-weak-vtables \ -Wno-four-char-constants -Wno-padded endif diff --git a/docs/index.html b/docs/index.html index 49c319547..a89ca6d67 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3191,8 +3191,7 @@ going back further in time. To reach the horizon, save states will be compressed (*). This means that more and more intermediate states will be removed and the interval between save states - becomes larger the further they are back in time. The very first - save state will not be removed.
+ becomes larger the further they are back in time.
(*) Compresion only works if 'Uncompressed size' is smaller than 'Buffer size'. diff --git a/src/common/LinkedObjectPool.hxx b/src/common/LinkedObjectPool.hxx index 3bbabfb9f..1b434cea8 100644 --- a/src/common/LinkedObjectPool.hxx +++ b/src/common/LinkedObjectPool.hxx @@ -129,6 +129,12 @@ class LinkedObjectPool */ const_iter next(const_iter i) const { return std::next(i, 1); } + /** + Canonical iterators from C++ STL. + */ + const_iter cbegin() const { return myList.cbegin(); } + const_iter cend() const { return myList.cend(); } + /** Answer whether 'current' is at the specified iterator. */ diff --git a/src/common/RewindManager.cxx b/src/common/RewindManager.cxx index f6d6d0b62..165eb1e19 100644 --- a/src/common/RewindManager.cxx +++ b/src/common/RewindManager.cxx @@ -42,7 +42,7 @@ void RewindManager::setup() mySize = myOSystem.settings().getInt(prefix + "tm.size"); if(mySize != myStateList.capacity()) - myStateList.resize(mySize); + resize(mySize); myUncompressed = myOSystem.settings().getInt(prefix + "tm.uncompressed"); @@ -56,7 +56,7 @@ void RewindManager::setup() if(HOR_SETTINGS[i] == myOSystem.settings().getString(prefix + "tm.horizon")) myHorizon = HORIZON_CYCLES[i]; - // calc interval growth factor + // calc interval growth factor for compression // this factor defines the backward horizon const double MAX_FACTOR = 1E8; double minFactor = 0, maxFactor = MAX_FACTOR; @@ -71,8 +71,8 @@ void RewindManager::setup() // horizon not reachable? if(myFactor == MAX_FACTOR) break; - // sum up interval cycles (first and last state are not compressed) - for(uInt32 i = myUncompressed + 1; i < mySize - 1; ++i) + // sum up interval cycles (first state is not compressed) + for(uInt32 i = myUncompressed + 1; i < mySize; ++i) { interval *= myFactor; cycleSum += interval; @@ -88,7 +88,6 @@ void RewindManager::setup() else maxFactor = myFactor; } -//cerr << "factor " << myFactor << endl; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -138,7 +137,7 @@ bool RewindManager::addState(const string& message, bool timeMachine) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 RewindManager::rewindState(uInt32 numStates) +uInt32 RewindManager::rewindStates(uInt32 numStates) { uInt64 startCycles = myOSystem.console().tia().cycles(); uInt32 i; @@ -146,7 +145,7 @@ uInt32 RewindManager::rewindState(uInt32 numStates) for(i = 0; i < numStates; ++i) { - if(!atFirst()) + if(!atFirst()) { if(!myLastTimeMachineAdd) // Set internal current iterator to previous state (back in time), @@ -177,7 +176,7 @@ uInt32 RewindManager::rewindState(uInt32 numStates) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 RewindManager::unwindState(uInt32 numStates) +uInt32 RewindManager::unwindStates(uInt32 numStates) { uInt64 startCycles = myOSystem.console().tia().cycles(); uInt32 i; @@ -210,44 +209,48 @@ uInt32 RewindManager::unwindState(uInt32 numStates) return i; } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 RewindManager::windStates(uInt32 numStates, bool unwind) +{ + if(unwind) + return unwindStates(numStates); + else + return rewindStates(numStates); +} + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void RewindManager::compressStates() { double expectedCycles = myInterval * myFactor * (1 + myFactor); double maxError = 1.5; uInt32 idx = myStateList.size() - 2; - //uInt32 removeIdx = 0; // in case maxError is <= 1.5 remove first state by default: Common::LinkedObjectPool::const_iter removeIter = myStateList.first(); - if(myUncompressed < mySize) + /*if(myUncompressed < mySize) // if compression is enabled, the first but one state is removed by default: - removeIter++; + removeIter++;*/ - //cerr << "idx: " << idx << endl; // iterate from last but one to first but one for(auto it = myStateList.previous(myStateList.last()); it != myStateList.first(); --it) { if(idx < mySize - myUncompressed) { -//cerr << *it << endl << endl; // debug code expectedCycles *= myFactor; uInt64 prevCycles = myStateList.previous(it)->cycles; uInt64 nextCycles = myStateList.next(it)->cycles; double error = expectedCycles / (nextCycles - prevCycles); -//cerr << "prevCycles: " << prevCycles << ", nextCycles: " << nextCycles << ", error: " << error << endl; if(error > maxError) { maxError = error; removeIter = it; - //removeIdx = idx; } } --idx; } myStateList.remove(removeIter); // remove -//cerr << "remove " << removeIdx << endl; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -295,7 +298,7 @@ string RewindManager::getUnitString(Int64 cycles) { // use the lower unit up to twice the nextCycles unit, except for an exact match of the nextCycles unit // TODO: does the latter make sense, e.g. for ROMs with changing scanlines? - if(cycles < UNIT_CYCLES[i + 1] * 2 && cycles % UNIT_CYCLES[i + 1] != 0) + if(cycles == 0 || cycles < UNIT_CYCLES[i + 1] * 2 && cycles % UNIT_CYCLES[i + 1] != 0) break; } result << cycles / UNIT_CYCLES[i] << " " << UNIT_NAMES[i]; @@ -306,14 +309,14 @@ string RewindManager::getUnitString(Int64 cycles) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 RewindManager::getFirstCycles() +uInt32 RewindManager::getFirstCycles() const { // TODO: check if valid return Common::LinkedObjectPool::const_iter(myStateList.first())->cycles; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 RewindManager::getCurrentCycles() +uInt32 RewindManager::getCurrentCycles() const { if(myStateList.currentIsValid()) return myStateList.current().cycles; @@ -322,9 +325,20 @@ uInt32 RewindManager::getCurrentCycles() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 RewindManager::getLastCycles() +uInt32 RewindManager::getLastCycles() const { // TODO: check if valid return Common::LinkedObjectPool::const_iter(myStateList.last())->cycles; } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +IntArray RewindManager::cyclesList() const +{ + IntArray arr; + + uInt64 firstCycle = getFirstCycles(); + for(auto it = myStateList.cbegin(); it != myStateList.cend(); ++it) + arr.push_back(uInt32(it->cycles - firstCycle)); + + return arr; +} diff --git a/src/common/RewindManager.hxx b/src/common/RewindManager.hxx index 8749aa109..6066beadf 100644 --- a/src/common/RewindManager.hxx +++ b/src/common/RewindManager.hxx @@ -38,6 +38,9 @@ class StateManager; to the end of the list (aka, all future states) are removed, and the internal iterator moves to the insertion point of the data (the end of the list). + If the list is full, states are either removed at the beginning (compression + off) or at selective positions (compression on). + @author Stephen Anthony */ class RewindManager @@ -106,22 +109,32 @@ class RewindManager bool addState(const string& message, bool timeMachine = false); /** - Rewind one level of the state list, and display the message associated + Rewind numStates levels of the state list, and display the message associated with that state. @param numStates Number of states to rewind @return Number of states to rewinded */ - uInt32 rewindState(uInt32 numStates = 1); + uInt32 rewindStates(uInt32 numStates = 1); /** - Unwind one level of the state list, and display the message associated + Unwind numStates levels of the state list, and display the message associated with that state. @param numStates Number of states to unwind @return Number of states to unwinded */ - uInt32 unwindState(uInt32 numStates = 1); + uInt32 unwindStates(uInt32 numStates = 1); + + /** + Rewind/unwind numStates levels of the state list, and display the message associated + with that state. + + @param numStates Number of states to wind + @param unwind unwind or rewind + @return Number of states to winded + */ + uInt32 windStates(uInt32 numStates, bool unwind); bool atFirst() const { return myStateList.atFirst(); } bool atLast() const { return myStateList.atLast(); } @@ -136,9 +149,15 @@ class RewindManager uInt32 getCurrentIdx() { return myStateList.currentIdx(); } uInt32 getLastIdx() { return myStateList.size(); } - uInt32 getFirstCycles(); - uInt32 getCurrentCycles(); - uInt32 getLastCycles(); + uInt32 getFirstCycles() const; + uInt32 getCurrentCycles() const; + uInt32 getLastCycles() const; + + /** + Get a collection of cycle timestamps, offset from the first one in + the list. This also determines the number of states in the list. + */ + IntArray cyclesList() const; private: OSystem& myOSystem; diff --git a/src/common/StateManager.cxx b/src/common/StateManager.cxx index 706f8f019..5ab51fb8f 100644 --- a/src/common/StateManager.cxx +++ b/src/common/StateManager.cxx @@ -142,17 +142,35 @@ void StateManager::toggleTimeMachine() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool StateManager::rewindState(uInt32 numStates) +bool StateManager::addExtraState(const string& message) { - RewindManager& r = myOSystem.state().rewindManager(); - return r.rewindState(numStates); + if(myActiveMode == Mode::TimeMachine) + { + RewindManager& r = myOSystem.state().rewindManager(); + return r.addState(message); + } + return false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool StateManager::unwindState(uInt32 numStates) +bool StateManager::rewindStates(uInt32 numStates) { RewindManager& r = myOSystem.state().rewindManager(); - return r.unwindState(numStates); + return r.rewindStates(numStates); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool StateManager::unwindStates(uInt32 numStates) +{ + RewindManager& r = myOSystem.state().rewindManager(); + return r.unwindStates(numStates); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool StateManager::windStates(uInt32 numStates, bool unwind) +{ + RewindManager& r = myOSystem.state().rewindManager(); + return r.windStates(numStates, unwind); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/common/StateManager.hxx b/src/common/StateManager.hxx index 0e322f8dd..851694299 100644 --- a/src/common/StateManager.hxx +++ b/src/common/StateManager.hxx @@ -72,14 +72,25 @@ class StateManager void setRewindMode(Mode mode) { myActiveMode = mode; } /** - Rewinds one state; this uses the RewindManager for its functionality. + Optionally adds one extra state when entering the Time Machine dialog; + this uses the RewindManager for its functionality. */ - bool rewindState(uInt32 numStates = 1); + bool addExtraState(const string& message); /** - Unwinds one state; this uses the RewindManager for its functionality. + Rewinds states; this uses the RewindManager for its functionality. */ - bool unwindState(uInt32 numStates = 1); + bool rewindStates(uInt32 numStates = 1); + + /** + Unwinds states; this uses the RewindManager for its functionality. + */ + bool unwindStates(uInt32 numStates = 1); + + /** + Rewinds/unwinds states; this uses the RewindManager for its functionality. + */ + bool windStates(uInt32 numStates, bool unwind); /** Updates the state of the system based on the currently active mode. diff --git a/src/common/Version.hxx b/src/common/Version.hxx index 44ab97567..c8a5972f8 100644 --- a/src/common/Version.hxx +++ b/src/common/Version.hxx @@ -18,7 +18,7 @@ #ifndef VERSION_HXX #define VERSION_HXX -#define STELLA_VERSION "5.1_a2" +#define STELLA_VERSION "5.1_b1" #define STELLA_BUILD "3826" #endif diff --git a/src/debugger/Debugger.cxx b/src/debugger/Debugger.cxx index 0eb01abcf..c840e9a78 100644 --- a/src/debugger/Debugger.cxx +++ b/src/debugger/Debugger.cxx @@ -538,7 +538,7 @@ uInt16 Debugger::windStates(uInt16 numStates, bool unwind, string& message) unlockBankswitchState(); uInt64 startCycles = myOSystem.console().tia().cycles(); - uInt16 winds = unwind ? r.unwindState(numStates) : r.rewindState(numStates); + uInt16 winds = r.windStates(numStates, unwind); message = r.getUnitString(myOSystem.console().tia().cycles() - startCycles); lockBankswitchState(); @@ -620,7 +620,8 @@ void Debugger::setStartState() // Save initial state and add it to the rewind list (except when in currently rewinding) RewindManager& r = myOSystem.state().rewindManager(); // avoid invalidating future states when entering the debugger e.g. during rewind - if(myOSystem.eventHandler().state() == EventHandlerState::EMULATION) + if(r.atLast() && (myOSystem.eventHandler().state() != EventHandlerState::TIMEMACHINE + || myOSystem.state().mode() == StateManager::Mode::Off)) addState("enter debugger"); else updateRewindbuttons(r); diff --git a/src/debugger/gui/DebuggerDialog.cxx b/src/debugger/gui/DebuggerDialog.cxx index 75ae3235b..861deff3f 100644 --- a/src/debugger/gui/DebuggerDialog.cxx +++ b/src/debugger/gui/DebuggerDialog.cxx @@ -93,11 +93,39 @@ void DebuggerDialog::handleKeyDown(StellaKey key, StellaMod mod) else if(key == KBDK_F12) { instance().debugger().parser().run("savesnap"); + return; + } + else if(StellaModTest::isAlt(mod) && !StellaModTest::isControl(mod)) + { + switch(key) + { + case KBDK_LEFT: // Alt-left(-shift) rewinds 1(10) states + if(StellaModTest::isShift(mod)) + doRewind10(); + else + doRewind(); + return; + case KBDK_RIGHT: // Alt-right(-shift) unwinds 1(10) states + if(StellaModTest::isShift(mod)) + doUnwind10(); + else + doUnwind(); + return; + case KBDK_DOWN: // Alt-down rewinds to start of list + doRewindAll(); + return; + case KBDK_UP: // Alt-up rewinds to end of list + doUnwindAll(); + return; + default: + break; + } } else if(StellaModTest::isControl(mod)) { switch(key) { +#if 0 case KBDK_R: if(StellaModTest::isAlt(mod)) doRewindAll(); @@ -105,7 +133,7 @@ void DebuggerDialog::handleKeyDown(StellaKey key, StellaMod mod) doRewind10(); else doRewind(); - break; + return; case KBDK_Y: if(StellaModTest::isAlt(mod)) doUnwindAll(); @@ -113,19 +141,20 @@ void DebuggerDialog::handleKeyDown(StellaKey key, StellaMod mod) doUnwind10(); else doUnwind(); - break; + return; +#endif case KBDK_S: doStep(); - break; + return; case KBDK_T: doTrace(); - break; + return; case KBDK_L: doScanlineAdvance(); - break; + return; case KBDK_F: doAdvance(); - break; + return; default: break; } diff --git a/src/emucore/EventHandler.cxx b/src/emucore/EventHandler.cxx index 419e764cb..ac36877ab 100644 --- a/src/emucore/EventHandler.cxx +++ b/src/emucore/EventHandler.cxx @@ -294,33 +294,25 @@ void EventHandler::handleKeyEvent(StellaKey key, StellaMod mod, bool state) { myOSystem.frameBuffer().toggleFullscreen(); } - // state rewinding must work in pause mode too + // State rewinding must work in pause mode too else if(myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE) { switch(key) { case KBDK_LEFT: // Alt-left(-shift) rewinds 1(10) states - myOSystem.frameBuffer().setPauseDelay(); - setEventState(EventHandlerState::PAUSE); - myOSystem.state().rewindState((StellaModTest::isShift(mod) && state) ? 10 : 1); + enterTimeMachineMenuMode((StellaModTest::isShift(mod) && state) ? 10 : 1, false); break; case KBDK_RIGHT: // Alt-right(-shift) unwinds 1(10) states - myOSystem.frameBuffer().setPauseDelay(); - setEventState(EventHandlerState::PAUSE); - myOSystem.state().unwindState((StellaModTest::isShift(mod) && state) ? 10 : 1); + enterTimeMachineMenuMode((StellaModTest::isShift(mod) && state) ? 10 : 1, true); break; case KBDK_DOWN: // Alt-down rewinds to start of list - myOSystem.frameBuffer().setPauseDelay(); - setEventState(EventHandlerState::PAUSE); - myOSystem.state().rewindState(1000); + enterTimeMachineMenuMode(1000, false); break; case KBDK_UP: // Alt-up rewinds to end of list - myOSystem.frameBuffer().setPauseDelay(); - setEventState(EventHandlerState::PAUSE); - myOSystem.state().unwindState(1000); + enterTimeMachineMenuMode(1000, true); break; default: @@ -1263,7 +1255,7 @@ bool EventHandler::eventStateChange(Event::Type type) case Event::TimeMachineMode: if(myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE) - enterMenuMode(EventHandlerState::TIMEMACHINE); + enterTimeMachineMenuMode(0, false); else if(myState == EventHandlerState::TIMEMACHINE) leaveMenuMode(); else @@ -1271,8 +1263,7 @@ bool EventHandler::eventStateChange(Event::Type type) break; case Event::DebuggerMode: - if(myState == EventHandlerState::EMULATION - || myState == EventHandlerState::PAUSE + if(myState == EventHandlerState::EMULATION || myState == EventHandlerState::PAUSE || myState == EventHandlerState::TIMEMACHINE) enterDebugMode(); else if(myState == EventHandlerState::DEBUGGER && myOSystem.debugger().canExit()) @@ -2164,6 +2155,17 @@ void EventHandler::leaveDebugMode() #endif } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EventHandler::enterTimeMachineMenuMode(uInt32 numWinds, bool unwind) +{ + // add one extra state if we are in Time Machine mode + // TODO: maybe remove this state if we leave the menu at this new state + myOSystem.state().addExtraState("enter Time Machine dialog"); // force new state + + // TODO: display last wind message (numWinds != 0) in time machine dialog + enterMenuMode(EventHandlerState::TIMEMACHINE); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void EventHandler::setEventState(EventHandlerState state) { diff --git a/src/emucore/EventHandler.hxx b/src/emucore/EventHandler.hxx index 72adbc12b..4821f4ca2 100644 --- a/src/emucore/EventHandler.hxx +++ b/src/emucore/EventHandler.hxx @@ -136,6 +136,7 @@ class EventHandler void leaveMenuMode(); bool enterDebugMode(); void leaveDebugMode(); + void enterTimeMachineMenuMode(uInt32 numWinds, bool unwind); void takeSnapshot(uInt32 number = 0); /** diff --git a/src/gui/TimeLineWidget.cxx b/src/gui/TimeLineWidget.cxx new file mode 100644 index 000000000..d99c7d58a --- /dev/null +++ b/src/gui/TimeLineWidget.cxx @@ -0,0 +1,232 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#include "bspf.hxx" +#include "Command.hxx" +#include "Dialog.hxx" +#include "Font.hxx" +#include "FBSurface.hxx" +#include "GuiObject.hxx" +#include "OSystem.hxx" + +#include "TimeLineWidget.hxx" + +// TODO - remove all references to _stepValue__ +// - fix posToValue to use _stepValue + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +TimeLineWidget::TimeLineWidget(GuiObject* boss, const GUI::Font& font, + int x, int y, int w, int h, + const string& label, int labelWidth, int cmd) + : ButtonWidget(boss, font, x, y, w, h, label, cmd), + _value(0), + _stepValue__(1), + _valueMin(0), + _valueMax(100), + _isDragging(false), + _labelWidth(labelWidth) +{ + _flags = WIDGET_ENABLED | WIDGET_TRACK_MOUSE; + _bgcolor = kDlgColor; + _bgcolorhi = kDlgColor; + + if(!_label.empty() && _labelWidth == 0) + _labelWidth = _font.getStringWidth(_label); + + _w = w + _labelWidth; + + _stepValue.reserve(100); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimeLineWidget::setValue(int value) +{ + if(value < _valueMin) value = _valueMin; + else if(value > _valueMax) value = _valueMax; + + if(value != _value) + { + _value = value; + setDirty(); + sendCommand(_cmd, _value, _id); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimeLineWidget::setMinValue(int value) +{ + _valueMin = value; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimeLineWidget::setMaxValue(int value) +{ + _valueMax = value; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimeLineWidget::setStepValues(const IntArray& steps) +{ + // Try to allocate as infrequently as possible + if(steps.size() > _stepValue.capacity()) + _stepValue.reserve(2 * steps.size()); + _stepValue.clear(); + + double scale = (_w - _labelWidth - 4) / double(steps.back()); + + // Skip the very last value; we take care of it outside the end of the loop + for(uInt32 i = 0; i < steps.size() - 1; ++i) + _stepValue.push_back(int(steps[i] * scale)); + + // Due to integer <-> double conversion, the last value is sometimes + // slightly less than the maximum value; we assign it manually to fix this + _stepValue.push_back(_w - _labelWidth - 4); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimeLineWidget::handleMouseMoved(int x, int y) +{ + // TODO: when the mouse is dragged outside the widget, the slider should + // snap back to the old value. + if(isEnabled() && _isDragging && x >= int(_labelWidth)) + setValue(posToValue(x - _labelWidth)); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimeLineWidget::handleMouseDown(int x, int y, MouseButton b, int clickCount) +{ + if(isEnabled() && b == MouseButton::LEFT) + { + _isDragging = true; + handleMouseMoved(x, y); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimeLineWidget::handleMouseUp(int x, int y, MouseButton b, int clickCount) +{ + if(isEnabled() && _isDragging) + sendCommand(_cmd, _value, _id); + + _isDragging = false; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimeLineWidget::handleMouseWheel(int x, int y, int direction) +{ + if(isEnabled()) + { + if(direction < 0) + handleEvent(Event::UIUp); + else if(direction > 0) + handleEvent(Event::UIDown); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool TimeLineWidget::handleEvent(Event::Type e) +{ + if(!isEnabled()) + return false; + + switch(e) + { + case Event::UIDown: + case Event::UILeft: + case Event::UIPgDown: + setValue(_value - _stepValue__); + break; + + case Event::UIUp: + case Event::UIRight: + case Event::UIPgUp: + setValue(_value + _stepValue__); + break; + + case Event::UIHome: + setValue(_valueMin); + break; + + case Event::UIEnd: + setValue(_valueMax); + break; + + default: + return false; + } + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimeLineWidget::drawWidget(bool hilite) +{ + FBSurface& s = _boss->dialog().surface(); + +#ifndef FLAT_UI + // Draw the label, if any + if(_labelWidth > 0) + s.drawString(_font, _label, _x, _y + 2, _labelWidth, + isEnabled() ? kTextColor : kColor, TextAlign::Right); + + // Draw the box + s.box(_x + _labelWidth, _y, _w - _labelWidth, _h, kColor, kShadowColor); + // Fill the box + s.fillRect(_x + _labelWidth + 2, _y + 2, _w - _labelWidth - 4, _h - 4, + !isEnabled() ? kBGColorHi : kWidColor); + // Draw the 'bar' + s.fillRect(_x + _labelWidth + 2, _y + 2, valueToPos(_value), _h - 4, + !isEnabled() ? kColor : hilite ? kSliderColorHi : kSliderColor); +#else + // Draw the label, if any + if(_labelWidth > 0) + s.drawString(_font, _label, _x, _y + 2, _labelWidth, + isEnabled() ? kTextColor : kColor, TextAlign::Left); + + // Draw the box + s.frameRect(_x + _labelWidth, _y, _w - _labelWidth, _h, isEnabled() && hilite ? kSliderColorHi : kShadowColor); + // Fill the box + s.fillRect(_x + _labelWidth + 1, _y + 1, _w - _labelWidth - 2, _h - 2, + !isEnabled() ? kBGColorHi : kWidColor); + // Draw the 'bar' + s.fillRect(_x + _labelWidth + 2, _y + 2, valueToPos(_value), _h - 4, + !isEnabled() ? kColor : hilite ? kSliderColorHi : kSliderColor); +#endif +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +int TimeLineWidget::valueToPos(int value) +{ + if(value < _valueMin) value = _valueMin; + else if(value > _valueMax) value = _valueMax; + + int real = _stepValue[BSPF::clamp(value, _valueMin, _valueMax)]; +#if 0 + int range = std::max(_valueMax - _valueMin, 1); // don't divide by zero + int actual = ((_w - _labelWidth - 4) * (value - _valueMin) / range); +cerr << "i=" << value << " real=" << real << endl << "actual=" << actual << endl << endl; +#endif + return real; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +int TimeLineWidget::posToValue(int pos) +{ + int value = (pos) * (_valueMax - _valueMin) / (_w - _labelWidth - 4) + _valueMin; + + // Scale the position to the correct interval (according to step value) + return value - (value % _stepValue__); +} diff --git a/src/gui/TimeLineWidget.hxx b/src/gui/TimeLineWidget.hxx new file mode 100644 index 000000000..397ee241e --- /dev/null +++ b/src/gui/TimeLineWidget.hxx @@ -0,0 +1,73 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef TIMELINE_WIDGET_HXX +#define TIMELINE_WIDGET_HXX + +#include "Widget.hxx" + +class TimeLineWidget : public ButtonWidget +{ + public: + TimeLineWidget(GuiObject* boss, const GUI::Font& font, + int x, int y, int w, int h, const string& label = "", + int labelWidth = 0, int cmd = 0); + + void setValue(int value); + int getValue() const { return _value; } + + void setMinValue(int value); + int getMinValue() const { return _valueMin; } + void setMaxValue(int value); + int getMaxValue() const { return _valueMax; } + + /** + Steps are not necessarily linear in a timeline, so we need info + on each interval instead. + */ + void setStepValues(const IntArray& steps); + + protected: + void handleMouseMoved(int x, int y) override; + void handleMouseDown(int x, int y, MouseButton b, int clickCount) override; + void handleMouseUp(int x, int y, MouseButton b, int clickCount) override; + void handleMouseWheel(int x, int y, int direction) override; + bool handleEvent(Event::Type event) override; + + void drawWidget(bool hilite) override; + + int valueToPos(int value); + int posToValue(int pos); + + protected: + int _value, _stepValue__; + int _valueMin, _valueMax; + bool _isDragging; + int _labelWidth; + + IntArray _stepValue; + + private: + // Following constructors and assignment operators not supported + TimeLineWidget() = delete; + TimeLineWidget(const TimeLineWidget&) = delete; + TimeLineWidget(TimeLineWidget&&) = delete; + TimeLineWidget& operator=(const TimeLineWidget&) = delete; + TimeLineWidget& operator=(TimeLineWidget&&) = delete; +}; + +#endif diff --git a/src/gui/TimeMachineDialog.cxx b/src/gui/TimeMachineDialog.cxx index 3a11374d3..9641adfed 100644 --- a/src/gui/TimeMachineDialog.cxx +++ b/src/gui/TimeMachineDialog.cxx @@ -24,18 +24,16 @@ #include "Widget.hxx" #include "StateManager.hxx" #include "RewindManager.hxx" - +#include "TimeLineWidget.hxx" #include "Console.hxx" #include "TIA.hxx" #include "System.hxx" - #include "TimeMachineDialog.hxx" #include "Base.hxx" using Common::Base; - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - TimeMachineDialog::TimeMachineDialog(OSystem& osystem, DialogContainer& parent, int max_w, int max_h) @@ -43,23 +41,6 @@ TimeMachineDialog::TimeMachineDialog(OSystem& osystem, DialogContainer& parent, { const int BUTTON_W = 16, BUTTON_H = 14; - /*static uInt32 PAUSE[BUTTON_H] = - { - 0b0001111001111000, - 0b0001111001111000, - 0b0001111001111000, - 0b0001111001111000, - 0b0001111001111000, - 0b0001111001111000, - 0b0001111001111000, - 0b0001111001111000, - 0b0001111001111000, - 0b0001111001111000, - 0b0001111001111000, - 0b0001111001111000, - 0b0001111001111000, - 0b0001111001111000 - };*/ static uInt32 PLAY[BUTTON_H] = { 0b0110000000000000, @@ -181,12 +162,11 @@ TimeMachineDialog::TimeMachineDialog(OSystem& osystem, DialogContainer& parent, }; const GUI::Font& font = instance().frameBuffer().font(); - const int H_BORDER = 6, BUTTON_GAP = 4, V_BORDER = 4; // FIXME, V_GAP = 4; + const int H_BORDER = 6, BUTTON_GAP = 4, V_BORDER = 4; const int buttonWidth = BUTTON_W + 8, buttonHeight = BUTTON_H + 10, rowHeight = font.getLineHeight(); - WidgetArray wid; int xpos, ypos; // Set real dimensions @@ -201,64 +181,61 @@ TimeMachineDialog::TimeMachineDialog(OSystem& osystem, DialogContainer& parent, // Add index info myCurrentIdxWidget = new StaticTextWidget(this, font, xpos, ypos, " ", TextAlign::Left, kBGColor); - myCurrentIdxWidget->setTextColor(kWidColor); + myCurrentIdxWidget->setTextColor(kColorInfo); myLastIdxWidget = new StaticTextWidget(this, font, _w - H_BORDER - font.getStringWidth("8888"), ypos, " ", TextAlign::Right, kBGColor); - myLastIdxWidget->setTextColor(kWidColor); + myLastIdxWidget->setTextColor(kColorInfo); + + // Add timeline + const uInt32 tl_h = myCurrentIdxWidget->getHeight() / 2, + tl_x = xpos + myCurrentIdxWidget->getWidth() + 8, + tl_y = ypos + (myCurrentIdxWidget->getHeight() - tl_h) / 2, + tl_w = myLastIdxWidget->getAbsX() - tl_x - 8; + myTimeline = new TimeLineWidget(this, font, tl_x, tl_y, tl_w, tl_h, "", 0, kTimeline); + myTimeline->setMinValue(0); ypos += rowHeight; // Add time info myCurrentTimeWidget = new StaticTextWidget(this, font, xpos, ypos + 3, "04:32 59", TextAlign::Left, kBGColor); - myCurrentTimeWidget->setTextColor(kWidColor); + myCurrentTimeWidget->setTextColor(kColorInfo); myLastTimeWidget = new StaticTextWidget(this, font, _w - H_BORDER - font.getStringWidth("XX:XX XX"), ypos + 3, "12:25 59", TextAlign::Right, kBGColor); - myLastTimeWidget->setTextColor(kWidColor); + myLastTimeWidget->setTextColor(kColorInfo); xpos = myCurrentTimeWidget->getRight() + BUTTON_GAP * 4; // Add buttons myRewindAllWidget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, REWIND_ALL, BUTTON_W, BUTTON_H, kRewindAll); - wid.push_back(myRewindAllWidget); xpos += buttonWidth + BUTTON_GAP; myRewind10Widget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, REWIND_10, BUTTON_W, BUTTON_H, kRewind10); - wid.push_back(myRewind10Widget); xpos += buttonWidth + BUTTON_GAP; myRewind1Widget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, REWIND_1, BUTTON_W, BUTTON_H, kRewind1); - wid.push_back(myRewind1Widget); xpos += buttonWidth + BUTTON_GAP*2; - /*myPauseWidget = new ButtonWidget(this, font, xpos, ypos - 2, buttonWidth + 4, buttonHeight + 4, PAUSE, - BUTTON_W, BUTTON_H, kPause); - wid.push_back(myPauseWidget); - myPauseWidget->clearFlags(WIDGET_ENABLED);*/ myPlayWidget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, PLAY, BUTTON_W, BUTTON_H, kPlay); - wid.push_back(myPlayWidget); xpos += buttonWidth + BUTTON_GAP*2; myUnwind1Widget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, UNWIND_1, BUTTON_W, BUTTON_H, kUnwind1); - wid.push_back(myUnwind1Widget); xpos += buttonWidth + BUTTON_GAP; myUnwind10Widget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, UNWIND_10, BUTTON_W, BUTTON_H, kUnwind10); - wid.push_back(myUnwind10Widget); xpos += buttonWidth + BUTTON_GAP; myUnwindAllWidget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, UNWIND_ALL, BUTTON_W, BUTTON_H, kUnwindAll); - wid.push_back(myUnwindAllWidget); xpos = myUnwindAllWidget->getRight() + BUTTON_GAP * 3; // Add message myMessageWidget = new StaticTextWidget(this, font, xpos, ypos + 3, " ", TextAlign::Left, kBGColor); - myMessageWidget->setTextColor(kWidColor); + myMessageWidget->setTextColor(kColorInfo); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -273,21 +250,75 @@ void TimeMachineDialog::center() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void TimeMachineDialog::loadConfig() { - surface().attributes().blending = true; - surface().attributes().blendalpha = 80; - surface().applyAttributes(); + RewindManager& r = instance().state().rewindManager(); + IntArray cycles = r.cyclesList(); + + // Set range and intervals for timeline + myTimeline->setMaxValue(cycles.size() - 1); + myTimeline->setStepValues(cycles); + + // Enable blending (only once is necessary) + if(!surface().attributes().blending) + { + surface().attributes().blending = true; + surface().attributes().blendalpha = 80; + surface().applyAttributes(); + } handleWinds(); myMessageWidget->setLabel(""); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimeMachineDialog::handleKeyDown(StellaKey key, StellaMod mod) +{ + // The following 'Alt' shortcuts duplicate the shortcuts in EventHandler + // It is best to keep them the same, so changes in EventHandler mean we + // need to update the logic here too + if(StellaModTest::isAlt(mod)) + { + switch(key) + { + case KBDK_LEFT: // Alt-left(-shift) rewinds 1(10) states + handleCommand(nullptr, StellaModTest::isShift(mod) ? kRewind10 : kRewind1, 0, 0); + break; + + case KBDK_RIGHT: // Alt-right(-shift) unwinds 1(10) states + handleCommand(nullptr, StellaModTest::isShift(mod) ? kUnwind10 : kUnwind1, 0, 0); + break; + + case KBDK_DOWN: // Alt-down rewinds to start of list + handleCommand(nullptr, kRewindAll, 0, 0); + break; + + case KBDK_UP: // Alt-up rewinds to end of list + handleCommand(nullptr, kUnwindAll, 0, 0); + break; + + default: + Dialog::handleKeyDown(key, mod); + } + } + else if(key == KBDK_SPACE || key == KBDK_ESCAPE) + handleCommand(nullptr, kPlay, 0, 0); + else + Dialog::handleKeyDown(key, mod); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void TimeMachineDialog::handleCommand(CommandSender* sender, int cmd, int data, int id) { -//cerr << cmd << endl; switch(cmd) { + case kTimeline: + { + Int32 winds = myTimeline->getValue() - + instance().state().rewindManager().getCurrentIdx() + 1; + handleWinds(winds); + break; + } + case kPlay: instance().eventHandler().leaveMenuMode(); break; @@ -321,12 +352,13 @@ void TimeMachineDialog::handleCommand(CommandSender* sender, int cmd, } } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - string TimeMachineDialog::getTimeString(uInt64 cycles) { const Int32 scanlines = std::max(instance().console().tia().scanlinesLastFrame(), 240u); const bool isNTSC = scanlines <= 287; const Int32 NTSC_FREQ = 1193182; // ~76*262*60 - const Int32 PAL_FREQ = 1182298; // ~76*312*50 + const Int32 PAL_FREQ = 1182298; // ~76*312*50 const Int32 freq = isNTSC ? NTSC_FREQ : PAL_FREQ; // = cycles/second uInt32 minutes = cycles / (freq * 60); @@ -351,21 +383,26 @@ void TimeMachineDialog::handleWinds(Int32 numWinds) if(numWinds) { uInt64 startCycles = instance().console().tia().cycles(); - if(numWinds < 0) - r.rewindState(-numWinds); - else - r.unwindState(numWinds); - string message = r.getUnitString(instance().console().tia().cycles() - startCycles); + if(numWinds < 0) r.rewindStates(-numWinds); + else if(numWinds > 0) r.unwindStates(numWinds); - myMessageWidget->setLabel((numWinds < 0 ? "(-" : "(+") + message + ")"); + uInt64 elapsed = instance().console().tia().cycles() - startCycles; + if(elapsed > 0) + { + string message = r.getUnitString(elapsed); + + // TODO: add message text from addState() + myMessageWidget->setLabel((numWinds < 0 ? "(-" : "(+") + message + ")"); + } } // Update time myCurrentTimeWidget->setLabel(getTimeString(r.getCurrentCycles() - r.getFirstCycles())); myLastTimeWidget->setLabel(getTimeString(r.getLastCycles() - r.getFirstCycles())); + myTimeline->setValue(r.getCurrentIdx()-1); // Update index myCurrentIdxWidget->setValue(r.getCurrentIdx()); myLastIdxWidget->setValue(r.getLastIdx()); - // enable/disable buttons + // Enable/disable buttons myRewindAllWidget->setEnabled(!r.atFirst()); myRewind10Widget->setEnabled(!r.atFirst()); myRewind1Widget->setEnabled(!r.atFirst()); diff --git a/src/gui/TimeMachineDialog.hxx b/src/gui/TimeMachineDialog.hxx index 05b49ce25..e842386d5 100644 --- a/src/gui/TimeMachineDialog.hxx +++ b/src/gui/TimeMachineDialog.hxx @@ -21,6 +21,7 @@ class CommandSender; class DialogContainer; class OSystem; +class TimeLineWidget; #include "Dialog.hxx" @@ -32,6 +33,7 @@ class TimeMachineDialog : public Dialog private: void loadConfig() override; + void handleKeyDown(StellaKey key, StellaMod mod) override; void handleCommand(CommandSender* sender, int cmd, int data, int id) override; /** This dialog uses its own positioning, so we override Dialog::center() */ @@ -45,7 +47,7 @@ class TimeMachineDialog : public Dialog private: enum { - kPause = 'TMps', + kTimeline = 'TMtl', kPlay = 'TMpl', kRewindAll = 'TMra', kRewind10 = 'TMr1', @@ -55,7 +57,8 @@ class TimeMachineDialog : public Dialog kUnwind1 = 'TMun', }; - // FIXME ButtonWidget* myPauseWidget; + TimeLineWidget* myTimeline; + ButtonWidget* myPlayWidget; ButtonWidget* myRewindAllWidget; ButtonWidget* myRewind10Widget; diff --git a/src/gui/module.mk b/src/gui/module.mk index 3058ac9c8..b598334c7 100644 --- a/src/gui/module.mk +++ b/src/gui/module.mk @@ -43,6 +43,7 @@ MODULE_OBJS := \ src/gui/SnapshotDialog.o \ src/gui/StringListWidget.o \ src/gui/TabWidget.o \ + src/gui/TimeLineWidget.o \ src/gui/TimeMachineDialog.o \ src/gui/TimeMachine.o \ src/gui/UIDialog.o \ diff --git a/src/macosx/stella.xcodeproj/project.pbxproj b/src/macosx/stella.xcodeproj/project.pbxproj index 0358d89a8..6942a8c56 100644 --- a/src/macosx/stella.xcodeproj/project.pbxproj +++ b/src/macosx/stella.xcodeproj/project.pbxproj @@ -570,6 +570,8 @@ DCE5CDE31BA10024005CD08A /* RiotRamWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCE5CDE11BA10024005CD08A /* RiotRamWidget.cxx */; }; DCE5CDE41BA10024005CD08A /* RiotRamWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCE5CDE21BA10024005CD08A /* RiotRamWidget.hxx */; }; DCE8B1871E7E03B300189864 /* FrameLayout.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCE8B1861E7E03B300189864 /* FrameLayout.hxx */; }; + DCE9158B201543B900960CC0 /* TimeLineWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCE91589201543B900960CC0 /* TimeLineWidget.cxx */; }; + DCE9158C201543B900960CC0 /* TimeLineWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCE9158A201543B900960CC0 /* TimeLineWidget.hxx */; }; DCEC58581E945125002F0246 /* DelayQueueWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCEC58561E945125002F0246 /* DelayQueueWidget.cxx */; }; DCEC58591E945125002F0246 /* DelayQueueWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCEC58571E945125002F0246 /* DelayQueueWidget.hxx */; }; DCEC585E1E945175002F0246 /* DelayQueueIterator.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCEC585B1E945175002F0246 /* DelayQueueIterator.hxx */; }; @@ -1239,6 +1241,8 @@ DCE5CDE11BA10024005CD08A /* RiotRamWidget.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RiotRamWidget.cxx; sourceTree = ""; }; DCE5CDE21BA10024005CD08A /* RiotRamWidget.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RiotRamWidget.hxx; sourceTree = ""; }; DCE8B1861E7E03B300189864 /* FrameLayout.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FrameLayout.hxx; sourceTree = ""; }; + DCE91589201543B900960CC0 /* TimeLineWidget.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimeLineWidget.cxx; sourceTree = ""; }; + DCE9158A201543B900960CC0 /* TimeLineWidget.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimeLineWidget.hxx; sourceTree = ""; }; DCEC58561E945125002F0246 /* DelayQueueWidget.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DelayQueueWidget.cxx; sourceTree = ""; }; DCEC58571E945125002F0246 /* DelayQueueWidget.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DelayQueueWidget.hxx; sourceTree = ""; }; DCEC585B1E945175002F0246 /* DelayQueueIterator.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DelayQueueIterator.hxx; sourceTree = ""; }; @@ -1903,6 +1907,8 @@ 2DEF21FB08BC033500B246B4 /* StringListWidget.hxx */, 2DDBEAD0084578BF00812C11 /* TabWidget.cxx */, 2DDBEAD1084578BF00812C11 /* TabWidget.hxx */, + DCE91589201543B900960CC0 /* TimeLineWidget.cxx */, + DCE9158A201543B900960CC0 /* TimeLineWidget.hxx */, DCA82C6D1FEB4E780059340F /* TimeMachine.cxx */, DCA82C6E1FEB4E780059340F /* TimeMachine.hxx */, DCA82C6F1FEB4E780059340F /* TimeMachineDialog.cxx */, @@ -2168,6 +2174,7 @@ 2D91740509BA90380026E9FF /* DialogContainer.hxx in Headers */, DC6D39881A3CE65000171E71 /* CartWDWidget.hxx in Headers */, 2D91740609BA90380026E9FF /* GameInfoDialog.hxx in Headers */, + DCE9158C201543B900960CC0 /* TimeLineWidget.hxx in Headers */, 2D91740709BA90380026E9FF /* GameList.hxx in Headers */, 2D91740809BA90380026E9FF /* GuiObject.hxx in Headers */, DC73BD861915E5B1003FAFAD /* FBSurfaceSDL2.hxx in Headers */, @@ -2581,6 +2588,7 @@ 2D9174B709BA90380026E9FF /* OptionsDialog.cxx in Sources */, 2D9174B809BA90380026E9FF /* PopUpWidget.cxx in Sources */, DCBDDE9A1D6A5F0E009DF1E9 /* Cart3EPlusWidget.cxx in Sources */, + DCE9158B201543B900960CC0 /* TimeLineWidget.cxx in Sources */, DCE5CDE31BA10024005CD08A /* RiotRamWidget.cxx in Sources */, 2D9174B909BA90380026E9FF /* ProgressDialog.cxx in Sources */, 2D9174BA09BA90380026E9FF /* ScrollBarWidget.cxx in Sources */, diff --git a/src/windows/Stella.vcxproj b/src/windows/Stella.vcxproj index c9225f627..0318c47a2 100644 --- a/src/windows/Stella.vcxproj +++ b/src/windows/Stella.vcxproj @@ -346,6 +346,7 @@ + @@ -651,6 +652,7 @@ + diff --git a/src/windows/Stella.vcxproj.filters b/src/windows/Stella.vcxproj.filters index 7997369dd..592651d33 100644 --- a/src/windows/Stella.vcxproj.filters +++ b/src/windows/Stella.vcxproj.filters @@ -891,6 +891,9 @@ Source Files\emucore\tia + + Source Files\gui + @@ -1823,6 +1826,9 @@ Header Files\emucore\tia + + Header Files\gui +