1 // Aseprite
2 // Copyright (C) 2001-2018  David Capello
3 //
4 // This program is distributed under the terms of
5 // the End-User License Agreement for Aseprite.
6 
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10 
11 #include "app/ui/preview_editor.h"
12 
13 #include "app/app.h"
14 #include "app/doc.h"
15 #include "app/ini_file.h"
16 #include "app/loop_tag.h"
17 #include "app/modules/editors.h"
18 #include "app/modules/gui.h"
19 #include "app/pref/preferences.h"
20 #include "app/ui/editor/editor.h"
21 #include "app/ui/editor/editor_customization_delegate.h"
22 #include "app/ui/editor/editor_view.h"
23 #include "app/ui/editor/navigate_state.h"
24 #include "app/ui/skin/skin_theme.h"
25 #include "app/ui/status_bar.h"
26 #include "app/ui/toolbar.h"
27 #include "app/ui_context.h"
28 #include "base/bind.h"
29 #include "doc/sprite.h"
30 #include "gfx/rect.h"
31 #include "ui/base.h"
32 #include "ui/button.h"
33 #include "ui/close_event.h"
34 #include "ui/message.h"
35 #include "ui/system.h"
36 
37 #include "doc/frame_tag.h"
38 
39 namespace app {
40 
41 using namespace app::skin;
42 using namespace ui;
43 
44 class MiniCenterButton : public CheckBox {
45 public:
MiniCenterButton()46   MiniCenterButton() : CheckBox("") {
47     setDecorative(true);
48     setSelected(true);
49     initTheme();
50   }
51 
52 protected:
onInitTheme(ui::InitThemeEvent & ev)53   void onInitTheme(ui::InitThemeEvent& ev) override {
54     CheckBox::onInitTheme(ev);
55     setStyle(SkinTheme::instance()->styles.windowCenterButton());
56   }
57 
onSetDecorativeWidgetBounds()58   void onSetDecorativeWidgetBounds() override {
59     SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
60     Widget* window = parent();
61     gfx::Rect rect(0, 0, 0, 0);
62     gfx::Size centerSize = this->sizeHint();
63     gfx::Size playSize = theme->calcSizeHint(this, theme->styles.windowPlayButton());
64     gfx::Size closeSize = theme->calcSizeHint(this, theme->styles.windowCloseButton());
65 
66     rect.w = centerSize.w;
67     rect.h = centerSize.h;
68     rect.offset(window->bounds().x2()
69                 - theme->styles.windowCloseButton()->margin().width() - closeSize.w
70                 - theme->styles.windowPlayButton()->margin().width() - playSize.w
71                 - style()->margin().right() - centerSize.w,
72                 window->bounds().y + style()->margin().top());
73 
74     setBounds(rect);
75   }
76 
onProcessMessage(Message * msg)77   bool onProcessMessage(Message* msg) override {
78     switch (msg->type()) {
79 
80       case kSetCursorMessage:
81         ui::set_mouse_cursor(kArrowCursor);
82         return true;
83     }
84 
85     return CheckBox::onProcessMessage(msg);
86   }
87 };
88 
89 class MiniPlayButton : public Button {
90 public:
MiniPlayButton()91   MiniPlayButton() : Button(""), m_isPlaying(false) {
92     enableFlags(CTRL_RIGHT_CLICK);
93     setDecorative(true);
94     initTheme();
95   }
96 
isPlaying() const97   bool isPlaying() const { return m_isPlaying; }
98 
setPlaying(bool state)99   void setPlaying(bool state) {
100     m_isPlaying = state;
101     setupIcons();
102   }
103 
104   obs::signal<void()> Popup;
105 
106 private:
onInitTheme(ui::InitThemeEvent & ev)107   void onInitTheme(ui::InitThemeEvent& ev) override {
108     Button::onInitTheme(ev);
109     setupIcons();
110   }
111 
onClick(Event & ev)112   void onClick(Event& ev) override {
113     m_isPlaying = !m_isPlaying;
114     setupIcons();
115 
116     Button::onClick(ev);
117   }
118 
onSetDecorativeWidgetBounds()119   void onSetDecorativeWidgetBounds() override {
120     SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
121     Widget* window = parent();
122     gfx::Rect rect(0, 0, 0, 0);
123     gfx::Size playSize = this->sizeHint();
124     gfx::Size closeSize = theme->calcSizeHint(this, theme->styles.windowCloseButton());
125     gfx::Border margin(0, 0, 0, 0);
126 
127     rect.w = playSize.w;
128     rect.h = playSize.h;
129     rect.offset(window->bounds().x2()
130                 - theme->styles.windowCloseButton()->margin().width() - closeSize.w
131                 - style()->margin().right() - playSize.w,
132                 window->bounds().y + style()->margin().top());
133 
134     setBounds(rect);
135   }
136 
onProcessMessage(Message * msg)137   bool onProcessMessage(Message* msg) override {
138     switch (msg->type()) {
139 
140       case kSetCursorMessage:
141         ui::set_mouse_cursor(kArrowCursor);
142         return true;
143 
144       case kMouseUpMessage: {
145         MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
146         if (mouseMsg->right()) {
147           if (hasCapture()) {
148             releaseMouse();
149             Popup();
150 
151             setSelected(false);
152             return true;
153           }
154         }
155         break;
156       }
157     }
158 
159     return Button::onProcessMessage(msg);
160   }
161 
setupIcons()162   void setupIcons() {
163     SkinTheme* theme = SkinTheme::instance();
164     if (m_isPlaying)
165       setStyle(theme->styles.windowStopButton());
166     else
167       setStyle(theme->styles.windowPlayButton());
168   }
169 
170   bool m_isPlaying;
171 };
172 
PreviewEditorWindow()173 PreviewEditorWindow::PreviewEditorWindow()
174   : Window(WithTitleBar, "Preview")
175   , m_docView(NULL)
176   , m_centerButton(new MiniCenterButton())
177   , m_playButton(new MiniPlayButton())
178   , m_refFrame(0)
179   , m_aniSpeed(1.0)
180   , m_relatedEditor(nullptr)
181 {
182   setAutoRemap(false);
183   setWantFocus(false);
184 
185   m_isEnabled = get_config_bool("MiniEditor", "Enabled", true);
186 
187   m_centerButton->Click.connect(base::Bind<void>(&PreviewEditorWindow::onCenterClicked, this));
188   m_playButton->Click.connect(base::Bind<void>(&PreviewEditorWindow::onPlayClicked, this));
189   m_playButton->Popup.connect(base::Bind<void>(&PreviewEditorWindow::onPopupSpeed, this));
190 
191   addChild(m_centerButton);
192   addChild(m_playButton);
193 
194   initTheme();
195 }
196 
~PreviewEditorWindow()197 PreviewEditorWindow::~PreviewEditorWindow()
198 {
199   set_config_bool("MiniEditor", "Enabled", m_isEnabled);
200 }
201 
setPreviewEnabled(bool state)202 void PreviewEditorWindow::setPreviewEnabled(bool state)
203 {
204   m_isEnabled = state;
205 
206   updateUsingEditor(current_editor);
207 }
208 
pressPlayButton()209 void PreviewEditorWindow::pressPlayButton()
210 {
211   m_playButton->setSelected(
212     !m_playButton->isSelected());
213   onPlayClicked();
214 }
215 
onProcessMessage(ui::Message * msg)216 bool PreviewEditorWindow::onProcessMessage(ui::Message* msg)
217 {
218   switch (msg->type()) {
219 
220     case kOpenMessage:
221       {
222         SkinTheme* theme = SkinTheme::instance();
223 
224         // Default bounds
225         int width = ui::display_w()/4;
226         int height = ui::display_h()/4;
227         int extra = 2*theme->dimensions.miniScrollbarSize();
228         setBounds(
229           gfx::Rect(
230             ui::display_w() - width - ToolBar::instance()->bounds().w - extra,
231             ui::display_h() - height - StatusBar::instance()->bounds().h - extra,
232             width, height));
233 
234         load_window_pos(this, "MiniEditor");
235         invalidate();
236       }
237       break;
238 
239     case kCloseMessage:
240       save_window_pos(this, "MiniEditor");
241       break;
242 
243   }
244 
245   return Window::onProcessMessage(msg);
246 }
247 
onInitTheme(ui::InitThemeEvent & ev)248 void PreviewEditorWindow::onInitTheme(ui::InitThemeEvent& ev)
249 {
250   Window::onInitTheme(ev);
251   setChildSpacing(0);
252 }
253 
onClose(ui::CloseEvent & ev)254 void PreviewEditorWindow::onClose(ui::CloseEvent& ev)
255 {
256   ButtonBase* closeButton = dynamic_cast<ButtonBase*>(ev.getSource());
257   if (closeButton &&
258       closeButton->type() == kWindowCloseButtonWidget) {
259     // Here we don't use "setPreviewEnabled" to change the state of
260     // "m_isEnabled" because we're coming from a close event of the
261     // window.
262     m_isEnabled = false;
263 
264     // Redraw the tool bar because it shows the mini editor enabled
265     // state. TODO abstract this event
266     ToolBar::instance()->invalidate();
267 
268     destroyDocView();
269   }
270 }
271 
onWindowResize()272 void PreviewEditorWindow::onWindowResize()
273 {
274   Window::onWindowResize();
275 
276   DocView* view = UIContext::instance()->activeView();
277   if (view)
278     updateUsingEditor(view->editor());
279 }
280 
hasDocument() const281 bool PreviewEditorWindow::hasDocument() const
282 {
283   return (m_docView && m_docView->document() != nullptr);
284 }
285 
docPref()286 DocumentPreferences& PreviewEditorWindow::docPref()
287 {
288   Doc* doc = (m_docView ? m_docView->document(): nullptr);
289   return Preferences::instance().document(doc);
290 }
291 
onCenterClicked()292 void PreviewEditorWindow::onCenterClicked()
293 {
294   if (!m_relatedEditor || !hasDocument())
295     return;
296 
297   bool autoScroll = m_centerButton->isSelected();
298   docPref().preview.autoScroll(autoScroll);
299   if (autoScroll)
300     updateUsingEditor(m_relatedEditor);
301 }
302 
onPlayClicked()303 void PreviewEditorWindow::onPlayClicked()
304 {
305   Editor* miniEditor = (m_docView ? m_docView->editor(): nullptr);
306   if (!miniEditor || !miniEditor->document())
307     return;
308 
309   if (m_playButton->isPlaying()) {
310     m_refFrame = miniEditor->frame();
311     miniEditor->play(Preferences::instance().preview.playOnce(),
312                      Preferences::instance().preview.playAll());
313   }
314   else {
315     miniEditor->stop();
316     if (m_relatedEditor)
317       miniEditor->setFrame(m_relatedEditor->frame());
318   }
319 }
320 
onPopupSpeed()321 void PreviewEditorWindow::onPopupSpeed()
322 {
323   Editor* miniEditor = (m_docView ? m_docView->editor(): nullptr);
324   if (!miniEditor || !miniEditor->document())
325     return;
326 
327   auto& pref = Preferences::instance();
328 
329   miniEditor->showAnimationSpeedMultiplierPopup(
330     pref.preview.playOnce,
331     pref.preview.playAll,
332     false);
333   m_aniSpeed = miniEditor->getAnimationSpeedMultiplier();
334 }
335 
previewEditor() const336 Editor* PreviewEditorWindow::previewEditor() const
337 {
338   return (m_docView ? m_docView->editor(): nullptr);
339 }
340 
updateUsingEditor(Editor * editor)341 void PreviewEditorWindow::updateUsingEditor(Editor* editor)
342 {
343   if (!m_isEnabled || !editor) {
344     hideWindow();
345     m_relatedEditor = nullptr;
346     return;
347   }
348 
349   if (!editor->isActive())
350     return;
351 
352   m_relatedEditor = editor;
353 
354   Doc* document = editor->document();
355   Editor* miniEditor = (m_docView ? m_docView->editor(): nullptr);
356 
357   if (!isVisible())
358     openWindow();
359 
360   // Document preferences used to store the preferred zoom/scroll point
361   auto& docPref = Preferences::instance().document(document);
362   bool autoScroll = docPref.preview.autoScroll();
363 
364   // Set the same location as in the given editor.
365   if (!miniEditor || miniEditor->document() != document) {
366     destroyDocView();
367 
368     m_docView = new DocView(document, DocView::Preview, this);
369     addChild(m_docView);
370 
371     miniEditor = m_docView->editor();
372     miniEditor->setZoom(render::Zoom::fromScale(docPref.preview.zoom()));
373     miniEditor->setLayer(editor->layer());
374     miniEditor->setFrame(editor->frame());
375     miniEditor->setState(EditorStatePtr(new NavigateState));
376     miniEditor->setAnimationSpeedMultiplier(m_aniSpeed);
377     miniEditor->add_observer(this);
378     layout();
379 
380     if (!autoScroll)
381       miniEditor->setEditorScroll(docPref.preview.scroll());
382   }
383 
384   m_centerButton->setSelected(autoScroll);
385   if (autoScroll) {
386     gfx::Point centerPoint = editor->getVisibleSpriteBounds().center();
387     miniEditor->centerInSpritePoint(centerPoint);
388 
389     saveScrollPref();
390   }
391 
392   if (!m_playButton->isPlaying()) {
393     miniEditor->stop();
394     miniEditor->setLayer(editor->layer());
395     miniEditor->setFrame(editor->frame());
396   }
397   else {
398     if (miniEditor->isPlaying()) {
399       doc::FrameTag* tag = editor
400         ->getCustomizationDelegate()
401         ->getFrameTagProvider()
402         ->getFrameTagByFrame(editor->frame(), true);
403 
404       doc::FrameTag* playingTag = editor
405         ->getCustomizationDelegate()
406         ->getFrameTagProvider()
407         ->getFrameTagByFrame(m_refFrame, true);
408 
409       if (tag == playingTag)
410         return;
411 
412       miniEditor->stop();
413     }
414 
415     if (!miniEditor->isPlaying())
416       miniEditor->setFrame(m_refFrame = editor->frame());
417 
418     miniEditor->play(Preferences::instance().preview.playOnce(),
419                      Preferences::instance().preview.playAll());
420   }
421 }
422 
uncheckCenterButton()423 void PreviewEditorWindow::uncheckCenterButton()
424 {
425   if (m_centerButton->isSelected()) {
426     m_centerButton->setSelected(false);
427     onCenterClicked();
428   }
429 }
430 
onStateChanged(Editor * editor)431 void PreviewEditorWindow::onStateChanged(Editor* editor)
432 {
433   // Sync editor playing state with MiniPlayButton state
434   if (m_playButton->isPlaying() != editor->isPlaying())
435     m_playButton->setPlaying(editor->isPlaying());
436 }
437 
onScrollChanged(Editor * miniEditor)438 void PreviewEditorWindow::onScrollChanged(Editor* miniEditor)
439 {
440   if (miniEditor->hasCapture()) {
441     saveScrollPref();
442     uncheckCenterButton();
443   }
444 }
445 
onZoomChanged(Editor * miniEditor)446 void PreviewEditorWindow::onZoomChanged(Editor* miniEditor)
447 {
448   saveScrollPref();
449 }
450 
saveScrollPref()451 void PreviewEditorWindow::saveScrollPref()
452 {
453   ASSERT(m_docView);
454   if (!m_docView)
455     return;
456 
457   Editor* miniEditor = m_docView->editor();
458   ASSERT(miniEditor);
459 
460   docPref().preview.scroll(View::getView(miniEditor)->viewScroll());
461   docPref().preview.zoom(miniEditor->zoom().scale());
462 }
463 
onScrollOtherEditor(Editor * editor)464 void PreviewEditorWindow::onScrollOtherEditor(Editor* editor)
465 {
466   updateUsingEditor(editor);
467 }
468 
onDisposeOtherEditor(Editor * editor)469 void PreviewEditorWindow::onDisposeOtherEditor(Editor* editor)
470 {
471   if (m_relatedEditor == editor)
472     updateUsingEditor(nullptr);
473 }
474 
onPreviewOtherEditor(Editor * editor)475 void PreviewEditorWindow::onPreviewOtherEditor(Editor* editor)
476 {
477   updateUsingEditor(editor);
478 }
479 
hideWindow()480 void PreviewEditorWindow::hideWindow()
481 {
482   destroyDocView();
483   if (isVisible())
484     closeWindow(NULL);
485 }
486 
destroyDocView()487 void PreviewEditorWindow::destroyDocView()
488 {
489   if (m_docView) {
490     m_docView->editor()->remove_observer(this);
491 
492     delete m_docView;
493     m_docView = nullptr;
494   }
495 }
496 
497 } // namespace app
498