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