Refactor Editor class to use a state design pattern.

+ Added EditorState and derived classes (StandByState, DrawingState, etc.)
+ Added StatusBarListener.
This commit is contained in:
David Capello 2011-04-10 20:15:17 -03:00
parent ef0cbf0e94
commit 5380877c4a
26 changed files with 1900 additions and 1209 deletions

View File

@ -281,11 +281,16 @@ add_library(aseprite-library
widgets/color_sliders.cpp
widgets/editor/click.cpp
widgets/editor/cursor.cpp
widgets/editor/drawing_state.cpp
widgets/editor/editor.cpp
widgets/editor/editor_listeners.cpp
widgets/editor/editor_view.cpp
widgets/editor/keys.cpp
widgets/editor/moving_pixels_state.cpp
widgets/editor/pixels_movement.cpp
widgets/editor/scrolling_state.cpp
widgets/editor/standby_state.cpp
widgets/editor/tool_loop_impl.cpp
widgets/fileview.cpp
widgets/groupbut.cpp
widgets/hex_color_entry.cpp

View File

@ -59,7 +59,7 @@ void GotoFirstFrameCommand::onExecute(Context* context)
sprite->setCurrentFrame(0);
update_screen_for_document(document);
current_editor->editor_update_statusbar_for_standby();
current_editor->updateStatusBar();
}
//////////////////////////////////////////////////////////////////////
@ -101,7 +101,7 @@ void GotoPreviousFrameCommand::onExecute(Context* context)
sprite->setCurrentFrame(sprite->getTotalFrames()-1);
update_screen_for_document(document);
current_editor->editor_update_statusbar_for_standby();
current_editor->updateStatusBar();
}
//////////////////////////////////////////////////////////////////////
@ -143,7 +143,7 @@ void GotoNextFrameCommand::onExecute(Context* context)
sprite->setCurrentFrame(0);
update_screen_for_document(document);
current_editor->editor_update_statusbar_for_standby();
current_editor->updateStatusBar();
}
//////////////////////////////////////////////////////////////////////
@ -180,7 +180,7 @@ void GotoLastFrameCommand::onExecute(Context* context)
sprite->setCurrentFrame(sprite->getTotalFrames()-1);
update_screen_for_document(document);
current_editor->editor_update_statusbar_for_standby();
current_editor->updateStatusBar();
}
//////////////////////////////////////////////////////////////////////

View File

@ -117,9 +117,12 @@ public:
}
void documentChanged(Editor* editor) OVERRIDE {
if (editor == current_editor) {
if (editor == current_editor)
update_mini_editor_frame(editor);
}
}
void stateChanged(Editor* editor) OVERRIDE {
// Do nothing
}
};
@ -133,6 +136,7 @@ void exit_module_editors()
{
if (mini_editor_frame) {
save_window_pos(mini_editor_frame, "MiniEditor");
delete mini_editor_frame;
mini_editor_frame = NULL;
}
@ -590,6 +594,7 @@ static void update_mini_editor_frame(Editor* editor)
if (mini_editor->getDocument() != document) {
mini_editor->setDocument(document);
mini_editor->setZoom(0);
mini_editor->setState(new EditorState);
}
mini_editor->centerInSpritePoint(pt.x, pt.y);

View File

@ -41,7 +41,7 @@ class PointShape;
class Tool;
// Interface to communicate the sprite editor with the tool when the user
// start using a tool to paint, select, pick color, etc.
// starts using a tool to paint, select, pick color, etc.
//
// All this information should be provided by the editor and consumed
// by the tool (+controller+intertwiner+pointshape+ink).

View File

@ -39,7 +39,6 @@ ToolLoopManager::ToolLoopManager(ToolLoop* toolLoop)
ToolLoopManager::~ToolLoopManager()
{
delete m_toolLoop;
}
bool ToolLoopManager::isCanceled() const

View File

@ -279,8 +279,7 @@ void Editor::editor_draw_cursor(int x, int y, bool refresh)
generate_cursor_boundaries();
// draw pixel/pen preview
if (cursor_type & CURSOR_PENCIL &&
m_state != EDITOR_STATE_DRAWING) {
if (cursor_type & CURSOR_PENCIL && m_state->requirePenPreview()) {
IToolSettings* tool_settings = UIContext::instance()
->getSettings()
->getToolSettings(current_tool);
@ -364,8 +363,7 @@ void Editor::editor_move_cursor(int x, int y, bool refresh)
ji_screen->clip = TRUE;
release_bitmap(ji_screen);
if (cursor_type & CURSOR_PENCIL &&
m_state != EDITOR_STATE_DRAWING) {
if (cursor_type & CURSOR_PENCIL && m_state->requirePenPreview()) {
Pen* pen = editor_get_current_pen();
editors_draw_sprite(m_sprite,
std::min(new_x, old_x)-pen->get_size()/2,
@ -420,8 +418,7 @@ void Editor::editor_clean_cursor(bool refresh)
}
// clean pixel/pen preview
if (cursor_type & CURSOR_PENCIL &&
m_state != EDITOR_STATE_DRAWING) {
if (cursor_type & CURSOR_PENCIL && m_state->requirePenPreview()) {
Pen* pen = editor_get_current_pen();
m_document->prepareExtraCel(x-pen->get_size()/2,

View File

@ -0,0 +1,165 @@
/* ASE - Allegro Sprite Editor
* Copyright (C) 2001-2011 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
*/
#include "config.h"
#include "widgets/editor/drawing_state.h"
#include "gui/message.h"
#include "gui/system.h"
#include "tools/ink.h"
#include "tools/tool.h"
#include "tools/tool_loop.h"
#include "tools/tool_loop_manager.h"
#include "widgets/editor/editor.h"
#include "widgets/editor/standby_state.h"
#include <allegro.h>
static tools::ToolLoopManager::Pointer pointer_from_msg(Message* msg)
{
tools::ToolLoopManager::Pointer::Button button =
(msg->mouse.right ? tools::ToolLoopManager::Pointer::Right:
(msg->mouse.middle ? tools::ToolLoopManager::Pointer::Middle:
tools::ToolLoopManager::Pointer::Left));
return tools::ToolLoopManager::Pointer(msg->mouse.x, msg->mouse.y, button);
}
DrawingState::DrawingState(tools::ToolLoop* toolLoop, Editor* editor, Message* msg)
: m_toolLoop(toolLoop)
, m_toolLoopManager(new tools::ToolLoopManager(toolLoop))
{
// Hide the cursor (mainly to clean the pen preview)
editor->hideDrawingCursor();
m_toolLoopManager->prepareLoop(pointer_from_msg(msg));
m_toolLoopManager->pressButton(pointer_from_msg(msg));
// Show drawing cursor again (without pen preview in this case)
editor->showDrawingCursor();
editor->captureMouse();
}
DrawingState::~DrawingState()
{
delete m_toolLoopManager;
delete m_toolLoop;
m_toolLoopManager = NULL;
m_toolLoop = NULL;
}
bool DrawingState::onMouseDown(Editor* editor, Message* msg)
{
// Drawing loop
ASSERT(m_toolLoopManager != NULL);
// Notify the mouse button down to the tool loop manager.
m_toolLoopManager->pressButton(pointer_from_msg(msg));
// Cancel drawing loop
if (m_toolLoopManager->isCanceled()) {
m_toolLoopManager->releaseLoop(pointer_from_msg(msg));
delete m_toolLoopManager;
m_toolLoopManager = NULL;
// Change to standby state
editor->setState(new StandbyState());
editor->releaseMouse();
}
return true;
}
bool DrawingState::onMouseUp(Editor* editor, Message* msg)
{
ASSERT(m_toolLoopManager != NULL);
// Notify the release of the mouse button to the tool loop manager.
if (m_toolLoopManager->releaseButton(pointer_from_msg(msg)))
return true;
m_toolLoopManager->releaseLoop(pointer_from_msg(msg));
// Back to standby state.
editor->setState(new StandbyState);
editor->releaseMouse();
return true;
}
bool DrawingState::onMouseMove(Editor* editor, Message* msg)
{
ASSERT(m_toolLoopManager != NULL);
acquire_bitmap(ji_screen);
// Hide the drawing cursor
editor->hideDrawingCursor();
// Infinite scroll
editor->controlInfiniteScroll(msg);
// Hide the cursor again
editor->hideDrawingCursor();
// notify mouse movement to the tool
ASSERT(m_toolLoopManager != NULL);
m_toolLoopManager->movement(pointer_from_msg(msg));
// draw the cursor again
editor->showDrawingCursor();
release_bitmap(ji_screen);
return true;
}
bool DrawingState::onSetCursor(Editor* editor)
{
if (m_toolLoop->getInk()->isEyedropper()) {
editor->hideDrawingCursor();
jmouse_set_cursor(JI_CURSOR_EYEDROPPER);
}
else {
jmouse_set_cursor(JI_CURSOR_NULL);
editor->showDrawingCursor();
}
return true;
}
bool DrawingState::onKeyDown(Editor* editor, Message* msg)
{
if (editor->processKeysToSetZoom(msg->key.scancode))
return true;
// When we are drawing, we "eat" all pressed keys.
return true;
}
bool DrawingState::onKeyUp(Editor* editor, Message* msg)
{
return true;
}
bool DrawingState::onUpdateStatusBar(Editor* editor)
{
// The status bar is updated by ToolLoopImpl::updateStatusBar()
// method called by the ToolLoopManager.
return false;
}

View File

@ -0,0 +1,55 @@
/* ASE - Allegro Sprite Editor
* Copyright (C) 2001-2011 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
*/
#ifndef WIDGETS_EDITOR_DRAWING_STATE_H_INCLUDED
#define WIDGETS_EDITOR_DRAWING_STATE_H_INCLUDED
#include "base/compiler_specific.h"
#include "widgets/editor/standby_state.h"
namespace tools {
class ToolLoop;
class ToolLoopManager;
}
class DrawingState : public StandbyState
{
public:
DrawingState(tools::ToolLoop* loop, Editor* editor, Message* msg);
virtual ~DrawingState();
virtual bool onMouseDown(Editor* editor, Message* msg) OVERRIDE;
virtual bool onMouseUp(Editor* editor, Message* msg) OVERRIDE;
virtual bool onMouseMove(Editor* editor, Message* msg) OVERRIDE;
virtual bool onSetCursor(Editor* editor) OVERRIDE;
virtual bool onKeyDown(Editor* editor, Message* msg) OVERRIDE;
virtual bool onKeyUp(Editor* editor, Message* msg) OVERRIDE;
virtual bool onUpdateStatusBar(Editor* editor) OVERRIDE;
// Drawing state doesn't require the pen-preview because we are
// already drawing (viewing the real trace).
virtual bool requirePenPreview() OVERRIDE { return false; }
private:
// The tool-loop.
tools::ToolLoop* m_toolLoop;
// Tool-loop manager
tools::ToolLoopManager* m_toolLoopManager;
};
#endif // WIDGETS_EDITOR_DRAWING_STATE_H_INCLUDED

File diff suppressed because it is too large Load Diff

View File

@ -26,6 +26,7 @@
#include "gui/base.h"
#include "gui/widget.h"
#include "widgets/editor/editor_listeners.h"
#include "widgets/editor/editor_state.h"
#define MIN_ZOOM 0
#define MAX_ZOOM 5
@ -37,8 +38,6 @@ class View;
namespace tools {
class Tool;
class ToolLoop;
class ToolLoopManager;
}
class Editor : public Widget
@ -49,6 +48,11 @@ public:
Editor();
~Editor();
EditorState* getState() const { return m_state; }
// Changes the state of the editor. Deletes the old state.
void setState(EditorState* state);
Document* getDocument() { return m_document; }
void setDocument(Document* document);
@ -81,13 +85,13 @@ public:
void drawMaskSafe();
void flashCurrentLayer();
void setMaskColorForPixelsMovement(const Color& color);
void screenToEditor(int xin, int yin, int *xout, int *yout);
void editorToScreen(int xin, int yin, int *xout, int *yout);
void showDrawingCursor();
void hideDrawingCursor();
void moveDrawingCursor();
void addListener(EditorListener* listener);
void removeListener(EditorListener* listener);
@ -97,8 +101,23 @@ public:
// Changes the scroll to see the given point as the center of the editor.
void centerInSpritePoint(int x, int y);
void updateStatusBar();
void editor_update_statusbar_for_standby();
// Control scroll when cursor goes out of the editor.
void controlInfiniteScroll(Message* msg);
tools::Tool* getCurrentEditorTool();
// Returns true if we are able to draw in the current doc/sprite/layer/cel.
bool canDraw();
// Returns true if the cursor is inside the active mask/selection.
bool isInsideSelection();
void setZoomAndCenterInMouse(int zoom, int mouse_x, int mouse_y);
bool processKeysToSetZoom(int scancode);
// in cursor.c
@ -111,19 +130,12 @@ public:
static void editor_cursor_exit();
private:
void editor_update_statusbar_for_pixel_movement();
void editor_update_quicktool();
void editor_draw_cursor(int x, int y, bool refresh = true);
void editor_move_cursor(int x, int y, bool refresh = true);
void editor_clean_cursor(bool refresh = true);
bool editor_cursor_is_subpixel();
// keys.c
bool editor_keys_toset_zoom(int scancode);
public:
// click.c
@ -147,45 +159,27 @@ protected:
private:
void drawGrid(const gfx::Rect& gridBounds, const Color& color);
void controlInfiniteScroll(Message* msg);
void dropPixels();
tools::Tool* getCurrentEditorTool();
void editor_request_size(int *w, int *h);
void editor_setcursor(int x, int y);
void editor_update_candraw();
void setZoomAndCenterInMouse(int zoom, int mouse_x, int mouse_y);
tools::ToolLoop* createToolLoopImpl(Context* context, Message* msg);
void editor_setcursor();
void for_each_pixel_of_pen(int screen_x, int screen_y,
int sprite_x, int sprite_y, int color,
void (*pixel)(BITMAP *bmp, int x, int y, int color));
// editor states
enum State {
EDITOR_STATE_STANDBY,
EDITOR_STATE_SCROLLING,
EDITOR_STATE_DRAWING,
};
// Main properties
State m_state; // Editor main state
// Current editor state (it can be shared between several editors to
// the same document). This member cannot be NULL.
EditorState* m_state;
Document* m_document; // Active document in the editor
Sprite* m_sprite; // Active sprite in the editor
int m_zoom; // Zoom in the editor
// Drawing cursor
int m_cursor_thick;
int m_cursor_screen_x; /* position in the screen (view) */
int m_cursor_screen_x; // Position in the screen (view)
int m_cursor_screen_y;
int m_cursor_editor_x; /* position in the editor (model) */
int m_cursor_editor_x; // Position in the editor (model)
int m_cursor_editor_y;
bool m_cursor_candraw : 1;
// True if the cursor is inside the mask/selection
bool m_insideSelection : 1;
// Current selected quicktool (this genererally should be NULL if
// the user is not pressing any keyboard key).
@ -195,20 +189,10 @@ private:
int m_offset_x;
int m_offset_y;
/* marching ants stuff */
// Marching ants stuff
int m_mask_timer_id;
int m_offset_count;
/* region that must be updated */
JRegion m_refresh_region;
// Tool-loop manager
tools::ToolLoopManager* m_toolLoopManager;
// Helper member to move selection. If this member is NULL it means the
// user is not moving pixels.
PixelsMovement* m_pixelsMovement;
// This slot is used to disconnect the Editor from CurrentToolChange
// signal (because the editor can be destroyed and the application
// still continue running and generating CurrentToolChange

View File

@ -26,7 +26,14 @@ class EditorListener
public:
virtual ~EditorListener() { }
virtual void dispose() = 0;
// Called when the editor's state changes.
virtual void stateChanged(Editor* editor) = 0;
// Called when the scroll or zoom of the editor changes.
virtual void scrollChanged(Editor* editor) = 0;
// Called when the document shown in the editor changes.
virtual void documentChanged(Editor* editor) = 0;
};

View File

@ -37,6 +37,11 @@ void EditorListeners::removeListener(EditorListener* listener)
m_listeners.removeListener(listener);
}
void EditorListeners::notifyStateChanged(Editor* editor)
{
m_listeners.notify(&EditorListener::stateChanged, editor);
}
void EditorListeners::notifyScrollChanged(Editor* editor)
{
m_listeners.notify(&EditorListener::scrollChanged, editor);

View File

@ -32,6 +32,7 @@ public:
void addListener(EditorListener* listener);
void removeListener(EditorListener* listener);
void notifyStateChanged(Editor* editor);
void notifyScrollChanged(Editor* editor);
void notifyDocumentChanged(Editor* editor);

View File

@ -0,0 +1,81 @@
/* ASE - Allegro Sprite Editor
* Copyright (C) 2001-2011 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
*/
#ifndef WIDGETS_EDITOR_EDITOR_STATE_H_INCLUDED
#define WIDGETS_EDITOR_EDITOR_STATE_H_INCLUDED
class Editor;
union Message;
// Represents one state of the sprite's editor (Editor class). This
// is a base class, a dummy state that ignores all events from the
// Editor. Subclasses overrides these methods to customize the
// behavior of the Editor to do different tasks (e.g. scrolling,
// drawing in the active sprite, etc.).
class EditorState
{
public:
virtual ~EditorState() { }
// Called just before this state is deleted and replaced by a new
// state in the Editor::setState() method.
virtual void onBeforeChangeState(Editor* editor) { }
// Called when this instance is set as the new Editor's state when
// Editor::setState() method is used.
virtual void onAfterChangeState(Editor* editor) { }
// Called when the current tool in the tool bar changes. It is
// useful for states which depends on the selected current tool (as
// MovingPixelsState which drops the pixels in case the user selects
// other drawing tool).
virtual void onCurrentToolChange(Editor* editor) { }
// Called when the user presses a mouse button over the editor.
virtual bool onMouseDown(Editor* editor, Message* msg) { return false; }
// Called when the user releases a mouse button.
virtual bool onMouseUp(Editor* editor, Message* msg) { return false; }
// Called when the user moves the mouse over the editor.
virtual bool onMouseMove(Editor* editor, Message* msg) { return false; }
// Called when the user moves the mouse wheel over the editor.
virtual bool onMouseWheel(Editor* editor, Message* msg) { return false; }
// Called each time the mouse changes its position so we can set an
// appropiated cursor depending on the new coordinates of the mouse
// pointer.
virtual bool onSetCursor(Editor* editor) { return false; }
// Called when a key is pressed over the current editor.
virtual bool onKeyDown(Editor* editor, Message* msg) { return false; }
// Called when a key is released.
virtual bool onKeyUp(Editor* editor, Message* msg) { return false; }
// Called when a key is released.
virtual bool onUpdateStatusBar(Editor* editor) { return false; }
// Returns true if the this state requires the pen-preview as
// drawing cursor.
virtual bool requirePenPreview() { return false; }
};
#endif // WIDGETS_EDITOR_EDITOR_STATE_H_INCLUDED

View File

@ -35,7 +35,7 @@
#include "widgets/color_bar.h"
#include "widgets/editor/editor.h"
bool Editor::editor_keys_toset_zoom(int scancode)
bool Editor::processKeysToSetZoom(int scancode)
{
if ((m_sprite) &&
(this->hasMouse()) &&

View File

@ -0,0 +1,274 @@
/* ASE - Allegro Sprite Editor
* Copyright (C) 2001-2011 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
*/
#include "config.h"
#include "widgets/editor/moving_pixels_state.h"
#include "app.h"
#include "app/color_utils.h"
#include "gfx/rect.h"
#include "gui/message.h"
#include "gui/system.h"
#include "gui/view.h"
#include "modules/editors.h"
#include "raster/mask.h"
#include "raster/sprite.h"
#include "tools/ink.h"
#include "tools/tool.h"
#include "util/misc.h"
#include "widgets/editor/editor.h"
#include "widgets/editor/pixels_movement.h"
#include "widgets/editor/standby_state.h"
#include "widgets/statebar.h"
#include <allegro.h>
MovingPixelsState::MovingPixelsState(Editor* editor, Message* msg, Image* imge, int x, int y, int opacity)
{
// Copy the mask to the extra cel image
Document* document = editor->getDocument();
Sprite* sprite = editor->getSprite();
Image* tmpImage = NewImageFromMask(document);
x = document->getMask()->x;
y = document->getMask()->y;
m_pixelsMovement = new PixelsMovement(document, sprite, tmpImage, x, y, opacity);
delete tmpImage;
// If the CTRL key is pressed start dragging a copy of the selection
if (key[KEY_LCONTROL] || key[KEY_RCONTROL]) // TODO configurable
m_pixelsMovement->copyMask();
else
m_pixelsMovement->cutMask();
editor->screenToEditor(msg->mouse.x, msg->mouse.y, &x, &y);
m_pixelsMovement->catchImage(x, y);
// Setup mask color
setTransparentColor(app_get_statusbar()->getTransparentColor());
app_get_statusbar()->addListener(this);
app_get_statusbar()->showMovePixelsOptions();
editor->captureMouse();
}
MovingPixelsState::~MovingPixelsState()
{
app_get_statusbar()->removeListener(this);
delete m_pixelsMovement;
}
void MovingPixelsState::onBeforeChangeState(Editor* editor)
{
ASSERT(m_pixelsMovement != NULL);
// If we are changing to another state, we've to drop the image.
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;
editor->releaseMouse();
app_get_statusbar()->hideMovePixelsOptions();
}
void MovingPixelsState::onCurrentToolChange(Editor* editor)
{
ASSERT(m_pixelsMovement != NULL);
tools::Tool* current_tool = editor->getCurrentEditorTool();
// 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(editor);
}
}
bool MovingPixelsState::onMouseDown(Editor* editor, Message* msg)
{
ASSERT(m_pixelsMovement != NULL);
// Start "moving pixels" loop
if (editor->isInsideSelection() && (msg->mouse.left ||
msg->mouse.right)) {
// Re-catch the image
int x, y;
editor->screenToEditor(msg->mouse.x, msg->mouse.y, &x, &y);
m_pixelsMovement->catchImageAgain(x, y);
editor->captureMouse();
return true;
}
// End "moving pixels" loop
else {
// Drop pixels (e.g. to start drawing)
dropPixels(editor);
}
// Use StandbyState implementation
return StandbyState::onMouseDown(editor, msg);
}
bool MovingPixelsState::onMouseUp(Editor* editor, Message* msg)
{
ASSERT(m_pixelsMovement != NULL);
// Drop the image temporarily in this location (where the user releases the mouse)
m_pixelsMovement->dropImageTemporarily();
editor->releaseMouse();
return true;
}
bool MovingPixelsState::onMouseMove(Editor* editor, Message* msg)
{
ASSERT(m_pixelsMovement != NULL);
// If there is a button pressed
if (m_pixelsMovement->isDragging()) {
// Infinite scroll
editor->controlInfiniteScroll(msg);
// Get the position of the mouse in the sprite
int x, y;
editor->screenToEditor(msg->mouse.x, msg->mouse.y, &x, &y);
// Drag the image to that position
gfx::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(editor->getSprite(),
bounds.x, bounds.y,
bounds.x+bounds.w-1,
bounds.y+bounds.h-1);
jmouse_show();
}
editor->updateStatusBar();
return true;
}
// Use StandbyState implementation
return StandbyState::onMouseMove(editor, msg);
}
bool MovingPixelsState::onMouseWheel(Editor* editor, Message* msg)
{
ASSERT(m_pixelsMovement != NULL);
// Use StandbyState implementation
return StandbyState::onMouseWheel(editor, msg);
}
bool MovingPixelsState::onSetCursor(Editor* editor)
{
ASSERT(m_pixelsMovement != NULL);
// Move selection
if (m_pixelsMovement->isDragging() || editor->isInsideSelection()) {
editor->hideDrawingCursor();
jmouse_set_cursor(JI_CURSOR_MOVE);
return true;
}
// Use StandbyState implementation
return StandbyState::onSetCursor(editor);
}
bool MovingPixelsState::onKeyDown(Editor* editor, Message* msg)
{
ASSERT(m_pixelsMovement != NULL);
if (msg->key.scancode == KEY_LCONTROL || // TODO configurable
msg->key.scancode == KEY_RCONTROL) {
// If the user press the CTRL key when he is dragging pixels (but
// not pressing the mouse buttons).
if (!jmouse_b(0) && m_pixelsMovement) {
// Drop pixels (sure the user will press the mouse button to
// start dragging a copy).
dropPixels(editor);
}
}
// Use StandbyState implementation
return StandbyState::onKeyDown(editor, msg);
}
bool MovingPixelsState::onKeyUp(Editor* editor, Message* msg)
{
ASSERT(m_pixelsMovement != NULL);
// Use StandbyState implementation
return StandbyState::onKeyUp(editor, msg);
}
bool MovingPixelsState::onUpdateStatusBar(Editor* editor)
{
ASSERT(m_pixelsMovement != NULL);
gfx::Rect bounds = m_pixelsMovement->getImageBounds();
app_get_statusbar()->setStatusText
(100, "Pos %d %d, Size %d %d",
bounds.x, bounds.y, bounds.w, bounds.h);
return true;
}
void MovingPixelsState::dispose()
{
// Never called as MovingPixelsState is removed automatically as
// StatusBar's listener.
}
void MovingPixelsState::onChangeTransparentColor(const Color& color)
{
setTransparentColor(color);
}
void MovingPixelsState::setTransparentColor(const Color& color)
{
ASSERT(current_editor != NULL);
ASSERT(m_pixelsMovement != NULL);
Sprite* sprite = current_editor->getSprite();
ASSERT(sprite != NULL);
int imgtype = sprite->getImgType();
m_pixelsMovement->setMaskColor(color_utils::color_for_image(color, imgtype));
}
void MovingPixelsState::dropPixels(Editor* editor)
{
// Just change to standby state (we'll receive an
// onBeforeChangeState event).
editor->setState(new StandbyState);
}

View File

@ -0,0 +1,60 @@
/* ASE - Allegro Sprite Editor
* Copyright (C) 2001-2011 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
*/
#ifndef WIDGETS_EDITOR_MOVING_PIXELS_STATE_H_INCLUDED
#define WIDGETS_EDITOR_MOVING_PIXELS_STATE_H_INCLUDED
#include "base/compiler_specific.h"
#include "widgets/editor/standby_state.h"
#include "widgets/statebar.h"
class Editor;
class Image;
class PixelsMovement;
class MovingPixelsState : public StandbyState, StatusBarListener
{
public:
MovingPixelsState(Editor* editor, Message* msg, Image* imge, int x, int y, int opacity);
virtual ~MovingPixelsState();
virtual void onBeforeChangeState(Editor* editor) OVERRIDE;
virtual void onCurrentToolChange(Editor* editor) OVERRIDE;
virtual bool onMouseDown(Editor* editor, Message* msg) OVERRIDE;
virtual bool onMouseUp(Editor* editor, Message* msg) OVERRIDE;
virtual bool onMouseMove(Editor* editor, Message* msg) OVERRIDE;
virtual bool onMouseWheel(Editor* editor, Message* msg) OVERRIDE;
virtual bool onSetCursor(Editor* editor) OVERRIDE;
virtual bool onKeyDown(Editor* editor, Message* msg) OVERRIDE;
virtual bool onKeyUp(Editor* editor, Message* msg) OVERRIDE;
virtual bool onUpdateStatusBar(Editor* editor) OVERRIDE;
protected:
// StatusBarListener interface
virtual void dispose() OVERRIDE;
virtual void onChangeTransparentColor(const Color& color) OVERRIDE;
private:
void setTransparentColor(const Color& color);
void dropPixels(Editor* editor);
// Helper member to move selection.
PixelsMovement* m_pixelsMovement;
};
#endif // WIDGETS_EDITOR_MOVING_PIXELS_STATE_H_INCLUDED

View File

@ -179,6 +179,8 @@ public:
DocumentWriter documentWriter(m_documentReader);
m_undoTransaction.pasteImage(image, cel->getX(), cel->getY(), cel->getOpacity());
m_undoTransaction.commit();
documentWriter->destroyExtraCel();
}
}

View File

@ -0,0 +1,100 @@
/* ASE - Allegro Sprite Editor
* Copyright (C) 2001-2011 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
*/
#include "config.h"
#include "widgets/editor/scrolling_state.h"
#include "app.h"
#include "gfx/rect.h"
#include "gui/message.h"
#include "gui/system.h"
#include "gui/view.h"
#include "modules/editors.h"
#include "raster/sprite.h"
#include "widgets/editor/editor.h"
#include "widgets/editor/standby_state.h"
#include "widgets/statebar.h"
ScrollingState::ScrollingState()
{
}
ScrollingState::~ScrollingState()
{
}
bool ScrollingState::onMouseDown(Editor* editor, Message* msg)
{
return true;
}
bool ScrollingState::onMouseUp(Editor* editor, Message* msg)
{
editor->setState(new StandbyState);
editor->releaseMouse();
return true;
}
bool ScrollingState::onMouseMove(Editor* editor, Message* msg)
{
View* view = View::getView(editor);
gfx::Rect vp = view->getViewportBounds();
gfx::Point scroll = view->getViewScroll();
editor->setEditorScroll(scroll.x+jmouse_x(1)-jmouse_x(0),
scroll.y+jmouse_y(1)-jmouse_y(0), true);
jmouse_control_infinite_scroll(vp);
int x, y;
editor->screenToEditor(jmouse_x(0), jmouse_y(0), &x, &y);
app_get_statusbar()->setStatusText
(0, "Pos %3d %3d (Size %3d %3d)", x, y,
editor->getSprite()->getWidth(),
editor->getSprite()->getHeight());
return true;
}
bool ScrollingState::onMouseWheel(Editor* editor, Message* msg)
{
return false;
}
bool ScrollingState::onSetCursor(Editor* editor)
{
editor->hideDrawingCursor();
jmouse_set_cursor(JI_CURSOR_SCROLL);
return true;
}
bool ScrollingState::onKeyDown(Editor* editor, Message* msg)
{
return false;
}
bool ScrollingState::onKeyUp(Editor* editor, Message* msg)
{
return false;
}
bool ScrollingState::onUpdateStatusBar(Editor* editor)
{
return false;
}

View File

@ -0,0 +1,40 @@
/* ASE - Allegro Sprite Editor
* Copyright (C) 2001-2011 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
*/
#ifndef WIDGETS_EDITOR_SCROLLING_STATE_H_INCLUDED
#define WIDGETS_EDITOR_SCROLLING_STATE_H_INCLUDED
#include "base/compiler_specific.h"
#include "widgets/editor/editor_state.h"
class ScrollingState : public EditorState
{
public:
ScrollingState();
virtual ~ScrollingState();
virtual bool onMouseDown(Editor* editor, Message* msg) OVERRIDE;
virtual bool onMouseUp(Editor* editor, Message* msg) OVERRIDE;
virtual bool onMouseMove(Editor* editor, Message* msg) OVERRIDE;
virtual bool onMouseWheel(Editor* editor, Message* msg) OVERRIDE;
virtual bool onSetCursor(Editor* editor) OVERRIDE;
virtual bool onKeyDown(Editor* editor, Message* msg) OVERRIDE;
virtual bool onKeyUp(Editor* editor, Message* msg) OVERRIDE;
virtual bool onUpdateStatusBar(Editor* editor) OVERRIDE;
};
#endif // WIDGETS_EDITOR_SCROLLING_STATE_H_INCLUDED

View File

@ -0,0 +1,383 @@
/* ASE - Allegro Sprite Editor
* Copyright (C) 2001-2011 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
*/
#include "config.h"
#include "widgets/editor/standby_state.h"
#include "app.h"
#include "commands/commands.h"
#include "commands/params.h"
#include "core/cfg.h"
#include "gfx/rect.h"
#include "gui/alert.h"
#include "gui/message.h"
#include "gui/system.h"
#include "gui/view.h"
#include "modules/editors.h"
#include "raster/layer.h"
#include "raster/mask.h"
#include "raster/sprite.h"
#include "tools/ink.h"
#include "tools/tool.h"
#include "ui_context.h"
#include "util/misc.h"
#include "widgets/color_bar.h"
#include "widgets/editor/drawing_state.h"
#include "widgets/editor/editor.h"
#include "widgets/editor/moving_pixels_state.h"
#include "widgets/editor/scrolling_state.h"
#include "widgets/editor/tool_loop_impl.h"
#include "widgets/statebar.h"
#include <allegro.h>
enum WHEEL_ACTION { WHEEL_NONE,
WHEEL_ZOOM,
WHEEL_VSCROLL,
WHEEL_HSCROLL,
WHEEL_FG,
WHEEL_BG,
WHEEL_FRAME };
static inline bool has_shifts(Message* msg, int shift)
{
return ((msg->any.shifts & shift) == shift);
}
StandbyState::StandbyState()
{
}
StandbyState::~StandbyState()
{
}
bool StandbyState::onMouseDown(Editor* editor, Message* msg)
{
if (editor->hasCapture())
return true;
UIContext* context = UIContext::instance();
tools::Tool* current_tool = editor->getCurrentEditorTool();
Sprite* sprite = editor->getSprite();
// Each time an editor is clicked the current editor and the active
// document are set.
set_current_editor(editor);
context->setActiveDocument(editor->getDocument());
// Start scroll loop
if (msg->mouse.middle ||
current_tool->getInk(msg->mouse.right ? 1: 0)->isScrollMovement()) {
editor->setState(new ScrollingState());
editor->captureMouse();
return true;
}
// Move frames position
if (current_tool->getInk(msg->mouse.right ? 1: 0)->isCelMovement()) {
if ((sprite->getCurrentLayer()) &&
(sprite->getCurrentLayer()->getType() == GFXOBJ_LAYER_IMAGE)) {
// TODO you can move the `Background' with tiled mode
if (sprite->getCurrentLayer()->is_background()) {
Alert::show(PACKAGE
"<<You can't move the `Background' layer."
"||&Close");
}
else if (!sprite->getCurrentLayer()->is_moveable()) {
Alert::show(PACKAGE "<<The layer movement is locked.||&Close");
}
else {
bool click2 = get_config_bool("Options", "MoveClick2", FALSE);
// TODO replace "interactive_move_layer" with a new EditorState
interactive_move_layer(click2 ? Editor::MODE_CLICKANDCLICK:
Editor::MODE_CLICKANDRELEASE,
TRUE, NULL);
}
}
}
// Move selected pixels
else if (editor->isInsideSelection() &&
current_tool->getInk(0)->isSelection() &&
msg->mouse.left) {
int x, y, opacity;
Image* image = sprite->getCurrentImage(&x, &y, &opacity);
if (image) {
if (!sprite->getCurrentLayer()->is_writable()) {
Alert::show(PACKAGE "<<The layer is locked.||&Close");
return true;
}
// Change to MovingPixelsState
editor->setState(new MovingPixelsState(editor, msg, image, x, y, opacity));
}
}
// Call the eyedropper command
else if (current_tool->getInk(msg->mouse.right ? 1: 0)->isEyedropper()) {
Command* eyedropper_cmd =
CommandsModule::instance()->getCommandByName(CommandId::Eyedropper);
Params params;
params.set("target", msg->mouse.right ? "background": "foreground");
UIContext::instance()->executeCommand(eyedropper_cmd, &params);
return true;
}
// Start the Tool-Loop
else if (sprite->getCurrentLayer()) {
tools::ToolLoop* toolLoop = create_tool_loop(editor, context, msg);
if (toolLoop)
editor->setState(new DrawingState(toolLoop, editor, msg));
}
return true;
}
bool StandbyState::onMouseUp(Editor* editor, Message* msg)
{
editor->releaseMouse();
return true;
}
bool StandbyState::onMouseMove(Editor* editor, Message* msg)
{
editor->moveDrawingCursor();
editor->updateStatusBar();
return true;
}
bool StandbyState::onMouseWheel(Editor* editor, Message* msg)
{
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 (app_get_colorbar()->getFgColor().getType() == Color::IndexType) {
newIndex = app_get_colorbar()->getFgColor().getIndex() + dz;
newIndex = MID(0, newIndex, 255);
}
app_get_colorbar()->setFgColor(Color::fromIndex(newIndex));
}
break;
case WHEEL_BG:
// if (m_state == EDITOR_STATE_STANDBY)
{
int newIndex = 0;
if (app_get_colorbar()->getBgColor().getType() == Color::IndexType) {
newIndex = app_get_colorbar()->getBgColor().getIndex() + dz;
newIndex = MID(0, newIndex, 255);
}
app_get_colorbar()->setBgColor(Color::fromIndex(newIndex));
}
break;
case WHEEL_FRAME:
// if (m_state == EDITOR_STATE_STANDBY)
{
Command* command = CommandsModule::instance()->getCommandByName
((dz < 0) ? CommandId::GotoNextFrame:
CommandId::GotoPreviousFrame);
if (command)
UIContext::instance()->executeCommand(command, NULL);
}
break;
case WHEEL_ZOOM: {
int zoom = MID(MIN_ZOOM, editor->getZoom()-dz, MAX_ZOOM);
if (editor->getZoom() != zoom)
editor->setZoomAndCenterInMouse(zoom, msg->mouse.x, msg->mouse.y);
break;
}
case WHEEL_HSCROLL:
case WHEEL_VSCROLL: {
View* view = View::getView(editor);
gfx::Rect vp = view->getViewportBounds();
int dx = 0;
int dy = 0;
if (wheelAction == WHEEL_HSCROLL) {
dx = dz * vp.w;
}
else {
dy = dz * vp.h;
}
if (scrollBigSteps) {
dx /= 2;
dy /= 2;
}
else {
dx /= 10;
dy /= 10;
}
gfx::Point scroll = view->getViewScroll();
editor->hideDrawingCursor();
editor->setEditorScroll(scroll.x+dx, scroll.y+dy, true);
editor->showDrawingCursor();
break;
}
}
return true;
}
bool StandbyState::onSetCursor(Editor* editor)
{
tools::Tool* current_tool = editor->getCurrentEditorTool();
if (current_tool) {
// If the current tool change selection (e.g. rectangular marquee, etc.)
if (current_tool->getInk(0)->isSelection()) {
// Move pixels
if (editor->isInsideSelection()) {
editor->hideDrawingCursor();
if (key[KEY_LCONTROL] ||
key[KEY_RCONTROL]) // TODO configurable keys
jmouse_set_cursor(JI_CURSOR_NORMAL_ADD);
else
jmouse_set_cursor(JI_CURSOR_MOVE);
return true;
}
}
else if (current_tool->getInk(0)->isEyedropper()) {
editor->hideDrawingCursor();
jmouse_set_cursor(JI_CURSOR_EYEDROPPER);
return true;
}
else if (current_tool->getInk(0)->isScrollMovement()) {
editor->hideDrawingCursor();
jmouse_set_cursor(JI_CURSOR_SCROLL);
return true;
}
else if (current_tool->getInk(0)->isCelMovement()) {
editor->hideDrawingCursor();
jmouse_set_cursor(JI_CURSOR_MOVE);
return true;
}
}
// Draw
if (editor->canDraw()) {
jmouse_set_cursor(JI_CURSOR_NULL);
editor->showDrawingCursor();
}
// Forbidden
else {
editor->hideDrawingCursor();
jmouse_set_cursor(JI_CURSOR_FORBIDDEN);
}
return true;
}
bool StandbyState::onKeyDown(Editor* editor, Message* msg)
{
return editor->processKeysToSetZoom(msg->key.scancode);
}
bool StandbyState::onKeyUp(Editor* editor, Message* msg)
{
return false;
}
bool StandbyState::onUpdateStatusBar(Editor* editor)
{
tools::Tool* current_tool = editor->getCurrentEditorTool();
Sprite* sprite = editor->getSprite();
int x, y;
editor->screenToEditor(jmouse_x(0), jmouse_y(0), &x, &y);
if (!sprite) {
app_get_statusbar()->clearText();
}
// For eye-dropper
else if (current_tool->getInk(0)->isEyedropper()) {
int imgtype = sprite->getImgType();
uint32_t pixel = sprite->getPixel(x, y);
Color color = Color::fromImage(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);
}
else {
Mask* mask = editor->getDocument()->getMask();
app_get_statusbar()->setStatusText
(0, "Pos %d %d, Size %d %d, Frame %d",
x, y,
((mask && mask->bitmap)? mask->w: sprite->getWidth()),
((mask && mask->bitmap)? mask->h: sprite->getHeight()),
sprite->getCurrentFrame()+1);
}
return true;
}

View File

@ -0,0 +1,44 @@
/* ASE - Allegro Sprite Editor
* Copyright (C) 2001-2011 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
*/
#ifndef WIDGETS_EDITOR_STANDBY_STATE_H_INCLUDED
#define WIDGETS_EDITOR_STANDBY_STATE_H_INCLUDED
#include "base/compiler_specific.h"
#include "widgets/editor/editor_state.h"
class StandbyState : public EditorState
{
public:
StandbyState();
virtual ~StandbyState();
virtual bool onMouseDown(Editor* editor, Message* msg) OVERRIDE;
virtual bool onMouseUp(Editor* editor, Message* msg) OVERRIDE;
virtual bool onMouseMove(Editor* editor, Message* msg) OVERRIDE;
virtual bool onMouseWheel(Editor* editor, Message* msg) OVERRIDE;
virtual bool onSetCursor(Editor* editor) OVERRIDE;
virtual bool onKeyDown(Editor* editor, Message* msg) OVERRIDE;
virtual bool onKeyUp(Editor* editor, Message* msg) OVERRIDE;
virtual bool onUpdateStatusBar(Editor* editor) OVERRIDE;
// Returns true as the standby state is the only one which shows the
// pen-preview.
virtual bool requirePenPreview() OVERRIDE { return true; }
};
#endif // WIDGETS_EDITOR_STANDBY_STATE_H_INCLUDED

View File

@ -0,0 +1,455 @@
/* ASE - Allegro Sprite Editor
* Copyright (C) 2001-2011 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
*/
#include "config.h"
#include "widgets/editor/tool_loop_impl.h"
#include "app.h"
#include "app/color.h"
#include "app/color_utils.h"
#include "context.h"
#include "gui/gui.h"
#include "modules/editors.h"
#include "raster/cel.h"
#include "raster/dirty.h"
#include "raster/image.h"
#include "raster/layer.h"
#include "raster/mask.h"
#include "raster/pen.h"
#include "raster/sprite.h"
#include "raster/stock.h"
#include "settings/settings.h"
#include "tools/ink.h"
#include "tools/tool.h"
#include "tools/tool_loop.h"
#include "undo/undo_history.h"
#include "undoers/add_cel.h"
#include "undoers/add_image.h"
#include "undoers/close_group.h"
#include "undoers/dirty_area.h"
#include "undoers/open_group.h"
#include "undoers/replace_image.h"
#include "undoers/set_cel_position.h"
#include "widgets/editor/editor.h"
#include "widgets/color_bar.h"
#include "widgets/statebar.h"
#include <allegro.h>
class ToolLoopImpl : public tools::ToolLoop
{
Editor* m_editor;
Context* m_context;
tools::Tool* m_tool;
Pen* m_pen;
Document* m_document;
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;
bool m_useMask;
Mask* m_mask;
gfx::Point m_maskOrigin;
int m_opacity;
int m_tolerance;
gfx::Point m_offset;
gfx::Point m_speed;
bool m_canceled;
int m_button;
int m_primary_color;
int m_secondary_color;
public:
ToolLoopImpl(Editor* editor,
Context* context,
tools::Tool* tool,
Document* document,
Sprite* sprite,
Layer* layer,
int button, const Color& primary_color, const Color& secondary_color)
: m_editor(editor)
, m_context(context)
, m_tool(tool)
, m_document(document)
, 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(color_utils::color_for_layer(primary_color, layer))
, m_secondary_color(color_utils::color_for_layer(secondary_color, layer))
{
// Settings
ISettings* settings = m_context->getSettings();
m_tiled_mode = settings->getTiledMode();
switch (tool->getFill(m_button)) {
case tools::FillNone:
m_filled = false;
break;
case tools::FillAlways:
m_filled = true;
break;
case tools::FillOptional:
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())->getCel(sprite->getCurrentFrame());
if (m_cel)
m_cel_image = sprite->getStock()->getImage(m_cel->getImage());
}
if (m_cel == NULL) {
// create the image
m_cel_image = image_new(sprite->getImgType(), sprite->getWidth(), sprite->getHeight());
image_clear(m_cel_image,
m_cel_image->mask_color);
// create the cel
m_cel = new Cel(sprite->getCurrentFrame(), 0);
static_cast<LayerImage*>(sprite->getCurrentLayer())->addCel(m_cel);
m_cel_created = true;
}
m_old_cel_x = m_cel->getX();
m_old_cel_y = m_cel->getY();
// region to draw
int x1, y1, x2, y2;
// non-tiled
if (m_tiled_mode == TILED_NONE) {
x1 = MIN(m_cel->getX(), 0);
y1 = MIN(m_cel->getY(), 0);
x2 = MAX(m_cel->getX()+m_cel_image->w, m_sprite->getWidth());
y2 = MAX(m_cel->getY()+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->getX(),
y1-m_cel->getY(), x2-x1, y2-y1,
m_cel_image->mask_color);
m_dst_image = image_new_copy(m_src_image);
m_useMask = m_document->isMaskVisible();
// Selection ink
if (getInk()->isSelection() && !m_document->isMaskVisible()) {
Mask emptyMask;
m_document->setMask(&emptyMask);
}
m_mask = m_document->getMask();
m_maskOrigin = (!m_mask->is_empty() ? gfx::Point(m_mask->x-x1, m_mask->y-y1):
gfx::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->setPosition(x1, y1);
m_offset.x = -x1;
m_offset.y = -y1;
// Set undo label for any kind of undo used in the whole loop
if (m_document->getUndoHistory()->isEnabled()) {
m_document->getUndoHistory()->setLabel(m_tool->getText().c_str());
if (getInk()->isSelection() ||
getInk()->isEyedropper() ||
getInk()->isScrollMovement()) {
m_document->getUndoHistory()->setModification(undo::DoesntModifyDocument);
}
else
m_document->getUndoHistory()->setModification(undo::ModifyDocument);
}
}
~ToolLoopImpl()
{
if (!m_canceled) {
undo::UndoHistory* undo = m_document->getUndoHistory();
// 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->getX() == m_old_cel_x &&
m_cel->getY() == 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->setImage(m_sprite->getStock()->addImage(m_cel_image));
// Is the undo enabled?.
if (undo->isEnabled()) {
// We can temporary remove the cel.
static_cast<LayerImage*>(m_sprite->getCurrentLayer())->removeCel(m_cel);
// We create the undo information (for the new cel_image
// in the stock and the new cel in the layer)...
undo->pushUndoer(new undoers::OpenGroup());
undo->pushUndoer(new undoers::AddImage(undo->getObjects(),
m_sprite->getStock(), m_cel->getImage()));
undo->pushUndoer(new undoers::AddCel(undo->getObjects(),
m_sprite->getCurrentLayer(), m_cel));
undo->pushUndoer(new undoers::CloseGroup());
// And finally we add the cel again in the layer.
static_cast<LayerImage*>(m_sprite->getCurrentLayer())->addCel(m_cel);
}
}
else {
// Undo the dirty region.
if (undo->isEnabled()) {
Dirty* dirty = new Dirty(m_cel_image, m_dst_image);
// TODO error handling
dirty->saveImagePixels(m_cel_image);
if (dirty != NULL)
undo->pushUndoer(new undoers::DirtyArea(undo->getObjects(),
m_cel_image, dirty));
delete 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->isEnabled()) {
undo->pushUndoer(new undoers::OpenGroup());
if (m_cel->getX() != m_old_cel_x ||
m_cel->getY() != m_old_cel_y) {
int x = m_cel->getX();
int y = m_cel->getY();
m_cel->setPosition(m_old_cel_x, m_old_cel_y);
undo->pushUndoer(new undoers::SetCelPosition(undo->getObjects(), m_cel));
m_cel->setPosition(x, y);
}
undo->pushUndoer(new undoers::ReplaceImage(undo->getObjects(),
m_sprite->getStock(), m_cel->getImage()));
undo->pushUndoer(new undoers::CloseGroup());
}
// Replace the image in the stock.
m_sprite->getStock()->replaceImage(m_cel->getImage(), 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_document->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->setPosition(m_old_cel_x, m_old_cel_y);
if (m_cel_created) {
static_cast<LayerImage*>(m_layer)->removeCel(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; }
tools::Tool* getTool() { return m_tool; }
Pen* getPen() { return m_pen; }
Document* getDocument() { return m_document; }
Sprite* getSprite() { return m_sprite; }
Layer* getLayer() { return m_layer; }
Image* getSrcImage() { return m_src_image; }
Image* getDstImage() { return m_dst_image; }
bool useMask() { return m_useMask; }
Mask* getMask() { return m_mask; }
gfx::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; }
gfx::Point getOffset() { return m_offset; }
void setSpeed(const gfx::Point& speed) { m_speed = speed; }
gfx::Point getSpeed() { return m_speed; }
tools::Ink* getInk() { return m_tool->getInk(m_button); }
tools::Controller* getController() { return m_tool->getController(m_button); }
tools::PointShape* getPointShape() { return m_tool->getPointShape(m_button); }
tools::Intertwine* getIntertwine() { return m_tool->getIntertwine(m_button); }
tools::TracePolicy getTracePolicy() { return m_tool->getTracePolicy(m_button); }
void cancel() { m_canceled = true; }
bool isCanceled() { return m_canceled; }
gfx::Point screenToSprite(const gfx::Point& screenPoint)
{
gfx::Point spritePoint;
m_editor->screenToEditor(screenPoint.x, screenPoint.y,
&spritePoint.x, &spritePoint.y);
return spritePoint;
}
void updateArea(const gfx::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);
}
};
tools::ToolLoop* create_tool_loop(Editor* editor, Context* context, Message* msg)
{
tools::Tool* current_tool = context->getSettings()->getCurrentTool();
if (!current_tool)
return NULL;
Sprite* sprite = editor->getSprite();
Layer* layer = sprite->getCurrentLayer();
if (!layer) {
Alert::show(PACKAGE "<<The current sprite does not have any layer.||&Close");
return NULL;
}
// If the active layer is not visible.
if (!layer->is_readable()) {
Alert::show(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()) {
Alert::show(PACKAGE
"<<The current layer is locked,"
"<<unlock it and try again"
"||&Close");
return NULL;
}
// Get fg/bg colors
ColorBar* colorbar = app_get_colorbar();
Color fg = colorbar->getFgColor();
Color bg = colorbar->getBgColor();
if (!fg.isValid() || !bg.isValid()) {
Alert::show(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
return
new ToolLoopImpl(editor,
context,
current_tool,
editor->getDocument(),
sprite, layer,
msg->mouse.left ? 0: 1,
msg->mouse.left ? fg: bg,
msg->mouse.left ? bg: fg);
}

View File

@ -0,0 +1,32 @@
/* ASE - Allegro Sprite Editor
* Copyright (C) 2001-2011 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
*/
#ifndef WIDGETS_EDITOR_TOOL_LOOP_IMPL_H_INCLUDED
#define WIDGETS_EDITOR_TOOL_LOOP_IMPL_H_INCLUDED
namespace tools {
class ToolLoop;
}
class Context;
class Editor;
union Message;
tools::ToolLoop* create_tool_loop(Editor* editor, Context* context, Message* msg);
#endif

View File

@ -164,6 +164,16 @@ StatusBar::~StatusBar()
delete m_tipwindow; // widget
}
void StatusBar::addListener(StatusBarListener* listener)
{
m_listeners.addListener(listener);
}
void StatusBar::removeListener(StatusBarListener* listener)
{
m_listeners.removeListener(listener);
}
void StatusBar::onCurrentToolChange()
{
if (isVisible()) {
@ -177,8 +187,8 @@ void StatusBar::onCurrentToolChange()
void StatusBar::onTransparentColorChange()
{
if (current_editor)
current_editor->setMaskColorForPixelsMovement(getTransparentColor());
m_listeners.notify<const Color&>(&StatusBarListener::onChangeTransparentColor,
getTransparentColor());
}
void StatusBar::clearText()

View File

@ -25,6 +25,7 @@
#include "base/compiler_specific.h"
#include "gui/base.h"
#include "gui/widget.h"
#include "listeners.h"
class Box;
class Button;
@ -51,12 +52,25 @@ private:
float m_pos;
};
class StatusBarListener
{
public:
virtual ~StatusBarListener() { }
virtual void dispose() = 0;
virtual void onChangeTransparentColor(const Color& color) = 0;
};
typedef Listeners<StatusBarListener> StatusBarListeners;
class StatusBar : public Widget
{
public:
StatusBar();
~StatusBar();
void addListener(StatusBarListener* listener);
void removeListener(StatusBarListener* listener);
void clearText();
bool setStatusText(int msecs, const char *format, ...);
@ -116,6 +130,8 @@ private:
Frame* m_tipwindow;
int m_hot_layer;
StatusBarListeners m_listeners;
};
#endif