Improve "Canvas Size" command to show rulers to change the canvas size.

+ Added EditorDecorator and EditorState::getDecorator().
+ Added EditorPreRender and EditorPostRender classes.
+ Added Image::rectblend and image_rectblend functions to draw
  transparent rectangles.
+ Added SelectTileState and Ruler classes.
This commit is contained in:
David Capello 2011-04-21 17:39:43 -03:00
parent 6293161028
commit 210e33f76a
12 changed files with 600 additions and 25 deletions

View File

@ -1,8 +1,7 @@
<!-- ASE - Allegro Sprite Editor -->
<!-- Copyright (C) 2001-2011 by David Capello -->
<jinete>
<window text="Canvas Size [work-in-progress]" name="canvas_size">
<box vertical="true">
<box vertical="true" name="main_box">
<box horizontal="true">
<box vertical="true" homogeneous="true">
<label text="Left:" />
@ -30,5 +29,4 @@
</box>
</box>
</box>
</window>
</jinete>

View File

@ -289,6 +289,7 @@ add_library(aseprite-library
widgets/editor/moving_pixels_state.cpp
widgets/editor/pixels_movement.cpp
widgets/editor/scrolling_state.cpp
widgets/editor/select_tile_state.cpp
widgets/editor/standby_state.cpp
widgets/editor/tool_loop_impl.cpp
widgets/fileview.cpp

View File

@ -19,18 +19,97 @@
#include "config.h"
#include "app/color_utils.h"
#include "base/unique_ptr.h"
#include "commands/command.h"
#include "document_wrappers.h"
#include "gui/gui.h"
#include "modules/editors.h"
#include "modules/gui.h"
#include "raster/image.h"
#include "raster/mask.h"
#include "raster/sprite.h"
#include "undo_transaction.h"
#include "widgets/color_bar.h"
#include "widgets/editor/editor.h"
#include "widgets/editor/select_tile_state.h"
#include <allegro/unicode.h>
// Frame used to show canvas parameters.
class CanvasSizeFrame : public Frame
, public SelectTileDelegate
{
public:
CanvasSizeFrame(int left, int top, int right, int bottom)
: Frame(false, "Canvas Size")
, m_editor(current_editor)
, m_rect(-left, -top,
current_editor->getSprite()->getWidth() + right,
current_editor->getSprite()->getHeight() + bottom)
{
m_mainBox = load_widget("canvas_size.xml", "main_box");
get_widgets(m_mainBox,
"left", &m_left,
"top", &m_top,
"right", &m_right,
"bottom", &m_bottom,
"ok", &m_ok, NULL);
addChild(m_mainBox);
m_left->setTextf("%d", left);
m_right->setTextf("%d", right);
m_top->setTextf("%d", top);
m_bottom->setTextf("%d", bottom);
m_editor->setDefaultState(EditorStatePtr(new SelectTileState(this, m_rect)));
}
~CanvasSizeFrame()
{
m_editor->setDefaultState(EditorStatePtr(new StandbyState));
}
bool pressedOk() { return get_killer() == m_ok; }
int getLeft() const { return m_left->getTextInt(); }
int getRight() const { return m_right->getTextInt(); }
int getTop() const { return m_top->getTextInt(); }
int getBottom() const { return m_bottom->getTextInt(); }
// SelectTileDelegate impleentation
virtual void onChangeRectangle(const gfx::Rect& rect) OVERRIDE
{
m_rect = rect;
m_left->setTextf("%d", -m_rect.x);
m_top->setTextf("%d", -m_rect.y);
m_right->setTextf("%d", (m_rect.x + m_rect.w) - current_editor->getSprite()->getWidth());
m_bottom->setTextf("%d", (m_rect.y + m_rect.h) - current_editor->getSprite()->getHeight());
}
protected:
virtual void onBroadcastMouseMessage(WidgetsList& targets) OVERRIDE
{
Frame::onBroadcastMouseMessage(targets);
// Add the editor as receptor of mouse events too.
targets.push_back(View::getView(m_editor));
}
private:
Editor* m_editor;
Widget* m_mainBox;
Widget* m_left;
Widget* m_right;
Widget* m_top;
Widget* m_bottom;
Widget* m_ok;
gfx::Rect m_rect;
};
//////////////////////////////////////////////////////////////////////
class CanvasSizeCommand : public Command
{
int m_left, m_right, m_top, m_bottom;
@ -64,40 +143,27 @@ void CanvasSizeCommand::onExecute(Context* context)
const Sprite* sprite(document->getSprite());
if (context->isUiAvailable()) {
JWidget left, top, right, bottom, ok;
// load the window widget
FramePtr window(load_widget("canvas_size.xml", "canvas_size"));
get_widgets(window,
"left", &left,
"top", &top,
"right", &right,
"bottom", &bottom,
"ok", &ok, NULL);
UniquePtr<CanvasSizeFrame> window(new CanvasSizeFrame(0, 0, 0, 0));
window->remap_window();
window->center_window();
left->setTextf("%d", m_left);
right->setTextf("%d", m_right);
top->setTextf("%d", m_top);
bottom->setTextf("%d", m_bottom);
load_window_pos(window, "CanvasSize");
window->setVisible(true);
window->open_window_fg();
save_window_pos(window, "CanvasSize");
if (window->get_killer() != ok)
if (!window->pressedOk())
return;
m_left = left->getTextInt();
m_right = right->getTextInt();
m_top = top->getTextInt();
m_bottom = bottom->getTextInt();
m_left = window->getLeft();
m_right = window->getRight();
m_top = window->getTop();
m_bottom = window->getBottom();
}
// resize canvas
// Resize canvas
int x1 = -m_left;
int y1 = -m_top;

View File

@ -296,6 +296,33 @@ void image_rectfill(Image* image, int x1, int y1, int x2, int y2, int color)
image->rectfill(x1, y1, x2, y2, color);
}
void image_rectblend(Image* image, int x1, int y1, int x2, int y2, int color, int opacity)
{
int t;
if (x1 > x2) {
t = x1;
x1 = x2;
x2 = t;
}
if (y1 > y2) {
t = y1;
y1 = y2;
y2 = t;
}
if ((x2 < 0) || (x1 >= image->w) || (y2 < 0) || (y1 >= image->h))
return;
if (x1 < 0) x1 = 0;
if (y1 < 0) y1 = 0;
if (x2 >= image->w) x2 = image->w-1;
if (y2 >= image->h) y2 = image->h-1;
image->rectblend(x1, y1, x2, y2, color, opacity);
}
typedef struct Data
{
Image* image;

View File

@ -64,6 +64,7 @@ public:
virtual void merge(const Image* src, int x, int y, int opacity, int blend_mode) = 0;
virtual void hline(int x1, int y, int x2, int color) = 0;
virtual void rectfill(int x1, int y1, int x2, int y2, int color) = 0;
virtual void rectblend(int x1, int y1, int x2, int y2, int color, int opacity) = 0;
virtual void to_allegro(BITMAP* bmp, int x, int y, const Palette* palette) const = 0;
};
@ -90,6 +91,7 @@ void image_hline(Image* image, int x1, int y, int x2, int color);
void image_vline(Image* image, int x, int y1, int y2, int color);
void image_rect(Image* image, int x1, int y1, int x2, int y2, int color);
void image_rectfill(Image* image, int x1, int y1, int x2, int y2, int color);
void image_rectblend(Image* image, int x1, int y1, int x2, int y2, int color, int opacity);
void image_line(Image* image, int x1, int y1, int x2, int y2, int color);
void image_ellipse(Image* image, int x1, int y1, int x2, int y2, int color);
void image_ellipsefill(Image* image, int x1, int y1, int x2, int y2, int color);

View File

@ -19,6 +19,7 @@
#ifndef RASTER_IMAGE_IMPL_H_INCLUDED
#define RASTER_IMAGE_IMPL_H_INCLUDED
#include "raster/blend.h"
#include "raster/image.h"
#include "raster/palette.h"
@ -221,6 +222,11 @@ public:
}
}
virtual void rectblend(int x1, int y1, int x2, int y2, int color, int opacity)
{
rectfill(x1, y1, x2, y2, color);
}
virtual void to_allegro(BITMAP* bmp, int x, int y, const Palette* palette) const;
};
@ -228,6 +234,21 @@ public:
//////////////////////////////////////////////////////////////////////
// Specializations
template<>
void ImageImpl<RgbTraits>::rectblend(int x1, int y1, int x2, int y2, int color, int opacity)
{
address_t addr;
int x, y;
for (y=y1; y<=y2; ++y) {
addr = line_address(y)+x1;
for (x=x1; x<=x2; ++x) {
*addr = _rgba_blend_normal(*addr, color, opacity);
++addr;
}
}
}
template<>
void ImageImpl<IndexedTraits>::clear(int color)
{

View File

@ -42,6 +42,7 @@
#include "util/misc.h"
#include "util/render.h"
#include "widgets/color_bar.h"
#include "widgets/editor/editor_decorator.h"
#include "widgets/editor/standby_state.h"
#include "widgets/statebar.h"
@ -50,6 +51,68 @@
using namespace gfx;
class EditorPreRenderImpl : public EditorPreRender
{
public:
EditorPreRenderImpl(Editor* editor, Image* image, Point& offset, int zoom)
: m_editor(editor)
, m_image(image)
, m_offset(offset)
, m_zoom(zoom)
{
}
Editor* getEditor() OVERRIDE
{
return m_editor;
}
Image* getImage() OVERRIDE
{
return m_image;
}
void fillRect(const gfx::Rect& rect, uint32_t rgbaColor, int opacity) OVERRIDE
{
image_rectblend(m_image,
m_offset.x + (rect.x << m_zoom),
m_offset.y + (rect.y << m_zoom),
m_offset.x + ((rect.x+rect.w) << m_zoom) - 1,
m_offset.y + ((rect.y+rect.h) << m_zoom) - 1, rgbaColor, opacity);
}
private:
Editor* m_editor;
Image* m_image;
Point m_offset;
int m_zoom;
};
class EditorPostRenderImpl : public EditorPostRender
{
public:
EditorPostRenderImpl(Editor* editor)
: m_editor(editor)
{
}
Editor* getEditor()
{
return m_editor;
}
void drawLine(int x1, int y1, int x2, int y2, int screenColor)
{
int u1, v1, u2, v2;
m_editor->editorToScreen(x1, y1, &u1, &v1);
m_editor->editorToScreen(x2, y2, &u2, &v2);
line(ji_screen, u1, v1, u2, v2, screenColor);
}
private:
Editor* m_editor;
};
Editor::Editor()
: Widget(editor_type())
, m_defaultState(EditorStatePtr(new StandbyState()))
@ -328,6 +391,13 @@ void Editor::drawSprite(int x1, int y1, int x2, int y2)
m_zoom, true);
if (rendered) {
// Pre-render decorator.
if (EditorDecorator* decorator = m_defaultState->getDecorator()) {
EditorPreRenderImpl preRender(this, rendered,
Point(-source_x, -source_y), m_zoom);
decorator->preRenderDecorator(&preRender);
}
#ifdef DRAWSPRITE_DOUBLEBUFFERED
BITMAP *bmp = create_bitmap(width, height);
@ -362,6 +432,12 @@ void Editor::drawSprite(int x1, int y1, int x2, int y2)
if (settings->getGridVisible())
this->drawGrid(settings->getGridBounds(),
settings->getGridColor());
// Post-render decorator.
if (EditorDecorator* decorator = m_defaultState->getDecorator()) {
EditorPostRenderImpl postRender(this);
decorator->postRenderDecorator(&postRender);
}
}
void Editor::drawSpriteSafe(int x1, int y1, int x2, int y2)
@ -690,10 +766,10 @@ void Editor::removeListener(EditorListener* listener)
}
// Returns the visible area of the active sprite.
gfx::Rect Editor::getVisibleSpriteBounds()
Rect Editor::getVisibleSpriteBounds()
{
// Return an empty rectangle if there is not a active sprite.
if (!m_sprite) return gfx::Rect();
if (!m_sprite) return Rect();
View* view = View::getView(this);
Rect vp = view->getViewportBounds();

View File

@ -0,0 +1,56 @@
/* 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_DECORATOR_H_INCLUDED
#define WIDGETS_EDITOR_EDITOR_DECORATOR_H_INCLUDED
namespace gfx {
class Rect;
}
class Editor;
class EditorDecorator;
class Graphics;
class Image;
class EditorPreRender
{
public:
virtual ~EditorPreRender() { }
virtual Editor* getEditor() = 0;
virtual Image* getImage() = 0;
virtual void fillRect(const gfx::Rect& rect, uint32_t rgbaColor, int opacity) = 0;
};
class EditorPostRender
{
public:
virtual ~EditorPostRender() { }
virtual Editor* getEditor() = 0;
virtual void drawLine(int x1, int y1, int x2, int y2, int screenColor) = 0;
};
class EditorDecorator
{
public:
virtual ~EditorDecorator() { }
virtual void preRenderDecorator(EditorPreRender* render) = 0;
virtual void postRenderDecorator(EditorPostRender* render) = 0;
};
#endif // WIDGETS_EDITOR_EDITOR_DECORATOR_H_INCLUDED

View File

@ -20,6 +20,7 @@
#define WIDGETS_EDITOR_EDITOR_STATE_H_INCLUDED
class Editor;
class EditorDecorator;
union Message;
// Represents one state of the sprite's editor (Editor class). This
@ -76,6 +77,9 @@ public:
// drawing cursor.
virtual bool requirePenPreview() { return false; }
// Called after the sprite is painted.
virtual EditorDecorator* getDecorator() { return NULL; }
};
#endif // WIDGETS_EDITOR_EDITOR_STATE_H_INCLUDED

View File

@ -0,0 +1,61 @@
/* 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_RULER_H_INCLUDED
#define WIDGETS_EDITOR_RULER_H_INCLUDED
// A ruler inside the editor. It is used by SelectTileState to show
// rulers that can be dragged by the user.
class Ruler
{
public:
enum Orientation { Horizontal, Vertical };
Ruler()
: m_orientation(Horizontal)
, m_position(0)
{
}
Ruler(Orientation orientation, int position)
: m_orientation(orientation)
, m_position(position)
{
}
Orientation getOrientation() const
{
return m_orientation;
}
int getPosition() const
{
return m_position;
}
void setPosition(int position)
{
m_position = position;
}
private:
Orientation m_orientation;
int m_position;
};
#endif // WIDGETS_EDITOR_RULER_H_INCLUDED

View File

@ -0,0 +1,189 @@
/* 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/select_tile_state.h"
#include "gfx/rect.h"
#include "gui/message.h"
#include "gui/system.h"
#include "gui/view.h"
#include "raster/image.h"
#include "raster/sprite.h"
#include "widgets/editor/editor.h"
#include <allegro/color.h>
SelectTileState::SelectTileState(SelectTileDelegate* delegate, const gfx::Rect& rc)
: m_delegate(delegate)
, m_rulers(4)
, m_movingRuler(-1)
{
m_rulers[H1] = Ruler(Ruler::Horizontal, rc.y);
m_rulers[H2] = Ruler(Ruler::Horizontal, rc.y+rc.h);
m_rulers[V1] = Ruler(Ruler::Vertical, rc.x);
m_rulers[V2] = Ruler(Ruler::Vertical, rc.x+rc.w);
}
bool SelectTileState::onMouseDown(Editor* editor, Message* msg)
{
if (msg->mouse.left || msg->mouse.right) {
m_movingRuler = -1;
for (int i=0; i<(int)m_rulers.size(); ++i) {
if (touchRuler(editor, m_rulers[i], msg->mouse.x, msg->mouse.y)) {
m_movingRuler = i;
break;
}
}
editor->captureMouse();
return true;
}
return StandbyState::onMouseDown(editor, msg);
}
bool SelectTileState::onMouseUp(Editor* editor, Message* msg)
{
m_movingRuler = -1;
return StandbyState::onMouseUp(editor, msg);
}
bool SelectTileState::onMouseMove(Editor* editor, Message* msg)
{
if (m_movingRuler >= 0) {
int u, v;
editor->screenToEditor(msg->mouse.x, msg->mouse.y, &u, &v);
switch (m_rulers[m_movingRuler].getOrientation()) {
case Ruler::Horizontal:
m_rulers[m_movingRuler].setPosition(v);
break;
case Ruler::Vertical:
m_rulers[m_movingRuler].setPosition(u);
break;
}
if (m_delegate)
m_delegate->onChangeRectangle(getBoxBounds());
editor->invalidate();
return true;
}
return StandbyState::onMouseMove(editor, msg);
}
bool SelectTileState::onSetCursor(Editor* editor)
{
int x = jmouse_x(0);
int y = jmouse_y(0);
for (Rulers::iterator it = m_rulers.begin(), end = m_rulers.end(); it != end; ++it) {
if (touchRuler(editor, *it, x, y)) {
switch (it->getOrientation()) {
case Ruler::Horizontal:
jmouse_set_cursor(JI_CURSOR_SIZE_T);
return true;
case Ruler::Vertical:
jmouse_set_cursor(JI_CURSOR_SIZE_L);
return true;
}
}
}
return false;
}
EditorDecorator* SelectTileState::getDecorator()
{
return this;
}
void SelectTileState::preRenderDecorator(EditorPreRender* render)
{
gfx::Rect rc = getBoxBounds();
Image* image = render->getImage();
Sprite* sprite = render->getEditor()->getSprite();
int sprite_w = sprite->getWidth();
int sprite_h = sprite->getHeight();
// Top band
if (rc.y > 0)
render->fillRect(gfx::Rect(0, 0, sprite_w, rc.y), _rgba(0, 0, 0, 255), 128);
// Bottom band
if (rc.y+rc.h < sprite_h)
render->fillRect(gfx::Rect(0, rc.y+rc.h, sprite_w, sprite_h-(rc.y+rc.h)), _rgba(0, 0, 0, 255), 128);
// Left band
if (rc.x > 0)
render->fillRect(gfx::Rect(0, rc.y, rc.x, rc.h), _rgba(0, 0, 0, 255), 128);
// Right band
if (rc.x+rc.w < sprite_w)
render->fillRect(gfx::Rect(rc.x+rc.w, rc.y, sprite_w-(rc.x+rc.w), rc.h), _rgba(0, 0, 0, 255), 128);
}
void SelectTileState::postRenderDecorator(EditorPostRender* render)
{
Editor* editor = render->getEditor();
int zoom = editor->getZoom();
gfx::Rect vp = View::getView(editor)->getViewportBounds();
int rulerColor = makecol(0, 0, 255);
vp.w += 1<<zoom;
vp.h += 1<<zoom;
editor->screenToEditor(vp, &vp);
for (Rulers::iterator it = m_rulers.begin(), end = m_rulers.end(); it != end; ++it) {
switch (it->getOrientation()) {
case Ruler::Horizontal:
render->drawLine(vp.x, it->getPosition(), vp.x+vp.w-1, it->getPosition(), rulerColor);
break;
case Ruler::Vertical:
render->drawLine(it->getPosition(), vp.y, it->getPosition(), vp.y+vp.h-1, rulerColor);
break;
}
}
}
gfx::Rect SelectTileState::getBoxBounds() const
{
int x1 = std::min(m_rulers[V1].getPosition(), m_rulers[V2].getPosition());
int y1 = std::min(m_rulers[H1].getPosition(), m_rulers[H2].getPosition());
int x2 = std::max(m_rulers[V1].getPosition(), m_rulers[V2].getPosition());
int y2 = std::max(m_rulers[H1].getPosition(), m_rulers[H2].getPosition());
return gfx::Rect(x1, y1, x2 - x1, y2 - y1);
}
bool SelectTileState::touchRuler(Editor* editor, Ruler& ruler, int x, int y)
{
int u, v;
editor->editorToScreen(ruler.getPosition(), ruler.getPosition(), &u, &v);
switch (ruler.getOrientation()) {
case Ruler::Horizontal: return (y >= v-2 && y <= v+2);
case Ruler::Vertical: return (x >= u-2 && x <= u+2);
}
return false;
}

View File

@ -0,0 +1,74 @@
/* 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_SELECT_TILE_STATE_H_INCLUDED
#define WIDGETS_EDITOR_SELECT_TILE_STATE_H_INCLUDED
#include "base/compiler_specific.h"
#include "widgets/editor/editor_decorator.h"
#include "widgets/editor/ruler.h"
#include "widgets/editor/standby_state.h"
#include <vector>
class SelectTileDelegate
{
public:
virtual ~SelectTileDelegate() { }
virtual void onChangeRectangle(const gfx::Rect& rect) = 0;
};
class SelectTileState : public StandbyState
, public EditorDecorator
{
enum { H1, H2, V1, V2 };
public:
SelectTileState(SelectTileDelegate* delegate, const gfx::Rect& rc);
// EditorState overrides
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 EditorDecorator* getDecorator() OVERRIDE;
// Returns false as it overrides default standby state behavior &
// look. This state uses normal arrow cursors.
virtual bool requirePenPreview() OVERRIDE { return false; }
// EditorDecorator overrides
virtual void preRenderDecorator(EditorPreRender* render) OVERRIDE;
virtual void postRenderDecorator(EditorPostRender* render) OVERRIDE;
private:
typedef std::vector<Ruler> Rulers;
// Returns the bounding box arranged by the rulers.
gfx::Rect getBoxBounds() const;
// Returns true if the position screen position (x, y) is touching
// the given ruler.
bool touchRuler(Editor* editor, Ruler& ruler, int x, int y);
SelectTileDelegate* m_delegate;
Rulers m_rulers;
int m_movingRuler;
};
#endif // WIDGETS_EDITOR_STANDBY_STATE_H_INCLUDED