1 // gui.cpp: Top level GUI for SWF player, for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
4 // 2011 Free Software Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 //
20
21 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h"
23 #endif
24
25 #include "gui.h"
26
27 #include <vector>
28 #include <algorithm>
29 #include <iostream>
30
31 #include "MovieClip.h"
32 #include "Renderer.h"
33 #include "sound_handler.h"
34 #include "movie_root.h"
35 #include "VM.h"
36 #include "DisplayObject.h"
37 #include "GnashEnums.h"
38 #include "RunResources.h"
39 #include "StreamProvider.h"
40 #include "ScreenShotter.h"
41 #include "Movie.h"
42
43 #ifdef GNASH_FPS_DEBUG
44 #include "ClockTime.h"
45 #include <boost/format.hpp>
46 #endif
47
48 /// Define this to make sure each frame is fully rendered from ground up
49 /// even if no motion has been detected in the movie.
50 //#define FORCE_REDRAW 1
51
52 /// Define this if you want to debug the *detection* of region updates only.
53 /// This will disable region updates for the backend (GUI+renderer) completely
54 /// so that only the last region (red frame) will be visible. However, this
55 /// slows down rendering as each frame is fully re-rendered. If you want to
56 /// debug the GUI part, however (see if blitting the region works), then you
57 /// probably won't define this.
58 #ifndef DISABLE_REGION_UPDATES_DEBUGGING
59 //#define REGION_UPDATES_DEBUGGING_FULL_REDRAW 1
60 #endif
61
62 #ifndef DISABLE_REGION_UPDATES_DEBUGGING
63 // a runtime check would make the { x; } block conditionally executed
64 #define IF_DEBUG_REGION_UPDATES(...) do { if (_showUpdatedRegions) { __VA_ARGS__; } } while(0);
65 #else
66 #define IF_DEBUG_REGION_UPDATES(x)
67 #endif
68
69 // Define this to have gnash print the mouse pointer coordinates
70 // as the mouse moves. See also ENABLE_KEYBOARD_MOUSE_MOVEMENTS
71 // to have more control over mouse pointer.
72 //
73 //#define DEBUG_MOUSE_COORDINATES 1
74
75 namespace gnash {
76
77 struct Gui::Display
78 {
Displaygnash::Gui::Display79 Display(Gui& g, movie_root& r) : _g(g), _r(r) {}
operator ()gnash::Gui::Display80 void operator()() const {
81 InvalidatedRanges world_ranges;
82 world_ranges.setWorld();
83 _g.setInvalidatedRegions(world_ranges);
84 _g.display(&_r);
85 }
86 private:
87 Gui& _g;
88 movie_root& _r;
89 };
90
Gui(RunResources & r)91 Gui::Gui(RunResources& r) :
92 _loop(true),
93 _xid(0),
94 _width(1),
95 _height(1),
96 _runResources(r),
97 _interval(0),
98 _redraw_flag(true),
99 _fullscreen(false),
100 _mouseShown(true),
101 _maxAdvances(0),
102 _advances(0),
103 _xscale(1.0f),
104 _yscale(1.0f),
105 _xoffset(0),
106 _yoffset(0)
107 #ifdef GNASH_FPS_DEBUG
108 ,fps_counter(0)
109 ,fps_counter_total(0)
110 ,fps_timer(0)
111 ,fps_timer_interval(0.0)
112 ,frames_dropped(0)
113 #endif
114 ,_movieDef(nullptr)
115 ,_stage(nullptr)
116 ,_stopped(false)
117 ,_started(false)
118 ,_showUpdatedRegions(false)
119
120 // NOTE: it's important that _systemClock is constructed
121 // before and destroyed after _virtualClock !
122 ,_systemClock()
123 ,_virtualClock(_systemClock)
124 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
125 ,_xpointer(0)
126 ,_ypointer(0)
127 ,_keyboardMouseMovements(true) // TODO: base default on gnashrc or always false and provide menu item to toggle
128 ,_keyboardMouseMovementsStep(1)
129 #endif
130 {
131
132 }
133
Gui(unsigned long xid,float scale,bool loop,RunResources & r)134 Gui::Gui(unsigned long xid, float scale, bool loop, RunResources& r)
135 :
136 _loop(loop),
137 _xid(xid),
138 _width(1),
139 _height(1),
140 _runResources(r),
141 _interval(0),
142 _redraw_flag(true),
143 _fullscreen(false),
144 _mouseShown(true),
145 _maxAdvances(0),
146 _advances(0),
147 _xscale(scale),
148 _yscale(scale),
149 _xoffset(0), // TODO: x and y offset will need update !
150 _yoffset(0)
151 #ifdef GNASH_FPS_DEBUG
152 ,fps_counter(0)
153 ,fps_counter_total(0)
154 ,fps_timer(0)
155 ,fps_timer_interval(0.0)
156 ,frames_dropped(0)
157 #endif
158 ,_movieDef(nullptr)
159 ,_stage(nullptr)
160 ,_stopped(false)
161 ,_started(false)
162 ,_showUpdatedRegions(false)
163
164 // NOTE: it's important that _systemClock is constructed
165 // before and destroyed after _virtualClock !
166 ,_systemClock()
167 ,_virtualClock(_systemClock)
168 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
169 ,_xpointer(0)
170 ,_ypointer(0)
171 ,_keyboardMouseMovements(true) // TODO: base default on gnashrc or always false and provide menu item to toggle
172 ,_keyboardMouseMovementsStep(1)
173 #endif
174 {
175 }
176
~Gui()177 Gui::~Gui()
178 {
179 if ( _movieDef.get() ) {
180 log_debug("~Gui - _movieDef refcount: %d", _movieDef->get_ref_count());
181 }
182
183 #ifdef GNASH_FPS_DEBUG
184 if ( fps_timer_interval ) {
185 std::cerr << "Total frame advances/drops: "
186 << fps_counter_total << "/" << frames_dropped << std::endl;
187 }
188 #endif
189 }
190
191 void
setClipboard(const std::string &)192 Gui::setClipboard(const std::string&)
193 {
194 LOG_ONCE(log_unimpl(_("Clipboard not yet supported in this GUI")));
195 }
196
197 void
setFullscreen()198 Gui::setFullscreen()
199 {
200 log_unimpl(_("Fullscreen not yet supported in this GUI"));
201 }
202
203 void
resizeWindow(int,int)204 Gui::resizeWindow(int /*width*/, int /*height*/)
205 {
206 log_unimpl(_("Window resize not yet supported in this GUI"));
207 }
208
209 void
unsetFullscreen()210 Gui::unsetFullscreen()
211 {
212 log_unimpl(_("Fullscreen not yet supported in this GUI"));
213 }
214
215 void
quit()216 Gui::quit()
217 {
218 // Take a screenshot of the last frame if required.
219 if (_screenShotter.get() && _renderer.get()) {
220 Display dis(*this, *_stage);
221 _screenShotter->last(*_renderer, &dis);
222 }
223
224 quitUI();
225 }
226
227 void
hideMenu()228 Gui::hideMenu()
229 {
230 LOG_ONCE(log_unimpl(_("Menu show/hide not yet supported in this GUI")));
231 }
232
233 bool
showMouse(bool)234 Gui::showMouse(bool /* show */)
235 {
236 LOG_ONCE(log_unimpl(_("Mouse show/hide not yet supported in this GUI")));
237 return true;
238 }
239
240 void
showMenu(bool)241 Gui::showMenu(bool /* show */)
242 {
243 LOG_ONCE(log_unimpl(_("Menu show/hide not yet supported in this GUI")));
244 }
245
246 void
allowScale(bool allow)247 Gui::allowScale(bool allow)
248 {
249 if (!_stage) {
250 log_error("Gui::allowScale called before a movie_root was available");
251 return;
252 }
253
254 if (allow) _stage->setStageScaleMode(movie_root::SCALEMODE_SHOWALL);
255 else _stage->setStageScaleMode(movie_root::SCALEMODE_NOSCALE);
256 }
257
258 void
toggleFullscreen()259 Gui::toggleFullscreen()
260 {
261 /// Sends request to Gnash core to change display state.
262 if (_fullscreen) {
263 _stage->setStageDisplayState(movie_root::DISPLAYSTATE_NORMAL);
264 } else {
265 _stage->setStageDisplayState(movie_root::DISPLAYSTATE_FULLSCREEN);
266 }
267 }
268
269 void
restart()270 Gui::restart()
271 {
272 _stage->reset();
273 _started = false;
274 start();
275 }
276
277 void
updateStageMatrix()278 Gui::updateStageMatrix()
279 {
280 if (!_stage) {
281 // When VM initializes, we'll get a call to resize_view, which
282 // would call us again.
283 log_error(_("Can't update stage matrix till VM is initialized"));
284 return;
285 }
286
287 assert(_stage); // when VM is initialized this should hold
288
289 float swfwidth = _movieDef->get_width_pixels();
290 float swfheight = _movieDef->get_height_pixels();
291
292 // Fetch scale mode
293 movie_root::ScaleMode scaleMode = _stage->getStageScaleMode();
294
295 switch (scaleMode) {
296 case movie_root::SCALEMODE_NOSCALE:
297 _xscale = _yscale = 1.0f;
298 break;
299
300 case movie_root::SCALEMODE_SHOWALL:
301 // set new scale value ( user-pixel / pseudo-pixel ). Do
302 // not divide by zero, or we end up with an invalid
303 // stage matrix that returns nan values.
304 _xscale = (swfwidth == 0.0f) ? 1.0f : _width / swfwidth;
305 _yscale = (swfheight == 0.0f) ? 1.0f : _height / swfheight;
306
307 // Scale proportionally, using smallest scale
308 if (_xscale < _yscale) {
309 _yscale = _xscale;
310 } else if (_yscale < _xscale) {
311 _xscale = _yscale;
312 }
313 break;
314
315 case movie_root::SCALEMODE_NOBORDER:
316 // set new scale value ( user-pixel / pseudo-pixel )
317 _xscale = (swfwidth == 0.0f) ? 1.0f : _width / swfwidth;
318 _yscale = (swfheight == 0.0f) ? 1.0f : _height / swfheight;
319
320 // Scale proportionally, using biggest scale
321 if (_xscale > _yscale) {
322 _yscale = _xscale;
323 } else if (_yscale > _xscale) {
324 _xscale = _yscale;
325 }
326 break;
327
328 case movie_root::SCALEMODE_EXACTFIT:
329 // NOTE: changing aspect ratio is valid!
330 _xscale = (swfwidth == 0.0f) ? 1.0f : _width / swfwidth;
331 _yscale = (swfheight == 0.0f) ? 1.0f : _height / swfheight;
332 break;
333
334 default:
335 log_error(_("Invalid scaleMode %d"), scaleMode);
336 break;
337 }
338
339 _xoffset=0;
340 _yoffset=0;
341
342 // Fetch align mode
343 movie_root::StageAlign align = _stage->getStageAlignment();
344 movie_root::StageHorizontalAlign halign = align.first;
345 movie_root::StageVerticalAlign valign = align.second;
346
347 // Handle horizontal alignment
348 switch ( halign ) {
349 case movie_root::STAGE_H_ALIGN_L:
350 {
351 // _xoffset=0 is fine
352 break;
353 }
354
355 case movie_root::STAGE_H_ALIGN_R:
356 {
357 // Offsets in pixels
358 float defWidth = swfwidth *= _xscale;
359 float diffWidth = _width-defWidth;
360 _xoffset = diffWidth;
361 break;
362 }
363
364 case movie_root::STAGE_V_ALIGN_C:
365 {
366 // Offsets in pixels
367 float defWidth = swfwidth *= _xscale;
368 float diffWidth = _width-defWidth;
369 _xoffset = diffWidth/2.0;
370 break;
371 }
372
373 default:
374 {
375 log_error(_("Invalid horizontal align %d"), valign);
376 break;
377 }
378 }
379
380 // Handle vertical alignment
381 switch ( valign ) {
382 case movie_root::STAGE_V_ALIGN_T:
383 {
384 // _yoffset=0 is fine
385 break;
386 }
387
388 case movie_root::STAGE_V_ALIGN_B:
389 {
390 float defHeight = swfheight *= _yscale;
391 float diffHeight = _height-defHeight;
392 _yoffset = diffHeight;
393 break;
394 }
395
396 case movie_root::STAGE_V_ALIGN_C:
397 {
398 float defHeight = swfheight *= _yscale;
399 float diffHeight = _height-defHeight;
400 _yoffset = diffHeight/2.0;
401 break;
402 }
403
404 default:
405 {
406 log_error(_("Invalid vertical align %d"), valign);
407 break;
408 }
409 }
410
411 //log_debug("updateStageMatrix: scaleMode:%d, valign:%d, halign:%d",
412 //scaleMode, valign, halign);
413
414 // TODO: have a generic set_matrix ?
415 if (_renderer.get()) {
416 _renderer->set_scale(_xscale, _yscale);
417 _renderer->set_translation(_xoffset, _yoffset);
418 } else {
419 //log_debug("updateStageMatrix: could not signal updated stage
420 //matrix to renderer (no renderer registered)");
421 }
422
423 // trigger redraw
424 //_redraw_flag |= (_width!=width) || (_height!=height);
425 _redraw_flag = true; // this fixes bug #21971
426 }
427
428
429 void
resize_view(int width,int height)430 Gui::resize_view(int width, int height)
431 {
432 GNASH_REPORT_FUNCTION;
433
434 assert(width > 0);
435 assert(height > 0);
436
437 if (_stage && _started) {
438 _stage->setDimensions(width, height);
439 }
440
441 _width = width;
442 _height = height;
443 _validbounds.setTo(0, 0, _width, _height);
444
445 updateStageMatrix();
446
447 if ( _stage && _started ) {
448 display(_stage);
449 }
450 }
451
452
453 void
toggleSound()454 Gui::toggleSound()
455 {
456 assert (_stage);
457 // @todo since we registered the sound handler, shouldn't we know
458 // already what it is ?!
459 #ifdef USE_SOUND
460 sound::sound_handler* s = _stage->runResources().soundHandler();
461
462 if (!s) return;
463
464 if (s->is_muted()) s->unmute();
465 else s->mute();
466 #endif // USE_SOUND
467 }
468
469 void
notifyMouseMove(int ux,int uy)470 Gui::notifyMouseMove(int ux, int uy)
471 {
472 movie_root* m = _stage;
473
474 if ( ! _started ) return;
475
476 if ( _stopped ) return;
477
478 // A stage pseudopixel is user pixel / _xscale wide
479 std::int32_t x = (ux-_xoffset) / _xscale;
480
481 // A stage pseudopixel is user pixel / _xscale high
482 std::int32_t y = (uy-_yoffset) / _yscale;
483
484 #ifdef DEBUG_MOUSE_COORDINATES
485 log_debug("mouse @ %d,%d", x, y);
486 #endif
487
488 if ( m->mouseMoved(x, y) ) {
489 // any action triggered by the
490 // event required screen refresh
491 display(m);
492 }
493
494 DisplayObject* activeEntity = m->getActiveEntityUnderPointer();
495 if ( activeEntity ) {
496 if ( activeEntity->isSelectableTextField() ) {
497 setCursor(CURSOR_INPUT);
498 } else if ( activeEntity->allowHandCursor() ) {
499 setCursor(CURSOR_HAND);
500 } else {
501 setCursor(CURSOR_NORMAL);
502 }
503 } else {
504 setCursor(CURSOR_NORMAL);
505 }
506
507 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
508 _xpointer = ux;
509 _ypointer = uy;
510 #endif
511
512
513 }
514
515 void
notifyMouseWheel(int delta)516 Gui::notifyMouseWheel(int delta)
517 {
518 movie_root* m = _stage;
519 assert(m);
520
521 if (!_started) return;
522 if (_stopped) return;
523
524 if (m->mouseWheel(delta)) {
525 // any action triggered by the
526 // event required screen refresh
527 display(m);
528 }
529 }
530
531 void
notifyMouseClick(bool mouse_pressed)532 Gui::notifyMouseClick(bool mouse_pressed)
533 {
534 movie_root* m = _stage;
535 assert(m);
536
537 if (!_started) return;
538 if (_stopped) return;
539
540 if (m->mouseClick(mouse_pressed)) {
541 // any action triggered by the
542 // event required screen refresh
543 display(m);
544 }
545 }
546
547 void
refreshView()548 Gui::refreshView()
549 {
550 movie_root* m = _stage;
551
552 if ( ! _started ) return;
553
554 assert(m);
555 _redraw_flag=true;
556 display(m);
557 }
558
559
560 void
notify_key_event(gnash::key::code k,int modifier,bool pressed)561 Gui::notify_key_event(gnash::key::code k, int modifier, bool pressed)
562 {
563
564 // Handle GUI shortcuts
565 if (pressed) {
566 if (k == gnash::key::ESCAPE) {
567 if (isFullscreen()) {
568 _stage->setStageDisplayState(movie_root::DISPLAYSTATE_NORMAL);
569 }
570 }
571
572 if (modifier & gnash::key::GNASH_MOD_CONTROL) {
573 switch (k) {
574 case gnash::key::o:
575 case gnash::key::O:
576 takeScreenShot();
577 break;
578 case gnash::key::r:
579 case gnash::key::R:
580 restart();
581 break;
582 case gnash::key::p:
583 case gnash::key::P:
584 pause();
585 break;
586 case gnash::key::l:
587 case gnash::key::L:
588 refreshView();
589 break;
590 case gnash::key::q:
591 case gnash::key::Q:
592 case gnash::key::w:
593 case gnash::key::W:
594 quit();
595 break;
596 case gnash::key::f:
597 case gnash::key::F:
598 toggleFullscreen();
599 break;
600 case gnash::key::h:
601 case gnash::key::H:
602 showUpdatedRegions(!showUpdatedRegions());
603 break;
604 case gnash::key::MINUS:
605 {
606 // Max interval allowed: 1 second (1FPS)
607 const size_t ni = std::min<size_t>(_interval + 2, 1000u);
608 setInterval(ni);
609 break;
610 }
611 case gnash::key::PLUS:
612 {
613 // Min interval allowed: 1/100 second (100FPS)
614 const size_t ni = std::max<size_t>(_interval - 2, 10u);
615 setInterval(ni);
616 break;
617 }
618 case gnash::key::EQUALS:
619 {
620 if (_stage) {
621 const float fps = _stage->getRootMovie().frameRate();
622 // Min interval allowed: 1/100 second (100FPS)
623 const size_t ni = 1000.0/fps;
624 setInterval(ni);
625 }
626 break;
627 }
628 default:
629 break;
630 }
631
632 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
633 if ( _keyboardMouseMovements ) {
634 int step = _keyboardMouseMovementsStep;
635 // x5 if SHIFT is pressed
636 if (modifier & gnash::key::GNASH_MOD_SHIFT) step *= 5;
637 switch (k) {
638 case gnash::key::UP:
639 {
640 int newx = _xpointer;
641 int newy = _ypointer-step;
642 if ( newy < 0 ) newy=0;
643 notifyMouseMove(newx, newy);
644 break;
645 }
646 case gnash::key::DOWN:
647 {
648 int newx = _xpointer;
649 int newy = _ypointer+step;
650 if ( newy >= _height ) newy = _height-1;
651 notifyMouseMove(newx, newy);
652 break;
653 }
654 case gnash::key::LEFT:
655 {
656 int newx = _xpointer-step;
657 int newy = _ypointer;
658 if ( newx < 0 ) newx = 0;
659 notifyMouseMove(newx, newy);
660 break;
661 }
662 case gnash::key::RIGHT:
663 {
664 const int newy = _ypointer;
665 int newx = _xpointer + step;
666 if ( newx >= _width ) newx = _width-1;
667 notifyMouseMove(newx, newy);
668 break;
669 }
670 default:
671 break;
672 }
673 }
674 #endif // ENABLE_KEYBOARD_MOUSE_MOVEMENTS
675 }
676 }
677
678 if (!_started) return;
679
680 if (_stopped) return;
681
682 if (_stage->keyEvent(k, pressed)) {
683 // any action triggered by the
684 // event required screen refresh
685 display(_stage);
686 }
687
688 }
689
690 bool
display(movie_root * m)691 Gui::display(movie_root* m)
692 {
693 assert(m == _stage); // why taking this arg ??
694
695 assert(_started);
696
697 InvalidatedRanges changed_ranges;
698 bool redraw_flag;
699
700 // Should the frame be rendered completely, even if it did not change?
701 #ifdef FORCE_REDRAW
702 redraw_flag = true;
703 #else
704 redraw_flag = _redraw_flag || want_redraw();
705 #endif
706
707 // reset class member if we do a redraw now
708 if (redraw_flag) _redraw_flag=false;
709
710 // Find out the surrounding frame of all characters which
711 // have been updated. This just checks what region of the stage has changed
712 // due to ActionScript code, the timeline or user events. The GUI can still
713 // choose to render a different part of the stage.
714 //
715 if (!redraw_flag) {
716
717 // choose snapping ranges factor
718 changed_ranges.setSnapFactor(1.3f);
719
720 // Use multi ranges only when GUI/Renderer supports it
721 // (Useless CPU overhead, otherwise)
722 changed_ranges.setSingleMode(!want_multiple_regions());
723
724 // scan through all sprites to compute invalidated bounds
725 m->add_invalidated_bounds(changed_ranges, false);
726
727 // grow ranges by a 2 pixels to avoid anti-aliasing issues
728 changed_ranges.growBy(40.0f / _xscale);
729
730 // optimize ranges
731 changed_ranges.combineRanges();
732 }
733
734 // TODO: Remove this and want_redraw to avoid confusion!?
735 if (redraw_flag) {
736 changed_ranges.setWorld();
737 }
738
739 // DEBUG ONLY:
740 // This is a good place to inspect the invalidated bounds state. Enable
741 // the following block (and parts of it) if you need to.
742 #if 0
743 {
744 // This may print a huge amount of information, but is useful to analyze
745 // the (visible) object structure of the movie and the flags of the
746 // characters. For example, a characters should have set the
747 // m_child_invalidated flag if at least one of it's childs has the
748 // invalidated flag set.
749 log_debug("DUMPING CHARACTER TREE");
750
751 InfoTree tr;
752 InfoTree::iterator top = tr.begin();
753 _stage->getMovieInfo(tr, top);
754
755 for (InfoTree::iterator i = tr.begin(), e = tr.end();
756 i != e; ++i) {
757 std::cout << std::string(tr.depth(i) * 2, ' ') << i->first << ": " <<
758 i->second << std::endl;
759 }
760
761
762 // less verbose, and often necessary: see the exact coordinates of the
763 // invalidated bounds (mainly to see if it's NULL or something else).
764 std::cout << "Calculated changed ranges: " << changed_ranges << "\n";
765 }
766 #endif
767
768 // Avoid drawing of stopped movies
769 if ( ! changed_ranges.isNull() ) { // use 'else'?
770 // Tell the GUI(!) that we only need to update this
771 // region. Note the GUI can do whatever it wants with
772 // this information. It may simply ignore the bounds
773 // (which will normally lead into a complete redraw),
774 // or it may extend or shrink the bounds as it likes. So,
775 // by calling set_invalidated_bounds we have no guarantee
776 // that only this part of the stage is rendered again.
777 #ifdef REGION_UPDATES_DEBUGGING_FULL_REDRAW
778 // redraw the full screen so that only the
779 // *new* invalidated region is visible
780 // (helps debugging)
781 InvalidatedRanges world_ranges;
782 world_ranges.setWorld();
783 setInvalidatedRegions(world_ranges);
784 #else
785 setInvalidatedRegions(changed_ranges);
786 #endif
787
788 // TODO: should this be called even if we're late ?
789 beforeRendering();
790
791 // Render the frame, if not late.
792 // It's up to the GUI/renderer combination
793 // to do any clipping, if desired.
794 m->display();
795
796 // show invalidated region using a red rectangle
797 // (Flash debug style)
798 IF_DEBUG_REGION_UPDATES (
799 if (_renderer.get() && !changed_ranges.isWorld()) {
800 for (size_t rno = 0; rno < changed_ranges.size(); rno++) {
801 const geometry::Range2d<int>& bounds =
802 changed_ranges.getRange(rno);
803
804 float xmin = bounds.getMinX();
805 float xmax = bounds.getMaxX();
806 float ymin = bounds.getMinY();
807 float ymax = bounds.getMaxY();
808
809 const std::vector<point> box = {
810 point(xmin, ymin),
811 point(xmax, ymin),
812 point(xmax, ymax),
813 point(xmin, ymax)
814 };
815
816 _renderer->draw_poly(box, rgba(0,0,0,0), rgba(255,0,0,255),
817 SWFMatrix(), false);
818
819 }
820 }
821 );
822
823 // show frame on screen
824 renderBuffer();
825 };
826
827 return true;
828 }
829
830 void
play()831 Gui::play()
832 {
833 if ( ! _stopped ) return;
834
835 _stopped = false;
836 if ( ! _started ) {
837 start();
838 } else {
839 assert (_stage);
840 #ifdef USE_SOUND
841 // @todo since we registered the sound handler, shouldn't we know
842 // already what it is ?!
843 sound::sound_handler* s = _stage->runResources().soundHandler();
844 if ( s ) s->unpause();
845 #endif // USE_SOUND
846 // log_debug("Starting virtual clock");
847 _virtualClock.resume();
848 }
849
850 playHook ();
851 }
852
853 void
stop()854 Gui::stop()
855 {
856 // _stage must be registered before this is called.
857 assert(_stage);
858
859 if ( _stopped ) return;
860 if ( isFullscreen() ) unsetFullscreen();
861
862 _stopped = true;
863
864 // @todo since we registered the sound handler, shouldn't we know
865 // already what it is ?!
866 sound::sound_handler* s = _stage->runResources().soundHandler();
867 if ( s ) s->pause();
868
869 // log_debug("Pausing virtual clock");
870 _virtualClock.pause();
871
872 stopHook();
873 }
874
875 void
pause()876 Gui::pause()
877 {
878 if (_stopped) {
879 play();
880 return;
881 }
882
883 // TODO: call stop() instead ?
884 // The only thing I see is that ::stop exits full-screen,
885 // but I'm not sure that's intended behaviour
886
887 // @todo since we registered the sound handler, shouldn't we know
888 // already what it is ?!
889 sound::sound_handler* s = _stage->runResources().soundHandler();
890 if (s) s->pause();
891 _stopped = true;
892
893 // log_debug("Pausing virtual clock");
894 _virtualClock.pause();
895
896 stopHook();
897 }
898
899 void
start()900 Gui::start()
901 {
902 assert ( ! _started );
903 if (_stopped) {
904 log_error(_("GUI is in stop mode, won't start application"));
905 return;
906 }
907
908 // Initializes the stage with a Movie and the passed flash vars.
909 _stage->init(_movieDef.get(), _flashVars);
910
911 bool background = true; // ??
912 _stage->set_background_alpha(background ? 1.0f : 0.05f);
913
914 // to properly update stageMatrix if scaling is given
915 resize_view(_width, _height);
916
917 // @todo since we registered the sound handler, shouldn't we know
918 // already what it is ?!
919 #ifdef USE_SOUND
920 sound::sound_handler* s = _stage->runResources().soundHandler();
921 if ( s ) {
922 if ( ! _audioDump.empty() ) {
923 s->setAudioDump(_audioDump);
924 }
925 s->unpause();
926 }
927 #endif // USE_SOUND
928 _started = true;
929
930 // log_debug("Starting virtual clock");
931 _virtualClock.resume();
932
933 }
934
935 bool
advanceMovie(bool doDisplay)936 Gui::advanceMovie(bool doDisplay)
937 {
938 if (isStopped()) {
939 return false;
940 }
941
942 if (!_started) {
943 start();
944 }
945
946 Display dis(*this, *_stage);
947 gnash::movie_root* m = _stage;
948
949 // Define REVIEW_ALL_FRAMES to have *all* frames
950 // consequentially displayed. Useful for debugging.
951 //#define REVIEW_ALL_FRAMES 1
952
953 #ifndef REVIEW_ALL_FRAMES
954 // Advance movie by one frame
955 const bool advanced = m->advance();
956 #else
957 const size_t cur_frame = m->getRootMovie().get_current_frame();
958 const size_t tot_frames = m->getRootMovie().get_frame_count();
959 const bool advanced = m->advance();
960
961 m->getRootMovie().ensureFrameLoaded(tot_frames);
962 m->goto_frame(cur_frame + 1);
963 m->getRootMovie().setPlayState(gnash::MovieClip::PLAYSTATE_PLAY);
964 // log_debug("Frame %d", m->getRootMovie().get_current_frame());
965 #endif
966
967 #ifdef GNASH_FPS_DEBUG
968 // will be a no-op if fps_timer_interval is zero
969 if (advanced) {
970 fpsCounterTick();
971 }
972 #endif
973
974 if (doDisplay && visible()) {
975 display(m);
976 }
977
978 if (!loops()) {
979 // can be 0 on malformed SWF
980 const size_t curframe = m->getRootMovie().get_current_frame();
981 const MovieClip& si = m->getRootMovie();
982 if (curframe + 1 >= si.get_frame_count()) {
983 quit();
984 }
985 }
986
987 if (_screenShotter.get() && _renderer.get()) {
988 _screenShotter->screenShot(*_renderer, _advances, doDisplay ? nullptr : &dis);
989 }
990
991 // Only increment advances and check for exit condition when we've
992 // really changed frame.
993 if (advanced) {
994 /// Quit if we've reached the frame advance limit.
995 if (_maxAdvances && (_advances > _maxAdvances)) {
996 quit();
997 }
998 ++_advances;
999 }
1000
1001 return advanced;
1002 }
1003
1004 void
setScreenShotter(std::unique_ptr<ScreenShotter> ss)1005 Gui::setScreenShotter(std::unique_ptr<ScreenShotter> ss)
1006 {
1007 _screenShotter.reset(ss.release());
1008 }
1009
1010 void
takeScreenShot()1011 Gui::takeScreenShot()
1012 {
1013 if (!_screenShotter.get()) {
1014 // If no ScreenShotter exists, none was requested at startup.
1015 // We use a default filename pattern.
1016 URL url(_runResources.streamProvider().baseURL());
1017 std::string::size_type p = url.path().rfind('/');
1018 const std::string& name = (p == std::string::npos) ? url.path() :
1019 url.path().substr(p + 1);
1020 const std::string& filename = "screenshot-" + name + "-%f";
1021 _screenShotter.reset(new ScreenShotter(filename, GNASH_FILETYPE_PNG));
1022 }
1023 assert (_screenShotter.get());
1024 _screenShotter->now();
1025 }
1026
1027 void
setCursor(gnash_cursor_type)1028 Gui::setCursor(gnash_cursor_type /*newcursor*/)
1029 {
1030 /* do nothing */
1031 }
1032
1033 bool
want_redraw()1034 Gui::want_redraw()
1035 {
1036 return false;
1037 }
1038
1039 void
setInvalidatedRegion(const SWFRect &)1040 Gui::setInvalidatedRegion(const SWFRect& /*bounds*/)
1041 {
1042 /* do nothing */
1043 }
1044
1045 void
setInvalidatedRegions(const InvalidatedRanges & ranges)1046 Gui::setInvalidatedRegions(const InvalidatedRanges& ranges)
1047 {
1048 // fallback to single regions
1049 geometry::Range2d<int> full = ranges.getFullArea();
1050
1051 SWFRect bounds;
1052
1053 if (full.isFinite()) {
1054 bounds = SWFRect(full.getMinX(), full.getMinY(),
1055 full.getMaxX(), full.getMaxY());
1056 }
1057 else if (full.isWorld()) {
1058 bounds.set_world();
1059 }
1060
1061 setInvalidatedRegion(bounds);
1062 }
1063
1064 #ifdef USE_SWFTREE
1065
1066 std::unique_ptr<movie_root::InfoTree>
getMovieInfo() const1067 Gui::getMovieInfo() const
1068 {
1069 std::unique_ptr<movie_root::InfoTree> tr;
1070
1071 if (!_stage) {
1072 return tr;
1073 }
1074
1075 tr.reset(new movie_root::InfoTree());
1076
1077 // Top nodes for the tree:
1078 // 1. VM information
1079 // 2. "Stage" information
1080 // 3. ...
1081
1082 movie_root::InfoTree::iterator topIter = tr->begin();
1083 movie_root::InfoTree::iterator firstLevelIter;
1084
1085 VM& vm = _stage->getVM();
1086
1087 std::ostringstream os;
1088
1089 //
1090 /// VM top level
1091 //
1092 os << "SWF " << vm.getSWFVersion();
1093 topIter = tr->insert(topIter, std::make_pair("Root SWF version", os.str()));
1094
1095 // This short-cut is to avoid a bug in movie_root's getMovieInfo,
1096 // which relies on the availability of a _rootMovie for doing
1097 // it's work, while we don't set it if we didn't start..
1098 //
1099 if (! _started) {
1100 topIter = tr->insert(topIter, std::make_pair("Stage properties",
1101 "not constructed yet"));
1102 return tr;
1103 }
1104
1105 movie_root& stage = vm.getRoot();
1106 stage.getMovieInfo(*tr, topIter);
1107
1108 //
1109 /// Mouse entities
1110 //
1111 topIter = tr->insert(topIter, std::make_pair("Mouse Entities", ""));
1112
1113 const DisplayObject* ch;
1114 ch = stage.getActiveEntityUnderPointer();
1115 if (ch) {
1116 std::stringstream ss;
1117 ss << ch->getTarget() << " (" + typeName(*ch)
1118 << " - depth:" << ch->get_depth()
1119 << " - useHandCursor:" << ch->allowHandCursor()
1120 << ")";
1121 firstLevelIter = tr->append_child(topIter,
1122 std::make_pair("Active entity under mouse pointer", ss.str()));
1123 }
1124
1125 ch = stage.getEntityUnderPointer();
1126 if (ch) {
1127 std::stringstream ss;
1128 ss << ch->getTarget() << " (" + typeName(*ch)
1129 << " - depth:" << ch->get_depth()
1130 << ")";
1131 firstLevelIter = tr->append_child(topIter,
1132 std::make_pair("Topmost entity under mouse pointer", ss.str()));
1133 }
1134
1135 ch = stage.getDraggingCharacter();
1136 if (ch) {
1137 std::stringstream ss;
1138 ss << ch->getTarget() << " (" + typeName(*ch)
1139 << " - depth:" << ch->get_depth() << ")";
1140 firstLevelIter = tr->append_child(topIter,
1141 std::make_pair("Dragging character: ", ss.str()));
1142 }
1143
1144 //
1145 /// GC row
1146 //
1147 topIter = tr->insert(topIter, std::make_pair("GC Statistics", ""));
1148 GC::CollectablesCount cc;
1149 _stage->gc().countCollectables(cc);
1150
1151 const std::string lbl = "GC managed ";
1152 for (auto& countinfo : cc) {
1153 const std::string& typ = countinfo.first;
1154 std::ostringstream ss;
1155 ss << countinfo.second;
1156 firstLevelIter = tr->append_child(topIter,
1157 std::make_pair(lbl + typ, ss.str()));
1158 }
1159
1160 tr->sort(firstLevelIter.begin(), firstLevelIter.end());
1161
1162 return tr;
1163 }
1164
1165 #endif
1166
1167 #ifdef GNASH_FPS_DEBUG
1168 void
fpsCounterTick()1169 Gui::fpsCounterTick()
1170 {
1171
1172 // increment this *before* the early return so that
1173 // frame count on exit is still valid
1174 ++fps_counter_total;
1175
1176 if (! fps_timer_interval) {
1177 return;
1178 }
1179
1180 std::uint64_t current_timer = clocktime::getTicks();
1181
1182 // TODO: keep fps_timer_interval in milliseconds to avoid the multiplication
1183 // at each fpsCounterTick call...
1184 std::uint64_t interval_ms = (std::uint64_t)(fps_timer_interval * 1000.0);
1185
1186 if (fps_counter_total==1) {
1187 fps_timer = current_timer;
1188 fps_start_timer = current_timer;
1189 }
1190
1191 ++fps_counter;
1192
1193 if (current_timer - fps_timer >= interval_ms) {
1194
1195 float secs = (current_timer - fps_timer) / 1000.0;
1196 float secs_total = (current_timer - fps_start_timer)/1000.0;
1197
1198 float rate = fps_counter/secs;
1199
1200 if (secs > 10000000) {
1201 // the timers are unsigned, so when the clock runs "backwards" it leads
1202 // to a very high difference value. In theory, this should never happen
1203 // with ticks, but it does on my machine (which may have a hw problem?).
1204 std::cerr << "Time glitch detected, need to restart FPS counters, sorry..." << std::endl;
1205
1206 fps_timer = current_timer;
1207 fps_start_timer = current_timer;
1208 fps_counter_total = 0;
1209 fps_counter = 0;
1210 return;
1211 }
1212
1213 // first FPS message?
1214 if (fps_timer == fps_start_timer) { // they're ints, so we can compare
1215 fps_rate_min = rate;
1216 fps_rate_max = rate;
1217 } else {
1218 fps_rate_min = std::min<float>(fps_rate_min, rate);
1219 fps_rate_max = std::max<float>(fps_rate_max, rate);
1220 }
1221
1222 float avg = fps_counter_total / secs_total;
1223
1224 //log_debug("Effective frame rate: %0.2f fps", (float)(fps_counter/secs));
1225 std::cerr << boost::format("Effective frame rate: %0.2f fps "
1226 "(min %0.2f, avg %0.2f, max %0.2f, "
1227 "%u frames in %0.1f secs total, "
1228 "dropped %u)") % rate %
1229 fps_rate_min % avg % fps_rate_max %
1230 fps_counter_total % secs_total %
1231 frames_dropped << std::endl;
1232
1233 fps_counter = 0;
1234 fps_timer = current_timer;
1235
1236 }
1237
1238 }
1239 #endif
1240
1241 void
addFlashVars(Gui::VariableMap & from)1242 Gui::addFlashVars(Gui::VariableMap& from)
1243 {
1244 for (auto& variable : from) {
1245 _flashVars[variable.first] = variable.second;
1246 }
1247 }
1248
1249 void
setMovieDefinition(movie_definition * md)1250 Gui::setMovieDefinition(movie_definition* md)
1251 {
1252 assert(!_movieDef);
1253 _movieDef = md;
1254 }
1255
1256 void
setStage(movie_root * stage)1257 Gui::setStage(movie_root* stage)
1258 {
1259 assert(stage);
1260 assert(!_stage);
1261 _stage = stage;
1262 }
1263
1264 bool
yesno(const std::string & question)1265 Gui::yesno(const std::string& question)
1266 {
1267 log_error(_("This GUI didn't override 'yesno', assuming 'yes' answer to "
1268 "question: %s"), question);
1269 return true;
1270 }
1271
1272 void
setQuality(Quality q)1273 Gui::setQuality(Quality q)
1274 {
1275 if (!_stage) {
1276 log_error(_("Gui::setQuality called before a movie_root was available"));
1277 return;
1278 }
1279 _stage->setQuality(q);
1280 }
1281
1282 Quality
getQuality() const1283 Gui::getQuality() const
1284 {
1285 if (!_stage) {
1286 log_error(_("Gui::getQuality called before a movie_root was available"));
1287 // just a guess..
1288 return QUALITY_HIGH;
1289 }
1290 return _stage->getQuality();
1291 }
1292
1293 }
1294
1295 // local Variables:
1296 // mode: C++
1297 // indent-tabs-mode: nil
1298 // End:
1299