Merge branch 'release/5.1'

This commit is contained in:
Christian Speckner 2018-01-29 21:17:00 +01:00
commit 87c59db4e0
20 changed files with 578 additions and 116 deletions

View File

@ -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

View File

@ -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.<br>
becomes larger the further they are back in time.<br>
(*) Compresion only works if 'Uncompressed size' is smaller than
'Buffer size'.
</td>

View File

@ -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.
*/

View File

@ -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<RewindState>::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<RewindState>::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<RewindState>::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;
}

View File

@ -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;

View File

@ -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);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -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.

View File

@ -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

View File

@ -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);

View File

@ -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;
}

View File

@ -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)
{

View File

@ -136,6 +136,7 @@ class EventHandler
void leaveMenuMode();
bool enterDebugMode();
void leaveDebugMode();
void enterTimeMachineMenuMode(uInt32 numWinds, bool unwind);
void takeSnapshot(uInt32 number = 0);
/**

232
src/gui/TimeLineWidget.cxx Normal file
View File

@ -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__);
}

View File

@ -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

View File

@ -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());

View File

@ -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;

View File

@ -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 \

View File

@ -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 = "<group>"; };
DCE5CDE21BA10024005CD08A /* RiotRamWidget.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RiotRamWidget.hxx; sourceTree = "<group>"; };
DCE8B1861E7E03B300189864 /* FrameLayout.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FrameLayout.hxx; sourceTree = "<group>"; };
DCE91589201543B900960CC0 /* TimeLineWidget.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimeLineWidget.cxx; sourceTree = "<group>"; };
DCE9158A201543B900960CC0 /* TimeLineWidget.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimeLineWidget.hxx; sourceTree = "<group>"; };
DCEC58561E945125002F0246 /* DelayQueueWidget.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DelayQueueWidget.cxx; sourceTree = "<group>"; };
DCEC58571E945125002F0246 /* DelayQueueWidget.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DelayQueueWidget.hxx; sourceTree = "<group>"; };
DCEC585B1E945175002F0246 /* DelayQueueIterator.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DelayQueueIterator.hxx; sourceTree = "<group>"; };
@ -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 */,

View File

@ -346,6 +346,7 @@
<ClCompile Include="..\gui\LoggerDialog.cxx" />
<ClCompile Include="..\gui\RadioButtonWidget.cxx" />
<ClCompile Include="..\gui\SnapshotDialog.cxx" />
<ClCompile Include="..\gui\TimeLineWidget.cxx" />
<ClCompile Include="..\gui\TimeMachine.cxx" />
<ClCompile Include="..\gui\TimeMachineDialog.cxx" />
<ClCompile Include="FSNodeWINDOWS.cxx" />
@ -651,6 +652,7 @@
<ClInclude Include="..\gui\LoggerDialog.hxx" />
<ClInclude Include="..\gui\RadioButtonWidget.hxx" />
<ClInclude Include="..\gui\SnapshotDialog.hxx" />
<ClInclude Include="..\gui\TimeLineWidget.hxx" />
<ClInclude Include="..\gui\TimeMachine.hxx" />
<ClInclude Include="..\gui\TimeMachineDialog.hxx" />
<ClInclude Include="..\libpng\pngdebug.h" />

View File

@ -891,6 +891,9 @@
<ClCompile Include="..\emucore\tia\frame-manager\YStartDetector.cxx">
<Filter>Source Files\emucore\tia</Filter>
</ClCompile>
<ClCompile Include="..\gui\TimeLineWidget.cxx">
<Filter>Source Files\gui</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\common\bspf.hxx">
@ -1823,6 +1826,9 @@
<ClInclude Include="..\emucore\tia\TIAConstants.hxx">
<Filter>Header Files\emucore\tia</Filter>
</ClInclude>
<ClInclude Include="..\gui\TimeLineWidget.hxx">
<Filter>Header Files\gui</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="stella.ico">