MythTV master
galleryslide.cpp
Go to the documentation of this file.
1// C++
2#include <algorithm>
3#include <cmath> // for roundf
4
5// MythTV
7#ifndef __cpp_size_t_suffix
9#endif
12
13// MythFrontend
14#include "galleryslide.h"
15
16#define LOC QString("Slide: ")
17#define SBLOC QString("SlideBuffer: ")
18
19
20// Number of slides to use for buffering image requests.
21// When browsing quickly the buffer will load consecutive slides until it fills.
22// If too large, rapid browsing will be stodgy (sequential access) for images that
23// aren't cached (Cached images are always fast).
24// If too small, rapid browsing will result in skipping slides rather than flicking
25// quickly through them.
26// Minimum is 4: 3 for displaying a transition, 1 to handle load requests
27static constexpr size_t SLIDE_BUFFER_SIZE { 9 };
28
29
35void AbstractAnimation::Start(bool forwards, float speed)
36{
37 m_forwards = forwards;
38 m_speed = speed;
39 m_running = true;
40}
41
42
51void Animation::Set(const QVariant& from, const QVariant& to,
52 std::chrono::milliseconds duration,
53 const QEasingCurve& curve, UIEffects::Centre centre)
54{
55 setStartValue(from);
56 setEndValue(to);
57 m_centre = centre;
58 setDuration(duration.count());
59 setEasingCurve(curve);
60}
61
62
68void Animation::Start(bool forwards, float speed)
69{
70 auto duration_ms = std::chrono::milliseconds(duration());
71 if (duration_ms == 0ms)
72 return;
73
74 m_elapsed = forwards ? 0ms : duration_ms;
75 setCurrentTime(m_elapsed.count());
76
77 AbstractAnimation::Start(forwards, speed);
78}
79
80
84{
85 if (!m_running)
86 return;
87
88 std::chrono::milliseconds current = MythDate::currentMSecsSinceEpochAsDuration();
89 std::chrono::milliseconds interval = std::min(current - m_lastUpdate, 50ms);
91 m_elapsed += (m_forwards ? interval : -interval) * static_cast<int>(m_speed);
92 setCurrentTime(m_elapsed.count());
93
94 // Detect completion
95 if ((m_forwards && m_elapsed.count() >= duration()) || (!m_forwards && m_elapsed <= 0ms))
96 Finished();
97}
98
99
104void Animation::updateCurrentValue(const QVariant &value)
105{
106 if (m_parent && m_running)
107 {
109
110 switch (m_type)
111 {
112 case None: break;
113 case Position: m_parent->SetPosition(value.toPoint()); break;
114 case Alpha: m_parent->SetAlpha(value.toInt()); break;
115 case Zoom: m_parent->SetZoom(value.toFloat()); break;
116 case HorizontalZoom: m_parent->SetHorizontalZoom(value.toFloat()); break;
117 case VerticalZoom: m_parent->SetVerticalZoom(value.toFloat()); break;
118 case Angle: m_parent->SetAngle(value.toFloat()); break;
119 }
120 }
121}
122
123
129{
130 // Signal group when child completes
131 m_group.append(child);
133}
134
135
140{
141 qDeleteAll(m_group);
142 m_group.clear();
143}
144
145
149{
150 if (!m_running || m_current < 0 || m_current >= m_group.size())
151 return;
152
153 // Pulse current running child
154 m_group.at(m_current)->Pulse();
155}
156
157
163void SequentialAnimation::Start(bool forwards, float speed)
164{
165 if (m_group.empty())
166 return;
167
168 m_current = forwards ? 0 : m_group.size() - 1;
169
170 // Start group, then first child
171 GroupAnimation::Start(forwards, speed);
172 m_group.at(m_current)->Start(m_forwards, m_speed);
173}
174
175
181{
182 // Set group speed for subsequent children
184
185 // Set active child
186 if (!m_running || m_current < 0 || m_current >= m_group.size())
187 return;
188
189 m_group.at(m_current)->SetSpeed(speed);
190}
191
192
197{
198 bool finished { false };
199
200 // Finish group when last child finishes
201 if (m_forwards)
202 {
203 m_current++;
204 finished = (m_current == m_group.size());
205 }
206 else
207 {
208 m_current--;
209 finished = (m_current < 0);
210 }
211
212 if (finished)
214 else
215 // Start next child
216 m_group.at(m_current)->Start(m_forwards, m_speed);
217}
218
219
223{
224 if (m_running)
225 {
226 // Pulse all children
227 for (AbstractAnimation *animation : std::as_const(m_group))
228 animation->Pulse();
229 }
230}
231
232
238void ParallelAnimation::Start(bool forwards, float speed)
239{
240 if (m_group.empty())
241 return;
242
243 m_finished = m_group.size();
244
245 // Start group, then all children
246 GroupAnimation::Start(forwards, speed);
247 for (AbstractAnimation *animation : std::as_const(m_group))
248 animation->Start(m_forwards, m_speed);
249}
250
251
257{
258 // Set group speed, then all children
260 for (AbstractAnimation *animation : std::as_const(m_group))
261 animation->SetSpeed(m_speed);
262}
263
264
269{
270 // Finish group when last child finishes
271 if (--m_finished == 0)
273}
274
275
280void PanAnimation::updateCurrentValue(const QVariant &value)
281{
282 if (m_parent && m_running)
283 {
284 Slide *image = m_parent;
285 image->SetPan(value.toPoint());
286 }
287}
288
289
296Slide::Slide(MythUIType *parent, const QString& name, MythUIImage *image)
297 : MythUIImage(parent, name)
298{
299 // Clone from image
300 CopyFrom(image);
301
302 // Null parent indicates we should become a child of the image (after
303 // copy to avoid recursion)
304 if (!parent)
305 {
306 // Slides sit on top of parent image area
307 SetArea(MythRect(image->GetArea().toQRect()));
308 m_area.moveTo(0, 0);
309 setParent(image);
310 m_parent = image;
311 image->AddChild(this);
312 }
313
314 // Provide animations for pan & zoom
315 if (GetPainter()->SupportsAnimation())
316 {
318 m_panAnimation = new PanAnimation(this);
319 }
320
321 connect(this, &MythUIImage::LoadComplete, this, &Slide::SlideLoaded);
322}
323
324
329{
330 delete m_zoomAnimation;
331 delete m_panAnimation;
332 LOG(VB_GUI, LOG_DEBUG, "Deleted Slide " + objectName());
333}
334
335
340{
341 m_state = kEmpty;
342 m_data.clear();
343 m_waitingFor.clear();
344 SetCropRect(0, 0, 0, 0);
345 SetVisible(false);
346}
347
348
354{
355 switch (m_state)
356 {
357 case kEmpty: return 'e';
358 case kFailed: return 'f';
359 case kLoaded: return m_waitingFor ? 'r' : 'a';
360 case kLoading: return m_waitingFor ? 'l' : 'p';
361 }
362 return '?';
363}
364
365
378bool Slide::LoadSlide(const ImagePtrK& im, int direction, bool notifyCompletion)
379{
380 m_direction = direction;
381 m_waitingFor = notifyCompletion ? im : ImagePtrK();
382
383 if (im == m_data)
384 {
385 LOG(VB_FILE, LOG_DEBUG, LOC + QString("Already loading/loaded %1 in %2")
386 .arg(im->m_filePath, objectName()));
387
388 if (m_state >= kLoaded && notifyCompletion)
389 // Image has been pre-loaded
390 emit ImageLoaded(this);
391
392 return (m_state >= kLoaded);
393 }
394
395 // Is a different image loading ?
396 if (m_state == kLoading)
397 {
398 // Can't abort image loads, so must wait for it to finish
399 // before starting new load
400 m_waitingFor = im;
401
402 LOG(VB_FILE, LOG_DEBUG, LOC + QString("Postponing load of %1 in %2")
403 .arg(im->m_filePath, objectName()));
404
405 return false;
406 }
407
408 // Start load
409 m_data = im;
411
412 if (im->m_type == kVideoFile)
413 {
414 // Use thumbnail, which has already been orientated
415 SetFilename(im->m_thumbNails.at(0).second);
417 }
418 else
419 {
420 // Load image
421 SetFilename(im->m_url);
422 SetOrientation(Orientation(m_data->m_orientation).GetCurrent());
423 }
424
425 // Load in background
426 Load(true);
427 return false;
428}
429
430
438{
440 if (m_state == kFailed)
441 LOG(VB_GENERAL, LOG_ERR, LOC +
442 QString("Failed to load %1").arg(m_data->m_filePath));
443
444 // Ignore superseded requests and preloads
445 if (m_data == m_waitingFor)
446 {
447 // Loaded image is the latest requested
448 emit ImageLoaded(this);
449 }
450 else if (m_waitingFor)
451 {
452 LOG(VB_FILE, LOG_DEBUG, LOC + QString("Starting delayed load %1")
453 .arg(m_waitingFor->m_filePath));
454
455 // Start latest postponed load
457 }
458}
459
460
466void Slide::Zoom(int percentage)
467{
468 // Sentinel indicates reset to default zoom
469 float newZoom = (percentage == 0)
470 ? 1.0F
471 : std::clamp(m_zoom * (1.0F + (percentage / 100.0F)), MIN_ZOOM, MAX_ZOOM);
472 if (newZoom != m_zoom)
473 {
474 if (m_zoomAnimation)
475 {
476 m_zoomAnimation->Set(m_zoom, newZoom, 250ms, QEasingCurve::OutQuad);
478 }
479 else
480 {
481 SetZoom(newZoom);
482 }
483 }
484}
485
486
493void Slide::SetZoom(float zoom)
494{
495 m_zoom = zoom;
497
498 // TODO
499 // MythUIImage displaces widget or doesn't centre for some combinations of
500 // zoom centre/cropping so frig centre for now.
502
503 SetPan(m_pan);
504}
505
506
511void Slide::Pan(QPoint offset)
512{
513 // Panning only possible when zoomed in
514 if (m_zoom > 1.0F)
515 {
516 QPoint start = m_pan;
517
518 // Sentinel indicates reset to centre
519 // Panning is applied to original (unzoomed) image co-ords.
520 // Adjust offset for zoom so that pan moves a constant screen distance rather
521 // than constant image distance
522 QPoint dest = offset.isNull() ? QPoint(0, 0) : start + offset / m_zoom;
523
524 if (m_panAnimation)
525 {
526 m_panAnimation->Set(start, dest, 250ms, QEasingCurve::Linear);
528 }
529 else
530 {
531 SetPan(dest);
532 }
533 }
534}
535
536
543void Slide::SetPan(QPoint pos)
544{
545 if (m_state == kFailed)
546 {
547 m_pan = pos;
548 return;
549 }
550
551 // Determine zoom of largest dimension
552 QRect imageArea = m_images[m_curPos]->rect();
553 float hRatio = float(imageArea.height()) / m_area.height();
554 float wRatio = float(imageArea.width()) / m_area.width();
555 float ratio = std::max(hRatio, wRatio); // TODO create a Rational number class
556
557 if (m_zoom != 0.0F)
558 ratio /= m_zoom;
559
560 // Determine crop area
561 int h = std::min(int(roundf(m_area.height() * ratio)), imageArea.height());
562 int w = std::min(int(roundf(m_area.width() * ratio)), imageArea.width());
563 int x = imageArea.center().x() - (w / 2);
564 int y = imageArea.center().y() - (h / 2);
565
566 // Constrain pan to boundaries
567 int limitX = (imageArea.width() - w) / 2;
568 int limitY = (imageArea.height() - h) / 2;
569 m_pan.setX(std::clamp(pos.x(), -limitX, limitX));
570 m_pan.setY(std::clamp(pos.y(), -limitY, limitY));
571
572 SetCropRect(x + m_pan.x(), y + m_pan.y(), w, h);
573 SetRedraw();
574}
575
576
581{
582 // Update zoom/pan animations
583 if (m_zoomAnimation)
585
586 if (m_panAnimation)
588}
589
590
592{
593 LOG(VB_GUI, LOG_DEBUG, "Deleted Slidebuffer");
594}
595
596
598{
599 QMutexLocker lock(&m_mutexQ);
600 for (Slide *s : std::as_const(m_queue))
601 s->Clear();
602 LOG(VB_GUI, LOG_DEBUG, "Aborted Slidebuffer");
603}
604
605
612{
613 // Require at least 4 slides: 2 for transitions, 1 to handle further requests
614 // and 1 to prevent solitary slide from being used whilst it is loading
615#ifdef __cpp_size_t_suffix
616 size_t size = std::max(SLIDE_BUFFER_SIZE, 4UZ);
617#else
618 size_t size = std::max(SLIDE_BUFFER_SIZE, 4_UZ);
619#endif
620
621 // Fill buffer with slides cloned from the XML image widget
622
623 // Create first as a child of the XML image.
624 auto *slide = new Slide(nullptr, "slide0", &image);
625
626 // Buffer is notified when it has loaded image
627 connect(slide, &Slide::ImageLoaded,
628 this, qOverload<Slide*>(&SlideBuffer::Flush));
629
630 m_queue.enqueue(slide);
631
632 // Rest are simple clones of first
633 for (size_t i = 1; i < size; ++i)
634 {
635 slide = new Slide(&image, QString("slide%1").arg(i), slide);
636
637 // All slides (except first) start off hidden
638 slide->SetVisible(false);
639
640 // Buffer is notified when it has loaded image
641 connect(slide, &Slide::ImageLoaded,
642 this, qOverload<Slide*>(&SlideBuffer::Flush));
643
644 m_queue.enqueue(slide);
645 }
646
647 m_nextLoad = 1;
648}
649
650
656{
657 QMutexLocker lock(&m_mutexQ);
658
659 QString state;
660 for (int i = 0; i < m_queue.size(); ++i)
661 {
662 QChar code(m_queue.at(i)->GetDebugState());
663 state += (i == m_nextLoad ? code.toUpper() : code);
664 }
665 return QString("[%1] (%2)").arg(state, m_queue.head()->objectName());
666}
667
668
676bool SlideBuffer::Load(const ImagePtrK& im, int direction)
677{
678 if (!im)
679 return false;
680
681 QMutexLocker lock(&m_mutexQ);
682
683 // Start loading image in next available slide
684 Slide *slide = m_queue.at(m_nextLoad);
685
686 // Further load requests will go to same slide if no free ones are available
687 if (m_nextLoad < m_queue.size() - 1)
688 ++m_nextLoad;
689
690 LOG(VB_FILE, LOG_DEBUG, SBLOC + QString("Loading %1 in %2, %3")
691 .arg(im->m_filePath, slide->objectName(), BufferState()));
692
693 return slide->LoadSlide(im, direction, true);
694}
695
696
702{
703 if (!im)
704 return;
705
706 QMutexLocker lock(&m_mutexQ);
707
708 // Start loading image in next available slide
709 Slide *slide = m_queue.at(m_nextLoad);
710
711 LOG(VB_FILE, LOG_DEBUG, SBLOC + QString("Preloading %1 in %2, %3")
712 .arg(im->m_filePath, slide->objectName(), BufferState()));
713
714 // Load silently
715 slide->LoadSlide(im);
716}
717
718
724{
725 QMutexLocker lock(&m_mutexQ);
726
727 // Reset slide & return to buffer for re-use
728 Slide *slide = m_queue.dequeue();
729 slide->Clear();
730 m_queue.enqueue(slide);
731
732 QString name = slide->objectName();
733
734 // Free constrained load ptr now a spare slide is available
735 if (!m_queue.at(--m_nextLoad)->IsEmpty())
736 ++m_nextLoad;
737
738 LOG(VB_FILE, LOG_DEBUG, SBLOC + QString("Released %1").arg(name));
739
740 // Flush any pending slides that originate from multiple requests (skipping)
741 Flush(m_queue.head(), "Pending");
742}
743
744
751void SlideBuffer::Flush(Slide *slide, const QString& reason)
752{
753 QMutexLocker lock(&m_mutexQ);
754
755 // Determine number of consecutive slides that are now available after head
756 // Include last slide to ensure transition speed is consistent: it will never
757 // be displayed because queue size is always > 2
758 int available = 1;
759 while (available < m_queue.size() && m_queue.at(available)->IsLoaded())
760 ++available;
761
762 if (available == 1)
763 return;
764
765 // Notify that more slides are available
766 ImagePtrK im = slide->GetImageData();
767 QString path = im ? im->m_filePath : "Unknown";
768
769 LOG(VB_FILE, LOG_DEBUG, SBLOC + QString("%1 %2 in %3, %4")
770 .arg(reason, path, slide->objectName(), BufferState()));
771
772 emit SlideReady(--available);
773}
774
776{
777 Flush(slide, "Loaded");
778};
779
780#include "moc_galleryslide.cpp"
Base animation class that is driven by a Myth pulse and implements variable speed.
Definition: galleryslide.h:27
virtual void Finished()
To be called when animation completes.
Definition: galleryslide.h:39
void finished()
Signals animation has finished.
virtual void Start(bool forwards, float speed=1.0)
Initialise & start base animation.
float m_speed
Real-time = 1.0, Double-speed = 2.0.
Definition: galleryslide.h:48
bool m_forwards
Play direction.
Definition: galleryslide.h:46
bool m_running
True whilst animation is active.
Definition: galleryslide.h:47
A single animation controlling alpha, zoom, rotation and position.
Definition: galleryslide.h:55
@ HorizontalZoom
Definition: galleryslide.h:58
std::chrono::milliseconds m_lastUpdate
Definition: galleryslide.h:86
UIEffects::Centre m_centre
Definition: galleryslide.h:82
void updateCurrentValue(const QVariant &value) override
Update animated value.
void Start(bool forwards=true, float speed=1.0) override
Start a single animation.
Slide * m_parent
Image to be animated.
Definition: galleryslide.h:80
void Pulse() override
Progress single animation.
void Set(const QVariant &from, const QVariant &to, std::chrono::milliseconds duration=500ms, const QEasingCurve &curve=QEasingCurve::InOutCubic, UIEffects::Centre centre=UIEffects::Middle)
Initialises an animation.
Type m_type
Definition: galleryslide.h:81
std::chrono::milliseconds m_elapsed
Current millisec position within animation, 0..duration.
Definition: galleryslide.h:85
void SetSpeed(float speed) override
Definition: galleryslide.h:99
QList< AbstractAnimation * > m_group
Definition: galleryslide.h:105
void Clear() override
Delete all child animations.
void Start(bool forwards, float speed=1.0) override
Initialise & start base animation.
Definition: galleryslide.h:97
virtual void Add(AbstractAnimation *child)
Add child animation to group.
Wrapper around QRect allowing us to handle percentage and other relative values for areas in mythui.
Definition: mythrect.h:18
QRect toQRect(void) const
Definition: mythrect.cpp:405
Image widget, displays a single image or multiple images in sequence.
Definition: mythuiimage.h:98
bool Load(bool allowLoadInBackground=true, bool forceStat=false)
Load the image(s), wraps ImageLoader::LoadImage()
QHash< int, MythImage * > m_images
Definition: mythuiimage.h:169
void CopyFrom(MythUIType *base) override
Copy this widgets state from another.
void SetFilename(const QString &filename)
Must be followed by a call to Load() to load the image.
unsigned int m_curPos
Definition: mythuiimage.h:177
void SetCropRect(int x, int y, int width, int height)
Crop the image using the given rectangle, useful for removing unsightly edges from imported images or...
void LoadComplete()
void SetOrientation(int orientation)
Saves the exif orientation value of the first image in the widget.
The base class on which all widgets and screens are based.
Definition: mythuitype.h:97
void AddChild(MythUIType *child)
Add a child UIType.
Definition: mythuitype.cpp:81
UIEffects m_effects
Definition: mythuitype.h:295
virtual void SetVisible(bool visible)
virtual MythPainter * GetPainter(void)
virtual void SetArea(const MythRect &rect)
Definition: mythuitype.cpp:596
void SetAngle(float angle)
Definition: mythuitype.cpp:965
void SetRedraw(void)
Definition: mythuitype.cpp:299
void SetVerticalZoom(float zoom)
Definition: mythuitype.cpp:959
virtual MythRect GetArea(void) const
If the object has a minimum area defined, return it, other wise return the default area.
Definition: mythuitype.cpp:871
MythUIType * m_parent
Definition: mythuitype.h:308
void SetPosition(int x, int y)
Convenience method, calls SetPosition(const MythPoint&) Override that instead to change functionality...
Definition: mythuitype.cpp:519
void SetAlpha(int newalpha)
Definition: mythuitype.cpp:928
void SetCentre(UIEffects::Centre centre)
Definition: mythuitype.cpp:942
void SetHorizontalZoom(float zoom)
Definition: mythuitype.cpp:953
MythRect m_area
Definition: mythuitype.h:288
Encapsulates Exif orientation processing.
Definition: imagemetadata.h:63
int GetCurrent() const
Determines orientation required for an image.
Specialised animation for panning slideshow images (MythUI doesn't support panning)
Definition: galleryslide.h:149
void updateCurrentValue(const QVariant &value) override
Update pan value.
void SetSpeed(float speed) override
Change speed of group and all child animations.
void Finished() override
A child animation has completed.
void Start(bool forwards, float speed=1.0) override
Start parallel group. All children play simultaneously.
int m_finished
Count of child animations that have finished.
Definition: galleryslide.h:142
void Pulse() override
Progress parallel animations.
void Start(bool forwards, float speed=1.0) override
Start sequential animation.
void Finished() override
A child animation has completed.
void Pulse() override
Progress sequential animation.
int m_current
Index of child currently playing.
Definition: galleryslide.h:124
void SetSpeed(float speed) override
Change speed of current child animation and all subsequent ones.
QString BufferState()
Determines buffer state for debug logging.
void Flush(Slide *slide, const QString &reason)
Signal if any slides are waiting to be displayed.
void ReleaseCurrent()
Move head slide to back of queue and flush waiting slides.
void Initialise(MythUIImage &image)
Construct buffer.
int m_nextLoad
Index of first spare slide, (or last slide if none spare)
Definition: galleryslide.h:255
bool Load(const ImagePtrK &im, int direction)
Assign an image to next available slide, start loading and signal when done.
void Preload(const ImagePtrK &im)
Load an image in next available slide.
QRecursiveMutex m_mutexQ
Queue protection.
Definition: galleryslide.h:253
QQueue< Slide * > m_queue
Queue of slides.
Definition: galleryslide.h:254
void SlideReady(int count)
Signals that buffer has (count) loaded slides awaiting display.
~SlideBuffer() override
A specialised image for slideshows.
Definition: galleryslide.h:159
int m_direction
Navigation that created this image, -1 = Prev, 0 = Update, 1 = Next.
Definition: galleryslide.h:198
QChar GetDebugState() const
Return debug status.
float m_zoom
Current zoom, 1.0 = fullsize.
Definition: galleryslide.h:196
void Pan(QPoint offset)
Initiate pan.
QPoint m_pan
Pan position (0,0) = no pan.
Definition: galleryslide.h:201
void ImageLoaded(Slide *)
Generated when the last requested image has loaded.
PanAnimation * m_panAnimation
Dedicated animation for panning, if supported.
Definition: galleryslide.h:200
void Zoom(int percentage)
Initiate slide zoom.
Animation * m_zoomAnimation
Dedicated animation for zoom, if supported.
Definition: galleryslide.h:199
ImagePtrK GetImageData() const
Definition: galleryslide.h:167
ImagePtrK m_data
The image currently loading/loaded.
Definition: galleryslide.h:193
SlideState m_state
Slide validity.
Definition: galleryslide.h:192
void SetZoom(float zoom)
Sets slide zoom.
bool LoadSlide(const ImagePtrK &im, int direction=0, bool notifyCompletion=false)
Load slide with an image.
void SetPan(QPoint pos)
Sets slide pan.
void Pulse() override
Update pan & zoom animations.
@ kLoading
Definition: galleryslide.h:190
void SlideLoaded()
An image has completed loading.
void Clear()
Reset slide to unused state.
~Slide() override
Destructor.
ImagePtrK m_waitingFor
The most recently requested image. Null for preloads. Differs from m_data when skipping.
Definition: galleryslide.h:195
Slide(MythUIType *parent, const QString &name, MythUIImage *image)
Clone slide from a theme MythUIImage.
Centre m_centre
#define LOC
#define SBLOC
static constexpr size_t SLIDE_BUFFER_SIZE
Defines specialised images used by the Gallery slideshow and the animation framework used by transfor...
#define MAX_ZOOM
Definition: galleryslide.h:20
#define MIN_ZOOM
Definition: galleryslide.h:19
Handles Exif/FFMpeg metadata tags for images.
QSharedPointer< ImageItemK > ImagePtrK
Definition: imagetypes.h:165
@ kVideoFile
A video.
Definition: imagetypes.h:40
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
std::chrono::milliseconds currentMSecsSinceEpochAsDuration(void)
Definition: mythdate.cpp:207
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
static eu8 clamp(eu8 value, eu8 low, eu8 high)
Definition: pxsup2dast.c:204