SPH
TimeLine.cpp
Go to the documentation of this file.
1 #include "windows/TimeLine.h"
2 #include "windows/Icons.data.h"
3 #include <wx/bmpbuttn.h>
4 
6 
7 std::map<int, Path> getSequenceFiles(const Path& inputPath) {
8  if (inputPath.empty()) {
9  throw Exception("sequence for empty path");
10  }
11 
12  const Path absolutePath = FileSystem::getAbsolutePath(inputPath);
13  Optional<OutputFile> deducedFile = OutputFile::getMaskFromPath(absolutePath);
14  if (!deducedFile) {
15  // just a single file, not part of a sequence (e.g. frag_final.ssf)
16  return { std::make_pair(0, absolutePath) };
17  }
18 
19  Path fileMask = deducedFile->getMask();
20  std::map<int, Path> fileMap;
21 
22  const Path dir = absolutePath.parentPath();
24 
25  for (Path& file : files) {
26  const Optional<OutputFile> deducedMask = OutputFile::getMaskFromPath(dir / file);
27  // check if part of the same sequence
28  if (deducedMask && deducedMask->getMask() == fileMask) {
29  const Optional<Size> index = OutputFile::getDumpIdx(dir / file);
30  SPH_ASSERT(index);
31  fileMap[index.value()] = dir / file;
32  }
33  }
34 
35  if (fileMap.empty()) {
36  throw Exception("Cannot open file " + inputPath.native());
37  }
38 
39  return fileMap;
40 }
41 
42 TimeLinePanel::TimeLinePanel(wxWindow* parent, const Path& inputFile, SharedPtr<ITimeLineCallbacks> callbacks)
43  : wxPanel(parent, wxID_ANY)
44  , callbacks(callbacks) {
45 
46  this->update(inputFile);
47 
48  this->SetMinSize(wxSize(300, 30));
49  this->Connect(wxEVT_PAINT, wxPaintEventHandler(TimeLinePanel::onPaint));
50  this->Connect(wxEVT_MOTION, wxMouseEventHandler(TimeLinePanel::onMouseMotion));
51  this->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(TimeLinePanel::onLeftClick));
52  this->Connect(wxEVT_KEY_UP, wxKeyEventHandler(TimeLinePanel::onKeyUp));
53 }
54 
55 void TimeLinePanel::update(const Path& inputFile) {
57  try {
58  fileMap = getSequenceFiles(inputFile);
59  } catch (const Exception& UNUSED(e)) {
60  // we already show one message box in Run, silence the error here
61  fileMap.clear();
62  currentFrame = 0;
63  return;
64  }
65 
66  OutputFile of(inputFile);
67  if (!of.hasWildcard()) {
68  currentFrame = OutputFile::getDumpIdx(inputFile).valueOr(0);
69  }
70 
71  this->Refresh();
72 }
73 
74 void TimeLinePanel::setFrame(const Size newFrame) {
75  currentFrame = newFrame;
76  this->Refresh();
77 }
78 
80  auto iter = fileMap.find(currentFrame);
81  SPH_ASSERT(iter != fileMap.end());
82  if (iter != fileMap.begin()) {
83  --iter;
84  currentFrame = iter->first;
85  this->reload();
86  }
87 }
88 
90  callbacks->startSequence(fileMap[currentFrame]);
91 }
92 
94  auto iter = fileMap.find(currentFrame);
95  SPH_ASSERT(iter != fileMap.end());
96  ++iter;
97  if (iter != fileMap.end()) {
98  currentFrame = iter->first;
99  this->reload();
100  }
101 }
102 
103 int TimeLinePanel::positionToFrame(const wxPoint position) const {
104  if (fileMap.empty()) {
105  return 0;
106  }
107  wxSize size = this->GetSize();
108  const int firstFrame = fileMap.begin()->first;
109  const int lastFrame = fileMap.rbegin()->first;
110  const int frame = firstFrame + int(roundf(float(position.x) * (lastFrame - firstFrame) / size.x));
111  const auto upperIter = fileMap.upper_bound(frame);
112  if (upperIter == fileMap.begin()) {
113  return upperIter->first;
114  } else {
115  auto lowerIter = upperIter;
116  --lowerIter;
117 
118  if (upperIter == fileMap.end()) {
119  return lowerIter->first;
120  } else {
121  // return the closer frame
122  const int lowerDist = frame - lowerIter->first;
123  const int upperDist = upperIter->first - frame;
124  return (upperDist < lowerDist) ? upperIter->first : lowerIter->first;
125  }
126  }
127 }
128 
129 void TimeLinePanel::reload() {
130  callbacks->frameChanged(fileMap[currentFrame]);
131  this->Refresh();
132 }
133 
134 void TimeLinePanel::onPaint(wxPaintEvent& UNUSED(evt)) {
135  if (fileMap.empty()) {
136  // nothing to do
137  return;
138  }
139 
140  wxPaintDC dc(this);
141  const wxSize size = dc.GetSize();
143  Rgba backgroundColor = Rgba(this->GetParent()->GetBackgroundColour());
144  wxPen pen = *wxBLACK_PEN;
145  pen.SetWidth(2);
146  wxBrush brush;
147  wxColour fillColor(backgroundColor.darken(0.3f));
148  brush.SetColour(fillColor);
149  pen.SetColour(fillColor);
150 
151  dc.SetBrush(brush);
152  dc.SetPen(pen);
153  dc.DrawRectangle(wxPoint(0, 0), size);
154  dc.SetTextForeground(wxColour(255, 255, 255));
155  wxFont font = dc.GetFont();
156  font.MakeSmaller();
157  dc.SetFont(font);
158 
159  const int fileCnt = fileMap.size();
160  if (fileCnt == 1) {
161  // nothing to draw
162  return;
163  }
164 
165  // ad hoc stepping
166  int step = 1;
167  if (fileCnt > 60) {
168  step = int(fileCnt / 60) * 5;
169  } else if (fileCnt > 30) {
170  step = 2;
171  }
172 
173  const int firstFrame = fileMap.begin()->first;
174  const int lastFrame = fileMap.rbegin()->first;
175  int i = 0;
176 
177  const bool isLightTheme = backgroundColor.intensity() > 0.5f;
178  if (isLightTheme) {
179  dc.SetTextForeground(wxColour(30, 30, 30));
180  }
181 
182  for (auto frameAndPath : fileMap) {
183  const int frame = frameAndPath.first;
184  bool keyframe = (i % step == 0);
185  bool doFull = keyframe;
186  if (frame == currentFrame) {
187  pen.SetColour(wxColour(255, 80, 0));
188  doFull = true;
189  } else if (frame == mouseFrame) {
190  pen.SetColour(wxColour(128, 128, 128));
191  doFull = true;
192  } else {
193  if (isLightTheme) {
194  pen.SetColour(wxColour(30, 30, 30));
195  } else {
196  pen.SetColour(wxColour(backgroundColor));
197  }
198  }
199  dc.SetPen(pen);
200  const int x = (frame - firstFrame) * size.x / (lastFrame - firstFrame);
201  if (doFull) {
202  dc.DrawLine(wxPoint(x, 0), wxPoint(x, size.y));
203  } else {
204  dc.DrawLine(wxPoint(x, 0), wxPoint(x, 5));
205  dc.DrawLine(wxPoint(x, size.y - 5), wxPoint(x, size.y));
206  }
207 
208  if (keyframe) {
209  const std::string text = std::to_string(frame);
210  const wxSize extent = dc.GetTextExtent(text);
211  if (x + extent.x + 3 < size.x) {
212  dc.DrawText(text, wxPoint(x + 3, size.y - 20));
213  }
214  }
215  ++i;
216  }
217 }
218 
219 void TimeLinePanel::onMouseMotion(wxMouseEvent& evt) {
220  mouseFrame = positionToFrame(evt.GetPosition());
221  this->Refresh();
222 }
223 
224 void TimeLinePanel::onLeftClick(wxMouseEvent& evt) {
225  currentFrame = positionToFrame(evt.GetPosition());
226  this->reload();
227 }
228 
229 void TimeLinePanel::onKeyUp(wxKeyEvent& evt) {
230  switch (evt.GetKeyCode()) {
231  case WXK_LEFT:
232  this->setPrevious();
233  break;
234  case WXK_RIGHT:
235  this->setNext();
236  break;
237 
238  default:
239  break;
240  }
241 }
242 
243 static wxBitmapButton* createButton(wxWindow* parent, const wxBitmap& bitmap) {
244  const wxSize buttonSize(60, 40);
245  wxBitmapButton* button = new wxBitmapButton(parent, wxID_ANY, bitmap);
246  button->SetMinSize(buttonSize);
247  return button;
248 }
249 
250 static wxBitmapButton* createButton(wxWindow* parent, char** data) {
251  wxBitmap bitmap(data);
252  return createButton(parent, bitmap);
253 }
254 
255 TimeLine::TimeLine(wxWindow* parent, const Path& inputFile, SharedPtr<ITimeLineCallbacks> callbacks)
256  : wxPanel(parent, wxID_ANY) {
257  wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
258 
259  wxImage nextImage = wxBitmap(nextData).ConvertToImage();
260  nextImage = nextImage.Mirror();
261  wxBitmapButton* prevButton = createButton(this, wxBitmap(nextImage, wxBITMAP_SCREEN_DEPTH));
262  sizer->Add(prevButton, 1, wxALL);
263 
264  wxBitmapButton* pauseButton = createButton(this, pauseData);
265  sizer->Add(pauseButton, 1, wxALL);
266 
267  wxBitmapButton* stopButton = createButton(this, stopData);
268  sizer->Add(stopButton, 1, wxALL);
269 
270  wxBitmapButton* playButton = createButton(this, playData);
271  sizer->Add(playButton, 1, wxALL);
272 
273  wxBitmapButton* nextButton = createButton(this, nextData);
274  sizer->Add(nextButton, 1, wxALL);
275 
276  sizer->AddSpacer(8);
277  timeline = new TimeLinePanel(this, inputFile, callbacks);
278  sizer->Add(timeline, 40, wxALL | wxEXPAND);
279 
280  this->SetSizer(sizer);
281  this->Layout();
282 
283  prevButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent& UNUSED(evt)) { timeline->setPrevious(); });
284  nextButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent& UNUSED(evt)) { timeline->setNext(); });
285  playButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent& UNUSED(evt)) { timeline->startSequence(); });
286  stopButton->Bind(wxEVT_BUTTON, [callbacks](wxCommandEvent& UNUSED(evt)) { callbacks->stop(); });
287  pauseButton->Bind(wxEVT_BUTTON, [callbacks](wxCommandEvent& UNUSED(evt)) { callbacks->pause(); });
288 }
289 
#define SPH_ASSERT(x,...)
Definition: Assert.h:94
NAMESPACE_SPH_BEGIN
Definition: BarnesHut.cpp:13
@ NO_THROW
Function cannot throw exceptions.
@ MAIN_THREAD
Function can only be executed from main thread.
#define CHECK_FUNCTION(flags)
Definition: CheckFunction.h:40
uint32_t Size
Integral type used to index arrays (by default).
Definition: Globals.h:16
#define UNUSED(x)
Definition: Object.h:37
#define NAMESPACE_SPH_END
Definition: Object.h:12
const wxSize buttonSize(250, -1)
NAMESPACE_SPH_BEGIN std::map< int, Path > getSequenceFiles(const Path &inputPath)
Definition: TimeLine.cpp:7
Generic dynamically allocated resizable storage.
Definition: Array.h:43
Generic exception.
Definition: Exceptions.h:10
virtual void pause() const =0
virtual void startSequence(const Path &firstFile) const =0
virtual void frameChanged(const Path &newFile) const =0
virtual void stop() const =0
Wrapper of type value of which may or may not be present.
Definition: Optional.h:23
INLINE Type & value()
Returns the reference to the stored value.
Definition: Optional.h:172
INLINE Type valueOr(const TOther &other) const
Returns the stored value if the object has been initialized, otherwise returns provided parameter.
Definition: Optional.h:188
Helper file generating file names for output files.
Definition: Output.h:21
static Optional< Size > getDumpIdx(const Path &path)
Extracts the dump index from given path generated by OutputFile.
Definition: Output.cpp:49
static Optional< OutputFile > getMaskFromPath(const Path &path, const Size firstDumpIdx=0)
Attemps to get the OutputFile from one of the path generated from it.
Definition: Output.cpp:72
bool hasWildcard() const
Returns true if the file mask contains (at least one) wildcard.
Definition: Output.cpp:89
Object representing a path on a filesystem.
Definition: Path.h:17
std::string native() const
Returns the native version of the path.
Definition: Path.cpp:71
bool empty() const
Checks if the path is empty.
Definition: Path.cpp:10
Path parentPath() const
Returns the parent directory. If the path is empty or root, return empty path.
Definition: Path.cpp:35
Definition: Color.h:8
float intensity() const
Returns the average intensity of the color.
Definition: Color.h:115
Rgba darken(const float amount) const
Returns a color darker by given factor.
Definition: Color.h:137
void startSequence()
Definition: TimeLine.cpp:89
void setPrevious()
Definition: TimeLine.cpp:79
void setFrame(const Size newFrame)
Definition: TimeLine.cpp:74
void setNext()
Definition: TimeLine.cpp:93
TimeLinePanel(wxWindow *parent, const Path &inputFile, SharedPtr< ITimeLineCallbacks > callbacks)
Definition: TimeLine.cpp:42
void update(const Path &inputFile)
Definition: TimeLine.cpp:55
TimeLine(wxWindow *parent, const Path &inputFile, SharedPtr< ITimeLineCallbacks > callbacks)
Definition: TimeLine.cpp:255
Array< Path > getFilesInDirectory(const Path &directory)
Alternatitve to iterateDirectory, returning all files in directory in an array.
Definition: FileSystem.cpp:342
Path getAbsolutePath(const Path &relativePath)
Returns the absolute path to the file.
Definition: FileSystem.cpp:48
Vector position(const Float a, const Float e, const Float u)
Computes the position on the elliptic trajectory.
Definition: TwoBody.cpp:82