mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-16 05:42:32 +00:00
2142 lines
55 KiB
C++
2142 lines
55 KiB
C++
/* ASE - Allegro Sprite Editor
|
|
* Copyright (C) 2001-2010 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 <stdio.h>
|
|
#include <allegro.h>
|
|
|
|
#include "jinete/jinete.h"
|
|
|
|
#include "ui_context.h"
|
|
#include "commands/commands.h"
|
|
#include "commands/params.h"
|
|
#include "app.h"
|
|
#include "core/cfg.h"
|
|
#include "core/color.h"
|
|
#include "modules/editors.h"
|
|
#include "modules/gfx.h"
|
|
#include "modules/gui.h"
|
|
#include "modules/skinneable_theme.h"
|
|
#include "modules/palettes.h"
|
|
#include "raster/raster.h"
|
|
#include "tools/tool.h"
|
|
#include "settings/settings.h"
|
|
#include "util/boundary.h"
|
|
#include "util/misc.h"
|
|
#include "util/recscr.h"
|
|
#include "util/render.h"
|
|
#include "widgets/colbar.h"
|
|
#include "widgets/editor.h"
|
|
#include "widgets/editor/pixels_movement.h"
|
|
#include "widgets/statebar.h"
|
|
|
|
#define has_shifts(msg,shift) \
|
|
(((msg)->any.shifts & (shift)) == (shift))
|
|
|
|
#define has_only_shifts(msg,shift) \
|
|
(((msg)->any.shifts & (KB_SHIFT_FLAG | \
|
|
KB_ALT_FLAG | \
|
|
KB_CTRL_FLAG)) == (shift))
|
|
|
|
static bool editor_view_msg_proc(JWidget widget, JMessage msg);
|
|
|
|
JWidget editor_view_new()
|
|
{
|
|
JWidget widget = jview_new();
|
|
SkinneableTheme* theme = static_cast<SkinneableTheme*>(widget->theme);
|
|
int l = theme->get_part(PART_EDITOR_SELECTED_W)->w;
|
|
int t = theme->get_part(PART_EDITOR_SELECTED_N)->h;
|
|
int r = theme->get_part(PART_EDITOR_SELECTED_E)->w;
|
|
int b = theme->get_part(PART_EDITOR_SELECTED_S)->h;
|
|
|
|
jwidget_set_border(widget, l, t, r, b);
|
|
jview_without_bars(widget);
|
|
jwidget_add_hook(widget, JI_WIDGET, editor_view_msg_proc, NULL);
|
|
|
|
return widget;
|
|
}
|
|
|
|
Editor::Editor()
|
|
: Widget(editor_type())
|
|
{
|
|
m_state = EDITOR_STATE_STANDBY;
|
|
m_sprite = NULL;
|
|
m_zoom = 0;
|
|
|
|
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_old_cursor_thick = 0;
|
|
|
|
m_cursor_candraw = false;
|
|
m_insideSelection = false;
|
|
m_alt_pressed = false;
|
|
m_ctrl_pressed = false;
|
|
m_space_pressed = false;
|
|
|
|
m_offset_x = 0;
|
|
m_offset_y = 0;
|
|
m_mask_timer_id = jmanager_add_timer(this, 100);
|
|
m_offset_count = 0;
|
|
|
|
m_refresh_region = NULL;
|
|
m_toolLoopManager = NULL;
|
|
m_pixelsMovement = NULL;
|
|
|
|
jwidget_focusrest(this, true);
|
|
|
|
App::instance()->CurrentToolChange.connect(&Editor::onCurrentToolChange, this);
|
|
}
|
|
|
|
Editor::~Editor()
|
|
{
|
|
jmanager_remove_timer(m_mask_timer_id);
|
|
remove_editor(this);
|
|
|
|
// Destroy tool-loop manager if it is created
|
|
delete m_toolLoopManager;
|
|
|
|
// Destroy all decorators
|
|
deleteDecorators();
|
|
}
|
|
|
|
int editor_type()
|
|
{
|
|
static int type = 0;
|
|
if (!type)
|
|
type = ji_register_widget_type();
|
|
return type;
|
|
}
|
|
|
|
void Editor::editor_set_sprite(Sprite* sprite)
|
|
{
|
|
if (this->hasMouse())
|
|
jmanager_free_mouse(); // TODO Why is this here? Review this code
|
|
|
|
// Do we need to drop files?
|
|
if (m_pixelsMovement)
|
|
dropPixels();
|
|
|
|
ASSERT(m_pixelsMovement == NULL && "You cannot change the current sprite while you are moving pixels");
|
|
ASSERT(m_state == EDITOR_STATE_STANDBY && "You can change the current sprite only in stand-by state");
|
|
|
|
// Change the sprite
|
|
m_sprite = sprite;
|
|
if (m_sprite) {
|
|
// Get the preferred sprite's settings to edit it
|
|
PreferredEditorSettings preferred = m_sprite->getPreferredEditorSettings();
|
|
|
|
// Change the editor's configuration using the retrieved sprite's settings
|
|
m_zoom = preferred.zoom;
|
|
|
|
editor_update();
|
|
editor_set_scroll(m_offset_x + preferred.scroll_x,
|
|
m_offset_y + preferred.scroll_y,
|
|
false);
|
|
}
|
|
// In this case sprite is NULL
|
|
else {
|
|
editor_update();
|
|
editor_set_scroll(0, 0, false); // No scroll
|
|
}
|
|
|
|
// Redraw the entire editor (because we have a new sprite to draw)
|
|
dirty();
|
|
}
|
|
|
|
// Sets the scroll position of the editor
|
|
void Editor::editor_set_scroll(int x, int y, int use_refresh_region)
|
|
{
|
|
JWidget view = jwidget_get_view(this);
|
|
int old_scroll_x, old_scroll_y;
|
|
JRegion region = NULL;
|
|
int thick = m_cursor_thick;
|
|
|
|
if (thick)
|
|
editor_clean_cursor();
|
|
|
|
if (use_refresh_region) {
|
|
region = jwidget_get_drawable_region(this, JI_GDR_CUTTOPWINDOWS);
|
|
jview_get_scroll(view, &old_scroll_x, &old_scroll_y);
|
|
}
|
|
|
|
jview_set_scroll(view, x, y);
|
|
|
|
if (m_sprite) {
|
|
PreferredEditorSettings preferred;
|
|
|
|
preferred.scroll_x = x - m_offset_x;
|
|
preferred.scroll_y = y - m_offset_y;
|
|
preferred.zoom = m_zoom;
|
|
|
|
m_sprite->setPreferredEditorSettings(preferred);
|
|
}
|
|
|
|
if (use_refresh_region) {
|
|
int new_scroll_x, new_scroll_y;
|
|
|
|
jview_get_scroll(view, &new_scroll_x, &new_scroll_y);
|
|
|
|
// Move screen with blits
|
|
jwidget_scroll(this, region,
|
|
old_scroll_x - new_scroll_x,
|
|
old_scroll_y - new_scroll_y);
|
|
|
|
jregion_free(region);
|
|
/* m_widget->flags &= ~JI_DIRTY; */
|
|
}
|
|
/* else { */
|
|
/* if (m_refresh_region) { */
|
|
/* jregion_free (m_refresh_region); */
|
|
/* m_refresh_region = NULL; */
|
|
/* } */
|
|
/* } */
|
|
|
|
if (thick)
|
|
editor_draw_cursor(m_cursor_screen_x, m_cursor_screen_y);
|
|
}
|
|
|
|
void Editor::editor_update()
|
|
{
|
|
JWidget view = jwidget_get_view(this);
|
|
jview_update(view);
|
|
}
|
|
|
|
/**
|
|
* Draws the specified portion of sprite in the editor.
|
|
*
|
|
* @warning You should setup the clip of the @ref ji_screen before
|
|
* calling this routine.
|
|
*/
|
|
void Editor::editor_draw_sprite(int x1, int y1, int x2, int y2)
|
|
{
|
|
JWidget view = jwidget_get_view(this);
|
|
JRect vp = jview_get_viewport_position(view);
|
|
int source_x, source_y, dest_x, dest_y, width, height;
|
|
int scroll_x, scroll_y;
|
|
|
|
// Get scroll
|
|
|
|
jview_get_scroll(view, &scroll_x, &scroll_y);
|
|
|
|
// Output information
|
|
|
|
source_x = x1 << m_zoom;
|
|
source_y = y1 << m_zoom;
|
|
dest_x = vp->x1 - scroll_x + m_offset_x + source_x;
|
|
dest_y = vp->y1 - scroll_y + m_offset_y + source_y;
|
|
width = (x2 - x1 + 1) << m_zoom;
|
|
height = (y2 - y1 + 1) << m_zoom;
|
|
|
|
// Clip from viewport
|
|
|
|
if (dest_x < vp->x1) {
|
|
source_x += vp->x1 - dest_x;
|
|
width -= vp->x1 - dest_x;
|
|
dest_x = vp->x1;
|
|
}
|
|
|
|
if (dest_y < vp->y1) {
|
|
source_y += vp->y1 - dest_y;
|
|
height -= vp->y1 - dest_y;
|
|
dest_y = vp->y1;
|
|
}
|
|
|
|
if (dest_x+width-1 > vp->x2-1)
|
|
width = vp->x2-dest_x;
|
|
|
|
if (dest_y+height-1 > vp->y2-1)
|
|
height = vp->y2-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)) {
|
|
// Generate the rendered image
|
|
Image* rendered = RenderEngine::renderSprite(m_sprite,
|
|
source_x, source_y,
|
|
width, height,
|
|
m_sprite->getCurrentFrame(),
|
|
m_zoom, true);
|
|
|
|
if (rendered) {
|
|
#ifdef DRAWSPRITE_DOUBLEBUFFERED
|
|
BITMAP *bmp = create_bitmap(width, height);
|
|
|
|
use_current_sprite_rgb_map();
|
|
image_to_allegro(rendered, bmp, 0, 0, m_sprite->getCurrentPalette());
|
|
blit(bmp, ji_screen, 0, 0, dest_x, dest_y, width, height);
|
|
restore_rgb_map();
|
|
|
|
image_free(rendered);
|
|
destroy_bitmap(bmp);
|
|
#else
|
|
acquire_bitmap(ji_screen);
|
|
image_to_allegro(rendered, ji_screen, dest_x, dest_y, m_sprite->getCurrentPalette());
|
|
release_bitmap(ji_screen);
|
|
|
|
image_free(rendered);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
jrect_free(vp);
|
|
|
|
// Draw grids
|
|
ISettings* settings = UIContext::instance()->getSettings();
|
|
|
|
// Draw the pixel grid
|
|
if (settings->getPixelGridVisible()) {
|
|
if (m_zoom > 1)
|
|
this->drawGrid(Rect(0, 0, 1, 1),
|
|
settings->getPixelGridColor());
|
|
}
|
|
|
|
// Draw the grid
|
|
if (settings->getGridVisible())
|
|
this->drawGrid(settings->getGridBounds(),
|
|
settings->getGridColor());
|
|
}
|
|
|
|
/**
|
|
* Draws the sprite taking care of the whole clipping region.
|
|
*
|
|
* For each rectangle calls @ref editor_draw_sprite.
|
|
*/
|
|
void Editor::editor_draw_sprite_safe(int x1, int y1, int x2, int y2)
|
|
{
|
|
JRegion region = jwidget_get_drawable_region(this, JI_GDR_CUTTOPWINDOWS);
|
|
int c, nrects = JI_REGION_NUM_RECTS(region);
|
|
int cx1, cy1, cx2, cy2;
|
|
JRect rc;
|
|
|
|
get_clip_rect(ji_screen, &cx1, &cy1, &cx2, &cy2);
|
|
|
|
for (c=0, rc=JI_REGION_RECTS(region);
|
|
c<nrects;
|
|
c++, rc++) {
|
|
add_clip_rect(ji_screen, rc->x1, rc->y1, rc->x2-1, rc->y2-1);
|
|
editor_draw_sprite(x1, y1, x2, y2);
|
|
set_clip_rect(ji_screen, cx1, cy1, cx2, cy2);
|
|
}
|
|
|
|
jregion_free(region);
|
|
}
|
|
|
|
/**
|
|
* 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::editor_draw_mask()
|
|
{
|
|
JWidget view = jwidget_get_view(this);
|
|
JRect vp = jview_get_viewport_position(view);
|
|
int scroll_x, scroll_y;
|
|
int x1, y1, x2, y2;
|
|
int c, x, y;
|
|
|
|
jview_get_scroll(view, &scroll_x, &scroll_y);
|
|
|
|
dotted_mode(m_offset_count);
|
|
|
|
x = vp->x1 - scroll_x + m_offset_x;
|
|
y = vp->y1 - scroll_y + m_offset_y;
|
|
|
|
int nseg = m_sprite->getBoundariesSegmentsCount();
|
|
const _BoundSeg* seg = m_sprite->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);
|
|
|
|
jrect_free(vp);
|
|
}
|
|
|
|
void Editor::editor_draw_mask_safe()
|
|
{
|
|
if ((m_sprite) && (m_sprite->getBoundariesSegments())) {
|
|
int thick = m_cursor_thick;
|
|
|
|
JRegion region = jwidget_get_drawable_region(this, JI_GDR_CUTTOPWINDOWS);
|
|
int c, nrects = JI_REGION_NUM_RECTS(region);
|
|
JRect rc;
|
|
|
|
acquire_bitmap(ji_screen);
|
|
|
|
if (thick)
|
|
editor_clean_cursor();
|
|
else
|
|
jmouse_hide();
|
|
|
|
for (c=0, rc=JI_REGION_RECTS(region);
|
|
c<nrects;
|
|
c++, rc++) {
|
|
set_clip_rect(ji_screen, rc->x1, rc->y1, rc->x2-1, rc->y2-1);
|
|
editor_draw_mask();
|
|
}
|
|
set_clip_rect(ji_screen, 0, 0, JI_SCREEN_W-1, JI_SCREEN_H-1);
|
|
jregion_free(region);
|
|
|
|
// 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, color_t color)
|
|
{
|
|
Rect grid(gridBounds);
|
|
if (grid.w < 1 || grid.h < 1)
|
|
return;
|
|
|
|
int grid_color = get_color_for_allegro(bitmap_color_depth(ji_screen), color);
|
|
JWidget view = jwidget_get_view(this);
|
|
JRect vp = jview_get_viewport_position(view);
|
|
int scroll_x, scroll_y;
|
|
int x1, y1, x2, y2;
|
|
int u1, v1, u2, v2;
|
|
int c;
|
|
|
|
jview_get_scroll(view, &scroll_x, &scroll_y);
|
|
|
|
scroll_x = vp->x1 - scroll_x + m_offset_x;
|
|
scroll_y = vp->y1 - scroll_y + m_offset_y;
|
|
|
|
x1 = ji_screen->cl;
|
|
y1 = ji_screen->ct;
|
|
x2 = ji_screen->cr-1;
|
|
y2 = ji_screen->cb-1;
|
|
|
|
screen_to_editor(x1, y1, &u1, &v1);
|
|
screen_to_editor(x2, y2, &u2, &v2);
|
|
|
|
grid.setOrigin(Point((grid.x % grid.w) - grid.w,
|
|
(grid.y % grid.h) - grid.h));
|
|
|
|
u1 = ((u1-grid.x) / grid.w) - 1;
|
|
v1 = ((v1-grid.y) / grid.h) - 1;
|
|
u2 = ((u2-grid.x) / grid.w) + 1;
|
|
v2 = ((v2-grid.y) / grid.h) + 1;
|
|
|
|
grid.x <<= m_zoom;
|
|
grid.y <<= m_zoom;
|
|
grid.w <<= m_zoom;
|
|
grid.h <<= m_zoom;
|
|
|
|
// Horizontal lines
|
|
x1 = scroll_x+grid.x+u1*grid.w;
|
|
x2 = scroll_x+grid.x+u2*grid.w;
|
|
|
|
for (c=v1; c<=v2; c++)
|
|
hline(ji_screen, x1, scroll_y+grid.y+c*grid.h, x2, grid_color);
|
|
|
|
// Vertical lines
|
|
y1 = scroll_y+grid.y+v1*grid.h;
|
|
y2 = scroll_y+grid.y+v2*grid.h;
|
|
|
|
for (c=u1; c<=u2; c++)
|
|
vline(ji_screen, scroll_x+grid.x+c*grid.w, y1, y2, grid_color);
|
|
|
|
jrect_free(vp);
|
|
}
|
|
|
|
void Editor::flashCurrentLayer()
|
|
{
|
|
int x, y;
|
|
const Image* src_image = m_sprite->getCurrentImage(&x, &y);
|
|
if (src_image) {
|
|
m_sprite->prepareExtraCel(0, 0, m_sprite->getWidth(), m_sprite->getHeight(), 255);
|
|
Image* flash_image = m_sprite->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) {
|
|
ase_uint32 color = image_getpixel(src_image, u-x, v-y);
|
|
if (color != src_image->mask_color) {
|
|
color_t ccc = color_rgb(255, 255, 255);
|
|
image_putpixel(flash_image, u, v,
|
|
get_color_for_image(flash_image->imgtype, ccc));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
editor_draw_sprite(0, 0, m_sprite->getWidth()-1, m_sprite->getHeight()-1);
|
|
gui_flip_screen();
|
|
|
|
vsync();
|
|
rest(100);
|
|
|
|
image_clear(flash_image, flash_image->mask_color);
|
|
editor_draw_sprite(0, 0, m_sprite->getWidth()-1, m_sprite->getHeight()-1);
|
|
}
|
|
}
|
|
|
|
void Editor::setMaskColorForPixelsMovement(color_t color)
|
|
{
|
|
ASSERT(m_sprite != NULL);
|
|
ASSERT(m_pixelsMovement != NULL);
|
|
|
|
int imgtype = m_sprite->getImgType();
|
|
m_pixelsMovement->setMaskColor(get_color_for_image(imgtype, color));
|
|
}
|
|
|
|
void Editor::deleteDecorators()
|
|
{
|
|
for (std::vector<Decorator*>::iterator
|
|
it=m_decorators.begin(); it!=m_decorators.end(); ++it)
|
|
delete *it;
|
|
m_decorators.clear();
|
|
}
|
|
|
|
void Editor::addDecorator(Decorator* decorator)
|
|
{
|
|
m_decorators.push_back(decorator);
|
|
}
|
|
|
|
void Editor::turnOnSelectionModifiers()
|
|
{
|
|
deleteDecorators();
|
|
|
|
int x1, y1, x2, y2;
|
|
|
|
editor_to_screen(m_sprite->getMask()->x, m_sprite->getMask()->y, &x1, &y1);
|
|
editor_to_screen(m_sprite->getMask()->x+m_sprite->getMask()->w-1,
|
|
m_sprite->getMask()->y+m_sprite->getMask()->h-1, &x2, &y2);
|
|
|
|
addDecorator(new Decorator(Decorator::SELECTION_NW, Rect(x1-8, y1-8, 8, 8)));
|
|
addDecorator(new Decorator(Decorator::SELECTION_N, Rect((x1+x2)/2-4, y1-8, 8, 8)));
|
|
addDecorator(new Decorator(Decorator::SELECTION_NE, Rect(x2, y1-8, 8, 8)));
|
|
addDecorator(new Decorator(Decorator::SELECTION_E, Rect(x2, (y1+y2)/2-4, 8, 8)));
|
|
addDecorator(new Decorator(Decorator::SELECTION_SE, Rect(x2, y2, 8, 8)));
|
|
addDecorator(new Decorator(Decorator::SELECTION_S, Rect((x1+x2)/2-4, y2, 8, 8)));
|
|
addDecorator(new Decorator(Decorator::SELECTION_SW, Rect(x1-8, y2, 8, 8)));
|
|
addDecorator(new Decorator(Decorator::SELECTION_W, Rect(x1-8, (y1+y2)/2-4, 8, 8)));
|
|
}
|
|
|
|
/**
|
|
Control scroll when cursor goes out of the editor
|
|
|
|
@param msg A mouse message received in Editor::onProcessMessage()
|
|
*/
|
|
void Editor::controlInfiniteScroll(JMessage msg)
|
|
{
|
|
JWidget view = jwidget_get_view(this);
|
|
JRect vp = jview_get_viewport_position(view);
|
|
|
|
if (jmouse_control_infinite_scroll(vp)) {
|
|
int scroll_x, scroll_y;
|
|
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->x1+1, old_x, vp->x2-2),
|
|
MID(vp->y1+1, old_y, vp->y2-2));
|
|
}
|
|
// This is better for high resolutions: scroll movement by big steps
|
|
else {
|
|
jmouse_set_position((old_x != msg->mouse.x) ?
|
|
(old_x + (vp->x1+vp->x2)/2)/2: msg->mouse.x,
|
|
|
|
(old_y != msg->mouse.y) ?
|
|
(old_y + (vp->y1+vp->y2)/2)/2: msg->mouse.y);
|
|
}
|
|
|
|
msg->mouse.x = jmouse_x(0);
|
|
msg->mouse.y = jmouse_y(0);
|
|
|
|
jview_get_scroll(view, &scroll_x, &scroll_y);
|
|
editor_set_scroll(scroll_x+old_x-msg->mouse.x,
|
|
scroll_y+old_y-msg->mouse.y, true);
|
|
}
|
|
|
|
jrect_free(vp);
|
|
}
|
|
|
|
void Editor::dropPixels()
|
|
{
|
|
ASSERT(m_pixelsMovement != NULL);
|
|
|
|
if (m_pixelsMovement->isDragging())
|
|
m_pixelsMovement->dropImageTemporarily();
|
|
|
|
// Drop pixels if the user press a button outside the selection
|
|
m_pixelsMovement->dropImage();
|
|
delete m_pixelsMovement;
|
|
m_pixelsMovement = NULL;
|
|
|
|
m_sprite->destroyExtraCel();
|
|
|
|
m_state = EDITOR_STATE_STANDBY;
|
|
releaseMouse();
|
|
|
|
app_get_statusbar()->hideMovePixelsOptions();
|
|
editor_update_statusbar_for_standby();
|
|
}
|
|
|
|
void Editor::screen_to_editor(int xin, int yin, int *xout, int *yout)
|
|
{
|
|
JWidget view = jwidget_get_view(this);
|
|
JRect vp = jview_get_viewport_position(view);
|
|
int scroll_x, scroll_y;
|
|
|
|
jview_get_scroll(view, &scroll_x, &scroll_y);
|
|
|
|
*xout = (xin - vp->x1 + scroll_x - m_offset_x) >> m_zoom;
|
|
*yout = (yin - vp->y1 + scroll_y - m_offset_y) >> m_zoom;
|
|
|
|
jrect_free(vp);
|
|
}
|
|
|
|
void Editor::editor_to_screen(int xin, int yin, int *xout, int *yout)
|
|
{
|
|
JWidget view = jwidget_get_view(this);
|
|
JRect vp = jview_get_viewport_position(view);
|
|
int scroll_x, scroll_y;
|
|
|
|
jview_get_scroll(view, &scroll_x, &scroll_y);
|
|
|
|
*xout = (vp->x1 - scroll_x + m_offset_x + (xin << m_zoom));
|
|
*yout = (vp->y1 - scroll_y + m_offset_y + (yin << m_zoom));
|
|
|
|
jrect_free(vp);
|
|
}
|
|
|
|
void Editor::show_drawing_cursor()
|
|
{
|
|
ASSERT(m_sprite != NULL);
|
|
|
|
if (!m_cursor_thick && m_cursor_candraw) {
|
|
jmouse_hide();
|
|
editor_draw_cursor(jmouse_x(0), jmouse_y(0));
|
|
jmouse_show();
|
|
}
|
|
}
|
|
|
|
void Editor::hide_drawing_cursor()
|
|
{
|
|
if (m_cursor_thick) {
|
|
jmouse_hide();
|
|
editor_clean_cursor();
|
|
jmouse_show();
|
|
}
|
|
}
|
|
|
|
void Editor::editor_update_statusbar_for_standby()
|
|
{
|
|
UIContext* context = UIContext::instance();
|
|
Tool* current_tool = context->getSettings()->getCurrentTool();
|
|
int x, y;
|
|
screen_to_editor(jmouse_x(0), jmouse_y(0), &x, &y);
|
|
|
|
// For eye-dropper
|
|
if (m_alt_pressed ||
|
|
current_tool->getInk(0)->isEyedropper()) {
|
|
int imgtype = m_sprite->getImgType();
|
|
ase_uint32 pixel = m_sprite->getPixel(x, y);
|
|
color_t color = color_from_image(imgtype, pixel);
|
|
|
|
int alpha = 255;
|
|
switch (imgtype) {
|
|
case IMAGE_RGB: alpha = _rgba_geta(pixel); break;
|
|
case IMAGE_GRAYSCALE: alpha = _graya_geta(pixel); break;
|
|
}
|
|
|
|
char buf[256];
|
|
usprintf(buf, "- Pos %d %d", x, y);
|
|
|
|
app_get_statusbar()->showColor(0, buf, color, alpha);
|
|
}
|
|
// For other tools
|
|
else {
|
|
app_get_statusbar()->setStatusText
|
|
(0, "Pos %d %d, Size %d %d, Frame %d",
|
|
x, y,
|
|
((m_sprite->getMask()->bitmap)? m_sprite->getMask()->w: m_sprite->getWidth()),
|
|
((m_sprite->getMask()->bitmap)? m_sprite->getMask()->h: m_sprite->getHeight()),
|
|
m_sprite->getCurrentFrame()+1);
|
|
}
|
|
}
|
|
|
|
// Update status bar for when the user is dragging pixels
|
|
void Editor::editor_update_statusbar_for_pixel_movement()
|
|
{
|
|
ASSERT(m_pixelsMovement != NULL);
|
|
|
|
Rect bounds = m_pixelsMovement->getImageBounds();
|
|
app_get_statusbar()->setStatusText
|
|
(100, "Pos %d %d, Size %d %d",
|
|
bounds.x, bounds.y, bounds.w, bounds.h);
|
|
}
|
|
|
|
void Editor::editor_refresh_region()
|
|
{
|
|
if (this->update_region) {
|
|
int thick = m_cursor_thick;
|
|
|
|
if (thick)
|
|
editor_clean_cursor();
|
|
else
|
|
jmouse_hide();
|
|
|
|
// TODO check if this work!!!!
|
|
/* jwidget_redraw_region */
|
|
/* (jwindow_get_manager (jwidget_get_window (widget)), */
|
|
/* m_refresh_region); */
|
|
/* jwidget_redraw_region (widget, m_refresh_region); */
|
|
jwidget_flush_redraw(this);
|
|
jmanager_dispatch_messages(ji_get_default_manager());
|
|
|
|
/* if (m_refresh_region) { */
|
|
/* jregion_free (m_refresh_region); */
|
|
/* m_refresh_region = NULL; */
|
|
/* } */
|
|
|
|
if (thick)
|
|
editor_draw_cursor(m_cursor_screen_x, m_cursor_screen_y);
|
|
else
|
|
jmouse_show();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Message handler for the a view widget that contains an editor
|
|
|
|
static bool editor_view_msg_proc(JWidget widget, JMessage msg)
|
|
{
|
|
switch (msg->type) {
|
|
|
|
case JM_SETPOS:
|
|
// This avoid the displacement of the widgets in the viewport
|
|
|
|
jrect_copy(widget->rc, &msg->setpos.rect);
|
|
jview_update(widget);
|
|
return true;
|
|
|
|
case JM_DRAW:
|
|
{
|
|
JWidget viewport = jview_get_viewport(widget);
|
|
JWidget child = reinterpret_cast<JWidget>(jlist_first_data(viewport->children));
|
|
JRect pos = jwidget_get_rect(widget);
|
|
SkinneableTheme* theme = static_cast<SkinneableTheme*>(widget->theme);
|
|
|
|
theme->draw_bounds_nw(ji_screen,
|
|
pos->x1, pos->y1,
|
|
pos->x2-1, pos->y2-1,
|
|
(child == current_editor) ? PART_EDITOR_SELECTED_NW:
|
|
PART_EDITOR_NORMAL_NW, false);
|
|
|
|
jrect_free(pos);
|
|
}
|
|
return true;
|
|
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Message handler for the editor
|
|
|
|
enum WHEEL_ACTION { WHEEL_NONE,
|
|
WHEEL_ZOOM,
|
|
WHEEL_VSCROLL,
|
|
WHEEL_HSCROLL,
|
|
WHEEL_FG,
|
|
WHEEL_BG,
|
|
WHEEL_FRAME };
|
|
|
|
bool Editor::onProcessMessage(JMessage msg)
|
|
{
|
|
ASSERT((m_state == EDITOR_STATE_DRAWING && m_toolLoopManager != NULL) ||
|
|
(m_state != EDITOR_STATE_DRAWING && m_toolLoopManager == NULL));
|
|
|
|
switch (msg->type) {
|
|
|
|
case JM_REQSIZE:
|
|
editor_request_size(&msg->reqsize.w, &msg->reqsize.h);
|
|
return true;
|
|
|
|
case JM_CLOSE:
|
|
// if (m_refresh_region)
|
|
// jregion_free(m_refresh_region);
|
|
break;
|
|
|
|
case JM_DRAW: {
|
|
SkinneableTheme* theme = static_cast<SkinneableTheme*>(this->theme);
|
|
|
|
if (m_old_cursor_thick == 0) {
|
|
m_old_cursor_thick = m_cursor_thick;
|
|
}
|
|
|
|
if (m_cursor_thick)
|
|
editor_clean_cursor();
|
|
|
|
// Editor without sprite
|
|
if (!m_sprite) {
|
|
JWidget view = jwidget_get_view(this);
|
|
JRect vp = jview_get_viewport_position(view);
|
|
|
|
jdraw_rectfill(vp, theme->get_editor_face_color());
|
|
draw_emptyset_symbol(ji_screen,
|
|
Rect(vp->x1, vp->y1, jrect_w(vp), jrect_h(vp)),
|
|
makecol(64, 64, 64));
|
|
|
|
jrect_free(vp);
|
|
}
|
|
// Editor with sprite
|
|
else {
|
|
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->get_editor_face_color());
|
|
|
|
// Draw the sprite in the editor
|
|
editor_draw_sprite(0, 0, m_sprite->getWidth()-1, m_sprite->getHeight()-1);
|
|
|
|
// Draw the sprite boundary
|
|
rect(ji_screen, x1-1, y1-1, x2+1, y2+1, theme->get_editor_sprite_border());
|
|
hline(ji_screen, x1-1, y2+2, x2+1, theme->get_editor_sprite_bottom_edge());
|
|
|
|
// Draw the mask boundaries
|
|
if (m_sprite->getBoundariesSegments()) {
|
|
editor_draw_mask();
|
|
jmanager_start_timer(m_mask_timer_id);
|
|
}
|
|
else {
|
|
jmanager_stop_timer(m_mask_timer_id);
|
|
}
|
|
|
|
// Draw decorators
|
|
for (std::vector<Decorator*>::iterator
|
|
it=m_decorators.begin(); it!=m_decorators.end(); ++it) {
|
|
(*it)->drawDecorator(this, ji_screen);
|
|
}
|
|
|
|
if (msg->draw.count == 0
|
|
&& m_old_cursor_thick != 0) {
|
|
editor_draw_cursor(jmouse_x(0), jmouse_y(0));
|
|
}
|
|
}
|
|
|
|
if (msg->draw.count == 0)
|
|
m_old_cursor_thick = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
case JM_TIMER:
|
|
if (msg->timer.timer_id == m_mask_timer_id) {
|
|
if (m_sprite) {
|
|
editor_draw_mask_safe();
|
|
|
|
// Set offset to make selection-movement effect
|
|
if (m_offset_count < 7)
|
|
m_offset_count++;
|
|
else
|
|
m_offset_count = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case JM_MOUSEENTER:
|
|
// When the mouse enter to the editor, we can calculate the
|
|
// 'cursor_candraw' field to avoid a heavy if-condition in the
|
|
// 'editor_setcursor' routine
|
|
editor_update_candraw();
|
|
|
|
if (msg->any.shifts & KB_ALT_FLAG) m_alt_pressed = true;
|
|
if (msg->any.shifts & KB_CTRL_FLAG) m_ctrl_pressed = true;
|
|
if (key[KEY_SPACE]) m_space_pressed = true;
|
|
break;
|
|
|
|
case JM_MOUSELEAVE:
|
|
hide_drawing_cursor();
|
|
|
|
if (m_alt_pressed) m_alt_pressed = false;
|
|
if (m_ctrl_pressed) m_ctrl_pressed = false;
|
|
if (m_space_pressed) m_space_pressed = false;
|
|
|
|
app_get_statusbar()->clearText();
|
|
break;
|
|
|
|
case JM_BUTTONPRESSED:
|
|
if (!m_sprite)
|
|
break;
|
|
|
|
// Drawing loop
|
|
if (m_state == EDITOR_STATE_DRAWING) {
|
|
ASSERT(m_toolLoopManager != NULL);
|
|
|
|
m_toolLoopManager->pressButton(msg);
|
|
|
|
// Cancel drawing loop
|
|
if (m_toolLoopManager->isCanceled()) {
|
|
m_toolLoopManager->releaseLoop(msg);
|
|
|
|
delete m_toolLoopManager;
|
|
m_toolLoopManager = NULL;
|
|
m_state = EDITOR_STATE_STANDBY;
|
|
|
|
// Redraw all the editors with this sprite
|
|
update_screen_for_sprite(m_sprite);
|
|
|
|
clear_keybuf();
|
|
|
|
editor_setcursor(msg->mouse.x, msg->mouse.y);
|
|
|
|
releaseMouse();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!hasCapture()) {
|
|
UIContext* context = UIContext::instance();
|
|
Tool* current_tool = context->getSettings()->getCurrentTool();
|
|
|
|
set_current_editor(this);
|
|
context->set_current_sprite(m_sprite);
|
|
|
|
// Start scroll loop
|
|
if (msg->mouse.middle ||
|
|
m_space_pressed ||
|
|
current_tool->getInk(msg->mouse.right ? 1: 0)->isScrollMovement()) {
|
|
m_state = EDITOR_STATE_SCROLLING;
|
|
|
|
editor_setcursor(msg->mouse.x, msg->mouse.y);
|
|
captureMouse();
|
|
return true;
|
|
}
|
|
|
|
if (m_pixelsMovement) {
|
|
// Start "moving pixels" loop
|
|
if (m_insideSelection) {
|
|
// Re-catch the image
|
|
int x, y;
|
|
screen_to_editor(msg->mouse.x, msg->mouse.y, &x, &y);
|
|
m_pixelsMovement->catchImageAgain(x, y);
|
|
|
|
captureMouse();
|
|
return true;
|
|
}
|
|
// End "moving pixels" loop
|
|
else {
|
|
// Drop pixels (e.g. to start drawing)
|
|
dropPixels();
|
|
}
|
|
}
|
|
|
|
// Move frames position
|
|
if ((m_ctrl_pressed &&
|
|
!current_tool->getInk(msg->mouse.right ? 1: 0)->isSelection()) ||
|
|
current_tool->getInk(msg->mouse.right ? 1: 0)->isCelMovement()) {
|
|
if ((m_sprite->getCurrentLayer()) &&
|
|
(m_sprite->getCurrentLayer()->type == GFXOBJ_LAYER_IMAGE)) {
|
|
// TODO you can move the `Background' with tiled mode
|
|
if (m_sprite->getCurrentLayer()->is_background()) {
|
|
jalert(_(PACKAGE
|
|
"<<You can't move the `Background' layer."
|
|
"||&Close"));
|
|
}
|
|
else if (!m_sprite->getCurrentLayer()->is_moveable()) {
|
|
jalert(_(PACKAGE
|
|
"<<The layer movement is locked."
|
|
"||&Close"));
|
|
}
|
|
else {
|
|
bool click2 = get_config_bool("Options", "MoveClick2", FALSE);
|
|
interactive_move_layer(click2 ? MODE_CLICKANDCLICK:
|
|
MODE_CLICKANDRELEASE,
|
|
TRUE, NULL); // TODO remove this routine
|
|
}
|
|
}
|
|
}
|
|
// Move selected pixels
|
|
else if (m_insideSelection &&
|
|
current_tool->getInk(0)->isSelection() &&
|
|
msg->mouse.left) {
|
|
int x, y, opacity;
|
|
Image* image = m_sprite->getCurrentImage(&x, &y, &opacity);
|
|
if (image) {
|
|
if (!m_sprite->getCurrentLayer()->is_writable()) {
|
|
jalert(_(PACKAGE
|
|
"<<The layer is locked."
|
|
"||&Close"));
|
|
return true;
|
|
}
|
|
|
|
// Copy the mask to the extra cel image
|
|
Image* tmpImage = NewImageFromMask(m_sprite);
|
|
x = m_sprite->getMask()->x;
|
|
y = m_sprite->getMask()->y;
|
|
m_pixelsMovement = new PixelsMovement(m_sprite, tmpImage, x, y, opacity);
|
|
delete tmpImage;
|
|
|
|
// If the CTRL key is pressed start dragging a copy of the selection
|
|
if (m_ctrl_pressed)
|
|
m_pixelsMovement->copyMask();
|
|
else
|
|
m_pixelsMovement->cutMask();
|
|
|
|
screen_to_editor(msg->mouse.x, msg->mouse.y, &x, &y);
|
|
m_pixelsMovement->catchImage(x, y);
|
|
|
|
// Setup mask color
|
|
setMaskColorForPixelsMovement(app_get_statusbar()->getTransparentColor());
|
|
|
|
// Update status bar
|
|
editor_update_statusbar_for_pixel_movement();
|
|
app_get_statusbar()->showMovePixelsOptions();
|
|
}
|
|
captureMouse();
|
|
}
|
|
// Call the eyedropper command
|
|
else if (m_alt_pressed ||
|
|
current_tool->getInk(msg->mouse.right ? 1: 0)->isEyedropper()) {
|
|
Command* eyedropper_cmd =
|
|
CommandsModule::instance()->get_command_by_name(CommandId::eyedropper);
|
|
|
|
Params params;
|
|
params.set("target", msg->mouse.right ? "background": "foreground");
|
|
|
|
UIContext::instance()->execute_command(eyedropper_cmd, ¶ms);
|
|
return true;
|
|
}
|
|
// Start the Tool-Loop
|
|
else if (m_sprite->getCurrentLayer()) {
|
|
ASSERT(m_toolLoopManager == NULL);
|
|
|
|
IToolLoop* toolLoop = createToolLoopImpl(UIContext::instance(), msg);
|
|
if (!toolLoop)
|
|
return true; // Return without capturing mouse
|
|
|
|
m_toolLoopManager = new ToolLoopManager(toolLoop);
|
|
if (!m_toolLoopManager)
|
|
return true; // Return without capturing mouse
|
|
|
|
// Clean the cursor (mainly to clean the pen preview)
|
|
int thick = m_cursor_thick;
|
|
if (thick)
|
|
editor_clean_cursor();
|
|
|
|
m_state = EDITOR_STATE_DRAWING;
|
|
|
|
m_toolLoopManager->prepareLoop(msg);
|
|
m_toolLoopManager->pressButton(msg);
|
|
|
|
// Redraw it (without pen preview)
|
|
if (thick)
|
|
editor_draw_cursor(msg->mouse.x, msg->mouse.y);
|
|
|
|
captureMouse();
|
|
}
|
|
}
|
|
return true;
|
|
|
|
case JM_MOTION:
|
|
if (!m_sprite)
|
|
break;
|
|
|
|
// Move the scroll
|
|
if (m_state == EDITOR_STATE_SCROLLING) {
|
|
JWidget view = jwidget_get_view(this);
|
|
JRect vp = jview_get_viewport_position(view);
|
|
int scroll_x, scroll_y;
|
|
|
|
jview_get_scroll(view, &scroll_x, &scroll_y);
|
|
editor_set_scroll(scroll_x+jmouse_x(1)-jmouse_x(0),
|
|
scroll_y+jmouse_y(1)-jmouse_y(0), true);
|
|
|
|
jmouse_control_infinite_scroll(vp);
|
|
jrect_free(vp);
|
|
|
|
{
|
|
int x, y;
|
|
screen_to_editor(jmouse_x(0), jmouse_y(0), &x, &y);
|
|
app_get_statusbar()->setStatusText
|
|
(0, "Pos %3d %3d (Size %3d %3d)", x, y,
|
|
m_sprite->getWidth(), m_sprite->getHeight());
|
|
}
|
|
}
|
|
// Moving pixels
|
|
else if (m_pixelsMovement) {
|
|
// If there is a button pressed
|
|
if (m_pixelsMovement->isDragging()) {
|
|
// Infinite scroll
|
|
controlInfiniteScroll(msg);
|
|
|
|
// Get the position of the mouse in the sprite
|
|
int x, y;
|
|
screen_to_editor(msg->mouse.x, msg->mouse.y, &x, &y);
|
|
|
|
// Drag the image to that position
|
|
Rect bounds = m_pixelsMovement->moveImage(x, y);
|
|
|
|
// If "bounds" is empty is because the cel was not moved
|
|
if (!bounds.isEmpty()) {
|
|
// Redraw the extra cel in the new position
|
|
jmouse_hide();
|
|
editors_draw_sprite_tiled(m_sprite,
|
|
bounds.x, bounds.y,
|
|
bounds.x+bounds.w-1,
|
|
bounds.y+bounds.h-1);
|
|
jmouse_show();
|
|
}
|
|
}
|
|
else {
|
|
// Draw cursor
|
|
if (m_cursor_thick) {
|
|
int x, y;
|
|
|
|
x = msg->mouse.x;
|
|
y = msg->mouse.y;
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
}
|
|
|
|
editor_update_statusbar_for_pixel_movement();
|
|
}
|
|
// In tool-loop
|
|
else if (m_state == EDITOR_STATE_DRAWING) {
|
|
acquire_bitmap(ji_screen);
|
|
|
|
ASSERT(m_toolLoopManager != NULL);
|
|
|
|
// Clean the area occupied by the cursor in the screen
|
|
if (m_cursor_thick)
|
|
editor_clean_cursor();
|
|
|
|
ASSERT(m_toolLoopManager != NULL);
|
|
|
|
// Infinite scroll
|
|
controlInfiniteScroll(msg);
|
|
|
|
// Clean the area occupied by the cursor in the screen
|
|
int thick = m_cursor_thick;
|
|
if (thick)
|
|
editor_clean_cursor();
|
|
|
|
// notify mouse movement to the tool
|
|
ASSERT(m_toolLoopManager != NULL);
|
|
m_toolLoopManager->movement(msg);
|
|
|
|
// draw the cursor again
|
|
if (thick)
|
|
editor_draw_cursor(msg->mouse.x, msg->mouse.y);
|
|
|
|
release_bitmap(ji_screen);
|
|
}
|
|
else if (m_state == EDITOR_STATE_STANDBY) {
|
|
// Draw cursor
|
|
if (m_cursor_thick) {
|
|
int x, y;
|
|
|
|
x = msg->mouse.x;
|
|
y = msg->mouse.y;
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
editor_update_statusbar_for_standby();
|
|
}
|
|
return true;
|
|
|
|
case JM_BUTTONRELEASED:
|
|
if (!m_sprite)
|
|
break;
|
|
|
|
// Drawing
|
|
if (m_state == EDITOR_STATE_DRAWING) {
|
|
ASSERT(m_toolLoopManager != NULL);
|
|
|
|
if (m_toolLoopManager->releaseButton(msg))
|
|
return true;
|
|
|
|
m_toolLoopManager->releaseLoop(msg);
|
|
|
|
delete m_toolLoopManager;
|
|
m_toolLoopManager = NULL;
|
|
m_state = EDITOR_STATE_STANDBY;
|
|
|
|
// redraw all the editors with this sprite
|
|
update_screen_for_sprite(m_sprite);
|
|
|
|
clear_keybuf();
|
|
}
|
|
else if (m_state != EDITOR_STATE_STANDBY) {
|
|
ASSERT(m_toolLoopManager == NULL);
|
|
m_state = EDITOR_STATE_STANDBY;
|
|
}
|
|
// Moving pixels
|
|
else if (m_pixelsMovement) {
|
|
// Drop the image temporarily in this location (where the user releases the mouse)
|
|
m_pixelsMovement->dropImageTemporarily();
|
|
}
|
|
|
|
editor_setcursor(msg->mouse.x, msg->mouse.y);
|
|
editor_update_statusbar_for_standby();
|
|
releaseMouse();
|
|
return true;
|
|
|
|
case JM_KEYPRESSED:
|
|
if (m_state == EDITOR_STATE_STANDBY ||
|
|
m_state == EDITOR_STATE_DRAWING) {
|
|
if (editor_keys_toset_zoom(msg->key.scancode))
|
|
return true;
|
|
}
|
|
|
|
if (this->hasMouse()) {
|
|
switch (msg->key.scancode) {
|
|
|
|
// Eye-dropper is activated with ALT key
|
|
case KEY_ALT:
|
|
m_alt_pressed = true;
|
|
editor_setcursor(jmouse_x(0), jmouse_y(0));
|
|
return true;
|
|
|
|
case KEY_LCONTROL:
|
|
case KEY_RCONTROL:
|
|
// If the user press the CTRL key when he is dragging pixels (but not pressing the mouse buttons)...
|
|
if (!m_ctrl_pressed && !jmouse_b(0) && m_pixelsMovement) {
|
|
// Drop pixels (sure the user will press the mouse button to start dragging a copy)
|
|
dropPixels();
|
|
}
|
|
m_ctrl_pressed = true;
|
|
editor_setcursor(jmouse_x(0), jmouse_y(0));
|
|
return true;
|
|
|
|
case KEY_SPACE:
|
|
m_space_pressed = true;
|
|
editor_setcursor(jmouse_x(0), jmouse_y(0));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// When we are drawing, we "eat" all pressed keys
|
|
if (m_state == EDITOR_STATE_DRAWING)
|
|
return true;
|
|
|
|
break;
|
|
|
|
case JM_KEYRELEASED:
|
|
switch (msg->key.scancode) {
|
|
|
|
// Eye-dropper is deactivated with ALT key
|
|
case KEY_ALT:
|
|
if (m_alt_pressed) {
|
|
m_alt_pressed = false;
|
|
editor_setcursor(jmouse_x(0), jmouse_y(0));
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case KEY_LCONTROL:
|
|
case KEY_RCONTROL:
|
|
if (m_ctrl_pressed) {
|
|
m_ctrl_pressed = false;
|
|
editor_setcursor(jmouse_x(0), jmouse_y(0));
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case KEY_SPACE:
|
|
if (m_space_pressed) {
|
|
// We have to clear all the KEY_SPACE in buffer
|
|
clear_keybuf();
|
|
|
|
m_space_pressed = false;
|
|
editor_setcursor(jmouse_x(0), jmouse_y(0));
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case JM_WHEEL:
|
|
if (m_state == EDITOR_STATE_STANDBY ||
|
|
m_state == EDITOR_STATE_DRAWING) {
|
|
// There are and sprite in the editor and the mouse is inside
|
|
if (m_sprite && this->hasMouse()) {
|
|
int dz = jmouse_z(1) - jmouse_z(0);
|
|
WHEEL_ACTION wheelAction = WHEEL_NONE;
|
|
bool scrollBigSteps = false;
|
|
|
|
// Without modifiers
|
|
if (!(msg->any.shifts & (KB_SHIFT_FLAG | KB_ALT_FLAG | KB_CTRL_FLAG))) {
|
|
wheelAction = WHEEL_ZOOM;
|
|
}
|
|
else {
|
|
#if 1 // TODO make it configurable
|
|
if (has_shifts(msg, KB_ALT_FLAG)) {
|
|
if (has_shifts(msg, KB_SHIFT_FLAG))
|
|
wheelAction = WHEEL_BG;
|
|
else
|
|
wheelAction = WHEEL_FG;
|
|
}
|
|
else if (has_shifts(msg, KB_CTRL_FLAG)) {
|
|
wheelAction = WHEEL_FRAME;
|
|
}
|
|
#else
|
|
if (has_shifts(msg, KB_CTRL_FLAG))
|
|
wheelAction = WHEEL_HSCROLL;
|
|
else
|
|
wheelAction = WHEEL_VSCROLL;
|
|
|
|
if (has_shifts(msg, KB_SHIFT_FLAG))
|
|
scrollBigSteps = true;
|
|
#endif
|
|
}
|
|
|
|
switch (wheelAction) {
|
|
|
|
case WHEEL_NONE:
|
|
// Do nothing
|
|
break;
|
|
|
|
case WHEEL_FG:
|
|
if (m_state == EDITOR_STATE_STANDBY) {
|
|
int newIndex = 0;
|
|
if (color_type(app_get_colorbar()->getFgColor()) == COLOR_TYPE_INDEX) {
|
|
newIndex = color_get_index(app_get_colorbar()->getFgColor()) + dz;
|
|
newIndex = MID(0, newIndex, 255);
|
|
}
|
|
app_get_colorbar()->setFgColor(color_index(newIndex));
|
|
}
|
|
break;
|
|
|
|
case WHEEL_BG:
|
|
if (m_state == EDITOR_STATE_STANDBY) {
|
|
int newIndex = 0;
|
|
if (color_type(app_get_colorbar()->getBgColor()) == COLOR_TYPE_INDEX) {
|
|
newIndex = color_get_index(app_get_colorbar()->getBgColor()) + dz;
|
|
newIndex = MID(0, newIndex, 255);
|
|
}
|
|
app_get_colorbar()->setBgColor(color_index(newIndex));
|
|
}
|
|
break;
|
|
|
|
case WHEEL_FRAME:
|
|
if (m_state == EDITOR_STATE_STANDBY) {
|
|
Command* command = CommandsModule::instance()->get_command_by_name
|
|
((dz < 0) ? CommandId::goto_next_frame: CommandId::goto_previous_frame);
|
|
if (command)
|
|
UIContext::instance()->execute_command(command, NULL);
|
|
}
|
|
break;
|
|
|
|
case WHEEL_ZOOM: {
|
|
int zoom = MID(MIN_ZOOM, m_zoom-dz, MAX_ZOOM);
|
|
if (m_zoom != zoom)
|
|
editor_set_zoom_and_center_in_mouse(zoom, msg->mouse.x, msg->mouse.y);
|
|
break;
|
|
}
|
|
|
|
case WHEEL_HSCROLL:
|
|
case WHEEL_VSCROLL: {
|
|
JWidget view = jwidget_get_view(this);
|
|
JRect vp = jview_get_viewport_position(view);
|
|
int scroll_x, scroll_y;
|
|
int dx = 0;
|
|
int dy = 0;
|
|
int thick = m_cursor_thick;
|
|
|
|
if (wheelAction == WHEEL_HSCROLL) {
|
|
dx = dz * jrect_w(vp);
|
|
}
|
|
else {
|
|
dy = dz * jrect_h(vp);
|
|
}
|
|
|
|
if (scrollBigSteps) {
|
|
dx /= 2;
|
|
dy /= 2;
|
|
}
|
|
else {
|
|
dx /= 10;
|
|
dy /= 10;
|
|
}
|
|
|
|
jview_get_scroll(view, &scroll_x, &scroll_y);
|
|
|
|
jmouse_hide();
|
|
if (thick)
|
|
editor_clean_cursor();
|
|
editor_set_scroll(scroll_x+dx, scroll_y+dy, true);
|
|
if (thick)
|
|
editor_draw_cursor(jmouse_x(0), jmouse_y(0));
|
|
jmouse_show();
|
|
|
|
jrect_free(vp);
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case JM_SETCURSOR:
|
|
editor_setcursor(msg->mouse.x, msg->mouse.y);
|
|
return true;
|
|
|
|
}
|
|
|
|
return Widget::onProcessMessage(msg);
|
|
}
|
|
|
|
// When the current tool is changed
|
|
void Editor::onCurrentToolChange()
|
|
{
|
|
UIContext* context = UIContext::instance();
|
|
Tool* current_tool = context->getSettings()->getCurrentTool();
|
|
|
|
// If the user changed the tool when he/she is moving pixels,
|
|
// we have to drop the pixels only if the new tool is not selection...
|
|
if (m_pixelsMovement &&
|
|
(!current_tool->getInk(0)->isSelection() ||
|
|
!current_tool->getInk(1)->isSelection())) {
|
|
// We have to drop pixels
|
|
dropPixels();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns size for the editor viewport
|
|
*/
|
|
void Editor::editor_request_size(int *w, int *h)
|
|
{
|
|
if (m_sprite) {
|
|
JWidget view = jwidget_get_view(this);
|
|
JRect vp = jview_get_viewport_position(view);
|
|
|
|
m_offset_x = jrect_w(vp)/2 - 1;
|
|
m_offset_y = jrect_h(vp)/2 - 1;
|
|
|
|
*w = (m_sprite->getWidth() << m_zoom) + m_offset_x*2;
|
|
*h = (m_sprite->getHeight() << m_zoom) + m_offset_y*2;
|
|
|
|
jrect_free(vp);
|
|
}
|
|
else {
|
|
*w = 4;
|
|
*h = 4;
|
|
}
|
|
}
|
|
|
|
void Editor::editor_setcursor(int x, int y)
|
|
{
|
|
UIContext* context = UIContext::instance();
|
|
Tool* current_tool = context->getSettings()->getCurrentTool();
|
|
|
|
switch (m_state) {
|
|
|
|
case EDITOR_STATE_SCROLLING:
|
|
hide_drawing_cursor();
|
|
jmouse_set_cursor(JI_CURSOR_SCROLL);
|
|
break;
|
|
|
|
case EDITOR_STATE_DRAWING:
|
|
if (current_tool->getInk(0)->isEyedropper()) {
|
|
hide_drawing_cursor();
|
|
jmouse_set_cursor(JI_CURSOR_EYEDROPPER);
|
|
return;
|
|
}
|
|
else {
|
|
jmouse_set_cursor(JI_CURSOR_NULL);
|
|
show_drawing_cursor();
|
|
}
|
|
break;
|
|
|
|
case EDITOR_STATE_STANDBY:
|
|
if (m_sprite) {
|
|
UIContext* context = UIContext::instance();
|
|
Tool* current_tool = context->getSettings()->getCurrentTool();
|
|
|
|
editor_update_candraw(); // TODO remove this
|
|
|
|
// Pixels movement
|
|
if (m_pixelsMovement) {
|
|
int x, y;
|
|
screen_to_editor(jmouse_x(0), jmouse_y(0), &x, &y);
|
|
|
|
// Move selection
|
|
if (m_pixelsMovement->isDragging() ||
|
|
m_sprite->getMask()->contains_point(x, y)) {
|
|
hide_drawing_cursor();
|
|
jmouse_set_cursor(JI_CURSOR_MOVE);
|
|
|
|
if (!m_insideSelection)
|
|
m_insideSelection = true;
|
|
return;
|
|
}
|
|
|
|
if (m_insideSelection)
|
|
m_insideSelection = false;
|
|
|
|
// Draw
|
|
if (m_cursor_candraw) {
|
|
jmouse_set_cursor(JI_CURSOR_NULL);
|
|
show_drawing_cursor();
|
|
}
|
|
// Forbidden
|
|
else {
|
|
hide_drawing_cursor();
|
|
jmouse_set_cursor(JI_CURSOR_FORBIDDEN);
|
|
}
|
|
}
|
|
// Eyedropper
|
|
else if (m_alt_pressed) {
|
|
hide_drawing_cursor();
|
|
jmouse_set_cursor(JI_CURSOR_EYEDROPPER);
|
|
}
|
|
// Move layer
|
|
else if (m_ctrl_pressed &&
|
|
(!current_tool ||
|
|
!current_tool->getInk(0)->isSelection())) {
|
|
hide_drawing_cursor();
|
|
jmouse_set_cursor(JI_CURSOR_MOVE);
|
|
}
|
|
// Scroll
|
|
else if (m_space_pressed) {
|
|
hide_drawing_cursor();
|
|
jmouse_set_cursor(JI_CURSOR_SCROLL);
|
|
}
|
|
else {
|
|
if (current_tool) {
|
|
// If the current tool change selection (e.g. rectangular marquee, etc.)
|
|
if (current_tool->getInk(0)->isSelection()) {
|
|
int x, y;
|
|
screen_to_editor(jmouse_x(0), jmouse_y(0), &x, &y);
|
|
|
|
// Move pixels
|
|
if (m_sprite->getMask()->contains_point(x, y)) {
|
|
hide_drawing_cursor();
|
|
if (m_ctrl_pressed)
|
|
jmouse_set_cursor(JI_CURSOR_NORMAL_ADD);
|
|
else
|
|
jmouse_set_cursor(JI_CURSOR_MOVE);
|
|
|
|
if (!m_insideSelection) {
|
|
m_insideSelection = true;
|
|
//turnOnSelectionModifiers();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
else if (current_tool->getInk(0)->isEyedropper()) {
|
|
hide_drawing_cursor();
|
|
jmouse_set_cursor(JI_CURSOR_EYEDROPPER);
|
|
return;
|
|
}
|
|
else if (current_tool->getInk(0)->isScrollMovement()) {
|
|
hide_drawing_cursor();
|
|
jmouse_set_cursor(JI_CURSOR_SCROLL);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (m_insideSelection) {
|
|
m_insideSelection = false;
|
|
//deleteDecorators();
|
|
}
|
|
|
|
// Draw
|
|
if (m_cursor_candraw) {
|
|
jmouse_set_cursor(JI_CURSOR_NULL);
|
|
show_drawing_cursor();
|
|
}
|
|
// Forbidden
|
|
else {
|
|
hide_drawing_cursor();
|
|
jmouse_set_cursor(JI_CURSOR_FORBIDDEN);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
hide_drawing_cursor();
|
|
jmouse_set_cursor(JI_CURSOR_NORMAL);
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
/* TODO this routine should be called in a change of context */
|
|
void Editor::editor_update_candraw()
|
|
{
|
|
m_cursor_candraw =
|
|
(m_sprite != NULL &&
|
|
m_sprite->getCurrentLayer() != NULL &&
|
|
m_sprite->getCurrentLayer()->is_image() &&
|
|
m_sprite->getCurrentLayer()->is_readable() &&
|
|
m_sprite->getCurrentLayer()->is_writable() /* && */
|
|
/* layer_get_cel(m_sprite->layer, m_sprite->frame) != NULL */
|
|
);
|
|
}
|
|
|
|
void Editor::editor_set_zoom_and_center_in_mouse(int zoom, int mouse_x, int mouse_y)
|
|
{
|
|
JWidget view = jwidget_get_view(this);
|
|
JRect vp = jview_get_viewport_position(view);
|
|
int x, y;
|
|
|
|
hide_drawing_cursor();
|
|
screen_to_editor(mouse_x, mouse_y, &x, &y);
|
|
|
|
x = m_offset_x - jrect_w(vp)/2 + ((1<<zoom)>>1) + (x << zoom);
|
|
y = m_offset_y - jrect_h(vp)/2 + ((1<<zoom)>>1) + (y << zoom);
|
|
|
|
if ((m_zoom != zoom) ||
|
|
(m_cursor_editor_x != (vp->x1+vp->x2)/2) ||
|
|
(m_cursor_editor_y != (vp->y1+vp->y2)/2)) {
|
|
int use_refresh_region = (m_zoom == zoom) ? true: false;
|
|
|
|
m_zoom = zoom;
|
|
|
|
editor_update();
|
|
editor_set_scroll(x, y, use_refresh_region);
|
|
|
|
jmouse_set_position((vp->x1+vp->x2)/2, (vp->y1+vp->y2)/2);
|
|
}
|
|
show_drawing_cursor();
|
|
jrect_free(vp);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Decorator implementation
|
|
|
|
Editor::Decorator::Decorator(Type type, const Rect& bounds)
|
|
{
|
|
m_type = type;
|
|
m_bounds = bounds;
|
|
}
|
|
|
|
Editor::Decorator::~Decorator()
|
|
{
|
|
}
|
|
|
|
void Editor::Decorator::drawDecorator(Editor* editor, BITMAP* bmp)
|
|
{
|
|
rect(bmp,
|
|
m_bounds.x, m_bounds.y,
|
|
m_bounds.x+m_bounds.w-1,
|
|
m_bounds.y+m_bounds.h-1, makecol(255, 255, 255));
|
|
|
|
rectfill(bmp,
|
|
m_bounds.x+1, m_bounds.y+1,
|
|
m_bounds.x+m_bounds.w-2, m_bounds.y+m_bounds.h-2,
|
|
makecol(0, 0, 0));
|
|
}
|
|
|
|
bool Editor::Decorator::isInsideDecorator(int x, int y)
|
|
{
|
|
return (m_bounds.contains(Point(x, y)));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// IToolLoop implementation
|
|
|
|
class ToolLoopImpl : public IToolLoop
|
|
{
|
|
Editor* m_editor;
|
|
Context* m_context;
|
|
Tool* m_tool;
|
|
Pen* m_pen;
|
|
Sprite* m_sprite;
|
|
Layer* m_layer;
|
|
Cel* m_cel;
|
|
Image* m_cel_image;
|
|
bool m_cel_created;
|
|
int m_old_cel_x;
|
|
int m_old_cel_y;
|
|
bool m_filled;
|
|
bool m_previewFilled;
|
|
int m_sprayWidth;
|
|
int m_spraySpeed;
|
|
TiledMode m_tiled_mode;
|
|
Image* m_src_image;
|
|
Image* m_dst_image;
|
|
Mask* m_mask;
|
|
Point m_maskOrigin;
|
|
int m_opacity;
|
|
int m_tolerance;
|
|
Point m_offset;
|
|
Point m_speed;
|
|
bool m_canceled;
|
|
int m_button;
|
|
int m_primary_color;
|
|
int m_secondary_color;
|
|
|
|
public:
|
|
ToolLoopImpl(Editor* editor, Context* context, Tool* tool, Sprite* sprite, Layer* layer,
|
|
int button, color_t primary_color, color_t secondary_color)
|
|
{
|
|
m_editor = editor;
|
|
m_context = context;
|
|
m_tool = tool;
|
|
m_sprite = sprite;
|
|
m_layer = layer;
|
|
m_cel = NULL;
|
|
m_cel_image = NULL;
|
|
m_cel_created = false;
|
|
m_canceled = false;
|
|
m_button = button;
|
|
m_primary_color = get_color_for_layer(layer, primary_color);
|
|
m_secondary_color = get_color_for_layer(layer, secondary_color);
|
|
|
|
// Settings
|
|
ISettings* settings = m_context->getSettings();
|
|
|
|
m_tiled_mode = settings->getTiledMode();
|
|
|
|
switch (tool->getFill(m_button)) {
|
|
case TOOL_FILL_NONE:
|
|
m_filled = false;
|
|
break;
|
|
case TOOL_FILL_ALWAYS:
|
|
m_filled = true;
|
|
break;
|
|
case TOOL_FILL_OPTIONAL:
|
|
m_filled = settings->getToolSettings(m_tool)->getFilled();
|
|
break;
|
|
}
|
|
m_previewFilled = settings->getToolSettings(m_tool)->getPreviewFilled();
|
|
|
|
m_sprayWidth = settings->getToolSettings(m_tool)->getSprayWidth();
|
|
m_spraySpeed = settings->getToolSettings(m_tool)->getSpraySpeed();
|
|
|
|
// Create the pen
|
|
IPenSettings* pen_settings = settings->getToolSettings(m_tool)->getPen();
|
|
ASSERT(pen_settings != NULL);
|
|
|
|
m_pen = new Pen(pen_settings->getType(),
|
|
pen_settings->getSize(),
|
|
pen_settings->getAngle());
|
|
|
|
// Get cel and image where we can draw
|
|
|
|
if (m_layer->is_image()) {
|
|
m_cel = static_cast<LayerImage*>(sprite->getCurrentLayer())->get_cel(sprite->getCurrentFrame());
|
|
if (m_cel)
|
|
m_cel_image = sprite->getStock()->image[m_cel->image];
|
|
}
|
|
|
|
if (m_cel == NULL) {
|
|
// create the image
|
|
m_cel_image = image_new(sprite->getImgType(), sprite->getWidth(), sprite->getHeight());
|
|
image_clear(m_cel_image, 0);
|
|
|
|
// create the cel
|
|
m_cel = cel_new(sprite->getCurrentFrame(), 0);
|
|
static_cast<LayerImage*>(sprite->getCurrentLayer())->add_cel(m_cel);
|
|
|
|
m_cel_created = true;
|
|
}
|
|
|
|
m_old_cel_x = m_cel->x;
|
|
m_old_cel_y = m_cel->y;
|
|
|
|
// region to draw
|
|
int x1, y1, x2, y2;
|
|
|
|
// non-tiled
|
|
if (m_tiled_mode == TILED_NONE) {
|
|
x1 = MIN(m_cel->x, 0);
|
|
y1 = MIN(m_cel->y, 0);
|
|
x2 = MAX(m_cel->x+m_cel_image->w, m_sprite->getWidth());
|
|
y2 = MAX(m_cel->y+m_cel_image->h, m_sprite->getHeight());
|
|
}
|
|
else { // tiled
|
|
x1 = 0;
|
|
y1 = 0;
|
|
x2 = m_sprite->getWidth();
|
|
y2 = m_sprite->getHeight();
|
|
}
|
|
|
|
// create two copies of the image region which we'll modify with the tool
|
|
m_src_image = image_crop(m_cel_image,
|
|
x1-m_cel->x,
|
|
y1-m_cel->y, x2-x1, y2-y1, 0);
|
|
m_dst_image = image_new_copy(m_src_image);
|
|
|
|
m_mask = m_sprite->getMask();
|
|
m_maskOrigin = (!m_mask->is_empty() ? Point(m_mask->x-x1, m_mask->y-y1):
|
|
Point(0, 0));
|
|
|
|
m_opacity = settings->getToolSettings(m_tool)->getOpacity();
|
|
m_tolerance = settings->getToolSettings(m_tool)->getTolerance();
|
|
m_speed.x = 0;
|
|
m_speed.y = 0;
|
|
|
|
// we have to modify the cel position because it's used in the
|
|
// `render_sprite' routine to draw the `dst_image'
|
|
m_cel->x = x1;
|
|
m_cel->y = y1;
|
|
m_offset.x = -x1;
|
|
m_offset.y = -y1;
|
|
|
|
// Set undo label for any kind of undo used in the whole loop
|
|
if (undo_is_enabled(m_sprite->getUndo()))
|
|
undo_set_label(m_sprite->getUndo(), m_tool->getText().c_str());
|
|
}
|
|
|
|
~ToolLoopImpl()
|
|
{
|
|
if (!m_canceled) {
|
|
// Paint ink
|
|
if (getInk()->isPaint()) {
|
|
/* if the size of each image is the same, we can create an undo
|
|
with only the differences between both images */
|
|
if (m_cel->x == m_old_cel_x &&
|
|
m_cel->y == m_old_cel_y &&
|
|
m_cel_image->w == m_dst_image->w &&
|
|
m_cel_image->h == m_dst_image->h) {
|
|
/* was the 'cel_image' created in the start of the tool-loop? */
|
|
if (m_cel_created) {
|
|
/* then we can keep the 'cel_image'... */
|
|
|
|
/* we copy the 'destination' image to the 'cel_image' */
|
|
image_copy(m_cel_image, m_dst_image, 0, 0);
|
|
|
|
/* add the 'cel_image' in the images' stock of the sprite */
|
|
m_cel->image = stock_add_image(m_sprite->getStock(), m_cel_image);
|
|
|
|
/* is the undo enabled? */
|
|
if (undo_is_enabled(m_sprite->getUndo())) {
|
|
/* we can temporary remove the cel */
|
|
static_cast<LayerImage*>(m_sprite->getCurrentLayer())->remove_cel(m_cel);
|
|
|
|
/* we create the undo information (for the new cel_image
|
|
in the stock and the new cel in the layer)... */
|
|
undo_open(m_sprite->getUndo());
|
|
undo_add_image(m_sprite->getUndo(), m_sprite->getStock(), m_cel->image);
|
|
undo_add_cel(m_sprite->getUndo(), m_sprite->getCurrentLayer(), m_cel);
|
|
undo_close(m_sprite->getUndo());
|
|
|
|
/* and finally we add the cel again in the layer */
|
|
static_cast<LayerImage*>(m_sprite->getCurrentLayer())->add_cel(m_cel);
|
|
}
|
|
}
|
|
else {
|
|
/* undo the dirty region */
|
|
if (undo_is_enabled(m_sprite->getUndo())) {
|
|
Dirty* dirty = dirty_new_from_differences(m_cel_image,
|
|
m_dst_image);
|
|
// TODO error handling
|
|
|
|
dirty_save_image_data(dirty);
|
|
if (dirty != NULL)
|
|
undo_dirty(m_sprite->getUndo(), dirty);
|
|
|
|
dirty_free(dirty);
|
|
}
|
|
|
|
/* copy the 'dst_image' to the cel_image */
|
|
image_copy(m_cel_image, m_dst_image, 0, 0);
|
|
}
|
|
}
|
|
/* if the size of both images are different, we have to replace
|
|
the entire image */
|
|
else {
|
|
if (undo_is_enabled(m_sprite->getUndo())) {
|
|
undo_open(m_sprite->getUndo());
|
|
|
|
if (m_cel->x != m_old_cel_x) {
|
|
int x = m_cel->x;
|
|
m_cel->x = m_old_cel_x;
|
|
undo_int(m_sprite->getUndo(), (GfxObj*)m_cel, &m_cel->x);
|
|
m_cel->x = x;
|
|
}
|
|
if (m_cel->y != m_old_cel_y) {
|
|
int y = m_cel->y;
|
|
m_cel->y = m_old_cel_y;
|
|
undo_int(m_sprite->getUndo(), (GfxObj*)m_cel, &m_cel->y);
|
|
m_cel->y = y;
|
|
}
|
|
|
|
undo_replace_image(m_sprite->getUndo(), m_sprite->getStock(), m_cel->image);
|
|
undo_close(m_sprite->getUndo());
|
|
}
|
|
|
|
/* replace the image in the stock */
|
|
stock_replace_image(m_sprite->getStock(), m_cel->image, m_dst_image);
|
|
|
|
/* destroy the old cel image */
|
|
image_free(m_cel_image);
|
|
|
|
/* now the `dst_image' is used, so we haven't to destroy it */
|
|
m_dst_image = NULL;
|
|
}
|
|
}
|
|
|
|
// Selection ink
|
|
if (getInk()->isSelection())
|
|
m_sprite->generateMaskBoundaries();
|
|
}
|
|
|
|
// If the trace was not canceled or it is not a 'paint' ink...
|
|
if (m_canceled || !getInk()->isPaint()) {
|
|
// Here we destroy the temporary 'cel' created and restore all as it was before
|
|
|
|
m_cel->x = m_old_cel_x;
|
|
m_cel->y = m_old_cel_y;
|
|
|
|
if (m_cel_created) {
|
|
static_cast<LayerImage*>(m_layer)->remove_cel(m_cel);
|
|
delete m_cel;
|
|
delete m_cel_image;
|
|
}
|
|
}
|
|
|
|
delete m_src_image;
|
|
delete m_dst_image;
|
|
delete m_pen;
|
|
}
|
|
|
|
// IToolLoop interface
|
|
Context* getContext() { return m_context; }
|
|
Tool* getTool() { return m_tool; }
|
|
Pen* getPen() { return m_pen; }
|
|
Sprite* getSprite() { return m_sprite; }
|
|
Layer* getLayer() { return m_layer; }
|
|
Image* getSrcImage() { return m_src_image; }
|
|
Image* getDstImage() { return m_dst_image; }
|
|
Mask* getMask() { return m_mask; }
|
|
Point getMaskOrigin() { return m_maskOrigin; }
|
|
int getMouseButton() { return m_button; }
|
|
int getPrimaryColor() { return m_primary_color; }
|
|
void setPrimaryColor(int color) { m_primary_color = color; }
|
|
int getSecondaryColor() { return m_secondary_color; }
|
|
void setSecondaryColor(int color) { m_secondary_color = color; }
|
|
int getOpacity() { return m_opacity; }
|
|
int getTolerance() { return m_tolerance; }
|
|
TiledMode getTiledMode() { return m_tiled_mode; }
|
|
bool getFilled() { return m_filled; }
|
|
bool getPreviewFilled() { return m_previewFilled; }
|
|
int getSprayWidth() { return m_sprayWidth; }
|
|
int getSpraySpeed() { return m_spraySpeed; }
|
|
Point getOffset() { return m_offset; }
|
|
void setSpeed(const Point& speed) { m_speed = speed; }
|
|
Point getSpeed() { return m_speed; }
|
|
ToolInk* getInk() { return m_tool->getInk(m_button); }
|
|
ToolController* getController() { return m_tool->getController(m_button); }
|
|
ToolPointShape* getPointShape() { return m_tool->getPointShape(m_button); }
|
|
ToolIntertwine* getIntertwine() { return m_tool->getIntertwine(m_button); }
|
|
ToolTracePolicy getTracePolicy() { return m_tool->getTracePolicy(m_button); }
|
|
|
|
void cancel() { m_canceled = true; }
|
|
bool isCanceled() { return m_canceled; }
|
|
|
|
Point screenToSprite(const Point& screenPoint)
|
|
{
|
|
Point spritePoint;
|
|
m_editor->screen_to_editor(screenPoint.x, screenPoint.y,
|
|
&spritePoint.x, &spritePoint.y);
|
|
return spritePoint;
|
|
}
|
|
|
|
void updateArea(const Rect& dirty_area)
|
|
{
|
|
int x1 = dirty_area.x-m_offset.x;
|
|
int y1 = dirty_area.y-m_offset.y;
|
|
int x2 = dirty_area.x-m_offset.x+dirty_area.w-1;
|
|
int y2 = dirty_area.y-m_offset.y+dirty_area.h-1;
|
|
|
|
acquire_bitmap(ji_screen);
|
|
editors_draw_sprite_tiled(m_sprite, x1, y1, x2, y2);
|
|
release_bitmap(ji_screen);
|
|
}
|
|
|
|
void updateStatusBar(const char* text)
|
|
{
|
|
app_get_statusbar()->setStatusText(0, text);
|
|
}
|
|
|
|
};
|
|
|
|
IToolLoop* Editor::createToolLoopImpl(Context* context, JMessage msg)
|
|
{
|
|
Tool* current_tool = context->getSettings()->getCurrentTool();
|
|
if (!current_tool)
|
|
return NULL;
|
|
|
|
Sprite* sprite = getSprite();
|
|
Layer* layer = sprite->getCurrentLayer();
|
|
|
|
if (!layer) {
|
|
jalert(_(PACKAGE
|
|
"<<The current sprite does not have any layer."
|
|
"||&Close"));
|
|
return NULL;
|
|
}
|
|
|
|
// if the active layer is not visible
|
|
if (!layer->is_readable()) {
|
|
jalert(_(PACKAGE
|
|
"<<The current layer is hidden,"
|
|
"<<make it visible and try again"
|
|
"||&Close"));
|
|
return NULL;
|
|
}
|
|
// if the active layer is read-only
|
|
else if (!layer->is_writable()) {
|
|
jalert(_(PACKAGE
|
|
"<<The current layer is locked,"
|
|
"<<unlock it and try again"
|
|
"||&Close"));
|
|
return NULL;
|
|
}
|
|
|
|
// Get fg/bg colors
|
|
ColorBar* colorbar = app_get_colorbar();
|
|
color_t fg = colorbar->getFgColor();
|
|
color_t bg = colorbar->getBgColor();
|
|
|
|
if (!color_is_valid(fg) || !color_is_valid(bg)) {
|
|
jalert(PACKAGE
|
|
"<<The current selected foreground and/or background color"
|
|
"<<is out of range. Select valid colors in the color-bar."
|
|
"||&Close");
|
|
return NULL;
|
|
}
|
|
|
|
// Create the new tool loop
|
|
ToolLoopImpl* tool_loop = new ToolLoopImpl(this,
|
|
context,
|
|
current_tool, sprite, layer,
|
|
msg->mouse.left ? 0: 1,
|
|
msg->mouse.left ? fg: bg,
|
|
msg->mouse.left ? bg: fg);
|
|
|
|
return tool_loop;
|
|
}
|