Add basic implementation of slice tool (#721) to modify theme parts

It still need support to export to .aseprite-data as <slice> XML elements, and animation support.
This commit is contained in:
David Capello 2017-03-06 19:27:43 -03:00
parent fa1b15a3f5
commit 643cad5c97
64 changed files with 2577 additions and 61 deletions

View File

@ -445,8 +445,8 @@
<key tool="eyedropper" shortcut="I" />
<key tool="hand" shortcut="H" />
<key tool="move" shortcut="V" />
<key tool="slice" shortcut="C" />
<key tool="zoom" shortcut="Z" />
<!-- key tool="slice" shortcut="K" /-->
<key tool="paint_bucket" shortcut="G" />
@ -769,6 +769,7 @@
<item command="ShowLayerEdges" text="&amp;Layer Edges" />
<item command="ShowSelectionEdges" text="&amp;Selection Edges" />
<item command="ShowGrid" text="&amp;Grid" />
<item command="ShowSlices" text="Sl&amp;ices" />
<item command="ShowPixelGrid" text="&amp;Pixel Grid" />
<separator />
<item command="ShowBrushPreview" text="&amp;Brush Preview" />
@ -915,6 +916,11 @@
<item command="RemoveFrameTag" text="&amp;Remove Tag" />
</menu>
<menu id="slice_popup">
<item command="SliceProperties" text="Slice &amp;Properties..." />
<item command="RemoveSlice" text="&amp;Remove Slice" />
</menu>
<menu id="palette_popup">
<item command="PaletteEditor" text="&amp;Palette Editor">
<param name="switch" value="true" />
@ -1118,15 +1124,15 @@
ink="move"
controller="freehand"
/>
<!-- tool id="slice"
<tool id="slice"
text="Slice Tool"
fill="always"
fill="none"
ink="slice"
controller="two_points"
pointshape="pixel"
intertwine="as_rectangles"
tracepolicy="last"
/-->
/>
</group>
<group id="paint_bucket" text="Paint Bucket Tool">

View File

@ -349,6 +349,7 @@
<option id="grid" type="bool" default="false" migrate="grid.visible" />
<option id="pixel_grid" type="bool" default="false" migrate="pixel_grid.visible" />
<option id="brush_preview" type="bool" default="true" />
<option id="slices" type="bool" default="true" />
</section>
</document>

View File

@ -438,6 +438,17 @@ open_dmp_file = Open the following file to debug your compilation:
do_it_later = Do it later
delete_file = Delete file, I've already sent it
[slice_properties]
title = Slice Properties
name = Slice Name:
user_data_tooltip = User Data
bounds = Bounds:
center = 9-Slices
x = X
y = Y
width = Width
height = Height
[sprite_properties]
title = Sprite Properties
filename = File name:

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<sprite>
<slices theme="theme.xml" />
</sprite>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -282,12 +282,6 @@
<part id="target_frames" x="176" y="224" w="32" h="16" />
<part id="target_layers" x="208" y="224" w="32" h="16" />
<part id="target_frames_layers" x="240" y="224" w="32" h="16" />
<part id="brush_circle" x="144" y="144" w="7" h="8" />
<part id="brush_circle_selected" x="144" y="152" w="7" h="8" />
<part id="brush_square" x="160" y="144" w="7" h="8" />
<part id="brush_square_selected" x="160" y="152" w="7" h="8" />
<part id="brush_line" x="176" y="144" w="6" h="6" />
<part id="brush_line_selected" x="176" y="152" w="6" h="6" />
<part id="selection_replace" x="176" y="160" w="7" h="7" />
<part id="selection_replace_selected" x="176" y="168" w="7" h="7" />
<part id="selection_add" x="192" y="160" w="7" h="7" />
@ -412,6 +406,7 @@
<part id="icon_grid" x="224" y="264" w="8" h="8" />
<part id="icon_save" x="232" y="264" w="8" h="8" />
<part id="icon_save_small" x="240" y="264" w="8" h="8" />
<part id="icon_slice" x="248" y="264" w="8" h="8" />
</parts>
<stylesheet>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Aseprite -->
<!-- Copyright (C) 2017 by David Capello -->
<gui>
<window id="slice_properties" text="@.title">
<grid columns="2">
<label text="@.name" />
<hbox>
<entry maxsize="256" id="name" magnet="true" expansive="true" />
<button id="user_data" icon="icon_user_data" tooltip="@.user_data_tooltip" />
</hbox>
<separator horizontal="true" cell_hspan="2" />
<box />
<hbox homogeneous="true">
<label text="@.x" />
<label text="@.y" />
<label text="@.width" />
<label text="@.height" />
</hbox>
<label text="@.bounds" />
<hbox homogeneous="true">
<entry maxsize="8" id="bounds_x" />
<entry maxsize="8" id="bounds_y" />
<entry maxsize="8" id="bounds_w" />
<entry maxsize="8" id="bounds_h" />
</hbox>
<check text="@.center" id="center" />
<hbox homogeneous="true">
<entry maxsize="8" id="center_x" />
<entry maxsize="8" id="center_y" />
<entry maxsize="8" id="center_w" />
<entry maxsize="8" id="center_h" />
</hbox>
<separator horizontal="true" cell_hspan="2" />
<hbox cell_hspan="2">
<boxfiller />
<hbox homogeneous="true">
<button text="@general.ok" closewindow="true" id="ok" magnet="true" minwidth="60" />
<button text="@general.cancel" closewindow="true" />
</hbox>
</hbox>
</grid>
</window>
</gui>

View File

@ -291,15 +291,41 @@ belongs to that cel, etc.
DWORD Flags
1 = Has text
2 = Has color
+ If flags has bit 1:
+ If flags have bit 1:
STRING Text
+ If flags has bit 2:
+ If flags have bit 2:
BYTE Color Red (0-255)
BYTE Color Green (0-255)
BYTE Color Blue (0-255)
BYTE Color Alpha (0-255)
```
### Slices Chunk (0x2021)
Field | Details |
----------- | -------------------------------- |
DWORD | Number of slices
BYTE[8] | Reserved
For each slice... |
DWORD | Number of "slice keys"
DWORD | Flags
| 1 - It's a 9-patches slice
DWORD | Reserved
STRING | Name
For each slice key ... |
DWORD | Frame number (this slice is valid from
| this frame to the end of the animation)
SIGNED WORD | Slice X position
SIGNED WORD | Slice Y position
WORD | Slice Width (can be 0 if this slice hidden in the
| animation from the given frame)
WORD | Slice Height
If flags have bit 1 |
SIGNED WORD | Center X position (relative to slice bounds)
SIGNED WORD | Center Y position
WORD | Center Width
WORD | Center Height
### Notes
#### NOTE.1

View File

@ -162,6 +162,7 @@ add_library(app-lib
cmd/add_frame_tag.cpp
cmd/add_layer.cpp
cmd/add_palette.cpp
cmd/add_slice.cpp
cmd/background_from_layer.cpp
cmd/clear_cel.cpp
cmd/clear_image.cpp
@ -188,6 +189,7 @@ add_library(app-lib
cmd/remove_frame_tag.cpp
cmd/remove_layer.cpp
cmd/remove_palette.cpp
cmd/remove_slice.cpp
cmd/replace_image.cpp
cmd/reselect_mask.cpp
cmd/set_cel_bounds.cpp
@ -209,6 +211,8 @@ add_library(app-lib
cmd/set_palette.cpp
cmd/set_pixel_format.cpp
cmd/set_pixel_ratio.cpp
cmd/set_slice_key.cpp
cmd/set_slice_name.cpp
cmd/set_sprite_size.cpp
cmd/set_total_frames.cpp
cmd/set_transparent_color.cpp
@ -221,6 +225,7 @@ add_library(app-lib
cmd/with_frame_tag.cpp
cmd/with_image.cpp
cmd/with_layer.cpp
cmd/with_slice.cpp
cmd/with_sprite.cpp
cmd_sequence.cpp
cmd_transaction.cpp
@ -307,6 +312,7 @@ add_library(app-lib
commands/cmd_remove_frame.cpp
commands/cmd_remove_frame_tag.cpp
commands/cmd_remove_layer.cpp
commands/cmd_remove_slice.cpp
commands/cmd_repeat_last_export.cpp
commands/cmd_reselect_mask.cpp
commands/cmd_reverse_frames.cpp
@ -324,6 +330,7 @@ add_library(app-lib
commands/cmd_set_palette_entry_size.cpp
commands/cmd_set_same_ink.cpp
commands/cmd_show.cpp
commands/cmd_slice_properties.cpp
commands/cmd_sprite_properties.cpp
commands/cmd_sprite_size.cpp
commands/cmd_switch_colors.cpp
@ -433,6 +440,7 @@ add_library(app-lib
ui/editor/editor_view.cpp
ui/editor/moving_cel_state.cpp
ui/editor/moving_pixels_state.cpp
ui/editor/moving_slice_state.cpp
ui/editor/moving_symmetry_state.cpp
ui/editor/navigate_state.cpp
ui/editor/pivot_helpers.cpp
@ -478,6 +486,7 @@ add_library(app-lib
ui/skin/skin_theme.cpp
ui/skin/style.cpp
ui/skin/style_sheet.cpp
ui/slice_window.cpp
ui/status_bar.cpp
ui/styled_button.cpp
ui/tabs.cpp

View File

@ -89,6 +89,7 @@ void AppMenus::reload()
m_celPopupMenu.reset(loadMenuById(handle, "cel_popup"));
m_celMovementPopupMenu.reset(loadMenuById(handle, "cel_movement_popup"));
m_frameTagPopupMenu.reset(loadMenuById(handle, "frame_tag_popup"));
m_slicePopupMenu.reset(loadMenuById(handle, "slice_popup"));
m_palettePopupMenu.reset(loadMenuById(handle, "palette_popup"));
m_inkPopupMenu.reset(loadMenuById(handle, "ink_popup"));
@ -284,6 +285,7 @@ void AppMenus::applyShortcutToMenuitemsWithCommand(Command* command, const Param
m_celPopupMenu,
m_celMovementPopupMenu,
m_frameTagPopupMenu,
m_slicePopupMenu,
m_palettePopupMenu,
m_inkPopupMenu
};

View File

@ -46,6 +46,7 @@ namespace app {
Menu* getCelPopupMenu() { return m_celPopupMenu; }
Menu* getCelMovementPopupMenu() { return m_celMovementPopupMenu; }
Menu* getFrameTagPopupMenu() { return m_frameTagPopupMenu; }
Menu* getSlicePopupMenu() { return m_slicePopupMenu; }
Menu* getPalettePopupMenu() { return m_palettePopupMenu; }
Menu* getInkPopupMenu() { return m_inkPopupMenu; }
@ -67,6 +68,7 @@ namespace app {
base::UniquePtr<Menu> m_celPopupMenu;
base::UniquePtr<Menu> m_celMovementPopupMenu;
base::UniquePtr<Menu> m_frameTagPopupMenu;
base::UniquePtr<Menu> m_slicePopupMenu;
base::UniquePtr<Menu> m_palettePopupMenu;
base::UniquePtr<Menu> m_inkPopupMenu;
obs::scoped_connection m_recentFilesConn;

64
src/app/cmd/add_slice.cpp Normal file
View File

@ -0,0 +1,64 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cmd/add_slice.h"
#include "doc/slice.h"
#include "doc/slice_io.h"
#include "doc/sprite.h"
namespace app {
namespace cmd {
using namespace doc;
AddSlice::AddSlice(Sprite* sprite, Slice* slice)
: WithSprite(sprite)
, WithSlice(slice)
, m_size(0)
{
}
void AddSlice::onExecute()
{
Sprite* sprite = this->sprite();
Slice* slice = this->slice();
sprite->slices().add(slice);
sprite->incrementVersion();
}
void AddSlice::onUndo()
{
Sprite* sprite = this->sprite();
Slice* slice = this->slice();
write_slice(m_stream, slice);
m_size = size_t(m_stream.tellp());
sprite->slices().remove(slice);
sprite->incrementVersion();
delete slice;
}
void AddSlice::onRedo()
{
Sprite* sprite = this->sprite();
Slice* slice = read_slice(m_stream);
sprite->slices().add(slice);
sprite->incrementVersion();
m_stream.str(std::string());
m_stream.clear();
m_size = 0;
}
} // namespace cmd
} // namespace app

43
src/app/cmd/add_slice.h Normal file
View File

@ -0,0 +1,43 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_ADD_SLICE_H_INCLUDED
#define APP_CMD_ADD_SLICE_H_INCLUDED
#pragma once
#include "app/cmd.h"
#include "app/cmd/with_slice.h"
#include "app/cmd/with_sprite.h"
#include <sstream>
namespace app {
namespace cmd {
using namespace doc;
class AddSlice : public Cmd
, public WithSprite
, public WithSlice {
public:
AddSlice(Sprite* sprite, Slice* slice);
protected:
void onExecute() override;
void onUndo() override;
void onRedo() override;
size_t onMemSize() const override {
return sizeof(*this) + m_size;
}
private:
size_t m_size;
std::stringstream m_stream;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -0,0 +1,39 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cmd/remove_slice.h"
namespace app {
namespace cmd {
using namespace doc;
RemoveSlice::RemoveSlice(Sprite* sprite, Slice* slice)
: AddSlice(sprite, slice)
{
}
void RemoveSlice::onExecute()
{
AddSlice::onUndo();
}
void RemoveSlice::onUndo()
{
AddSlice::onRedo();
}
void RemoveSlice::onRedo()
{
AddSlice::onUndo();
}
} // namespace cmd
} // namespace app

View File

@ -0,0 +1,30 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_REMOVE_SLICE_H_INCLUDED
#define APP_CMD_REMOVE_SLICE_H_INCLUDED
#pragma once
#include "app/cmd/add_slice.h"
namespace app {
namespace cmd {
using namespace doc;
class RemoveSlice : public AddSlice {
public:
RemoveSlice(Sprite* sprite, Slice* slice);
protected:
void onExecute() override;
void onUndo() override;
void onRedo() override;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -0,0 +1,55 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cmd/set_slice_key.h"
#include "doc/slice.h"
#include "doc/slices.h"
#include "doc/sprite.h"
namespace app {
namespace cmd {
SetSliceKey::SetSliceKey(Slice* slice,
const doc::frame_t frame,
const doc::SliceKey& sliceKey)
: WithSlice(slice)
, m_frame(frame)
, m_newSliceKey(sliceKey)
{
auto it = slice->getIteratorByFrame(frame);
if (it != slice->end() && it->frame() == frame)
m_oldSliceKey = *it->value();
}
void SetSliceKey::onExecute()
{
if (!m_newSliceKey.isEmpty())
slice()->insert(m_frame, m_newSliceKey);
else
slice()->remove(m_frame);
slice()->incrementVersion();
slice()->owner()->sprite()->incrementVersion();
}
void SetSliceKey::onUndo()
{
if (!m_oldSliceKey.isEmpty())
slice()->insert(m_frame, m_oldSliceKey);
else
slice()->remove(m_frame);
slice()->incrementVersion();
slice()->owner()->sprite()->incrementVersion();
}
} // namespace cmd
} // namespace app

View File

@ -0,0 +1,43 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_SET_SLICE_KEY_H_INCLUDED
#define APP_CMD_SET_SLICE_KEY_H_INCLUDED
#pragma once
#include "app/cmd.h"
#include "app/cmd/with_slice.h"
#include "doc/frame.h"
#include "doc/slice.h"
namespace app {
namespace cmd {
using namespace doc;
class SetSliceKey : public Cmd
, public WithSlice {
public:
SetSliceKey(Slice* slice,
const doc::frame_t frame,
const doc::SliceKey& sliceKey);
protected:
void onExecute() override;
void onUndo() override;
size_t onMemSize() const override {
return sizeof(*this);
}
private:
doc::frame_t m_frame;
doc::SliceKey m_oldSliceKey;
doc::SliceKey m_newSliceKey;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -0,0 +1,41 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cmd/set_slice_name.h"
#include "doc/document.h"
#include "doc/document_event.h"
#include "doc/slice.h"
#include "doc/sprite.h"
namespace app {
namespace cmd {
SetSliceName::SetSliceName(Slice* slice, const std::string& name)
: WithSlice(slice)
, m_oldName(slice->name())
, m_newName(name)
{
}
void SetSliceName::onExecute()
{
slice()->setName(m_newName);
slice()->incrementVersion();
}
void SetSliceName::onUndo()
{
slice()->setName(m_oldName);
slice()->incrementVersion();
}
} // namespace cmd
} // namespace app

View File

@ -0,0 +1,40 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_SET_SLICE_NAME_H_INCLUDED
#define APP_CMD_SET_SLICE_NAME_H_INCLUDED
#pragma once
#include "app/cmd.h"
#include "app/cmd/with_slice.h"
#include <string>
namespace app {
namespace cmd {
using namespace doc;
class SetSliceName : public Cmd
, public WithSlice {
public:
SetSliceName(Slice* slice, const std::string& name);
protected:
void onExecute() override;
void onUndo() override;
size_t onMemSize() const override {
return sizeof(*this);
}
private:
std::string m_oldName;
std::string m_newName;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -0,0 +1,31 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cmd/with_slice.h"
#include "doc/slice.h"
namespace app {
namespace cmd {
using namespace doc;
WithSlice::WithSlice(Slice* slice)
: m_sliceId(slice->id())
{
}
Slice* WithSlice::slice()
{
return get<Slice>(m_sliceId);
}
} // namespace cmd
} // namespace app

33
src/app/cmd/with_slice.h Normal file
View File

@ -0,0 +1,33 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_WITH_SLICE_H_INCLUDED
#define APP_CMD_WITH_SLICE_H_INCLUDED
#pragma once
#include "doc/object_id.h"
namespace doc {
class Slice;
}
namespace app {
namespace cmd {
using namespace doc;
class WithSlice {
public:
WithSlice(Slice* slice);
Slice* slice();
private:
ObjectId m_sliceId;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -124,7 +124,9 @@ void OpenFileCommand::onExecute(Context* context)
if (m_filename.empty())
return;
int flags = (m_repeatCheckbox ? FILE_LOAD_SEQUENCE_ASK_CHECKBOX: 0);
int flags =
FILE_LOAD_DATA_FILE |
(m_repeatCheckbox ? FILE_LOAD_SEQUENCE_ASK_CHECKBOX: 0);
switch (m_seqDecision) {
case SequenceDecision::Ask:

View File

@ -0,0 +1,114 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/app.h"
#include "app/cmd/remove_slice.h"
#include "app/cmd/set_slice_key.h"
#include "app/commands/command.h"
#include "app/context_access.h"
#include "app/document_api.h"
#include "app/modules/gui.h"
#include "app/ui/status_bar.h"
#include "app/transaction.h"
#include "base/convert_to.h"
#include "doc/slice.h"
#include "doc/sprite.h"
#include "ui/alert.h"
#include "ui/widget.h"
namespace app {
class RemoveSliceCommand : public Command {
public:
RemoveSliceCommand();
Command* clone() const override { return new RemoveSliceCommand(*this); }
protected:
void onLoadParams(const Params& params) override;
bool onEnabled(Context* context) override;
void onExecute(Context* context) override;
private:
std::string m_sliceName;
ObjectId m_sliceId;
};
RemoveSliceCommand::RemoveSliceCommand()
: Command("RemoveSlice",
"Remove Slice",
CmdRecordableFlag)
{
}
void RemoveSliceCommand::onLoadParams(const Params& params)
{
m_sliceName = params.get("name");
std::string id = params.get("id");
if (!id.empty())
m_sliceId = ObjectId(base::convert_to<doc::ObjectId>(id));
else
m_sliceId = NullId;
}
bool RemoveSliceCommand::onEnabled(Context* context)
{
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::HasActiveSprite |
ContextFlags::HasActiveLayer);
}
void RemoveSliceCommand::onExecute(Context* context)
{
const ContextReader reader(context);
const Sprite* sprite = reader.sprite();
frame_t frame = reader.frame();
const Slice* foundSlice = nullptr;
if (!m_sliceName.empty())
foundSlice = sprite->slices().getByName(m_sliceName);
else if (m_sliceId != NullId)
foundSlice = sprite->slices().getById(m_sliceId);
if (!foundSlice)
return;
std::string sliceName = foundSlice->name();
{
ContextWriter writer(reader, 500);
Document* document(writer.document());
Sprite* sprite(writer.sprite());
Transaction transaction(writer.context(), "Remove Slice");
Slice* slice = const_cast<Slice*>(foundSlice);
if (slice->size() > 1) {
transaction.execute(new cmd::SetSliceKey(slice, frame, SliceKey()));
}
else {
transaction.execute(new cmd::RemoveSlice(sprite, slice));
}
transaction.commit();
document->notifyGeneralUpdate();
}
StatusBar::instance()->invalidate();
if (!sliceName.empty())
StatusBar::instance()->showTip(1000, "Slice '%s' removed", sliceName.c_str());
else
StatusBar::instance()->showTip(1000, "Slice removed");
}
Command* CommandFactory::createRemoveSliceCommand()
{
return new RemoveSliceCommand;
}
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -171,6 +171,28 @@ protected:
}
};
class ShowSlicesCommand : public Command {
public:
ShowSlicesCommand()
: Command("ShowSlices",
"Show Slices",
CmdUIOnlyFlag) {
}
Command* clone() const override { return new ShowSlicesCommand(*this); }
protected:
bool onChecked(Context* ctx) override {
DocumentPreferences& docPref = Preferences::instance().document(ctx->activeDocument());
return docPref.show.slices();
}
void onExecute(Context* ctx) override {
DocumentPreferences& docPref = Preferences::instance().document(ctx->activeDocument());
docPref.show.slices(!docPref.show.slices());
}
};
Command* CommandFactory::createShowExtrasCommand()
{
return new ShowExtrasCommand;
@ -201,4 +223,9 @@ Command* CommandFactory::createShowBrushPreviewCommand()
return new ShowBrushPreviewCommand;
}
Command* CommandFactory::createShowSlicesCommand()
{
return new ShowSlicesCommand;
}
} // namespace app

View File

@ -0,0 +1,119 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cmd/set_slice_key.h"
#include "app/cmd/set_slice_name.h"
#include "app/cmd/set_user_data.h"
#include "app/commands/command.h"
#include "app/context_access.h"
#include "app/transaction.h"
#include "app/ui/slice_window.h"
#include "base/convert_to.h"
#include "doc/slice.h"
#include "doc/sprite.h"
namespace app {
using namespace ui;
class SlicePropertiesCommand : public Command {
public:
SlicePropertiesCommand();
Command* clone() const override { return new SlicePropertiesCommand(*this); }
protected:
void onLoadParams(const Params& params) override;
bool onEnabled(Context* context) override;
void onExecute(Context* context) override;
private:
std::string m_sliceName;
ObjectId m_sliceId;
};
SlicePropertiesCommand::SlicePropertiesCommand()
: Command("SliceProperties",
"Slice Properties",
CmdUIOnlyFlag)
, m_sliceId(NullId)
{
}
void SlicePropertiesCommand::onLoadParams(const Params& params)
{
m_sliceName = params.get("name");
std::string id = params.get("id");
if (!id.empty())
m_sliceId = ObjectId(base::convert_to<doc::ObjectId>(id));
else
m_sliceId = NullId;
}
bool SlicePropertiesCommand::onEnabled(Context* context)
{
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable);
}
void SlicePropertiesCommand::onExecute(Context* context)
{
const ContextReader reader(context);
const Sprite* sprite = reader.sprite();
frame_t frame = reader.frame();
const Slice* foundSlice = nullptr;
if (!m_sliceName.empty())
foundSlice = sprite->slices().getByName(m_sliceName);
else if (m_sliceId != NullId)
foundSlice = sprite->slices().getById(m_sliceId);
if (!foundSlice)
return;
const doc::SliceKey* key = foundSlice->getByFrame(frame);
if (!key)
return;
SliceWindow window(sprite, foundSlice, frame);
if (!window.show())
return;
{
ContextWriter writer(reader, 500);
Transaction transaction(writer.context(), "Slice Properties");
Slice* slice = const_cast<Slice*>(foundSlice);
std::string name = window.nameValue();
if (slice->name() != name)
transaction.execute(new cmd::SetSliceName(slice, name));
if (slice->userData() != window.userDataValue())
transaction.execute(new cmd::SetUserData(slice, window.userDataValue()));
if (key->bounds() != window.boundsValue() ||
key->center() != window.centerValue()) {
SliceKey newKey = *key;
newKey.setBounds(window.boundsValue());
newKey.setCenter(window.centerValue());
transaction.execute(new cmd::SetSliceKey(slice, frame, newKey));
}
transaction.commit();
writer.document()->notifyGeneralUpdate();
}
}
Command* CommandFactory::createSlicePropertiesCommand()
{
return new SlicePropertiesCommand;
}
} // namespace app

View File

@ -98,6 +98,7 @@ FOR_EACH_COMMAND(Refresh)
FOR_EACH_COMMAND(RemoveFrame)
FOR_EACH_COMMAND(RemoveFrameTag)
FOR_EACH_COMMAND(RemoveLayer)
FOR_EACH_COMMAND(RemoveSlice)
FOR_EACH_COMMAND(RepeatLastExport)
FOR_EACH_COMMAND(ReplaceColor)
FOR_EACH_COMMAND(ReselectMask)
@ -125,6 +126,8 @@ FOR_EACH_COMMAND(ShowLayerEdges)
FOR_EACH_COMMAND(ShowOnionSkin)
FOR_EACH_COMMAND(ShowPixelGrid)
FOR_EACH_COMMAND(ShowSelectionEdges)
FOR_EACH_COMMAND(ShowSlices)
FOR_EACH_COMMAND(SliceProperties)
FOR_EACH_COMMAND(SnapToGrid)
FOR_EACH_COMMAND(SpriteProperties)
FOR_EACH_COMMAND(SpriteSize)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -31,6 +31,8 @@
#include "doc/layer.h"
#include "doc/palette.h"
#include "doc/palette_io.h"
#include "doc/slice.h"
#include "doc/slice_io.h"
#include "doc/sprite.h"
#include "doc/string_io.h"
#include "doc/subobjects_io.h"
@ -286,6 +288,17 @@ private:
}
}
// Read slices
int nslices = read32(s);
if (nslices >= 1 && nslices < 0xffffff) {
for (int i = 0; i < nslices; ++i) {
ObjectId sliceId = read32(s);
Slice* slice = loadObject<Slice*>("slice", sliceId, &Reader::readSlice);
if (slice)
spr->slices().add(slice);
}
}
return spr.release();
}
@ -350,6 +363,10 @@ private:
return read_frame_tag(s, false);
}
Slice* readSlice(std::ifstream& s) {
return read_slice(s, false);
}
// Fix issues that the restoration process could produce.
void fixUndetectedDocumentIssues(app::Document* doc) {
Sprite* spr = doc->sprite();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -30,6 +30,8 @@
#include "doc/layer.h"
#include "doc/palette.h"
#include "doc/palette_io.h"
#include "doc/slice.h"
#include "doc/slice_io.h"
#include "doc/sprite.h"
#include "doc/string_io.h"
@ -72,6 +74,10 @@ public:
if (!saveObject("frtag", frtag, &Writer::writeFrameTag))
return false;
for (Slice* slice : spr->slices())
if (!saveObject("slice", slice, &Writer::writeSlice))
return false;
// Get all layers (visible, hidden, subchildren, etc.)
LayerList layers = spr->allLayers();
@ -155,6 +161,11 @@ private:
for (FrameTag* frtag : spr->frameTags())
write32(s, frtag->id());
// IDs of all slices
write32(s, spr->slices().size());
for (const Slice* slice : spr->slices())
write32(s, slice->id());
return true;
}
@ -220,6 +231,11 @@ private:
return true;
}
bool writeSlice(std::ofstream& s, Slice* slice) {
write_slice(s, slice);
return true;
}
template<typename T>
bool saveObject(const char* prefix, T* obj, bool (Writer::*writeMember)(std::ofstream&, T*)) {
if (isCanceled())

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -39,6 +39,7 @@
#define ASE_FILE_CHUNK_FRAME_TAGS 0x2018
#define ASE_FILE_CHUNK_PALETTE 0x2019
#define ASE_FILE_CHUNK_USER_DATA 0x2020
#define ASE_FILE_CHUNK_SLICES 0x2021
#define ASE_FILE_LAYER_IMAGE 0
#define ASE_FILE_LAYER_GROUP 1
@ -54,6 +55,8 @@
#define ASE_CEL_EXTRA_FLAG_PRECISE_BOUNDS 1
#define ASE_SLICE_FLAG_HAS_CENTER_BOUNDS 1
namespace app {
using namespace base;
@ -139,6 +142,9 @@ static void ase_file_write_mask_chunk(FILE* f, ASE_FrameHeader* frame_header, Ma
static void ase_file_read_frame_tags_chunk(FILE* f, FrameTags* frameTags);
static void ase_file_write_frame_tags_chunk(FILE* f, ASE_FrameHeader* frame_header, const FrameTags* frameTags,
const frame_t fromFrame, const frame_t toFrame);
static void ase_file_read_slices_chunk(FILE* f, Slices* slices);
static void ase_file_write_slices_chunk(FILE* f, ASE_FrameHeader* frame_header, const Slices* slices,
const frame_t fromFrame, const frame_t toFrame);
static void ase_file_read_user_data_chunk(FILE* f, UserData* userData);
static void ase_file_write_user_data_chunk(FILE* f, ASE_FrameHeader* frame_header, const UserData* userData);
static bool ase_has_groups(LayerGroup* group);
@ -325,6 +331,10 @@ bool AseFormat::onLoad(FileOp* fop)
ase_file_read_frame_tags_chunk(f, &sprite->frameTags());
break;
case ASE_FILE_CHUNK_SLICES:
ase_file_read_slices_chunk(f, &sprite->slices());
break;
case ASE_FILE_CHUNK_USER_DATA: {
UserData userData;
ase_file_read_user_data_chunk(f, &userData);
@ -455,6 +465,13 @@ bool AseFormat::onSave(FileOp* fop)
ase_file_write_frame_tags_chunk(f, &frame_header, &sprite->frameTags(),
fop->roi().fromFrame(),
fop->roi().toFrame());
// Writer slice chunks
if (sprite->slices().size() > 0)
ase_file_write_slices_chunk(f, &frame_header,
&sprite->slices(),
fop->roi().fromFrame(),
fop->roi().toFrame());
}
// Write cel chunks
@ -1593,7 +1610,7 @@ static void ase_file_write_frame_tags_chunk(FILE* f, ASE_FrameHeader* frame_head
int tags = 0;
for (const FrameTag* tag : *frameTags) {
// Skip tags that are outside the given ROI
// Skip tags that are outside of the given ROI
if (tag->fromFrame() > toFrame ||
tag->toFrame() < fromFrame)
continue;
@ -1668,6 +1685,120 @@ static void ase_file_write_user_data_chunk(FILE* f, ASE_FrameHeader* frame_heade
}
}
static void ase_file_read_slices_chunk(FILE* f, Slices* slices)
{
size_t nslices = fgetl(f); // Number of slices
fgetl(f); // 8 bytes reserved
fgetl(f);
for (size_t i=0; i<nslices; ++i) {
size_t nkeys = fgetl(f); // Number of keys
int flags = fgetl(f); // Flags
fgetl(f); // 4 bytes reserved
std::string name = ase_file_read_string(f); // Name
base::UniquePtr<Slice> slice(new Slice);
slice->setName(name);
// For each key
for (size_t j=0; j<nkeys; ++j) {
frame_t frame = fgetl(f);
gfx::Rect bounds, center;
bounds.x = fgetl(f);
bounds.y = fgetl(f);
bounds.w = fgetl(f);
bounds.h = fgetl(f);
if (flags & ASE_SLICE_FLAG_HAS_CENTER_BOUNDS) {
center.x = fgetl(f);
center.y = fgetl(f);
center.w = fgetl(f);
center.h = fgetl(f);
}
slice->insert(frame, SliceKey(bounds, center));
}
slices->add(slice);
slice.release();
}
}
static void ase_file_write_slices_chunk(FILE* f, ASE_FrameHeader* frame_header,
const Slices* slices,
const frame_t fromFrame,
const frame_t toFrame)
{
ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_SLICES);
size_t nslices = 0;
for (Slice* slice : *slices) {
// Skip slices that are outside of the given ROI
if (slice->range(fromFrame, toFrame).empty())
continue;
++nslices;
}
fputl(nslices, f);
fputl(0, f); // 8 reserved bytes
fputl(0, f);
for (Slice* slice : *slices) {
// Skip slices that are outside of the given ROI
auto range = slice->range(fromFrame, toFrame);
if (range.empty())
continue;
int flags = 0;
for (auto key : range) {
if (key && !key->center().isEmpty()) {
flags |= ASE_SLICE_FLAG_HAS_CENTER_BOUNDS;
break;
}
}
fputl(range.countKeys(), f); // number of keys
fputl(flags, f); // flags
fputl(0, f); // 4 bytes reserved
ase_file_write_string(f, slice->name()); // slice name
frame_t frame = fromFrame;
const SliceKey* oldKey = nullptr;
for (auto key : range) {
if (frame == fromFrame || key != oldKey) {
fputl(frame, f);
fputl(key ? key->bounds().x: 0, f);
fputl(key ? key->bounds().y: 0, f);
fputl(key ? key->bounds().w: 0, f);
fputl(key ? key->bounds().h: 0, f);
if (flags & ASE_SLICE_FLAG_HAS_CENTER_BOUNDS) {
if (key && !key->center().isEmpty()) {
fputl(key->center().x, f);
fputl(key->center().y, f);
fputl(key->center().w, f);
fputl(key->center().h, f);
}
else {
fputl(0, f);
fputl(0, f);
fputl(key ? key->bounds().w: 0, f);
fputl(key ? key->bounds().h: 0, f);
}
}
oldKey = key;
}
++frame;
}
--nslices;
}
ASSERT(nslices == 0);
}
static bool ase_has_groups(LayerGroup* group)
{
for (Layer* child : group->layers()) {

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -21,12 +21,14 @@
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
#include "app/ui/status_bar.h"
#include "app/xml_document.h"
#include "base/fs.h"
#include "base/mutex.h"
#include "base/scoped_lock.h"
#include "base/shared_ptr.h"
#include "base/string.h"
#include "doc/doc.h"
#include "doc/slice.h" // TODO add this header file in doc.h
#include "docio/detect_format.h"
#include "render/quantization.h"
#include "render/render.h"
@ -41,6 +43,36 @@ namespace app {
using namespace base;
namespace {
void updateXmlPartFromSliceKey(const SliceKey* key, TiXmlElement* xmlPart)
{
xmlPart->SetAttribute("x", key->bounds().x);
xmlPart->SetAttribute("y", key->bounds().y);
if (key->center().isEmpty()) {
xmlPart->SetAttribute("w", key->bounds().w);
xmlPart->SetAttribute("h", key->bounds().h);
if (xmlPart->Attribute("w1")) xmlPart->RemoveAttribute("w1");
if (xmlPart->Attribute("w2")) xmlPart->RemoveAttribute("w2");
if (xmlPart->Attribute("w3")) xmlPart->RemoveAttribute("w3");
if (xmlPart->Attribute("h1")) xmlPart->RemoveAttribute("h1");
if (xmlPart->Attribute("h2")) xmlPart->RemoveAttribute("h2");
if (xmlPart->Attribute("h3")) xmlPart->RemoveAttribute("h3");
}
else {
xmlPart->SetAttribute("w1", key->center().x);
xmlPart->SetAttribute("w2", key->center().w);
xmlPart->SetAttribute("w3", key->bounds().w - key->center().x2());
xmlPart->SetAttribute("h1", key->center().y);
xmlPart->SetAttribute("h2", key->center().h);
xmlPart->SetAttribute("h3", key->bounds().h - key->center().y2());
if (xmlPart->Attribute("w")) xmlPart->RemoveAttribute("w");
if (xmlPart->Attribute("h")) xmlPart->RemoveAttribute("h");
}
}
} // anonymous namespace
std::string get_readable_extensions()
{
std::string buf;
@ -285,13 +317,21 @@ FileOp* FileOp::createLoadDocumentOperation(Context* context, const char* filena
}
}
}
else
else {
fop->m_filename = filename;
}
/* load just one frame */
// Load just one frame
if (flags & FILE_LOAD_ONE_FRAME)
fop->m_oneframe = true;
// Does data file exist?
if (flags & FILE_LOAD_DATA_FILE) {
std::string dataFilename = base::replace_extension(filename, "aseprite-data");
if (base::is_file(dataFilename))
fop->m_dataFilename = dataFilename;
}
done:;
return fop.release();
}
@ -520,6 +560,11 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context,
fop->m_document->setFormatOptions(opts);
}
// Does data file exist?
std::string dataFilename = base::replace_extension(filename, "aseprite-data");
if (base::is_file(dataFilename))
fop->m_dataFilename = dataFilename;
return fop.release();
}
@ -650,9 +695,17 @@ void FileOp::operate(IFileOpProgress* progress)
// Direct load from one file.
else {
// Call the "load" procedure.
if (!m_format->load(this))
if (!m_format->load(this)) {
setError("Error loading sprite from file \"%s\"\n",
m_filename.c_str());
}
}
// Load special data from .aseprite-data file
if (m_document &&
m_document->sprite() &&
!m_dataFilename.empty()) {
loadData();
}
}
// Save //////////////////////////////////////////////////////////////////////
@ -707,9 +760,18 @@ void FileOp::operate(IFileOpProgress* progress)
// Direct save to a file.
else {
// Call the "save" procedure.
if (!m_format->save(this))
if (!m_format->save(this)) {
setError("Error saving the sprite in the file \"%s\"\n",
m_filename.c_str());
}
}
// Save special data from .aseprite-data file
if (m_document &&
m_document->sprite() &&
!hasError() &&
!m_dataFilename.empty()) {
saveData();
}
#else
setError(
@ -896,7 +958,7 @@ Image* FileOp::sequenceImage(PixelFormat pixelFormat, int w, int h)
void FileOp::setError(const char *format, ...)
{
char buf_error[4096];
char buf_error[4096]; // TODO possible stack overflow
va_list ap;
va_start(ap, format);
vsnprintf(buf_error, sizeof(buf_error), format, ap);
@ -995,4 +1057,141 @@ void FileOp::prepareForSequence()
m_formatOptions.reset();
}
void FileOp::loadData()
{
try {
XmlDocumentRef xmlDoc = open_xml(m_dataFilename);
TiXmlHandle handle(xmlDoc.get());
TiXmlElement* xmlSlices = handle
.FirstChild("sprite")
.FirstChild("slices").ToElement();
// Update theme.xml file
if (xmlSlices &&
xmlSlices->Attribute("theme")) {
std::string themeFileName = xmlSlices->Attribute("theme");
// Open theme XML file
XmlDocumentRef xmlThemeDoc = open_xml(
base::join_path(base::get_file_path(m_dataFilename), themeFileName));
TiXmlHandle themeHandle(xmlThemeDoc.get());
for (TiXmlElement* xmlPart = themeHandle
.FirstChild("theme")
.FirstChild("parts")
.FirstChild("part").ToElement();
xmlPart;
xmlPart=xmlPart->NextSiblingElement()) {
const char* partId = xmlPart->Attribute("id");
if (!partId)
continue;
auto slice = new doc::Slice();
slice->setName(partId);
doc::SliceKey key;
int x = strtol(xmlPart->Attribute("x"), NULL, 10);
int y = strtol(xmlPart->Attribute("y"), NULL, 10);
if (xmlPart->Attribute("w1")) {
int w1 = xmlPart->Attribute("w1") ? strtol(xmlPart->Attribute("w1"), NULL, 10): 0;
int w2 = xmlPart->Attribute("w2") ? strtol(xmlPart->Attribute("w2"), NULL, 10): 0;
int w3 = xmlPart->Attribute("w3") ? strtol(xmlPart->Attribute("w3"), NULL, 10): 0;
int h1 = xmlPart->Attribute("h1") ? strtol(xmlPart->Attribute("h1"), NULL, 10): 0;
int h2 = xmlPart->Attribute("h2") ? strtol(xmlPart->Attribute("h2"), NULL, 10): 0;
int h3 = xmlPart->Attribute("h3") ? strtol(xmlPart->Attribute("h3"), NULL, 10): 0;
key.setBounds(gfx::Rect(x, y, w1+w2+w3, h1+h2+h3));
key.setCenter(gfx::Rect(w1, h1, w2, h2));
}
else if (xmlPart->Attribute("w")) {
int w = xmlPart->Attribute("w") ? strtol(xmlPart->Attribute("w"), NULL, 10): 0;
int h = xmlPart->Attribute("h") ? strtol(xmlPart->Attribute("h"), NULL, 10): 0;
key.setBounds(gfx::Rect(x, y, w, h));
}
slice->insert(0, key);
m_document->sprite()->slices().add(slice);
}
}
}
catch (const std::exception& ex) {
setError("Error loading data file: %s\n", ex.what());
}
}
void FileOp::saveData()
{
try {
XmlDocumentRef xmlDoc = open_xml(m_dataFilename);
TiXmlHandle handle(xmlDoc.get());
TiXmlElement* xmlSlices = handle
.FirstChild("sprite")
.FirstChild("slices").ToElement();
if (xmlSlices &&
xmlSlices->Attribute("theme")) {
// Open theme XML file
std::string themeFileName = base::join_path(
base::get_file_path(m_dataFilename), xmlSlices->Attribute("theme"));
XmlDocumentRef xmlThemeDoc = open_xml(themeFileName);
TiXmlHandle themeHandle(xmlThemeDoc.get());
TiXmlElement* xmlNext = nullptr;
std::set<std::string> existent;
TiXmlElement* xmlParts =
themeHandle
.FirstChild("theme")
.FirstChild("parts").ToElement();
for (TiXmlElement* xmlPart=(TiXmlElement*)xmlParts->FirstChild("part");
xmlPart;
xmlPart=xmlNext) {
xmlNext = xmlPart->NextSiblingElement();
const char* partId = xmlPart->Attribute("id");
if (!partId)
continue;
bool found = false;
for (auto slice : m_document->sprite()->slices()) {
const SliceKey* key;
if ((slice->name() == partId) &&
(key = slice->getByFrame(0))) {
existent.insert(slice->name());
updateXmlPartFromSliceKey(key, xmlPart);
found = true;
break;
}
}
// Delete this <part> element (as the slice was removed)
if (!found)
xmlPart->Parent()->RemoveChild(xmlPart);
}
for (auto slice : m_document->sprite()->slices()) {
const SliceKey* key;
if (existent.find(slice->name()) == existent.end() &&
(key = slice->getByFrame(0))) {
TiXmlElement xmlPart("part");
xmlPart.SetAttribute("id", slice->name().c_str());
updateXmlPartFromSliceKey(key, &xmlPart);
xmlParts->InsertAfterChild(xmlParts->LastChild(), xmlPart);
}
}
save_xml(xmlThemeDoc, themeFileName);
}
}
catch (const std::exception& ex) {
setError("Error loading data file: %s\n", ex.what());
}
}
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -25,6 +25,7 @@
#define FILE_LOAD_SEQUENCE_ASK_CHECKBOX 0x00000004
#define FILE_LOAD_SEQUENCE_YES 0x00000008
#define FILE_LOAD_ONE_FRAME 0x00000010
#define FILE_LOAD_DATA_FILE 0x00000020
namespace doc {
class Document;
@ -173,6 +174,7 @@ namespace app {
// releaseDocument() member function)
Document* m_document; // Loaded document, or document to be saved.
std::string m_filename; // File-name to load/save.
std::string m_dataFilename; // File-name for a special XML .aseprite-data where extra sprite data can be stored
FileOpROI m_roi;
// Shared fields between threads.
@ -206,6 +208,8 @@ namespace app {
} m_seq;
void prepareForSequence();
void loadData();
void saveData();
};
// Available extensions for each load/save operation.

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -13,6 +13,7 @@
#include "app/document_undo.h"
#include "app/tools/pick_ink.h"
#include "doc/mask.h"
#include "doc/slice.h"
#include "gfx/region.h"
namespace app {
@ -168,16 +169,42 @@ public:
class SliceInk : public Ink {
AlgoHLine m_proc;
bool m_createSlice;
gfx::Rect m_maxBounds;
public:
SliceInk() {
m_createSlice = false;
}
Ink* clone() override { return new SliceInk(*this); }
bool isSlice() const override { return true; }
bool needsCelCoordinates() const override { return false; }
void prepareInk(ToolLoop* loop) override { }
void prepareInk(ToolLoop* loop) override {
m_proc = get_ink_proc<XorInkProcessing>(loop->sprite()->pixelFormat());
}
void inkHline(int x1, int y, int x2, ToolLoop* loop) override {
// TODO show the selection-preview with a XOR color or something like that
draw_hline(loop->getDstImage(), x1, y, x2, loop->getPrimaryColor());
if (m_createSlice)
m_maxBounds |= gfx::Rect(x1, y, x2-x1+1, 1);
else
(*m_proc)(x1, y, x2, loop);
}
void setFinalStep(ToolLoop* loop, bool state) override {
m_createSlice = state;
if (state) {
m_maxBounds = gfx::Rect(0, 0, 0, 0);
}
else if (loop->getMouseButton() == ToolLoop::Left) {
Slice* slice = new Slice;
SliceKey key(m_maxBounds);
slice->insert(loop->getFrame(), key);
loop->addSlice(slice);
}
}
};
@ -345,7 +372,6 @@ public:
m_maxBounds |= gfx::Rect(x1, y, x2-x1+1, 1);
}
// TODO show the selection-preview with a XOR color or something like that
else {
(*m_proc)(x1, y, x2, loop);
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -26,6 +26,7 @@ namespace doc {
class Mask;
class Remap;
class RgbMap;
class Slice;
class Sprite;
}
@ -117,6 +118,9 @@ namespace app {
virtual Mask* getMask() = 0;
virtual void setMask(Mask* newMask) = 0;
// Adds a new slice (only for slice ink)
virtual void addSlice(doc::Slice* newSlice) = 0;
// Gets mask X,Y origin coordinates
virtual gfx::Point getMaskOrigin() = 0;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -109,7 +109,9 @@ bool ToolLoopManager::releaseButton(const Pointer& pointer)
bool res = m_toolLoop->getController()->releaseButton(m_stroke, spritePoint);
if (!res && (m_toolLoop->getInk()->isSelection() || m_toolLoop->getFilled())) {
if (!res && (m_toolLoop->getInk()->isSelection() ||
m_toolLoop->getInk()->isSlice() ||
m_toolLoop->getFilled())) {
m_toolLoop->getInk()->setFinalStep(m_toolLoop, true);
doLoopStep(true);
m_toolLoop->getInk()->setFinalStep(m_toolLoop, false);

View File

@ -53,9 +53,6 @@ bool AppMenuItem::onProcessMessage(Message* msg)
case kCloseMessage:
// disable the menu (the keyboard shortcuts are processed by "manager_msg_proc")
setEnabled(false);
if (!s_contextParams.empty())
s_contextParams.clear();
break;
default:

View File

@ -52,6 +52,7 @@
#include "doc/doc.h"
#include "doc/document_event.h"
#include "doc/mask_boundaries.h"
#include "doc/slice.h"
#include "she/surface.h"
#include "she/system.h"
#include "ui/ui.h"
@ -703,7 +704,7 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc)
g->fillRegion(theme->colors.editorFace(), outside);
}
// Grids
// Grids & slices
{
// Clipping
gfx::Rect cliprc = editorToScreen(rc).offset(-bounds().origin());
@ -743,6 +744,10 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc)
m_docPref.grid.color(), alpha);
}
}
// Draw slices
if (m_docPref.show.slices())
drawSlices(g);
}
}
@ -954,6 +959,51 @@ void Editor::drawGrid(Graphics* g, const gfx::Rect& spriteBounds, const Rect& gr
g->drawVLine(grid_color, c, y1, spriteBounds.h);
}
void Editor::drawSlices(ui::Graphics* g)
{
if ((m_flags & kShowSlices) == 0)
return;
if (!isVisible() || !m_document)
return;
for (auto slice : m_sprite->slices()) {
auto key = slice->getByFrame(m_frame);
if (!key)
continue;
doc::color_t docColor = slice->userData().color();
gfx::Color color = gfx::rgba(doc::rgba_getr(docColor),
doc::rgba_getg(docColor),
doc::rgba_getb(docColor),
doc::rgba_geta(docColor));
gfx::Rect out =
editorToScreen(key->bounds())
.offset(-bounds().origin());
if (!key->center().isEmpty()) {
gfx::Rect in =
editorToScreen(gfx::Rect(key->center()).offset(key->bounds().origin()))
.offset(-bounds().origin());
auto in_color = gfx::rgba(gfx::getr(color),
gfx::getg(color),
gfx::getb(color),
doc::rgba_geta(docColor)/4);
if (in.y > out.y && in.y < out.y2())
g->drawHLine(in_color, out.x, in.y, out.w);
if (in.y2() > out.y && in.y2() < out.y2())
g->drawHLine(in_color, out.x, in.y2(), out.w);
if (in.x > out.x && in.x < out.x2())
g->drawVLine(in_color, in.x, out.y, out.h);
if (in.x2() > out.x && in.x2() < out.x2())
g->drawVLine(in_color, in.x2(), out.y, out.h);
}
g->drawRect(color, out);
}
}
void Editor::flashCurrentLayer()
{
if (!Preferences::instance().experimental.flashLayer())
@ -1621,6 +1671,63 @@ bool Editor::isInsideSelection()
m_document->mask()->containsPoint(spritePos.x, spritePos.y);
}
EditorHit Editor::calcHit(const gfx::Point& mouseScreenPos)
{
tools::Ink* ink = getCurrentEditorInk();
if (ink) {
// Check if we can transform slices
if (ink->isSlice()) {
if (m_docPref.show.slices()) {
for (auto slice : m_sprite->slices()) {
auto key = slice->getByFrame(m_frame);
if (key) {
gfx::Rect bounds = editorToScreen(key->bounds());
gfx::Rect center = key->center();
if (bounds.contains(mouseScreenPos) &&
!bounds.shrink(5*guiscale()).contains(mouseScreenPos)) {
int border =
(mouseScreenPos.x <= bounds.x ? LEFT: 0) |
(mouseScreenPos.y <= bounds.y ? TOP: 0) |
(mouseScreenPos.x >= bounds.x2() ? RIGHT: 0) |
(mouseScreenPos.y >= bounds.y2() ? BOTTOM: 0);
EditorHit hit(EditorHit::SliceBounds);
hit.setBorder(border);
hit.setSlice(slice);
return hit;
}
else if (!center.isEmpty()) {
center = editorToScreen(
center.offset(key->bounds().origin()));
bool horz1 = gfx::Rect(bounds.x, center.y-2*guiscale(), bounds.w, 5*guiscale()).contains(mouseScreenPos);
bool horz2 = gfx::Rect(bounds.x, center.y2()-2*guiscale(), bounds.w, 5*guiscale()).contains(mouseScreenPos);
bool vert1 = gfx::Rect(center.x-2*guiscale(), bounds.y, 5*guiscale(), bounds.h).contains(mouseScreenPos);
bool vert2 = gfx::Rect(center.x2()-2*guiscale(), bounds.y, 5*guiscale(), bounds.h).contains(mouseScreenPos);
if (horz1 || horz2 || vert1 || vert2) {
int border =
(horz1 ? TOP: 0) |
(horz2 ? BOTTOM: 0) |
(vert1 ? LEFT: 0) |
(vert2 ? RIGHT: 0);
EditorHit hit(EditorHit::SliceCenter);
hit.setBorder(border);
hit.setSlice(slice);
return hit;
}
}
}
}
}
}
}
return EditorHit(EditorHit::None);
}
void Editor::setZoomAndCenterInMouse(const Zoom& zoom,
const gfx::Point& mousePos,
ZoomBehavior zoomBehavior)

View File

@ -16,6 +16,7 @@
#include "app/tools/tool_loop_modifiers.h"
#include "app/ui/color_source.h"
#include "app/ui/editor/brush_preview.h"
#include "app/ui/editor/editor_hit.h"
#include "app/ui/editor/editor_observers.h"
#include "app/ui/editor/editor_state.h"
#include "app/ui/editor/editor_states_history.h"
@ -74,13 +75,15 @@ namespace app {
kShowOutside = 8,
kShowDecorators = 16,
kShowSymmetryLine = 32,
kUseNonactiveLayersOpacityWhenEnabled = 64,
kShowSlices = 64,
kUseNonactiveLayersOpacityWhenEnabled = 128,
kDefaultEditorFlags = (kShowGrid |
kShowMask |
kShowOnionskin |
kShowOutside |
kShowDecorators |
kShowSymmetryLine |
kShowSlices |
kUseNonactiveLayersOpacityWhenEnabled)
};
@ -195,6 +198,10 @@ namespace app {
// Returns true if the cursor is inside the active mask/selection.
bool isInsideSelection();
// Returns the element that will be modified if the mouse is used
// in the given position.
EditorHit calcHit(const gfx::Point& mouseScreenPos);
void setZoomAndCenterInMouse(const render::Zoom& zoom,
const gfx::Point& mousePos, ZoomBehavior zoomBehavior);
@ -271,7 +278,8 @@ namespace app {
void drawMaskSafe();
void drawMask(ui::Graphics* g);
void drawGrid(ui::Graphics* g, const gfx::Rect& spriteBounds, const gfx::Rect& gridBounds,
const app::Color& color, int alpha);
const app::Color& color, int alpha);
void drawSlices(ui::Graphics* g);
void setCursor(const gfx::Point& mouseScreenPos);

View File

@ -0,0 +1,48 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UI_EDITOR_HIT_H_INCLUDED
#define APP_UI_EDITOR_HIT_H_INCLUDED
#pragma once
namespace doc {
class Slice;
}
namespace app {
// TODO Complete this class to calculate if the mouse hit any Editor
// element/decorator
class EditorHit {
public:
enum Type {
None,
SliceBounds,
SliceCenter,
};
EditorHit(Type type)
: m_type(type)
, m_border(0)
, m_slice(nullptr) {
}
Type type() const { return m_type; }
int border() const { return m_border; }
doc::Slice* slice() const { return m_slice; }
void setBorder(int border) { m_border = border; }
void setSlice(doc::Slice* slice) { m_slice = slice; }
private:
Type m_type;
int m_border;
doc::Slice* m_slice;
};
} // namespace app
#endif // APP_UI_EDITOR_HIT_H_INCLUDED

View File

@ -82,7 +82,6 @@ MovingCelState::MovingCelState(Editor* editor,
, m_celList(collect.celList())
, m_celOffset(0.0, 0.0)
, m_celScale(1.0, 1.0)
, m_canceled(false)
, m_hasReference(false)
, m_scaled(false)
, m_handle(handle)
@ -147,8 +146,7 @@ bool MovingCelState::onMouseUp(Editor* editor, MouseMessage* msg)
}
if (modified) {
// If the user didn't cancel the operation...
if (!m_canceled) {
{
ContextWriter writer(m_reader, 1000);
Transaction transaction(writer.context(), "Cel Movement", ModifyDocument);
DocumentApi api = document->getApi(transaction);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -61,7 +61,6 @@ namespace app {
gfx::PointF m_celOffset;
gfx::SizeF m_celMainSize;
gfx::SizeF m_celScale;
bool m_canceled;
bool m_maskVisible;
bool m_hasReference;
bool m_scaled;

View File

@ -0,0 +1,145 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/editor/moving_slice_state.h"
#include "app/cmd/set_slice_key.h"
#include "app/context_access.h"
#include "app/transaction.h"
#include "app/ui/editor/editor.h"
#include "app/ui/status_bar.h"
#include "app/ui_context.h"
#include "doc/slice.h"
#include "ui/message.h"
#include <cmath>
namespace app {
using namespace ui;
MovingSliceState::MovingSliceState(Editor* editor, MouseMessage* msg,
const EditorHit& hit)
: m_hit(hit)
{
m_mouseStart = editor->screenToEditor(msg->position());
auto keyPtr = m_hit.slice()->getByFrame(editor->frame());
ASSERT(keyPtr);
if (keyPtr)
m_keyStart = m_key = *keyPtr;
editor->captureMouse();
}
bool MovingSliceState::onMouseUp(Editor* editor, MouseMessage* msg)
{
{
ContextWriter writer(UIContext::instance(), 1000);
Transaction transaction(writer.context(), "Slice Movement", ModifyDocument);
doc::SliceKey newKey = m_key;
m_hit.slice()->insert(editor->frame(), m_keyStart);
transaction.execute(new cmd::SetSliceKey(m_hit.slice(),
editor->frame(),
newKey));
transaction.commit();
}
editor->backToPreviousState();
editor->releaseMouse();
return true;
}
bool MovingSliceState::onMouseMove(Editor* editor, MouseMessage* msg)
{
gfx::Point newCursorPos = editor->screenToEditor(msg->position());
gfx::Point delta = newCursorPos - m_mouseStart;
m_key = m_keyStart;
gfx::Rect rc =
(m_hit.type() == EditorHit::SliceCenter ? m_key.center():
m_key.bounds());
if (m_hit.border() & LEFT) {
rc.x += delta.x;
rc.w -= delta.x;
if (rc.w < 1) {
rc.x += rc.w-1;
rc.w = 1;
}
}
if (m_hit.border() & TOP) {
rc.y += delta.y;
rc.h -= delta.y;
if (rc.h < 1) {
rc.y += rc.h-1;
rc.h = 1;
}
}
if (m_hit.border() & RIGHT) {
rc.w += delta.x;
if (rc.w < 1)
rc.w = 1;
}
if (m_hit.border() & BOTTOM) {
rc.h += delta.y;
if (rc.h < 1)
rc.h = 1;
}
if (m_hit.type() == EditorHit::SliceCenter)
m_key.setCenter(rc);
else
m_key.setBounds(rc);
// Update the slice key
m_hit.slice()->insert(editor->frame(), m_key);
// Redraw the editor.
editor->invalidate();
// Use StandbyState implementation
return StandbyState::onMouseMove(editor, msg);
}
bool MovingSliceState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
{
switch (m_hit.border()) {
case TOP | LEFT:
editor->showMouseCursor(kSizeNWCursor);
break;
case TOP:
editor->showMouseCursor(kSizeNCursor);
break;
case TOP | RIGHT:
editor->showMouseCursor(kSizeNECursor);
break;
case LEFT:
editor->showMouseCursor(kSizeWCursor);
break;
case RIGHT:
editor->showMouseCursor(kSizeECursor);
break;
case BOTTOM | LEFT:
editor->showMouseCursor(kSizeSWCursor);
break;
case BOTTOM:
editor->showMouseCursor(kSizeSCursor);
break;
case BOTTOM | RIGHT:
editor->showMouseCursor(kSizeSECursor);
break;
}
return true;
}
} // namespace app

View File

@ -0,0 +1,38 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UI_EDITOR_MOVING_SLICE_STATE_H_INCLUDED
#define APP_UI_EDITOR_MOVING_SLICE_STATE_H_INCLUDED
#pragma once
#include "app/ui/editor/editor_hit.h"
#include "app/ui/editor/standby_state.h"
#include "doc/slice.h"
namespace app {
class Editor;
class MovingSliceState : public StandbyState {
public:
MovingSliceState(Editor* editor, ui::MouseMessage* msg,
const EditorHit& hit);
bool onMouseUp(Editor* editor, ui::MouseMessage* msg) override;
bool onMouseMove(Editor* editor, ui::MouseMessage* msg) override;
bool onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos) override;
bool requireBrushPreview() override { return false; }
private:
EditorHit m_hit;
gfx::Point m_mouseStart;
doc::SliceKey m_keyStart;
doc::SliceKey m_key;
};
} // namespace app
#endif

View File

@ -11,6 +11,7 @@
#include "app/ui/editor/standby_state.h"
#include "app/app.h"
#include "app/app_menus.h"
#include "app/color_picker.h"
#include "app/commands/cmd_eyedropper.h"
#include "app/commands/commands.h"
@ -22,6 +23,7 @@
#include "app/tools/ink.h"
#include "app/tools/pick_ink.h"
#include "app/tools/tool.h"
#include "app/ui/app_menuitem.h"
#include "app/ui/document_view.h"
#include "app/ui/editor/drawing_state.h"
#include "app/ui/editor/editor.h"
@ -29,6 +31,7 @@
#include "app/ui/editor/handle_type.h"
#include "app/ui/editor/moving_cel_state.h"
#include "app/ui/editor/moving_pixels_state.h"
#include "app/ui/editor/moving_slice_state.h"
#include "app/ui/editor/moving_symmetry_state.h"
#include "app/ui/editor/pivot_helpers.h"
#include "app/ui/editor/pixels_movement.h"
@ -46,6 +49,7 @@
#include "base/pi.h"
#include "doc/layer.h"
#include "doc/mask.h"
#include "doc/slice.h"
#include "doc/sprite.h"
#include "fixmath/fixmath.h"
#include "gfx/rect.h"
@ -217,6 +221,29 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg)
return true;
}
if (clickedInk->isSlice()) {
EditorHit hit = editor->calcHit(msg->position());
switch (hit.type()) {
case EditorHit::SliceBounds:
case EditorHit::SliceCenter:
if (msg->left()) {
MovingSliceState* newState = new MovingSliceState(editor, msg, hit);
editor->setState(EditorStatePtr(newState));
}
else {
Menu* popupMenu = AppMenus::instance()->getSlicePopupMenu();
if (popupMenu) {
Params params;
params.set("id", base::convert_to<std::string>(hit.slice()->id()).c_str());
AppMenuItem::setContextParams(params);
popupMenu->showPopup(msg->position());
AppMenuItem::setContextParams(Params());
}
}
return true;
}
}
// Only if the selected tool or quick tool is selection, we give the
// possibility to transform/move the selection. In other case,
// e.g. when selection is used with right-click mode, the
@ -409,8 +436,41 @@ bool StandbyState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
return true;
}
else if (ink->isSlice()) {
editor->showBrushPreview(mouseScreenPos);
return true;
EditorHit hit = editor->calcHit(mouseScreenPos);
switch (hit.type()) {
case EditorHit::None:
// Do nothing, continue
break;
case EditorHit::SliceBounds:
case EditorHit::SliceCenter:
switch (hit.border()) {
case TOP | LEFT:
editor->showMouseCursor(kSizeNWCursor);
break;
case TOP:
editor->showMouseCursor(kSizeNCursor);
break;
case TOP | RIGHT:
editor->showMouseCursor(kSizeNECursor);
break;
case LEFT:
editor->showMouseCursor(kSizeWCursor);
break;
case RIGHT:
editor->showMouseCursor(kSizeECursor);
break;
case BOTTOM | LEFT:
editor->showMouseCursor(kSizeSWCursor);
break;
case BOTTOM:
editor->showMouseCursor(kSizeSCursor);
break;
case BOTTOM | RIGHT:
editor->showMouseCursor(kSizeSECursor);
break;
}
return true;
}
}
}
@ -490,6 +550,27 @@ bool StandbyState::onUpdateStatusBar(Editor* editor)
buf+std::strlen(buf), " :grid: %d %d", col, row);
}
if (editor->docPref().show.slices()) {
int count = 0;
for (auto slice : editor->document()->sprite()->slices()) {
auto key = slice->getByFrame(editor->frame());
if (key &&
key->bounds().contains(
int(std::floor(spritePos.x)),
int(std::floor(spritePos.y)))) {
if (++count == 3) {
sprintf(
buf+std::strlen(buf), " :slice: ...");
break;
}
sprintf(
buf+std::strlen(buf), " :slice: %s",
slice->name().c_str());
}
}
}
StatusBar::instance()->setStatusText(0, buf);
}
@ -700,6 +781,7 @@ bool StandbyState::Decorator::onSetCursor(tools::Ink* ink, Editor* editor, const
return true;
}
// Move symmetry
gfx::Rect box1, box2;
if (getSymmetryHandles(editor, box1, box2) &&
(box1.contains(mouseScreenPos) ||

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -11,6 +11,7 @@
#include "app/ui/editor/tool_loop_impl.h"
#include "app/app.h"
#include "app/cmd/add_slice.h"
#include "app/cmd/set_mask.h"
#include "app/color.h"
#include "app/color_utils.h"
@ -415,6 +416,10 @@ public:
// Show selection edges
m_docPref.show.selectionEdges(true);
}
// Slice ink
else if (getInk()->isSlice()) {
redraw = true;
}
m_transaction.commit();
}
@ -461,6 +466,9 @@ public:
void setMask(Mask* newMask) override {
m_transaction.execute(new cmd::SetMask(m_document, newMask));
}
void addSlice(Slice* newSlice) override {
m_transaction.execute(new cmd::AddSlice(m_sprite, newSlice));
}
gfx::Point getMaskOrigin() override { return m_maskOrigin; }
bool getFilled() override { return m_filled; }
bool getPreviewFilled() override { return m_previewFilled; }
@ -603,6 +611,7 @@ public:
bool useMask() override { return false; }
Mask* getMask() override { return nullptr; }
void setMask(Mask* newMask) override { }
void addSlice(Slice* newSlice) override { }
gfx::Point getMaskOrigin() override { return gfx::Point(0, 0); }
bool getFilled() override { return false; }
bool getPreviewFilled() override { return false; }

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -19,10 +19,6 @@ namespace doc {
class Sprite;
}
namespace ui {
class Splitter;
}
namespace app {
class FrameTagWindow : protected app::gen::FrameTagProperties {

113
src/app/ui/slice_window.cpp Normal file
View File

@ -0,0 +1,113 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/slice_window.h"
#include "app/document.h"
#include "app/ui/user_data_popup.h"
#include "base/bind.h"
#include "doc/slice.h"
#include "doc/sprite.h"
#include <algorithm>
namespace app {
SliceWindow::SliceWindow(const doc::Sprite* sprite,
const doc::Slice* slice,
const doc::frame_t frameNum)
: m_userData(slice->userData())
{
name()->setText(slice->name());
const doc::SliceKey* key = slice->getByFrame(frameNum);
ASSERT(key);
if (!key)
return;
userData()->Click.connect(base::Bind<void>(&SliceWindow::onPopupUserData, this));
boundsX()->setTextf("%d", key->bounds().x);
boundsY()->setTextf("%d", key->bounds().y);
boundsW()->setTextf("%d", key->bounds().w);
boundsH()->setTextf("%d", key->bounds().h);
center()->Click.connect(base::Bind<void>(&SliceWindow::onCenterChange, this));
if (!key->center().isEmpty()) {
center()->setSelected(true);
centerX()->setTextf("%d", key->center().x);
centerY()->setTextf("%d", key->center().y);
centerW()->setTextf("%d", key->center().w);
centerH()->setTextf("%d", key->center().h);
}
else
onCenterChange();
}
bool SliceWindow::show()
{
openWindowInForeground();
return (closer() == ok());
}
std::string SliceWindow::nameValue() const
{
return name()->text();
}
gfx::Rect SliceWindow::boundsValue() const
{
gfx::Rect rc(boundsX()->textInt(),
boundsY()->textInt(),
boundsW()->textInt(),
boundsH()->textInt());
if (rc.w < 1) rc.w = 1;
if (rc.h < 1) rc.h = 1;
return rc;
}
gfx::Rect SliceWindow::centerValue() const
{
if (!center()->isSelected())
return gfx::Rect(0, 0, 0, 0);
gfx::Rect rc(centerX()->textInt(),
centerY()->textInt(),
centerW()->textInt(),
centerH()->textInt());
if (rc.w < 1) rc.w = 1;
if (rc.h < 1) rc.h = 1;
return rc;
}
void SliceWindow::onCenterChange()
{
bool state = center()->isSelected();
centerX()->setEnabled(state);
centerY()->setEnabled(state);
centerW()->setEnabled(state);
centerH()->setEnabled(state);
if (state) {
centerX()->setText("1");
centerY()->setText("1");
centerW()->setTextf("%d", std::max(1, boundsW()->textInt()-2));
centerH()->setTextf("%d", std::max(1, boundsH()->textInt()-2));
}
}
void SliceWindow::onPopupUserData()
{
show_user_data_popup(userData()->bounds(), m_userData);
}
} // namespace app

46
src/app/ui/slice_window.h Normal file
View File

@ -0,0 +1,46 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UI_SLICE_WINDOW_H_INCLUDED
#define APP_UI_SLICE_WINDOW_H_INCLUDED
#pragma once
#include "doc/anidir.h"
#include "doc/frame.h"
#include "doc/user_data.h"
#include "slice_properties.xml.h"
namespace doc {
class Slice;
class Sprite;
}
namespace app {
class SliceWindow : protected app::gen::SliceProperties {
public:
SliceWindow(const doc::Sprite* sprite,
const doc::Slice* slice,
const doc::frame_t frame);
bool show();
std::string nameValue() const;
gfx::Rect boundsValue() const;
gfx::Rect centerValue() const;
const doc::UserData& userDataValue() { return m_userData; }
private:
void onCenterChange();
void onPopupUserData();
doc::UserData m_userData;
};
}
#endif

View File

@ -936,6 +936,7 @@ bool Timeline::onProcessMessage(Message* msg)
if (popupMenu) {
AppMenuItem::setContextParams(params);
popupMenu->showPopup(mouseMsg->position());
AppMenuItem::setContextParams(Params());
m_state = STATE_STANDBY;
invalidate();

View File

@ -1,5 +1,5 @@
# Aseprite Document Library
# Copyright (C) 2001-2016 David Capello
# Copyright (C) 2001-2017 David Capello
if(WIN32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
@ -58,6 +58,9 @@ add_library(doc-lib
selected_frames.cpp
selected_layers.cpp
site.cpp
slice.cpp
slice_io.cpp
slices.cpp
sort_palette.cpp
sprite.cpp
sprites.cpp

View File

@ -1,4 +1,4 @@
Copyright (c) 2001-2016 David Capello
Copyright (c) 2001-2017 David Capello
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@ -1,4 +1,4 @@
# Aseprite Document Library
*Copyright (C) 2001-2016 David Capello*
*Copyright (C) 2001-2017 David Capello*
> Distributed under [MIT license](LICENSE.txt)

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2016 David Capello
// Copyright (c) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -34,6 +34,8 @@
#include "doc/remap.h"
#include "doc/rgbmap.h"
#include "doc/site.h"
#include "doc/slice.h"
#include "doc/slices.h"
#include "doc/sprite.h"
#endif

189
src/doc/keyframes.h Normal file
View File

@ -0,0 +1,189 @@
// Aseprite Document Library
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DOC_KEYFRAMES_H_INCLUDED
#define DOC_KEYFRAMES_H_INCLUDED
#pragma once
#include "base/disable_copying.h"
#include "doc/frame.h"
#include <vector>
namespace doc {
template<typename T>
class Keyframes {
public:
class Key {
public:
Key(const frame_t frame, T* value) : m_frame(frame), m_value(value) { }
frame_t frame() const { return m_frame; }
T* value() const { return m_value; }
void setFrame(const frame_t frame) { m_frame = frame; }
void setValue(T* value) { m_value = value; }
private:
frame_t m_frame;
T* m_value;
};
typedef std::vector<Key> List;
typedef typename List::iterator iterator;
typedef typename List::const_iterator const_iterator;
class Range {
public:
class RangeIterator {
public:
RangeIterator(const iterator& it,
const iterator& end, const frame_t frame)
: m_it(it), m_end(end), m_frame(frame) {
if (it != end) {
m_next = it;
++m_next;
}
else
m_next = end;
}
RangeIterator& operator++() {
++m_frame;
if (m_next != m_end &&
m_next->frame() == m_frame) {
m_it = m_next;
++m_next;
}
return *this;
}
bool operator!=(const RangeIterator& other) {
return (m_frame != other.m_frame+1);
}
T* operator*() {
if (m_it != m_end)
return m_it->value();
else
return nullptr;
}
private:
iterator m_it, m_next;
const iterator m_end;
frame_t m_frame;
};
Range(const iterator& fromIt,
const iterator& endIt,
const frame_t from,
const frame_t to)
: m_fromIt(fromIt), m_endIt(endIt)
, m_from(from), m_to(to) {
}
RangeIterator begin() const {
return RangeIterator(m_fromIt, m_endIt, m_from);
}
RangeIterator end() const {
return RangeIterator(m_fromIt, m_endIt, m_to);
}
bool empty() const {
return (m_fromIt == m_endIt);
}
size_t countKeys() const {
size_t count = 0;
auto it = m_fromIt;
for (; it!=m_endIt; ++it) {
if (it->frame() > m_to)
break;
++count;
}
return count;
}
private:
const iterator m_fromIt, m_endIt;
const frame_t m_from, m_to;
};
Keyframes() { }
void insert(const frame_t frame, T* value) {
auto it = getIterator(frame);
if (it == end())
m_keys.push_back(Key(frame, value));
else if (it->frame() == frame)
it->setValue(value);
else {
++it;
m_keys.insert(it, Key(frame, value));
}
}
T* remove(const frame_t frame) {
auto it = getIterator(frame);
if (it != end()) {
T* value = it->value();
m_keys.erase(it);
return value;
}
else
return nullptr;
}
T* operator[](const frame_t frame) {
auto it = getIterator(frame);
if (it != end() && it->value())
return it->value();
else
return nullptr;
}
iterator begin() { return m_keys.begin(); }
iterator end() { return m_keys.end(); }
const_iterator begin() const { return m_keys.begin(); }
const_iterator end() const { return m_keys.end(); }
std::size_t size() const { return m_keys.size(); }
bool empty() const { return m_keys.empty(); }
iterator getIterator(const frame_t frame) {
auto
it = m_keys.begin(),
end = m_keys.end();
auto next = it;
for (; it != end; it=next) {
++next;
if ((frame >= it->frame()) &&
(next == end || frame < next->frame())) {
return it;
}
}
return end;
}
frame_t fromFrame() const {
if (!m_keys.empty())
return m_keys.front().frame();
else
return -1;
}
frame_t toFrame() const {
if (!m_keys.empty())
return m_keys.back().frame();
else
return -1;
}
Range range(const frame_t from,
const frame_t to) {
return Range(getIterator(from), end(), from, to);
}
private:
List m_keys;
// Disable operator=
DISABLE_COPYING(Keyframes);
};
} // namespace doc
#endif

133
src/doc/keyframes_tests.cpp Normal file
View File

@ -0,0 +1,133 @@
// Aseprite Document Library
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gtest/gtest.h>
#include "doc/keyframes.h"
using namespace doc;
TEST(Keyframes, Operations)
{
Keyframes<int> k;
EXPECT_TRUE(k.empty());
EXPECT_EQ(0, k.size());
k.insert(0, new int(5));
EXPECT_FALSE(k.empty());
EXPECT_EQ(1, k.size());
EXPECT_EQ(0, k.fromFrame());
EXPECT_EQ(0, k.toFrame());
EXPECT_EQ(nullptr, k[-1]);
EXPECT_EQ(5, *k[0]);
EXPECT_EQ(5, *k[1]);
EXPECT_EQ(5, *k[2]);
k.insert(2, new int(6));
EXPECT_EQ(2, k.size());
EXPECT_EQ(0, k.fromFrame());
EXPECT_EQ(2, k.toFrame());
EXPECT_EQ(nullptr, k[-1]);
EXPECT_EQ(5, *k[0]);
EXPECT_EQ(5, *k[1]);
EXPECT_EQ(6, *k[2]);
EXPECT_EQ(6, *k[3]);
k.insert(1, new int(3));
EXPECT_EQ(3, k.size());
EXPECT_EQ(0, k.fromFrame());
EXPECT_EQ(2, k.toFrame());
EXPECT_EQ(5, *k[0]);
EXPECT_EQ(3, *k[1]);
EXPECT_EQ(6, *k[2]);
EXPECT_EQ(6, *k[3]);
k.remove(0);
EXPECT_EQ(2, k.size());
EXPECT_EQ(1, k.fromFrame());
EXPECT_EQ(2, k.toFrame());
EXPECT_EQ(nullptr, k[0]);
EXPECT_EQ(3, *k[1]);
EXPECT_EQ(6, *k[2]);
EXPECT_EQ(6, *k[3]);
k.remove(3);
EXPECT_EQ(1, k.size());
EXPECT_EQ(1, k.fromFrame());
EXPECT_EQ(1, k.toFrame());
EXPECT_EQ(nullptr, k[0]);
EXPECT_EQ(3, *k[1]);
EXPECT_EQ(3, *k[2]);
EXPECT_EQ(3, *k[3]);
}
TEST(Keyframes, Range)
{
Keyframes<int> k;
k.insert(0, new int(5));
k.insert(2, nullptr);
k.insert(4, new int(8));
k.insert(6, new int(2));
EXPECT_EQ(0, k.fromFrame());
EXPECT_EQ(6, k.toFrame());
EXPECT_EQ(5, *k[0]);
EXPECT_EQ(5, *k[1]);
EXPECT_EQ(nullptr, k[2]);
EXPECT_EQ(nullptr, k[3]);
EXPECT_EQ(8, *k[4]);
EXPECT_EQ(8, *k[5]);
EXPECT_EQ(2, *k[6]);
EXPECT_EQ(2, *k[7]);
int pass = 0;
for (int* value : k.range(0, 0)) {
EXPECT_EQ(5, *value);
++pass;
}
EXPECT_EQ(1, pass);
frame_t f = 1;
for (int* value : k.range(1, 5)) {
switch (f) {
case 1: EXPECT_EQ(5, *value); break;
case 2: EXPECT_EQ(nullptr, value); break;
case 3: EXPECT_EQ(nullptr, value); break;
case 4: EXPECT_EQ(8, *value); break;
case 5: EXPECT_EQ(8, *value); break;
default:
ASSERT_TRUE(false);
}
++f;
}
EXPECT_EQ(f, 6);
EXPECT_TRUE(k.range(-3, -1).empty());
EXPECT_FALSE(k.range(0, 1).empty());
EXPECT_FALSE(k.range(2, 3).empty());
EXPECT_FALSE(k.range(7, 7).empty());
EXPECT_EQ(1, k.range(0, 0).countKeys());
EXPECT_EQ(1, k.range(0, 1).countKeys());
EXPECT_EQ(2, k.range(0, 2).countKeys());
EXPECT_EQ(4, k.range(0, 7).countKeys());
EXPECT_EQ(1, k.range(2, 3).countKeys());
EXPECT_EQ(2, k.range(2, 4).countKeys());
EXPECT_EQ(3, k.range(3, 7).countKeys());
EXPECT_EQ(1, k.range(7, 7).countKeys());
}
int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2016 David Capello
// Copyright (c) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -24,6 +24,7 @@ namespace doc {
Sprite,
Document,
FrameTag,
Slice,
};
} // namespace doc

83
src/doc/slice.cpp Normal file
View File

@ -0,0 +1,83 @@
// Aseprite Document Library
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "doc/slice.h"
#include "base/debug.h"
#include "doc/slices.h"
namespace doc {
SliceKey::SliceKey()
{
}
SliceKey::SliceKey(// const frame_t frame,
const gfx::Rect& bounds,
const gfx::Rect& center)
: m_bounds(bounds)
, m_center(center)
{
}
Slice::Slice()
: WithUserData(ObjectType::Slice)
, m_owner(nullptr)
, m_name("Slice")
{
// TODO default color should be configurable
userData().setColor(doc::rgba(0, 0, 255, 255));
}
Slice::~Slice()
{
ASSERT(!m_owner);
}
int Slice::getMemSize() const
{
return sizeof(*this) + sizeof(SliceKey)*m_keys.size();
}
void Slice::insert(const frame_t frame, const SliceKey& key)
{
m_keys.insert(frame, new SliceKey(key));
}
void Slice::remove(const frame_t frame)
{
delete m_keys.remove(frame);
}
const SliceKey* Slice::getByFrame(const frame_t frame) const
{
auto it = const_cast<Slice*>(this)->m_keys.getIterator(frame);
if (it != m_keys.end())
return it->value();
else
return nullptr;
}
Slice::iterator Slice::getIteratorByFrame(const frame_t frame) const
{
return const_cast<Slice*>(this)->m_keys.getIterator(frame);
}
void Slice::setOwner(Slices* owner)
{
m_owner = owner;
}
void Slice::setName(const std::string& name)
{
m_name = name;
}
} // namespace doc

88
src/doc/slice.h Normal file
View File

@ -0,0 +1,88 @@
// Aseprite Document Library
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DOC_SLICE_H_INCLUDED
#define DOC_SLICE_H_INCLUDED
#pragma once
#include "doc/frame.h"
#include "doc/keyframes.h"
#include "doc/with_user_data.h"
#include "gfx/rect.h"
#include <string>
namespace doc {
class Slices;
class SliceKey {
public:
SliceKey();
SliceKey(const gfx::Rect& bounds,
const gfx::Rect& center = gfx::Rect());
bool isEmpty() const { return m_bounds.isEmpty(); }
const gfx::Rect& bounds() const { return m_bounds; }
const gfx::Rect& center() const { return m_center; }
void setBounds(const gfx::Rect& bounds) { m_bounds = bounds; }
void setCenter(const gfx::Rect& center) { m_center = center; }
private:
gfx::Rect m_bounds;
gfx::Rect m_center;
};
class Slice : public WithUserData {
typedef Keyframes<SliceKey> List;
public:
typedef List::iterator iterator;
typedef List::const_iterator const_iterator;
Slice();
~Slice();
int getMemSize() const override;
void insert(const frame_t frame, const SliceKey& key);
void remove(const frame_t frame);
const SliceKey* getByFrame(const frame_t frame) const;
iterator getIteratorByFrame(const frame_t frame) const;
iterator begin() { return m_keys.begin(); }
iterator end() { return m_keys.end(); }
const_iterator begin() const { return m_keys.begin(); }
const_iterator end() const { return m_keys.end(); }
std::size_t size() const { return m_keys.size(); }
bool empty() const { return m_keys.empty(); }
Slices* owner() const { return m_owner; }
frame_t fromFrame() const { return m_keys.fromFrame(); }
frame_t toFrame() const { return m_keys.toFrame(); }
const std::string& name() const { return m_name; }
void setOwner(Slices* owner);
void setName(const std::string& name);
List::Range range(const frame_t from, const frame_t to) {
return m_keys.range(from, to);
}
private:
Slices* m_owner;
std::string m_name;
List m_keys;
DISABLE_COPYING(Slice);
};
} // namespace doc
#endif

84
src/doc/slice_io.cpp Normal file
View File

@ -0,0 +1,84 @@
// Aseprite Document Library
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "doc/slice_io.h"
#include "base/serialization.h"
#include "base/unique_ptr.h"
#include "doc/slice.h"
#include "doc/string_io.h"
#include "doc/user_data_io.h"
#include <iostream>
namespace doc {
using namespace base::serialization;
using namespace base::serialization::little_endian;
void write_slice(std::ostream& os, const Slice* slice)
{
write32(os, slice->id());
write_string(os, slice->name());
write_user_data(os, slice->userData());
// Number of keys
write32(os, slice->size());
for (const auto& key : *slice) {
write32(os, key.frame());
write_slicekey(os, *key.value());
}
}
Slice* read_slice(std::istream& is, bool setId)
{
ObjectId id = read32(is);
std::string name = read_string(is);
UserData userData = read_user_data(is);
size_t nkeys = read32(is);
base::UniquePtr<Slice> slice(new Slice);
slice->setName(name);
while (nkeys--) {
frame_t fr = read32(is);
slice->insert(fr, read_slicekey(is));
}
if (setId)
slice->setId(id);
return slice.release();
}
void write_slicekey(std::ostream& os, const SliceKey& sliceKey)
{
write32(os, sliceKey.bounds().x);
write32(os, sliceKey.bounds().y);
write32(os, sliceKey.bounds().w);
write32(os, sliceKey.bounds().h);
write32(os, sliceKey.center().x);
write32(os, sliceKey.center().y);
write32(os, sliceKey.center().w);
write32(os, sliceKey.center().h);
}
SliceKey read_slicekey(std::istream& is)
{
gfx::Rect bounds, center;
bounds.x = read32(is);
bounds.y = read32(is);
bounds.w = read32(is);
bounds.h = read32(is);
center.x = read32(is);
center.y = read32(is);
center.w = read32(is);
center.h = read32(is);
return SliceKey(bounds, center);
}
}

26
src/doc/slice_io.h Normal file
View File

@ -0,0 +1,26 @@
// Aseprite Document Library
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DOC_SLICE_IO_H_INCLUDED
#define DOC_SLICE_IO_H_INCLUDED
#pragma once
#include <iosfwd>
namespace doc {
class Slice;
class SliceKey;
void write_slice(std::ostream& os, const Slice* slice);
Slice* read_slice(std::istream& is, bool setId = true);
void write_slicekey(std::ostream& os, const SliceKey& sliceKey);
SliceKey read_slicekey(std::istream& is);
} // namespace doc
#endif

67
src/doc/slices.cpp Normal file
View File

@ -0,0 +1,67 @@
// Aseprite Document Library
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "doc/slices.h"
#include "base/debug.h"
#include "doc/slice.h"
#include <algorithm>
namespace doc {
Slices::Slices(Sprite* sprite)
: m_sprite(sprite)
{
}
Slices::~Slices()
{
for (Slice* slice : m_slices) {
slice->setOwner(nullptr);
delete slice;
}
}
void Slices::add(Slice* slice)
{
m_slices.push_back(slice);
slice->setOwner(this);
}
void Slices::remove(Slice* slice)
{
auto it = std::find(m_slices.begin(), m_slices.end(), slice);
ASSERT(it != m_slices.end());
if (it != m_slices.end())
m_slices.erase(it);
slice->setOwner(nullptr);
}
Slice* Slices::getByName(const std::string& name) const
{
for (Slice* slice : *this) {
if (slice->name() == name)
return slice;
}
return nullptr;
}
Slice* Slices::getById(ObjectId id) const
{
for (Slice* slice : *this) {
if (slice->id() == id)
return slice;
}
return nullptr;
}
} // namespace doc

57
src/doc/slices.h Normal file
View File

@ -0,0 +1,57 @@
// Aseprite Document Library
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DOC_SLICES_H_INCLUDED
#define DOC_SLICES_H_INCLUDED
#pragma once
#include "base/disable_copying.h"
#include "doc/object_id.h"
#include <string>
#include <vector>
namespace doc {
class Slice;
class Sprite;
class Slices {
typedef std::vector<Slice*> List;
public:
typedef List::iterator iterator;
typedef List::const_iterator const_iterator;
Slices(Sprite* sprite);
~Slices();
Sprite* sprite() { return m_sprite; }
void add(Slice* slice);
void remove(Slice* slice);
Slice* getByName(const std::string& name) const;
Slice* getById(const ObjectId id) const;
iterator begin() { return m_slices.begin(); }
iterator end() { return m_slices.end(); }
const_iterator begin() const { return m_slices.begin(); }
const_iterator end() const { return m_slices.end(); }
std::size_t size() const { return m_slices.size(); }
bool empty() const { return m_slices.empty(); }
private:
Sprite* m_sprite;
List m_slices;
DISABLE_COPYING(Slices);
};
} // namespace doc
#endif

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2016 David Capello
// Copyright (c) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -39,6 +39,7 @@ Sprite::Sprite(PixelFormat format, int width, int height, int ncolors)
, m_pixelRatio(1, 1)
, m_frames(1)
, m_frameTags(this)
, m_slices(this)
{
ASSERT(width > 0 && height > 0);

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2016 David Capello
// Copyright (c) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -19,6 +19,7 @@
#include "doc/object.h"
#include "doc/pixel_format.h"
#include "doc/pixel_ratio.h"
#include "doc/slices.h"
#include "doc/sprite_position.h"
#include "gfx/rect.h"
@ -128,6 +129,9 @@ namespace doc {
const FrameTags& frameTags() const { return m_frameTags; }
FrameTags& frameTags() { return m_frameTags; }
const Slices& slices() const { return m_slices; }
Slices& slices() { return m_slices; }
////////////////////////////////////////
// Shared Images and CelData (for linked Cels)
@ -173,6 +177,7 @@ namespace doc {
mutable RgbMap* m_rgbMap;
FrameTags m_frameTags;
Slices m_slices;
// Disable default constructor and copying
Sprite();

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2015 David Capello
// Copyright (c) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -19,6 +19,7 @@ namespace doc {
}
const UserData& userData() const { return m_userData; }
UserData& userData() { return m_userData; }
void setUserData(const UserData& userData) {
m_userData = userData;