mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-10 21:44:22 +00:00
1191 lines
30 KiB
C++
1191 lines
30 KiB
C++
/* ASEPRITE
|
|
* Copyright (C) 2001-2013 David Capello
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
// #define DRAWSPRITE_DOUBLEBUFFERED
|
|
|
|
#include "config.h"
|
|
|
|
#include "widgets/editor/editor.h"
|
|
|
|
#include "app.h"
|
|
#include "app/color.h"
|
|
#include "app/color_utils.h"
|
|
#include "base/bind.h"
|
|
#include "base/unique_ptr.h"
|
|
#include "commands/commands.h"
|
|
#include "commands/params.h"
|
|
#include "document_location.h"
|
|
#include "ini_file.h"
|
|
#include "modules/gfx.h"
|
|
#include "modules/gui.h"
|
|
#include "modules/palettes.h"
|
|
#include "raster/raster.h"
|
|
#include "settings/document_settings.h"
|
|
#include "settings/settings.h"
|
|
#include "skin/skin_theme.h"
|
|
#include "tools/ink.h"
|
|
#include "tools/tool.h"
|
|
#include "tools/tool_box.h"
|
|
#include "ui/gui.h"
|
|
#include "ui_context.h"
|
|
#include "util/boundary.h"
|
|
#include "util/misc.h"
|
|
#include "util/render.h"
|
|
#include "widgets/color_bar.h"
|
|
#include "widgets/editor/editor_customization_delegate.h"
|
|
#include "widgets/editor/editor_decorator.h"
|
|
#include "widgets/editor/moving_pixels_state.h"
|
|
#include "widgets/editor/pixels_movement.h"
|
|
#include "widgets/editor/standby_state.h"
|
|
#include "widgets/status_bar.h"
|
|
#include "widgets/toolbar.h"
|
|
|
|
#include <allegro.h>
|
|
#include <stdio.h>
|
|
|
|
using namespace gfx;
|
|
using namespace ui;
|
|
|
|
class EditorPreRenderImpl : public EditorPreRender
|
|
{
|
|
public:
|
|
EditorPreRenderImpl(Editor* editor, Image* image, const Point& offset, int zoom)
|
|
: m_editor(editor)
|
|
, m_image(image)
|
|
, m_offset(offset)
|
|
, m_zoom(zoom)
|
|
{
|
|
}
|
|
|
|
Editor* getEditor() OVERRIDE
|
|
{
|
|
return m_editor;
|
|
}
|
|
|
|
Image* getImage() OVERRIDE
|
|
{
|
|
return m_image;
|
|
}
|
|
|
|
void fillRect(const gfx::Rect& rect, uint32_t rgbaColor, int opacity) OVERRIDE
|
|
{
|
|
image_rectblend(m_image,
|
|
m_offset.x + (rect.x << m_zoom),
|
|
m_offset.y + (rect.y << m_zoom),
|
|
m_offset.x + ((rect.x+rect.w) << m_zoom) - 1,
|
|
m_offset.y + ((rect.y+rect.h) << m_zoom) - 1, rgbaColor, opacity);
|
|
}
|
|
|
|
private:
|
|
Editor* m_editor;
|
|
Image* m_image;
|
|
Point m_offset;
|
|
int m_zoom;
|
|
};
|
|
|
|
class EditorPostRenderImpl : public EditorPostRender
|
|
{
|
|
public:
|
|
EditorPostRenderImpl(Editor* editor)
|
|
: m_editor(editor)
|
|
{
|
|
}
|
|
|
|
Editor* getEditor()
|
|
{
|
|
return m_editor;
|
|
}
|
|
|
|
void drawLine(int x1, int y1, int x2, int y2, int screenColor)
|
|
{
|
|
int u1, v1, u2, v2;
|
|
m_editor->editorToScreen(x1, y1, &u1, &v1);
|
|
m_editor->editorToScreen(x2, y2, &u2, &v2);
|
|
line(ji_screen, u1, v1, u2, v2, screenColor);
|
|
}
|
|
|
|
private:
|
|
Editor* m_editor;
|
|
};
|
|
|
|
Editor::Editor(Document* document)
|
|
: Widget(editor_type())
|
|
, m_state(new StandbyState())
|
|
, m_decorator(NULL)
|
|
, m_document(document)
|
|
, m_sprite(m_document->getSprite())
|
|
, m_layer(m_sprite->getFolder()->getFirstLayer())
|
|
, m_frame(FrameNumber(0))
|
|
, m_zoom(0)
|
|
, m_mask_timer(100, this)
|
|
, m_customizationDelegate(NULL)
|
|
, m_docView(NULL)
|
|
{
|
|
// Add the first state into the history.
|
|
m_statesHistory.push(m_state);
|
|
|
|
m_cursor_thick = 0;
|
|
m_cursor_screen_x = 0;
|
|
m_cursor_screen_y = 0;
|
|
m_cursor_editor_x = 0;
|
|
m_cursor_editor_y = 0;
|
|
|
|
m_quicktool = NULL;
|
|
|
|
m_offset_x = 0;
|
|
m_offset_y = 0;
|
|
m_offset_count = 0;
|
|
|
|
this->setFocusStop(true);
|
|
|
|
m_currentToolChangeSlot =
|
|
App::instance()->CurrentToolChange.connect(&Editor::onCurrentToolChange, this);
|
|
|
|
m_fgColorChangeSlot =
|
|
ColorBar::instance()->FgColorChange.connect(Bind<void>(&Editor::onFgColorChange, this));
|
|
}
|
|
|
|
Editor::~Editor()
|
|
{
|
|
setCustomizationDelegate(NULL);
|
|
|
|
m_mask_timer.stop();
|
|
|
|
// Remove this editor as observer of CurrentToolChange signal.
|
|
App::instance()->CurrentToolChange.disconnect(m_currentToolChangeSlot);
|
|
delete m_currentToolChangeSlot;
|
|
|
|
// Remove this editor as observer of FgColorChange
|
|
ColorBar::instance()->FgColorChange.disconnect(m_fgColorChangeSlot);
|
|
delete m_fgColorChangeSlot;
|
|
}
|
|
|
|
WidgetType editor_type()
|
|
{
|
|
static WidgetType type = kGenericWidget;
|
|
if (type == kGenericWidget)
|
|
type = register_widget_type();
|
|
return type;
|
|
}
|
|
|
|
void Editor::setStateInternal(const EditorStatePtr& newState)
|
|
{
|
|
hideDrawingCursor();
|
|
|
|
// Fire before change state event, set the state, and fire after
|
|
// change state event.
|
|
EditorState::BeforeChangeAction beforeChangeAction =
|
|
m_state->onBeforeChangeState(this, newState);
|
|
|
|
// Push a new state
|
|
if (newState) {
|
|
if (beforeChangeAction == EditorState::DiscardState)
|
|
m_statesHistory.pop();
|
|
|
|
m_statesHistory.push(newState);
|
|
m_state = newState;
|
|
}
|
|
// Go to previous state
|
|
else {
|
|
m_state->onBeforePopState(this);
|
|
|
|
m_statesHistory.pop();
|
|
m_state = m_statesHistory.top();
|
|
}
|
|
|
|
ASSERT(m_state != NULL);
|
|
|
|
// Change to the new state.
|
|
m_state->onAfterChangeState(this);
|
|
|
|
// Redraw all the editors with the same document of this editor
|
|
update_screen_for_document(m_document);
|
|
|
|
// Clear keyboard buffer just in case (to avoid sending keys to the
|
|
// new state).
|
|
clear_keybuf();
|
|
|
|
// Notify observers
|
|
m_observers.notifyStateChanged(this);
|
|
|
|
// Setup the new mouse cursor
|
|
editor_setcursor();
|
|
|
|
updateStatusBar();
|
|
}
|
|
|
|
void Editor::setState(const EditorStatePtr& newState)
|
|
{
|
|
setStateInternal(newState);
|
|
}
|
|
|
|
void Editor::backToPreviousState()
|
|
{
|
|
setStateInternal(EditorStatePtr(NULL));
|
|
}
|
|
|
|
void Editor::setLayer(const Layer* layer)
|
|
{
|
|
m_layer = const_cast<Layer*>(layer);
|
|
updateStatusBar();
|
|
}
|
|
|
|
void Editor::setFrame(FrameNumber frame)
|
|
{
|
|
if (m_frame != frame) {
|
|
m_frame = frame;
|
|
m_observers.notifyFrameChanged(this);
|
|
|
|
invalidate();
|
|
updateStatusBar();
|
|
}
|
|
}
|
|
|
|
void Editor::getDocumentLocation(DocumentLocation* location) const
|
|
{
|
|
location->document(m_document);
|
|
location->sprite(m_sprite);
|
|
location->layer(m_layer);
|
|
location->frame(m_frame);
|
|
}
|
|
|
|
DocumentLocation Editor::getDocumentLocation() const
|
|
{
|
|
DocumentLocation location;
|
|
getDocumentLocation(&location);
|
|
return location;
|
|
}
|
|
|
|
void Editor::setDefaultScroll()
|
|
{
|
|
View* view = View::getView(this);
|
|
Rect vp = view->getViewportBounds();
|
|
|
|
setEditorScroll(m_offset_x - vp.w/2 + (m_sprite->getWidth()/2),
|
|
m_offset_y - vp.h/2 + (m_sprite->getHeight()/2), false);
|
|
}
|
|
|
|
// Sets the scroll position of the editor
|
|
void Editor::setEditorScroll(int x, int y, int use_refresh_region)
|
|
{
|
|
View* view = View::getView(this);
|
|
Point oldScroll;
|
|
Region region;
|
|
int thick = m_cursor_thick;
|
|
|
|
if (thick)
|
|
editor_clean_cursor();
|
|
|
|
if (use_refresh_region) {
|
|
getDrawableRegion(region, kCutTopWindows);
|
|
oldScroll = view->getViewScroll();
|
|
}
|
|
|
|
view->setViewScroll(Point(x, y));
|
|
Point newScroll = view->getViewScroll();
|
|
|
|
if (use_refresh_region) {
|
|
// Move screen with blits
|
|
scrollRegion(region,
|
|
oldScroll.x - newScroll.x,
|
|
oldScroll.y - newScroll.y);
|
|
}
|
|
|
|
if (thick)
|
|
editor_draw_cursor(m_cursor_screen_x, m_cursor_screen_y);
|
|
|
|
// Notify observers
|
|
m_observers.notifyScrollChanged(this);
|
|
}
|
|
|
|
void Editor::updateEditor()
|
|
{
|
|
View::getView(this)->updateView();
|
|
}
|
|
|
|
void Editor::drawSpriteUnclippedRect(const gfx::Rect& rc)
|
|
{
|
|
View* view = View::getView(this);
|
|
Rect vp = view->getViewportBounds();
|
|
int source_x, source_y, dest_x, dest_y, width, height;
|
|
|
|
// Get scroll
|
|
|
|
Point scroll = view->getViewScroll();
|
|
|
|
// Output information
|
|
|
|
source_x = rc.x << m_zoom;
|
|
source_y = rc.y << m_zoom;
|
|
dest_x = vp.x - scroll.x + m_offset_x + source_x;
|
|
dest_y = vp.y - scroll.y + m_offset_y + source_y;
|
|
width = rc.w << m_zoom;
|
|
height = rc.h << m_zoom;
|
|
|
|
// Clip from viewport
|
|
|
|
if (dest_x < vp.x) {
|
|
source_x += vp.x - dest_x;
|
|
width -= vp.x - dest_x;
|
|
dest_x = vp.x;
|
|
}
|
|
|
|
if (dest_y < vp.y) {
|
|
source_y += vp.y - dest_y;
|
|
height -= vp.y - dest_y;
|
|
dest_y = vp.y;
|
|
}
|
|
|
|
if (dest_x+width-1 > vp.x + vp.w-1)
|
|
width = vp.x + vp.w - dest_x;
|
|
|
|
if (dest_y+height-1 > vp.y + vp.h-1)
|
|
height = vp.y + vp.h - dest_y;
|
|
|
|
// Clip from screen
|
|
|
|
if (dest_x < ji_screen->cl) {
|
|
source_x += ji_screen->cl - dest_x;
|
|
width -= ji_screen->cl - dest_x;
|
|
dest_x = ji_screen->cl;
|
|
}
|
|
|
|
if (dest_y < ji_screen->ct) {
|
|
source_y += ji_screen->ct - dest_y;
|
|
height -= ji_screen->ct - dest_y;
|
|
dest_y = ji_screen->ct;
|
|
}
|
|
|
|
if (dest_x+width-1 >= ji_screen->cr)
|
|
width = ji_screen->cr-dest_x;
|
|
|
|
if (dest_y+height-1 >= ji_screen->cb)
|
|
height = ji_screen->cb-dest_y;
|
|
|
|
// Clip from sprite
|
|
|
|
if (source_x < 0) {
|
|
width += source_x;
|
|
dest_x -= source_x;
|
|
source_x = 0;
|
|
}
|
|
|
|
if (source_y < 0) {
|
|
height += source_y;
|
|
dest_y -= source_y;
|
|
source_y = 0;
|
|
}
|
|
|
|
if (source_x+width > (m_sprite->getWidth() << m_zoom)) {
|
|
width = (m_sprite->getWidth() << m_zoom) - source_x;
|
|
}
|
|
|
|
if (source_y+height > (m_sprite->getHeight() << m_zoom)) {
|
|
height = (m_sprite->getHeight() << m_zoom) - source_y;
|
|
}
|
|
|
|
// Draw the sprite
|
|
|
|
if ((width > 0) && (height > 0)) {
|
|
RenderEngine renderEngine(m_document, m_sprite, m_layer, m_frame);
|
|
|
|
// Generate the rendered image
|
|
UniquePtr<Image> rendered
|
|
(renderEngine.renderSprite(source_x, source_y, width, height,
|
|
m_frame, m_zoom, true));
|
|
|
|
if (rendered) {
|
|
// Pre-render decorator.
|
|
if (m_decorator) {
|
|
EditorPreRenderImpl preRender(this, rendered,
|
|
Point(-source_x, -source_y), m_zoom);
|
|
m_decorator->preRenderDecorator(&preRender);
|
|
}
|
|
|
|
#ifdef DRAWSPRITE_DOUBLEBUFFERED
|
|
BITMAP *bmp = create_bitmap(width, height);
|
|
|
|
image_to_allegro(rendered, bmp, 0, 0, m_sprite->getPalette(m_frame));
|
|
blit(bmp, ji_screen, 0, 0, dest_x, dest_y, width, height);
|
|
|
|
destroy_bitmap(bmp);
|
|
#else
|
|
acquire_bitmap(ji_screen);
|
|
image_to_allegro(rendered, ji_screen, dest_x, dest_y,
|
|
m_sprite->getPalette(m_frame));
|
|
release_bitmap(ji_screen);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// Draw grids
|
|
IDocumentSettings* docSettings =
|
|
UIContext::instance()->getSettings()->getDocumentSettings(m_document);
|
|
|
|
// Draw the pixel grid
|
|
if (docSettings->getPixelGridVisible()) {
|
|
if (m_zoom > 1)
|
|
this->drawGrid(Rect(0, 0, 1, 1),
|
|
docSettings->getPixelGridColor());
|
|
}
|
|
|
|
// Draw the grid
|
|
if (docSettings->getGridVisible())
|
|
this->drawGrid(docSettings->getGridBounds(),
|
|
docSettings->getGridColor());
|
|
|
|
// Draw the mask
|
|
if (m_document->getBoundariesSegments())
|
|
this->drawMask();
|
|
|
|
// Post-render decorator.
|
|
if (m_decorator) {
|
|
EditorPostRenderImpl postRender(this);
|
|
m_decorator->postRenderDecorator(&postRender);
|
|
}
|
|
}
|
|
|
|
void Editor::drawSpriteClipped(const gfx::Region& updateRegion)
|
|
{
|
|
Region region;
|
|
getDrawableRegion(region, kCutTopWindows);
|
|
|
|
int cx1, cy1, cx2, cy2;
|
|
get_clip_rect(ji_screen, &cx1, &cy1, &cx2, &cy2);
|
|
|
|
for (Region::const_iterator
|
|
it=region.begin(), end=region.end(); it != end; ++it) {
|
|
const Rect& rc = *it;
|
|
|
|
add_clip_rect(ji_screen, rc.x, rc.y, rc.x2()-1, rc.y2()-1);
|
|
|
|
for (Region::const_iterator
|
|
it2=updateRegion.begin(), end2=updateRegion.end(); it2 != end2; ++it2) {
|
|
drawSpriteUnclippedRect(*it2);
|
|
}
|
|
|
|
set_clip_rect(ji_screen, cx1, cy1, cx2, cy2);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
void Editor::drawMask()
|
|
{
|
|
View* view = View::getView(this);
|
|
Rect vp = view->getViewportBounds();
|
|
Point scroll = view->getViewScroll();
|
|
int x1, y1, x2, y2;
|
|
int c, x, y;
|
|
|
|
dotted_mode(m_offset_count);
|
|
|
|
x = vp.x - scroll.x + m_offset_x;
|
|
y = vp.y - scroll.y + m_offset_y;
|
|
|
|
int nseg = m_document->getBoundariesSegmentsCount();
|
|
const _BoundSeg* seg = m_document->getBoundariesSegments();
|
|
|
|
for (c=0; c<nseg; ++c, ++seg) {
|
|
x1 = seg->x1<<m_zoom;
|
|
y1 = seg->y1<<m_zoom;
|
|
x2 = seg->x2<<m_zoom;
|
|
y2 = seg->y2<<m_zoom;
|
|
|
|
#if 1 // Bounds inside mask
|
|
if (!seg->open)
|
|
#else // Bounds outside mask
|
|
if (seg->open)
|
|
#endif
|
|
{
|
|
if (x1 == x2) {
|
|
x1--;
|
|
x2--;
|
|
y2--;
|
|
}
|
|
else {
|
|
y1--;
|
|
y2--;
|
|
x2--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (x1 == x2) {
|
|
y2--;
|
|
}
|
|
else {
|
|
x2--;
|
|
}
|
|
}
|
|
|
|
line(ji_screen, x+x1, y+y1, x+x2, y+y2, 0);
|
|
}
|
|
|
|
dotted_mode(-1);
|
|
}
|
|
|
|
void Editor::drawMaskSafe()
|
|
{
|
|
if (isVisible() &&
|
|
m_document &&
|
|
m_document->getBoundariesSegments()) {
|
|
int thick = m_cursor_thick;
|
|
|
|
Region region;
|
|
getDrawableRegion(region, kCutTopWindows);
|
|
|
|
acquire_bitmap(ji_screen);
|
|
|
|
if (thick)
|
|
editor_clean_cursor();
|
|
else
|
|
jmouse_hide();
|
|
|
|
for (Region::const_iterator it=region.begin(), end=region.end();
|
|
it != end; ++it) {
|
|
const Rect& rc = *it;
|
|
set_clip_rect(ji_screen, rc.x, rc.y, rc.x2()-1, rc.y2()-1);
|
|
drawMask();
|
|
}
|
|
set_clip_rect(ji_screen, 0, 0, JI_SCREEN_W-1, JI_SCREEN_H-1);
|
|
|
|
// Draw the cursor
|
|
if (thick)
|
|
editor_draw_cursor(m_cursor_screen_x, m_cursor_screen_y);
|
|
else
|
|
jmouse_show();
|
|
|
|
release_bitmap(ji_screen);
|
|
}
|
|
}
|
|
|
|
void Editor::drawGrid(const Rect& gridBounds, const app::Color& color)
|
|
{
|
|
// Copy the grid bounds
|
|
Rect grid(gridBounds);
|
|
if (grid.w < 1 || grid.h < 1)
|
|
return;
|
|
|
|
// 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;
|
|
|
|
// Change the grid position to the first grid's tile
|
|
grid.setOrigin(Point((grid.x % grid.w) - grid.w,
|
|
(grid.y % grid.h) - grid.h));
|
|
if (grid.x < 0) grid.x += grid.w;
|
|
if (grid.y < 0) grid.y += grid.h;
|
|
|
|
// Convert the "grid" rectangle to screen coordinates
|
|
editorToScreen(grid, &grid);
|
|
|
|
// Get the grid's color
|
|
int grid_color = color_utils::color_for_allegro(color, bitmap_color_depth(ji_screen));
|
|
|
|
// Get the position of the sprite in the screen.
|
|
Rect spriteRect;
|
|
editorToScreen(Rect(0, 0, m_sprite->getWidth(), m_sprite->getHeight()), &spriteRect);
|
|
|
|
// Draw horizontal lines
|
|
int x1 = spriteRect.x;
|
|
int y1 = grid.y;
|
|
int x2 = spriteRect.x+spriteRect.w;
|
|
int y2 = spriteRect.y+spriteRect.h;
|
|
|
|
for (int c=y1; c<=y2; c+=grid.h)
|
|
hline(ji_screen, x1, c, x2, grid_color);
|
|
|
|
// Draw vertical lines
|
|
x1 = grid.x;
|
|
y1 = spriteRect.y;
|
|
|
|
for (int c=x1; c<=x2; c+=grid.w)
|
|
vline(ji_screen, c, y1, y2, grid_color);
|
|
}
|
|
|
|
void Editor::flashCurrentLayer()
|
|
{
|
|
#if 0 // TODO this flash effect can be done
|
|
// only with hardware acceleration.
|
|
// Finish it when the
|
|
// Allegro 5 port is ready.
|
|
int x, y;
|
|
const Image* src_image = m_sprite->getCurrentImage(&x, &y);
|
|
if (src_image) {
|
|
m_document->prepareExtraCel(0, 0, m_sprite->getWidth(), m_sprite->getHeight(), 255);
|
|
Image* flash_image = m_document->getExtraCelImage();
|
|
int u, v;
|
|
|
|
image_clear(flash_image, flash_image->mask_color);
|
|
for (v=0; v<flash_image->h; ++v) {
|
|
for (u=0; u<flash_image->w; ++u) {
|
|
if (u-x >= 0 && u-x < src_image->w &&
|
|
v-y >= 0 && v-y < src_image->h) {
|
|
uint32_t color = image_getpixel(src_image, u-x, v-y);
|
|
if (color != src_image->mask_color) {
|
|
Color ccc = Color::fromRgb(255, 255, 255);
|
|
image_putpixel(flash_image, u, v,
|
|
color_utils::color_for_image(ccc, flash_image->imgtype));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
drawSpriteSafe(0, 0, m_sprite->getWidth()-1, m_sprite->getHeight()-1);
|
|
gui_flip_screen();
|
|
|
|
image_clear(flash_image, flash_image->mask_color);
|
|
drawSpriteSafe(0, 0, m_sprite->getWidth()-1, m_sprite->getHeight()-1);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Editor::controlInfiniteScroll(Message* msg)
|
|
{
|
|
View* view = View::getView(this);
|
|
Rect vp = view->getViewportBounds();
|
|
|
|
if (jmouse_control_infinite_scroll(vp)) {
|
|
int old_x = msg->mouse.x;
|
|
int old_y = msg->mouse.y;
|
|
|
|
msg->mouse.x = jmouse_x(0);
|
|
msg->mouse.y = jmouse_y(0);
|
|
|
|
// Smooth scroll movement
|
|
if (get_config_bool("Options", "MoveSmooth", TRUE)) {
|
|
jmouse_set_position(MID(vp.x+1, old_x, vp.x+vp.w-2),
|
|
MID(vp.y+1, old_y, vp.y+vp.h-2));
|
|
}
|
|
// This is better for high resolutions: scroll movement by big steps
|
|
else {
|
|
jmouse_set_position((old_x != msg->mouse.x) ? (old_x + (vp.x+vp.w/2))/2: msg->mouse.x,
|
|
(old_y != msg->mouse.y) ? (old_y + (vp.y+vp.h/2))/2: msg->mouse.y);
|
|
}
|
|
|
|
msg->mouse.x = jmouse_x(0);
|
|
msg->mouse.y = jmouse_y(0);
|
|
|
|
Point scroll = view->getViewScroll();
|
|
setEditorScroll(scroll.x+old_x-msg->mouse.x,
|
|
scroll.y+old_y-msg->mouse.y, true);
|
|
}
|
|
}
|
|
|
|
tools::Tool* Editor::getCurrentEditorTool()
|
|
{
|
|
if (m_quicktool)
|
|
return m_quicktool;
|
|
else {
|
|
UIContext* context = UIContext::instance();
|
|
return context->getSettings()->getCurrentTool();
|
|
}
|
|
}
|
|
|
|
void Editor::screenToEditor(int xin, int yin, int* xout, int* yout)
|
|
{
|
|
View* view = View::getView(this);
|
|
Rect vp = view->getViewportBounds();
|
|
Point scroll = view->getViewScroll();
|
|
|
|
*xout = (xin - vp.x + scroll.x - m_offset_x) >> m_zoom;
|
|
*yout = (yin - vp.y + scroll.y - m_offset_y) >> m_zoom;
|
|
}
|
|
|
|
void Editor::screenToEditor(const Rect& in, Rect* out)
|
|
{
|
|
int x1, y1, x2, y2;
|
|
screenToEditor(in.x, in.y, &x1, &y1);
|
|
screenToEditor(in.x+in.w, in.y+in.h, &x2, &y2);
|
|
*out = Rect(x1, y1, x2 - x1, y2 - y1);
|
|
}
|
|
|
|
void Editor::editorToScreen(int xin, int yin, int* xout, int* yout)
|
|
{
|
|
View* view = View::getView(this);
|
|
Rect vp = view->getViewportBounds();
|
|
Point scroll = view->getViewScroll();
|
|
|
|
*xout = (vp.x - scroll.x + m_offset_x + (xin << m_zoom));
|
|
*yout = (vp.y - scroll.y + m_offset_y + (yin << m_zoom));
|
|
}
|
|
|
|
void Editor::editorToScreen(const Rect& in, Rect* out)
|
|
{
|
|
int x1, y1, x2, y2;
|
|
editorToScreen(in.x, in.y, &x1, &y1);
|
|
editorToScreen(in.x+in.w, in.y+in.h, &x2, &y2);
|
|
*out = Rect(x1, y1, x2 - x1, y2 - y1);
|
|
}
|
|
|
|
void Editor::showDrawingCursor()
|
|
{
|
|
ASSERT(m_sprite != NULL);
|
|
|
|
if (!m_cursor_thick && canDraw()) {
|
|
jmouse_hide();
|
|
editor_draw_cursor(jmouse_x(0), jmouse_y(0));
|
|
jmouse_show();
|
|
}
|
|
}
|
|
|
|
void Editor::hideDrawingCursor()
|
|
{
|
|
if (m_cursor_thick) {
|
|
jmouse_hide();
|
|
editor_clean_cursor();
|
|
jmouse_show();
|
|
}
|
|
}
|
|
|
|
void Editor::moveDrawingCursor()
|
|
{
|
|
// Draw cursor
|
|
if (m_cursor_thick) {
|
|
int x, y;
|
|
|
|
x = jmouse_x(0);
|
|
y = jmouse_y(0);
|
|
|
|
// Redraw it only when the mouse change to other pixel (not
|
|
// when the mouse moves only).
|
|
if ((m_cursor_screen_x != x) || (m_cursor_screen_y != y)) {
|
|
jmouse_hide();
|
|
editor_move_cursor(x, y);
|
|
jmouse_show();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Editor::addObserver(EditorObserver* observer)
|
|
{
|
|
m_observers.addObserver(observer);
|
|
}
|
|
|
|
void Editor::removeObserver(EditorObserver* observer)
|
|
{
|
|
m_observers.removeObserver(observer);
|
|
}
|
|
|
|
void Editor::setCustomizationDelegate(EditorCustomizationDelegate* delegate)
|
|
{
|
|
if (m_customizationDelegate)
|
|
m_customizationDelegate->dispose();
|
|
|
|
m_customizationDelegate = delegate;
|
|
}
|
|
|
|
// Returns the visible area of the active sprite.
|
|
Rect Editor::getVisibleSpriteBounds()
|
|
{
|
|
// Return an empty rectangle if there is not a active sprite.
|
|
if (!m_sprite) return Rect();
|
|
|
|
View* view = View::getView(this);
|
|
Rect vp = view->getViewportBounds();
|
|
int x1, y1, x2, y2;
|
|
|
|
screenToEditor(vp.x, vp.y, &x1, &y1);
|
|
screenToEditor(vp.x+vp.w-1, vp.y+vp.h-1, &x2, &y2);
|
|
|
|
return Rect(x1, y1, x2-x1+1, y2-y1+1);
|
|
}
|
|
|
|
// Changes the scroll to see the given point as the center of the editor.
|
|
void Editor::centerInSpritePoint(int x, int y)
|
|
{
|
|
View* view = View::getView(this);
|
|
Rect vp = view->getViewportBounds();
|
|
|
|
hideDrawingCursor();
|
|
|
|
x = m_offset_x - (vp.w/2) + ((1<<m_zoom)>>1) + (x << m_zoom);
|
|
y = m_offset_y - (vp.h/2) + ((1<<m_zoom)>>1) + (y << m_zoom);
|
|
|
|
updateEditor();
|
|
setEditorScroll(x, y, false);
|
|
|
|
showDrawingCursor();
|
|
invalidate();
|
|
|
|
// Notify observers
|
|
m_observers.notifyScrollChanged(this);
|
|
}
|
|
|
|
void Editor::updateStatusBar()
|
|
{
|
|
// Setup status bar using the current editor's state
|
|
m_state->onUpdateStatusBar(this);
|
|
}
|
|
|
|
void Editor::editor_update_quicktool()
|
|
{
|
|
if (m_customizationDelegate) {
|
|
UIContext* context = UIContext::instance();
|
|
tools::Tool* current_tool = context->getSettings()->getCurrentTool();
|
|
tools::Tool* old_quicktool = m_quicktool;
|
|
|
|
m_quicktool = m_customizationDelegate->getQuickTool(current_tool);
|
|
|
|
// If the tool has changed, we must to update the status bar because
|
|
// the new tool can display something different in the status bar (e.g. Eyedropper)
|
|
if (old_quicktool != m_quicktool)
|
|
updateStatusBar();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Message handler for the editor
|
|
|
|
bool Editor::onProcessMessage(Message* msg)
|
|
{
|
|
switch (msg->type) {
|
|
|
|
case kPaintMessage: {
|
|
SkinTheme* theme = static_cast<SkinTheme*>(this->getTheme());
|
|
|
|
int old_cursor_thick = m_cursor_thick;
|
|
if (m_cursor_thick)
|
|
editor_clean_cursor();
|
|
|
|
// Editor without sprite
|
|
if (!m_sprite) {
|
|
View* view = View::getView(this);
|
|
Rect vp = view->getViewportBounds();
|
|
|
|
jdraw_rectfill(vp, theme->getColor(ThemeColor::EditorFace));
|
|
draw_emptyset_symbol(ji_screen, vp, ui::rgba(64, 64, 64));
|
|
}
|
|
// Editor with sprite
|
|
else {
|
|
try {
|
|
// Lock the sprite to read/render it.
|
|
DocumentReader documentReader(m_document);
|
|
int x1, y1, x2, y2;
|
|
|
|
// Draw the background outside of sprite's bounds
|
|
x1 = this->rc->x1 + m_offset_x;
|
|
y1 = this->rc->y1 + m_offset_y;
|
|
x2 = x1 + (m_sprite->getWidth() << m_zoom) - 1;
|
|
y2 = y1 + (m_sprite->getHeight() << m_zoom) - 1;
|
|
|
|
jrectexclude(ji_screen,
|
|
this->rc->x1, this->rc->y1,
|
|
this->rc->x2-1, this->rc->y2-1,
|
|
x1-1, y1-1, x2+1, y2+2, theme->getColor(ThemeColor::EditorFace));
|
|
|
|
// Draw the sprite in the editor
|
|
drawSpriteUnclippedRect(gfx::Rect(0, 0, m_sprite->getWidth(), m_sprite->getHeight()));
|
|
|
|
// Draw the sprite boundary
|
|
rect(ji_screen, x1-1, y1-1, x2+1, y2+1, to_system(theme->getColor(ThemeColor::EditorSpriteBorder)));
|
|
hline(ji_screen, x1-1, y2+2, x2+1, to_system(theme->getColor(ThemeColor::EditorSpriteBottomBorder)));
|
|
|
|
// Draw the mask boundaries
|
|
if (m_document->getBoundariesSegments()) {
|
|
drawMask();
|
|
m_mask_timer.start();
|
|
}
|
|
else {
|
|
m_mask_timer.stop();
|
|
}
|
|
|
|
// Draw the cursor again
|
|
if (old_cursor_thick != 0) {
|
|
editor_draw_cursor(jmouse_x(0), jmouse_y(0));
|
|
}
|
|
}
|
|
catch (const LockedDocumentException&) {
|
|
// The sprite is locked to be read, so we can draw an opaque
|
|
// background only.
|
|
|
|
View* view = View::getView(this);
|
|
Rect vp = view->getViewportBounds();
|
|
jdraw_rectfill(vp, theme->getColor(ThemeColor::EditorFace));
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
case kTimerMessage:
|
|
if (msg->timer.timer == &m_mask_timer) {
|
|
if (isVisible() && m_sprite) {
|
|
drawMaskSafe();
|
|
|
|
// Set offset to make selection-movement effect
|
|
if (m_offset_count < 7)
|
|
m_offset_count++;
|
|
else
|
|
m_offset_count = 0;
|
|
}
|
|
else if (m_mask_timer.isRunning()) {
|
|
m_mask_timer.stop();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case kMouseEnterMessage:
|
|
editor_update_quicktool();
|
|
break;
|
|
|
|
case kMouseLeaveMessage:
|
|
hideDrawingCursor();
|
|
StatusBar::instance()->clearText();
|
|
break;
|
|
|
|
case kMouseDownMessage:
|
|
if (m_sprite) {
|
|
EditorStatePtr holdState(m_state);
|
|
return m_state->onMouseDown(this, msg);
|
|
}
|
|
break;
|
|
|
|
case kMouseMoveMessage:
|
|
if (m_sprite) {
|
|
EditorStatePtr holdState(m_state);
|
|
return m_state->onMouseMove(this, msg);
|
|
}
|
|
break;
|
|
|
|
case kMouseUpMessage:
|
|
if (m_sprite) {
|
|
EditorStatePtr holdState(m_state);
|
|
if (m_state->onMouseUp(this, msg))
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case kKeyDownMessage:
|
|
if (m_sprite) {
|
|
EditorStatePtr holdState(m_state);
|
|
bool used = m_state->onKeyDown(this, msg);
|
|
|
|
if (hasMouse()) {
|
|
editor_update_quicktool();
|
|
editor_setcursor();
|
|
}
|
|
|
|
if (used)
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case kKeyUpMessage:
|
|
if (m_sprite) {
|
|
EditorStatePtr holdState(m_state);
|
|
bool used = m_state->onKeyUp(this, msg);
|
|
|
|
if (hasMouse()) {
|
|
editor_update_quicktool();
|
|
editor_setcursor();
|
|
}
|
|
|
|
if (used)
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case kFocusLeaveMessage:
|
|
// As we use keys like Space-bar as modifier, we can clear the
|
|
// keyboard buffer when we lost the focus.
|
|
clear_keybuf();
|
|
break;
|
|
|
|
case kMouseWheelMessage:
|
|
if (m_sprite && hasMouse()) {
|
|
EditorStatePtr holdState(m_state);
|
|
if (m_state->onMouseWheel(this, msg))
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case kSetCursorMessage:
|
|
editor_setcursor();
|
|
return true;
|
|
}
|
|
|
|
return Widget::onProcessMessage(msg);
|
|
}
|
|
|
|
void Editor::onPreferredSize(PreferredSizeEvent& ev)
|
|
{
|
|
gfx::Size sz(0, 0);
|
|
|
|
if (m_sprite) {
|
|
View* view = View::getView(this);
|
|
Rect vp = view->getViewportBounds();
|
|
|
|
m_offset_x = std::max<int>(vp.w/2, vp.w - m_sprite->getWidth()/2);
|
|
m_offset_y = std::max<int>(vp.h/2, vp.h - m_sprite->getHeight()/2);
|
|
|
|
sz.w = (m_sprite->getWidth() << m_zoom) + m_offset_x*2;
|
|
sz.h = (m_sprite->getHeight() << m_zoom) + m_offset_y*2;
|
|
}
|
|
else {
|
|
sz.w = 4;
|
|
sz.h = 4;
|
|
}
|
|
ev.setPreferredSize(sz);
|
|
}
|
|
|
|
// When the current tool is changed
|
|
void Editor::onCurrentToolChange()
|
|
{
|
|
m_state->onCurrentToolChange(this);
|
|
}
|
|
|
|
void Editor::onFgColorChange()
|
|
{
|
|
if (m_cursor_thick) {
|
|
hideDrawingCursor();
|
|
showDrawingCursor();
|
|
}
|
|
}
|
|
|
|
void Editor::editor_setcursor()
|
|
{
|
|
bool used = false;
|
|
if (m_sprite)
|
|
used = m_state->onSetCursor(this);
|
|
|
|
if (!used) {
|
|
hideDrawingCursor();
|
|
jmouse_set_cursor(kArrowCursor);
|
|
}
|
|
}
|
|
|
|
bool Editor::canDraw()
|
|
{
|
|
return (m_layer != NULL &&
|
|
m_layer->isImage() &&
|
|
m_layer->isReadable() &&
|
|
m_layer->isWritable());
|
|
}
|
|
|
|
bool Editor::isInsideSelection()
|
|
{
|
|
int x, y;
|
|
screenToEditor(jmouse_x(0), jmouse_y(0), &x, &y);
|
|
return
|
|
m_document != NULL &&
|
|
m_document->isMaskVisible() &&
|
|
m_document->getMask()->containsPoint(x, y);
|
|
}
|
|
|
|
void Editor::setZoomAndCenterInMouse(int zoom, int mouse_x, int mouse_y)
|
|
{
|
|
View* view = View::getView(this);
|
|
Rect vp = view->getViewportBounds();
|
|
int x, y;
|
|
bool centerMouse = get_config_bool("Editor", "CenterMouseInZoom", false);
|
|
int mx, my;
|
|
|
|
hideDrawingCursor();
|
|
screenToEditor(mouse_x, mouse_y, &x, &y);
|
|
|
|
if (centerMouse) {
|
|
mx = vp.x+vp.w/2;
|
|
my = vp.y+vp.h/2;
|
|
}
|
|
else {
|
|
mx = mouse_x;
|
|
my = mouse_y;
|
|
}
|
|
|
|
x = m_offset_x - (mx - vp.x) + ((1<<zoom)>>1) + (x << zoom);
|
|
y = m_offset_y - (my - vp.y) + ((1<<zoom)>>1) + (y << zoom);
|
|
|
|
if ((m_zoom != zoom) ||
|
|
(m_cursor_editor_x != mx) ||
|
|
(m_cursor_editor_y != my)) {
|
|
int use_refresh_region = (m_zoom == zoom) ? true: false;
|
|
|
|
m_zoom = zoom;
|
|
|
|
updateEditor();
|
|
setEditorScroll(x, y, use_refresh_region);
|
|
|
|
if (centerMouse)
|
|
jmouse_set_position(mx, my);
|
|
|
|
// Notify observers
|
|
m_observers.notifyScrollChanged(this);
|
|
}
|
|
showDrawingCursor();
|
|
}
|
|
|
|
void Editor::pasteImage(const Image* image, int x, int y)
|
|
{
|
|
// 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.
|
|
tools::Tool* currentTool = getCurrentEditorTool();
|
|
if (!currentTool->getInk(0)->isSelection()) {
|
|
tools::Tool* defaultSelectionTool =
|
|
App::instance()->getToolBox()->getToolById(tools::WellKnownTools::RectangularMarquee);
|
|
|
|
ToolBar::instance()->selectTool(defaultSelectionTool);
|
|
}
|
|
|
|
Document* document = getDocument();
|
|
int opacity = 255;
|
|
Sprite* sprite = getSprite();
|
|
Layer* layer = getLayer();
|
|
|
|
// Check bounds where the image will be pasted.
|
|
{
|
|
// First we limit the image inside the sprite's bounds.
|
|
x = MID(0, x, sprite->getWidth() - image->w);
|
|
y = MID(0, y, sprite->getHeight() - image->h);
|
|
|
|
// Then we check if the image will be visible by the user.
|
|
Rect visibleBounds = getVisibleSpriteBounds();
|
|
x = MID(visibleBounds.x-image->w, x, visibleBounds.x+visibleBounds.w-1);
|
|
y = MID(visibleBounds.y-image->h, y, visibleBounds.y+visibleBounds.h-1);
|
|
|
|
// If the visible part of the pasted image will not fit in the
|
|
// visible bounds of the editor, we put the image in the center of
|
|
// the visible bounds.
|
|
Rect visiblePasted = visibleBounds.createIntersect(gfx::Rect(x, y, image->w, image->h));
|
|
if (((visibleBounds.w >= image->w && visiblePasted.w < image->w/2) ||
|
|
(visibleBounds.w < image->w && visiblePasted.w < visibleBounds.w/2)) ||
|
|
((visibleBounds.h >= image->h && visiblePasted.h < image->w/2) ||
|
|
(visibleBounds.h < image->h && visiblePasted.h < visibleBounds.h/2))) {
|
|
x = visibleBounds.x + visibleBounds.w/2 - image->w/2;
|
|
y = visibleBounds.y + visibleBounds.h/2 - image->h/2;
|
|
}
|
|
}
|
|
|
|
PixelsMovement* pixelsMovement =
|
|
new PixelsMovement(UIContext::instance(),
|
|
document, sprite, layer,
|
|
image, x, y, opacity, "Paste");
|
|
|
|
// Select the pasted image so the user can move it and transform it.
|
|
pixelsMovement->maskImage(image, x, y);
|
|
|
|
setState(EditorStatePtr(new MovingPixelsState(this, NULL, pixelsMovement, NoHandle)));
|
|
}
|