1// Aseprite
2// Copyright (C) 2018-2022 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "ui/ui.h"
13
14#include "app/commands/command.h"
15#include "app/commands/commands.h"
16#include "app/context.h"
17#include "app/modules/editors.h"
18#include "app/modules/gfx.h"
19#include "app/pref/preferences.h"
20#include "app/ui/editor/editor.h"
21#include "app/ui/editor/editor_render.h"
22#include "app/ui/keyboard_shortcuts.h"
23#include "app/ui/status_bar.h"
24#include "app/util/conversion_to_surface.h"
25#include "doc/image.h"
26#include "doc/palette.h"
27#include "doc/primitives.h"
28#include "doc/sprite.h"
29#include "os/surface.h"
30#include "os/system.h"
31
32#include <cstring>
33
34#define PREVIEW_TILED 1
35#define PREVIEW_FIT_ON_SCREEN 2
36
37namespace app {
38
39using namespace ui;
40using namespace doc;
41using namespace filters;
42
43class PreviewWindow : public Window {
44public:
45 PreviewWindow(Context* context, Editor* editor)
46 : Window(DesktopWindow)
47 , m_context(context)
48 , m_editor(editor)
49 , m_doc(editor->document())
50 , m_sprite(editor->sprite())
51 , m_pal(m_sprite->palette(editor->frame()))
52 , m_proj(editor->projection())
53 , m_index_bg_color(-1)
54 , m_doublebuf(Image::create(
55 IMAGE_RGB,
56 editor->display()->size().w,
57 editor->display()->size().h))
58 , m_doublesur(os::instance()->makeRgbaSurface(
59 editor->display()->size().w,
60 editor->display()->size().h)) {
61 // Do not use DocWriter (do not lock the document) because we
62 // will call other sub-commands (e.g. previous frame, next frame,
63 // etc.).
64 View* view = View::getView(editor);
65 DocumentPreferences& docPref = Preferences::instance().document(m_doc);
66 m_tiled = (filters::TiledMode)docPref.tiled.mode();
67
68 // Free mouse
69 editor->manager()->freeMouse();
70
71 // Clear extras (e.g. pen preview)
72 m_doc->setExtraCel(ExtraCelRef(nullptr));
73
74 gfx::Rect vp = view->viewportBounds();
75 gfx::Point scroll = view->viewScroll();
76
77 m_oldMousePos = mousePosInDisplay();
78 m_pos.x = -scroll.x + vp.x + editor->padding().x;
79 m_pos.y = -scroll.y + vp.y + editor->padding().y;
80
81 setFocusStop(true);
82 captureMouse();
83 }
84
85protected:
86 virtual bool onProcessMessage(Message* msg) override {
87 switch (msg->type()) {
88
89 case kCloseMessage:
90 releaseMouse();
91 break;
92
93 case kMouseMoveMessage: {
94 MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
95 gfx::Point mousePos = mouseMsg->position();
96
97 gfx::Rect bounds = this->bounds();
98 gfx::Border border;
99 if (bounds.w > 64*guiscale()) {
100 border.left(32*guiscale());
101 border.right(32*guiscale());
102 }
103 if (bounds.h > 64*guiscale()) {
104 border.top(32*guiscale());
105 border.bottom(32*guiscale());
106 }
107
108 m_delta += mousePos - m_oldMousePos;
109 m_oldMousePos = mousePos;
110
111 invalidate();
112 break;
113 }
114
115 case kMouseUpMessage: {
116 closeWindow(this);
117 break;
118 }
119
120 case kKeyDownMessage: {
121 KeyMessage* keyMsg = static_cast<KeyMessage*>(msg);
122 Command* command = NULL;
123 Params params;
124 KeyboardShortcuts::instance()
125 ->getCommandFromKeyMessage(msg, &command, &params);
126
127 // Change frame
128 if (command != NULL &&
129 (command->id() == CommandId::GotoFirstFrame() ||
130 command->id() == CommandId::GotoPreviousFrame() ||
131 command->id() == CommandId::GotoNextFrame() ||
132 command->id() == CommandId::GotoLastFrame())) {
133 m_context->executeCommand(command, params);
134 invalidate();
135 m_render.reset(nullptr); // Re-render
136 }
137#if 0
138 // Play the animation
139 else if (command != NULL &&
140 std::strcmp(command->short_name(), CommandId::PlayAnimation()) == 0) {
141 // TODO
142 }
143#endif
144 // Change background color
145 else if (keyMsg->scancode() == kKeyPlusPad ||
146 keyMsg->unicodeChar() == '+') {
147 if (m_index_bg_color == -1 ||
148 m_index_bg_color < m_pal->size()-1) {
149 ++m_index_bg_color;
150
151 invalidate();
152 }
153 }
154 else if (keyMsg->scancode() == kKeyMinusPad ||
155 keyMsg->unicodeChar() == '-') {
156 if (m_index_bg_color >= 0) {
157 --m_index_bg_color; // can be -1 which is the checkered background
158
159 invalidate();
160 }
161 }
162 else {
163 closeWindow(this);
164 }
165
166 return true;
167 }
168
169 case kSetCursorMessage:
170 ui::set_mouse_cursor(kNoCursor);
171 return true;
172 }
173
174 return Window::onProcessMessage(msg);
175 }
176
177 virtual void onPaint(PaintEvent& ev) override {
178 gfx::Size displaySize = display()->size();
179 Graphics* g = ev.graphics();
180 EditorRender& render = m_editor->renderEngine();
181 render.setRefLayersVisiblity(false);
182 render.setProjection(render::Projection());
183 render.disableOnionskin();
184 render.setTransparentBackground();
185
186 // Render sprite and leave the result in 'm_render' variable
187 if (m_render == nullptr) {
188 ImageBufferPtr buf = render.getRenderImageBuffer();
189 m_render.reset(Image::create(IMAGE_RGB,
190 m_sprite->width(), m_sprite->height(), buf));
191
192 render.renderSprite(
193 m_render.get(), m_sprite, m_editor->frame());
194 }
195
196 int x, y, w, h, u, v;
197 x = m_pos.x + m_proj.applyX(m_proj.removeX(m_delta.x));
198 y = m_pos.y + m_proj.applyY(m_proj.removeY(m_delta.y));
199 w = m_proj.applyX(m_sprite->width());
200 h = m_proj.applyY(m_sprite->height());
201
202 if (int(m_tiled) & int(TiledMode::X_AXIS)) x = SGN(x) * (ABS(x)%w);
203 if (int(m_tiled) & int(TiledMode::Y_AXIS)) y = SGN(y) * (ABS(y)%h);
204
205 render.setProjection(m_proj);
206 if (m_index_bg_color == -1) {
207 render.setupBackground(m_doc, m_doublebuf->pixelFormat());
208 render.renderCheckeredBackground(
209 m_doublebuf.get(),
210 gfx::Clip(0, 0, -m_pos.x, -m_pos.y,
211 m_doublebuf->width(), m_doublebuf->height()));
212 }
213 else {
214 doc::clear_image(m_doublebuf.get(), m_pal->getEntry(m_index_bg_color));
215 }
216
217 switch (m_tiled) {
218 case TiledMode::NONE:
219 render.renderImage(m_doublebuf.get(), m_render.get(), m_pal, x, y,
220 255, doc::BlendMode::NORMAL);
221 break;
222 case TiledMode::X_AXIS:
223 for (u=x-w; u<displaySize.w+w; u+=w)
224 render.renderImage(m_doublebuf.get(), m_render.get(), m_pal, u, y,
225 255, doc::BlendMode::NORMAL);
226 break;
227 case TiledMode::Y_AXIS:
228 for (v=y-h; v<displaySize.h+h; v+=h)
229 render.renderImage(m_doublebuf.get(), m_render.get(), m_pal, x, v,
230 255, doc::BlendMode::NORMAL);
231 break;
232 case TiledMode::BOTH:
233 for (v=y-h; v<displaySize.h+h; v+=h)
234 for (u=x-w; u<displaySize.w+w; u+=w)
235 render.renderImage(m_doublebuf.get(), m_render.get(), m_pal, u, v,
236 255, doc::BlendMode::NORMAL);
237 break;
238 }
239
240 convert_image_to_surface(m_doublebuf.get(), m_pal,
241 m_doublesur.get(), 0, 0, 0, 0, m_doublebuf->width(), m_doublebuf->height());
242 g->blit(m_doublesur.get(), 0, 0, 0, 0, m_doublesur->width(), m_doublesur->height());
243 }
244
245private:
246 Context* m_context;
247 Editor* m_editor;
248 Doc* m_doc;
249 Sprite* m_sprite;
250 const Palette* m_pal;
251 gfx::Point m_pos;
252 gfx::Point m_oldMousePos;
253 gfx::Point m_delta;
254 render::Projection m_proj;
255 int m_index_bg_color;
256 std::unique_ptr<Image> m_render;
257 std::unique_ptr<Image> m_doublebuf;
258 os::SurfaceRef m_doublesur;
259 filters::TiledMode m_tiled;
260};
261
262class FullscreenPreviewCommand : public Command {
263public:
264 FullscreenPreviewCommand();
265
266protected:
267 bool onEnabled(Context* context) override;
268 void onExecute(Context* context) override;
269};
270
271FullscreenPreviewCommand::FullscreenPreviewCommand()
272 : Command(CommandId::FullscreenPreview(), CmdUIOnlyFlag)
273{
274}
275
276bool FullscreenPreviewCommand::onEnabled(Context* context)
277{
278 return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
279 ContextFlags::HasActiveSprite);
280}
281
282// Shows the sprite using the complete screen.
283void FullscreenPreviewCommand::onExecute(Context* context)
284{
285 Editor* editor = current_editor;
286
287 // Cancel operation if current editor does not have a sprite
288 if (!editor || !editor->sprite())
289 return;
290
291 PreviewWindow window(context, editor);
292 window.openWindowInForeground();
293
294 // Check that the full screen invalidation code is working
295 // correctly. This check is just in case that some regression is
296 // introduced in ui::Manager() that doesn't handle correctly the
297 // invalidation of the manager when it's fully covered by the closed
298 // window (desktop windows, like PreviewWindow, match this case).
299 ASSERT(editor->manager()->hasFlags(DIRTY));
300}
301
302Command* CommandFactory::createFullscreenPreviewCommand()
303{
304 return new FullscreenPreviewCommand;
305}
306
307} // namespace app
308