2015-02-12 12:16:25 -03:00
|
|
|
// Aseprite
|
2021-02-02 15:59:28 -03:00
|
|
|
// Copyright (C) 2018-2021 Igara Studio S.A.
|
2018-03-06 19:21:09 -03:00
|
|
|
// Copyright (C) 2001-2018 David Capello
|
2015-02-12 12:16:25 -03:00
|
|
|
//
|
2016-08-26 17:02:58 -03:00
|
|
|
// This program is distributed under the terms of
|
|
|
|
// the End-User License Agreement for Aseprite.
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2013-08-05 21:20:19 -03:00
|
|
|
#ifdef HAVE_CONFIG_H
|
2008-04-28 16:32:45 +00:00
|
|
|
#include "config.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#endif
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "app/ui/editor/editor.h"
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "app/app.h"
|
2010-09-26 15:59:32 -03:00
|
|
|
#include "app/color.h"
|
2016-03-17 17:16:35 -03:00
|
|
|
#include "app/color_picker.h"
|
2010-08-25 00:26:37 -03:00
|
|
|
#include "app/color_utils.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "app/commands/commands.h"
|
|
|
|
#include "app/commands/params.h"
|
2017-12-06 14:44:40 -03:00
|
|
|
#include "app/commands/quick_command.h"
|
2014-03-12 19:35:26 -03:00
|
|
|
#include "app/console.h"
|
2018-07-07 02:47:42 -03:00
|
|
|
#include "app/doc_event.h"
|
2017-12-06 14:44:40 -03:00
|
|
|
#include "app/i18n/strings.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "app/ini_file.h"
|
2015-04-27 10:08:48 -03:00
|
|
|
#include "app/modules/editors.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "app/modules/gfx.h"
|
|
|
|
#include "app/modules/gui.h"
|
|
|
|
#include "app/modules/palettes.h"
|
2015-02-15 09:48:38 -03:00
|
|
|
#include "app/pref/preferences.h"
|
2020-10-02 19:03:53 -03:00
|
|
|
#include "app/snap_to_grid.h"
|
2016-04-25 15:20:53 -03:00
|
|
|
#include "app/tools/active_tool.h"
|
2017-06-16 16:28:48 -03:00
|
|
|
#include "app/tools/controller.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "app/tools/ink.h"
|
|
|
|
#include "app/tools/tool.h"
|
|
|
|
#include "app/tools/tool_box.h"
|
|
|
|
#include "app/ui/color_bar.h"
|
2014-01-28 23:56:44 -03:00
|
|
|
#include "app/ui/context_bar.h"
|
2016-04-04 18:46:48 -03:00
|
|
|
#include "app/ui/editor/drawing_state.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "app/ui/editor/editor_customization_delegate.h"
|
|
|
|
#include "app/ui/editor/editor_decorator.h"
|
2018-06-19 23:25:31 -03:00
|
|
|
#include "app/ui/editor/editor_render.h"
|
2016-04-25 15:20:53 -03:00
|
|
|
#include "app/ui/editor/glue.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "app/ui/editor/moving_pixels_state.h"
|
|
|
|
#include "app/ui/editor/pixels_movement.h"
|
2015-04-06 16:12:28 -03:00
|
|
|
#include "app/ui/editor/play_state.h"
|
2017-01-06 17:44:08 -03:00
|
|
|
#include "app/ui/editor/scrolling_state.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "app/ui/editor/standby_state.h"
|
2017-01-06 17:44:08 -03:00
|
|
|
#include "app/ui/editor/zooming_state.h"
|
2014-01-28 23:56:44 -03:00
|
|
|
#include "app/ui/main_window.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "app/ui/skin/skin_theme.h"
|
|
|
|
#include "app/ui/status_bar.h"
|
2019-09-05 15:03:13 -03:00
|
|
|
#include "app/ui/timeline/timeline.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "app/ui/toolbar.h"
|
|
|
|
#include "app/ui_context.h"
|
2020-06-09 19:56:25 -03:00
|
|
|
#include "app/util/conversion_to_surface.h"
|
2020-04-07 10:40:56 -03:00
|
|
|
#include "app/util/layer_utils.h"
|
2016-10-11 16:41:50 -03:00
|
|
|
#include "base/chrono.h"
|
2019-04-28 21:27:05 -03:00
|
|
|
#include "base/clamp.h"
|
2014-10-20 22:21:31 -03:00
|
|
|
#include "doc/doc.h"
|
2015-06-11 17:44:27 -03:00
|
|
|
#include "doc/mask_boundaries.h"
|
2017-03-06 19:27:43 -03:00
|
|
|
#include "doc/slice.h"
|
2020-07-21 16:01:06 -03:00
|
|
|
#include "fmt/format.h"
|
2018-10-18 15:29:16 -03:00
|
|
|
#include "os/color_space.h"
|
2018-08-09 12:58:43 -03:00
|
|
|
#include "os/surface.h"
|
|
|
|
#include "os/system.h"
|
2019-11-08 13:27:00 -03:00
|
|
|
#include "render/rasterize.h"
|
2013-08-05 21:20:19 -03:00
|
|
|
#include "ui/ui.h"
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2018-06-22 15:41:22 -03:00
|
|
|
#include <algorithm>
|
2015-08-13 21:47:30 -03:00
|
|
|
#include <cmath>
|
2014-06-26 22:14:39 -03:00
|
|
|
#include <cstdio>
|
2017-06-16 16:28:48 -03:00
|
|
|
#include <limits>
|
2017-12-05 13:31:34 -03:00
|
|
|
#include <memory>
|
2011-03-30 18:27:52 -07:00
|
|
|
|
2013-08-05 21:20:19 -03:00
|
|
|
namespace app {
|
|
|
|
|
|
|
|
using namespace app::skin;
|
2010-09-25 16:22:32 -03:00
|
|
|
using namespace gfx;
|
2012-06-17 22:02:54 -03:00
|
|
|
using namespace ui;
|
2014-12-28 11:06:11 -03:00
|
|
|
using namespace render;
|
2010-09-25 16:22:32 -03:00
|
|
|
|
2016-10-11 16:41:50 -03:00
|
|
|
// TODO these should be grouped in some kind of "performance counters"
|
|
|
|
static base::Chrono renderChrono;
|
|
|
|
static double renderElapsed = 0.0;
|
|
|
|
|
2013-08-05 21:20:19 -03:00
|
|
|
class EditorPostRenderImpl : public EditorPostRender {
|
2011-04-21 17:39:43 -03:00
|
|
|
public:
|
2014-08-31 14:17:49 -03:00
|
|
|
EditorPostRenderImpl(Editor* editor, Graphics* g)
|
2011-04-21 17:39:43 -03:00
|
|
|
: m_editor(editor)
|
2015-04-27 12:03:18 -03:00
|
|
|
, m_g(g) {
|
2011-04-21 17:39:43 -03:00
|
|
|
}
|
|
|
|
|
2015-09-30 16:34:43 -03:00
|
|
|
Editor* getEditor() override {
|
2011-04-21 17:39:43 -03:00
|
|
|
return m_editor;
|
|
|
|
}
|
|
|
|
|
2020-07-21 16:39:51 -03:00
|
|
|
Graphics* getGraphics() override {
|
|
|
|
return m_g;
|
|
|
|
}
|
|
|
|
|
2018-06-21 12:58:11 -03:00
|
|
|
void drawLine(gfx::Color color, int x1, int y1, int x2, int y2) override {
|
2014-11-24 00:09:22 -03:00
|
|
|
gfx::Point a(x1, y1);
|
|
|
|
gfx::Point b(x2, y2);
|
|
|
|
a = m_editor->editorToScreen(a);
|
|
|
|
b = m_editor->editorToScreen(b);
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
gfx::Rect bounds = m_editor->bounds();
|
2014-11-24 00:09:22 -03:00
|
|
|
a.x -= bounds.x;
|
|
|
|
a.y -= bounds.y;
|
|
|
|
b.x -= bounds.x;
|
|
|
|
b.y -= bounds.y;
|
2018-06-21 12:58:11 -03:00
|
|
|
m_g->drawLine(color, a, b);
|
2011-04-21 17:39:43 -03:00
|
|
|
}
|
|
|
|
|
2015-04-27 12:03:18 -03:00
|
|
|
void drawRectXor(const gfx::Rect& rc) override {
|
|
|
|
gfx::Rect rc2 = m_editor->editorToScreen(rc);
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
gfx::Rect bounds = m_editor->bounds();
|
2015-04-27 12:03:18 -03:00
|
|
|
rc2.x -= bounds.x;
|
|
|
|
rc2.y -= bounds.y;
|
|
|
|
|
|
|
|
m_g->setDrawMode(Graphics::DrawMode::Xor);
|
|
|
|
m_g->drawRect(gfx::rgba(255, 255, 255), rc2);
|
|
|
|
m_g->setDrawMode(Graphics::DrawMode::Solid);
|
|
|
|
}
|
|
|
|
|
2018-06-21 12:58:11 -03:00
|
|
|
void fillRect(gfx::Color color, const gfx::Rect& rc) override {
|
|
|
|
gfx::Rect rc2 = m_editor->editorToScreen(rc);
|
|
|
|
gfx::Rect bounds = m_editor->bounds();
|
|
|
|
rc2.x -= bounds.x;
|
|
|
|
rc2.y -= bounds.y;
|
|
|
|
m_g->fillRect(color, rc2);
|
|
|
|
}
|
|
|
|
|
2011-04-21 17:39:43 -03:00
|
|
|
private:
|
|
|
|
Editor* m_editor;
|
2014-08-31 14:17:49 -03:00
|
|
|
Graphics* m_g;
|
2011-04-21 17:39:43 -03:00
|
|
|
};
|
|
|
|
|
2014-12-28 11:06:11 -03:00
|
|
|
// static
|
2018-06-19 23:25:31 -03:00
|
|
|
EditorRender* Editor::m_renderEngine = nullptr;
|
2015-08-19 14:25:57 -03:00
|
|
|
|
2018-07-07 11:54:44 -03:00
|
|
|
Editor::Editor(Doc* document, EditorFlags flags)
|
2019-09-23 20:02:19 -03:00
|
|
|
: Widget(Editor::Type())
|
2011-11-13 20:40:19 -03:00
|
|
|
, m_state(new StandbyState())
|
|
|
|
, m_decorator(NULL)
|
2013-01-20 18:40:37 -03:00
|
|
|
, m_document(document)
|
2014-07-29 00:53:24 -03:00
|
|
|
, m_sprite(m_document->sprite())
|
2016-06-07 19:38:56 -03:00
|
|
|
, m_layer(m_sprite->root()->firstLayer())
|
2014-12-28 20:39:11 -03:00
|
|
|
, m_frame(frame_t(0))
|
2016-03-24 13:04:35 -03:00
|
|
|
, m_docPref(Preferences::instance().document(document))
|
2021-05-19 16:36:43 -03:00
|
|
|
, m_tiledModeHelper(app::TiledModeHelper(m_docPref.tiled.mode(), m_sprite))
|
2015-06-25 12:44:47 -03:00
|
|
|
, m_brushPreview(this)
|
2016-04-04 18:46:48 -03:00
|
|
|
, m_toolLoopModifiers(tools::ToolLoopModifiers::kNone)
|
2015-08-04 12:26:58 -03:00
|
|
|
, m_padding(0, 0)
|
2015-08-18 15:46:17 -03:00
|
|
|
, m_antsTimer(100, this)
|
|
|
|
, m_antsOffset(0)
|
2011-10-29 19:21:19 -03:00
|
|
|
, m_customizationDelegate(NULL)
|
2013-01-20 18:40:37 -03:00
|
|
|
, m_docView(NULL)
|
2014-02-02 19:14:27 -03:00
|
|
|
, m_flags(flags)
|
2014-08-19 08:17:57 -03:00
|
|
|
, m_secondaryButton(false)
|
2019-02-18 11:47:35 -03:00
|
|
|
, m_flashing(Flashing::None)
|
2015-05-07 19:08:24 -03:00
|
|
|
, m_aniSpeed(1.0)
|
2016-12-07 08:40:08 -03:00
|
|
|
, m_isPlaying(false)
|
2017-03-23 00:57:21 -03:00
|
|
|
, m_showGuidesThisCel(nullptr)
|
2020-08-04 16:29:22 -03:00
|
|
|
, m_showAutoCelGuides(false)
|
2017-04-14 10:02:02 -03:00
|
|
|
, m_tagFocusBand(-1)
|
2008-04-28 16:32:45 +00:00
|
|
|
{
|
2018-06-19 23:25:31 -03:00
|
|
|
if (!m_renderEngine)
|
|
|
|
m_renderEngine = new EditorRender;
|
|
|
|
|
2016-05-23 19:22:02 -03:00
|
|
|
m_proj.setPixelRatio(m_sprite->pixelRatio());
|
|
|
|
|
2011-11-13 20:40:19 -03:00
|
|
|
// Add the first state into the history.
|
|
|
|
m_statesHistory.push(m_state);
|
|
|
|
|
2012-04-05 19:00:19 -03:00
|
|
|
this->setFocusStop(true);
|
2010-08-12 00:29:51 -03:00
|
|
|
|
2016-09-13 15:02:00 -03:00
|
|
|
App::instance()->activeToolManager()->add_observer(this);
|
2011-07-28 20:02:07 -03:00
|
|
|
|
2014-06-12 22:29:19 -03:00
|
|
|
m_fgColorChangeConn =
|
2015-05-18 16:53:25 -03:00
|
|
|
Preferences::instance().colorBar.fgColor.AfterChange.connect(
|
2020-07-03 21:51:46 -03:00
|
|
|
[this]{ onFgColorChange(); });
|
2014-02-06 20:22:52 -03:00
|
|
|
|
2015-07-06 13:05:06 -03:00
|
|
|
m_contextBarBrushChangeConn =
|
2016-04-22 13:19:06 -03:00
|
|
|
App::instance()->contextBar()->BrushChange.connect(
|
2020-07-03 21:51:46 -03:00
|
|
|
[this]{ onContextBarBrushChange(); });
|
2015-07-06 13:05:06 -03:00
|
|
|
|
2015-06-15 11:29:12 -03:00
|
|
|
// Restore last site in preferences
|
2016-08-11 15:04:15 -03:00
|
|
|
{
|
|
|
|
frame_t preferredFrame = m_docPref.site.frame();
|
|
|
|
if (preferredFrame >= 0 && preferredFrame <= m_sprite->lastFrame())
|
|
|
|
setFrame(preferredFrame);
|
|
|
|
|
|
|
|
LayerList layers = m_sprite->allBrowsableLayers();
|
|
|
|
layer_t layerIndex = m_docPref.site.layer();
|
2016-08-19 18:51:15 -03:00
|
|
|
if (layerIndex >= 0 && layerIndex < int(layers.size()))
|
2016-08-11 15:04:15 -03:00
|
|
|
setLayer(layers[layerIndex]);
|
|
|
|
}
|
2015-06-15 11:29:12 -03:00
|
|
|
|
2020-07-03 21:51:46 -03:00
|
|
|
m_tiledConnBefore = m_docPref.tiled.BeforeChange.connect([this]{ onTiledModeBeforeChange(); });
|
|
|
|
m_tiledConn = m_docPref.tiled.AfterChange.connect([this]{ onTiledModeChange(); });
|
|
|
|
m_gridConn = m_docPref.grid.AfterChange.connect([this]{ invalidate(); });
|
|
|
|
m_pixelGridConn = m_docPref.pixelGrid.AfterChange.connect([this]{ invalidate(); });
|
|
|
|
m_bgConn = m_docPref.bg.AfterChange.connect([this]{ invalidate(); });
|
|
|
|
m_onionskinConn = m_docPref.onionskin.AfterChange.connect([this]{ invalidate(); });
|
|
|
|
m_symmetryModeConn = Preferences::instance().symmetryMode.enabled.AfterChange.connect([this]{ invalidateIfActive(); });
|
2016-03-24 13:04:35 -03:00
|
|
|
m_showExtrasConn =
|
|
|
|
m_docPref.show.AfterChange.connect(
|
2020-07-03 21:51:46 -03:00
|
|
|
[this]{ onShowExtrasChange(); });
|
2014-08-08 01:00:02 -03:00
|
|
|
|
2016-09-13 15:02:00 -03:00
|
|
|
m_document->add_observer(this);
|
2014-12-08 14:57:56 -03:00
|
|
|
|
2015-05-10 19:32:42 -03:00
|
|
|
m_state->onEnterState(this);
|
2010-01-27 02:52:30 +00:00
|
|
|
}
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2010-01-27 02:52:30 +00:00
|
|
|
Editor::~Editor()
|
|
|
|
{
|
2015-06-15 11:29:12 -03:00
|
|
|
if (m_document && m_sprite) {
|
2016-08-11 15:04:15 -03:00
|
|
|
LayerList layers = m_sprite->allBrowsableLayers();
|
|
|
|
layer_t layerIndex = doc::find_layer_index(layers, layer());
|
|
|
|
|
2016-03-24 13:04:35 -03:00
|
|
|
m_docPref.site.frame(frame());
|
2016-08-11 15:04:15 -03:00
|
|
|
m_docPref.site.layer(layerIndex);
|
2015-06-15 11:29:12 -03:00
|
|
|
}
|
|
|
|
|
2015-03-31 17:31:45 -03:00
|
|
|
m_observers.notifyDestroyEditor(this);
|
2016-09-13 15:02:00 -03:00
|
|
|
m_document->remove_observer(this);
|
|
|
|
App::instance()->activeToolManager()->remove_observer(this);
|
2014-12-08 14:57:56 -03:00
|
|
|
|
2011-10-29 19:21:19 -03:00
|
|
|
setCustomizationDelegate(NULL);
|
|
|
|
|
2015-08-18 15:46:17 -03:00
|
|
|
m_antsTimer.stop();
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
|
2015-05-20 16:23:53 -03:00
|
|
|
void Editor::destroyEditorSharedInternals()
|
|
|
|
{
|
2018-06-19 23:25:31 -03:00
|
|
|
if (m_renderEngine) {
|
|
|
|
delete m_renderEngine;
|
|
|
|
m_renderEngine = nullptr;
|
|
|
|
}
|
2015-05-20 16:23:53 -03:00
|
|
|
}
|
|
|
|
|
2015-04-27 10:08:48 -03:00
|
|
|
bool Editor::isActive() const
|
|
|
|
{
|
|
|
|
return (current_editor == this);
|
|
|
|
}
|
|
|
|
|
2019-09-23 20:02:19 -03:00
|
|
|
// static
|
|
|
|
WidgetType Editor::Type()
|
2008-04-28 16:32:45 +00:00
|
|
|
{
|
2013-04-03 22:07:24 -03:00
|
|
|
static WidgetType type = kGenericWidget;
|
|
|
|
if (type == kGenericWidget)
|
|
|
|
type = register_widget_type();
|
2008-04-28 16:32:45 +00:00
|
|
|
return type;
|
|
|
|
}
|
|
|
|
|
2011-11-13 20:40:19 -03:00
|
|
|
void Editor::setStateInternal(const EditorStatePtr& newState)
|
2011-04-21 17:29:43 -03:00
|
|
|
{
|
2015-06-25 12:44:47 -03:00
|
|
|
m_brushPreview.hide();
|
2011-04-10 20:15:17 -03:00
|
|
|
|
2011-04-21 17:29:43 -03:00
|
|
|
// Fire before change state event, set the state, and fire after
|
|
|
|
// change state event.
|
2015-05-10 19:32:42 -03:00
|
|
|
EditorState::LeaveAction leaveAction =
|
|
|
|
m_state->onLeaveState(this, newState.get());
|
2011-11-13 20:40:19 -03:00
|
|
|
|
|
|
|
// Push a new state
|
|
|
|
if (newState) {
|
2015-05-10 19:32:42 -03:00
|
|
|
if (leaveAction == EditorState::DiscardState)
|
2011-11-13 20:40:19 -03:00
|
|
|
m_statesHistory.pop();
|
|
|
|
|
|
|
|
m_statesHistory.push(newState);
|
|
|
|
m_state = newState;
|
|
|
|
}
|
|
|
|
// Go to previous state
|
|
|
|
else {
|
|
|
|
m_state->onBeforePopState(this);
|
|
|
|
|
2016-11-30 12:35:01 -03:00
|
|
|
// Save the current state into "m_deletedStates" just to keep a
|
|
|
|
// reference to it to avoid delete it right now. We'll delete it
|
|
|
|
// in the next Editor::onProcessMessage().
|
|
|
|
//
|
|
|
|
// This is necessary for PlayState because it removes itself
|
|
|
|
// calling Editor::stop() from PlayState::onPlaybackTick(). If we
|
|
|
|
// delete the PlayState inside the "Tick" timer signal, the
|
|
|
|
// program will crash (because we're iterating the
|
|
|
|
// PlayState::m_playTimer slots).
|
|
|
|
m_deletedStates.push(m_state);
|
|
|
|
|
2011-11-13 20:40:19 -03:00
|
|
|
m_statesHistory.pop();
|
|
|
|
m_state = m_statesHistory.top();
|
|
|
|
}
|
2011-04-10 20:15:17 -03:00
|
|
|
|
2015-04-02 20:42:43 -03:00
|
|
|
ASSERT(m_state);
|
2012-01-12 00:39:39 -03:00
|
|
|
|
2011-04-10 20:15:17 -03:00
|
|
|
// Change to the new state.
|
2015-05-10 19:32:42 -03:00
|
|
|
m_state->onEnterState(this);
|
2011-04-10 20:15:17 -03:00
|
|
|
|
2012-08-18 15:22:23 -03:00
|
|
|
// Notify observers
|
|
|
|
m_observers.notifyStateChanged(this);
|
2011-04-10 20:15:17 -03:00
|
|
|
|
2016-05-04 12:32:39 -03:00
|
|
|
// Redraw layer edges
|
|
|
|
if (m_docPref.show.layerEdges())
|
|
|
|
invalidate();
|
|
|
|
|
2011-04-10 20:15:17 -03:00
|
|
|
// Setup the new mouse cursor
|
2021-03-02 13:50:49 -03:00
|
|
|
setCursor(mousePosInDisplay());
|
2011-04-10 20:15:17 -03:00
|
|
|
|
|
|
|
updateStatusBar();
|
|
|
|
}
|
|
|
|
|
2011-11-13 20:40:19 -03:00
|
|
|
void Editor::setState(const EditorStatePtr& newState)
|
|
|
|
{
|
|
|
|
setStateInternal(newState);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::backToPreviousState()
|
|
|
|
{
|
|
|
|
setStateInternal(EditorStatePtr(NULL));
|
|
|
|
}
|
|
|
|
|
2016-01-05 16:37:52 -03:00
|
|
|
void Editor::getInvalidDecoratoredRegion(gfx::Region& region)
|
|
|
|
{
|
|
|
|
// Remove decorated region that cannot be just moved because it
|
|
|
|
// must be redrawn in another position when the Editor's scroll
|
|
|
|
// changes (e.g. symmetry handles).
|
|
|
|
if ((m_flags & kShowDecorators) && m_decorator)
|
|
|
|
m_decorator->getInvalidDecoratoredRegion(this, region);
|
2018-06-22 15:41:22 -03:00
|
|
|
|
|
|
|
#if ENABLE_DEVMODE
|
|
|
|
// TODO put this in other widget
|
|
|
|
if (Preferences::instance().perf.showRenderTime()) {
|
|
|
|
if (!m_perfInfoBounds.isEmpty())
|
|
|
|
region |= gfx::Region(m_perfInfoBounds);
|
|
|
|
}
|
|
|
|
#endif // ENABLE_DEVMODE
|
2016-01-05 16:37:52 -03:00
|
|
|
}
|
|
|
|
|
2013-03-11 20:29:45 -03:00
|
|
|
void Editor::setLayer(const Layer* layer)
|
|
|
|
{
|
2019-07-04 15:04:15 -03:00
|
|
|
const bool changed = (m_layer != layer);
|
|
|
|
const bool gridVisible = (changed && m_docPref.show.grid());
|
|
|
|
|
|
|
|
doc::Grid oldGrid, newGrid;
|
|
|
|
if (gridVisible)
|
|
|
|
oldGrid = getSite().grid();
|
2015-07-31 14:30:11 -03:00
|
|
|
|
2014-08-28 09:36:34 -03:00
|
|
|
m_observers.notifyBeforeLayerChanged(this);
|
2013-03-11 20:29:45 -03:00
|
|
|
m_layer = const_cast<Layer*>(layer);
|
2014-08-28 09:36:34 -03:00
|
|
|
m_observers.notifyAfterLayerChanged(this);
|
2013-11-10 22:59:25 -03:00
|
|
|
|
2019-07-04 15:04:15 -03:00
|
|
|
if (gridVisible)
|
|
|
|
newGrid = getSite().grid();
|
|
|
|
|
2015-07-31 14:30:11 -03:00
|
|
|
if (m_document && changed) {
|
2016-05-03 16:23:38 -03:00
|
|
|
if (// If the onion skinning depends on the active layer
|
|
|
|
m_docPref.onionskin.currentLayer() ||
|
|
|
|
// If the user want to see the active layer edges...
|
2016-12-01 15:20:35 -03:00
|
|
|
m_docPref.show.layerEdges() ||
|
|
|
|
// If there is a different opacity for nonactive-layers
|
2017-03-23 00:57:21 -03:00
|
|
|
Preferences::instance().experimental.nonactiveLayersOpacity() < 255 ||
|
|
|
|
// If the automatic cel guides are visible...
|
2019-07-04 15:04:15 -03:00
|
|
|
m_showGuidesThisCel ||
|
|
|
|
// If grid settings changed
|
|
|
|
(gridVisible &&
|
|
|
|
(oldGrid.tileSize() != newGrid.tileSize() ||
|
|
|
|
oldGrid.origin() != newGrid.origin()))) {
|
2016-05-03 16:23:38 -03:00
|
|
|
// We've to redraw the whole editor
|
2015-07-31 14:30:11 -03:00
|
|
|
invalidate();
|
2016-05-03 16:23:38 -03:00
|
|
|
}
|
2015-07-31 14:30:11 -03:00
|
|
|
}
|
|
|
|
|
2015-05-05 13:24:26 -03:00
|
|
|
// The active layer has changed.
|
|
|
|
if (isActive())
|
|
|
|
UIContext::instance()->notifyActiveSiteChanged();
|
|
|
|
|
2013-03-11 20:29:45 -03:00
|
|
|
updateStatusBar();
|
|
|
|
}
|
|
|
|
|
2014-12-28 20:39:11 -03:00
|
|
|
void Editor::setFrame(frame_t frame)
|
2013-03-11 20:29:45 -03:00
|
|
|
{
|
2015-05-05 13:24:26 -03:00
|
|
|
if (m_frame == frame)
|
|
|
|
return;
|
2013-03-11 20:29:45 -03:00
|
|
|
|
2015-05-05 13:24:26 -03:00
|
|
|
m_observers.notifyBeforeFrameChanged(this);
|
2015-08-18 17:56:40 -03:00
|
|
|
{
|
|
|
|
HideBrushPreview hide(m_brushPreview);
|
|
|
|
m_frame = frame;
|
|
|
|
}
|
2015-05-05 13:24:26 -03:00
|
|
|
m_observers.notifyAfterFrameChanged(this);
|
|
|
|
|
|
|
|
// The active frame has changed.
|
|
|
|
if (isActive())
|
|
|
|
UIContext::instance()->notifyActiveSiteChanged();
|
|
|
|
|
2021-06-08 20:34:15 -03:00
|
|
|
// Invalidate canvas area
|
|
|
|
invalidateCanvas();
|
2015-05-05 13:24:26 -03:00
|
|
|
updateStatusBar();
|
2013-03-11 20:29:45 -03:00
|
|
|
}
|
|
|
|
|
2015-04-20 16:27:09 -03:00
|
|
|
void Editor::getSite(Site* site) const
|
2013-03-11 20:29:45 -03:00
|
|
|
{
|
2015-04-20 16:27:09 -03:00
|
|
|
site->document(m_document);
|
|
|
|
site->sprite(m_sprite);
|
|
|
|
site->layer(m_layer);
|
|
|
|
site->frame(m_frame);
|
2019-03-29 15:57:10 -03:00
|
|
|
|
2019-05-07 10:28:37 -03:00
|
|
|
if (!m_selectedSlices.empty() &&
|
|
|
|
getCurrentEditorInk()->isSlice()) {
|
2019-05-02 16:26:13 -03:00
|
|
|
site->selectedSlices(m_selectedSlices);
|
2019-05-07 10:28:37 -03:00
|
|
|
}
|
2019-09-05 15:03:13 -03:00
|
|
|
|
|
|
|
// TODO we should not access timeline directly here
|
|
|
|
Timeline* timeline = App::instance()->timeline();
|
|
|
|
if (timeline &&
|
2021-04-08 15:17:39 -03:00
|
|
|
timeline->isVisible() &&
|
2019-09-05 15:03:13 -03:00
|
|
|
timeline->range().enabled()) {
|
|
|
|
site->range(timeline->range());
|
|
|
|
}
|
2019-03-29 15:57:10 -03:00
|
|
|
|
|
|
|
if (m_layer && m_layer->isTilemap()) {
|
2020-06-25 19:21:35 -03:00
|
|
|
TilemapMode tilemapMode = site->tilemapMode();
|
|
|
|
TilesetMode tilesetMode = site->tilesetMode();
|
2019-03-29 15:57:10 -03:00
|
|
|
const ColorBar* colorbar = ColorBar::instance();
|
|
|
|
ASSERT(colorbar);
|
2020-06-25 19:21:35 -03:00
|
|
|
if (colorbar) {
|
|
|
|
tilemapMode = colorbar->tilemapMode();
|
|
|
|
tilesetMode = colorbar->tilesetMode();
|
|
|
|
}
|
|
|
|
site->tilemapMode(tilemapMode);
|
|
|
|
site->tilesetMode(tilesetMode);
|
2019-03-29 15:57:10 -03:00
|
|
|
}
|
2013-03-11 20:29:45 -03:00
|
|
|
}
|
|
|
|
|
2015-04-20 16:27:09 -03:00
|
|
|
Site Editor::getSite() const
|
2013-03-11 20:29:45 -03:00
|
|
|
{
|
2015-04-20 16:27:09 -03:00
|
|
|
Site site;
|
|
|
|
getSite(&site);
|
|
|
|
return site;
|
2013-03-11 20:29:45 -03:00
|
|
|
}
|
|
|
|
|
2016-02-11 21:09:31 -03:00
|
|
|
void Editor::setZoom(const render::Zoom& zoom)
|
|
|
|
{
|
2016-05-23 19:22:02 -03:00
|
|
|
if (m_proj.zoom() != zoom) {
|
|
|
|
m_proj.setZoom(zoom);
|
2016-02-11 21:09:31 -03:00
|
|
|
notifyZoomChanged();
|
|
|
|
}
|
2016-02-18 13:58:45 -03:00
|
|
|
else {
|
|
|
|
// Just copy the zoom as the internal "Zoom::m_internalScale"
|
|
|
|
// value might be different and we want to keep this value updated
|
|
|
|
// for better zooming experience in StateWithWheelBehavior.
|
2016-05-23 19:22:02 -03:00
|
|
|
m_proj.setZoom(zoom);
|
2016-02-18 13:58:45 -03:00
|
|
|
}
|
2016-02-11 21:09:31 -03:00
|
|
|
}
|
|
|
|
|
2013-01-20 18:40:37 -03:00
|
|
|
void Editor::setDefaultScroll()
|
2020-03-27 16:53:17 -03:00
|
|
|
{
|
|
|
|
if (Preferences::instance().editor.autoFit())
|
|
|
|
setScrollAndZoomToFitScreen();
|
|
|
|
else
|
|
|
|
setScrollToCenter();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::setScrollToCenter()
|
2008-04-28 16:32:45 +00:00
|
|
|
{
|
2013-01-20 18:40:37 -03:00
|
|
|
View* view = View::getView(this);
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
Rect vp = view->viewportBounds();
|
2017-11-07 15:21:03 -03:00
|
|
|
gfx::Size canvas = canvasSize();
|
2011-04-02 18:12:41 -03:00
|
|
|
|
2014-11-24 00:09:22 -03:00
|
|
|
setEditorScroll(
|
|
|
|
gfx::Point(
|
2017-11-07 15:21:03 -03:00
|
|
|
m_padding.x - vp.w/2 + m_proj.applyX(canvas.w)/2,
|
|
|
|
m_padding.y - vp.h/2 + m_proj.applyY(canvas.h)/2));
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
|
2016-09-15 09:50:00 -03:00
|
|
|
void Editor::setScrollAndZoomToFitScreen()
|
|
|
|
{
|
|
|
|
View* view = View::getView(this);
|
2017-11-07 15:21:03 -03:00
|
|
|
gfx::Rect vp = view->viewportBounds();
|
|
|
|
gfx::Size canvas = canvasSize();
|
2016-09-16 15:44:26 -03:00
|
|
|
Zoom zoom = m_proj.zoom();
|
2016-09-15 09:50:00 -03:00
|
|
|
|
2017-11-07 15:21:03 -03:00
|
|
|
if (float(vp.w) / float(canvas.w) <
|
|
|
|
float(vp.h) / float(canvas.h)) {
|
|
|
|
if (vp.w < m_proj.applyX(canvas.w)) {
|
|
|
|
while (vp.w < m_proj.applyX(canvas.w)) {
|
2016-09-15 09:50:00 -03:00
|
|
|
if (!zoom.out())
|
|
|
|
break;
|
2016-09-16 15:44:26 -03:00
|
|
|
m_proj.setZoom(zoom);
|
|
|
|
}
|
2016-09-15 09:50:00 -03:00
|
|
|
}
|
2017-11-07 15:21:03 -03:00
|
|
|
else if (vp.w > m_proj.applyX(canvas.w)) {
|
2016-09-15 09:50:00 -03:00
|
|
|
bool out = true;
|
2017-11-07 15:21:03 -03:00
|
|
|
while (vp.w > m_proj.applyX(canvas.w)) {
|
2016-09-15 09:50:00 -03:00
|
|
|
if (!zoom.in()) {
|
|
|
|
out = false;
|
|
|
|
break;
|
|
|
|
}
|
2016-09-16 15:44:26 -03:00
|
|
|
m_proj.setZoom(zoom);
|
2016-09-15 09:50:00 -03:00
|
|
|
}
|
2016-09-16 15:44:26 -03:00
|
|
|
if (out) {
|
2016-09-15 09:50:00 -03:00
|
|
|
zoom.out();
|
2016-09-16 15:44:26 -03:00
|
|
|
m_proj.setZoom(zoom);
|
|
|
|
}
|
2016-09-15 09:50:00 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2017-11-07 15:21:03 -03:00
|
|
|
if (vp.h < m_proj.applyY(canvas.h)) {
|
|
|
|
while (vp.h < m_proj.applyY(canvas.h)) {
|
2016-09-15 09:50:00 -03:00
|
|
|
if (!zoom.out())
|
|
|
|
break;
|
2016-09-16 15:44:26 -03:00
|
|
|
m_proj.setZoom(zoom);
|
2016-09-15 09:50:00 -03:00
|
|
|
}
|
|
|
|
}
|
2017-11-07 15:21:03 -03:00
|
|
|
else if (vp.h > m_proj.applyY(canvas.h)) {
|
2016-09-15 09:50:00 -03:00
|
|
|
bool out = true;
|
2017-11-07 15:21:03 -03:00
|
|
|
while (vp.h > m_proj.applyY(canvas.h)) {
|
2016-09-15 09:50:00 -03:00
|
|
|
if (!zoom.in()) {
|
|
|
|
out = false;
|
|
|
|
break;
|
|
|
|
}
|
2016-09-16 15:44:26 -03:00
|
|
|
m_proj.setZoom(zoom);
|
2016-09-15 09:50:00 -03:00
|
|
|
}
|
2016-09-16 15:44:26 -03:00
|
|
|
if (out) {
|
2016-09-15 09:50:00 -03:00
|
|
|
zoom.out();
|
2016-09-16 15:44:26 -03:00
|
|
|
m_proj.setZoom(zoom);
|
|
|
|
}
|
2016-09-15 09:50:00 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-13 15:01:45 -03:00
|
|
|
updateEditor(false);
|
2016-09-15 09:50:00 -03:00
|
|
|
setEditorScroll(
|
|
|
|
gfx::Point(
|
2017-11-07 15:21:03 -03:00
|
|
|
m_padding.x - vp.w/2 + m_proj.applyX(canvas.w)/2,
|
|
|
|
m_padding.y - vp.h/2 + m_proj.applyY(canvas.h)/2));
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
|
2010-07-18 14:44:21 -03:00
|
|
|
// Sets the scroll position of the editor
|
2016-01-05 16:37:52 -03:00
|
|
|
void Editor::setEditorScroll(const gfx::Point& scroll)
|
2008-04-28 16:32:45 +00:00
|
|
|
{
|
2016-01-05 16:37:52 -03:00
|
|
|
View::getView(this)->setViewScroll(scroll);
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
|
2015-08-14 19:46:48 -03:00
|
|
|
void Editor::setEditorZoom(const render::Zoom& zoom)
|
2014-08-13 00:22:29 -03:00
|
|
|
{
|
2015-05-07 15:13:26 -03:00
|
|
|
setZoomAndCenterInMouse(
|
2021-03-02 13:50:49 -03:00
|
|
|
zoom, mousePosInDisplay(),
|
2015-05-07 15:13:26 -03:00
|
|
|
Editor::ZoomBehavior::CENTER);
|
2014-08-13 00:22:29 -03:00
|
|
|
}
|
|
|
|
|
2020-01-13 15:01:45 -03:00
|
|
|
void Editor::updateEditor(const bool restoreScrollPos)
|
2008-04-28 16:32:45 +00:00
|
|
|
{
|
2020-01-13 15:01:45 -03:00
|
|
|
View::getView(this)->updateView(restoreScrollPos);
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
|
2014-12-08 14:57:56 -03:00
|
|
|
void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& spriteRectToDraw, int dx, int dy)
|
2008-04-28 16:32:45 +00:00
|
|
|
{
|
2014-12-08 14:57:56 -03:00
|
|
|
// Clip from sprite and apply zoom
|
2015-06-09 16:22:47 -03:00
|
|
|
gfx::Rect rc = m_sprite->bounds().createIntersection(spriteRectToDraw);
|
2016-05-23 19:22:02 -03:00
|
|
|
rc = m_proj.apply(rc);
|
2014-12-08 14:57:56 -03:00
|
|
|
|
2018-06-22 15:41:22 -03:00
|
|
|
gfx::Rect dest(dx + m_padding.x + rc.x,
|
|
|
|
dy + m_padding.y + rc.y, 0, 0);
|
2014-02-06 20:22:52 -03:00
|
|
|
|
|
|
|
// Clip from graphics/screen
|
|
|
|
const gfx::Rect& clip = g->getClipBounds();
|
2018-06-22 15:41:22 -03:00
|
|
|
if (dest.x < clip.x) {
|
|
|
|
rc.x += clip.x - dest.x;
|
|
|
|
rc.w -= clip.x - dest.x;
|
|
|
|
dest.x = clip.x;
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
2018-06-22 15:41:22 -03:00
|
|
|
if (dest.y < clip.y) {
|
|
|
|
rc.y += clip.y - dest.y;
|
|
|
|
rc.h -= clip.y - dest.y;
|
|
|
|
dest.y = clip.y;
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
2018-06-22 15:41:22 -03:00
|
|
|
if (dest.x+rc.w > clip.x+clip.w) {
|
|
|
|
rc.w = clip.x+clip.w-dest.x;
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
2018-06-22 15:41:22 -03:00
|
|
|
if (dest.y+rc.h > clip.y+clip.h) {
|
|
|
|
rc.h = clip.y+clip.h-dest.y;
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
|
2014-12-28 11:06:11 -03:00
|
|
|
if (rc.isEmpty())
|
|
|
|
return;
|
2013-03-11 20:29:45 -03:00
|
|
|
|
2018-06-22 15:41:22 -03:00
|
|
|
// Bounds of pixels from the sprite canvas that will be exposed in
|
|
|
|
// this render cycle.
|
|
|
|
gfx::Rect expose = m_proj.remove(rc);
|
|
|
|
|
|
|
|
// If the zoom level is less than 100%, we add extra pixels to
|
|
|
|
// the exposed area. Those pixels could be shown in the
|
|
|
|
// rendering process depending on each cel position.
|
|
|
|
// E.g. when we are drawing in a cel with position < (0,0)
|
|
|
|
if (m_proj.scaleX() < 1.0)
|
|
|
|
expose.enlargeXW(int(1./m_proj.scaleX()));
|
|
|
|
// If the zoom level is more than %100 we add an extra pixel to
|
|
|
|
// expose just in case the zoom requires to display it. Note:
|
|
|
|
// this is really necessary to avoid showing invalid destination
|
|
|
|
// areas in ToolLoopImpl.
|
|
|
|
else if (m_proj.scaleX() > 1.0)
|
|
|
|
expose.enlargeXW(1);
|
|
|
|
|
|
|
|
if (m_proj.scaleY() < 1.0)
|
|
|
|
expose.enlargeYH(int(1./m_proj.scaleY()));
|
|
|
|
else if (m_proj.scaleY() > 1.0)
|
|
|
|
expose.enlargeYH(1);
|
|
|
|
|
|
|
|
expose &= m_sprite->bounds();
|
|
|
|
|
|
|
|
const int maxw = std::max(0, m_sprite->width()-expose.x);
|
|
|
|
const int maxh = std::max(0, m_sprite->height()-expose.y);
|
2020-04-07 19:47:16 -03:00
|
|
|
expose.w = base::clamp(expose.w, 0, maxw);
|
|
|
|
expose.h = base::clamp(expose.h, 0, maxh);
|
2018-06-22 15:41:22 -03:00
|
|
|
if (expose.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// rc2 is the rectangle used to create a temporal rendered image of the sprite
|
2018-06-28 16:56:35 -03:00
|
|
|
const bool newEngine =
|
|
|
|
(Preferences::instance().experimental.newRenderEngine()
|
|
|
|
// Reference layers + zoom > 100% need the old render engine for
|
|
|
|
// sub-pixel rendering.
|
|
|
|
&& (!m_sprite->hasVisibleReferenceLayers()
|
|
|
|
|| (m_proj.scaleX() <= 1.0
|
|
|
|
&& m_proj.scaleY() <= 1.0)));
|
2018-06-22 15:41:22 -03:00
|
|
|
gfx::Rect rc2;
|
|
|
|
if (newEngine) {
|
|
|
|
rc2 = expose; // New engine, exposed rectangle (without zoom)
|
|
|
|
dest.x = dx + m_padding.x + m_proj.applyX(rc2.x);
|
|
|
|
dest.y = dy + m_padding.y + m_proj.applyY(rc2.y);
|
|
|
|
dest.w = m_proj.applyX(rc2.w);
|
|
|
|
dest.h = m_proj.applyY(rc2.h);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
rc2 = rc; // Old engine, same rectangle with zoom
|
|
|
|
dest.w = rc.w;
|
|
|
|
dest.h = rc.h;
|
|
|
|
}
|
|
|
|
|
2018-08-08 17:27:26 -03:00
|
|
|
std::unique_ptr<Image> rendered(nullptr);
|
2014-12-28 11:06:11 -03:00
|
|
|
try {
|
|
|
|
// Generate a "expose sprite pixels" notification. This is used by
|
|
|
|
// tool managers that need to validate this region (copy pixels from
|
|
|
|
// the original cel) before it can be used by the RenderEngine.
|
2018-06-22 15:41:22 -03:00
|
|
|
m_document->notifyExposeSpritePixels(m_sprite, gfx::Region(expose));
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2014-12-28 11:06:11 -03:00
|
|
|
// Create a temporary RGB bitmap to draw all to it
|
2018-06-22 15:41:22 -03:00
|
|
|
rendered.reset(Image::create(IMAGE_RGB, rc2.w, rc2.h,
|
2018-06-19 23:25:31 -03:00
|
|
|
m_renderEngine->getRenderImageBuffer()));
|
2016-05-23 19:22:02 -03:00
|
|
|
|
2019-02-25 18:02:58 -03:00
|
|
|
m_renderEngine->setNewBlendMethod(Preferences::instance().experimental.newBlend());
|
2018-06-19 23:25:31 -03:00
|
|
|
m_renderEngine->setRefLayersVisiblity(true);
|
|
|
|
m_renderEngine->setSelectedLayer(m_layer);
|
2016-12-12 15:52:00 -03:00
|
|
|
if (m_flags & Editor::kUseNonactiveLayersOpacityWhenEnabled)
|
2018-06-19 23:25:31 -03:00
|
|
|
m_renderEngine->setNonactiveLayersOpacity(Preferences::instance().experimental.nonactiveLayersOpacity());
|
2016-12-12 15:52:00 -03:00
|
|
|
else
|
2018-06-19 23:25:31 -03:00
|
|
|
m_renderEngine->setNonactiveLayersOpacity(255);
|
2018-06-22 15:41:22 -03:00
|
|
|
m_renderEngine->setProjection(
|
|
|
|
newEngine ? render::Projection(): m_proj);
|
2018-06-19 23:25:31 -03:00
|
|
|
m_renderEngine->setupBackground(m_document, rendered->pixelFormat());
|
|
|
|
m_renderEngine->disableOnionskin();
|
2014-12-28 11:06:11 -03:00
|
|
|
|
|
|
|
if ((m_flags & kShowOnionskin) == kShowOnionskin) {
|
2016-03-24 13:04:35 -03:00
|
|
|
if (m_docPref.onionskin.active()) {
|
2015-05-27 14:49:01 -03:00
|
|
|
OnionskinOptions opts(
|
2016-03-24 13:04:35 -03:00
|
|
|
(m_docPref.onionskin.type() == app::gen::OnionskinType::MERGE ?
|
2015-05-27 14:49:01 -03:00
|
|
|
render::OnionskinType::MERGE:
|
2016-03-24 13:04:35 -03:00
|
|
|
(m_docPref.onionskin.type() == app::gen::OnionskinType::RED_BLUE_TINT ?
|
2015-05-27 14:49:01 -03:00
|
|
|
render::OnionskinType::RED_BLUE_TINT:
|
|
|
|
render::OnionskinType::NONE)));
|
|
|
|
|
2016-03-24 13:04:35 -03:00
|
|
|
opts.position(m_docPref.onionskin.position());
|
|
|
|
opts.prevFrames(m_docPref.onionskin.prevFrames());
|
|
|
|
opts.nextFrames(m_docPref.onionskin.nextFrames());
|
|
|
|
opts.opacityBase(m_docPref.onionskin.opacityBase());
|
|
|
|
opts.opacityStep(m_docPref.onionskin.opacityStep());
|
|
|
|
opts.layer(m_docPref.onionskin.currentLayer() ? m_layer: nullptr);
|
2015-05-27 14:49:01 -03:00
|
|
|
|
2019-10-01 14:55:08 -03:00
|
|
|
Tag* tag = nullptr;
|
2016-03-24 13:04:35 -03:00
|
|
|
if (m_docPref.onionskin.loopTag())
|
2019-10-01 14:55:08 -03:00
|
|
|
tag = m_sprite->tags().innerTag(m_frame);
|
2015-05-27 17:42:41 -03:00
|
|
|
opts.loopTag(tag);
|
|
|
|
|
2018-06-19 23:25:31 -03:00
|
|
|
m_renderEngine->setOnionskin(opts);
|
2011-04-21 17:39:43 -03:00
|
|
|
}
|
2014-12-28 11:06:11 -03:00
|
|
|
}
|
2011-04-21 17:39:43 -03:00
|
|
|
|
2015-08-19 10:03:29 -03:00
|
|
|
ExtraCelRef extraCel = m_document->extraCel();
|
2020-09-18 19:29:43 -03:00
|
|
|
if (extraCel &&
|
|
|
|
extraCel->type() != render::ExtraType::NONE) {
|
2018-06-19 23:25:31 -03:00
|
|
|
m_renderEngine->setExtraImage(
|
2015-08-19 10:03:29 -03:00
|
|
|
extraCel->type(),
|
|
|
|
extraCel->cel(),
|
|
|
|
extraCel->image(),
|
|
|
|
extraCel->blendMode(),
|
2015-04-26 15:59:28 -03:00
|
|
|
m_layer, m_frame);
|
|
|
|
}
|
2014-12-28 11:06:11 -03:00
|
|
|
|
2018-06-19 23:25:31 -03:00
|
|
|
m_renderEngine->renderSprite(
|
2018-08-08 17:27:26 -03:00
|
|
|
rendered.get(), m_sprite, m_frame, gfx::Clip(0, 0, rc2));
|
2014-12-28 11:06:11 -03:00
|
|
|
|
2018-06-19 23:25:31 -03:00
|
|
|
m_renderEngine->removeExtraImage();
|
2019-12-03 15:27:00 -03:00
|
|
|
|
|
|
|
// If the checked background is visible in this sprite, we save
|
|
|
|
// all settings of the background for this document.
|
|
|
|
if (!m_sprite->isOpaque())
|
|
|
|
m_docPref.bg.forceSection();
|
2014-12-28 11:06:11 -03:00
|
|
|
}
|
|
|
|
catch (const std::exception& e) {
|
|
|
|
Console::showException(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rendered) {
|
2018-08-09 12:58:43 -03:00
|
|
|
// Convert the render to a os::Surface
|
2020-07-07 19:06:48 -03:00
|
|
|
static os::SurfaceRef tmp = nullptr; // TODO move this to other centralized place
|
2018-10-18 15:29:16 -03:00
|
|
|
|
|
|
|
if (!tmp ||
|
|
|
|
tmp->width() < rc2.w ||
|
|
|
|
tmp->height() < rc2.h ||
|
|
|
|
tmp->colorSpace() != m_document->osColorSpace()) {
|
2018-06-22 15:41:22 -03:00
|
|
|
const int maxw = std::max(rc2.w, tmp ? tmp->width(): 0);
|
|
|
|
const int maxh = std::max(rc2.h, tmp ? tmp->height(): 0);
|
2020-07-07 19:06:48 -03:00
|
|
|
tmp = os::instance()->makeSurface(
|
2018-10-18 15:29:16 -03:00
|
|
|
maxw, maxh, m_document->osColorSpace());
|
2015-06-02 19:28:49 -03:00
|
|
|
}
|
2018-10-18 15:29:16 -03:00
|
|
|
|
2014-12-28 11:06:11 -03:00
|
|
|
if (tmp->nativeHandle()) {
|
2019-06-11 10:42:41 -03:00
|
|
|
if (newEngine) {
|
|
|
|
// Without doing something on the "tmp" surface before (like
|
|
|
|
// just drawing a pixel), we get a strange behavior where
|
|
|
|
// pixels are not updated correctly on the editor (e.g. when
|
|
|
|
// zoom < 100%). I didn't have enough time to investigate this
|
|
|
|
// issue yet, but this is a partial fix/hack.
|
|
|
|
//
|
|
|
|
// TODO review why do we need to do this, it looks like some
|
|
|
|
// internal state of a SkCanvas or SkBitmap thing is
|
|
|
|
// updated after this, because convert_image_to_surface()
|
|
|
|
// will overwrite these pixels anyway.
|
2020-03-01 23:42:08 -03:00
|
|
|
os::Paint paint;
|
|
|
|
paint.color(gfx::rgba(0, 0, 0, 255));
|
|
|
|
tmp->drawRect(gfx::Rect(0, 0, 1, 1), paint);
|
2019-06-11 10:42:41 -03:00
|
|
|
}
|
|
|
|
|
2018-08-08 17:27:26 -03:00
|
|
|
convert_image_to_surface(rendered.get(), m_sprite->palette(m_frame),
|
2020-07-07 19:06:48 -03:00
|
|
|
tmp.get(), 0, 0, 0, 0, rc2.w, rc2.h);
|
2018-10-18 15:29:16 -03:00
|
|
|
|
2018-06-22 15:41:22 -03:00
|
|
|
if (newEngine) {
|
2020-07-07 19:06:48 -03:00
|
|
|
g->drawSurface(tmp.get(), gfx::Rect(0, 0, rc2.w, rc2.h), dest);
|
2018-06-22 15:41:22 -03:00
|
|
|
}
|
|
|
|
else {
|
2020-07-07 19:06:48 -03:00
|
|
|
g->blit(tmp.get(), 0, 0, dest.x, dest.y, dest.w, dest.h);
|
2018-06-22 15:41:22 -03:00
|
|
|
}
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
}
|
2017-11-08 12:58:47 -03:00
|
|
|
|
|
|
|
// Draw grids
|
|
|
|
{
|
|
|
|
gfx::Rect enclosingRect(
|
|
|
|
m_padding.x + dx,
|
|
|
|
m_padding.y + dy,
|
|
|
|
m_proj.applyX(m_sprite->width()),
|
|
|
|
m_proj.applyY(m_sprite->height()));
|
|
|
|
|
2018-06-22 15:41:22 -03:00
|
|
|
IntersectClip clip(g, dest);
|
2017-11-08 12:58:47 -03:00
|
|
|
if (clip) {
|
|
|
|
// Draw the pixel grid
|
|
|
|
if ((m_proj.zoom().scale() > 2.0) && m_docPref.show.pixelGrid()) {
|
|
|
|
int alpha = m_docPref.pixelGrid.opacity();
|
|
|
|
|
|
|
|
if (m_docPref.pixelGrid.autoOpacity()) {
|
|
|
|
alpha = int(alpha * (m_proj.zoom().scale()-2.) / (16.-2.));
|
2020-04-07 19:47:16 -03:00
|
|
|
alpha = base::clamp(alpha, 0, 255);
|
2017-11-08 12:58:47 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
drawGrid(g, enclosingRect, Rect(0, 0, 1, 1),
|
|
|
|
m_docPref.pixelGrid.color(), alpha);
|
2019-12-03 15:27:00 -03:00
|
|
|
|
|
|
|
// Save all pixel grid settings that are unset
|
|
|
|
m_docPref.pixelGrid.forceSection();
|
2017-11-08 12:58:47 -03:00
|
|
|
}
|
2019-12-03 15:27:00 -03:00
|
|
|
m_docPref.show.pixelGrid.forceDirtyFlag();
|
2017-11-08 12:58:47 -03:00
|
|
|
|
|
|
|
// Draw the grid
|
|
|
|
if (m_docPref.show.grid()) {
|
2019-03-29 15:57:10 -03:00
|
|
|
gfx::Rect gridrc;
|
|
|
|
if (!m_state->getGridBounds(this, gridrc))
|
|
|
|
gridrc = getSite().gridBounds();
|
|
|
|
|
2017-11-08 12:58:47 -03:00
|
|
|
if (m_proj.applyX(gridrc.w) > 2 &&
|
|
|
|
m_proj.applyY(gridrc.h) > 2) {
|
|
|
|
int alpha = m_docPref.grid.opacity();
|
|
|
|
|
|
|
|
if (m_docPref.grid.autoOpacity()) {
|
|
|
|
double len = (m_proj.applyX(gridrc.w) +
|
|
|
|
m_proj.applyY(gridrc.h)) / 2.;
|
|
|
|
alpha = int(alpha * len / 32.);
|
2020-04-07 19:47:16 -03:00
|
|
|
alpha = base::clamp(alpha, 0, 255);
|
2017-11-08 12:58:47 -03:00
|
|
|
}
|
|
|
|
|
2019-10-10 15:08:59 -03:00
|
|
|
if (alpha > 8) {
|
|
|
|
drawGrid(g, enclosingRect, gridrc,
|
2017-11-08 12:58:47 -03:00
|
|
|
m_docPref.grid.color(), alpha);
|
2019-10-10 15:08:59 -03:00
|
|
|
}
|
2017-11-08 12:58:47 -03:00
|
|
|
}
|
2019-12-03 15:27:00 -03:00
|
|
|
|
|
|
|
// Save all grid settings that are unset
|
|
|
|
m_docPref.grid.forceSection();
|
2017-11-08 12:58:47 -03:00
|
|
|
}
|
2019-12-03 15:27:00 -03:00
|
|
|
m_docPref.show.grid.forceDirtyFlag();
|
2017-11-08 12:58:47 -03:00
|
|
|
}
|
|
|
|
}
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
|
2017-11-07 16:07:58 -03:00
|
|
|
void Editor::drawBackground(ui::Graphics* g)
|
|
|
|
{
|
|
|
|
if (!(m_flags & kShowOutside))
|
|
|
|
return;
|
|
|
|
|
|
|
|
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
|
|
|
|
|
|
|
|
gfx::Size canvas = canvasSize();
|
|
|
|
gfx::Rect rc(0, 0, canvas.w, canvas.h);
|
|
|
|
rc = editorToScreen(rc);
|
|
|
|
rc.offset(-bounds().origin());
|
|
|
|
|
|
|
|
// Fill the outside (parts of the editor that aren't covered by the
|
|
|
|
// sprite).
|
|
|
|
gfx::Region outside(clientBounds());
|
|
|
|
outside.createSubtraction(outside, gfx::Region(rc));
|
|
|
|
g->fillRegion(theme->colors.editorFace(), outside);
|
|
|
|
|
|
|
|
// Draw the borders that enclose the sprite.
|
|
|
|
rc.enlarge(1);
|
|
|
|
g->drawRect(theme->colors.editorSpriteBorder(), rc);
|
|
|
|
g->drawHLine(theme->colors.editorSpriteBottomBorder(), rc.x, rc.y2(), rc.w);
|
|
|
|
}
|
|
|
|
|
2014-12-08 14:57:56 -03:00
|
|
|
void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc)
|
2014-02-06 20:22:52 -03:00
|
|
|
{
|
2014-12-08 14:57:56 -03:00
|
|
|
gfx::Rect rc = _rc;
|
|
|
|
// For odd zoom scales minor than 100% we have to add an extra window
|
|
|
|
// just to make sure the whole rectangle is drawn.
|
2016-05-23 19:22:02 -03:00
|
|
|
if (m_proj.scaleX() < 1.0) rc.w += int(1./m_proj.scaleX());
|
|
|
|
if (m_proj.scaleY() < 1.0) rc.h += int(1./m_proj.scaleY());
|
2014-12-08 14:57:56 -03:00
|
|
|
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
gfx::Rect client = clientBounds();
|
2014-02-06 20:22:52 -03:00
|
|
|
gfx::Rect spriteRect(
|
2015-08-04 12:26:58 -03:00
|
|
|
client.x + m_padding.x,
|
|
|
|
client.y + m_padding.y,
|
2016-05-23 19:22:02 -03:00
|
|
|
m_proj.applyX(m_sprite->width()),
|
|
|
|
m_proj.applyY(m_sprite->height()));
|
2014-02-06 20:22:52 -03:00
|
|
|
gfx::Rect enclosingRect = spriteRect;
|
|
|
|
|
|
|
|
// Draw the main sprite at the center.
|
|
|
|
drawOneSpriteUnclippedRect(g, rc, 0, 0);
|
|
|
|
|
2015-05-18 16:53:25 -03:00
|
|
|
// Document preferences
|
2016-03-24 13:04:35 -03:00
|
|
|
if (int(m_docPref.tiled.mode()) & int(filters::TiledMode::X_AXIS)) {
|
2017-11-07 15:21:03 -03:00
|
|
|
drawOneSpriteUnclippedRect(g, rc, spriteRect.w, 0);
|
|
|
|
drawOneSpriteUnclippedRect(g, rc, spriteRect.w*2, 0);
|
2014-02-06 20:22:52 -03:00
|
|
|
|
2017-11-07 15:21:03 -03:00
|
|
|
enclosingRect = gfx::Rect(spriteRect.x, spriteRect.y, spriteRect.w*3, spriteRect.h);
|
2014-02-06 20:22:52 -03:00
|
|
|
}
|
|
|
|
|
2016-03-24 13:04:35 -03:00
|
|
|
if (int(m_docPref.tiled.mode()) & int(filters::TiledMode::Y_AXIS)) {
|
2017-11-07 15:21:03 -03:00
|
|
|
drawOneSpriteUnclippedRect(g, rc, 0, spriteRect.h);
|
|
|
|
drawOneSpriteUnclippedRect(g, rc, 0, spriteRect.h*2);
|
2014-02-06 20:22:52 -03:00
|
|
|
|
2017-11-07 15:21:03 -03:00
|
|
|
enclosingRect = gfx::Rect(spriteRect.x, spriteRect.y, spriteRect.w, spriteRect.h*3);
|
2014-02-06 20:22:52 -03:00
|
|
|
}
|
|
|
|
|
2016-03-24 13:04:35 -03:00
|
|
|
if (m_docPref.tiled.mode() == filters::TiledMode::BOTH) {
|
2017-11-07 15:21:03 -03:00
|
|
|
drawOneSpriteUnclippedRect(g, rc, spriteRect.w, spriteRect.h);
|
|
|
|
drawOneSpriteUnclippedRect(g, rc, spriteRect.w*2, spriteRect.h);
|
|
|
|
drawOneSpriteUnclippedRect(g, rc, spriteRect.w, spriteRect.h*2);
|
|
|
|
drawOneSpriteUnclippedRect(g, rc, spriteRect.w*2, spriteRect.h*2);
|
2014-02-06 20:22:52 -03:00
|
|
|
|
|
|
|
enclosingRect = gfx::Rect(
|
2017-11-07 15:21:03 -03:00
|
|
|
spriteRect.x, spriteRect.y,
|
|
|
|
spriteRect.w*3, spriteRect.h*3);
|
2014-08-09 17:14:56 -03:00
|
|
|
}
|
2014-02-06 20:22:52 -03:00
|
|
|
|
2017-11-08 12:58:47 -03:00
|
|
|
// Draw slices
|
2017-11-09 15:08:09 -03:00
|
|
|
if (m_docPref.show.slices())
|
|
|
|
drawSlices(g);
|
2014-03-03 00:09:14 -03:00
|
|
|
|
2015-10-26 17:51:32 -03:00
|
|
|
// Symmetry mode
|
2015-11-16 10:06:28 -03:00
|
|
|
if (isActive() &&
|
|
|
|
(m_flags & Editor::kShowSymmetryLine) &&
|
|
|
|
Preferences::instance().symmetryMode.enabled()) {
|
2017-03-27 15:27:37 -03:00
|
|
|
int mode = int(m_docPref.symmetry.mode());
|
|
|
|
if (mode & int(app::gen::SymmetryMode::HORIZONTAL)) {
|
|
|
|
double x = m_docPref.symmetry.xAxis();
|
|
|
|
if (x > 0) {
|
|
|
|
gfx::Color color = color_utils::color_for_ui(m_docPref.grid.color());
|
|
|
|
g->drawVLine(color,
|
2017-11-09 18:19:18 -03:00
|
|
|
spriteRect.x + m_proj.applyX(mainTilePosition().x) + int(m_proj.applyX<double>(x)),
|
|
|
|
enclosingRect.y,
|
2017-03-27 15:27:37 -03:00
|
|
|
enclosingRect.h);
|
2015-10-26 17:51:32 -03:00
|
|
|
}
|
2017-03-27 15:27:37 -03:00
|
|
|
}
|
|
|
|
if (mode & int(app::gen::SymmetryMode::VERTICAL)) {
|
|
|
|
double y = m_docPref.symmetry.yAxis();
|
|
|
|
if (y > 0) {
|
|
|
|
gfx::Color color = color_utils::color_for_ui(m_docPref.grid.color());
|
|
|
|
g->drawHLine(color,
|
2017-11-09 18:19:18 -03:00
|
|
|
enclosingRect.x,
|
|
|
|
spriteRect.y + m_proj.applyY(mainTilePosition().y) + int(m_proj.applyY<double>(y)),
|
2017-03-27 15:27:37 -03:00
|
|
|
enclosingRect.w);
|
2015-10-26 17:51:32 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-23 00:57:21 -03:00
|
|
|
// Draw active layer/cel edges
|
2020-08-04 16:29:22 -03:00
|
|
|
if ((m_docPref.show.layerEdges() || m_showAutoCelGuides) &&
|
2016-05-04 12:32:39 -03:00
|
|
|
// Show layer edges only on "standby" like states where brush
|
|
|
|
// preview is shown (e.g. with this we avoid to showing the
|
|
|
|
// edges in states like DrawingState, etc.).
|
|
|
|
m_state->requireBrushPreview()) {
|
2016-05-03 16:23:38 -03:00
|
|
|
Cel* cel = (m_layer ? m_layer->cel(m_frame): nullptr);
|
|
|
|
if (cel) {
|
2020-10-13 18:12:29 -03:00
|
|
|
gfx::Color color = color_utils::color_for_ui(Preferences::instance().guides.layerEdgesColor());
|
|
|
|
drawCelBounds(g, cel, color);
|
|
|
|
|
|
|
|
// Draw tile numbers
|
|
|
|
if (m_docPref.show.tileNumbers() &&
|
|
|
|
cel->layer()->isTilemap()) {
|
2020-10-16 16:54:14 -03:00
|
|
|
drawTileNumbers(g, cel);
|
2020-10-13 18:12:29 -03:00
|
|
|
}
|
2017-03-23 20:23:25 -03:00
|
|
|
|
2020-10-16 16:54:14 -03:00
|
|
|
// Draw auto-guides to other cel
|
2020-08-04 16:29:22 -03:00
|
|
|
if (m_showAutoCelGuides &&
|
|
|
|
m_showGuidesThisCel != cel) {
|
2017-03-23 00:57:21 -03:00
|
|
|
drawCelGuides(g, cel, m_showGuidesThisCel);
|
2020-08-04 16:29:22 -03:00
|
|
|
}
|
2016-05-03 16:23:38 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-03 00:09:14 -03:00
|
|
|
// Draw the mask
|
2020-05-18 17:58:22 -03:00
|
|
|
if (m_document->hasMaskBoundaries())
|
2014-03-03 00:09:14 -03:00
|
|
|
drawMask(g);
|
|
|
|
|
|
|
|
// Post-render decorator.
|
2014-08-09 17:14:56 -03:00
|
|
|
if ((m_flags & kShowDecorators) && m_decorator) {
|
2014-08-31 14:17:49 -03:00
|
|
|
EditorPostRenderImpl postRender(this, g);
|
2014-03-03 00:09:14 -03:00
|
|
|
m_decorator->postRenderDecorator(&postRender);
|
|
|
|
}
|
2014-02-06 20:22:52 -03:00
|
|
|
}
|
|
|
|
|
2013-01-20 18:40:37 -03:00
|
|
|
void Editor::drawSpriteClipped(const gfx::Region& updateRegion)
|
2008-04-28 16:32:45 +00:00
|
|
|
{
|
2014-11-24 19:13:25 -03:00
|
|
|
Region screenRegion;
|
|
|
|
getDrawableRegion(screenRegion, kCutTopWindows);
|
2010-01-27 02:52:30 +00:00
|
|
|
|
2021-02-18 12:30:14 -03:00
|
|
|
ScreenGraphics screenGraphics(display());
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
GraphicsPtr editorGraphics = getGraphics(clientBounds());
|
2013-01-13 22:39:44 -03:00
|
|
|
|
2014-11-24 19:13:25 -03:00
|
|
|
for (const Rect& updateRect : updateRegion) {
|
|
|
|
for (const Rect& screenRect : screenRegion) {
|
2017-11-07 16:07:58 -03:00
|
|
|
IntersectClip clip(&screenGraphics, screenRect);
|
2014-11-24 19:13:25 -03:00
|
|
|
if (clip)
|
2015-04-02 20:42:43 -03:00
|
|
|
drawSpriteUnclippedRect(editorGraphics.get(), updateRect);
|
2013-01-20 18:40:37 -03:00
|
|
|
}
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Draws the boundaries, really this routine doesn't use the "mask"
|
|
|
|
* field of the sprite, only the "bound" field (so you can have other
|
|
|
|
* mask in the sprite and could be showed other boundaries), to
|
|
|
|
* regenerate boundaries, use the sprite_generate_mask_boundaries()
|
|
|
|
* routine.
|
|
|
|
*/
|
2014-03-03 00:09:14 -03:00
|
|
|
void Editor::drawMask(Graphics* g)
|
2008-04-28 16:32:45 +00:00
|
|
|
{
|
2016-03-24 13:04:35 -03:00
|
|
|
if ((m_flags & kShowMask) == 0 ||
|
|
|
|
!m_docPref.show.selectionEdges())
|
2014-02-02 19:14:27 -03:00
|
|
|
return;
|
|
|
|
|
2020-05-18 17:58:22 -03:00
|
|
|
ASSERT(m_document->hasMaskBoundaries());
|
2015-06-11 17:44:27 -03:00
|
|
|
|
2017-11-08 15:59:33 -03:00
|
|
|
gfx::Point pt = mainTilePosition();
|
|
|
|
pt.x = m_padding.x + m_proj.applyX(pt.x);
|
|
|
|
pt.y = m_padding.y + m_proj.applyY(pt.y);
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2020-05-18 17:58:22 -03:00
|
|
|
// Create the mask boundaries path
|
|
|
|
auto& segs = m_document->maskBoundaries();
|
|
|
|
segs.createPathIfNeeeded();
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2020-05-18 17:58:22 -03:00
|
|
|
CheckedDrawMode checked(g, m_antsOffset,
|
|
|
|
gfx::rgba(0, 0, 0, 255),
|
|
|
|
gfx::rgba(255, 255, 255, 255));
|
|
|
|
os::Paint paint;
|
|
|
|
paint.style(os::Paint::Stroke);
|
|
|
|
paint.color(gfx::rgba(0, 0, 0));
|
2020-05-19 09:27:24 -03:00
|
|
|
|
|
|
|
// We translate the path instead of applying a matrix to the
|
|
|
|
// ui::Graphics so the "checked" pattern is not scaled too.
|
|
|
|
gfx::Path path;
|
|
|
|
segs.path().transform(m_proj.scaleMatrix(), &path);
|
|
|
|
path.offset(pt.x, pt.y);
|
|
|
|
g->drawPath(path, paint);
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
|
2011-04-01 22:23:38 -03:00
|
|
|
void Editor::drawMaskSafe()
|
2008-04-28 16:32:45 +00:00
|
|
|
{
|
2014-08-09 17:14:56 -03:00
|
|
|
if ((m_flags & kShowMask) == 0)
|
2014-02-02 19:14:27 -03:00
|
|
|
return;
|
|
|
|
|
2011-04-06 22:09:01 -03:00
|
|
|
if (isVisible() &&
|
|
|
|
m_document &&
|
2020-05-18 17:58:22 -03:00
|
|
|
m_document->hasMaskBoundaries()) {
|
2013-01-13 22:39:44 -03:00
|
|
|
Region region;
|
|
|
|
getDrawableRegion(region, kCutTopWindows);
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
region.offset(-bounds().origin());
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2015-06-25 12:44:47 -03:00
|
|
|
HideBrushPreview hide(m_brushPreview);
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
GraphicsPtr g = getGraphics(clientBounds());
|
2014-03-03 00:09:14 -03:00
|
|
|
|
2014-11-24 19:13:25 -03:00
|
|
|
for (const gfx::Rect& rc : region) {
|
2015-04-02 20:42:43 -03:00
|
|
|
IntersectClip clip(g.get(), rc);
|
2014-03-03 00:09:14 -03:00
|
|
|
if (clip)
|
2015-04-02 20:42:43 -03:00
|
|
|
drawMask(g.get());
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-24 19:13:25 -03:00
|
|
|
void Editor::drawGrid(Graphics* g, const gfx::Rect& spriteBounds, const Rect& gridBounds, const app::Color& color, int alpha)
|
2008-04-28 16:32:45 +00:00
|
|
|
{
|
2014-08-09 17:14:56 -03:00
|
|
|
if ((m_flags & kShowGrid) == 0)
|
2014-02-02 19:14:27 -03:00
|
|
|
return;
|
|
|
|
|
2011-04-20 23:17:05 -03:00
|
|
|
// Copy the grid bounds
|
2010-04-28 23:44:32 -03:00
|
|
|
Rect grid(gridBounds);
|
- All tools stuff refactored in various files/components.
- Added classes: IToolLoop, Tool, ToolGroup, ToolInk, ToolController, ToolPointShape, ToolIntertwine, ToolBox, etc.
- Added ToolLoopManager.
- Removed old src/modules/tools.cpp.
- Added ISettings and UISettingsImpl, adding the tools settings (onion skinning, grid, tiled mode, etc.).
- Added App::PenSizeBeforeChange, PenSizeAfterChange, CurrentToolChange signals.
- Renamed Context::get_bg/fg_color to getBg/FgColor.
- Refactored Brush class to Pen and added PenType.
- Renamed tiled_t to TiledMode.
- get_config_rect now uses the new Rect class imported from Vaca instead of old jrect.
- Added default_skin.xml to load tool icons.
- Added pen preview in Editor::cursor stuff.
- Added Editor::decorators.
Note: This big patch is from some time ago. I did my best to pre-commit other small changes before this big one.
2010-03-07 17:47:45 -02:00
|
|
|
if (grid.w < 1 || grid.h < 1)
|
|
|
|
return;
|
|
|
|
|
2011-04-20 23:17:05 -03:00
|
|
|
// Move the grid bounds to a non-negative position.
|
|
|
|
if (grid.x < 0) grid.x += (ABS(grid.x)/grid.w+1) * grid.w;
|
|
|
|
if (grid.y < 0) grid.y += (ABS(grid.y)/grid.h+1) * grid.h;
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2011-04-20 23:17:05 -03:00
|
|
|
// Change the grid position to the first grid's tile
|
- All tools stuff refactored in various files/components.
- Added classes: IToolLoop, Tool, ToolGroup, ToolInk, ToolController, ToolPointShape, ToolIntertwine, ToolBox, etc.
- Added ToolLoopManager.
- Removed old src/modules/tools.cpp.
- Added ISettings and UISettingsImpl, adding the tools settings (onion skinning, grid, tiled mode, etc.).
- Added App::PenSizeBeforeChange, PenSizeAfterChange, CurrentToolChange signals.
- Renamed Context::get_bg/fg_color to getBg/FgColor.
- Refactored Brush class to Pen and added PenType.
- Renamed tiled_t to TiledMode.
- get_config_rect now uses the new Rect class imported from Vaca instead of old jrect.
- Added default_skin.xml to load tool icons.
- Added pen preview in Editor::cursor stuff.
- Added Editor::decorators.
Note: This big patch is from some time ago. I did my best to pre-commit other small changes before this big one.
2010-03-07 17:47:45 -02:00
|
|
|
grid.setOrigin(Point((grid.x % grid.w) - grid.w,
|
2012-01-05 19:45:03 -03:00
|
|
|
(grid.y % grid.h) - grid.h));
|
2011-04-20 23:17:05 -03:00
|
|
|
if (grid.x < 0) grid.x += grid.w;
|
|
|
|
if (grid.y < 0) grid.y += grid.h;
|
|
|
|
|
|
|
|
// Convert the "grid" rectangle to screen coordinates
|
2014-11-24 00:09:22 -03:00
|
|
|
grid = editorToScreen(grid);
|
2014-11-27 23:48:53 -03:00
|
|
|
if (grid.w < 1 || grid.h < 1)
|
|
|
|
return;
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2014-03-03 00:09:14 -03:00
|
|
|
// Adjust for client area
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
gfx::Rect bounds = this->bounds();
|
|
|
|
grid.offset(-bounds.origin());
|
2014-03-03 00:09:14 -03:00
|
|
|
|
|
|
|
while (grid.x-grid.w >= spriteBounds.x) grid.x -= grid.w;
|
|
|
|
while (grid.y-grid.h >= spriteBounds.y) grid.y -= grid.h;
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2014-03-03 00:09:14 -03:00
|
|
|
// Get the grid's color
|
2014-06-28 16:10:39 -03:00
|
|
|
gfx::Color grid_color = color_utils::color_for_ui(color);
|
2014-11-24 19:13:25 -03:00
|
|
|
grid_color = gfx::rgba(
|
|
|
|
gfx::getr(grid_color),
|
|
|
|
gfx::getg(grid_color),
|
|
|
|
gfx::getb(grid_color), alpha);
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2011-04-20 23:17:05 -03:00
|
|
|
// Draw horizontal lines
|
2014-03-03 00:09:14 -03:00
|
|
|
int x1 = spriteBounds.x;
|
2011-04-20 23:17:05 -03:00
|
|
|
int y1 = grid.y;
|
2014-03-03 00:09:14 -03:00
|
|
|
int x2 = spriteBounds.x + spriteBounds.w;
|
|
|
|
int y2 = spriteBounds.y + spriteBounds.h;
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2011-04-20 23:17:05 -03:00
|
|
|
for (int c=y1; c<=y2; c+=grid.h)
|
2014-03-03 00:09:14 -03:00
|
|
|
g->drawHLine(grid_color, x1, c, spriteBounds.w);
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2011-04-20 23:17:05 -03:00
|
|
|
// Draw vertical lines
|
|
|
|
x1 = grid.x;
|
2014-03-03 00:09:14 -03:00
|
|
|
y1 = spriteBounds.y;
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2011-04-20 23:17:05 -03:00
|
|
|
for (int c=x1; c<=x2; c+=grid.w)
|
2014-03-03 00:09:14 -03:00
|
|
|
g->drawVLine(grid_color, c, y1, spriteBounds.h);
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
|
2017-03-06 19:27:43 -03:00
|
|
|
void Editor::drawSlices(ui::Graphics* g)
|
|
|
|
{
|
|
|
|
if ((m_flags & kShowSlices) == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!isVisible() || !m_document)
|
|
|
|
return;
|
|
|
|
|
2019-05-02 16:26:13 -03:00
|
|
|
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
|
2017-11-09 15:08:09 -03:00
|
|
|
gfx::Point mainOffset(mainTilePosition());
|
|
|
|
|
2017-03-06 19:27:43 -03:00
|
|
|
for (auto slice : m_sprite->slices()) {
|
|
|
|
auto key = slice->getByFrame(m_frame);
|
|
|
|
if (!key)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
doc::color_t docColor = slice->userData().color();
|
|
|
|
gfx::Color color = gfx::rgba(doc::rgba_getr(docColor),
|
|
|
|
doc::rgba_getg(docColor),
|
|
|
|
doc::rgba_getb(docColor),
|
|
|
|
doc::rgba_geta(docColor));
|
2017-11-09 15:08:09 -03:00
|
|
|
gfx::Rect out = key->bounds();
|
|
|
|
out.offset(mainOffset);
|
|
|
|
out = editorToScreen(out);
|
|
|
|
out.offset(-bounds().origin());
|
2017-03-06 19:27:43 -03:00
|
|
|
|
2017-03-14 19:00:24 -03:00
|
|
|
// Center slices
|
2017-03-16 10:35:26 -03:00
|
|
|
if (key->hasCenter()) {
|
2017-03-06 19:27:43 -03:00
|
|
|
gfx::Rect in =
|
|
|
|
editorToScreen(gfx::Rect(key->center()).offset(key->bounds().origin()))
|
|
|
|
.offset(-bounds().origin());
|
|
|
|
|
|
|
|
auto in_color = gfx::rgba(gfx::getr(color),
|
|
|
|
gfx::getg(color),
|
|
|
|
gfx::getb(color),
|
|
|
|
doc::rgba_geta(docColor)/4);
|
|
|
|
if (in.y > out.y && in.y < out.y2())
|
|
|
|
g->drawHLine(in_color, out.x, in.y, out.w);
|
|
|
|
if (in.y2() > out.y && in.y2() < out.y2())
|
|
|
|
g->drawHLine(in_color, out.x, in.y2(), out.w);
|
|
|
|
if (in.x > out.x && in.x < out.x2())
|
|
|
|
g->drawVLine(in_color, in.x, out.y, out.h);
|
|
|
|
if (in.x2() > out.x && in.x2() < out.x2())
|
|
|
|
g->drawVLine(in_color, in.x2(), out.y, out.h);
|
|
|
|
}
|
|
|
|
|
2017-03-14 19:00:24 -03:00
|
|
|
// Pivot
|
|
|
|
if (key->hasPivot()) {
|
|
|
|
gfx::Rect in =
|
|
|
|
editorToScreen(gfx::Rect(key->pivot(), gfx::Size(1, 1)).offset(key->bounds().origin()))
|
|
|
|
.offset(-bounds().origin());
|
|
|
|
|
|
|
|
auto in_color = gfx::rgba(gfx::getr(color),
|
|
|
|
gfx::getg(color),
|
|
|
|
gfx::getb(color),
|
|
|
|
doc::rgba_geta(docColor)/4);
|
|
|
|
g->drawRect(in_color, in);
|
|
|
|
}
|
|
|
|
|
2019-05-02 16:26:13 -03:00
|
|
|
if (isSliceSelected(slice) &&
|
|
|
|
getCurrentEditorInk()->isSlice()) {
|
|
|
|
PaintWidgetPartInfo info;
|
|
|
|
theme->paintWidgetPart(
|
|
|
|
g, theme->styles.colorbarSelection(), out, info);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
g->drawRect(color, out);
|
|
|
|
}
|
2017-03-06 19:27:43 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-16 16:54:14 -03:00
|
|
|
void Editor::drawTileNumbers(ui::Graphics* g, const Cel* cel)
|
|
|
|
{
|
|
|
|
gfx::Color color = color_utils::color_for_ui(Preferences::instance().guides.autoGuidesColor());
|
|
|
|
gfx::Color fgColor = color_utils::blackandwhite_neg(color);
|
|
|
|
|
|
|
|
const doc::Grid grid = getSite().grid();
|
|
|
|
const gfx::Size tileSize = editorToScreen(grid.tileToCanvas(gfx::Rect(0, 0, 1, 1))).size();
|
|
|
|
if (tileSize.h > g->font()->height()) {
|
2020-10-16 17:05:38 -03:00
|
|
|
const gfx::Point offset =
|
|
|
|
gfx::Point(tileSize.w/2,
|
|
|
|
tileSize.h/2 - g->font()->height()/2)
|
|
|
|
+ mainTilePosition();
|
|
|
|
|
2020-10-30 16:33:34 -03:00
|
|
|
int ti_offset =
|
2020-10-30 20:57:36 -03:00
|
|
|
static_cast<LayerTilemap*>(cel->layer())->tileset()->baseIndex() - 1;
|
2020-10-30 16:33:34 -03:00
|
|
|
|
2020-10-16 16:54:14 -03:00
|
|
|
const doc::Image* image = cel->image();
|
|
|
|
std::string text;
|
|
|
|
for (int y=0; y<image->height(); ++y) {
|
|
|
|
for (int x=0; x<image->width(); ++x) {
|
|
|
|
doc::tile_t t = image->getPixel(x, y);
|
2020-10-30 16:33:34 -03:00
|
|
|
if (t != doc::notile) {
|
2020-10-16 16:54:14 -03:00
|
|
|
gfx::Point pt = editorToScreen(grid.tileToCanvas(gfx::Point(x, y)));
|
|
|
|
pt -= bounds().origin();
|
|
|
|
pt += offset;
|
|
|
|
|
2020-10-30 16:33:34 -03:00
|
|
|
text = fmt::format("{}", int(t & doc::tile_i_mask) + ti_offset);
|
2020-10-16 16:54:14 -03:00
|
|
|
pt.x -= g->measureUIText(text).w/2;
|
|
|
|
g->drawText(text, fgColor, color, pt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-23 20:23:25 -03:00
|
|
|
void Editor::drawCelBounds(ui::Graphics* g, const Cel* cel, const gfx::Color color)
|
2017-03-23 00:57:21 -03:00
|
|
|
{
|
2017-03-23 20:23:25 -03:00
|
|
|
g->drawRect(color, getCelScreenBounds(cel));
|
2017-03-23 00:57:21 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::drawCelGuides(ui::Graphics* g, const Cel* cel, const Cel* mouseCel)
|
|
|
|
{
|
|
|
|
gfx::Rect
|
|
|
|
sprCelBounds = cel->bounds(),
|
|
|
|
scrCelBounds = getCelScreenBounds(cel),
|
|
|
|
scrCmpBounds, sprCmpBounds;
|
|
|
|
if (mouseCel) {
|
|
|
|
scrCmpBounds = getCelScreenBounds(mouseCel);
|
|
|
|
sprCmpBounds = mouseCel->bounds();
|
2017-03-23 20:23:25 -03:00
|
|
|
|
2020-10-13 18:12:29 -03:00
|
|
|
const gfx::Color color = color_utils::color_for_ui(Preferences::instance().guides.autoGuidesColor());
|
|
|
|
drawCelBounds(g, mouseCel, color);
|
2017-03-23 00:57:21 -03:00
|
|
|
}
|
|
|
|
// Use whole canvas
|
|
|
|
else {
|
|
|
|
sprCmpBounds = m_sprite->bounds();
|
2017-11-09 16:06:22 -03:00
|
|
|
scrCmpBounds =
|
|
|
|
editorToScreen(
|
|
|
|
gfx::Rect(sprCmpBounds).offset(mainTilePosition()))
|
|
|
|
.offset(gfx::Point(-bounds().origin()));
|
2017-03-23 00:57:21 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
const int midX = scrCelBounds.x+scrCelBounds.w/2;
|
|
|
|
const int midY = scrCelBounds.y+scrCelBounds.h/2;
|
|
|
|
|
|
|
|
if (sprCelBounds.x2() < sprCmpBounds.x) {
|
|
|
|
drawCelHGuide(g,
|
|
|
|
sprCelBounds.x2(), sprCmpBounds.x,
|
|
|
|
scrCelBounds.x2(), scrCmpBounds.x, midY,
|
|
|
|
scrCelBounds, scrCmpBounds, scrCmpBounds.x);
|
|
|
|
}
|
|
|
|
else if (sprCelBounds.x > sprCmpBounds.x2()) {
|
|
|
|
drawCelHGuide(g,
|
|
|
|
sprCmpBounds.x2(), sprCelBounds.x,
|
|
|
|
scrCmpBounds.x2(), scrCelBounds.x, midY,
|
|
|
|
scrCelBounds, scrCmpBounds, scrCmpBounds.x2()-1);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (sprCelBounds.x != sprCmpBounds.x &&
|
|
|
|
sprCelBounds.x2() != sprCmpBounds.x) {
|
|
|
|
drawCelHGuide(g,
|
|
|
|
sprCmpBounds.x, sprCelBounds.x,
|
|
|
|
scrCmpBounds.x, scrCelBounds.x, midY,
|
|
|
|
scrCelBounds, scrCmpBounds, scrCmpBounds.x);
|
|
|
|
}
|
|
|
|
if (sprCelBounds.x != sprCmpBounds.x2() &&
|
|
|
|
sprCelBounds.x2() != sprCmpBounds.x2()) {
|
|
|
|
drawCelHGuide(g,
|
|
|
|
sprCmpBounds.x2(), sprCelBounds.x2(),
|
|
|
|
scrCmpBounds.x2(), scrCelBounds.x2(), midY,
|
|
|
|
scrCelBounds, scrCmpBounds, scrCmpBounds.x2()-1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sprCelBounds.y2() < sprCmpBounds.y) {
|
|
|
|
drawCelVGuide(g,
|
|
|
|
sprCelBounds.y2(), sprCmpBounds.y,
|
|
|
|
scrCelBounds.y2(), scrCmpBounds.y, midX,
|
|
|
|
scrCelBounds, scrCmpBounds, scrCmpBounds.y);
|
|
|
|
}
|
|
|
|
else if (sprCelBounds.y > sprCmpBounds.y2()) {
|
|
|
|
drawCelVGuide(g,
|
|
|
|
sprCmpBounds.y2(), sprCelBounds.y,
|
|
|
|
scrCmpBounds.y2(), scrCelBounds.y, midX,
|
|
|
|
scrCelBounds, scrCmpBounds, scrCmpBounds.y2()-1);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (sprCelBounds.y != sprCmpBounds.y &&
|
|
|
|
sprCelBounds.y2() != sprCmpBounds.y) {
|
|
|
|
drawCelVGuide(g,
|
|
|
|
sprCmpBounds.y, sprCelBounds.y,
|
|
|
|
scrCmpBounds.y, scrCelBounds.y, midX,
|
|
|
|
scrCelBounds, scrCmpBounds, scrCmpBounds.y);
|
|
|
|
}
|
|
|
|
if (sprCelBounds.y != sprCmpBounds.y2() &&
|
|
|
|
sprCelBounds.y2() != sprCmpBounds.y2()) {
|
|
|
|
drawCelVGuide(g,
|
|
|
|
sprCmpBounds.y2(), sprCelBounds.y2(),
|
|
|
|
scrCmpBounds.y2(), scrCelBounds.y2(), midX,
|
|
|
|
scrCelBounds, scrCmpBounds, scrCmpBounds.y2()-1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::drawCelHGuide(ui::Graphics* g,
|
|
|
|
const int sprX1, const int sprX2,
|
|
|
|
const int scrX1, const int scrX2, const int scrY,
|
|
|
|
const gfx::Rect& scrCelBounds, const gfx::Rect& scrCmpBounds,
|
|
|
|
const int dottedX)
|
|
|
|
{
|
2017-03-25 01:01:59 -03:00
|
|
|
gfx::Color color = color_utils::color_for_ui(Preferences::instance().guides.autoGuidesColor());
|
2020-07-21 16:56:23 -03:00
|
|
|
g->drawHLine(color, std::min(scrX1, scrX2), scrY, std::abs(scrX2 - scrX1));
|
2017-03-23 00:57:21 -03:00
|
|
|
|
|
|
|
// Vertical guide to touch the horizontal line
|
|
|
|
{
|
|
|
|
CheckedDrawMode checked(g, 0, color, gfx::ColorNone);
|
|
|
|
|
|
|
|
if (scrY < scrCmpBounds.y)
|
|
|
|
g->drawVLine(color, dottedX, scrCelBounds.y, scrCmpBounds.y - scrCelBounds.y);
|
|
|
|
else if (scrY > scrCmpBounds.y2())
|
|
|
|
g->drawVLine(color, dottedX, scrCmpBounds.y2(), scrCelBounds.y2() - scrCmpBounds.y2());
|
|
|
|
}
|
|
|
|
|
2020-07-21 16:01:06 -03:00
|
|
|
auto text = fmt::format("{}px", ABS(sprX2 - sprX1));
|
2017-03-23 00:57:21 -03:00
|
|
|
const int textW = Graphics::measureUITextLength(text, font());
|
|
|
|
g->drawText(text,
|
2017-03-23 20:23:25 -03:00
|
|
|
color_utils::blackandwhite_neg(color), color,
|
2017-03-23 00:57:21 -03:00
|
|
|
gfx::Point((scrX1+scrX2)/2-textW/2, scrY-textHeight()));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::drawCelVGuide(ui::Graphics* g,
|
|
|
|
const int sprY1, const int sprY2,
|
|
|
|
const int scrY1, const int scrY2, const int scrX,
|
|
|
|
const gfx::Rect& scrCelBounds, const gfx::Rect& scrCmpBounds,
|
|
|
|
const int dottedY)
|
|
|
|
{
|
2017-03-25 01:01:59 -03:00
|
|
|
gfx::Color color = color_utils::color_for_ui(Preferences::instance().guides.autoGuidesColor());
|
2020-07-21 16:56:23 -03:00
|
|
|
g->drawVLine(color, scrX, std::min(scrY1, scrY2), std::abs(scrY2 - scrY1));
|
2017-03-23 00:57:21 -03:00
|
|
|
|
|
|
|
// Horizontal guide to touch the vertical line
|
|
|
|
{
|
|
|
|
CheckedDrawMode checked(g, 0, color, gfx::ColorNone);
|
|
|
|
|
|
|
|
if (scrX < scrCmpBounds.x)
|
|
|
|
g->drawHLine(color, scrCelBounds.x, dottedY, scrCmpBounds.x - scrCelBounds.x);
|
|
|
|
else if (scrX > scrCmpBounds.x2())
|
|
|
|
g->drawHLine(color, scrCmpBounds.x2(), dottedY, scrCelBounds.x2() - scrCmpBounds.x2());
|
|
|
|
}
|
|
|
|
|
2020-07-21 16:01:06 -03:00
|
|
|
auto text = fmt::format("{}px", ABS(sprY2 - sprY1));
|
2017-03-23 00:57:21 -03:00
|
|
|
g->drawText(text,
|
2017-03-23 20:23:25 -03:00
|
|
|
color_utils::blackandwhite_neg(color), color,
|
2017-03-23 00:57:21 -03:00
|
|
|
gfx::Point(scrX, (scrY1+scrY2)/2-textHeight()/2));
|
|
|
|
}
|
|
|
|
|
|
|
|
gfx::Rect Editor::getCelScreenBounds(const Cel* cel)
|
|
|
|
{
|
2017-11-09 16:06:22 -03:00
|
|
|
gfx::Point mainOffset(mainTilePosition());
|
2017-03-23 00:57:21 -03:00
|
|
|
gfx::Rect layerEdges;
|
|
|
|
if (m_layer->isReference()) {
|
2017-11-09 16:06:22 -03:00
|
|
|
layerEdges =
|
|
|
|
editorToScreenF(
|
|
|
|
gfx::RectF(cel->boundsF()).offset(mainOffset.x,
|
|
|
|
mainOffset.y))
|
|
|
|
.offset(gfx::PointF(-bounds().origin()));
|
2017-03-23 00:57:21 -03:00
|
|
|
}
|
|
|
|
else {
|
2017-11-09 16:06:22 -03:00
|
|
|
layerEdges =
|
|
|
|
editorToScreen(
|
|
|
|
gfx::Rect(cel->bounds()).offset(mainOffset))
|
|
|
|
.offset(-bounds().origin());
|
2017-03-23 00:57:21 -03:00
|
|
|
}
|
|
|
|
return layerEdges;
|
|
|
|
}
|
|
|
|
|
2010-03-30 23:40:01 -03:00
|
|
|
void Editor::flashCurrentLayer()
|
|
|
|
{
|
2015-05-18 16:53:25 -03:00
|
|
|
if (!Preferences::instance().experimental.flashLayer())
|
2014-10-25 16:04:39 -03:00
|
|
|
return;
|
|
|
|
|
2015-04-20 16:27:09 -03:00
|
|
|
Site site = getSite();
|
2020-09-18 19:29:43 -03:00
|
|
|
if (site.cel()) {
|
2019-02-18 11:47:35 -03:00
|
|
|
// Hide and destroy the extra cel used by the brush preview
|
|
|
|
// because we'll need to use the extra cel now for the flashing
|
|
|
|
// layer.
|
|
|
|
m_brushPreview.hide();
|
2014-10-25 16:04:39 -03:00
|
|
|
|
2018-06-19 23:25:31 -03:00
|
|
|
m_renderEngine->removePreviewImage();
|
2014-11-08 17:03:29 -03:00
|
|
|
|
2015-08-19 10:03:29 -03:00
|
|
|
ExtraCelRef extraCel(new ExtraCel);
|
2019-11-08 13:27:00 -03:00
|
|
|
extraCel->setType(render::ExtraType::OVER_COMPOSITE);
|
2015-08-19 10:03:29 -03:00
|
|
|
extraCel->setBlendMode(BlendMode::NEG_BW);
|
2010-03-30 23:40:01 -03:00
|
|
|
|
2019-02-18 11:47:35 -03:00
|
|
|
m_document->setExtraCel(extraCel);
|
|
|
|
m_flashing = Flashing::WithFlashExtraCel;
|
2010-03-30 23:40:01 -03:00
|
|
|
|
2021-06-08 20:34:15 -03:00
|
|
|
invalidateCanvas();
|
2010-03-30 23:40:01 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-05 16:37:52 -03:00
|
|
|
gfx::Point Editor::autoScroll(MouseMessage* msg, AutoScroll dir)
|
2010-04-24 21:04:03 -03:00
|
|
|
{
|
2016-12-02 20:37:54 -03:00
|
|
|
gfx::Point mousePos = msg->position();
|
|
|
|
if (!Preferences::instance().editor.autoScroll())
|
|
|
|
return mousePos;
|
|
|
|
|
|
|
|
// Hide the brush preview
|
2017-04-19 19:25:57 -03:00
|
|
|
//HideBrushPreview hide(m_brushPreview);
|
2011-02-20 18:35:21 -03:00
|
|
|
View* view = View::getView(this);
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
gfx::Rect vp = view->viewportBounds();
|
2014-02-02 18:46:19 -03:00
|
|
|
|
2014-08-25 22:49:19 -03:00
|
|
|
if (!vp.contains(mousePos)) {
|
2014-08-25 23:30:27 -03:00
|
|
|
gfx::Point delta = (mousePos - m_oldPos);
|
2014-09-09 21:51:47 -03:00
|
|
|
gfx::Point deltaScroll = delta;
|
2014-08-25 23:30:27 -03:00
|
|
|
|
|
|
|
if (!((mousePos.x < vp.x && delta.x < 0) ||
|
2014-09-09 21:51:47 -03:00
|
|
|
(mousePos.x >= vp.x+vp.w && delta.x > 0))) {
|
2014-08-25 23:30:27 -03:00
|
|
|
delta.x = 0;
|
2014-09-09 21:51:47 -03:00
|
|
|
}
|
2014-08-25 23:30:27 -03:00
|
|
|
|
|
|
|
if (!((mousePos.y < vp.y && delta.y < 0) ||
|
2014-09-09 21:51:47 -03:00
|
|
|
(mousePos.y >= vp.y+vp.h && delta.y > 0))) {
|
2014-08-25 23:30:27 -03:00
|
|
|
delta.y = 0;
|
2014-09-09 21:51:47 -03:00
|
|
|
}
|
2014-08-25 23:30:27 -03:00
|
|
|
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
gfx::Point scroll = view->viewScroll();
|
2014-09-09 21:51:47 -03:00
|
|
|
if (dir == AutoScroll::MouseDir) {
|
|
|
|
scroll += delta;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
scroll -= deltaScroll;
|
|
|
|
}
|
2016-01-05 16:37:52 -03:00
|
|
|
setEditorScroll(scroll);
|
2014-08-25 22:49:19 -03:00
|
|
|
|
2014-09-09 21:51:47 -03:00
|
|
|
mousePos -= delta;
|
2021-03-02 13:50:49 -03:00
|
|
|
ui::set_mouse_position(mousePos,
|
|
|
|
display());
|
2014-09-09 21:51:47 -03:00
|
|
|
|
2014-08-25 22:49:19 -03:00
|
|
|
m_oldPos = mousePos;
|
|
|
|
mousePos = gfx::Point(
|
2020-04-07 19:47:16 -03:00
|
|
|
base::clamp(mousePos.x, vp.x, vp.x2()-1),
|
|
|
|
base::clamp(mousePos.y, vp.y, vp.y2()-1));
|
2010-04-24 21:04:03 -03:00
|
|
|
}
|
2014-08-25 22:49:19 -03:00
|
|
|
else
|
|
|
|
m_oldPos = mousePos;
|
2013-07-28 21:17:07 -03:00
|
|
|
|
2014-02-02 18:46:19 -03:00
|
|
|
return mousePos;
|
2010-04-24 21:04:03 -03:00
|
|
|
}
|
|
|
|
|
2019-05-07 10:28:37 -03:00
|
|
|
tools::Tool* Editor::getCurrentEditorTool() const
|
2010-10-27 16:42:34 -03:00
|
|
|
{
|
2016-04-25 15:20:53 -03:00
|
|
|
return App::instance()->activeTool();
|
2014-08-19 08:17:57 -03:00
|
|
|
}
|
|
|
|
|
2019-05-07 10:28:37 -03:00
|
|
|
tools::Ink* Editor::getCurrentEditorInk() const
|
2014-08-19 08:17:57 -03:00
|
|
|
{
|
2015-04-27 11:31:48 -03:00
|
|
|
tools::Ink* ink = m_state->getStateInk();
|
|
|
|
if (ink)
|
|
|
|
return ink;
|
2016-04-25 15:20:53 -03:00
|
|
|
else
|
|
|
|
return App::instance()->activeToolManager()->activeInk();
|
2010-10-27 16:42:34 -03:00
|
|
|
}
|
|
|
|
|
2018-10-26 10:01:25 -03:00
|
|
|
bool Editor::isAutoSelectLayer()
|
2016-11-29 18:36:08 -03:00
|
|
|
{
|
2018-10-26 10:01:25 -03:00
|
|
|
tools::Ink* ink = getCurrentEditorInk();
|
|
|
|
if (ink && ink->isAutoSelectLayer())
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
return App::instance()->contextBar()->isAutoSelectLayer();
|
2016-11-29 18:36:08 -03:00
|
|
|
}
|
|
|
|
|
2014-11-24 00:09:22 -03:00
|
|
|
gfx::Point Editor::screenToEditor(const gfx::Point& pt)
|
2008-04-28 16:32:45 +00:00
|
|
|
{
|
2011-02-20 18:35:21 -03:00
|
|
|
View* view = View::getView(this);
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
Rect vp = view->viewportBounds();
|
|
|
|
Point scroll = view->viewScroll();
|
2014-11-24 00:09:22 -03:00
|
|
|
return gfx::Point(
|
2016-05-23 19:22:02 -03:00
|
|
|
m_proj.removeX(pt.x - vp.x + scroll.x - m_padding.x),
|
|
|
|
m_proj.removeY(pt.y - vp.y + scroll.y - m_padding.y));
|
2011-04-20 23:17:05 -03:00
|
|
|
}
|
|
|
|
|
2016-10-10 23:42:47 -03:00
|
|
|
gfx::PointF Editor::screenToEditorF(const gfx::Point& pt)
|
|
|
|
{
|
|
|
|
View* view = View::getView(this);
|
|
|
|
Rect vp = view->viewportBounds();
|
|
|
|
Point scroll = view->viewScroll();
|
|
|
|
return gfx::PointF(
|
|
|
|
m_proj.removeX<double>(pt.x - vp.x + scroll.x - m_padding.x),
|
|
|
|
m_proj.removeY<double>(pt.y - vp.y + scroll.y - m_padding.y));
|
|
|
|
}
|
|
|
|
|
2014-11-24 00:09:22 -03:00
|
|
|
Point Editor::editorToScreen(const gfx::Point& pt)
|
2008-04-28 16:32:45 +00:00
|
|
|
{
|
2011-02-20 18:35:21 -03:00
|
|
|
View* view = View::getView(this);
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
Rect vp = view->viewportBounds();
|
|
|
|
Point scroll = view->viewScroll();
|
2014-11-24 00:09:22 -03:00
|
|
|
return Point(
|
2016-05-23 19:22:02 -03:00
|
|
|
(vp.x - scroll.x + m_padding.x + m_proj.applyX(pt.x)),
|
|
|
|
(vp.y - scroll.y + m_padding.y + m_proj.applyY(pt.y)));
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
|
2016-11-02 15:01:31 -03:00
|
|
|
gfx::PointF Editor::editorToScreenF(const gfx::PointF& pt)
|
|
|
|
{
|
|
|
|
View* view = View::getView(this);
|
|
|
|
Rect vp = view->viewportBounds();
|
|
|
|
Point scroll = view->viewScroll();
|
|
|
|
return PointF(
|
|
|
|
(vp.x - scroll.x + m_padding.x + m_proj.applyX<double>(pt.x)),
|
|
|
|
(vp.y - scroll.y + m_padding.y + m_proj.applyY<double>(pt.y)));
|
|
|
|
}
|
|
|
|
|
2014-11-24 00:09:22 -03:00
|
|
|
Rect Editor::screenToEditor(const Rect& rc)
|
2011-04-20 23:17:05 -03:00
|
|
|
{
|
2014-11-24 00:09:22 -03:00
|
|
|
return gfx::Rect(
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
screenToEditor(rc.origin()),
|
|
|
|
screenToEditor(rc.point2()));
|
2014-11-24 00:09:22 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
Rect Editor::editorToScreen(const Rect& rc)
|
|
|
|
{
|
|
|
|
return gfx::Rect(
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
editorToScreen(rc.origin()),
|
|
|
|
editorToScreen(rc.point2()));
|
2011-04-20 23:17:05 -03:00
|
|
|
}
|
|
|
|
|
2016-11-02 15:01:31 -03:00
|
|
|
gfx::RectF Editor::editorToScreenF(const gfx::RectF& rc)
|
|
|
|
{
|
|
|
|
return gfx::RectF(
|
|
|
|
editorToScreenF(rc.origin()),
|
|
|
|
editorToScreenF(rc.point2()));
|
|
|
|
}
|
|
|
|
|
2016-09-13 15:02:00 -03:00
|
|
|
void Editor::add_observer(EditorObserver* observer)
|
2011-04-02 15:47:44 -03:00
|
|
|
{
|
2016-09-13 15:02:00 -03:00
|
|
|
m_observers.add_observer(observer);
|
2011-04-02 15:47:44 -03:00
|
|
|
}
|
|
|
|
|
2016-09-13 15:02:00 -03:00
|
|
|
void Editor::remove_observer(EditorObserver* observer)
|
2011-04-02 15:47:44 -03:00
|
|
|
{
|
2016-09-13 15:02:00 -03:00
|
|
|
m_observers.remove_observer(observer);
|
2011-04-02 15:47:44 -03:00
|
|
|
}
|
|
|
|
|
2011-10-29 19:21:19 -03:00
|
|
|
void Editor::setCustomizationDelegate(EditorCustomizationDelegate* delegate)
|
|
|
|
{
|
|
|
|
if (m_customizationDelegate)
|
|
|
|
m_customizationDelegate->dispose();
|
|
|
|
|
|
|
|
m_customizationDelegate = delegate;
|
|
|
|
}
|
|
|
|
|
2017-06-23 07:01:12 -03:00
|
|
|
Rect Editor::getViewportBounds()
|
|
|
|
{
|
|
|
|
return screenToEditor(View::getView(this)->viewportBounds());
|
|
|
|
}
|
|
|
|
|
2011-04-02 18:12:41 -03:00
|
|
|
// Returns the visible area of the active sprite.
|
2011-04-21 17:39:43 -03:00
|
|
|
Rect Editor::getVisibleSpriteBounds()
|
2011-04-02 18:12:41 -03:00
|
|
|
{
|
2017-06-23 07:01:12 -03:00
|
|
|
if (m_sprite)
|
2019-09-05 17:56:55 -03:00
|
|
|
return getViewportBounds().createIntersection(gfx::Rect(canvasSize()));
|
2011-04-02 18:12:41 -03:00
|
|
|
|
2017-06-23 07:01:12 -03:00
|
|
|
// This cannot happen, the sprite must be != nullptr. In old
|
|
|
|
// Aseprite versions we were using one Editor to show multiple
|
|
|
|
// sprites (switching the sprite inside the editor). Now we have one
|
|
|
|
// (or more) editor(s) for each sprite.
|
|
|
|
ASSERT(false);
|
2011-04-02 18:12:41 -03:00
|
|
|
|
2017-06-23 07:01:12 -03:00
|
|
|
// Return an empty rectangle if there is not a active sprite.
|
|
|
|
return Rect();
|
2011-04-02 18:12:41 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Changes the scroll to see the given point as the center of the editor.
|
2014-11-24 00:09:22 -03:00
|
|
|
void Editor::centerInSpritePoint(const gfx::Point& spritePos)
|
2011-04-02 18:12:41 -03:00
|
|
|
{
|
2015-06-25 12:44:47 -03:00
|
|
|
HideBrushPreview hide(m_brushPreview);
|
2011-04-02 18:12:41 -03:00
|
|
|
View* view = View::getView(this);
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
Rect vp = view->viewportBounds();
|
2011-04-02 18:12:41 -03:00
|
|
|
|
2014-11-24 00:09:22 -03:00
|
|
|
gfx::Point scroll(
|
2016-05-23 19:22:02 -03:00
|
|
|
m_padding.x - (vp.w/2) + m_proj.applyX(1)/2 + m_proj.applyX(spritePos.x),
|
|
|
|
m_padding.y - (vp.h/2) + m_proj.applyY(1)/2 + m_proj.applyY(spritePos.y));
|
2011-04-02 18:12:41 -03:00
|
|
|
|
2020-01-13 15:01:45 -03:00
|
|
|
updateEditor(false);
|
2016-01-05 16:37:52 -03:00
|
|
|
setEditorScroll(scroll);
|
2011-04-02 18:12:41 -03:00
|
|
|
invalidate();
|
|
|
|
}
|
|
|
|
|
2011-04-10 20:15:17 -03:00
|
|
|
void Editor::updateStatusBar()
|
2010-07-23 23:27:46 -03:00
|
|
|
{
|
2015-03-18 12:54:05 -03:00
|
|
|
if (!hasMouse())
|
|
|
|
return;
|
|
|
|
|
2011-04-10 20:15:17 -03:00
|
|
|
// Setup status bar using the current editor's state
|
|
|
|
m_state->onUpdateStatusBar(this);
|
2010-07-23 23:27:46 -03:00
|
|
|
}
|
|
|
|
|
2014-08-24 19:19:38 -03:00
|
|
|
void Editor::updateQuicktool()
|
2010-10-27 21:02:48 -03:00
|
|
|
{
|
2016-04-04 18:46:48 -03:00
|
|
|
if (m_customizationDelegate && !hasCapture()) {
|
2019-01-09 15:04:25 -03:00
|
|
|
auto atm = App::instance()->activeToolManager();
|
|
|
|
tools::Tool* selectedTool = atm->selectedTool();
|
2010-10-27 21:02:48 -03:00
|
|
|
|
2014-08-24 19:19:38 -03:00
|
|
|
// Don't change quicktools if we are in a selection tool and using
|
|
|
|
// the selection modifiers.
|
2016-04-25 15:20:53 -03:00
|
|
|
if (selectedTool->getInk(0)->isSelection() &&
|
2019-01-09 15:04:25 -03:00
|
|
|
int(m_customizationDelegate->getPressedKeyAction(KeyContext::SelectionTool)) != 0) {
|
|
|
|
if (atm->quickTool())
|
|
|
|
atm->newQuickToolSelectedFromEditor(nullptr);
|
2015-08-25 13:29:19 -03:00
|
|
|
return;
|
2019-01-09 15:04:25 -03:00
|
|
|
}
|
2014-08-24 19:19:38 -03:00
|
|
|
|
2016-04-25 15:20:53 -03:00
|
|
|
tools::Tool* newQuicktool =
|
|
|
|
m_customizationDelegate->getQuickTool(selectedTool);
|
2014-11-15 19:57:03 -03:00
|
|
|
|
|
|
|
// Check if the current state accept the given quicktool.
|
2016-04-25 15:20:53 -03:00
|
|
|
if (newQuicktool && !m_state->acceptQuickTool(newQuicktool))
|
2014-11-15 19:57:03 -03:00
|
|
|
return;
|
|
|
|
|
2019-01-09 15:04:25 -03:00
|
|
|
atm->newQuickToolSelectedFromEditor(newQuicktool);
|
2011-10-29 19:21:19 -03:00
|
|
|
}
|
2010-10-27 21:02:48 -03:00
|
|
|
}
|
|
|
|
|
2016-04-25 15:20:53 -03:00
|
|
|
void Editor::updateToolByTipProximity(ui::PointerType pointerType)
|
2016-04-21 00:57:06 -03:00
|
|
|
{
|
2016-04-25 15:20:53 -03:00
|
|
|
auto activeToolManager = App::instance()->activeToolManager();
|
|
|
|
|
|
|
|
if (pointerType == ui::PointerType::Eraser) {
|
|
|
|
activeToolManager->eraserTipProximity();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
activeToolManager->regularTipProximity();
|
|
|
|
}
|
2016-04-21 00:57:06 -03:00
|
|
|
}
|
|
|
|
|
2020-12-14 14:49:16 -03:00
|
|
|
void Editor::updateToolLoopModifiersIndicators(const bool firstFromMouseDown)
|
2014-08-24 19:19:38 -03:00
|
|
|
{
|
2016-04-04 18:46:48 -03:00
|
|
|
int modifiers = int(tools::ToolLoopModifiers::kNone);
|
2016-11-29 18:36:08 -03:00
|
|
|
const bool autoSelectLayer = isAutoSelectLayer();
|
|
|
|
bool newAutoSelectLayer = autoSelectLayer;
|
2016-04-04 18:46:48 -03:00
|
|
|
KeyAction action;
|
2014-11-16 18:33:31 -03:00
|
|
|
|
2016-04-04 18:46:48 -03:00
|
|
|
if (m_customizationDelegate) {
|
2019-01-09 15:04:25 -03:00
|
|
|
auto atm = App::instance()->activeToolManager();
|
|
|
|
|
2016-04-04 18:46:48 -03:00
|
|
|
// When the mouse is captured, is when we are scrolling, or
|
|
|
|
// drawing, or moving, or selecting, etc. So several
|
|
|
|
// parameters/tool-loop-modifiers are static.
|
|
|
|
if (hasCapture()) {
|
|
|
|
modifiers |= (int(m_toolLoopModifiers) &
|
|
|
|
(int(tools::ToolLoopModifiers::kReplaceSelection) |
|
|
|
|
int(tools::ToolLoopModifiers::kAddSelection) |
|
2018-10-26 14:04:08 -03:00
|
|
|
int(tools::ToolLoopModifiers::kSubtractSelection) |
|
|
|
|
int(tools::ToolLoopModifiers::kIntersectSelection)));
|
2016-04-04 18:46:48 -03:00
|
|
|
|
2021-04-16 13:58:12 -03:00
|
|
|
tools::Tool* tool = atm->selectedTool();
|
|
|
|
tools::Controller* controller = (tool ? tool->getController(0): nullptr);
|
|
|
|
tools::Ink* ink = (tool ? tool->getInk(0): nullptr);
|
2017-06-22 17:47:56 -03:00
|
|
|
|
|
|
|
// Shape tools modifiers (line, curves, rectangles, etc.)
|
|
|
|
if (controller && controller->isTwoPoints()) {
|
|
|
|
action = m_customizationDelegate->getPressedKeyAction(KeyContext::ShapeTool);
|
2020-12-14 14:49:16 -03:00
|
|
|
|
2021-04-16 13:58:12 -03:00
|
|
|
// For two-points-selection-like tools (Rectangular/Elliptical
|
|
|
|
// Marquee) we prefer to activate the
|
|
|
|
// square-aspect/rotation/etc. only when the user presses the
|
|
|
|
// modifier key again in the ToolLoop (and not before starting
|
|
|
|
// the loop). So Alt+selection will add a selection, but
|
|
|
|
// willn't start the square-aspect until we press Alt key
|
|
|
|
// again, or Alt+Shift+selection tool will subtract the
|
2020-12-14 14:49:16 -03:00
|
|
|
// selection but will not start the rotation until we release
|
|
|
|
// and press the Alt key again.
|
2021-04-16 13:58:12 -03:00
|
|
|
if (!firstFromMouseDown ||
|
|
|
|
!ink || !ink->isSelection()) {
|
|
|
|
if (int(action & KeyAction::MoveOrigin))
|
|
|
|
modifiers |= int(tools::ToolLoopModifiers::kMoveOrigin);
|
|
|
|
if (int(action & KeyAction::SquareAspect))
|
|
|
|
modifiers |= int(tools::ToolLoopModifiers::kSquareAspect);
|
|
|
|
if (int(action & KeyAction::DrawFromCenter))
|
|
|
|
modifiers |= int(tools::ToolLoopModifiers::kFromCenter);
|
|
|
|
if (int(action & KeyAction::RotateShape))
|
|
|
|
modifiers |= int(tools::ToolLoopModifiers::kRotateShape);
|
|
|
|
}
|
2017-06-22 17:47:56 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Freehand modifiers
|
|
|
|
if (controller && controller->isFreehand()) {
|
|
|
|
action = m_customizationDelegate->getPressedKeyAction(KeyContext::FreehandTool);
|
|
|
|
if (int(action & KeyAction::AngleSnapFromLastPoint))
|
|
|
|
modifiers |= int(tools::ToolLoopModifiers::kSquareAspect);
|
|
|
|
}
|
2016-04-04 18:46:48 -03:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// We update the selection mode only if we're not selecting.
|
|
|
|
action = m_customizationDelegate->getPressedKeyAction(KeyContext::SelectionTool);
|
|
|
|
|
|
|
|
gen::SelectionMode mode = Preferences::instance().selection.mode();
|
2016-11-30 21:07:30 -03:00
|
|
|
if (int(action & KeyAction::SubtractSelection) ||
|
|
|
|
// Don't use "subtract" mode if the selection was activated
|
|
|
|
// with the "right click mode = a selection-like tool"
|
|
|
|
(m_secondaryButton &&
|
2019-01-09 15:04:25 -03:00
|
|
|
atm->selectedTool() &&
|
|
|
|
atm->selectedTool()->getInk(0)->isSelection())) {
|
2016-04-04 18:46:48 -03:00
|
|
|
mode = gen::SelectionMode::SUBTRACT;
|
2016-11-30 21:07:30 -03:00
|
|
|
}
|
2018-10-26 14:04:08 -03:00
|
|
|
else if (int(action & KeyAction::IntersectSelection)) {
|
|
|
|
mode = gen::SelectionMode::INTERSECT;
|
|
|
|
}
|
2016-11-30 21:07:30 -03:00
|
|
|
else if (int(action & KeyAction::AddSelection)) {
|
|
|
|
mode = gen::SelectionMode::ADD;
|
|
|
|
}
|
2016-04-04 18:46:48 -03:00
|
|
|
switch (mode) {
|
2018-10-26 14:04:08 -03:00
|
|
|
case gen::SelectionMode::DEFAULT: modifiers |= int(tools::ToolLoopModifiers::kReplaceSelection); break;
|
|
|
|
case gen::SelectionMode::ADD: modifiers |= int(tools::ToolLoopModifiers::kAddSelection); break;
|
|
|
|
case gen::SelectionMode::SUBTRACT: modifiers |= int(tools::ToolLoopModifiers::kSubtractSelection); break;
|
|
|
|
case gen::SelectionMode::INTERSECT: modifiers |= int(tools::ToolLoopModifiers::kIntersectSelection); break;
|
2016-04-04 18:46:48 -03:00
|
|
|
}
|
2014-08-24 19:19:38 -03:00
|
|
|
|
2016-04-04 18:46:48 -03:00
|
|
|
// For move tool
|
|
|
|
action = m_customizationDelegate->getPressedKeyAction(KeyContext::MoveTool);
|
|
|
|
if (int(action & KeyAction::AutoSelectLayer))
|
2020-04-18 21:21:14 -03:00
|
|
|
newAutoSelectLayer = Preferences::instance().editor.autoSelectLayerQuick();
|
2016-11-29 18:36:08 -03:00
|
|
|
else
|
|
|
|
newAutoSelectLayer = Preferences::instance().editor.autoSelectLayer();
|
2016-04-04 18:46:48 -03:00
|
|
|
}
|
2014-11-16 18:33:31 -03:00
|
|
|
}
|
|
|
|
|
2016-04-22 13:19:06 -03:00
|
|
|
ContextBar* ctxBar = App::instance()->contextBar();
|
2016-04-04 18:46:48 -03:00
|
|
|
|
|
|
|
if (int(m_toolLoopModifiers) != modifiers) {
|
|
|
|
m_toolLoopModifiers = tools::ToolLoopModifiers(modifiers);
|
2014-11-16 18:33:31 -03:00
|
|
|
|
2016-04-04 18:46:48 -03:00
|
|
|
// TODO the contextbar should be a observer of the current editor
|
|
|
|
ctxBar->updateToolLoopModifiersIndicators(m_toolLoopModifiers);
|
2014-11-16 18:33:31 -03:00
|
|
|
|
2016-04-04 18:46:48 -03:00
|
|
|
if (auto drawingState = dynamic_cast<DrawingState*>(m_state.get())) {
|
|
|
|
drawingState->notifyToolLoopModifiersChange(this);
|
|
|
|
}
|
|
|
|
}
|
2014-08-24 19:19:38 -03:00
|
|
|
|
2016-11-29 18:36:08 -03:00
|
|
|
if (autoSelectLayer != newAutoSelectLayer)
|
|
|
|
ctxBar->updateAutoSelectLayer(newAutoSelectLayer);
|
2014-08-24 19:19:38 -03:00
|
|
|
}
|
|
|
|
|
2016-03-17 17:16:35 -03:00
|
|
|
app::Color Editor::getColorByPosition(const gfx::Point& mousePos)
|
|
|
|
{
|
|
|
|
Site site = getSite();
|
|
|
|
if (site.sprite()) {
|
2016-10-13 19:58:42 -03:00
|
|
|
gfx::PointF editorPos = screenToEditorF(mousePos);
|
2016-03-17 17:16:35 -03:00
|
|
|
|
|
|
|
ColorPicker picker;
|
2020-08-21 18:07:37 -03:00
|
|
|
site.tilemapMode(TilemapMode::Pixels);
|
2016-10-13 19:58:42 -03:00
|
|
|
picker.pickColor(site, editorPos, m_proj,
|
|
|
|
ColorPicker::FromComposition);
|
2016-03-17 17:16:35 -03:00
|
|
|
return picker.color();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return app::Color::fromMask();
|
|
|
|
}
|
|
|
|
|
2020-08-21 18:07:37 -03:00
|
|
|
doc::tile_t Editor::getTileByPosition(const gfx::Point& mousePos)
|
|
|
|
{
|
|
|
|
Site site = getSite();
|
|
|
|
if (site.sprite()) {
|
|
|
|
gfx::PointF editorPos = screenToEditorF(mousePos);
|
|
|
|
|
|
|
|
ColorPicker picker;
|
|
|
|
site.tilemapMode(TilemapMode::Tiles);
|
|
|
|
picker.pickColor(site, editorPos, m_proj,
|
|
|
|
ColorPicker::FromComposition);
|
|
|
|
|
|
|
|
return picker.tile();
|
|
|
|
}
|
|
|
|
else
|
2020-10-30 16:33:34 -03:00
|
|
|
return doc::notile;
|
2020-08-21 18:07:37 -03:00
|
|
|
}
|
|
|
|
|
2020-08-31 19:02:34 -03:00
|
|
|
bool Editor::startStraightLineWithFreehandTool(const tools::Pointer* pointer)
|
2017-06-16 16:28:48 -03:00
|
|
|
{
|
|
|
|
tools::Tool* tool = App::instance()->activeToolManager()->selectedTool();
|
2017-11-27 13:53:56 -03:00
|
|
|
// TODO add support for more buttons (X1, X2, etc.)
|
2020-08-31 19:02:34 -03:00
|
|
|
int i = (pointer && pointer->button() == tools::Pointer::Button::Right ? 1: 0);
|
2017-06-16 16:28:48 -03:00
|
|
|
return
|
2017-06-19 12:34:56 -03:00
|
|
|
(isActive() &&
|
2017-06-19 13:18:38 -03:00
|
|
|
(hasMouse() || hasCapture()) &&
|
2017-06-19 12:34:56 -03:00
|
|
|
tool &&
|
2017-11-27 13:53:56 -03:00
|
|
|
tool->getController(i)->isFreehand() &&
|
|
|
|
tool->getInk(i)->isPaint() &&
|
2017-06-16 16:28:48 -03:00
|
|
|
(getCustomizationDelegate()
|
|
|
|
->getPressedKeyAction(KeyContext::FreehandTool) & KeyAction::StraightLineFromLastPoint) == KeyAction::StraightLineFromLastPoint &&
|
2018-07-07 11:54:44 -03:00
|
|
|
document()->lastDrawingPoint() != Doc::NoLastDrawingPoint());
|
2017-06-16 16:28:48 -03:00
|
|
|
}
|
|
|
|
|
2019-05-02 16:26:13 -03:00
|
|
|
bool Editor::isSliceSelected(const doc::Slice* slice) const
|
|
|
|
{
|
|
|
|
ASSERT(slice);
|
|
|
|
return m_selectedSlices.contains(slice->id());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::clearSlicesSelection()
|
|
|
|
{
|
2019-05-07 10:28:37 -03:00
|
|
|
if (!m_selectedSlices.empty()) {
|
|
|
|
m_selectedSlices.clear();
|
|
|
|
invalidate();
|
|
|
|
|
|
|
|
if (isActive())
|
|
|
|
UIContext::instance()->notifyActiveSiteChanged();
|
|
|
|
}
|
2019-05-02 16:26:13 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::selectSlice(const doc::Slice* slice)
|
|
|
|
{
|
|
|
|
ASSERT(slice);
|
|
|
|
m_selectedSlices.insert(slice->id());
|
|
|
|
invalidate();
|
2019-05-07 10:28:37 -03:00
|
|
|
|
|
|
|
if (isActive())
|
|
|
|
UIContext::instance()->notifyActiveSiteChanged();
|
2019-05-02 16:26:13 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::selectSliceBox(const gfx::Rect& box)
|
|
|
|
{
|
|
|
|
m_selectedSlices.clear();
|
|
|
|
for (auto slice : m_sprite->slices()) {
|
|
|
|
auto key = slice->getByFrame(m_frame);
|
|
|
|
if (key && key->bounds().intersects(box))
|
|
|
|
m_selectedSlices.insert(slice->id());
|
|
|
|
}
|
|
|
|
invalidate();
|
2019-05-07 10:28:37 -03:00
|
|
|
|
|
|
|
if (isActive())
|
|
|
|
UIContext::instance()->notifyActiveSiteChanged();
|
|
|
|
|
2019-05-02 16:26:13 -03:00
|
|
|
return !m_selectedSlices.empty();
|
|
|
|
}
|
|
|
|
|
2019-05-07 10:28:37 -03:00
|
|
|
void Editor::selectAllSlices()
|
|
|
|
{
|
|
|
|
for (auto slice : m_sprite->slices())
|
|
|
|
m_selectedSlices.insert(slice->id());
|
|
|
|
invalidate();
|
|
|
|
|
|
|
|
if (isActive())
|
|
|
|
UIContext::instance()->notifyActiveSiteChanged();
|
|
|
|
}
|
|
|
|
|
2019-05-02 16:26:13 -03:00
|
|
|
void Editor::cancelSelections()
|
|
|
|
{
|
2019-05-07 10:28:37 -03:00
|
|
|
clearSlicesSelection();
|
2019-05-02 16:26:13 -03:00
|
|
|
}
|
|
|
|
|
2010-07-18 14:44:21 -03:00
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
// Message handler for the editor
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2011-04-02 13:14:07 -03:00
|
|
|
bool Editor::onProcessMessage(Message* msg)
|
2010-01-27 02:52:30 +00:00
|
|
|
{
|
2016-11-30 12:35:01 -03:00
|
|
|
// Delete states
|
|
|
|
if (!m_deletedStates.empty())
|
|
|
|
m_deletedStates.clear();
|
|
|
|
|
2013-07-28 21:17:07 -03:00
|
|
|
switch (msg->type()) {
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2013-04-04 21:53:29 -03:00
|
|
|
case kTimerMessage:
|
2015-08-18 15:46:17 -03:00
|
|
|
if (static_cast<TimerMessage*>(msg)->timer() == &m_antsTimer) {
|
2013-01-20 18:40:37 -03:00
|
|
|
if (isVisible() && m_sprite) {
|
2012-01-05 19:45:03 -03:00
|
|
|
drawMaskSafe();
|
|
|
|
|
|
|
|
// Set offset to make selection-movement effect
|
2015-08-18 15:46:17 -03:00
|
|
|
if (m_antsOffset < 7)
|
|
|
|
m_antsOffset++;
|
2012-01-05 19:45:03 -03:00
|
|
|
else
|
2015-08-18 15:46:17 -03:00
|
|
|
m_antsOffset = 0;
|
2012-01-05 19:45:03 -03:00
|
|
|
}
|
2015-08-18 15:46:17 -03:00
|
|
|
else if (m_antsTimer.isRunning()) {
|
|
|
|
m_antsTimer.stop();
|
2013-01-20 18:40:37 -03:00
|
|
|
}
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2019-09-11 16:08:58 -03:00
|
|
|
case kFocusEnterMessage: {
|
|
|
|
ASSERT(m_state);
|
|
|
|
if (m_state)
|
|
|
|
m_state->onEditorGotFocus(this);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2013-04-04 21:53:29 -03:00
|
|
|
case kMouseEnterMessage:
|
2017-12-05 13:31:34 -03:00
|
|
|
m_brushPreview.hide();
|
2016-04-04 18:46:48 -03:00
|
|
|
updateToolLoopModifiersIndicators();
|
2016-05-02 13:24:30 -03:00
|
|
|
updateQuicktool();
|
2008-04-28 16:32:45 +00:00
|
|
|
break;
|
|
|
|
|
2013-04-04 21:53:29 -03:00
|
|
|
case kMouseLeaveMessage:
|
2015-06-25 12:44:47 -03:00
|
|
|
m_brushPreview.hide();
|
2018-11-12 17:52:05 -03:00
|
|
|
StatusBar::instance()->showDefaultText();
|
2020-08-04 16:29:22 -03:00
|
|
|
|
|
|
|
// Hide autoguides
|
|
|
|
updateAutoCelGuides(nullptr);
|
2008-04-28 16:32:45 +00:00
|
|
|
break;
|
|
|
|
|
2013-04-04 21:53:29 -03:00
|
|
|
case kMouseDownMessage:
|
2011-11-06 11:49:25 -03:00
|
|
|
if (m_sprite) {
|
2014-08-19 08:17:57 -03:00
|
|
|
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
|
|
|
|
|
2019-02-18 11:47:35 -03:00
|
|
|
// If we're going to start drawing, we cancel the flashing
|
|
|
|
// layer.
|
|
|
|
if (m_flashing != Flashing::None) {
|
|
|
|
m_flashing = Flashing::None;
|
2021-06-08 20:34:15 -03:00
|
|
|
invalidateCanvas();
|
2019-02-18 11:47:35 -03:00
|
|
|
}
|
|
|
|
|
2014-08-19 08:17:57 -03:00
|
|
|
m_oldPos = mouseMsg->position();
|
2016-04-25 15:20:53 -03:00
|
|
|
updateToolByTipProximity(mouseMsg->pointerType());
|
2017-03-23 00:57:21 -03:00
|
|
|
updateAutoCelGuides(msg);
|
2016-04-20 22:33:10 -03:00
|
|
|
|
2016-11-30 21:07:30 -03:00
|
|
|
// Only when we right-click with the regular "paint bg-color
|
|
|
|
// right-click mode" we will mark indicate that the secondary
|
|
|
|
// button was used (m_secondaryButton == true).
|
|
|
|
if (mouseMsg->right() && !m_secondaryButton) {
|
|
|
|
m_secondaryButton = true;
|
2014-08-19 08:17:57 -03:00
|
|
|
}
|
2014-02-02 18:46:19 -03:00
|
|
|
|
2016-11-30 21:07:30 -03:00
|
|
|
updateToolLoopModifiersIndicators();
|
|
|
|
updateQuicktool();
|
|
|
|
setCursor(mouseMsg->position());
|
|
|
|
|
2016-04-25 15:20:53 -03:00
|
|
|
App::instance()->activeToolManager()
|
|
|
|
->pressButton(pointer_from_msg(this, mouseMsg));
|
|
|
|
|
2012-01-05 19:45:03 -03:00
|
|
|
EditorStatePtr holdState(m_state);
|
2020-12-14 14:49:16 -03:00
|
|
|
bool state = m_state->onMouseDown(this, mouseMsg);
|
|
|
|
|
|
|
|
// Re-update the tool modifiers if the state has changed
|
|
|
|
// (e.g. we are on DrawingState now). This is required for the
|
|
|
|
// Line tool to be able to Shift+press mouse buttons to start
|
|
|
|
// drawing lines with the angle snapped.
|
|
|
|
if (m_state != holdState)
|
|
|
|
updateToolLoopModifiersIndicators(true);
|
|
|
|
|
|
|
|
return state;
|
2011-11-06 11:49:25 -03:00
|
|
|
}
|
2011-04-10 20:15:17 -03:00
|
|
|
break;
|
- All tools stuff refactored in various files/components.
- Added classes: IToolLoop, Tool, ToolGroup, ToolInk, ToolController, ToolPointShape, ToolIntertwine, ToolBox, etc.
- Added ToolLoopManager.
- Removed old src/modules/tools.cpp.
- Added ISettings and UISettingsImpl, adding the tools settings (onion skinning, grid, tiled mode, etc.).
- Added App::PenSizeBeforeChange, PenSizeAfterChange, CurrentToolChange signals.
- Renamed Context::get_bg/fg_color to getBg/FgColor.
- Refactored Brush class to Pen and added PenType.
- Renamed tiled_t to TiledMode.
- get_config_rect now uses the new Rect class imported from Vaca instead of old jrect.
- Added default_skin.xml to load tool icons.
- Added pen preview in Editor::cursor stuff.
- Added Editor::decorators.
Note: This big patch is from some time ago. I did my best to pre-commit other small changes before this big one.
2010-03-07 17:47:45 -02:00
|
|
|
|
2013-04-04 21:53:29 -03:00
|
|
|
case kMouseMoveMessage:
|
2011-11-06 11:49:25 -03:00
|
|
|
if (m_sprite) {
|
2012-01-05 19:45:03 -03:00
|
|
|
EditorStatePtr holdState(m_state);
|
2016-04-25 15:20:53 -03:00
|
|
|
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
|
2016-04-20 22:33:10 -03:00
|
|
|
|
2016-04-25 15:20:53 -03:00
|
|
|
updateToolByTipProximity(mouseMsg->pointerType());
|
2017-03-23 00:57:21 -03:00
|
|
|
updateAutoCelGuides(msg);
|
2016-04-20 22:33:10 -03:00
|
|
|
|
2013-07-28 21:17:07 -03:00
|
|
|
return m_state->onMouseMove(this, static_cast<MouseMessage*>(msg));
|
2011-11-06 11:49:25 -03:00
|
|
|
}
|
2011-04-10 20:15:17 -03:00
|
|
|
break;
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2013-04-04 21:53:29 -03:00
|
|
|
case kMouseUpMessage:
|
2011-04-10 20:15:17 -03:00
|
|
|
if (m_sprite) {
|
2012-01-05 19:45:03 -03:00
|
|
|
EditorStatePtr holdState(m_state);
|
2015-06-25 12:44:47 -03:00
|
|
|
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
|
|
|
|
bool result = m_state->onMouseUp(this, mouseMsg);
|
2014-08-19 08:17:57 -03:00
|
|
|
|
2016-04-25 15:20:53 -03:00
|
|
|
updateToolByTipProximity(mouseMsg->pointerType());
|
2017-03-23 00:57:21 -03:00
|
|
|
updateAutoCelGuides(msg);
|
2016-04-20 22:33:10 -03:00
|
|
|
|
2014-08-27 08:35:37 -03:00
|
|
|
if (!hasCapture()) {
|
2016-04-25 15:20:53 -03:00
|
|
|
App::instance()->activeToolManager()->releaseButtons();
|
2014-08-19 08:17:57 -03:00
|
|
|
m_secondaryButton = false;
|
|
|
|
|
2016-04-04 18:46:48 -03:00
|
|
|
updateToolLoopModifiersIndicators();
|
2016-05-02 13:24:30 -03:00
|
|
|
updateQuicktool();
|
2015-06-25 12:44:47 -03:00
|
|
|
setCursor(mouseMsg->position());
|
2014-08-19 08:17:57 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (result)
|
2012-01-05 19:45:03 -03:00
|
|
|
return true;
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
2011-04-10 20:15:17 -03:00
|
|
|
break;
|
2010-05-03 23:01:05 -03:00
|
|
|
|
2016-03-19 11:33:05 -03:00
|
|
|
case kDoubleClickMessage:
|
|
|
|
if (m_sprite) {
|
|
|
|
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
|
|
|
|
EditorStatePtr holdState(m_state);
|
2016-04-20 22:33:10 -03:00
|
|
|
|
2016-04-25 15:20:53 -03:00
|
|
|
updateToolByTipProximity(mouseMsg->pointerType());
|
2016-04-20 22:33:10 -03:00
|
|
|
|
2016-03-19 11:33:05 -03:00
|
|
|
bool used = m_state->onDoubleClick(this, mouseMsg);
|
|
|
|
if (used)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2016-02-18 13:58:45 -03:00
|
|
|
case kTouchMagnifyMessage:
|
|
|
|
if (m_sprite) {
|
|
|
|
EditorStatePtr holdState(m_state);
|
|
|
|
return m_state->onTouchMagnify(this, static_cast<TouchMessage*>(msg));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2013-04-04 21:53:29 -03:00
|
|
|
case kKeyDownMessage:
|
2018-06-22 15:41:22 -03:00
|
|
|
#if ENABLE_DEVMODE
|
|
|
|
// Switch render mode
|
|
|
|
if (!msg->ctrlPressed() &&
|
|
|
|
static_cast<KeyMessage*>(msg)->scancode() == kKeyF1) {
|
|
|
|
Preferences::instance().experimental.newRenderEngine(
|
|
|
|
!Preferences::instance().experimental.newRenderEngine());
|
2021-06-08 20:34:15 -03:00
|
|
|
invalidateCanvas();
|
2018-06-22 15:41:22 -03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
#endif
|
2011-04-10 20:15:17 -03:00
|
|
|
if (m_sprite) {
|
2012-01-05 19:45:03 -03:00
|
|
|
EditorStatePtr holdState(m_state);
|
2013-07-28 21:17:07 -03:00
|
|
|
bool used = m_state->onKeyDown(this, static_cast<KeyMessage*>(msg));
|
2010-08-11 21:48:12 -03:00
|
|
|
|
2016-05-02 13:24:30 -03:00
|
|
|
updateToolLoopModifiersIndicators();
|
2017-03-23 00:57:21 -03:00
|
|
|
updateAutoCelGuides(msg);
|
2012-01-05 19:45:03 -03:00
|
|
|
if (hasMouse()) {
|
2014-08-24 19:19:38 -03:00
|
|
|
updateQuicktool();
|
2021-03-02 13:50:49 -03:00
|
|
|
setCursor(mousePosInDisplay());
|
2012-01-05 19:45:03 -03:00
|
|
|
}
|
- All tools stuff refactored in various files/components.
- Added classes: IToolLoop, Tool, ToolGroup, ToolInk, ToolController, ToolPointShape, ToolIntertwine, ToolBox, etc.
- Added ToolLoopManager.
- Removed old src/modules/tools.cpp.
- Added ISettings and UISettingsImpl, adding the tools settings (onion skinning, grid, tiled mode, etc.).
- Added App::PenSizeBeforeChange, PenSizeAfterChange, CurrentToolChange signals.
- Renamed Context::get_bg/fg_color to getBg/FgColor.
- Refactored Brush class to Pen and added PenType.
- Renamed tiled_t to TiledMode.
- get_config_rect now uses the new Rect class imported from Vaca instead of old jrect.
- Added default_skin.xml to load tool icons.
- Added pen preview in Editor::cursor stuff.
- Added Editor::decorators.
Note: This big patch is from some time ago. I did my best to pre-commit other small changes before this big one.
2010-03-07 17:47:45 -02:00
|
|
|
|
2012-01-05 19:45:03 -03:00
|
|
|
if (used)
|
|
|
|
return true;
|
2010-04-24 21:23:40 -03:00
|
|
|
}
|
2011-04-10 20:15:17 -03:00
|
|
|
break;
|
- All tools stuff refactored in various files/components.
- Added classes: IToolLoop, Tool, ToolGroup, ToolInk, ToolController, ToolPointShape, ToolIntertwine, ToolBox, etc.
- Added ToolLoopManager.
- Removed old src/modules/tools.cpp.
- Added ISettings and UISettingsImpl, adding the tools settings (onion skinning, grid, tiled mode, etc.).
- Added App::PenSizeBeforeChange, PenSizeAfterChange, CurrentToolChange signals.
- Renamed Context::get_bg/fg_color to getBg/FgColor.
- Refactored Brush class to Pen and added PenType.
- Renamed tiled_t to TiledMode.
- get_config_rect now uses the new Rect class imported from Vaca instead of old jrect.
- Added default_skin.xml to load tool icons.
- Added pen preview in Editor::cursor stuff.
- Added Editor::decorators.
Note: This big patch is from some time ago. I did my best to pre-commit other small changes before this big one.
2010-03-07 17:47:45 -02:00
|
|
|
|
2013-04-04 21:53:29 -03:00
|
|
|
case kKeyUpMessage:
|
2011-04-10 20:15:17 -03:00
|
|
|
if (m_sprite) {
|
2012-01-05 19:45:03 -03:00
|
|
|
EditorStatePtr holdState(m_state);
|
2013-07-28 21:17:07 -03:00
|
|
|
bool used = m_state->onKeyUp(this, static_cast<KeyMessage*>(msg));
|
- All tools stuff refactored in various files/components.
- Added classes: IToolLoop, Tool, ToolGroup, ToolInk, ToolController, ToolPointShape, ToolIntertwine, ToolBox, etc.
- Added ToolLoopManager.
- Removed old src/modules/tools.cpp.
- Added ISettings and UISettingsImpl, adding the tools settings (onion skinning, grid, tiled mode, etc.).
- Added App::PenSizeBeforeChange, PenSizeAfterChange, CurrentToolChange signals.
- Renamed Context::get_bg/fg_color to getBg/FgColor.
- Refactored Brush class to Pen and added PenType.
- Renamed tiled_t to TiledMode.
- get_config_rect now uses the new Rect class imported from Vaca instead of old jrect.
- Added default_skin.xml to load tool icons.
- Added pen preview in Editor::cursor stuff.
- Added Editor::decorators.
Note: This big patch is from some time ago. I did my best to pre-commit other small changes before this big one.
2010-03-07 17:47:45 -02:00
|
|
|
|
2016-05-02 13:24:30 -03:00
|
|
|
updateToolLoopModifiersIndicators();
|
2017-03-23 00:57:21 -03:00
|
|
|
updateAutoCelGuides(msg);
|
2012-01-05 19:45:03 -03:00
|
|
|
if (hasMouse()) {
|
2014-08-24 19:19:38 -03:00
|
|
|
updateQuicktool();
|
2021-03-02 13:50:49 -03:00
|
|
|
setCursor(mousePosInDisplay());
|
2012-01-05 19:45:03 -03:00
|
|
|
}
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2012-01-05 19:45:03 -03:00
|
|
|
if (used)
|
|
|
|
return true;
|
- All tools stuff refactored in various files/components.
- Added classes: IToolLoop, Tool, ToolGroup, ToolInk, ToolController, ToolPointShape, ToolIntertwine, ToolBox, etc.
- Added ToolLoopManager.
- Removed old src/modules/tools.cpp.
- Added ISettings and UISettingsImpl, adding the tools settings (onion skinning, grid, tiled mode, etc.).
- Added App::PenSizeBeforeChange, PenSizeAfterChange, CurrentToolChange signals.
- Renamed Context::get_bg/fg_color to getBg/FgColor.
- Refactored Brush class to Pen and added PenType.
- Renamed tiled_t to TiledMode.
- get_config_rect now uses the new Rect class imported from Vaca instead of old jrect.
- Added default_skin.xml to load tool icons.
- Added pen preview in Editor::cursor stuff.
- Added Editor::decorators.
Note: This big patch is from some time ago. I did my best to pre-commit other small changes before this big one.
2010-03-07 17:47:45 -02:00
|
|
|
}
|
2010-10-27 21:02:48 -03:00
|
|
|
break;
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2013-04-04 21:53:29 -03:00
|
|
|
case kMouseWheelMessage:
|
2011-04-10 20:15:17 -03:00
|
|
|
if (m_sprite && hasMouse()) {
|
2012-01-05 19:45:03 -03:00
|
|
|
EditorStatePtr holdState(m_state);
|
2013-07-28 21:17:07 -03:00
|
|
|
if (m_state->onMouseWheel(this, static_cast<MouseMessage*>(msg)))
|
2012-01-05 19:45:03 -03:00
|
|
|
return true;
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2013-04-04 21:53:29 -03:00
|
|
|
case kSetCursorMessage:
|
2015-06-25 12:44:47 -03:00
|
|
|
setCursor(static_cast<MouseMessage*>(msg)->position());
|
2010-01-30 16:43:13 +00:00
|
|
|
return true;
|
2019-02-18 11:47:35 -03:00
|
|
|
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
|
2019-02-18 11:47:35 -03:00
|
|
|
bool result = Widget::onProcessMessage(msg);
|
|
|
|
|
|
|
|
if (msg->type() == kPaintMessage &&
|
|
|
|
m_flashing != Flashing::None) {
|
|
|
|
const PaintMessage* ptmsg = static_cast<const PaintMessage*>(msg);
|
|
|
|
if (ptmsg->count() == 0) {
|
|
|
|
if (m_flashing == Flashing::WithFlashExtraCel) {
|
|
|
|
m_flashing = Flashing::WaitingDeferedPaint;
|
|
|
|
|
|
|
|
// We have to defer an invalidation so we can keep the
|
|
|
|
// flashing layer in the extra cel some time.
|
|
|
|
defer_invalid_rect(View::getView(this)->viewportBounds());
|
|
|
|
}
|
|
|
|
else if (m_flashing == Flashing::WaitingDeferedPaint) {
|
|
|
|
m_flashing = Flashing::None;
|
|
|
|
|
|
|
|
if (m_brushPreview.onScreen()) {
|
|
|
|
m_brushPreview.hide();
|
|
|
|
|
|
|
|
// Destroy the extra cel explicitly (it could happend
|
|
|
|
// automatically by the m_brushPreview.show()) just in case
|
|
|
|
// that the brush preview will not use the extra cel
|
|
|
|
// (e.g. in the case of the Eraser tool).
|
|
|
|
m_document->setExtraCel(ExtraCelRef(nullptr));
|
|
|
|
|
2021-03-02 13:50:49 -03:00
|
|
|
showBrushPreview(mousePosInDisplay());
|
2019-02-18 11:47:35 -03:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
m_document->setExtraCel(ExtraCelRef(nullptr));
|
|
|
|
}
|
|
|
|
|
2019-02-18 14:10:01 -03:00
|
|
|
// Redraw all editors (without this the preview editor will
|
|
|
|
// still show the flashing layer).
|
|
|
|
for (auto editor : UIContext::instance()->getAllEditorsIncludingPreview(m_document)) {
|
2021-06-08 20:34:15 -03:00
|
|
|
editor->invalidateCanvas();
|
2019-02-18 14:10:01 -03:00
|
|
|
|
|
|
|
// Re-generate painting messages just right now (it looks
|
|
|
|
// like the widget update region is lost after the last
|
|
|
|
// kPaintMessage).
|
|
|
|
editor->flushRedraw();
|
|
|
|
}
|
2019-02-18 11:47:35 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
|
2015-12-03 21:50:05 -03:00
|
|
|
void Editor::onSizeHint(SizeHintEvent& ev)
|
2011-07-28 20:02:07 -03:00
|
|
|
{
|
2012-09-26 18:34:52 -03:00
|
|
|
gfx::Size sz(0, 0);
|
2010-01-27 02:52:30 +00:00
|
|
|
if (m_sprite) {
|
2016-05-23 19:22:02 -03:00
|
|
|
gfx::Point padding = calcExtraPadding(m_proj);
|
2017-11-07 15:21:03 -03:00
|
|
|
gfx::Size canvas = canvasSize();
|
|
|
|
sz.w = m_proj.applyX(canvas.w) + padding.x*2;
|
|
|
|
sz.h = m_proj.applyY(canvas.h) + padding.y*2;
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
else {
|
2012-09-26 18:34:52 -03:00
|
|
|
sz.w = 4;
|
|
|
|
sz.h = 4;
|
|
|
|
}
|
2015-12-03 21:50:05 -03:00
|
|
|
ev.setSizeHint(sz);
|
2012-09-26 18:34:52 -03:00
|
|
|
}
|
|
|
|
|
2015-04-10 13:02:15 -03:00
|
|
|
void Editor::onResize(ui::ResizeEvent& ev)
|
|
|
|
{
|
|
|
|
Widget::onResize(ev);
|
2016-05-23 19:22:02 -03:00
|
|
|
m_padding = calcExtraPadding(m_proj);
|
2015-04-10 13:02:15 -03:00
|
|
|
}
|
|
|
|
|
2014-02-06 20:22:52 -03:00
|
|
|
void Editor::onPaint(ui::PaintEvent& ev)
|
|
|
|
{
|
2017-12-05 13:31:34 -03:00
|
|
|
std::unique_ptr<HideBrushPreview> hide;
|
2019-02-18 11:47:35 -03:00
|
|
|
if (m_flashing == Flashing::None) {
|
|
|
|
// If we are drawing the editor for a tooltip background or any
|
|
|
|
// other semi-transparent widget (e.g. popups), we destroy the brush
|
|
|
|
// preview/extra cel to avoid drawing a part of the brush in the
|
|
|
|
// transparent widget background.
|
|
|
|
if (ev.isTransparentBg()) {
|
|
|
|
m_brushPreview.discardBrushPreview();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
hide.reset(new HideBrushPreview(m_brushPreview));
|
|
|
|
}
|
2017-12-05 13:31:34 -03:00
|
|
|
}
|
|
|
|
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
Graphics* g = ev.graphics();
|
|
|
|
gfx::Rect rc = clientBounds();
|
|
|
|
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
|
2014-02-06 20:22:52 -03:00
|
|
|
|
|
|
|
// Editor without sprite
|
|
|
|
if (!m_sprite) {
|
2015-02-15 15:29:16 -03:00
|
|
|
g->fillRect(theme->colors.editorFace(), rc);
|
2014-02-06 20:22:52 -03:00
|
|
|
}
|
|
|
|
// Editor with sprite
|
|
|
|
else {
|
|
|
|
try {
|
2019-09-24 15:57:26 -03:00
|
|
|
// Lock the sprite to read/render it. Here we don't wait if the
|
|
|
|
// document is locked (e.g. a filter is being applied to the
|
|
|
|
// sprite) to avoid locking the UI.
|
|
|
|
DocReader documentReader(m_document, 0);
|
2014-02-06 20:22:52 -03:00
|
|
|
|
|
|
|
// Draw the sprite in the editor
|
2016-10-11 16:41:50 -03:00
|
|
|
renderChrono.reset();
|
2017-11-07 16:07:58 -03:00
|
|
|
drawBackground(g);
|
2014-07-30 01:28:15 -03:00
|
|
|
drawSpriteUnclippedRect(g, gfx::Rect(0, 0, m_sprite->width(), m_sprite->height()));
|
2016-10-11 16:41:50 -03:00
|
|
|
renderElapsed = renderChrono.elapsed();
|
|
|
|
|
2018-06-22 15:41:22 -03:00
|
|
|
#if ENABLE_DEVMODE
|
2016-10-11 16:41:50 -03:00
|
|
|
// Show performance stats (TODO show performance stats in other widget)
|
|
|
|
if (Preferences::instance().perf.showRenderTime()) {
|
|
|
|
View* view = View::getView(this);
|
|
|
|
gfx::Rect vp = view->viewportBounds();
|
|
|
|
char buf[128];
|
2018-06-22 15:41:22 -03:00
|
|
|
sprintf(buf, "%c %.4gs",
|
|
|
|
Preferences::instance().experimental.newRenderEngine() ? 'N': 'O',
|
|
|
|
renderElapsed);
|
2017-02-06 17:58:55 -03:00
|
|
|
g->drawText(
|
2016-10-11 16:41:50 -03:00
|
|
|
buf,
|
|
|
|
gfx::rgba(255, 255, 255, 255),
|
|
|
|
gfx::rgba(0, 0, 0, 255),
|
|
|
|
vp.origin() - bounds().origin());
|
2018-06-22 15:41:22 -03:00
|
|
|
|
|
|
|
m_perfInfoBounds.setOrigin(vp.origin());
|
|
|
|
m_perfInfoBounds.setSize(g->measureUIText(buf));
|
2016-10-11 16:41:50 -03:00
|
|
|
}
|
2018-06-22 15:41:22 -03:00
|
|
|
#endif // ENABLE_DEVMODE
|
2014-02-06 20:22:52 -03:00
|
|
|
|
|
|
|
// Draw the mask boundaries
|
2020-05-18 17:58:22 -03:00
|
|
|
if (m_document->hasMaskBoundaries()) {
|
2014-03-03 00:09:14 -03:00
|
|
|
drawMask(g);
|
2015-08-18 15:46:17 -03:00
|
|
|
m_antsTimer.start();
|
2014-02-06 20:22:52 -03:00
|
|
|
}
|
|
|
|
else {
|
2015-08-18 15:46:17 -03:00
|
|
|
m_antsTimer.stop();
|
2014-02-06 20:22:52 -03:00
|
|
|
}
|
|
|
|
}
|
2018-07-14 22:47:03 -03:00
|
|
|
catch (const LockedDocException&) {
|
2019-09-24 15:57:26 -03:00
|
|
|
// The sprite is locked, so we cannot render it, we can draw an
|
|
|
|
// opaque background now, and defer the rendering of the sprite
|
|
|
|
// for later.
|
2015-02-15 15:29:16 -03:00
|
|
|
g->fillRect(theme->colors.editorFace(), rc);
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
defer_invalid_rect(g->getClipBounds().offset(bounds().origin()));
|
2014-02-06 20:22:52 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-25 12:44:47 -03:00
|
|
|
void Editor::onInvalidateRegion(const gfx::Region& region)
|
|
|
|
{
|
|
|
|
Widget::onInvalidateRegion(region);
|
|
|
|
m_brushPreview.invalidateRegion(region);
|
|
|
|
}
|
|
|
|
|
2012-09-26 18:34:52 -03:00
|
|
|
// When the current tool is changed
|
2016-04-25 15:20:53 -03:00
|
|
|
void Editor::onActiveToolChange(tools::Tool* tool)
|
2012-09-26 18:34:52 -03:00
|
|
|
{
|
2016-04-25 15:20:53 -03:00
|
|
|
m_state->onActiveToolChange(this, tool);
|
2018-07-24 12:45:57 -03:00
|
|
|
if (hasMouse()) {
|
|
|
|
updateStatusBar();
|
2021-03-02 13:50:49 -03:00
|
|
|
setCursor(mousePosInDisplay());
|
2018-07-24 12:45:57 -03:00
|
|
|
}
|
2012-09-26 18:34:52 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::onFgColorChange()
|
|
|
|
{
|
2015-06-25 12:44:47 -03:00
|
|
|
m_brushPreview.redraw();
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
|
2015-07-06 13:05:06 -03:00
|
|
|
void Editor::onContextBarBrushChange()
|
2015-05-18 16:53:25 -03:00
|
|
|
{
|
2015-06-25 12:44:47 -03:00
|
|
|
m_brushPreview.redraw();
|
2015-05-18 16:53:25 -03:00
|
|
|
}
|
|
|
|
|
2017-12-14 09:02:49 -03:00
|
|
|
void Editor::onTiledModeBeforeChange()
|
|
|
|
{
|
|
|
|
m_oldMainTilePos = mainTilePosition();
|
|
|
|
}
|
|
|
|
|
2017-11-07 15:21:03 -03:00
|
|
|
void Editor::onTiledModeChange()
|
|
|
|
{
|
2017-12-14 09:02:49 -03:00
|
|
|
ASSERT(m_sprite);
|
|
|
|
|
2021-05-28 15:29:16 -03:00
|
|
|
m_tiledModeHelper.mode(m_docPref.tiled.mode());
|
|
|
|
|
2017-12-14 09:02:49 -03:00
|
|
|
// Get the sprite point in the middle of the editor, so we can
|
|
|
|
// restore this with the new tiled mode in the main tile.
|
|
|
|
View* view = View::getView(this);
|
|
|
|
gfx::Rect vp = view->viewportBounds();
|
|
|
|
gfx::Point screenPos(vp.x + vp.w/2,
|
|
|
|
vp.y + vp.h/2);
|
|
|
|
gfx::Point spritePos(screenToEditor(screenPos));
|
|
|
|
spritePos -= m_oldMainTilePos;
|
|
|
|
|
|
|
|
// Update padding
|
2017-11-07 15:21:03 -03:00
|
|
|
m_padding = calcExtraPadding(m_proj);
|
2017-12-14 09:02:49 -03:00
|
|
|
|
|
|
|
spritePos += mainTilePosition();
|
|
|
|
screenPos = editorToScreen(spritePos);
|
|
|
|
|
|
|
|
centerInSpritePoint(spritePos);
|
2017-11-07 15:21:03 -03:00
|
|
|
}
|
|
|
|
|
2016-03-24 13:04:35 -03:00
|
|
|
void Editor::onShowExtrasChange()
|
|
|
|
{
|
|
|
|
invalidate();
|
|
|
|
}
|
|
|
|
|
2018-10-18 15:29:16 -03:00
|
|
|
void Editor::onColorSpaceChanged(DocEvent& ev)
|
|
|
|
{
|
|
|
|
// As the document has a new color space, we've to redraw the
|
|
|
|
// complete canvas again with the new color profile.
|
|
|
|
invalidate();
|
|
|
|
}
|
|
|
|
|
2018-07-07 02:47:42 -03:00
|
|
|
void Editor::onExposeSpritePixels(DocEvent& ev)
|
2014-12-08 14:57:56 -03:00
|
|
|
{
|
|
|
|
if (m_state && ev.sprite() == m_sprite)
|
|
|
|
m_state->onExposeSpritePixels(ev.region());
|
|
|
|
}
|
|
|
|
|
2018-07-07 02:47:42 -03:00
|
|
|
void Editor::onSpritePixelRatioChanged(DocEvent& ev)
|
2016-05-24 12:25:11 -03:00
|
|
|
{
|
|
|
|
m_proj.setPixelRatio(ev.sprite()->pixelRatio());
|
|
|
|
invalidate();
|
|
|
|
}
|
|
|
|
|
2020-04-07 10:40:56 -03:00
|
|
|
// TODO similar to ActiveSiteHandler::onBeforeRemoveLayer() and Timeline::onBeforeRemoveLayer()
|
2018-07-06 21:06:03 -03:00
|
|
|
void Editor::onBeforeRemoveLayer(DocEvent& ev)
|
2017-06-28 17:20:08 -03:00
|
|
|
{
|
|
|
|
m_showGuidesThisCel = nullptr;
|
2020-04-07 10:40:56 -03:00
|
|
|
|
|
|
|
// If the layer that was removed is the selected one in the editor,
|
|
|
|
// or is an ancestor of the selected one.
|
|
|
|
Layer* layerToSelect = candidate_if_layer_is_deleted(layer(), ev.layer());
|
|
|
|
if (layer() != layerToSelect)
|
|
|
|
setLayer(layerToSelect);
|
2017-06-28 17:20:08 -03:00
|
|
|
}
|
|
|
|
|
2020-08-04 10:40:42 -03:00
|
|
|
void Editor::onBeforeRemoveCel(DocEvent& ev)
|
2017-04-14 13:40:39 -03:00
|
|
|
{
|
|
|
|
m_showGuidesThisCel = nullptr;
|
|
|
|
}
|
|
|
|
|
2019-10-01 14:55:08 -03:00
|
|
|
void Editor::onAddTag(DocEvent& ev)
|
2017-04-14 10:02:02 -03:00
|
|
|
{
|
|
|
|
m_tagFocusBand = -1;
|
|
|
|
}
|
|
|
|
|
2019-10-01 14:55:08 -03:00
|
|
|
void Editor::onRemoveTag(DocEvent& ev)
|
2017-04-14 10:02:02 -03:00
|
|
|
{
|
|
|
|
m_tagFocusBand = -1;
|
2018-09-17 15:41:28 -03:00
|
|
|
if (m_state)
|
2019-10-01 14:55:08 -03:00
|
|
|
m_state->onRemoveTag(this, ev.tag());
|
2017-04-14 10:02:02 -03:00
|
|
|
}
|
|
|
|
|
2019-05-02 16:26:13 -03:00
|
|
|
void Editor::onRemoveSlice(DocEvent& ev)
|
|
|
|
{
|
|
|
|
ASSERT(ev.slice());
|
|
|
|
if (ev.slice() &&
|
|
|
|
m_selectedSlices.contains(ev.slice()->id())) {
|
|
|
|
m_selectedSlices.erase(ev.slice()->id());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-02 13:50:49 -03:00
|
|
|
void Editor::setCursor(const gfx::Point& mouseDisplayPos)
|
2008-04-28 16:32:45 +00:00
|
|
|
{
|
2018-07-24 12:45:57 -03:00
|
|
|
Rect vp = View::getView(this)->viewportBounds();
|
2021-03-02 13:50:49 -03:00
|
|
|
if (!vp.contains(mouseDisplayPos))
|
2018-07-24 12:45:57 -03:00
|
|
|
return;
|
|
|
|
|
2011-04-10 20:15:17 -03:00
|
|
|
bool used = false;
|
|
|
|
if (m_sprite)
|
2021-03-02 13:50:49 -03:00
|
|
|
used = m_state->onSetCursor(this, mouseDisplayPos);
|
2008-04-28 16:32:45 +00:00
|
|
|
|
2015-06-25 12:44:47 -03:00
|
|
|
if (!used)
|
|
|
|
showMouseCursor(kArrowCursor);
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
|
|
|
|
2011-04-10 20:15:17 -03:00
|
|
|
bool Editor::canDraw()
|
2008-04-28 16:32:45 +00:00
|
|
|
{
|
2013-03-11 20:29:45 -03:00
|
|
|
return (m_layer != NULL &&
|
|
|
|
m_layer->isImage() &&
|
2016-06-15 15:27:38 -03:00
|
|
|
m_layer->isVisibleHierarchy() &&
|
2016-10-17 13:48:52 -03:00
|
|
|
m_layer->isEditableHierarchy() &&
|
|
|
|
!m_layer->isReference());
|
2008-04-28 16:32:45 +00:00
|
|
|
}
|
- All tools stuff refactored in various files/components.
- Added classes: IToolLoop, Tool, ToolGroup, ToolInk, ToolController, ToolPointShape, ToolIntertwine, ToolBox, etc.
- Added ToolLoopManager.
- Removed old src/modules/tools.cpp.
- Added ISettings and UISettingsImpl, adding the tools settings (onion skinning, grid, tiled mode, etc.).
- Added App::PenSizeBeforeChange, PenSizeAfterChange, CurrentToolChange signals.
- Renamed Context::get_bg/fg_color to getBg/FgColor.
- Refactored Brush class to Pen and added PenType.
- Renamed tiled_t to TiledMode.
- get_config_rect now uses the new Rect class imported from Vaca instead of old jrect.
- Added default_skin.xml to load tool icons.
- Added pen preview in Editor::cursor stuff.
- Added Editor::decorators.
Note: This big patch is from some time ago. I did my best to pre-commit other small changes before this big one.
2010-03-07 17:47:45 -02:00
|
|
|
|
2011-04-10 20:15:17 -03:00
|
|
|
bool Editor::isInsideSelection()
|
|
|
|
{
|
2021-03-02 13:50:49 -03:00
|
|
|
gfx::Point spritePos = screenToEditor(mousePosInDisplay());
|
2017-11-08 15:59:33 -03:00
|
|
|
spritePos -= mainTilePosition();
|
|
|
|
|
2016-05-02 13:33:29 -03:00
|
|
|
KeyAction action = m_customizationDelegate->getPressedKeyAction(KeyContext::SelectionTool);
|
2011-04-10 20:15:17 -03:00
|
|
|
return
|
2016-05-02 13:33:29 -03:00
|
|
|
(action == KeyAction::None) &&
|
|
|
|
m_document &&
|
|
|
|
m_document->isMaskVisible() &&
|
|
|
|
m_document->mask()->containsPoint(spritePos.x, spritePos.y);
|
2011-04-10 20:15:17 -03:00
|
|
|
}
|
|
|
|
|
2017-09-27 13:07:37 -03:00
|
|
|
bool Editor::canStartMovingSelectionPixels()
|
|
|
|
{
|
|
|
|
return
|
2017-09-28 11:27:05 -03:00
|
|
|
isInsideSelection() &&
|
|
|
|
// We cannot move the selection when add/subtract modes are
|
|
|
|
// enabled (we prefer to modify the selection on those modes
|
|
|
|
// instead of moving pixels).
|
|
|
|
((int(m_toolLoopModifiers) & int(tools::ToolLoopModifiers::kReplaceSelection)) ||
|
2017-10-03 15:03:51 -03:00
|
|
|
// We can move the selection on add mode if the preferences says so.
|
|
|
|
((int(m_toolLoopModifiers) & int(tools::ToolLoopModifiers::kAddSelection)) &&
|
|
|
|
Preferences::instance().selection.moveOnAddMode()) ||
|
2017-09-28 11:27:05 -03:00
|
|
|
// We can move the selection when the Copy selection key (Ctrl) is pressed.
|
|
|
|
(m_customizationDelegate &&
|
|
|
|
int(m_customizationDelegate->getPressedKeyAction(KeyContext::TranslatingSelection) & KeyAction::CopySelection)));
|
2017-09-27 13:07:37 -03:00
|
|
|
}
|
|
|
|
|
2019-09-05 15:03:13 -03:00
|
|
|
bool Editor::keepTimelineRange()
|
|
|
|
{
|
2019-09-11 18:18:31 -03:00
|
|
|
if (auto movingPixels = dynamic_cast<MovingPixelsState*>(m_state.get())) {
|
2019-09-05 15:03:13 -03:00
|
|
|
if (movingPixels->canHandleFrameChange())
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-03-06 19:27:43 -03:00
|
|
|
EditorHit Editor::calcHit(const gfx::Point& mouseScreenPos)
|
|
|
|
{
|
|
|
|
tools::Ink* ink = getCurrentEditorInk();
|
|
|
|
|
|
|
|
if (ink) {
|
|
|
|
// Check if we can transform slices
|
|
|
|
if (ink->isSlice()) {
|
|
|
|
if (m_docPref.show.slices()) {
|
2017-11-09 15:08:09 -03:00
|
|
|
gfx::Point mainOffset(mainTilePosition());
|
|
|
|
|
2017-03-06 19:27:43 -03:00
|
|
|
for (auto slice : m_sprite->slices()) {
|
|
|
|
auto key = slice->getByFrame(m_frame);
|
|
|
|
if (key) {
|
2017-11-09 15:08:09 -03:00
|
|
|
gfx::Rect bounds = key->bounds();
|
|
|
|
bounds.offset(mainOffset);
|
|
|
|
bounds = editorToScreen(bounds);
|
|
|
|
|
2017-03-06 19:27:43 -03:00
|
|
|
gfx::Rect center = key->center();
|
|
|
|
|
2017-04-12 15:40:27 -03:00
|
|
|
// Move bounds
|
|
|
|
if (bounds.contains(mouseScreenPos) &&
|
|
|
|
!bounds.shrink(5*guiscale()).contains(mouseScreenPos)) {
|
2017-03-06 19:27:43 -03:00
|
|
|
int border =
|
|
|
|
(mouseScreenPos.x <= bounds.x ? LEFT: 0) |
|
|
|
|
(mouseScreenPos.y <= bounds.y ? TOP: 0) |
|
|
|
|
(mouseScreenPos.x >= bounds.x2() ? RIGHT: 0) |
|
|
|
|
(mouseScreenPos.y >= bounds.y2() ? BOTTOM: 0);
|
|
|
|
|
|
|
|
EditorHit hit(EditorHit::SliceBounds);
|
|
|
|
hit.setBorder(border);
|
|
|
|
hit.setSlice(slice);
|
|
|
|
return hit;
|
|
|
|
}
|
2017-04-12 15:40:27 -03:00
|
|
|
|
|
|
|
// Move center
|
|
|
|
if (!center.isEmpty()) {
|
2017-03-06 19:27:43 -03:00
|
|
|
center = editorToScreen(
|
|
|
|
center.offset(key->bounds().origin()));
|
|
|
|
|
|
|
|
bool horz1 = gfx::Rect(bounds.x, center.y-2*guiscale(), bounds.w, 5*guiscale()).contains(mouseScreenPos);
|
|
|
|
bool horz2 = gfx::Rect(bounds.x, center.y2()-2*guiscale(), bounds.w, 5*guiscale()).contains(mouseScreenPos);
|
|
|
|
bool vert1 = gfx::Rect(center.x-2*guiscale(), bounds.y, 5*guiscale(), bounds.h).contains(mouseScreenPos);
|
|
|
|
bool vert2 = gfx::Rect(center.x2()-2*guiscale(), bounds.y, 5*guiscale(), bounds.h).contains(mouseScreenPos);
|
|
|
|
|
|
|
|
if (horz1 || horz2 || vert1 || vert2) {
|
|
|
|
int border =
|
|
|
|
(horz1 ? TOP: 0) |
|
|
|
|
(horz2 ? BOTTOM: 0) |
|
|
|
|
(vert1 ? LEFT: 0) |
|
|
|
|
(vert2 ? RIGHT: 0);
|
|
|
|
EditorHit hit(EditorHit::SliceCenter);
|
|
|
|
hit.setBorder(border);
|
|
|
|
hit.setSlice(slice);
|
|
|
|
return hit;
|
|
|
|
}
|
|
|
|
}
|
2017-04-12 15:40:27 -03:00
|
|
|
|
|
|
|
// Move all the slice
|
|
|
|
if (bounds.contains(mouseScreenPos)) {
|
|
|
|
EditorHit hit(EditorHit::SliceBounds);
|
|
|
|
hit.setBorder(CENTER | MIDDLE);
|
|
|
|
hit.setSlice(slice);
|
|
|
|
return hit;
|
|
|
|
}
|
2017-03-06 19:27:43 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return EditorHit(EditorHit::None);
|
|
|
|
}
|
|
|
|
|
2015-08-14 19:46:48 -03:00
|
|
|
void Editor::setZoomAndCenterInMouse(const Zoom& zoom,
|
2016-05-23 19:22:02 -03:00
|
|
|
const gfx::Point& mousePos,
|
|
|
|
ZoomBehavior zoomBehavior)
|
2010-05-06 22:15:58 -03:00
|
|
|
{
|
2015-07-31 16:18:53 -03:00
|
|
|
HideBrushPreview hide(m_brushPreview);
|
2011-02-20 18:35:21 -03:00
|
|
|
View* view = View::getView(this);
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
Rect vp = view->viewportBounds();
|
2016-05-23 19:22:02 -03:00
|
|
|
Projection proj = m_proj;
|
|
|
|
proj.setZoom(zoom);
|
2015-05-07 15:13:26 -03:00
|
|
|
|
|
|
|
gfx::Point screenPos;
|
|
|
|
gfx::Point spritePos;
|
2015-05-07 17:23:44 -03:00
|
|
|
gfx::PointT<double> subpixelPos(0.5, 0.5);
|
|
|
|
|
2014-04-19 20:43:23 -03:00
|
|
|
switch (zoomBehavior) {
|
2015-05-07 15:13:26 -03:00
|
|
|
case ZoomBehavior::CENTER:
|
|
|
|
screenPos = gfx::Point(vp.x + vp.w/2,
|
|
|
|
vp.y + vp.h/2);
|
2014-04-19 20:43:23 -03:00
|
|
|
break;
|
2015-05-07 15:13:26 -03:00
|
|
|
case ZoomBehavior::MOUSE:
|
|
|
|
screenPos = mousePos;
|
2014-04-19 20:43:23 -03:00
|
|
|
break;
|
|
|
|
}
|
2019-04-28 21:27:05 -03:00
|
|
|
|
2019-08-06 15:17:11 -03:00
|
|
|
// Limit zooming screen position to the visible sprite bounds (we
|
|
|
|
// use canvasSize() because if the tiled mode is enabled, we need
|
|
|
|
// extra space for the zoom)
|
|
|
|
gfx::Rect visibleBounds = editorToScreen(
|
|
|
|
getViewportBounds().createIntersection(gfx::Rect(gfx::Point(0, 0), canvasSize())));
|
2019-04-28 21:27:05 -03:00
|
|
|
screenPos.x = base::clamp(screenPos.x, visibleBounds.x, visibleBounds.x2()-1);
|
|
|
|
screenPos.y = base::clamp(screenPos.y, visibleBounds.y, visibleBounds.y2()-1);
|
|
|
|
|
2015-05-07 15:13:26 -03:00
|
|
|
spritePos = screenToEditor(screenPos);
|
2014-04-19 20:43:23 -03:00
|
|
|
|
2016-05-23 19:22:02 -03:00
|
|
|
if (zoomBehavior == ZoomBehavior::MOUSE) {
|
2015-05-07 17:23:44 -03:00
|
|
|
gfx::Point screenPos2 = editorToScreen(spritePos);
|
|
|
|
|
2016-05-23 19:22:02 -03:00
|
|
|
if (m_proj.scaleX() > 1.0) {
|
|
|
|
subpixelPos.x = (0.5 + screenPos.x - screenPos2.x) / m_proj.scaleX();
|
|
|
|
if (proj.scaleX() > m_proj.scaleX()) {
|
|
|
|
double t = 1.0 / proj.scaleX();
|
|
|
|
if (subpixelPos.x >= 0.5-t && subpixelPos.x <= 0.5+t)
|
|
|
|
subpixelPos.x = 0.5;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_proj.scaleY() > 1.0) {
|
|
|
|
subpixelPos.y = (0.5 + screenPos.y - screenPos2.y) / m_proj.scaleY();
|
|
|
|
if (proj.scaleY() > m_proj.scaleY()) {
|
|
|
|
double t = 1.0 / proj.scaleY();
|
|
|
|
if (subpixelPos.y >= 0.5-t && subpixelPos.y <= 0.5+t)
|
|
|
|
subpixelPos.y = 0.5;
|
|
|
|
}
|
2015-08-04 12:26:58 -03:00
|
|
|
}
|
2015-05-07 17:23:44 -03:00
|
|
|
}
|
|
|
|
|
2016-05-23 19:22:02 -03:00
|
|
|
gfx::Point padding = calcExtraPadding(proj);
|
2015-05-07 15:13:26 -03:00
|
|
|
gfx::Point scrollPos(
|
2016-05-23 19:22:02 -03:00
|
|
|
padding.x - (screenPos.x-vp.x) + proj.applyX(spritePos.x+proj.removeX(1)/2) + int(proj.applyX(subpixelPos.x)),
|
|
|
|
padding.y - (screenPos.y-vp.y) + proj.applyY(spritePos.y+proj.removeY(1)/2) + int(proj.applyY(subpixelPos.y)));
|
2010-05-06 22:15:58 -03:00
|
|
|
|
2016-02-18 13:58:45 -03:00
|
|
|
setZoom(zoom);
|
2010-05-06 22:15:58 -03:00
|
|
|
|
2016-05-23 19:22:02 -03:00
|
|
|
if ((m_proj.zoom() != zoom) || (screenPos != view->viewScroll())) {
|
2020-01-13 15:01:45 -03:00
|
|
|
updateEditor(false);
|
2016-01-05 16:37:52 -03:00
|
|
|
setEditorScroll(scrollPos);
|
2010-05-06 22:15:58 -03:00
|
|
|
}
|
|
|
|
}
|
2012-01-05 23:21:51 -03:00
|
|
|
|
2015-07-23 22:42:14 -03:00
|
|
|
void Editor::pasteImage(const Image* image, const Mask* mask)
|
2012-01-05 23:21:51 -03:00
|
|
|
{
|
2015-07-23 22:42:14 -03:00
|
|
|
ASSERT(image);
|
2015-11-02 17:47:56 -03:00
|
|
|
|
2018-08-08 17:27:26 -03:00
|
|
|
std::unique_ptr<Mask> temp_mask;
|
2015-11-02 17:47:56 -03:00
|
|
|
if (!mask) {
|
|
|
|
gfx::Rect visibleBounds = getVisibleSpriteBounds();
|
|
|
|
gfx::Rect imageBounds = image->bounds();
|
|
|
|
|
|
|
|
temp_mask.reset(new Mask);
|
|
|
|
temp_mask->replace(
|
|
|
|
gfx::Rect(visibleBounds.x + visibleBounds.w/2 - imageBounds.w/2,
|
|
|
|
visibleBounds.y + visibleBounds.h/2 - imageBounds.h/2,
|
|
|
|
imageBounds.w, imageBounds.h));
|
|
|
|
|
|
|
|
mask = temp_mask.get();
|
|
|
|
}
|
2015-07-23 22:42:14 -03:00
|
|
|
|
2012-01-06 00:41:02 -03:00
|
|
|
// Change to a selection tool: it's necessary for PixelsMovement
|
|
|
|
// which will use the extra cel for transformation preview, and is
|
|
|
|
// not compatible with the drawing cursor preview which overwrite
|
|
|
|
// the extra cel.
|
2014-08-19 08:17:57 -03:00
|
|
|
if (!getCurrentEditorInk()->isSelection()) {
|
2012-01-06 00:41:02 -03:00
|
|
|
tools::Tool* defaultSelectionTool =
|
2016-04-22 13:19:06 -03:00
|
|
|
App::instance()->toolBox()->getToolById(tools::WellKnownTools::RectangularMarquee);
|
2012-01-06 00:41:02 -03:00
|
|
|
|
2012-07-09 13:20:58 -03:00
|
|
|
ToolBar::instance()->selectTool(defaultSelectionTool);
|
2012-01-06 00:41:02 -03:00
|
|
|
}
|
|
|
|
|
2014-07-30 01:28:15 -03:00
|
|
|
Sprite* sprite = this->sprite();
|
2012-02-11 18:35:46 -03:00
|
|
|
|
|
|
|
// Check bounds where the image will be pasted.
|
2015-07-23 22:42:14 -03:00
|
|
|
int x = mask->bounds().x;
|
|
|
|
int y = mask->bounds().y;
|
2012-02-11 18:35:46 -03:00
|
|
|
{
|
2017-06-23 07:01:12 -03:00
|
|
|
const Rect visibleBounds = getViewportBounds();
|
2017-06-23 06:29:36 -03:00
|
|
|
const Point maskCenter = mask->bounds().center();
|
2012-02-11 18:35:46 -03:00
|
|
|
|
2015-08-19 14:48:16 -03:00
|
|
|
// If the pasted image original location center point isn't
|
|
|
|
// visible, we center the image in the editor's visible bounds.
|
2017-06-23 06:29:36 -03:00
|
|
|
if (maskCenter.x < visibleBounds.x ||
|
|
|
|
maskCenter.x >= visibleBounds.x2()) {
|
2015-08-19 14:48:16 -03:00
|
|
|
x = visibleBounds.x + visibleBounds.w/2 - image->width()/2;
|
|
|
|
}
|
|
|
|
// In other case, if the center is visible, we put the pasted
|
|
|
|
// image in its original location.
|
|
|
|
else {
|
2020-04-07 19:47:16 -03:00
|
|
|
x = base::clamp(x, visibleBounds.x-image->width(), visibleBounds.x2()-1);
|
2017-06-23 06:29:36 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (maskCenter.y < visibleBounds.y ||
|
|
|
|
maskCenter.y >= visibleBounds.y2()) {
|
|
|
|
y = visibleBounds.y + visibleBounds.h/2 - image->height()/2;
|
|
|
|
}
|
|
|
|
else {
|
2020-04-07 19:47:16 -03:00
|
|
|
y = base::clamp(y, visibleBounds.y-image->height(), visibleBounds.y2()-1);
|
2015-08-19 14:48:16 -03:00
|
|
|
}
|
|
|
|
|
2017-06-29 16:03:38 -03:00
|
|
|
// Limit the image inside the sprite's bounds.
|
|
|
|
if (sprite->width() <= image->width() ||
|
|
|
|
sprite->height() <= image->height()) {
|
2020-04-07 19:47:16 -03:00
|
|
|
// TODO review this (I think limits are wrong and high limit can
|
|
|
|
// be negative here)
|
|
|
|
x = base::clamp(x, 0, sprite->width() - image->width());
|
|
|
|
y = base::clamp(y, 0, sprite->height() - image->height());
|
2017-06-29 16:03:38 -03:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Also we always limit the 1 image pixel inside the sprite's bounds.
|
2020-04-07 19:47:16 -03:00
|
|
|
x = base::clamp(x, -image->width()+1, sprite->width()-1);
|
|
|
|
y = base::clamp(y, -image->height()+1, sprite->height()-1);
|
2017-06-29 16:03:38 -03:00
|
|
|
}
|
2012-02-11 18:35:46 -03:00
|
|
|
}
|
|
|
|
|
2020-10-02 19:03:53 -03:00
|
|
|
Site site = getSite();
|
|
|
|
|
|
|
|
// Snap to grid a pasted tilemap
|
|
|
|
// TODO should we move this to PixelsMovement or MovingPixelsState?
|
|
|
|
if (site.tilemapMode() == TilemapMode::Tiles) {
|
|
|
|
gfx::Rect gridBounds = site.gridBounds();
|
|
|
|
gfx::Point pt = snap_to_grid(gridBounds,
|
|
|
|
gfx::Point(x, y),
|
|
|
|
PreferSnapTo::ClosestGridVertex);
|
|
|
|
x = pt.x;
|
|
|
|
y = pt.y;
|
|
|
|
}
|
|
|
|
|
2015-06-08 12:13:55 -03:00
|
|
|
// Clear brush preview, as the extra cel will be replaced with the
|
|
|
|
// pasted image.
|
2015-06-25 12:44:47 -03:00
|
|
|
m_brushPreview.hide();
|
2015-06-08 12:13:55 -03:00
|
|
|
|
2015-07-23 22:42:14 -03:00
|
|
|
Mask mask2(*mask);
|
|
|
|
mask2.setOrigin(x, y);
|
|
|
|
|
2013-12-29 19:53:28 -03:00
|
|
|
PixelsMovementPtr pixelsMovement(
|
2020-10-02 19:03:53 -03:00
|
|
|
new PixelsMovement(UIContext::instance(), site,
|
2015-07-29 16:35:34 -03:00
|
|
|
image, &mask2, "Paste"));
|
2012-01-05 23:21:51 -03:00
|
|
|
|
|
|
|
setState(EditorStatePtr(new MovingPixelsState(this, NULL, pixelsMovement, NoHandle)));
|
|
|
|
}
|
2013-08-05 21:20:19 -03:00
|
|
|
|
2015-08-13 21:47:30 -03:00
|
|
|
void Editor::startSelectionTransformation(const gfx::Point& move, double angle)
|
2014-08-08 01:00:02 -03:00
|
|
|
{
|
2019-09-11 18:18:31 -03:00
|
|
|
if (auto movingPixels = dynamic_cast<MovingPixelsState*>(m_state.get())) {
|
2020-09-18 19:29:43 -03:00
|
|
|
movingPixels->translate(gfx::PointF(move));
|
2015-08-13 21:47:30 -03:00
|
|
|
if (std::fabs(angle) > 1e-5)
|
|
|
|
movingPixels->rotate(angle);
|
2014-08-08 01:00:02 -03:00
|
|
|
}
|
2020-09-25 09:48:56 -03:00
|
|
|
else if (auto standby = dynamic_cast<StandbyState*>(m_state.get())) {
|
2015-08-13 21:47:30 -03:00
|
|
|
standby->startSelectionTransformation(this, move, angle);
|
2014-08-08 01:00:02 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-06 16:22:44 -03:00
|
|
|
void Editor::startFlipTransformation(doc::algorithm::FlipType flipType)
|
|
|
|
{
|
2019-09-11 18:18:31 -03:00
|
|
|
if (auto movingPixels = dynamic_cast<MovingPixelsState*>(m_state.get()))
|
2018-07-06 16:22:44 -03:00
|
|
|
movingPixels->flip(flipType);
|
2019-09-11 18:18:31 -03:00
|
|
|
else if (auto standby = dynamic_cast<StandbyState*>(m_state.get()))
|
2018-07-06 16:22:44 -03:00
|
|
|
standby->startFlipTransformation(this, flipType);
|
|
|
|
}
|
|
|
|
|
2020-09-18 19:29:43 -03:00
|
|
|
void Editor::updateTransformation(const Transformation& transform)
|
|
|
|
{
|
|
|
|
if (auto movingPixels = dynamic_cast<MovingPixelsState*>(m_state.get()))
|
|
|
|
movingPixels->updateTransformation(transform);
|
|
|
|
}
|
|
|
|
|
2014-04-28 01:45:22 -03:00
|
|
|
void Editor::notifyScrollChanged()
|
|
|
|
{
|
|
|
|
m_observers.notifyScrollChanged(this);
|
2019-02-20 15:57:20 -03:00
|
|
|
|
|
|
|
ASSERT(m_state);
|
|
|
|
if (m_state)
|
|
|
|
m_state->onScrollChange(this);
|
2020-03-20 15:04:53 -03:00
|
|
|
|
|
|
|
// Update status bar and mouse cursor
|
|
|
|
if (hasMouse()) {
|
|
|
|
updateStatusBar();
|
2021-03-02 13:50:49 -03:00
|
|
|
setCursor(mousePosInDisplay());
|
2020-03-20 15:04:53 -03:00
|
|
|
}
|
2014-04-28 01:45:22 -03:00
|
|
|
}
|
|
|
|
|
2016-02-11 21:09:31 -03:00
|
|
|
void Editor::notifyZoomChanged()
|
|
|
|
{
|
|
|
|
m_observers.notifyZoomChanged(this);
|
|
|
|
}
|
|
|
|
|
2017-01-06 17:44:08 -03:00
|
|
|
bool Editor::checkForScroll(ui::MouseMessage* msg)
|
|
|
|
{
|
|
|
|
tools::Ink* clickedInk = getCurrentEditorInk();
|
|
|
|
|
|
|
|
// Start scroll loop
|
|
|
|
if (msg->middle() || clickedInk->isScrollMovement()) { // TODO msg->middle() should be customizable
|
|
|
|
startScrollingState(msg);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::checkForZoom(ui::MouseMessage* msg)
|
|
|
|
{
|
|
|
|
tools::Ink* clickedInk = getCurrentEditorInk();
|
|
|
|
|
|
|
|
// Start scroll loop
|
|
|
|
if (clickedInk->isZoom()) {
|
|
|
|
startZoomingState(msg);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::startScrollingState(ui::MouseMessage* msg)
|
|
|
|
{
|
|
|
|
EditorStatePtr newState(new ScrollingState);
|
|
|
|
setState(newState);
|
|
|
|
newState->onMouseDown(this, msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::startZoomingState(ui::MouseMessage* msg)
|
|
|
|
{
|
|
|
|
EditorStatePtr newState(new ZoomingState);
|
|
|
|
setState(newState);
|
|
|
|
newState->onMouseDown(this, msg);
|
|
|
|
}
|
|
|
|
|
2016-11-30 10:23:29 -03:00
|
|
|
void Editor::play(const bool playOnce,
|
|
|
|
const bool playAll)
|
2015-04-06 16:12:28 -03:00
|
|
|
{
|
2015-05-28 11:13:15 -03:00
|
|
|
ASSERT(m_state);
|
|
|
|
if (!m_state)
|
|
|
|
return;
|
|
|
|
|
2016-12-07 08:40:08 -03:00
|
|
|
if (m_isPlaying)
|
|
|
|
stop();
|
|
|
|
|
|
|
|
m_isPlaying = true;
|
|
|
|
setState(EditorStatePtr(new PlayState(playOnce, playAll)));
|
2015-04-06 16:12:28 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::stop()
|
|
|
|
{
|
2015-05-28 11:13:15 -03:00
|
|
|
ASSERT(m_state);
|
|
|
|
if (!m_state)
|
|
|
|
return;
|
|
|
|
|
2016-12-07 08:40:08 -03:00
|
|
|
if (m_isPlaying) {
|
|
|
|
while (m_state && !dynamic_cast<PlayState*>(m_state.get()))
|
|
|
|
backToPreviousState();
|
|
|
|
|
|
|
|
m_isPlaying = false;
|
|
|
|
|
|
|
|
ASSERT(m_state && dynamic_cast<PlayState*>(m_state.get()));
|
|
|
|
if (m_state)
|
|
|
|
backToPreviousState();
|
|
|
|
}
|
2015-04-06 16:12:28 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::isPlaying() const
|
|
|
|
{
|
2016-12-07 08:40:08 -03:00
|
|
|
return m_isPlaying;
|
2015-04-06 16:12:28 -03:00
|
|
|
}
|
|
|
|
|
2015-12-18 18:51:30 -03:00
|
|
|
void Editor::showAnimationSpeedMultiplierPopup(Option<bool>& playOnce,
|
2016-11-30 10:23:29 -03:00
|
|
|
Option<bool>& playAll,
|
|
|
|
const bool withStopBehaviorOptions)
|
2015-05-07 19:08:24 -03:00
|
|
|
{
|
2016-11-30 10:23:29 -03:00
|
|
|
const double options[] = { 0.25, 0.5, 1.0, 1.5, 2.0, 3.0 };
|
2015-05-07 19:08:24 -03:00
|
|
|
Menu menu;
|
|
|
|
|
|
|
|
for (double option : options) {
|
2020-07-21 16:01:06 -03:00
|
|
|
MenuItem* item = new MenuItem(fmt::format("Speed x{}", option));
|
2020-07-03 21:51:46 -03:00
|
|
|
item->Click.connect([this, option]{ setAnimationSpeedMultiplier(option); });
|
2015-05-07 19:08:24 -03:00
|
|
|
item->setSelected(m_aniSpeed == option);
|
|
|
|
menu.addChild(item);
|
|
|
|
}
|
|
|
|
|
2015-12-18 18:51:30 -03:00
|
|
|
menu.addChild(new MenuSeparator);
|
|
|
|
|
|
|
|
// Play once option
|
|
|
|
{
|
|
|
|
MenuItem* item = new MenuItem("Play Once");
|
|
|
|
item->Click.connect(
|
|
|
|
[&playOnce]() {
|
|
|
|
playOnce(!playOnce());
|
|
|
|
});
|
|
|
|
item->setSelected(playOnce());
|
|
|
|
menu.addChild(item);
|
|
|
|
}
|
|
|
|
|
2016-11-30 10:23:29 -03:00
|
|
|
// Play all option
|
|
|
|
{
|
|
|
|
MenuItem* item = new MenuItem("Play All Frames (Ignore Tags)");
|
|
|
|
item->Click.connect(
|
|
|
|
[&playAll]() {
|
|
|
|
playAll(!playAll());
|
|
|
|
});
|
|
|
|
item->setSelected(playAll());
|
|
|
|
menu.addChild(item);
|
|
|
|
}
|
|
|
|
|
2015-06-19 12:09:34 -03:00
|
|
|
if (withStopBehaviorOptions) {
|
|
|
|
MenuItem* item = new MenuItem("Rewind on Stop");
|
|
|
|
item->Click.connect(
|
|
|
|
[]() {
|
|
|
|
// Switch the "rewind_on_stop" option
|
|
|
|
Preferences::instance().general.rewindOnStop(
|
|
|
|
!Preferences::instance().general.rewindOnStop());
|
|
|
|
});
|
|
|
|
item->setSelected(Preferences::instance().general.rewindOnStop());
|
|
|
|
menu.addChild(item);
|
|
|
|
}
|
|
|
|
|
2021-03-19 18:57:56 -03:00
|
|
|
menu.showPopup(mousePosInDisplay(), display());
|
2016-11-30 10:23:29 -03:00
|
|
|
|
|
|
|
if (isPlaying()) {
|
2016-11-30 12:35:01 -03:00
|
|
|
// Re-play
|
2016-11-30 10:23:29 -03:00
|
|
|
stop();
|
|
|
|
play(playOnce(),
|
|
|
|
playAll());
|
|
|
|
}
|
2015-05-07 19:08:24 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
double Editor::getAnimationSpeedMultiplier() const
|
|
|
|
{
|
|
|
|
return m_aniSpeed;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::setAnimationSpeedMultiplier(double speed)
|
|
|
|
{
|
|
|
|
m_aniSpeed = speed;
|
|
|
|
}
|
|
|
|
|
2017-04-06 18:41:18 -03:00
|
|
|
void Editor::showMouseCursor(CursorType cursorType,
|
|
|
|
const Cursor* cursor)
|
2015-06-25 12:44:47 -03:00
|
|
|
{
|
|
|
|
m_brushPreview.hide();
|
2017-04-06 18:41:18 -03:00
|
|
|
ui::set_mouse_cursor(cursorType, cursor);
|
2015-06-25 12:44:47 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::showBrushPreview(const gfx::Point& screenPos)
|
|
|
|
{
|
|
|
|
m_brushPreview.show(screenPos);
|
|
|
|
}
|
|
|
|
|
2016-05-23 19:22:02 -03:00
|
|
|
gfx::Point Editor::calcExtraPadding(const Projection& proj)
|
2015-08-04 12:26:58 -03:00
|
|
|
{
|
|
|
|
View* view = View::getView(this);
|
|
|
|
if (view) {
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-04 14:39:04 -03:00
|
|
|
Rect vp = view->viewportBounds();
|
2017-11-07 15:21:03 -03:00
|
|
|
gfx::Size canvas = canvasSize();
|
2015-08-04 12:26:58 -03:00
|
|
|
return gfx::Point(
|
2017-11-07 15:21:03 -03:00
|
|
|
std::max<int>(vp.w/2, vp.w - proj.applyX(canvas.w)),
|
|
|
|
std::max<int>(vp.h/2, vp.h - proj.applyY(canvas.h)));
|
2015-08-04 12:26:58 -03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
return gfx::Point(0, 0);
|
|
|
|
}
|
|
|
|
|
2017-11-07 15:21:03 -03:00
|
|
|
gfx::Size Editor::canvasSize() const
|
|
|
|
{
|
2021-05-19 16:36:43 -03:00
|
|
|
return m_tiledModeHelper.canvasSize();
|
2017-11-07 15:21:03 -03:00
|
|
|
}
|
|
|
|
|
2017-11-08 15:59:33 -03:00
|
|
|
gfx::Point Editor::mainTilePosition() const
|
|
|
|
{
|
2021-05-19 16:36:43 -03:00
|
|
|
return m_tiledModeHelper.mainTilePosition();
|
2017-11-08 15:59:33 -03:00
|
|
|
}
|
|
|
|
|
2017-11-10 16:29:47 -03:00
|
|
|
void Editor::expandRegionByTiledMode(gfx::Region& rgn,
|
|
|
|
const bool withProj) const
|
|
|
|
{
|
2021-05-19 16:36:43 -03:00
|
|
|
m_tiledModeHelper.expandRegionByTiledMode(rgn, withProj ? &m_proj : nullptr);
|
2017-11-10 16:29:47 -03:00
|
|
|
}
|
|
|
|
|
2019-09-05 18:04:05 -03:00
|
|
|
void Editor::collapseRegionByTiledMode(gfx::Region& rgn) const
|
|
|
|
{
|
2021-05-19 16:36:43 -03:00
|
|
|
m_tiledModeHelper.collapseRegionByTiledMode(rgn);
|
2019-09-05 18:04:05 -03:00
|
|
|
}
|
|
|
|
|
2015-08-13 17:25:39 -03:00
|
|
|
bool Editor::isMovingPixels() const
|
|
|
|
{
|
|
|
|
return (dynamic_cast<MovingPixelsState*>(m_state.get()) != nullptr);
|
|
|
|
}
|
|
|
|
|
2015-08-14 13:06:26 -03:00
|
|
|
void Editor::dropMovingPixels()
|
2015-08-13 17:25:39 -03:00
|
|
|
{
|
|
|
|
ASSERT(isMovingPixels());
|
|
|
|
backToPreviousState();
|
|
|
|
}
|
|
|
|
|
2021-06-08 20:34:15 -03:00
|
|
|
void Editor::invalidateCanvas()
|
|
|
|
{
|
|
|
|
if (m_sprite)
|
|
|
|
invalidateRect(editorToScreen(getVisibleSpriteBounds()));
|
|
|
|
else
|
|
|
|
invalidate();
|
|
|
|
}
|
|
|
|
|
2015-12-22 19:05:11 -03:00
|
|
|
void Editor::invalidateIfActive()
|
|
|
|
{
|
2021-06-08 20:34:15 -03:00
|
|
|
|
2015-12-22 19:05:11 -03:00
|
|
|
if (isActive())
|
|
|
|
invalidate();
|
|
|
|
}
|
|
|
|
|
2017-03-23 00:57:21 -03:00
|
|
|
void Editor::updateAutoCelGuides(ui::Message* msg)
|
|
|
|
{
|
|
|
|
Cel* oldShowGuidesThisCel = m_showGuidesThisCel;
|
2020-08-04 16:29:22 -03:00
|
|
|
bool oldShowAutoCelGuides = m_showAutoCelGuides;
|
|
|
|
|
|
|
|
m_showAutoCelGuides = (
|
|
|
|
msg &&
|
|
|
|
getCurrentEditorInk()->isCelMovement() &&
|
|
|
|
m_docPref.show.autoGuides() &&
|
|
|
|
m_customizationDelegate &&
|
|
|
|
int(m_customizationDelegate->getPressedKeyAction(KeyContext::MoveTool) & KeyAction::AutoSelectLayer));
|
2017-03-23 00:57:21 -03:00
|
|
|
|
|
|
|
// Check if the user is pressing the Ctrl or Cmd key on move
|
|
|
|
// tool to show automatic guides.
|
2020-08-04 16:29:22 -03:00
|
|
|
if (m_showAutoCelGuides &&
|
2017-03-23 00:57:21 -03:00
|
|
|
m_state->requireBrushPreview()) {
|
2020-09-25 09:48:56 -03:00
|
|
|
auto mouseMsg = dynamic_cast<ui::MouseMessage*>(msg);
|
2017-03-23 00:57:21 -03:00
|
|
|
|
|
|
|
ColorPicker picker;
|
|
|
|
picker.pickColor(getSite(),
|
|
|
|
screenToEditorF(mouseMsg ? mouseMsg->position():
|
2021-03-02 13:50:49 -03:00
|
|
|
mousePosInDisplay()),
|
2017-03-23 00:57:21 -03:00
|
|
|
m_proj, ColorPicker::FromComposition);
|
|
|
|
m_showGuidesThisCel = (picker.layer() ? picker.layer()->cel(m_frame):
|
|
|
|
nullptr);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
m_showGuidesThisCel = nullptr;
|
|
|
|
}
|
|
|
|
|
2020-08-04 16:29:22 -03:00
|
|
|
if (m_showGuidesThisCel != oldShowGuidesThisCel ||
|
|
|
|
m_showAutoCelGuides != oldShowAutoCelGuides) {
|
2017-03-23 00:57:21 -03:00
|
|
|
invalidate();
|
2020-08-04 16:29:22 -03:00
|
|
|
}
|
2017-03-23 00:57:21 -03:00
|
|
|
}
|
|
|
|
|
2017-12-06 14:44:40 -03:00
|
|
|
// static
|
|
|
|
void Editor::registerCommands()
|
|
|
|
{
|
|
|
|
Commands::instance()
|
|
|
|
->add(
|
|
|
|
new QuickCommand(
|
|
|
|
CommandId::SwitchNonactiveLayersOpacity(),
|
|
|
|
[]{
|
|
|
|
static int oldValue = -1;
|
|
|
|
auto& option = Preferences::instance().experimental.nonactiveLayersOpacity;
|
|
|
|
if (oldValue == -1) {
|
|
|
|
oldValue = option();
|
|
|
|
if (option() == 255)
|
|
|
|
option(128);
|
|
|
|
else
|
|
|
|
option(255);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
const int newValue = oldValue;
|
|
|
|
oldValue = option();
|
|
|
|
option(newValue);
|
|
|
|
}
|
|
|
|
app_refresh_screen();
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2013-08-05 21:20:19 -03:00
|
|
|
} // namespace app
|