wxTimelineCtrlWidget
Loading...
Searching...
No Matches
wxTimelineCtrl_impl.h
Go to the documentation of this file.
1#include "wxTimelineCtrl.h"
2
3template<typename T>
5{
6 m_artProvider = new TimelineArtProvider();
7
8 m_defaultDuration = 10;
9 m_startTime = wxDateTime::Today();
10 m_totalDuration = 120;
11 m_totalTime = m_totalDuration;
12 m_visibleDuration = 60;
13
14 m_minVisibleDuration = 10;
15 m_maxVisibleDuration = m_totalDuration;
16 m_firstVisibleTime = 0;
17
18 m_minScrollerVisibleDuration = m_minVisibleDuration;
19 int defaultScrollerView = wxMax(m_visibleDuration + 30, m_visibleDuration * 2);
20 defaultScrollerView = wxMin(defaultScrollerView, m_totalDuration);
21 defaultScrollerView = wxMax(defaultScrollerView, m_minScrollerVisibleDuration);
22 m_scrollerVisibleDuration = defaultScrollerView;
23 m_scrollerFirstVisibleTime = 0;
24 AdjustMainViewToScrollerView();
25
26 m_stateLeftArrow = TimelineElementState::Normal;
27 m_stateRightArrow = TimelineElementState::Normal;
28 m_stateVisibleFrame = TimelineElementState::Normal;
29
30 m_mouseDown = false;
31 m_mouseCaptured = false;
32 m_moveDirection = 0;
33
34 m_isSelecting = false;
35 m_selectionRect = wxRect();
36
37 m_ptStartPos = wxDefaultPosition;
38 m_ptEndPos = wxDefaultPosition;
39
40 m_selectedElement = ET_NONE;
41 m_lastElement = ET_NONE;
42
43 m_colorCounter = 0;
44
45 m_visibleItemBegin = m_items.end();
46 m_visibleItemEnd = m_items.end();
47 m_lastTask = m_items.end();
48 m_activeTask = m_items.end();
49 m_contextMenuItemIndex = -1;
50
51 m_timerMove.SetOwner(this, wxID_ANY);
52
53 m_isDraggingDetachedItem = false;
54 m_detachedDragItemOriginalIndex = -1;
55 m_showOriginalPositionPlaceholder = false;
56 m_dragScrollerItemInitialClickTimeOffset = 0;
57 m_pFloatingItemWin = nullptr;
58
59 m_dropIndicatorRect = wxRect();
60 m_dropIndicatorRectScroller = wxRect();
61
62 m_isSnapping = false;
63}
64
65template<typename T>
67{
68 Bind(wxEVT_PAINT, &wxTimelineCtrl<T>::OnPaint, this);
69 Bind(wxEVT_SIZE, &wxTimelineCtrl<T>::OnSize, this);
70 Bind(wxEVT_ERASE_BACKGROUND, &wxTimelineCtrl<T>::OnEraseBackground, this);
71
72 Bind(wxEVT_LEFT_DOWN, &wxTimelineCtrl<T>::OnMouse, this);
73 Bind(wxEVT_LEFT_UP, &wxTimelineCtrl<T>::OnMouse, this);
74 Bind(wxEVT_LEFT_DCLICK, &wxTimelineCtrl<T>::OnMouse, this);
75
76 Bind(wxEVT_RIGHT_DOWN, &wxTimelineCtrl<T>::OnMouse, this);
77 Bind(wxEVT_RIGHT_UP, &wxTimelineCtrl<T>::OnMouse, this);
78 Bind(wxEVT_RIGHT_DCLICK, &wxTimelineCtrl<T>::OnMouse, this);
79
80 Bind(wxEVT_MIDDLE_DOWN, &wxTimelineCtrl<T>::OnMouse, this);
81 Bind(wxEVT_MIDDLE_UP, &wxTimelineCtrl<T>::OnMouse, this);
82 Bind(wxEVT_MIDDLE_DCLICK, &wxTimelineCtrl<T>::OnMouse, this);
83
84 Bind(wxEVT_MOTION, &wxTimelineCtrl<T>::OnMouse, this);
85 Bind(wxEVT_MOUSEWHEEL, &wxTimelineCtrl<T>::OnMouse, this);
86 Bind(wxEVT_LEAVE_WINDOW, &wxTimelineCtrl<T>::OnMouse, this);
87 Bind(wxEVT_ENTER_WINDOW, &wxTimelineCtrl<T>::OnMouse, this);
88
89 Bind(wxEVT_MOUSE_CAPTURE_LOST, &wxTimelineCtrl<T>::OnMouseCaptureLost, this);
90
91 Bind(wxEVT_KEY_DOWN, &wxTimelineCtrl<T>::OnKeyDown, this);
92
93 Bind(wxEVT_TIMER, &wxTimelineCtrl<T>::OnTimer, this);
94
95 Bind(wxEVT_MENU, [this](wxCommandEvent& event) {
96 if (event.GetId() == ID_TIMELINE_DELETE)
97 {
98 RemoveSelectedItems();
99 }
100 else if (event.GetId() == ID_TIMELINE_DELETE_SCROLLER_ITEM)
101 {
102 RemoveContextScrollerItem();
103 }
104 });
105}
106
107template<typename T>
108void wxTimelineCtrl<T>::OnPaint(wxPaintEvent&)
109{
110 wxBufferedPaintDC dc(this, m_buffer);
111 wxGCDC gdc(dc);
112 Draw(gdc);
113}
114
115template<typename T>
117
118template<typename T>
120{
121 m_artProvider->DrawBackground(dc, m_rectBackground);
122 DrawTimeline(dc);
123 DrawScroller(dc);
124 m_artProvider->DrawGap(dc, m_rectTimelineTrack, m_rectVisibleFrame);
125
126 if (m_isDraggingDetachedItem && m_detachedDragItemVisual.Data && m_pFloatingItemWin == nullptr)
127 {
128 TimelineItem<T> itemToDraw = m_detachedDragItemVisual;
129 itemToDraw.Colour = itemToDraw.Colour.ChangeLightness(120);
130 wxColour c = itemToDraw.Colour;
131 itemToDraw.Colour.Set(c.Red(), c.Green(), c.Blue(), 180);
133
134 wxRect detachedItemRectOnScreen(m_detachedDragItemScreenPos, m_detachedDragItemSize);
135
136 m_artProvider->DrawItem(dc, detachedItemRectOnScreen, GetClientRect() , itemToDraw, false , false );
137 }
138}
139
140template<typename T>
142{
143 m_artProvider->DrawTimelineBackground(dc, m_rectTimeline);
144
145 const int visibleStart = m_firstVisibleTime;
146 const int visibleEnd = m_firstVisibleTime + m_visibleDuration;
147
148 if (visibleEnd <= visibleStart)
149 {
150 return;
151 }
152
153 m_artProvider->DrawTimelineTrack(dc, m_rectTimelineMain);
154
155 const int timeBuffer = 1;
156 wxDateTime adjustedStart = m_startTime + wxTimeSpan::Seconds(wxMax(0, visibleStart - timeBuffer));
157 wxDateTime adjustedEnd = m_startTime + wxTimeSpan::Seconds(visibleEnd + timeBuffer);
158
159 wxRect clipRectTimeScale = m_rectTimelineTimeScale;
160 clipRectTimeScale.Inflate(10, 0);
161 wxDCClipper timeScaleClipper(dc, clipRectTimeScale);
162
163 m_artProvider->DrawTimeScale(
164 dc,
165 m_rectTimelineTimeScale,
166 adjustedStart,
167 adjustedEnd
168 );
169
170 wxDCClipper mainTimelineClipper(dc, m_rectTimelineMain);
171
172 wxString rangeText = FormatTime(visibleStart) + " - " + FormatTime(visibleEnd);
173 wxString zoomText = wxString::Format("Zoom: %s", FormatTime(m_visibleDuration));
174
175 dc.SetTextForeground(wxColour(80, 80, 80));
176 dc.SetFont(wxFont(wxFontInfo(8).Family(wxFONTFAMILY_DEFAULT).Style(wxFONTSTYLE_NORMAL).Weight(wxFONTWEIGHT_NORMAL)));
177 dc.DrawText(rangeText, m_rectTimelineMain.x + 5, m_rectTimelineMain.y + 5);
178 dc.DrawText(zoomText, m_rectTimelineMain.x + 5, m_rectTimelineMain.y + 20);
179
180 if (m_visibleItemBegin == m_items.end() && m_visibleItemEnd == m_items.end())
181 {
182 return;
183 }
184
185 for (size_t index = 0; index < m_items.size(); ++index)
186 {
187 auto& item = m_items[index];
188 if (!item.Data) {
189 continue;
190 }
191 if (item.Rect.width <= 0) {
192 continue;
193 }
194
195 try {
196 m_artProvider->DrawItem(dc, item.Rect, m_rectTimelineTrack, item, false, false);
197 }
198 catch (const std::exception& ) {
199 }
200 }
201
202 if (m_isDraggingDetachedItem)
203 {
204 if (!m_dropIndicatorRect.IsEmpty() && m_detachedDragItemOriginalIndex != -1)
205 {
206 wxDCClipper clip(dc, m_rectTimelineTrack);
207 TimelineItem<T> previewItem = m_items[m_detachedDragItemOriginalIndex];
208 if (m_isSnapping)
209 {
210 previewItem.Colour.Set(0, 255, 0, 128); // Green for snap
211 }
212 else
213 {
214 wxColour c = previewItem.Colour;
215 previewItem.Colour.Set(c.Red(), c.Green(), c.Blue(), 128); // semi-transparent
216 }
217 m_artProvider->DrawItem(dc, m_dropIndicatorRect, m_rectTimelineTrack, previewItem, false, false);
218 }
219 }
220
221
222 if (!m_selectionRect.IsEmpty())
223 {
224 wxColour selectionColor(0, 120, 215, 80);
225 dc.SetBrush(wxBrush(selectionColor));
226 dc.SetPen(wxPen(wxColour(0, 120, 215), 1));
227
228 wxRect clippedSelectionRect = m_selectionRect.Intersect(m_rectTimelineTrack);
229 if (!clippedSelectionRect.IsEmpty())
230 {
231 dc.DrawRectangle(clippedSelectionRect);
232
233 if (clippedSelectionRect.width > 20)
234 {
235 int selStartTime = m_firstVisibleTime + TimelineCoordToTime(clippedSelectionRect.GetLeft() - m_rectTimelineTrack.x);
236 int selEndTime = m_firstVisibleTime + TimelineCoordToTime(clippedSelectionRect.GetRight() - m_rectTimelineTrack.x);
237 wxString selTimeText = FormatTime(selStartTime) + " - " + FormatTime(selEndTime);
238
239 dc.SetTextForeground(wxColour(0, 0, 0));
240 dc.SetFont(wxFont(wxFontInfo(8).Family(wxFONTFAMILY_DEFAULT)));
241
242 wxSize textSize = dc.GetTextExtent(selTimeText);
243 int textX = clippedSelectionRect.x + (clippedSelectionRect.width - textSize.GetWidth()) / 2;
244 int textY = clippedSelectionRect.y + (clippedSelectionRect.height - textSize.GetHeight()) / 2;
245
246 if (textY >= clippedSelectionRect.y && (textY + textSize.GetHeight()) <= clippedSelectionRect.GetBottom())
247 {
248 dc.DrawText(selTimeText, textX, textY);
249 }
250 }
251 }
252 }
253}
254
255template<typename T>
257{
258 m_artProvider->DrawScrollerBackground(dc, m_rectScroller);
259 m_artProvider->DrawScrollerTrack(dc, m_rectScrollerMain);
260 m_artProvider->DrawLeftArrow(dc, m_rectLeftArrowDraw, m_stateLeftArrow);
261 m_artProvider->DrawRightArrow(dc, m_rectRightArrowDraw, m_stateRightArrow);
262 m_artProvider->DrawTimeScale(dc, m_rectScrollerTimeScale,
263 m_startTime + wxTimeSpan::Seconds(m_scrollerFirstVisibleTime),
264 m_startTime + wxTimeSpan::Seconds(m_scrollerFirstVisibleTime + m_scrollerVisibleDuration)
265 );
266 wxDCClipper clip(dc, m_rectScrollerTrack);
267 for (size_t i = 0; i < m_items.size(); ++i)
268 {
269 if (m_isDraggingDetachedItem && m_showOriginalPositionPlaceholder && (int)i == m_detachedDragItemOriginalIndex)
270 {
271 continue;
272 }
273
274 auto& item = m_items[i];
275 if (!item.Data)
276 continue;
277 int itemStartTime = item.Data->GetStartTime();
278 int itemEndTime = item.Data->GetEndTime();
279 if (itemEndTime <= m_scrollerFirstVisibleTime || itemStartTime >= m_scrollerFirstVisibleTime + m_scrollerVisibleDuration)
280 continue;
281
282 int itemX_start_in_track = ScrollerTimeToCoord(itemStartTime);
283 int itemX_end_in_track = ScrollerTimeToCoord(itemEndTime);
284
285 itemX_start_in_track = wxMax(0, itemX_start_in_track);
286 itemX_end_in_track = wxMin(m_rectScrollerTrack.width, itemX_end_in_track);
287
288 if (itemX_end_in_track > itemX_start_in_track)
289 {
290 wxRect itemRectInScroller = m_rectScrollerTrack;
291 itemRectInScroller.x = m_rectScrollerTrack.x + itemX_start_in_track;
292 itemRectInScroller.width = itemX_end_in_track - itemX_start_in_track;
293
294 m_artProvider->DrawItem(dc, itemRectInScroller, m_rectScrollerTrack, item, true, false);
295
296 if ((int)i == m_contextMenuItemIndex)
297 {
298 wxPen oldPen = dc.GetPen();
299 wxBrush oldBrush = dc.GetBrush();
300
301 dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT), 2, wxPENSTYLE_SOLID));
302 dc.SetBrush(*wxTRANSPARENT_BRUSH);
303 wxRect highlightRect = itemRectInScroller;
304 dc.DrawRectangle(highlightRect);
305
306 dc.SetPen(oldPen);
307 dc.SetBrush(oldBrush);
308 }
309 }
310 }
311
312 if (m_isDraggingDetachedItem)
313 {
314 if (!m_dropIndicatorRectScroller.IsEmpty() && m_detachedDragItemOriginalIndex != -1)
315 {
316 //wxDCClipper clip(dc, m_rectScrollerTrack);
317 TimelineItem<T> previewItem = m_items[m_detachedDragItemOriginalIndex];
318 if (m_isSnapping)
319 {
320 previewItem.Colour.Set(0, 255, 0, 128); // Green for snap
321 }
322 else
323 {
324 wxColour c = previewItem.Colour;
325 previewItem.Colour.Set(c.Red(), c.Green(), c.Blue(), 128); // semi-transparent
326 }
327 m_artProvider->DrawItem(dc, m_dropIndicatorRectScroller, m_rectScrollerTrack, previewItem, true, false);
328 }
329 }
330
331 if (m_isDraggingDetachedItem && m_showOriginalPositionPlaceholder && m_originalPositionPlaceholderRectScroller.width > 0)
332 {
333 wxColour placeholderFillColour = m_originalPositionPlaceholderColour;
334 placeholderFillColour.Set(placeholderFillColour.Red(), placeholderFillColour.Green(), placeholderFillColour.Blue(), 30);
335
336 dc.SetPen(wxPen(m_originalPositionPlaceholderColour, 1, wxPENSTYLE_DOT));
337 dc.SetBrush(wxBrush(placeholderFillColour));
338 dc.DrawRectangle(m_originalPositionPlaceholderRectScroller);
339 }
340
341 m_artProvider->DrawVisibleFrame(dc, m_rectVisibleFrame, m_stateVisibleFrame);
342}
343
344template<typename T>
346{
347 m_buffer.Create(GetSize(), 32);
348 RecalcRects();
349 RecalcItems();
350 Refresh();
351}
352
353template<typename T>
355{
356 m_rectBackground = GetClientRect();
357
358 //int timelineHeight = 80;
359 int scrollerHeight = 60;
360 int gapHeight = 8;
361
362
363 m_rectTimeline = m_rectBackground;
364 m_rectTimeline.height -= scrollerHeight + gapHeight;
365
366 m_rectTimelineTimeScale = wxRect(m_rectTimeline.x, m_rectTimeline.GetBottom() - 20, m_rectTimeline.width, 20);
367 m_rectTimelineMain = wxRect(m_rectTimeline.x, m_rectTimeline.y + 5, m_rectTimeline.width, m_rectTimeline.height - 25);
368 m_rectTimelineTrack = m_rectTimelineMain.Deflate(4, 4);
369
370 m_rectScroller = wxRect(m_rectTimeline.x, m_rectTimeline.GetBottom() + gapHeight, m_rectTimeline.width, scrollerHeight);
371 m_rectScrollerMain = wxRect(m_rectScroller.x + 20, m_rectScroller.y + 5, m_rectScroller.width - 40, scrollerHeight - 25);
372 m_rectScrollerTrack = m_rectScrollerMain.Deflate(4, 4);
373
374 m_rectLeftArrow = wxRect(m_rectScroller.x, m_rectScroller.y + 10, 20, 40);
375 m_rectRightArrow = wxRect(m_rectScroller.GetRight() - 20, m_rectScroller.y + 10, 20, 40);
376
377 m_rectLeftArrowDraw = m_rectLeftArrow.Deflate(5);
378 m_rectRightArrowDraw = m_rectRightArrow.Deflate(5);
379
380 m_rectScrollerTimeScale = wxRect(m_rectScrollerMain.x, m_rectScrollerMain.GetBottom() + 2, m_rectScrollerMain.width, 20);
381
382 RecalcVisibleFrame();
383}
384
385template<typename T>
387{
388 if (m_scrollerVisibleDuration <= 0 || m_rectScrollerTrack.width <= 0)
389 {
390 m_rectVisibleFrame = wxRect();
391 m_rectVisibleFrameLeft = wxRect();
392 m_rectVisibleFrameRight = wxRect();
393 return;
394 }
395
396 int trackX = m_rectScrollerTrack.x;
397 int trackW = m_rectScrollerTrack.width;
398
399 double mainViewStartRelativeToScroller = 0.0;
400 double mainViewEndRelativeToScroller = 1.0;
401
402 if (m_scrollerVisibleDuration > 0) {
403 mainViewStartRelativeToScroller = (double)(m_firstVisibleTime - m_scrollerFirstVisibleTime) / m_scrollerVisibleDuration;
404 mainViewEndRelativeToScroller = (double)(m_firstVisibleTime + m_visibleDuration - m_scrollerFirstVisibleTime) / m_scrollerVisibleDuration;
405 }
406
407 mainViewStartRelativeToScroller = wxMax(0.0, mainViewStartRelativeToScroller);
408 mainViewEndRelativeToScroller = wxMin(1.0, mainViewEndRelativeToScroller);
409 if (mainViewEndRelativeToScroller < mainViewStartRelativeToScroller) mainViewEndRelativeToScroller = mainViewStartRelativeToScroller;
410
411 int x1 = trackX + wxRound(mainViewStartRelativeToScroller * trackW);
412 int x2 = trackX + wxRound(mainViewEndRelativeToScroller * trackW);
413
414 x1 = wxMax(trackX, x1);
415 x2 = wxMin(trackX + trackW, x2);
416 if (x2 < x1) x2 = x1;
417
418 m_rectVisibleFrame = wxRect(
419 wxPoint(x1, m_rectScrollerTrack.y - 5),
420 wxPoint(x2, m_rectScrollerTrack.GetBottom() + 5)
421 );
422
423 int gripSize = 4;
424 m_rectVisibleFrameLeft = wxRect(
425 m_rectVisibleFrame.x - gripSize,
426 m_rectVisibleFrame.y,
427 2 * gripSize,
428 m_rectVisibleFrame.height
429 );
430 m_rectVisibleFrameRight = wxRect(
431 m_rectVisibleFrame.GetRight() - gripSize,
432 m_rectVisibleFrame.y,
433 2 * gripSize,
434 m_rectVisibleFrame.height
435 );
436}
437
438
439template<typename T>
441{
442 m_firstVisibleTime, m_visibleDuration, m_rectTimelineTrack.width, m_rectTimelineTrack.y, m_rectTimelineTrack.height;
443
444 int visibleStart = m_firstVisibleTime;
445 int visibleDuration = m_visibleDuration;
446 int width = m_rectTimelineTrack.width;
447
448 m_visibleItemBegin = m_items.end();
449 m_visibleItemEnd = m_items.end();
450
451 for (auto& item : m_items)
452 {
453 item.Rect = wxRect();
454 item.m_displayLane = 0;
455 }
456
457 if (m_items.empty())
458 {
459 return;
460 }
461
462 std::vector<size_t> sorted_indices(m_items.size());
463 std::iota(sorted_indices.begin(), sorted_indices.end(), 0);
464
465 std::stable_sort(sorted_indices.begin(), sorted_indices.end(),
466 [&](size_t a_idx, size_t b_idx) {
467 const auto& item_a_data = m_items[a_idx].Data;
468 const auto& item_b_data = m_items[b_idx].Data;
469
470 if (!item_a_data && !item_b_data) return false;
471 if (!item_a_data) return false;
472 if (!item_b_data) return true;
473
474 if (item_a_data->GetStartTime() != item_b_data->GetStartTime()) {
475 return item_a_data->GetStartTime() < item_b_data->GetStartTime();
476 }
477 return item_a_data->GetEndTime() < item_b_data->GetEndTime();
478 });
479
480 std::vector<int> lane_end_times;
481 int max_lane_used = -1;
482
483 for (size_t item_original_index : sorted_indices)
484 {
485 TimelineItem<T>& current_item = m_items[item_original_index];
486 if (!current_item.Data) continue;
487
488 int item_start_time = current_item.Data->GetStartTime();
489 int item_end_time = current_item.Data->GetEndTime();
490
491 if (item_start_time >= item_end_time) {
492 current_item.m_displayLane = 0;
493 item_original_index, item_start_time, item_end_time;
494 continue;
495 }
496
497 int assigned_lane = -1;
498 for (size_t lane_idx = 0; lane_idx < lane_end_times.size(); ++lane_idx)
499 {
500 if (item_start_time >= lane_end_times[lane_idx])
501 {
502 assigned_lane = lane_idx;
503 lane_end_times[lane_idx] = item_end_time;
504 break;
505 }
506 }
507
508 if (assigned_lane == -1)
509 {
510 assigned_lane = lane_end_times.size();
511 lane_end_times.push_back(item_end_time);
512 }
513 current_item.m_displayLane = assigned_lane;
514 if (assigned_lane > max_lane_used)
515 {
516 max_lane_used = assigned_lane;
517 }
518 }
519
520 int num_lanes = m_items.empty() ? 1 : (max_lane_used + 1);
521 int lane_height = m_rectTimelineTrack.height;
522
523 if (num_lanes > 0 && m_rectTimelineTrack.height > 0)
524 {
525 lane_height = m_rectTimelineTrack.height / num_lanes;
526 }
527 if (lane_height < 1 && m_rectTimelineTrack.height > 0) lane_height = 1;
528 if (m_rectTimelineTrack.height <= 0) lane_height = 0;
529
530 max_lane_used, num_lanes, lane_height, m_rectTimelineTrack.height;
531
532 for (auto it = m_items.begin(); it != m_items.end(); ++it)
533 {
534 size_t currentIndex = std::distance(m_items.begin(), it);
535 if (!it->Data) {
536 continue;
537 }
538
539 int itemStart = it->Data->GetStartTime();
540 int itemEnd = it->Data->GetEndTime();
541
542 bool isVisible = !(itemEnd <= visibleStart || itemStart >= visibleStart + visibleDuration);
543
544 if (isVisible)
545 {
546 int relativeStart = itemStart - visibleStart;
547 int relativeEnd = itemEnd - visibleStart;
548
549 if (relativeStart < 0) relativeStart = 0;
550 if (relativeEnd > visibleDuration) relativeEnd = visibleDuration;
551
552 if (visibleDuration <= 0 || width <= 0) {
553 continue;
554 }
555
556 int xStart = (relativeStart * width) / visibleDuration;
557 int xEnd = (relativeEnd * width) / visibleDuration;
558
559 if (xEnd < xStart) xEnd = xStart;
560
561 int item_rect_y = m_rectTimelineTrack.y + it->m_displayLane * lane_height;
562 int item_rect_height = lane_height;
563
564 if (lane_height > 0 && it->m_displayLane == num_lanes - 1) {
565 item_rect_height = (m_rectTimelineTrack.y + m_rectTimelineTrack.height) - item_rect_y;
566 }
567 if (item_rect_height < 0) item_rect_height = 0;
568
569
570 wxRect rect(
571 m_rectTimelineTrack.x + xStart,
572 item_rect_y,
573 xEnd - xStart,
574 item_rect_height
575 );
576
577 if (rect.height <= 0 && m_rectTimelineTrack.height > 0 && num_lanes > 0) {
578 }
579
580
581 it->Rect = rect;
582 currentIndex, itemStart, itemEnd, it->m_displayLane,
583 rect.x, rect.y, rect.width, rect.height;
584
585 if (m_visibleItemBegin == m_items.end())
586 m_visibleItemBegin = it;
587 m_visibleItemEnd = it + 1;
588 }
589 else
590 {
591 currentIndex, itemStart, itemEnd;
592 }
593 }
594}
595
596
597template<typename T>
598void wxTimelineCtrl<T>::OnMouse(wxMouseEvent& event)
599{
600 const wxPoint pos = event.GetPosition();
601 ElementType currentHoverType = ET_NONE;
602
603 if (m_isDraggingDetachedItem) {
604 currentHoverType = ET_SCROLLER_ITEM_DRAG;
605 } else if (!m_mouseCaptured) {
606 currentHoverType = GetElementFromPos(pos);
607 } else {
608 if (m_mouseDown && m_selectedElement != ET_NONE) {
609 currentHoverType = m_selectedElement;
610 } else {
611 currentHoverType = m_lastElement;
612 }
613 }
614
615 if (event.Moving() && !m_isDraggingDetachedItem)
616 {
617 if (currentHoverType != m_lastElement)
618 {
619 OnLeaveElement(m_lastElement);
620 OnEnterElement(currentHoverType);
621 }
622 }
623
624 if (event.IsButton())
625 SetFocus();
626
627 if (event.LeftDown())
628 {
629 m_contextMenuItemIndex = -1;
630
631 if (event.ControlDown())
632 {
633 if (currentHoverType == ET_SCROLLER_ITEM_DRAG && m_hoveredScrollerItemIndex != -1 && !m_isDraggingDetachedItem)
634 {
635 m_mouseDown = true;
636 m_selectedElement = ET_SCROLLER_ITEM_DRAG;
637
638 m_isDraggingDetachedItem = true;
639 m_detachedDragItemOriginalIndex = m_hoveredScrollerItemIndex;
640 m_dragPreviewTime = -1;
641
642 TimelineItem<T>& originalItem = m_items[m_detachedDragItemOriginalIndex];
643 if (!originalItem.Data) {
644 m_isDraggingDetachedItem = false;
645 return;
646 }
647
648 m_showOriginalPositionPlaceholder = true;
649 m_originalPositionPlaceholderColour = originalItem.Colour;
650
651 int itemStartTime = originalItem.Data->GetStartTime();
652 int itemEndTime = originalItem.Data->GetEndTime();
653 int x1_track = ScrollerTimeToCoord(itemStartTime);
654 int x2_track = ScrollerTimeToCoord(itemEndTime);
655
656 wxLogDebug("OnMouse LeftDown Ctrl: itemStartTime=%d, itemEndTime=%d, duration=%d", itemStartTime, itemEndTime, itemEndTime - itemStartTime);
657 wxLogDebug("OnMouse LeftDown Ctrl: m_scrollerFirstVisibleTime=%d, m_scrollerVisibleDuration=%d, m_rectScrollerTrack.width=%d", m_scrollerFirstVisibleTime, m_scrollerVisibleDuration, m_rectScrollerTrack.width);
658 wxLogDebug("OnMouse LeftDown Ctrl: x1_track=%d, x2_track=%d, pixel_width_on_scroller=%d", x1_track, x2_track, x2_track - x1_track);
659
660 m_originalPositionPlaceholderRectScroller = wxRect(
661 m_rectScrollerTrack.x + wxClip(x1_track, 0, m_rectScrollerTrack.width),
662 m_rectScrollerTrack.y,
663 wxClip(x2_track, 0, m_rectScrollerTrack.width) - wxClip(x1_track, 0, m_rectScrollerTrack.width),
664 m_rectScrollerTrack.height
665 );
666
667 if (m_originalPositionPlaceholderRectScroller.width < 0) {
668 m_originalPositionPlaceholderRectScroller.width = 0;
669 }
670 wxLogDebug("OnMouse LeftDown Ctrl: m_originalPositionPlaceholderRectScroller.width=%d, height=%d", m_originalPositionPlaceholderRectScroller.width, m_originalPositionPlaceholderRectScroller.height);
671
672
673 m_detachedDragItemVisual = originalItem;
674
675 int placeholderWidth = m_originalPositionPlaceholderRectScroller.width;
676 wxLogDebug("OnMouse LeftDown Ctrl: placeholderWidth before adjustment = %d", placeholderWidth);
677
678 if (placeholderWidth <= 0 && (itemEndTime - itemStartTime > 0)) {
679 placeholderWidth = 1;
680 wxLogDebug("OnMouse LeftDown Ctrl: placeholderWidth corrected to 1 due to zero/negative pixel width but positive duration");
681 } else if (placeholderWidth < 0) {
682 placeholderWidth = 0;
683 wxLogDebug("OnMouse LeftDown Ctrl: placeholderWidth corrected to 0 due to negative pixel width");
684 }
685
686
687 int finalPopupWindowWidth = placeholderWidth;
688 if (finalPopupWindowWidth < 30 && placeholderWidth > 0) {
689 finalPopupWindowWidth = 30;
690 wxLogDebug("OnMouse LeftDown Ctrl: finalPopupWindowWidth adjusted to 30 (minimum for visible placeholder)");
691 } else if (finalPopupWindowWidth < 5 && placeholderWidth == 0 && (itemEndTime - itemStartTime > 0)) {
692 finalPopupWindowWidth = 30; // If item has duration but placeholder is 0, make popup 30
693 wxLogDebug("OnMouse LeftDown Ctrl: finalPopupWindowWidth set to 30 (item has duration, placeholder 0)");
694 } else if (finalPopupWindowWidth == 0 && (itemEndTime - itemStartTime == 0)) {
695 finalPopupWindowWidth = 30; // Zero duration item, popup 30
696 wxLogDebug("OnMouse LeftDown Ctrl: finalPopupWindowWidth set to 30 (zero duration item)");
697 }
698 else {
699 wxLogDebug("OnMouse LeftDown Ctrl: finalPopupWindowWidth set to placeholderWidth = %d", placeholderWidth);
700 }
701 if (finalPopupWindowWidth <=0 ) finalPopupWindowWidth = 30; // Absolute minimum fallback
702
703 m_detachedDragItemSize.SetWidth(finalPopupWindowWidth);
704 m_detachedDragItemSize.SetHeight(wxMax(15, m_originalPositionPlaceholderRectScroller.height > 4 ? m_originalPositionPlaceholderRectScroller.height - 2 * m_ScrollerVMargin : 15));
705
706 wxLogDebug("OnMouse LeftDown Ctrl: m_detachedDragItemSize.width=%d, height=%d", m_detachedDragItemSize.GetWidth(), m_detachedDragItemSize.GetHeight());
707
708 if (m_originalPositionPlaceholderRectScroller.width > 0) {
709 double relativeX = static_cast<double>(pos.x - m_originalPositionPlaceholderRectScroller.GetLeft()) / m_originalPositionPlaceholderRectScroller.GetWidth();
710 m_cursorToDetachedVisualOffset.x = static_cast<int>(round(relativeX * m_detachedDragItemSize.GetWidth()));
711 } else {
712 m_cursorToDetachedVisualOffset.x = m_detachedDragItemSize.GetWidth() / 2;
713 }
714
715 if (m_originalPositionPlaceholderRectScroller.height > 0) {
716 double relativeY = static_cast<double>(pos.y - m_originalPositionPlaceholderRectScroller.GetTop()) / m_originalPositionPlaceholderRectScroller.GetHeight();
717 m_cursorToDetachedVisualOffset.y = static_cast<int>(round(relativeY * m_detachedDragItemSize.GetHeight()));
718 } else {
719 m_cursorToDetachedVisualOffset.y = m_detachedDragItemSize.GetHeight() / 2;
720 }
721
722 wxPoint screenMousePos = ClientToScreen(pos);
723 m_detachedDragItemScreenPos = screenMousePos - m_cursorToDetachedVisualOffset;
724
725 int mouseTimeInScroller = ScrollerCoordToTime(pos.x - m_rectScrollerTrack.x);
726 m_dragScrollerItemInitialClickTimeOffset = mouseTimeInScroller - itemStartTime;
727
728 if (m_pFloatingItemWin) {
729 m_pFloatingItemWin->Destroy();
730 m_pFloatingItemWin = nullptr;
731 }
732
733 m_pFloatingItemWin = new FloatingItemPopupWindow<T>(this,
734 m_detachedDragItemVisual,
735 m_detachedDragItemSize,
736 m_artProvider);
737 if (m_pFloatingItemWin) {
738 m_pFloatingItemWin->Move(m_detachedDragItemScreenPos);
739 m_pFloatingItemWin->Show();
740 }
741
742 if (!HasCapture()) { CaptureMouse(); m_mouseCaptured = true; }
743 }
744 else if ((event.ShiftDown() || event.AltDown()) && m_rectTimelineTrack.Contains(pos) && !m_isDraggingDetachedItem)
745 {
746 m_isSelecting = true;
747 m_selectionStart = pos;
748 m_selectionEnd = pos;
749 m_selectionRect = wxRect(pos, wxSize(1, m_rectTimelineTrack.height));
750 if (!HasCapture()) { CaptureMouse(); m_mouseCaptured = true; }
751 }
752 else if (!m_isDraggingDetachedItem)
753 {
754 OnMouseDown(currentHoverType, pos);
755 }
756 }
757 else if (!m_isDraggingDetachedItem)
758 {
759 if ((event.ShiftDown() || event.AltDown()) && m_rectTimelineTrack.Contains(pos))
760 {
761 m_isSelecting = true;
762 m_selectionStart = pos;
763 m_selectionEnd = pos;
764 m_selectionRect = wxRect(pos, wxSize(1, m_rectTimelineTrack.height));
765 if (!HasCapture()) { CaptureMouse(); m_mouseCaptured = true; }
766 }
767 else
768 {
769 if (!event.ShiftDown() &&
770 !(currentHoverType >= ET_TIMELINE_ITEM && currentHoverType <= ET_TIMELINE_ITEM_MAX))
771 {
772 ClearSelection();
773 }
774 OnMouseDown(currentHoverType, pos);
775 }
776 }
777 }
778 else if (event.RightDown() && !m_isDraggingDetachedItem)
779 {
780 if (currentHoverType == ET_SCROLLER_ITEM_DRAG && m_hoveredScrollerItemIndex != -1)
781 {
782 m_contextMenuItemIndex = m_hoveredScrollerItemIndex;
783 // Если были выделены элементы на основной временной шкале, очистим это выделение,
784 // так как сейчас контекст переключается на элемент в полосе прокрутки.
785 // Важно не вызывать ClearSelection(), так как он сбрасывает m_contextMenuItemIndex.
786 if (!m_selectedItems.empty())
787 {
788 m_selectedItems.clear();
789 Refresh(); // Обновляем отображение, чтобы снять выделение с элементов временной шкалы
790 }
791 ShowContextMenu(ClientToScreen(pos));
792 }
793 else
794 {
795 m_contextMenuItemIndex = -1;
796 if (currentHoverType >= ET_TIMELINE_ITEM && currentHoverType <= ET_TIMELINE_ITEM_MAX)
797 {
798 size_t index = std::distance(m_items.begin(), m_activeTask);
799 if (index < m_items.size())
800 {
801 if (!IsItemSelected(index))
802 {
803 if (event.ControlDown()) ToggleItemSelection(index);
804 else SelectItem(index, true);
805 }
806 else if (!event.ControlDown() && m_selectedItems.size() > 1)
807 {
808 SelectItem(index, true);
809 }
810 SendItemEvent(wxEVT_TIMELINE_SELECTION, index);
811 }
812 }
813 if (!m_selectedItems.empty())
814 {
815 ShowContextMenu(ClientToScreen(pos));
816 }
817 }
818 }
819 else if (event.LeftUp())
820 {
821 if (m_isDraggingDetachedItem) {
822 OnMouseUp(ET_SCROLLER_ITEM_DRAG, pos);
823 }
824 else if (m_isSelecting)
825 {
826 m_isSelecting = false;
827 if (m_selectionRect.width < 5) ClearSelection();
828 else if (event.AltDown()) ZoomToSelection();
829 if (m_mouseCaptured && HasCapture()) { ReleaseMouse(); m_mouseCaptured = false;}
830 }
831 else if (m_selectedElement != ET_NONE)
832 {
833 OnMouseUp(m_selectedElement, pos);
834 }
835 else {
836 if (m_mouseCaptured && HasCapture()) { ReleaseMouse(); m_mouseCaptured = false; }
837 }
838
839 if (!m_isDraggingDetachedItem) {
840 ElementType newHoverTypeAfterUp = GetElementFromPos(pos);
841 if (newHoverTypeAfterUp != m_lastElement) {
842 OnLeaveElement(m_lastElement);
843 OnEnterElement(newHoverTypeAfterUp);
844 }
845 }
846 }
847 else if (event.RightUp() && !m_isDraggingDetachedItem)
848 {
849 ElementType newHoverTypeAfterRightUp = GetElementFromPos(pos);
850 if (newHoverTypeAfterRightUp != m_lastElement) {
851 OnLeaveElement(m_lastElement);
852 OnEnterElement(newHoverTypeAfterRightUp);
853 }
854 }
855 else if (event.Dragging())
856 {
857 if (m_isDraggingDetachedItem) {
858 OnMouseDrag(ET_SCROLLER_ITEM_DRAG, pos);
859 }
860 else if (m_isSelecting)
861 {
862 m_selectionEnd = pos;
863 int left = wxMin(m_selectionStart.x, m_selectionEnd.x);
864 int right = wxMax(m_selectionStart.x, m_selectionEnd.x);
865 left = wxMax(left, m_rectTimelineTrack.x);
866 right = wxMin(right, m_rectTimelineTrack.x + m_rectTimelineTrack.width);
867 m_selectionRect = wxRect(left, m_rectTimelineTrack.y, right - left, m_rectTimelineTrack.height);
868 }
869 else if (m_mouseDown)
870 {
871 OnMouseDrag(m_selectedElement, pos);
872 }
873 }
874
875 if (event.GetWheelRotation() != 0 && !m_isDraggingDetachedItem)
876 {
877 if (m_contextMenuItemIndex != -1) {
878 m_contextMenuItemIndex = -1;
879 }
880
881 int deltaSteps = event.GetWheelRotation() / event.GetWheelDelta();
882 if (event.GetModifiers() == wxMOD_CONTROL)
883 {
884 if (m_rectScroller.Contains(pos) && m_totalDuration > m_minScrollerVisibleDuration)
885 {
886 // Scroller zoom is disabled
887 }
888 }
889 else
890 {
891 int scrollDeltaTime = deltaSteps * (event.GetModifiers() == wxMOD_SHIFT ? GetVisibleDuration() / 4
892 : wxMin(15, GetVisibleDuration() / 10));
893 if (scrollDeltaTime == 0 && deltaSteps != 0) scrollDeltaTime = deltaSteps > 0 ? 1 : -1;
894 SetFirstVisibleTime(GetFirstVisibleTime() - scrollDeltaTime);
895 }
896 }
897 if (event.Dragging() || event.IsButton() || event.GetWheelRotation() != 0) {
898 Refresh();
899 }
900}
901
902template<typename T>
904{
905 wxLogDebug(wxT(">[OnEnterElement] Trying to enter Type: %d. m_selectedElement: %d"), type, m_selectedElement);
906 switch (type)
907 {
908 case ET_VISIBLE_FRAME:
909 case ET_VISIBLE_FRAME_LEFT:
910 case ET_VISIBLE_FRAME_RIGHT:
911 if (m_selectedElement != type) m_stateVisibleFrame = TimelineElementState::Hover;
912 break;
913 case ET_LEFT_ARROW:
914 if (m_stateLeftArrow != TimelineElementState::Disabled && m_selectedElement != ET_LEFT_ARROW)
915 m_stateLeftArrow = TimelineElementState::Hover;
916 break;
917 case ET_RIGHT_ARROW:
918 if (m_stateRightArrow != TimelineElementState::Disabled && m_selectedElement != ET_RIGHT_ARROW)
919 m_stateRightArrow = TimelineElementState::Hover;
920 break;
921 case ET_TIMELINE:
922 m_activeTask = m_items.end();
923 break;
924 default:
925 break;
926 }
927
928 if (type >= ET_TIMELINE_ITEM && type <= ET_TIMELINE_ITEM_MAX)
929 {
930 if (m_activeTask != m_items.end() && m_selectedElement != type)
931 {
932 m_activeTask->State = TimelineElementState::Hover;
933 wxLogDebug(wxT(">[OnEnterElement] TIMELINE_ITEM family (%d) hovered."), type);
934 }
935 else if (m_activeTask == m_items.end())
936 {
937 wxLogDebug(wxT(">[OnEnterElement] WARNING: type is TIMELINE_ITEM family but m_activeTask is end(). Type: %d"), type);
938 }
939 }
940 ChangeLastElement(type);
941 Refresh();
942 wxLogDebug(wxT("<[OnEnterElement] Entered Type: %d. m_lastElement is now: %d"), type, m_lastElement);
943}
944
945template<typename T>
947{
948 wxLogDebug(wxT(">[OnLeaveElement] Leaving Type: %d. m_selectedElement: %d"), type, m_selectedElement);
949 switch (type)
950 {
951 case ET_VISIBLE_FRAME:
952 case ET_VISIBLE_FRAME_LEFT:
953 case ET_VISIBLE_FRAME_RIGHT:
954 if (m_selectedElement != type) m_stateVisibleFrame = TimelineElementState::Normal;
955 break;
956 case ET_LEFT_ARROW:
957 if (m_stateLeftArrow != TimelineElementState::Disabled && m_selectedElement != ET_LEFT_ARROW)
958 m_stateLeftArrow = TimelineElementState::Normal;
959 break;
960 case ET_RIGHT_ARROW:
961 if (m_stateRightArrow != TimelineElementState::Disabled && m_selectedElement != ET_RIGHT_ARROW)
962 m_stateRightArrow = TimelineElementState::Normal;
963 break;
964 default:
965 break;
966 }
967 if (type >= ET_TIMELINE_ITEM && type <= ET_TIMELINE_ITEM_MAX)
968 {
969 if (m_activeTask != m_items.end() && m_selectedElement != type)
970 {
971 m_activeTask->State = TimelineElementState::Normal;
972 wxLogDebug(wxT(">[OnLeaveElement] TIMELINE_ITEM family (%d) unhovered."), type);
973 }
974 }
975 Refresh();
976 wxLogDebug(wxT("<[OnLeaveElement] Left Type: %d."), type);
977}
978
979template<typename T>
981{
982 if (m_mouseCaptured)
983 {
984 ReleaseMouse();
985 m_mouseCaptured = false;
986 }
987}
988
989template<typename T>
991{
992 if (m_activeTask == m_items.end()) return;
993 if (m_activeTask < m_items.begin() || m_activeTask >= m_items.end()) { m_activeTask = m_items.end(); return; }
994 size_t index = std::distance(m_items.begin(), m_activeTask);
995 if (index >= m_items.size()) return;
996 if (!m_activeTask->Data) return;
997
1000 {
1001 ToggleItemSelection(index);
1002 SendItemEvent(wxEVT_TIMELINE_SELECTION, index);
1003 return;
1004 }
1005 else
1006 {
1007 if (!IsItemSelected(index)) SelectItem(index);
1008 else if (m_selectedItems.size() > 1) SelectItem(index);
1009 SendItemEvent(wxEVT_TIMELINE_SELECTION, index);
1010 }
1011
1012 m_mouseDown = true;
1013 m_selectedElement = type;
1014 if (m_activeTask != m_items.end() && m_activeTask->Data)
1015 {
1016 m_activeTask->State = TimelineElementState::Pressed;
1017 m_dragFirstVisibleTime = m_activeTask->Data->GetStartTime();
1018 m_dragVisibleDuration = m_activeTask->Data->GetDuration();
1019 m_ptStartPos = pos;
1020 }
1021 if (!HasCapture())
1022 {
1023 CaptureMouse();
1024 m_mouseCaptured = true;
1025 }
1026 Refresh();
1027}
1028
1029template<typename T>
1031{
1032 if (m_activeTask != m_items.end())
1033 {
1034 m_activeTask->State = TimelineElementState::Normal;
1035 }
1038}
1039
1040template<typename T>
1042{
1043 OnTimelineItemMove(pos);
1045}
1046
1047template<typename T>
1049{
1050 if (m_activeTask == m_items.end() || !m_activeTask->Data) return;
1051 int dx = pos.x - m_ptStartPos.x;
1052 int dt = TimelineCoordToTime(dx);
1053 int newStart = m_dragFirstVisibleTime + dt;
1054 int duration = m_dragVisibleDuration;
1055
1056 // Определяем направление движения от исходной позиции
1057 bool movingRight = (dt > 0);
1058
1059 if (movingRight)
1060 {
1061 int collisionLimit = m_totalDuration;
1062 for (auto it = m_items.begin(); it != m_items.end(); ++it)
1063 {
1064 if (it == m_activeTask || !it->Data) continue;
1065
1066 // Рассматриваем элементы, которые являются потенциальными препятствиями при движении вправо
1067 if (it->Data->GetStartTime() >= m_dragFirstVisibleTime)
1068 {
1069 // Если мы бы пересеклись с элементом
1070 if (newStart + duration > it->Data->GetStartTime())
1071 {
1072 collisionLimit = wxMin(collisionLimit, it->Data->GetStartTime());
1073 }
1074 }
1075 }
1076
1078 {
1079 if (collisionLimit < m_totalDuration) // Ограничено другим элементом
1080 {
1082 }
1083 else // Ограничено концом временной шкалы, можно расширять
1084 {
1085 SetTotalDuration(newStart + duration);
1086 }
1087 }
1088 }
1089 else // движемся влево или не движемся
1090 {
1091 int collisionLimit = 0;
1092 for (auto it = m_items.begin(); it != m_items.end(); ++it)
1093 {
1094 if (it == m_activeTask || !it->Data) continue;
1095
1096 // Рассматриваем элементы, которые являются потенциальными препятствиями при движении влево
1097 if (it->Data->GetEndTime() <= m_dragFirstVisibleTime)
1098 {
1099 // Если мы бы пересеклись с элементом
1100 if (newStart < it->Data->GetEndTime())
1101 {
1102 collisionLimit = wxMax(collisionLimit, it->Data->GetEndTime());
1103 }
1104 }
1105 }
1107 {
1109 }
1110 }
1111
1112 // Ограничиваем позицию границами временной шкалы
1113 if (newStart < 0) newStart = 0;
1114 if (newStart + duration > m_totalDuration)
1115 {
1116 newStart = m_totalDuration - duration;
1117 }
1118 if (newStart < 0) newStart = 0; // Повторная проверка, если длительность > общей длительности
1119
1120 if (newStart != m_activeTask->Data->GetStartTime())
1121 {
1122 m_activeTask->Data->SetStartTime(newStart);
1123 m_activeTask->Data->SetDuration(duration);
1124 SendItemEvent(wxEVT_SCROLL_THUMBTRACK, std::distance(m_items.begin(), m_activeTask));
1125 RecalcItems();
1126 Refresh();
1127 }
1128}
1129
1130template<typename T>
1132{
1133 if (m_activeTask == m_items.end() || !m_activeTask->Data) return;
1134 int dx = pos.x - m_ptStartPos.x;
1135 int dt = TimelineCoordToTime(dx);
1136 int newStart = m_dragFirstVisibleTime + dt;
1137 int end = m_dragFirstVisibleTime + m_dragVisibleDuration;
1138 if (newStart < 0) newStart = 0;
1139 if (newStart > end - 1) newStart = end - 1;
1140 if (m_activeTask != m_items.begin())
1141 {
1142 auto prev = m_activeTask - 1;
1143 if (prev->Data && newStart < prev->Data->GetStartTime() + prev->Data->GetDuration())
1144 newStart = prev->Data->GetStartTime() + prev->Data->GetDuration();
1145 }
1146 if (newStart != m_activeTask->Data->GetStartTime())
1147 {
1148 m_activeTask->Data->SetStartTime(newStart);
1149 m_activeTask->Data->SetDuration(end - newStart);
1150 SendItemEvent(wxEVT_SCROLL_THUMBTRACK, std::distance(m_items.begin(), m_activeTask));
1151 RecalcItems();
1152 Refresh();
1153 }
1154}
1155
1156template<typename T>
1158{
1159 if (m_activeTask == m_items.end() || !m_activeTask->Data) return;
1160 int dx = pos.x - m_ptStartPos.x;
1161 int dt = TimelineCoordToTime(dx);
1162 int newDuration = m_dragVisibleDuration + dt;
1163 if (newDuration < 1) newDuration = 1;
1164 if (m_activeTask + 1 != m_items.end())
1165 {
1166 auto next = m_activeTask + 1;
1167 if (next->Data && m_activeTask->Data->GetStartTime() + newDuration > next->Data->GetStartTime())
1168 newDuration = next->Data->GetStartTime() - m_activeTask->Data->GetStartTime();
1169 }
1170 if (newDuration != m_activeTask->Data->GetDuration())
1171 {
1172 m_activeTask->Data->SetDuration(newDuration);
1173 SendItemEvent(wxEVT_SCROLL_THUMBTRACK, std::distance(m_items.begin(), m_activeTask));
1174 RecalcItems();
1175 Refresh();
1176 }
1177}
1178
1179template<typename T>
1181{
1182 if (event.GetId() == m_timerMove.GetId())
1183 {
1184 if (!m_mouseDown || (m_selectedElement != ET_LEFT_ARROW && m_selectedElement != ET_RIGHT_ARROW))
1185 {
1186 m_timerMove.Stop();
1187 m_moveDirection = 0;
1188 return;
1189 }
1190
1191 if (m_timerMove.IsOneShot())
1192 {
1193 m_timerMove.Start(50);
1194 }
1195
1196 SetFirstVisibleTime(GetFirstVisibleTime() + m_moveDirection);
1197
1198 const int maxScrollSpeed = GetVisibleDuration() / 4;
1199 if (m_moveDirection > 0)
1200 {
1201 m_moveDirection = wxMin(m_moveDirection + 1, maxScrollSpeed > 0 ? maxScrollSpeed : 15);
1202 }
1203 else
1204 {
1205 m_moveDirection = wxMax(m_moveDirection - 1, -(maxScrollSpeed > 0 ? maxScrollSpeed : 15));
1206 }
1207 }
1208 else
1209 {
1210 event.Skip();
1211 }
1212}
1213
1214template<typename T>
1216{
1217 wxMenu menu;
1218 bool menuHasItems = false;
1219
1220 if (m_contextMenuItemIndex != -1)
1221 {
1222 if (m_contextMenuItemIndex >= 0 && m_contextMenuItemIndex < (int)m_items.size())
1223 {
1224 menu.Append(ID_TIMELINE_DELETE_SCROLLER_ITEM, wxString::Format("Delete Element \"%s\"", m_items[m_contextMenuItemIndex].GetItemName()));
1225 menuHasItems = true;
1226 }
1227 else
1228 {
1229 m_contextMenuItemIndex = -1;
1230 }
1231 }
1232 else if (!m_selectedItems.empty())
1233 {
1234 if (m_selectedItems.size() == 1)
1235 {
1236 size_t idx = m_selectedItems[0];
1237 if (idx < m_items.size())
1238 {
1239 menu.Append(ID_TIMELINE_DELETE, wxString::Format("Удалить элемент \"%s\"", m_items[idx].GetItemName()));
1240 }
1241 else
1242 {
1243 menu.Append(ID_TIMELINE_DELETE, "Удалить выбранный элемент");
1244 }
1245 }
1246 else
1247 {
1248 menu.Append(ID_TIMELINE_DELETE, wxString::Format("Удалить %zu элемента(ов)", m_selectedItems.size()));
1249 }
1250 menuHasItems = true;
1251 }
1252
1253
1254 if (menuHasItems)
1255 {
1257 }
1258}
1259
1260
1261template<typename T>
1263{
1264 int timeDelta = 0;
1265 bool processed = true;
1266
1267 switch (event.GetKeyCode())
1268 {
1269 case WXK_TAB:
1270 Navigate(!event.ShiftDown());
1271 break;
1272
1273 case WXK_LEFT:
1274 case WXK_RIGHT:
1275 {
1276 int direction = (event.GetKeyCode() == WXK_LEFT) ? -1 : 1;
1277 int stepSize;
1278
1279 if (event.GetModifiers() == wxMOD_CONTROL) {
1280 stepSize = 1;
1281 }
1282 else if (event.GetModifiers() == wxMOD_SHIFT) {
1283 stepSize = wxMax(1, GetVisibleDuration() / 4);
1284 }
1285 else {
1286 stepSize = wxMax(1, GetVisibleDuration() / 10);
1287 stepSize = wxMin(stepSize, 15);
1288 }
1290 }
1291 break;
1292
1293 case WXK_PAGEUP:
1294 timeDelta = -GetVisibleDuration();
1295 break;
1296 case WXK_PAGEDOWN:
1297 timeDelta = GetVisibleDuration();
1298 break;
1299
1300 case WXK_HOME:
1301 SetFirstVisibleTime(0);
1302 break;
1303 case WXK_END:
1304 {
1305 int targetFirstVisibleTime = m_totalDuration - m_visibleDuration;
1306 SetFirstVisibleTime(targetFirstVisibleTime);
1307 }
1308 break;
1309
1311 case '-':
1312 case WXK_NUMPAD_ADD:
1313 case '=':
1314 case '+':
1315 {
1316 int zoomDirection = (event.GetKeyCode() == WXK_NUMPAD_ADD || event.GetKeyCode() == '=' || event.GetKeyCode() == '+') ? -1 : 1;
1317 int zoomAmount;
1318 if (event.GetModifiers() == wxMOD_SHIFT)
1319 zoomAmount = wxMax(1, GetVisibleDuration() / 2);
1320 else if (event.GetModifiers() == wxMOD_CONTROL)
1321 zoomAmount = wxMax(1, GetVisibleDuration() / 20);
1322 else
1323 zoomAmount = wxMax(1, GetVisibleDuration() / 10);
1324
1326 Zoom(zoomDirection * zoomAmount);
1327 }
1328 break;
1329
1330 case 'Z':
1331 if (event.GetModifiers() == wxMOD_CONTROL) ZoomToSelection();
1332 else processed = false;
1333 break;
1334 case 'A':
1335 if (event.GetModifiers() == wxMOD_CONTROL) ShowAllTimeline();
1336 else processed = false;
1337 break;
1338 case '1': if (event.GetModifiers() == wxMOD_CONTROL) SetZoomPreset(ZOOM_SECONDS_10); else processed = false; break;
1339 case '2': if (event.GetModifiers() == wxMOD_CONTROL) SetZoomPreset(ZOOM_SECONDS_30); else processed = false; break;
1340 case '3': if (event.GetModifiers() == wxMOD_CONTROL) SetZoomPreset(ZOOM_MINUTE_1); else processed = false; break;
1341 case '4': if (event.GetModifiers() == wxMOD_CONTROL) SetZoomPreset(ZOOM_MINUTES_2); else processed = false; break;
1342 case '5': if (event.GetModifiers() == wxMOD_CONTROL) SetZoomPreset(ZOOM_MINUTES_5); else processed = false; break;
1343 case '6': if (event.GetModifiers() == wxMOD_CONTROL) SetZoomPreset(ZOOM_MINUTES_10); else processed = false; break;
1344 case '0': if (event.GetModifiers() == wxMOD_CONTROL) SetZoomPreset(ZOOM_ALL); else processed = false; break;
1345
1346 case WXK_ESCAPE:
1347 ClearSelection();
1348 break;
1349 case WXK_DELETE:
1350 if (!m_selectedItems.empty()) RemoveSelectedItems();
1351 break;
1352
1353 default:
1354 processed = false;
1355 break;
1356 }
1357
1358 if (timeDelta != 0)
1359 {
1360 SetFirstVisibleTime(GetFirstVisibleTime() + timeDelta);
1361 }
1362
1363 if (!processed)
1364 {
1365 event.Skip();
1366 }
1367}
1368
1369template<typename T>
1371{
1372 if (m_firstVisibleTime <= 0) m_stateLeftArrow = TimelineElementState::Disabled;
1373 else if (m_stateLeftArrow == TimelineElementState::Disabled)
1374 m_stateLeftArrow = m_lastElement == ET_LEFT_ARROW ? TimelineElementState::Hover : TimelineElementState::Normal;
1375 if (GetLastVisibleTime() >= m_totalDuration) m_stateRightArrow = TimelineElementState::Disabled;
1376 else if (m_stateRightArrow == TimelineElementState::Disabled)
1377 m_stateRightArrow = m_lastElement == ET_RIGHT_ARROW ? TimelineElementState::Hover : TimelineElementState::Normal;
1378}
1379
1380template<typename T>
1382{
1383 RecalcVisibleFrame();
1384 RecalcItems();
1385 Refresh();
1386}
1387
1388
1389template<typename T>
1391{
1392 ElementType type = ET_NONE;
1393 m_hoveredScrollerItemIndex = -1;
1394
1395 if (m_rectScroller.Contains(pos))
1396 {
1397 type = ET_SCROLLER;
1398 if (m_rectLeftArrow.Contains(pos)) type = ET_LEFT_ARROW;
1399 else if (m_rectRightArrow.Contains(pos)) type = ET_RIGHT_ARROW;
1400 else if (m_rectVisibleFrameLeft.Contains(pos)) type = ET_VISIBLE_FRAME_LEFT;
1401 else if (m_rectVisibleFrameRight.Contains(pos)) type = ET_VISIBLE_FRAME_RIGHT;
1402 else if (m_rectVisibleFrame.Contains(pos)) type = ET_VISIBLE_FRAME;
1403 else if (m_rectScrollerTrack.Contains(pos))
1404 {
1405 for (size_t i = 0; i < m_items.size(); ++i)
1406 {
1407 auto& item = m_items[i];
1408 if (!item.Data) continue;
1409 int itemStartTime = item.Data->GetStartTime();
1410 int itemEndTime = item.Data->GetEndTime();
1411 if (itemEndTime <= m_scrollerFirstVisibleTime || itemStartTime >= m_scrollerFirstVisibleTime + m_scrollerVisibleDuration)
1412 continue;
1413 int itemX_start_in_track = ScrollerTimeToCoord(itemStartTime);
1414 int itemX_end_in_track = ScrollerTimeToCoord(itemEndTime);
1416 itemX_end_in_track = wxMin(m_rectScrollerTrack.width, itemX_end_in_track);
1418 {
1419 wxRect itemRectInScroller = m_rectScrollerTrack;
1420 itemRectInScroller.x = m_rectScrollerTrack.x + itemX_start_in_track;
1422 if (itemRectInScroller.Contains(pos))
1423 {
1424 m_hoveredScrollerItemIndex = i;
1425 type = ET_SCROLLER_ITEM_DRAG;
1426 break;
1427 }
1428 }
1429 }
1430 if (type != ET_SCROLLER_ITEM_DRAG)
1431 {
1432 type = ET_SCROLLER;
1433 }
1434 }
1435 }
1436 else if (m_rectTimeline.Contains(pos))
1437 {
1438 type = ET_TIMELINE;
1439 if (!m_items.empty())
1440 {
1441 int i = ET_TIMELINE_ITEM;
1442 m_activeTask = m_items.end();
1443 for (auto it = m_items.begin(); it != m_items.end(); ++it, i += 3)
1444 {
1445 if (it->Data && !it->Rect.IsEmpty() && it->Rect.Contains(pos))
1446 {
1447 m_activeTask = it;
1448 return static_cast<ElementType>(i);
1449 }
1450 }
1451 }
1452 }
1453 return type;
1454}
1455
1456template<typename T>
1458{
1459 m_lastElement = type;
1460 SetCursor(GetCursorFromType(type));
1461}
1462
1463template<typename T>
1465{
1466 wxLogDebug(wxT(">>[OnMouseDown] Type: %d at (%d,%d). m_mouseCaptured: %d, m_selectedElement (before): %d"), type, pos.x, pos.y, m_mouseCaptured, m_selectedElement);
1467 m_dragCurrentPos = m_dragStartPos = pos;
1468 m_mouseDown = false;
1469 m_selectedElement = type;
1470
1471 switch (type)
1472 {
1473 case ET_VISIBLE_FRAME:
1474 case ET_VISIBLE_FRAME_LEFT:
1475 case ET_VISIBLE_FRAME_RIGHT:
1476 m_mouseDown = true;
1477 OnVisibleFrameDown(pos, type);
1478 break;
1479 case ET_LEFT_ARROW:
1480 m_mouseDown = true;
1481 OnArrowDown(true);
1482 break;
1483 case ET_RIGHT_ARROW:
1484 m_mouseDown = true;
1485 OnArrowDown(false);
1486 break;
1487 case ET_SCROLLER:
1488 OnScrollerDown(pos);
1489 break;
1490 case ET_TIMELINE:
1491 ClearSelection();
1492 wxLogDebug(wxT(">>[OnMouseDown] Click on empty TIMELINE area. Selection Cleared."));
1493 break;
1494 default:
1495 if (type >= ET_TIMELINE_ITEM && type <= ET_TIMELINE_ITEM_MAX)
1496 {
1497 OnTimelineDown(pos, type);
1498 }
1499 break;
1500 }
1501 Refresh();
1502 wxLogDebug(wxT("<<[OnMouseDown] END. m_selectedElement: %d, m_mouseDown: %d, m_mouseCaptured: %d"), m_selectedElement, m_mouseDown, m_mouseCaptured);
1503}
1504
1505
1506template<typename T>
1508{
1509 m_dragCurrentPos = pos;
1510
1511 if (m_isDraggingDetachedItem)
1512 {
1514 m_detachedDragItemScreenPos = screenMousePos - m_cursorToDetachedVisualOffset;
1515
1516 if (m_pFloatingItemWin) {
1517 m_pFloatingItemWin->Move(m_detachedDragItemScreenPos);
1518 }
1519
1520 m_dropIndicatorRect = wxRect();
1521 m_dropIndicatorRectScroller = wxRect();
1522 m_dragPreviewTime = -1;
1523 m_isSnapping = false;
1524
1525 if (m_detachedDragItemOriginalIndex < 0 || m_detachedDragItemOriginalIndex >= (int)m_items.size() || !m_items[m_detachedDragItemOriginalIndex].Data)
1526 {
1527 Refresh();
1528 return;
1529 }
1530 int duration = m_items[m_detachedDragItemOriginalIndex].Data->GetDuration();
1531
1532 int mouseTime = -1;
1533 if (m_rectScrollerTrack.Contains(pos)) {
1534 mouseTime = ScrollerCoordToTime(pos.x - m_rectScrollerTrack.x);
1535 } else if (m_rectTimelineTrack.Contains(pos)) {
1536 mouseTime = TimelineCoordToTime(pos.x - m_rectTimelineTrack.x) + m_firstVisibleTime;
1537 }
1538
1539 if (mouseTime != -1)
1540 {
1541 int rawStartTime = mouseTime - m_dragScrollerItemInitialClickTimeOffset;
1543
1544 // Snapping logic to any item's start or end
1545 const int snapThresholdPixels = 10;
1546 int snapThresholdTime = TimelineCoordToTime(snapThresholdPixels);
1547 int bestSnapTime = -1;
1549
1550 for (size_t i = 0; i < m_items.size(); ++i)
1551 {
1552 if ((int)i == m_detachedDragItemOriginalIndex || !m_items[i].Data) continue;
1553
1554 int itemStart = m_items[i].Data->GetStartTime();
1555 int itemEnd = m_items[i].Data->GetEndTime();
1556
1557 // Snap to start of other item
1561 }
1562 // Snap to end of other item
1566 }
1567 // Snap end of dragged item to start of other item
1571 }
1572 // Snap end of dragged item to end of other item
1576 }
1577 }
1578
1579 if (bestSnapTime != -1) {
1581 m_isSnapping = true;
1582 }
1583
1584 // Final clamping to total duration (for preview only)
1586 if (previewStartTime + duration > m_totalDuration) {
1587 previewStartTime = m_totalDuration - duration;
1588 }
1590
1591 m_dragPreviewTime = previewStartTime;
1593
1594 // Calculate indicator for main timeline
1595 int visibleStart = m_firstVisibleTime;
1596 int visibleEnd = m_firstVisibleTime + m_visibleDuration;
1598 {
1599 int x1 = TimelineTimeToCoord(previewStartTime - visibleStart);
1600 int x2 = TimelineTimeToCoord(previewEndTime - visibleStart);
1601 if (x2 <= x1) x2 = x1 + 1;
1602 m_dropIndicatorRect = wxRect(m_rectTimelineTrack.x + x1, m_rectTimelineTrack.y, x2 - x1, m_rectTimelineTrack.height);
1603 }
1604
1605 // Calculate indicator for scroller
1606 int scrollerVisibleStart = m_scrollerFirstVisibleTime;
1607 int scrollerVisibleEnd = m_scrollerFirstVisibleTime + m_scrollerVisibleDuration;
1609 {
1610 int sx1 = ScrollerTimeToCoord(previewStartTime);
1611 int sx2 = ScrollerTimeToCoord(previewEndTime);
1612 if (sx2 <= sx1) sx2 = sx1 + 1;
1613 m_dropIndicatorRectScroller = wxRect(m_rectScrollerTrack.x + sx1, m_rectScrollerTrack.y, sx2 - sx1, m_rectScrollerTrack.height);
1614 }
1615 }
1616
1617 Refresh();
1618 return;
1619 }
1620
1621 switch (type)
1622 {
1623 case ET_VISIBLE_FRAME:
1624 case ET_VISIBLE_FRAME_LEFT:
1625 case ET_VISIBLE_FRAME_RIGHT:
1626 OnVisibleFrameDrag(pos);
1627 break;
1628 default: break;
1629 }
1630 if (type >= ET_TIMELINE_ITEM && type <= ET_TIMELINE_ITEM_MAX) OnTimelineDrag(pos, type);
1631 // Refresh();
1632}
1633
1634template<typename T>
1636{
1637 if (m_isDraggingDetachedItem)
1638 {
1639 if (m_pFloatingItemWin) {
1640 m_pFloatingItemWin->Destroy();
1641 m_pFloatingItemWin = nullptr;
1642 }
1643
1644 if (m_detachedDragItemOriginalIndex != -1 && m_dragPreviewTime != -1)
1645 {
1646 TimelineItem<T>& draggedItem = m_items[m_detachedDragItemOriginalIndex];
1647 if (draggedItem.Data)
1648 {
1649 int duration = draggedItem.Data->GetDuration();
1650 int newStartTime = m_dragPreviewTime;
1652
1653 // Find first conflict to determine shift
1654 int firstConflictStartTime = -1;
1655 for (size_t i = 0; i < m_items.size(); ++i)
1656 {
1657 if ((int)i == m_detachedDragItemOriginalIndex || !m_items[i].Data) continue;
1658
1659 int itemStart = m_items[i].Data->GetStartTime();
1660 int itemEnd = m_items[i].Data->GetEndTime();
1661
1662 // Check for overlap
1664 {
1666 {
1668 }
1669 }
1670 }
1671
1672 if (firstConflictStartTime != -1)
1673 {
1675 if (shiftAmount > 0)
1676 {
1677 // Collect items to shift
1679 for (size_t i = 0; i < m_items.size(); ++i)
1680 {
1681 if ((int)i != m_detachedDragItemOriginalIndex && m_items[i].Data && m_items[i].Data->GetStartTime() >= firstConflictStartTime)
1682 {
1683 itemsToShiftIndices.push_back(i);
1684 }
1685 }
1686
1687 // Sort them by start time to shift correctly
1688 std::sort(itemsToShiftIndices.begin(), itemsToShiftIndices.end(), [&](size_t a, size_t b){
1689 return m_items[a].Data->GetStartTime() < m_items[b].Data->GetStartTime();
1690 });
1691
1692 // Shift items
1693 for (size_t index : itemsToShiftIndices)
1694 {
1695 m_items[index].Data->SetStartTime(m_items[index].Data->GetStartTime() + shiftAmount);
1696 m_items[index].Data->SetEndTime(m_items[index].Data->GetEndTime() + shiftAmount);
1697 }
1698 }
1699 }
1700
1701 draggedItem.Data->SetStartTime(newStartTime);
1702 draggedItem.Data->SetEndTime(newEndTime);
1703 UpdateTotalDurationForItems();
1704 }
1705 }
1706
1707 m_isDraggingDetachedItem = false;
1708 m_detachedDragItemOriginalIndex = -1;
1709 m_showOriginalPositionPlaceholder = false;
1710 m_dragPreviewTime = -1;
1711 m_dropIndicatorRect = wxRect();
1712 m_dropIndicatorRectScroller = wxRect();
1713
1714 if (m_mouseCaptured && HasCapture()) { ReleaseMouse(); m_mouseCaptured = false; }
1715 m_mouseDown = false;
1716 m_selectedElement = ET_NONE;
1717
1718 RecalcItems();
1719 Refresh();
1720
1721 ElementType typeUnderMouseNow = GetElementFromPos(pos);
1722 if (typeUnderMouseNow != m_lastElement) {
1723 OnLeaveElement(m_lastElement);
1724 OnEnterElement(typeUnderMouseNow);
1725 } else if (m_lastElement != ET_NONE) {
1726 OnLeaveElement(m_lastElement);
1727 OnEnterElement(typeUnderMouseNow);
1728 }
1729 return;
1730 }
1731
1732
1733 switch (typeFromMouseDown)
1734 {
1735 case ET_VISIBLE_FRAME:
1736 case ET_VISIBLE_FRAME_LEFT:
1737 case ET_VISIBLE_FRAME_RIGHT:
1738 OnVisibleFrameUp(pos);
1739 break;
1740 case ET_LEFT_ARROW:
1741 OnArrowUp(true);
1742 break;
1743 case ET_RIGHT_ARROW:
1744 OnArrowUp(false);
1745 break;
1746 case ET_TIMELINE:
1747 break;
1748 default:
1749 if (typeFromMouseDown >= ET_TIMELINE_ITEM && typeFromMouseDown <= ET_TIMELINE_ITEM_MAX)
1750 {
1751 OnTimelineUp(pos, typeFromMouseDown);
1752 UpdateTotalDurationForItems();
1753 }
1754 break;
1755 }
1756
1757 if (m_mouseCaptured && HasCapture())
1758 {
1759 ReleaseMouse();
1760 m_mouseCaptured = false;
1761 }
1762 else if (HasCapture()) {
1763 ReleaseMouse();
1764 }
1765
1766 m_mouseDown = false;
1767
1768
1769 //ElementType previousSelectedElement = m_selectedElement;
1770 m_selectedElement = ET_NONE;
1771
1772 ElementType typeUnderMouseNow = GetElementFromPos(pos);
1773 if (typeUnderMouseNow != m_lastElement)
1774 {
1775 OnEnterElement(typeUnderMouseNow);
1776 }
1777 else if (m_lastElement != ET_NONE && m_lastElement != ET_SCROLLER_ITEM_DRAG && !(m_lastElement >= ET_TIMELINE_ITEM && m_lastElement <= ET_TIMELINE_ITEM_MAX && m_activeTask == m_items.end()))
1778 {
1779 OnLeaveElement(m_lastElement);
1780 OnEnterElement(typeUnderMouseNow);
1781 }
1782}
1783
1784
1785template<typename T>
1787{
1788 switch (type)
1789 {
1790 case ET_SCROLLER: return wxCURSOR_HAND;
1791 case ET_VISIBLE_FRAME: return wxCURSOR_SIZING;
1792 case ET_VISIBLE_FRAME_LEFT: case ET_VISIBLE_FRAME_RIGHT: return wxCURSOR_SIZEWE;
1793 case ET_SCROLLER_ITEM_DRAG: return wxCURSOR_HAND;
1794 case ET_RIGHT_ARROW: break;
1795 case ET_TIMELINE: break;
1796 default: break;
1797 }
1798 if (type >= ET_TIMELINE_ITEM && type <= ET_TIMELINE_ITEM_MAX)
1799 {
1800 return wxCURSOR_SIZING;
1801 }
1802 return wxCURSOR_ARROW;
1803}
1804
1805template<typename T>
1807{
1808 if (m_mouseCaptured && HasCapture())
1809 {
1810 ReleaseMouse();
1811 m_mouseCaptured = false;
1812 }
1813 ElementType currentElement = GetElementFromPos(pos);
1814 if (currentElement == ET_VISIBLE_FRAME || currentElement == ET_VISIBLE_FRAME_LEFT || currentElement == ET_VISIBLE_FRAME_RIGHT)
1815 m_stateVisibleFrame = TimelineElementState::Hover;
1816 else m_stateVisibleFrame = TimelineElementState::Normal;
1817 Refresh();
1818}
1819
1820template<typename T>
1822{
1823 m_mouseDown = true;
1824 m_selectedElement = type;
1825 m_stateVisibleFrame = TimelineElementState::Pressed;
1826 m_dragFirstVisibleTime = m_firstVisibleTime;
1827 m_dragVisibleDuration = m_visibleDuration;
1828 m_ptStartPos = pos;
1829 m_ptEndPos = pos;
1830 if (!HasCapture())
1831 {
1832 CaptureMouse();
1833 m_mouseCaptured = true;
1834 }
1835}
1836
1837template<typename T>
1839{
1840 m_ptEndPos = pos;
1841 int dx = m_ptEndPos.x - m_ptStartPos.x;
1842 int dt = 0;
1843
1844 if (m_rectScrollerTrack.width > 0 && m_scrollerVisibleDuration > 0) {
1845 dt = (dx * m_scrollerVisibleDuration) / m_rectScrollerTrack.width;
1846 }
1847
1848 int originalMainFirstVisibleTime = m_firstVisibleTime;
1849 int originalMainVisibleDuration = m_visibleDuration;
1850
1851 int newCalcFirstVisibleTime = m_firstVisibleTime;
1852 int newCalcVisibleDuration = m_visibleDuration;
1853
1854 switch (m_selectedElement)
1855 {
1856 case ET_VISIBLE_FRAME:
1857 {
1858 newCalcFirstVisibleTime = m_dragFirstVisibleTime + dt;
1859 }
1860 break;
1861
1862 case ET_VISIBLE_FRAME_LEFT:
1863 {
1864 int tentativeNewStartTime = m_dragFirstVisibleTime + dt;
1865 int fixedEndTime = m_dragFirstVisibleTime + m_dragVisibleDuration;
1866
1867 if (fixedEndTime - tentativeNewStartTime < m_minVisibleDuration)
1868 {
1869 tentativeNewStartTime = fixedEndTime - m_minVisibleDuration;
1870 }
1871
1873 tentativeNewStartTime = wxMin(tentativeNewStartTime, m_totalDuration - m_minVisibleDuration);
1874
1877 }
1878 break;
1879
1880 case ET_VISIBLE_FRAME_RIGHT:
1881 {
1882 int fixedStartTime = m_dragFirstVisibleTime;
1883 int tentativeNewEndTime = (m_dragFirstVisibleTime + m_dragVisibleDuration) + dt;
1884
1885 if (tentativeNewEndTime - fixedStartTime < m_minVisibleDuration)
1886 {
1887 tentativeNewEndTime = fixedStartTime + m_minVisibleDuration;
1888 }
1889
1890 tentativeNewEndTime = wxMin(tentativeNewEndTime, m_totalDuration);
1891 tentativeNewEndTime = wxMax(tentativeNewEndTime, m_minVisibleDuration);
1892
1895 }
1896 break;
1897
1898 default:
1899 return;
1900 }
1901
1902 newCalcVisibleDuration = wxMax(m_minVisibleDuration, newCalcVisibleDuration);
1903 newCalcVisibleDuration = wxMin(newCalcVisibleDuration, m_maxVisibleDuration);
1905
1907 {
1909 }
1910 if (newCalcFirstVisibleTime + newCalcVisibleDuration > m_totalDuration)
1911 {
1912 if (m_selectedElement == ET_VISIBLE_FRAME || m_selectedElement == ET_VISIBLE_FRAME_RIGHT)
1913 {
1915 }
1916 }
1918
1919 m_firstVisibleTime = newCalcFirstVisibleTime;
1920 m_visibleDuration = newCalcVisibleDuration;
1921
1922 bool scrollerViewChanged = false;
1923 int newScrollerFirstVisibleTime = m_scrollerFirstVisibleTime;
1924
1925 if (m_firstVisibleTime < m_scrollerFirstVisibleTime)
1926 {
1927 newScrollerFirstVisibleTime = m_firstVisibleTime;
1928 scrollerViewChanged = true;
1929 }
1930 else if (m_firstVisibleTime + m_visibleDuration > m_scrollerFirstVisibleTime + m_scrollerVisibleDuration)
1931 {
1932 newScrollerFirstVisibleTime = (m_firstVisibleTime + m_visibleDuration) - m_scrollerVisibleDuration;
1933 scrollerViewChanged = true;
1934 }
1935
1937 {
1939 if (newScrollerFirstVisibleTime + m_scrollerVisibleDuration > m_totalDuration)
1940 {
1941 newScrollerFirstVisibleTime = m_totalDuration - m_scrollerVisibleDuration;
1942 }
1944 m_scrollerFirstVisibleTime = newScrollerFirstVisibleTime;
1945 }
1946
1947 bool mainViewActuallyChanged = (m_firstVisibleTime != originalMainFirstVisibleTime || m_visibleDuration != originalMainVisibleDuration);
1948
1950 {
1951 AdjustMainViewToScrollerView();
1952 CalcArrowsState();
1953 RecalcVisibleFrame();
1954 RecalcItems();
1955 Refresh();
1956 }
1957}
1958
1959template<typename T>
1961{
1963 // Do nothing to prevent teleporting and dragging the visible frame on scroller track click.
1964 // The frame can still be dragged by clicking on it directly.
1965}
1966
1967template<typename T>
1969{
1970 TimelineElementState& state = isLeft ? m_stateLeftArrow : m_stateRightArrow;
1971
1973 {
1975
1976 if (!HasCapture())
1977 {
1978 CaptureMouse();
1979 m_mouseCaptured = true;
1980 }
1981
1982 m_moveDirection = isLeft ? -1 : 1;
1983 SetFirstVisibleTime(GetFirstVisibleTime() + m_moveDirection);
1984 m_timerMove.Start(250, wxTIMER_ONE_SHOT);
1985 }
1986}
1987
1988template<typename T>
1990{
1991 TimelineElementState& state = isLeft ? m_stateLeftArrow : m_stateRightArrow;
1992
1994 {
1995 }
1996
1997 m_moveDirection = 0;
1998 m_timerMove.Stop();
1999}
2000
2001template<typename T>
2003{
2005 event.SetEventObject(this);
2006 event.SetInt(index);
2007 event.SetClientData(index >= 0 && index < static_cast<int>(m_items.size()) ? m_items[index].Data : nullptr);
2009}
2010
2011template<typename T>
2013{
2014 if (m_items.empty()) {
2015 m_maxVisibleDuration = m_totalDuration;
2016 return;
2017 }
2018 int minDuration = m_totalDuration;
2019 for (auto& item : m_items)
2020 {
2021 if (item.Data && minDuration > item.Data->GetDuration())
2022 minDuration = item.Data->GetDuration();
2023 }
2024 m_maxVisibleDuration = wxMin(minDuration * m_rectTimelineTrack.width / (m_MinItemSize / 2), m_totalDuration);
2025 if (m_visibleDuration > m_maxVisibleDuration) SetVisibleDuration(m_maxVisibleDuration);
2026}
2027
2028template<typename T>
2030{
2031 //int oldMainFirstVisibleTime = m_firstVisibleTime;
2032 //int oldScrollerFirstVisibleTime = m_scrollerFirstVisibleTime;
2033
2034 m_firstVisibleTime = seconds;
2035
2036 bool scrollerViewNeedsAdjustment = false;
2037 int targetScrollerFirstVisibleTime = m_scrollerFirstVisibleTime;
2038
2039 if (m_firstVisibleTime < m_scrollerFirstVisibleTime)
2040 {
2041 targetScrollerFirstVisibleTime = m_firstVisibleTime;
2043 }
2044 else if (m_firstVisibleTime + m_visibleDuration > m_scrollerFirstVisibleTime + m_scrollerVisibleDuration)
2045 {
2046 targetScrollerFirstVisibleTime = (m_firstVisibleTime + m_visibleDuration) - m_scrollerVisibleDuration;
2048 }
2049
2051 {
2053 {
2055 }
2056 if (targetScrollerFirstVisibleTime + m_scrollerVisibleDuration > m_totalDuration)
2057 {
2058 targetScrollerFirstVisibleTime = m_totalDuration - m_scrollerVisibleDuration;
2060 }
2061 m_scrollerFirstVisibleTime = targetScrollerFirstVisibleTime;
2062 }
2063
2064 AdjustMainViewToScrollerView();
2065 CalcArrowsState();
2066 RecalcVisibleFrame();
2067 RecalcItems();
2068 Refresh();
2069}
2070
2071template<typename T>
2073{
2074 if (seconds <= 0)
2075 seconds = m_minVisibleDuration;
2076
2077 int targetMainVisibleDuration = wxClip(seconds, m_minVisibleDuration, m_maxVisibleDuration);
2079
2080 int currentMainViewCenterTime = m_firstVisibleTime + m_visibleDuration / 2;
2081
2082 bool scrollerViewChanged = false;
2083 int oldScrollerVisibleDuration = m_scrollerVisibleDuration;
2084 int oldScrollerFirstVisibleTime = m_scrollerFirstVisibleTime;
2085
2086 if (targetMainVisibleDuration > m_scrollerVisibleDuration)
2087 {
2089 newScrollerDuration = wxMin(newScrollerDuration, m_totalDuration);
2090 newScrollerDuration = wxMax(newScrollerDuration, m_minScrollerVisibleDuration);
2092
2093 if (newScrollerDuration != m_scrollerVisibleDuration) {
2094 int currentScrollerCenterTime = m_scrollerFirstVisibleTime + m_scrollerVisibleDuration / 2;
2095 m_scrollerVisibleDuration = newScrollerDuration;
2096 m_scrollerFirstVisibleTime = currentScrollerCenterTime - m_scrollerVisibleDuration / 2;
2097
2098 m_scrollerFirstVisibleTime = wxClip(m_scrollerFirstVisibleTime, 0, m_totalDuration - m_scrollerVisibleDuration);
2099 if (m_scrollerFirstVisibleTime < 0) m_scrollerFirstVisibleTime = 0;
2100 scrollerViewChanged = true;
2101 }
2102 }
2103
2104 m_visibleDuration = targetMainVisibleDuration;
2105 m_firstVisibleTime = currentMainViewCenterTime - m_visibleDuration / 2;
2106 AdjustMainViewToScrollerView();
2107 CalcArrowsState();
2108 RecalcVisibleFrame();
2109 RecalcItems();
2110 Refresh();
2111
2112 if (scrollerViewChanged && (m_scrollerVisibleDuration != oldScrollerVisibleDuration || m_scrollerFirstVisibleTime != oldScrollerFirstVisibleTime))
2113 {
2114 }
2115}
2116
2117template<typename T>
2119{
2120 if (m_rectScrollerTrack.width <= 0 || m_scrollerVisibleDuration <= 0)
2121 {
2122 return m_scrollerFirstVisibleTime;
2123 }
2124 double timePerPixel = static_cast<double>(m_scrollerVisibleDuration) / m_rectScrollerTrack.width;
2125 return m_scrollerFirstVisibleTime + static_cast<int>(round(coordInTrack * timePerPixel));
2126}
2127
2128template<typename T>
2130{
2131 if (m_scrollerVisibleDuration <= 0 || m_rectScrollerTrack.width <= 0)
2132 {
2133 return 0;
2134 }
2135 int timeRelativeToScrollerStart = time - m_scrollerFirstVisibleTime;
2136 double pixelsPerTime = static_cast<double>(m_rectScrollerTrack.width) / m_scrollerVisibleDuration;
2137 return static_cast<int>(round(timeRelativeToScrollerStart * pixelsPerTime));
2138}
2139
2140template<typename T>
2142{
2143 int minAllowableDuration = wxMax(m_minScrollerVisibleDuration, m_minVisibleDuration);
2146
2147 if (seconds == m_totalDuration)
2148 return;
2149
2150 int oldTotalDuration = m_totalDuration;
2151 m_totalDuration = seconds;
2152 m_totalTime = m_totalDuration;
2153
2154 if (m_scrollerVisibleDuration > m_totalDuration || m_scrollerFirstVisibleTime + m_scrollerVisibleDuration > m_totalDuration)
2155 {
2156 m_scrollerVisibleDuration = wxMin(m_scrollerVisibleDuration, m_totalDuration);
2157 m_scrollerVisibleDuration = wxMax(m_minScrollerVisibleDuration, m_scrollerVisibleDuration);
2158 m_scrollerFirstVisibleTime = wxMin(m_scrollerFirstVisibleTime, m_totalDuration - m_scrollerVisibleDuration);
2159 if (m_scrollerFirstVisibleTime < 0) m_scrollerFirstVisibleTime = 0;
2160 }
2161 else if (oldTotalDuration > 0 && m_scrollerVisibleDuration == oldTotalDuration && m_totalDuration > oldTotalDuration)
2162 {
2163 m_scrollerVisibleDuration = m_totalDuration;
2164 m_scrollerFirstVisibleTime = 0;
2165 }
2166
2167 if (m_maxVisibleDuration > m_totalDuration) {
2168 m_maxVisibleDuration = m_totalDuration;
2169 }
2170 m_maxVisibleDuration = wxMax(m_minVisibleDuration, m_maxVisibleDuration);
2171
2172
2173 int desiredVisibleDuration = m_visibleDuration;
2175 desiredVisibleDuration = wxMin(desiredVisibleDuration, m_maxVisibleDuration);
2176 desiredVisibleDuration = wxMax(desiredVisibleDuration, m_minVisibleDuration);
2177
2178 m_visibleDuration = desiredVisibleDuration;
2179
2180 if (m_firstVisibleTime + m_visibleDuration > m_totalDuration)
2181 {
2182 m_firstVisibleTime = m_totalDuration - m_visibleDuration;
2183 }
2184 if (m_firstVisibleTime < 0)
2185 {
2186 m_firstVisibleTime = 0;
2187 }
2188 AdjustMainViewToScrollerView();
2189
2190 CalcMaxVisibleDuration();
2191 if (m_visibleDuration > m_maxVisibleDuration) {
2192 m_visibleDuration = m_maxVisibleDuration;
2193 if (m_firstVisibleTime + m_visibleDuration > m_totalDuration) {
2194 m_firstVisibleTime = m_totalDuration - m_visibleDuration;
2195 }
2196 if (m_firstVisibleTime < 0) m_firstVisibleTime = 0;
2197 AdjustMainViewToScrollerView();
2198 }
2199
2200 CalcArrowsState();
2201 RecalcVisibleFrame();
2202 RecalcItems();
2203 Refresh();
2204}
2205
2206template<typename T>
2208{
2209 m_visibleDuration = wxMax(m_minVisibleDuration, m_visibleDuration);
2210 m_visibleDuration = wxMin(m_visibleDuration, m_maxVisibleDuration);
2211 m_visibleDuration = wxMin(m_visibleDuration, m_totalDuration);
2212
2213 m_visibleDuration = wxMin(m_visibleDuration, m_scrollerVisibleDuration);
2214
2215 m_firstVisibleTime = wxMax(m_firstVisibleTime, m_scrollerFirstVisibleTime);
2216 if (m_firstVisibleTime + m_visibleDuration > m_scrollerFirstVisibleTime + m_scrollerVisibleDuration) {
2217 m_firstVisibleTime = (m_scrollerFirstVisibleTime + m_scrollerVisibleDuration) - m_visibleDuration;
2218 }
2219
2220 m_firstVisibleTime = wxMax(0, m_firstVisibleTime);
2221 if (m_firstVisibleTime + m_visibleDuration > m_totalDuration) {
2222 m_firstVisibleTime = m_totalDuration - m_visibleDuration;
2223 if (m_firstVisibleTime < 0) {
2224 m_firstVisibleTime = 0;
2225 m_visibleDuration = wxMin(m_visibleDuration, m_totalDuration);
2226 m_visibleDuration = wxMax(m_visibleDuration, m_minVisibleDuration);
2227 }
2228 }
2229
2230 if (m_visibleDuration > m_totalDuration) {
2231 m_visibleDuration = m_totalDuration;
2232 m_visibleDuration = wxMax(m_visibleDuration, m_minVisibleDuration);
2233 }
2234 if (m_firstVisibleTime + m_visibleDuration > m_totalDuration) {
2235 m_firstVisibleTime = m_totalDuration - m_visibleDuration;
2236 if (m_firstVisibleTime < 0) m_firstVisibleTime = 0;
2237 }
2238 if (m_firstVisibleTime < 0) m_firstVisibleTime = 0;
2239}
2240
2241template<typename T>
2243{
2244 if (!data)
2245 {
2246 wxLogError("wxTimelineCtrl::AddItem - Attempted to add nullptr data.");
2247 return;
2248 }
2249
2250 wxColour itemColor = colour.IsOk() ? colour : GetItemColour(m_colorCounter++);
2251
2253
2254 UpdateTotalDurationForItems();
2255
2256 CalcMaxVisibleDuration();
2257 RecalcItems();
2258 Refresh();
2259}
2260
2261template<typename T>
2263{
2264 if (first < 0) first = 0;
2265 int maxFirst = m_totalDuration - m_visibleDuration;
2266 if (maxFirst < 0) maxFirst = 0;
2267 if (first > maxFirst) return maxFirst;
2268 return first;
2269}
2270
2271template<typename T>
2273{
2274 if (m_selectedItems.empty()) return;
2275 wxVector<size_t> selectedItemsCopy = m_selectedItems;
2276 for (auto it = selectedItemsCopy.begin(); it != selectedItemsCopy.end();)
2277 {
2278 if (*it >= m_items.size()) it = selectedItemsCopy.erase(it);
2279 else ++it;
2280 }
2281 if (selectedItemsCopy.empty()) { m_selectedItems.clear(); return; }
2284 for (size_t index : selectedItemsCopy)
2285 {
2286 if (index < m_items.size() && m_items[index].Data) itemsToDelete.push_back(m_items[index].Data);
2287 }
2288 for (size_t i = 0; i < itemsToDelete.size(); ++i)
2289 {
2291 event.SetEventObject(this);
2292 event.SetInt(static_cast<int>(i));
2293 event.SetClientData(itemsToDelete[i]);
2295 }
2296 for (size_t index : selectedItemsCopy)
2297 {
2298 if (index < m_items.size()) m_items.erase(m_items.begin() + index);
2299 }
2300 m_activeTask = m_items.end();
2301 m_lastTask = m_items.end();
2302 m_visibleItemBegin = m_items.end();
2303 m_visibleItemEnd = m_items.end();
2304 m_selectedItems.clear();
2305 m_contextMenuItemIndex = -1;
2306 UpdateTotalDurationForItems();
2307 if (m_firstVisibleTime + m_visibleDuration > m_totalDuration)
2308 SetFirstVisibleTime(wxMax(0, m_totalDuration - m_visibleDuration));
2309 RecalcItems();
2310 Refresh();
2311}
2312
2313template<typename T>
2315{
2317 {
2318 m_contextMenuItemIndex = -1;
2319 return;
2320 }
2321
2322 T* itemData = m_items[m_contextMenuItemIndex].Data;
2323
2325 event.SetEventObject(this);
2326 event.SetInt(m_contextMenuItemIndex);
2327 event.SetClientData(itemData);
2329
2330 m_items.erase(m_items.begin() + m_contextMenuItemIndex);
2331
2332 m_contextMenuItemIndex = -1;
2333
2334 if (m_activeTask != m_items.end() && std::distance(m_items.begin(), m_activeTask) >= (int)m_items.size()) {
2335 m_activeTask = m_items.end();
2336 }
2337 if (m_lastTask != m_items.end() && std::distance(m_items.begin(), m_lastTask) >= (int)m_items.size()) {
2338 m_lastTask = m_items.end();
2339 }
2340
2341 ClearSelection();
2342
2343 UpdateTotalDurationForItems();
2344 if (m_firstVisibleTime + m_visibleDuration > m_totalDuration)
2345 {
2346 SetFirstVisibleTime(wxMax(0, m_totalDuration - m_visibleDuration));
2347 }
2348 else
2349 {
2350 RecalcItems();
2351 }
2352 Refresh();
2353}
TimelineElementState
Definition TimelineItem.h:12
Definition FloatingItemPopupWindow.h:14
FloatingItemPopupWindow(wxWindow *parent, const TimelineItem< T > &itemToDraw, const wxSize &size, TimelineArtProvider *artProvider)
Definition FloatingItemPopupWindow.h:16
Definition TimelineArtProvider.h:15
Definition TimelineItem.h:22
wxColour Colour
Definition TimelineItem.h:57
T * Data
Definition TimelineItem.h:55
int m_displayLane
Definition TimelineItem.h:59
TimelineElementState State
Definition TimelineItem.h:56
Definition wxTimelineCtrl.h:38
void SetEventHandlers()
Definition wxTimelineCtrl_impl.h:66
void OnTimelineItemChangeRight(const wxPoint &pos)
Definition wxTimelineCtrl_impl.h:1157
void OnArrowDown(bool isLeft)
Definition wxTimelineCtrl_impl.h:1968
void SetFirstVisibleTime(int seconds)
Definition wxTimelineCtrl_impl.h:2029
wxCursor GetCursorFromType(ElementType type)
Definition wxTimelineCtrl_impl.h:1786
ElementType
Definition wxTimelineCtrl.h:44
int ScrollerCoordToTime(int coord) const
Definition wxTimelineCtrl_impl.h:2118
void OnLeaveElement(ElementType type)
Definition wxTimelineCtrl_impl.h:946
void OnVisibleFrameDrag(const wxPoint &pos)
Definition wxTimelineCtrl_impl.h:1838
void OnTimelineUp(const wxPoint &pos, ElementType type)
Definition wxTimelineCtrl_impl.h:1030
void OnVisibleFrameUp(const wxPoint &pos)
Definition wxTimelineCtrl_impl.h:1806
void OnTimer(wxTimerEvent &evt)
Definition wxTimelineCtrl_impl.h:1180
void OnSize(wxSizeEvent &evt)
Definition wxTimelineCtrl_impl.h:345
void AddItem(T *data, const wxColour &colour=wxNullColour)
Definition wxTimelineCtrl_impl.h:2242
void SetTotalDuration(int seconds)
Definition wxTimelineCtrl_impl.h:2141
void RemoveContextScrollerItem()
Definition wxTimelineCtrl_impl.h:2314
void OnVisibleFrameDown(const wxPoint &pos, ElementType type)
Definition wxTimelineCtrl_impl.h:1821
void RemoveSelectedItems()
Definition wxTimelineCtrl_impl.h:2272
void OnMouseDown(ElementType type, const wxPoint &pos)
Definition wxTimelineCtrl_impl.h:1464
void AdjustMainViewToScrollerView()
Definition wxTimelineCtrl_impl.h:2207
void OnMouse(wxMouseEvent &evt)
Definition wxTimelineCtrl_impl.h:598
void OnEraseBackground(wxEraseEvent &evt)
Definition wxTimelineCtrl_impl.h:116
void RecalcVisibleFrame()
Definition wxTimelineCtrl_impl.h:386
void OnTimelineItemChangeLeft(const wxPoint &pos)
Definition wxTimelineCtrl_impl.h:1131
void TimeChanged()
Definition wxTimelineCtrl_impl.h:1381
void DrawScroller(wxDC &dc)
Definition wxTimelineCtrl_impl.h:256
void DrawTimeline(wxDC &dc)
Definition wxTimelineCtrl_impl.h:141
void OnArrowUp(bool isLeft)
Definition wxTimelineCtrl_impl.h:1989
void SetVisibleDuration(int seconds)
Definition wxTimelineCtrl_impl.h:2072
void Init()
Definition wxTimelineCtrl_impl.h:4
int ClampFirstVisibleTime(int first) const
Definition wxTimelineCtrl_impl.h:2262
void OnKeyDown(wxKeyEvent &evt)
Definition wxTimelineCtrl_impl.h:1262
void RecalcRects()
Definition wxTimelineCtrl_impl.h:354
void OnTimelineDrag(const wxPoint &pos, ElementType type)
Definition wxTimelineCtrl_impl.h:1041
void Draw(wxDC &dc)
Definition wxTimelineCtrl_impl.h:119
void CalcMaxVisibleDuration()
Definition wxTimelineCtrl_impl.h:2012
void OnScrollerDown(const wxPoint &pos)
Definition wxTimelineCtrl_impl.h:1960
void RecalcItems()
Definition wxTimelineCtrl_impl.h:440
void SendItemEvent(wxEventType evtType, int index)
Definition wxTimelineCtrl_impl.h:2002
void ChangeLastElement(ElementType type)
Definition wxTimelineCtrl_impl.h:1457
void OnEnterElement(ElementType type)
Definition wxTimelineCtrl_impl.h:903
void OnTimelineDown(const wxPoint &pos, ElementType type)
Definition wxTimelineCtrl_impl.h:990
void OnPaint(wxPaintEvent &evt)
Definition wxTimelineCtrl_impl.h:108
void OnMouseUp(ElementType type, const wxPoint &pos)
Definition wxTimelineCtrl_impl.h:1635
void ShowContextMenu(const wxPoint &pos)
Definition wxTimelineCtrl_impl.h:1215
void OnMouseDrag(ElementType type, const wxPoint &pos)
Definition wxTimelineCtrl_impl.h:1507
ElementType GetElementFromPos(const wxPoint &pos)
Definition wxTimelineCtrl_impl.h:1390
void OnMouseCaptureLost(wxMouseCaptureLostEvent &evt)
Definition wxTimelineCtrl_impl.h:980
void OnTimelineItemMove(const wxPoint &pos)
Definition wxTimelineCtrl_impl.h:1048
int ScrollerTimeToCoord(int time)
Definition wxTimelineCtrl_impl.h:2129
void CalcArrowsState()
Definition wxTimelineCtrl_impl.h:1370
@ ID_TIMELINE_DELETE_SCROLLER_ITEM
Definition wxTimelineCtrl.h:32
@ ID_TIMELINE_DELETE
Definition wxTimelineCtrl.h:31
Tval wxClip(Tval value, Tval min_val, Tval max_val)
Definition wxTimelineCtrl.h:18