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
+