mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-12 07:13:23 +00:00
Add ability to edit multiple slices
Related to #721 and #1651, still need more work to give a better UX to edit static and animated slices.
This commit is contained in:
parent
40957c196f
commit
73de6c8b1d
@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Aseprite -->
|
||||
<!-- Copyright (C) 2017-2018 by David Capello -->
|
||||
<!-- Copyright (C) 2019 Igara Studio S.A. -->
|
||||
<!-- Copyright (C) 2017-2018 David Capello -->
|
||||
<gui>
|
||||
<window id="slice_properties" text="@.title">
|
||||
<grid columns="2">
|
||||
@ -30,16 +31,16 @@
|
||||
|
||||
<check text="@.center" id="center" />
|
||||
<hbox homogeneous="true">
|
||||
<expr id="center_x" text="0" />
|
||||
<expr id="center_y" text="0" />
|
||||
<expr id="center_w" text="0" />
|
||||
<expr id="center_h" text="0" />
|
||||
<expr id="center_x" />
|
||||
<expr id="center_y" />
|
||||
<expr id="center_w" />
|
||||
<expr id="center_h" />
|
||||
</hbox>
|
||||
|
||||
<check text="@.pivot" id="pivot" />
|
||||
<hbox>
|
||||
<expr id="pivot_x" text="0" />
|
||||
<expr id="pivot_y" text="0" />
|
||||
<expr id="pivot_x" />
|
||||
<expr id="pivot_y" />
|
||||
</hbox>
|
||||
|
||||
<separator horizontal="true" cell_hspan="2" />
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -10,6 +11,8 @@
|
||||
|
||||
#include "app/cmd/add_slice.h"
|
||||
|
||||
#include "app/doc.h"
|
||||
#include "app/doc_event.h"
|
||||
#include "doc/slice.h"
|
||||
#include "doc/slice_io.h"
|
||||
#include "doc/sprite.h"
|
||||
@ -42,9 +45,7 @@ void AddSlice::onUndo()
|
||||
write_slice(m_stream, slice);
|
||||
m_size = size_t(m_stream.tellp());
|
||||
|
||||
sprite->slices().remove(slice);
|
||||
sprite->incrementVersion();
|
||||
delete slice;
|
||||
removeSlice(sprite, slice);
|
||||
}
|
||||
|
||||
void AddSlice::onRedo()
|
||||
@ -52,13 +53,37 @@ void AddSlice::onRedo()
|
||||
Sprite* sprite = this->sprite();
|
||||
Slice* slice = read_slice(m_stream);
|
||||
|
||||
sprite->slices().add(slice);
|
||||
sprite->incrementVersion();
|
||||
addSlice(sprite, slice);
|
||||
|
||||
m_stream.str(std::string());
|
||||
m_stream.clear();
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
void AddSlice::addSlice(Sprite* sprite, Slice* slice)
|
||||
{
|
||||
sprite->slices().add(slice);
|
||||
sprite->incrementVersion();
|
||||
|
||||
Doc* doc = static_cast<Doc*>(sprite->document());
|
||||
DocEvent ev(doc);
|
||||
ev.sprite(sprite);
|
||||
ev.slice(slice);
|
||||
doc->notify_observers<DocEvent&>(&DocObserver::onAddSlice, ev);
|
||||
}
|
||||
|
||||
void AddSlice::removeSlice(Sprite* sprite, Slice* slice)
|
||||
{
|
||||
Doc* doc = static_cast<Doc*>(sprite->document());
|
||||
DocEvent ev(doc);
|
||||
ev.sprite(sprite);
|
||||
ev.slice(slice);
|
||||
doc->notify_observers<DocEvent&>(&DocObserver::onRemoveSlice, ev);
|
||||
|
||||
sprite->slices().remove(slice);
|
||||
sprite->incrementVersion();
|
||||
delete slice;
|
||||
}
|
||||
|
||||
} // namespace cmd
|
||||
} // namespace app
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -33,6 +34,9 @@ namespace cmd {
|
||||
}
|
||||
|
||||
private:
|
||||
void addSlice(Sprite* sprite, Slice* slice);
|
||||
void removeSlice(Sprite* sprite, Slice* slice);
|
||||
|
||||
size_t m_size;
|
||||
std::stringstream m_stream;
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2017-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -14,9 +15,10 @@
|
||||
#include "app/commands/command.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/ui/status_bar.h"
|
||||
#include "app/tx.h"
|
||||
#include "app/ui/status_bar.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "doc/selected_objects.h"
|
||||
#include "doc/slice.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "ui/alert.h"
|
||||
@ -66,40 +68,63 @@ void RemoveSliceCommand::onExecute(Context* context)
|
||||
const ContextReader reader(context);
|
||||
const Sprite* sprite = reader.sprite();
|
||||
frame_t frame = reader.frame();
|
||||
const Slice* foundSlice = nullptr;
|
||||
SelectedObjects slicesToDelete;
|
||||
|
||||
if (!m_sliceName.empty())
|
||||
foundSlice = sprite->slices().getByName(m_sliceName);
|
||||
else if (m_sliceId != NullId)
|
||||
foundSlice = sprite->slices().getById(m_sliceId);
|
||||
std::string sliceName;
|
||||
{
|
||||
Slice* slice = nullptr;
|
||||
if (!m_sliceName.empty())
|
||||
slice = sprite->slices().getByName(m_sliceName);
|
||||
else if (m_sliceId != NullId)
|
||||
slice = sprite->slices().getById(m_sliceId);
|
||||
|
||||
if (!foundSlice)
|
||||
if (slice)
|
||||
slicesToDelete.insert(slice->id());
|
||||
else
|
||||
slicesToDelete = reader.site()->selectedSlices();
|
||||
}
|
||||
|
||||
// Nothing to delete
|
||||
if (slicesToDelete.empty())
|
||||
return;
|
||||
|
||||
std::string sliceName = foundSlice->name();
|
||||
if (slicesToDelete.size() == 1) {
|
||||
Slice* slice = slicesToDelete.frontAs<Slice>();
|
||||
ASSERT(slice);
|
||||
if (slice)
|
||||
sliceName = slice->name();
|
||||
}
|
||||
|
||||
{
|
||||
ContextWriter writer(reader, 500);
|
||||
Doc* document(writer.document());
|
||||
Sprite* sprite(writer.sprite());
|
||||
Tx tx(writer.context(), "Remove Slice");
|
||||
Slice* slice = const_cast<Slice*>(foundSlice);
|
||||
|
||||
if (slice->size() > 1) {
|
||||
tx(new cmd::SetSliceKey(slice, frame, SliceKey()));
|
||||
}
|
||||
else {
|
||||
tx(new cmd::RemoveSlice(sprite, slice));
|
||||
for (auto slice : slicesToDelete.iterateAs<Slice>()) {
|
||||
ASSERT(slice);
|
||||
if (!slice)
|
||||
continue;
|
||||
|
||||
if (slice->size() > 1) {
|
||||
tx(new cmd::SetSliceKey(slice, frame, SliceKey()));
|
||||
}
|
||||
else {
|
||||
tx(new cmd::RemoveSlice(sprite, slice));
|
||||
}
|
||||
}
|
||||
|
||||
tx.commit();
|
||||
document->notifyGeneralUpdate();
|
||||
}
|
||||
|
||||
StatusBar::instance()->invalidate();
|
||||
if (!sliceName.empty())
|
||||
StatusBar::instance()->showTip(1000, "Slice '%s' removed", sliceName.c_str());
|
||||
StatusBar::instance()->showTip(
|
||||
1000, "Slice '%s' removed", sliceName.c_str());
|
||||
else
|
||||
StatusBar::instance()->showTip(1000, "Slice removed");
|
||||
StatusBar::instance()->showTip(
|
||||
1000, "%d slice(s) removed", slicesToDelete.size());
|
||||
}
|
||||
|
||||
Command* CommandFactory::createRemoveSliceCommand()
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2017-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -64,45 +65,76 @@ void SlicePropertiesCommand::onExecute(Context* context)
|
||||
const ContextReader reader(context);
|
||||
const Sprite* sprite = reader.sprite();
|
||||
frame_t frame = reader.frame();
|
||||
const Slice* foundSlice = nullptr;
|
||||
SelectedObjects slices;
|
||||
|
||||
if (!m_sliceName.empty())
|
||||
foundSlice = sprite->slices().getByName(m_sliceName);
|
||||
else if (m_sliceId != NullId)
|
||||
foundSlice = sprite->slices().getById(m_sliceId);
|
||||
{
|
||||
Slice* slice = nullptr;
|
||||
if (!m_sliceName.empty())
|
||||
slice = sprite->slices().getByName(m_sliceName);
|
||||
else if (m_sliceId != NullId)
|
||||
slice = sprite->slices().getById(m_sliceId);
|
||||
|
||||
if (!foundSlice)
|
||||
if (slice)
|
||||
slices.insert(slice->id());
|
||||
else
|
||||
slices = reader.site()->selectedSlices();
|
||||
}
|
||||
|
||||
// Nothing to delete
|
||||
if (slices.empty())
|
||||
return;
|
||||
|
||||
const doc::SliceKey* key = foundSlice->getByFrame(frame);
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
SliceWindow window(sprite, foundSlice, frame);
|
||||
SliceWindow window(sprite, slices, frame);
|
||||
if (!window.show())
|
||||
return;
|
||||
|
||||
{
|
||||
const SliceWindow::Mods mods = window.modifiedFields();
|
||||
ContextWriter writer(reader, 500);
|
||||
Tx tx(writer.context(), "Slice Properties");
|
||||
Slice* slice = const_cast<Slice*>(foundSlice);
|
||||
|
||||
std::string name = window.nameValue();
|
||||
for (Slice* slice : slices.iterateAs<Slice>()) {
|
||||
// Change name
|
||||
if (mods & SliceWindow::kName) {
|
||||
std::string name = window.nameValue();
|
||||
if (slice->name() != name)
|
||||
tx(new cmd::SetSliceName(slice, name));
|
||||
}
|
||||
|
||||
if (slice->name() != name)
|
||||
tx(new cmd::SetSliceName(slice, name));
|
||||
// Change user data
|
||||
if ((mods & SliceWindow::kUserData) &&
|
||||
slice->userData() != window.userDataValue()) {
|
||||
tx(new cmd::SetUserData(slice, window.userDataValue()));
|
||||
}
|
||||
|
||||
if (slice->userData() != window.userDataValue())
|
||||
tx(new cmd::SetUserData(slice, window.userDataValue()));
|
||||
// Change slice properties
|
||||
const doc::SliceKey* key = slice->getByFrame(frame);
|
||||
if (!key)
|
||||
continue;
|
||||
|
||||
if (key->bounds() != window.boundsValue() ||
|
||||
key->center() != window.centerValue() ||
|
||||
key->pivot() != window.pivotValue()) {
|
||||
SliceKey newKey = *key;
|
||||
newKey.setBounds(window.boundsValue());
|
||||
newKey.setCenter(window.centerValue());
|
||||
newKey.setPivot(window.pivotValue());
|
||||
tx(new cmd::SetSliceKey(slice, frame, newKey));
|
||||
gfx::Rect newBounds = newKey.bounds();
|
||||
gfx::Rect newCenter = newKey.center();
|
||||
gfx::Point newPivot = newKey.pivot();
|
||||
if (mods & SliceWindow::kBoundsX) newBounds.x = window.boundsValue().x;
|
||||
if (mods & SliceWindow::kBoundsY) newBounds.y = window.boundsValue().y;
|
||||
if (mods & SliceWindow::kBoundsW) newBounds.w = window.boundsValue().w;
|
||||
if (mods & SliceWindow::kBoundsH) newBounds.h = window.boundsValue().h;
|
||||
if (mods & SliceWindow::kCenterX) newCenter.x = window.centerValue().x;
|
||||
if (mods & SliceWindow::kCenterY) newCenter.y = window.centerValue().y;
|
||||
if (mods & SliceWindow::kCenterW) newCenter.w = window.centerValue().w;
|
||||
if (mods & SliceWindow::kCenterH) newCenter.h = window.centerValue().h;
|
||||
if (mods & SliceWindow::kPivotX) newPivot.x = window.pivotValue().x;
|
||||
if (mods & SliceWindow::kPivotY) newPivot.y = window.pivotValue().y;
|
||||
newKey.setBounds(newBounds);
|
||||
newKey.setCenter(newCenter);
|
||||
newKey.setPivot(newPivot);
|
||||
|
||||
if (key->bounds() != newKey.bounds() ||
|
||||
key->center() != newKey.center() ||
|
||||
key->pivot() != newKey.pivot()) {
|
||||
tx(new cmd::SetSliceKey(slice, frame, newKey));
|
||||
}
|
||||
}
|
||||
|
||||
tx.commit();
|
||||
|
@ -1,5 +1,6 @@
|
||||
// Aseprite
|
||||
// Copyright (c) 2001-2018 David Capello
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -17,6 +18,7 @@ namespace doc {
|
||||
class Image;
|
||||
class Layer;
|
||||
class LayerImage;
|
||||
class Slice;
|
||||
class Sprite;
|
||||
}
|
||||
|
||||
@ -35,6 +37,8 @@ namespace app {
|
||||
, m_image(NULL)
|
||||
, m_imageIndex(-1)
|
||||
, m_frame(0)
|
||||
, m_frameTag(nullptr)
|
||||
, m_slice(nullptr)
|
||||
, m_targetLayer(NULL)
|
||||
, m_targetFrame(0) {
|
||||
}
|
||||
@ -48,6 +52,7 @@ namespace app {
|
||||
int imageIndex() const { return m_imageIndex; }
|
||||
frame_t frame() const { return m_frame; }
|
||||
FrameTag* frameTag() const { return m_frameTag; }
|
||||
Slice* slice() const { return m_slice; }
|
||||
const gfx::Region& region() const { return m_region; }
|
||||
|
||||
void sprite(Sprite* sprite) { m_sprite = sprite; }
|
||||
@ -57,6 +62,7 @@ namespace app {
|
||||
void imageIndex(int imageIndex) { m_imageIndex = imageIndex; }
|
||||
void frame(frame_t frame) { m_frame = frame; }
|
||||
void frameTag(FrameTag* frameTag) { m_frameTag = frameTag; }
|
||||
void slice(Slice* slice) { m_slice = slice; }
|
||||
void region(const gfx::Region& rgn) { m_region = rgn; }
|
||||
|
||||
// Destination of the operation.
|
||||
@ -75,6 +81,7 @@ namespace app {
|
||||
int m_imageIndex;
|
||||
frame_t m_frame;
|
||||
FrameTag* m_frameTag;
|
||||
Slice* m_slice;
|
||||
gfx::Region m_region;
|
||||
|
||||
// For copy/move commands, the m_layer/m_frame are source of the
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2018 David Capello
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -30,6 +30,7 @@ namespace app {
|
||||
virtual void onAddLayer(DocEvent& ev) { }
|
||||
virtual void onAddFrame(DocEvent& ev) { }
|
||||
virtual void onAddCel(DocEvent& ev) { }
|
||||
virtual void onAddSlice(DocEvent& ev) { }
|
||||
virtual void onAddFrameTag(DocEvent& ev) { }
|
||||
|
||||
virtual void onBeforeRemoveLayer(DocEvent& ev) { }
|
||||
@ -40,6 +41,7 @@ namespace app {
|
||||
virtual void onRemoveFrame(DocEvent& ev) { }
|
||||
virtual void onRemoveFrameTag(DocEvent& ev) { }
|
||||
virtual void onRemoveCel(DocEvent& ev) { }
|
||||
virtual void onRemoveSlice(DocEvent& ev) { }
|
||||
|
||||
virtual void onSpriteSizeChanged(DocEvent& ev) { }
|
||||
virtual void onSpriteTransparentColorChanged(DocEvent& ev) { }
|
||||
|
@ -1,5 +1,6 @@
|
||||
// Aseprite
|
||||
// Copyright (c) 2001-2018 David Capello
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -11,6 +12,7 @@
|
||||
#include "doc/frame.h"
|
||||
#include "doc/selected_frames.h"
|
||||
#include "doc/selected_layers.h"
|
||||
#include "doc/selected_objects.h"
|
||||
|
||||
namespace doc {
|
||||
class Cel;
|
||||
@ -84,6 +86,12 @@ namespace app {
|
||||
m_selectedFrames = selectedFrames;
|
||||
}
|
||||
|
||||
const doc::SelectedObjects& selectedSlices() const { return m_selectedSlices; }
|
||||
doc::SelectedObjects& selectedSlices() { return m_selectedSlices; }
|
||||
void selectedSlices(const doc::SelectedObjects& set) {
|
||||
m_selectedSlices = set;
|
||||
}
|
||||
|
||||
doc::Palette* palette();
|
||||
doc::Image* image(int* x = nullptr, int* y = nullptr, int* opacity = nullptr) const;
|
||||
doc::Palette* palette() const;
|
||||
@ -96,6 +104,7 @@ namespace app {
|
||||
doc::frame_t m_frame;
|
||||
doc::SelectedLayers m_selectedLayers;
|
||||
doc::SelectedFrames m_selectedFrames;
|
||||
doc::SelectedObjects m_selectedSlices;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -591,6 +591,9 @@ bool DocView::onClear(Context* ctx)
|
||||
|
||||
void DocView::onCancel(Context* ctx)
|
||||
{
|
||||
if (m_editor)
|
||||
m_editor->cancelSelections();
|
||||
|
||||
// Deselect mask
|
||||
if (ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
|
||||
ContextFlags::HasVisibleMask)) {
|
||||
|
@ -380,6 +380,8 @@ void Editor::getSite(Site* site) const
|
||||
site->sprite(m_sprite);
|
||||
site->layer(m_layer);
|
||||
site->frame(m_frame);
|
||||
if (!m_selectedSlices.empty())
|
||||
site->selectedSlices(m_selectedSlices);
|
||||
}
|
||||
|
||||
Site Editor::getSite() const
|
||||
@ -1003,6 +1005,7 @@ void Editor::drawSlices(ui::Graphics* g)
|
||||
if (!isVisible() || !m_document)
|
||||
return;
|
||||
|
||||
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
|
||||
gfx::Point mainOffset(mainTilePosition());
|
||||
|
||||
for (auto slice : m_sprite->slices()) {
|
||||
@ -1053,7 +1056,15 @@ void Editor::drawSlices(ui::Graphics* g)
|
||||
g->drawRect(in_color, in);
|
||||
}
|
||||
|
||||
g->drawRect(color, out);
|
||||
if (isSliceSelected(slice) &&
|
||||
getCurrentEditorInk()->isSlice()) {
|
||||
PaintWidgetPartInfo info;
|
||||
theme->paintWidgetPart(
|
||||
g, theme->styles.colorbarSelection(), out, info);
|
||||
}
|
||||
else {
|
||||
g->drawRect(color, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1610,6 +1621,42 @@ bool Editor::startStraightLineWithFreehandTool(const ui::MouseMessage* msg)
|
||||
document()->lastDrawingPoint() != Doc::NoLastDrawingPoint());
|
||||
}
|
||||
|
||||
bool Editor::isSliceSelected(const doc::Slice* slice) const
|
||||
{
|
||||
ASSERT(slice);
|
||||
return m_selectedSlices.contains(slice->id());
|
||||
}
|
||||
|
||||
void Editor::clearSlicesSelection()
|
||||
{
|
||||
m_selectedSlices.clear();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void Editor::selectSlice(const doc::Slice* slice)
|
||||
{
|
||||
ASSERT(slice);
|
||||
m_selectedSlices.insert(slice->id());
|
||||
invalidate();
|
||||
}
|
||||
|
||||
bool Editor::selectSliceBox(const gfx::Rect& box)
|
||||
{
|
||||
m_selectedSlices.clear();
|
||||
for (auto slice : m_sprite->slices()) {
|
||||
auto key = slice->getByFrame(m_frame);
|
||||
if (key && key->bounds().intersects(box))
|
||||
m_selectedSlices.insert(slice->id());
|
||||
}
|
||||
invalidate();
|
||||
return !m_selectedSlices.empty();
|
||||
}
|
||||
|
||||
void Editor::cancelSelections()
|
||||
{
|
||||
m_selectedSlices.clear();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Message handler for the editor
|
||||
|
||||
@ -2040,6 +2087,15 @@ void Editor::onRemoveFrameTag(DocEvent& ev)
|
||||
m_state->onRemoveFrameTag(this, ev.frameTag());
|
||||
}
|
||||
|
||||
void Editor::onRemoveSlice(DocEvent& ev)
|
||||
{
|
||||
ASSERT(ev.slice());
|
||||
if (ev.slice() &&
|
||||
m_selectedSlices.contains(ev.slice()->id())) {
|
||||
m_selectedSlices.erase(ev.slice()->id());
|
||||
}
|
||||
}
|
||||
|
||||
void Editor::setCursor(const gfx::Point& mouseScreenPos)
|
||||
{
|
||||
Rect vp = View::getView(this)->viewportBounds();
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (c) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -24,6 +24,7 @@
|
||||
#include "doc/algorithm/flip_type.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/image_buffer.h"
|
||||
#include "doc/selected_objects.h"
|
||||
#include "filters/tiled_mode.h"
|
||||
#include "gfx/fwd.h"
|
||||
#include "obs/connection.h"
|
||||
@ -36,6 +37,8 @@
|
||||
#include "ui/timer.h"
|
||||
#include "ui/widget.h"
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace doc {
|
||||
class Layer;
|
||||
class Sprite;
|
||||
@ -275,6 +278,17 @@ namespace app {
|
||||
// freehand tool is pressed.
|
||||
bool startStraightLineWithFreehandTool(const ui::MouseMessage* msg);
|
||||
|
||||
// Functions to handle the set of selected slices.
|
||||
bool isSliceSelected(const doc::Slice* slice) const;
|
||||
void clearSlicesSelection();
|
||||
void selectSlice(const doc::Slice* slice);
|
||||
bool selectSliceBox(const gfx::Rect& box);
|
||||
bool hasSelectedSlices() const { return !m_selectedSlices.empty(); }
|
||||
|
||||
// Called by DocView's InputChainElement::onCancel() impl when Esc
|
||||
// key is pressed to cancel the active selection.
|
||||
void cancelSelections();
|
||||
|
||||
static void registerCommands();
|
||||
|
||||
protected:
|
||||
@ -297,6 +311,7 @@ namespace app {
|
||||
void onRemoveCel(DocEvent& ev) override;
|
||||
void onAddFrameTag(DocEvent& ev) override;
|
||||
void onRemoveFrameTag(DocEvent& ev) override;
|
||||
void onRemoveSlice(DocEvent& ev) override;
|
||||
|
||||
// ActiveToolObserver impl
|
||||
void onActiveToolChange(tools::Tool* tool) override;
|
||||
@ -422,6 +437,9 @@ namespace app {
|
||||
gfx::Rect m_perfInfoBounds;
|
||||
#endif
|
||||
|
||||
// For slices
|
||||
doc::SelectedObjects m_selectedSlices;
|
||||
|
||||
// The render engine must be shared between all editors so when a
|
||||
// DrawingState is being used in one editor, other editors for the
|
||||
// same document can show the same preview image/stroke being drawn
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2017-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -19,22 +20,33 @@
|
||||
#include "doc/slice.h"
|
||||
#include "ui/message.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace app {
|
||||
|
||||
using namespace ui;
|
||||
|
||||
MovingSliceState::MovingSliceState(Editor* editor, MouseMessage* msg,
|
||||
const EditorHit& hit)
|
||||
: m_hit(hit)
|
||||
MovingSliceState::MovingSliceState(Editor* editor,
|
||||
MouseMessage* msg,
|
||||
const EditorHit& hit,
|
||||
const doc::SelectedObjects& selectedSlices)
|
||||
: m_frame(editor->frame())
|
||||
, m_hit(hit)
|
||||
, m_items(std::max<std::size_t>(1, selectedSlices.size()))
|
||||
{
|
||||
m_mouseStart = editor->screenToEditor(msg->position());
|
||||
|
||||
auto keyPtr = m_hit.slice()->getByFrame(editor->frame());
|
||||
ASSERT(keyPtr);
|
||||
if (keyPtr)
|
||||
m_keyStart = m_key = *keyPtr;
|
||||
if (selectedSlices.empty()) {
|
||||
m_items[0] = getItemForSlice(m_hit.slice());
|
||||
}
|
||||
else {
|
||||
int i = 0;
|
||||
for (Slice* slice : selectedSlices.iterateAs<Slice>()) {
|
||||
ASSERT(slice);
|
||||
m_items[i++] = getItemForSlice(slice);
|
||||
}
|
||||
}
|
||||
|
||||
editor->captureMouse();
|
||||
}
|
||||
@ -45,12 +57,11 @@ bool MovingSliceState::onMouseUp(Editor* editor, MouseMessage* msg)
|
||||
ContextWriter writer(UIContext::instance(), 1000);
|
||||
Tx tx(writer.context(), "Slice Movement", ModifyDocument);
|
||||
|
||||
doc::SliceKey newKey = m_key;
|
||||
m_hit.slice()->insert(editor->frame(), m_keyStart);
|
||||
for (const auto& item : m_items) {
|
||||
item.slice->insert(m_frame, item.oldKey);
|
||||
tx(new cmd::SetSliceKey(item.slice, m_frame, item.newKey));
|
||||
}
|
||||
|
||||
tx(new cmd::SetSliceKey(m_hit.slice(),
|
||||
editor->frame(),
|
||||
newKey));
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
@ -63,54 +74,64 @@ bool MovingSliceState::onMouseMove(Editor* editor, MouseMessage* msg)
|
||||
{
|
||||
gfx::Point newCursorPos = editor->screenToEditor(msg->position());
|
||||
gfx::Point delta = newCursorPos - m_mouseStart;
|
||||
gfx::Rect totalBounds = selectedSlicesBounds();
|
||||
|
||||
m_key = m_keyStart;
|
||||
gfx::Rect rc =
|
||||
(m_hit.type() == EditorHit::SliceCenter ? m_key.center():
|
||||
m_key.bounds());
|
||||
ASSERT(totalBounds.w > 0);
|
||||
ASSERT(totalBounds.h > 0);
|
||||
|
||||
// Move slice
|
||||
if (m_hit.border() == (CENTER | MIDDLE)) {
|
||||
rc.x += delta.x;
|
||||
rc.y += delta.y;
|
||||
}
|
||||
else {
|
||||
if (m_hit.border() & LEFT) {
|
||||
for (auto& item : m_items) {
|
||||
auto& key = item.newKey;
|
||||
key = item.oldKey;
|
||||
|
||||
gfx::Rect rc =
|
||||
(m_hit.type() == EditorHit::SliceCenter ? key.center():
|
||||
key.bounds());
|
||||
|
||||
// Move slice
|
||||
if (m_hit.border() == (CENTER | MIDDLE)) {
|
||||
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;
|
||||
}
|
||||
else {
|
||||
if (m_hit.border() & LEFT) {
|
||||
rc.x += delta.x * (totalBounds.x2() - rc.x) / totalBounds.w;
|
||||
rc.w -= delta.x * rc.w / totalBounds.w;
|
||||
if (rc.w < 1) {
|
||||
rc.x += rc.w-1;
|
||||
rc.w = 1;
|
||||
}
|
||||
}
|
||||
if (m_hit.border() & TOP) {
|
||||
rc.y += delta.y * (totalBounds.y2() - rc.y) / totalBounds.h;
|
||||
rc.h -= delta.y * rc.h / totalBounds.h;
|
||||
if (rc.h < 1) {
|
||||
rc.y += rc.h-1;
|
||||
rc.h = 1;
|
||||
}
|
||||
}
|
||||
if (m_hit.border() & RIGHT) {
|
||||
rc.x += delta.x * (rc.x - totalBounds.x) / totalBounds.w;
|
||||
rc.w += delta.x * rc.w / totalBounds.w;
|
||||
if (rc.w < 1)
|
||||
rc.w = 1;
|
||||
}
|
||||
if (m_hit.border() & BOTTOM) {
|
||||
rc.y += delta.y * (rc.y - totalBounds.y) / totalBounds.h;
|
||||
rc.h += delta.y * rc.h / totalBounds.h;
|
||||
if (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)
|
||||
key.setCenter(rc);
|
||||
else
|
||||
key.setBounds(rc);
|
||||
|
||||
// Update the slice key
|
||||
item.slice->insert(m_frame, key);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@ -149,4 +170,25 @@ bool MovingSliceState::onSetCursor(Editor* editor, const gfx::Point& mouseScreen
|
||||
return true;
|
||||
}
|
||||
|
||||
MovingSliceState::Item MovingSliceState::getItemForSlice(doc::Slice* slice)
|
||||
{
|
||||
Item item;
|
||||
item.slice = slice;
|
||||
|
||||
auto keyPtr = slice->getByFrame(m_frame);
|
||||
ASSERT(keyPtr);
|
||||
if (keyPtr)
|
||||
item.oldKey = item.newKey = *keyPtr;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
gfx::Rect MovingSliceState::selectedSlicesBounds() const
|
||||
{
|
||||
gfx::Rect bounds;
|
||||
for (auto& item : m_items)
|
||||
bounds |= item.oldKey.bounds();
|
||||
return bounds;
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -10,6 +11,8 @@
|
||||
|
||||
#include "app/ui/editor/editor_hit.h"
|
||||
#include "app/ui/editor/standby_state.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/selected_objects.h"
|
||||
#include "doc/slice.h"
|
||||
|
||||
namespace app {
|
||||
@ -17,8 +20,10 @@ namespace app {
|
||||
|
||||
class MovingSliceState : public StandbyState {
|
||||
public:
|
||||
MovingSliceState(Editor* editor, ui::MouseMessage* msg,
|
||||
const EditorHit& hit);
|
||||
MovingSliceState(Editor* editor,
|
||||
ui::MouseMessage* msg,
|
||||
const EditorHit& hit,
|
||||
const doc::SelectedObjects& selectedSlices);
|
||||
|
||||
bool onMouseUp(Editor* editor, ui::MouseMessage* msg) override;
|
||||
bool onMouseMove(Editor* editor, ui::MouseMessage* msg) override;
|
||||
@ -27,10 +32,19 @@ namespace app {
|
||||
bool requireBrushPreview() override { return false; }
|
||||
|
||||
private:
|
||||
struct Item {
|
||||
doc::Slice* slice;
|
||||
doc::SliceKey oldKey;
|
||||
doc::SliceKey newKey;
|
||||
};
|
||||
|
||||
Item getItemForSlice(doc::Slice* slice);
|
||||
gfx::Rect selectedSlicesBounds() const;
|
||||
|
||||
doc::frame_t m_frame;
|
||||
EditorHit m_hit;
|
||||
gfx::Point m_mouseStart;
|
||||
doc::SliceKey m_keyStart;
|
||||
doc::SliceKey m_key;
|
||||
std::vector<Item> m_items;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
@ -239,14 +239,28 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg)
|
||||
case EditorHit::SliceBounds:
|
||||
case EditorHit::SliceCenter:
|
||||
if (msg->left()) {
|
||||
MovingSliceState* newState = new MovingSliceState(editor, msg, hit);
|
||||
// If we click outside all slices, we clear the selection of slices.
|
||||
if (!hit.slice() || !site.selectedSlices().contains(hit.slice()->id())) {
|
||||
editor->clearSlicesSelection();
|
||||
|
||||
site = Site();
|
||||
editor->getSite(&site);
|
||||
}
|
||||
|
||||
MovingSliceState* newState = new MovingSliceState(
|
||||
editor, msg, hit, site.selectedSlices());
|
||||
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());
|
||||
// When the editor doesn't have a set of selected slices,
|
||||
// we set the specific clicked slice for the commands (in
|
||||
// other case, those commands will get the selected set of
|
||||
// slices from Site::selectedSlices() field).
|
||||
if (!editor->hasSelectedSlices())
|
||||
params.set("id", base::convert_to<std::string>(hit.slice()->id()).c_str());
|
||||
AppMenuItem::setContextParams(params);
|
||||
popupMenu->showPopup(msg->position());
|
||||
AppMenuItem::setContextParams(Params());
|
||||
|
@ -582,19 +582,30 @@ public:
|
||||
|
||||
void onSliceRect(const gfx::Rect& bounds) override {
|
||||
if (getMouseButton() == ToolLoop::Left) {
|
||||
Slice* slice = new Slice;
|
||||
SliceKey key(bounds);
|
||||
slice->insert(getFrame(), key);
|
||||
// Try to select slices, but if it returns false, it means that
|
||||
// there are no slices in the box to be selected, so we show a
|
||||
// popup menu to create a new one.
|
||||
if (!m_editor->selectSliceBox(bounds) &&
|
||||
(bounds.w > 1 || bounds.h > 1)) {
|
||||
Slice* slice = new Slice;
|
||||
SliceKey key(bounds);
|
||||
slice->insert(getFrame(), key);
|
||||
|
||||
auto color = Preferences::instance().slices.defaultColor();
|
||||
slice->userData().setColor(
|
||||
doc::rgba(color.getRed(),
|
||||
color.getGreen(),
|
||||
color.getBlue(),
|
||||
color.getAlpha()));
|
||||
auto color = Preferences::instance().slices.defaultColor();
|
||||
slice->userData().setColor(
|
||||
doc::rgba(color.getRed(),
|
||||
color.getGreen(),
|
||||
color.getBlue(),
|
||||
color.getAlpha()));
|
||||
|
||||
m_transaction.execute(new cmd::AddSlice(m_sprite, slice));
|
||||
m_transaction.execute(new cmd::AddSlice(m_sprite, slice));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel the operation (do not create a new transaction for this
|
||||
// no-op, e.g. just change the set of selected slices).
|
||||
m_canceled = true;
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -21,45 +22,80 @@
|
||||
namespace app {
|
||||
|
||||
SliceWindow::SliceWindow(const doc::Sprite* sprite,
|
||||
const doc::Slice* slice,
|
||||
const doc::frame_t frameNum)
|
||||
: m_userData(slice->userData())
|
||||
const doc::SelectedObjects& slices,
|
||||
const doc::frame_t frame)
|
||||
: m_mods(kNone)
|
||||
{
|
||||
name()->setText(slice->name());
|
||||
|
||||
const doc::SliceKey* key = slice->getByFrame(frameNum);
|
||||
ASSERT(key);
|
||||
if (!key)
|
||||
return;
|
||||
ASSERT(!slices.empty());
|
||||
|
||||
Slice* slice = slices.frontAs<Slice>();
|
||||
m_userData = slice->userData();
|
||||
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);
|
||||
if (slices.size() == 1) {
|
||||
// If we are going to edit just one slice, we indicate like all
|
||||
// fields were modified, so then the slice properties transaction
|
||||
// is created comparing the window fields with the slice fields
|
||||
// (and not with which field was modified).
|
||||
m_mods = kAll;
|
||||
|
||||
center()->Click.connect(base::Bind<void>(&SliceWindow::onCenterChange, this));
|
||||
pivot()->Click.connect(base::Bind<void>(&SliceWindow::onPivotChange, this));
|
||||
name()->setText(slice->name());
|
||||
|
||||
if (key->hasCenter()) {
|
||||
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);
|
||||
const doc::SliceKey* key = slice->getByFrame(frame);
|
||||
ASSERT(key);
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
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));
|
||||
pivot()->Click.connect(base::Bind<void>(&SliceWindow::onPivotChange, this));
|
||||
|
||||
if (key->hasCenter()) {
|
||||
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();
|
||||
}
|
||||
|
||||
if (key->hasPivot()) {
|
||||
pivot()->setSelected(true);
|
||||
pivotX()->setTextf("%d", key->pivot().x);
|
||||
pivotY()->setTextf("%d", key->pivot().y);
|
||||
}
|
||||
else {
|
||||
onPivotChange();
|
||||
}
|
||||
}
|
||||
// Edit multiple slices
|
||||
else {
|
||||
onCenterChange();
|
||||
}
|
||||
ui::Entry* entries[] = {
|
||||
name(),
|
||||
boundsX(), boundsY(), boundsW(), boundsH(),
|
||||
centerX(), centerY(), centerW(), centerH(),
|
||||
pivotX(), pivotY() };
|
||||
const Mods entryMods[] = {
|
||||
kName,
|
||||
kBoundsX, kBoundsY, kBoundsW, kBoundsH,
|
||||
kCenterX, kCenterY, kCenterW, kCenterH,
|
||||
kPivotX, kPivotY };
|
||||
|
||||
if (key->hasPivot()) {
|
||||
pivot()->setSelected(true);
|
||||
pivotX()->setTextf("%d", key->pivot().x);
|
||||
pivotY()->setTextf("%d", key->pivot().y);
|
||||
}
|
||||
else {
|
||||
onPivotChange();
|
||||
for (int i=0; i<sizeof(entries)/sizeof(entries[0]); ++i) {
|
||||
auto entry = entries[i];
|
||||
Mods mod = entryMods[i];
|
||||
entry->setSuffix("*");
|
||||
entry->Change.connect(
|
||||
[this, entry, mod]{
|
||||
onModifyField(entry, mod);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,7 +176,16 @@ void SliceWindow::onPivotChange()
|
||||
|
||||
void SliceWindow::onPopupUserData()
|
||||
{
|
||||
show_user_data_popup(userData()->bounds(), m_userData);
|
||||
if (show_user_data_popup(userData()->bounds(), m_userData))
|
||||
m_mods = Mods(int(m_mods) | int(kUserData));
|
||||
}
|
||||
|
||||
void SliceWindow::onModifyField(ui::Entry* entry,
|
||||
const Mods mods)
|
||||
{
|
||||
if (entry)
|
||||
entry->setSuffix(std::string());
|
||||
m_mods = Mods(int(m_mods) | int(mods));
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -10,6 +11,7 @@
|
||||
|
||||
#include "doc/anidir.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/selected_objects.h"
|
||||
#include "doc/user_data.h"
|
||||
|
||||
#include "slice_properties.xml.h"
|
||||
@ -23,8 +25,25 @@ namespace app {
|
||||
|
||||
class SliceWindow : protected app::gen::SliceProperties {
|
||||
public:
|
||||
enum Mods {
|
||||
kNone = 0x0000,
|
||||
kName = 0x0001,
|
||||
kBoundsX = 0x0002,
|
||||
kBoundsY = 0x0004,
|
||||
kBoundsW = 0x0008,
|
||||
kBoundsH = 0x0010,
|
||||
kCenterX = 0x0020,
|
||||
kCenterY = 0x0040,
|
||||
kCenterW = 0x0080,
|
||||
kCenterH = 0x0100,
|
||||
kPivotX = 0x0200,
|
||||
kPivotY = 0x0400,
|
||||
kUserData = 0x0800,
|
||||
kAll = 0xffff,
|
||||
};
|
||||
|
||||
SliceWindow(const doc::Sprite* sprite,
|
||||
const doc::Slice* slice,
|
||||
const doc::SelectedObjects& slices,
|
||||
const doc::frame_t frame);
|
||||
|
||||
bool show();
|
||||
@ -35,12 +54,21 @@ namespace app {
|
||||
gfx::Point pivotValue() const;
|
||||
const doc::UserData& userDataValue() { return m_userData; }
|
||||
|
||||
Mods modifiedFields() const { return m_mods; }
|
||||
|
||||
private:
|
||||
void onCenterChange();
|
||||
void onPivotChange();
|
||||
void onPopupUserData();
|
||||
void onModifyField(ui::Entry* entry, const Mods mods);
|
||||
|
||||
doc::UserData m_userData;
|
||||
|
||||
// Flags used to know what specific entry/checkbox was modified
|
||||
// when we edit multiple-slices in the same property dialog. In
|
||||
// this way we know what field modify of each slice in
|
||||
// SlicePropertiesCommand::onExecute().
|
||||
Mods m_mods;
|
||||
};
|
||||
|
||||
}
|
||||
|
107
src/doc/selected_objects.h
Normal file
107
src/doc/selected_objects.h
Normal file
@ -0,0 +1,107 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifndef DOC_SELECTED_OBJECTS_H_INCLUDED
|
||||
#define DOC_SELECTED_OBJECTS_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "base/debug.h"
|
||||
#include "doc/object.h"
|
||||
#include "doc/object_id.h"
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace doc {
|
||||
|
||||
// A set of selected objects (e.g. select set of slices)
|
||||
class SelectedObjects {
|
||||
public:
|
||||
typedef std::set<ObjectId> Set;
|
||||
typedef Set::iterator iterator;
|
||||
typedef Set::const_iterator const_iterator;
|
||||
|
||||
template<typename T>
|
||||
class IteratableAs {
|
||||
public:
|
||||
class IteratorAs {
|
||||
public:
|
||||
typedef T* value_type;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef T** pointer;
|
||||
typedef T*& reference;
|
||||
typedef std::forward_iterator_tag iterator_category;
|
||||
IteratorAs(const const_iterator& it) : m_it(it) { }
|
||||
bool operator==(const IteratorAs& other) const {
|
||||
return m_it == other.m_it;
|
||||
}
|
||||
bool operator!=(const IteratorAs& other) const {
|
||||
return !operator==(other);
|
||||
}
|
||||
T* operator*() const {
|
||||
return doc::get<T>(*m_it);
|
||||
}
|
||||
IteratorAs& operator++() {
|
||||
++m_it;
|
||||
return *this;
|
||||
}
|
||||
private:
|
||||
const_iterator m_it;
|
||||
};
|
||||
|
||||
typedef IteratorAs iterator;
|
||||
|
||||
iterator begin() const { return m_begin; }
|
||||
iterator end() const { return m_end; }
|
||||
|
||||
IteratableAs(const SelectedObjects& objs)
|
||||
: m_begin(objs.begin())
|
||||
, m_end(objs.end()) { }
|
||||
private:
|
||||
IteratorAs m_begin, m_end;
|
||||
};
|
||||
|
||||
iterator begin() { return m_set.begin(); }
|
||||
iterator end() { return m_set.end(); }
|
||||
const_iterator begin() const { return m_set.begin(); }
|
||||
const_iterator end() const { return m_set.end(); }
|
||||
|
||||
bool empty() const { return m_set.empty(); }
|
||||
size_t size() const { return m_set.size(); }
|
||||
|
||||
void clear() {
|
||||
m_set.clear();
|
||||
}
|
||||
|
||||
void insert(const ObjectId id) {
|
||||
m_set.insert(id);
|
||||
}
|
||||
|
||||
bool contains(const ObjectId id) const {
|
||||
return (m_set.find(id) != end());
|
||||
}
|
||||
|
||||
void erase(const ObjectId id) {
|
||||
auto it = m_set.find(id);
|
||||
if (it != end())
|
||||
m_set.erase(it);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* frontAs() const {
|
||||
ASSERT(!m_set.empty());
|
||||
return doc::get<T>(*m_set.begin());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
IteratableAs<T> iterateAs() const { return IteratableAs<T>(*this); }
|
||||
|
||||
private:
|
||||
Set m_set;
|
||||
};
|
||||
|
||||
} // namespace doc
|
||||
|
||||
#endif // DOC_SELECTED_OBJECTS_H_INCLUDED
|
@ -186,8 +186,10 @@ std::string Entry::selectedText() const
|
||||
|
||||
void Entry::setSuffix(const std::string& suffix)
|
||||
{
|
||||
m_suffix = suffix;
|
||||
invalidate();
|
||||
if (m_suffix != suffix) {
|
||||
m_suffix = suffix;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
void Entry::setTranslateDeadKeys(bool state)
|
||||
|
Loading…
x
Reference in New Issue
Block a user