void RotateCommand::onExecute(Context* context) { ContextReader reader(context); { CelList cels; bool rotateSprite = false; // Flip the mask or current cel if (m_flipMask) { DocumentRange range = App::instance()->getMainWindow()->getTimeline()->range(); if (range.enabled()) cels = get_unique_cels(reader.sprite(), range); else if (reader.cel()) cels.push_back(reader.cel()); } // Flip the whole sprite else if (reader.sprite()) { for (Cel* cel : reader.sprite()->uniqueCels()) cels.push_back(cel); rotateSprite = true; } if (cels.empty()) // Nothing to do return; RotateJob job(reader, m_angle, cels, rotateSprite); job.startJob(); job.waitJob(); } reader.document()->generateMaskBoundaries(); update_screen_for_document(reader.document()); }
void StatusBar::onCelOpacityChange() { try { ContextWriter writer(UIContext::instance(), 500); DocumentRange range = App::instance()->getMainWindow()->getTimeline()->range(); if (range.enabled()) { for (Cel* cel : get_unique_cels(writer.sprite(), range)) cel->setOpacity(m_slider->getValue()); } else { Cel* cel = writer.cel(); if (cel) { // Update the opacity cel->setOpacity(m_slider->getValue()); } } // Update the editors update_screen_for_document(writer.document()); } catch (LockedDocumentException&) { // do nothing } }
bool ReverseFramesCommand::onEnabled(Context* context) { DocumentRange range = App::instance()->getMainWindow()->getTimeline()->range(); return context->checkFlags(ContextFlags::ActiveDocumentIsWritable) && range.enabled() && range.frames() >= 2; // We need at least 2 frames to reverse }
void ReverseFramesCommand::onExecute(Context* context) { DocumentRange range = App::instance()->getMainWindow()->getTimeline()->range(); if (!range.enabled()) return; // Nothing to do Document* doc = context->activeDocument(); reverse_frames(doc, range); update_screen_for_document(doc); }
void reverse_frames(Document* doc, const DocumentRange& range) { const app::Context* context = static_cast<app::Context*>(doc->context()); const ContextReader reader(context); ContextWriter writer(reader); Transaction transaction(writer.context(), "Reverse Frames"); DocumentApi api = doc->getApi(transaction); Sprite* sprite = doc->sprite(); frame_t frameBegin, frameEnd; int layerBegin, layerEnd; bool moveFrames = false; switch (range.type()) { case DocumentRange::kCels: frameBegin = range.frameBegin(); frameEnd = range.frameEnd(); layerBegin = range.layerBegin(); layerEnd = range.layerEnd() + 1; break; case DocumentRange::kFrames: frameBegin = range.frameBegin(); frameEnd = range.frameEnd(); moveFrames = true; break; case DocumentRange::kLayers: frameBegin = frame_t(0); frameEnd = sprite->totalFrames()-1; layerBegin = range.layerBegin(); layerEnd = range.layerEnd() + 1; break; } if (moveFrames) { for (frame_t frameRev = frameEnd+1; frameRev > frameBegin; --frameRev) { api.moveFrame(sprite, frameBegin, frameRev); } } else { std::vector<Layer*> layers; sprite->getLayersList(layers); for (int layerIdx = layerBegin; layerIdx != layerEnd; ++layerIdx) { for (frame_t frame = frameBegin, frameRev = frameEnd; frame != (frameBegin+frameEnd)/2+1; ++frame, --frameRev) { LayerImage* layer = static_cast<LayerImage*>(layers[layerIdx]); api.swapCel(layer, frame, frameRev); } } } transaction.commit(); }
void onCommitChange() { base::ScopedValue<bool> switchSelf(m_selfUpdate, true, false); m_timer.stop(); const int newOpacity = opacityValue(); const int count = countCels(); if ((count > 1) || (count == 1 && m_cel && (newOpacity != m_cel->opacity() || m_userData != m_cel->data()->userData()))) { try { ContextWriter writer(UIContext::instance()); Transaction transaction(writer.context(), "Set Cel Properties"); DocumentRange range; if (m_range.enabled()) range = m_range; else { range.startRange(m_cel->layer(), m_cel->frame(), DocumentRange::kCels); range.endRange(m_cel->layer(), m_cel->frame()); } Sprite* sprite = m_document->sprite(); for (Cel* cel : sprite->uniqueCels(range.selectedFrames())) { if (range.contains(cel->layer())) { if (!cel->layer()->isBackground() && newOpacity != cel->opacity()) { transaction.execute(new cmd::SetCelOpacity(cel, newOpacity)); } if (m_newUserData && m_userData != cel->data()->userData()) { transaction.execute(new cmd::SetUserData(cel->data(), m_userData)); // Redraw timeline because the cel's user data/color // might have changed. App::instance()->timeline()->invalidate(); } } } transaction.commit(); } catch (const std::exception& e) { Console::showException(e); } update_screen_for_document(m_document); } }
void CopyCommand::onExecute(Context* context) { const ContextReader reader(context); // Copy a range from the timeline. DocumentRange range = App::instance()->getMainWindow()->getTimeline()->range(); if (range.enabled()) { clipboard::copy_range(reader, range); } else if (reader.site()->document() && static_cast<const app::Document*>(reader.site()->document())->isMaskVisible() && reader.site()->image()) { clipboard::copy(reader); } }
Status CommandReplyBuilder::addOutputDocs(DocumentRange outputDocs) { invariant(_state == State::kOutputDocs); auto rangeData = outputDocs.data(); auto dataSize = rangeData.length(); _builder.appendBuf(rangeData.data(), dataSize); return Status::OK(); }
void ClearCommand::onExecute(Context* context) { // Clear of several frames is handled with ClearCel command. DocumentRange range = App::instance()->getMainWindow()->getTimeline()->range(); if (range.enabled()) { Command* subCommand = NULL; switch (range.type()) { case DocumentRange::kCels: subCommand = CommandsModule::instance()->getCommandByName(CommandId::ClearCel); break; case DocumentRange::kFrames: subCommand = CommandsModule::instance()->getCommandByName(CommandId::RemoveFrame); break; case DocumentRange::kLayers: subCommand = CommandsModule::instance()->getCommandByName(CommandId::RemoveLayer); break; } if (subCommand) { context->executeCommand(subCommand); return; } } // TODO add support to clear the mask in the selected range of frames. ContextWriter writer(context); Document* document = writer.document(); bool visibleMask = document->isMaskVisible(); if (!writer.cel()) return; { Transaction transaction(writer.context(), "Clear"); transaction.execute(new cmd::ClearMask(writer.cel())); if (visibleMask) transaction.execute(new cmd::DeselectMask(document)); transaction.commit(); } if (visibleMask) document->generateMaskBoundaries(); update_screen_for_document(document); }
Status CommandReplyBuilder::addOutputDocs(DocumentRange outputDocs) { invariant(_state == State::kOutputDocs); auto rangeData = outputDocs.data(); auto dataSize = rangeData.length(); auto hasSpace = _hasSpaceFor(dataSize); if (!hasSpace.isOK()) { return hasSpace; } _builder.appendBuf(rangeData.data(), dataSize); return Status::OK(); }
// TODO the DocumentRange should be "iteratable" to replace this function CelList get_cels_in_range(Sprite* sprite, const DocumentRange& range) { CelList cels; for (LayerIndex layerIdx = range.layerBegin(); layerIdx <= range.layerEnd(); ++layerIdx) { Layer* layer = sprite->indexToLayer(layerIdx); if (!layer->isImage()) continue; LayerImage* layerImage = static_cast<LayerImage*>(layer); for (FrameNumber frame = range.frameEnd(), begin = range.frameBegin().previous(); frame != begin; frame = frame.previous()) { Cel* cel = layerImage->getCel(frame); if (cel) cels.push_back(cel); } } return cels; }
Status LegacyReplyBuilder::addOutputDocs(DocumentRange docs) { invariant(_state == State::kOutputDocs); invariant(_allowAddingOutputDocs); auto dataSize = docs.data().length(); auto hasSpace = _hasSpaceFor(dataSize); if (!hasSpace.isOK()) { return hasSpace; } // The temporary obj is used to address the case when where is not enough space. // BSONArray overhead can not be estimated upfront. std::vector<BSONObj> docsTmp{}; std::size_t lenTmp = 0; std::size_t tmpIndex(_currentIndex); for (auto&& it : docs) { docsTmp.emplace_back(it.getOwned()); lenTmp += BSONObjBuilder::numStr(++tmpIndex).length() + 2; // space for storing array index } hasSpace = _hasSpaceFor(dataSize + lenTmp); if (!hasSpace.isOK()) { return hasSpace; } // vector::insert instead of swap allows to call addOutputDoc(s) multiple times _outputDocs.insert(_outputDocs.end(), std::make_move_iterator(docsTmp.begin()), std::make_move_iterator(docsTmp.end())); _currentIndex = tmpIndex; _currentLength += lenTmp; _currentLength += dataSize; return Status::OK(); }
bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg) { if (editor->hasCapture()) return true; UIContext* context = UIContext::instance(); tools::Ink* clickedInk = editor->getCurrentEditorInk(); Site site; editor->getSite(&site); app::Document* document = static_cast<app::Document*>(site.document()); Layer* layer = site.layer(); // When an editor is clicked the current view is changed. context->setActiveView(editor->getDocumentView()); // Start scroll loop if (checkForScroll(editor, msg) || checkForZoom(editor, msg)) return true; // Move cel X,Y coordinates if (clickedInk->isCelMovement()) { // Handle "Auto Select Layer" if (editor->isAutoSelectLayer()) { gfx::Point cursor = editor->screenToEditor(msg->position()); ColorPicker picker; picker.pickColor(site, cursor, ColorPicker::FromComposition); DocumentRange range = App::instance()->getMainWindow()->getTimeline()->range(); // Change layer only when the layer is diffrent from current one, and // the range we selected is not with multiple cels. bool layerChanged = (layer != picker.layer()); bool rangeEnabled = range.enabled(); bool rangeSingleCel = ((range.type() == DocumentRange::kCels) && (range.layers() == 1) && (range.frames() == 1)); if (layerChanged && (!rangeEnabled || rangeSingleCel)) { layer = picker.layer(); if (layer) { editor->setLayer(layer); editor->flashCurrentLayer(); } } } if ((layer) && (layer->type() == ObjectType::LayerImage)) { // TODO we should be able to move the `Background' with tiled mode if (layer->isBackground()) { StatusBar::instance()->showTip(1000, "The background layer cannot be moved"); } else if (!layer->isVisible()) { StatusBar::instance()->showTip(1000, "Layer '%s' is hidden", layer->name().c_str()); } else if (!layer->isMovable() || !layer->isEditable()) { StatusBar::instance()->showTip(1000, "Layer '%s' is locked", layer->name().c_str()); } else if (!layer->cel(editor->frame())) { StatusBar::instance()->showTip(1000, "Cel is empty, nothing to move"); } else { // Change to MovingCelState editor->setState(EditorStatePtr(new MovingCelState(editor, msg))); } } return true; } // Call the eyedropper command if (clickedInk->isEyedropper()) { editor->captureMouse(); callEyedropper(editor); return true; } if (clickedInk->isSelection()) { // Transform selected pixels if (editor->isActive() && document->isMaskVisible() && m_decorator->getTransformHandles(editor)) { TransformHandles* transfHandles = m_decorator->getTransformHandles(editor); // Get the handle covered by the mouse. HandleType handle = transfHandles->getHandleAtPoint(editor, msg->position(), document->getTransformation()); if (handle != NoHandle) { int x, y, opacity; Image* image = site.image(&x, &y, &opacity); if (layer && image) { if (!layer->isEditable()) { StatusBar::instance()->showTip(1000, "Layer '%s' is locked", layer->name().c_str()); return true; } // Change to MovingPixelsState transformSelection(editor, msg, handle); } return true; } } // Move selected pixels if (layer && editor->isInsideSelection() && msg->left()) { if (!layer->isEditable()) { StatusBar::instance()->showTip(1000, "Layer '%s' is locked", layer->name().c_str()); return true; } // Change to MovingPixelsState transformSelection(editor, msg, MoveHandle); return true; } } // Move symmetry gfx::Rect box1, box2; if (m_decorator->getSymmetryHandles(editor, box1, box2) && (box1.contains(msg->position()) || box2.contains(msg->position()))) { auto& symmetry = Preferences::instance().document(editor->document()).symmetry; auto mode = symmetry.mode(); bool horz = (mode == app::gen::SymmetryMode::HORIZONTAL); auto& axis = (horz ? symmetry.xAxis: symmetry.yAxis); editor->setState( EditorStatePtr(new MovingSymmetryState(editor, msg, mode, axis))); return true; } // Start the Tool-Loop if (layer) { tools::ToolLoop* toolLoop = create_tool_loop(editor, context); if (toolLoop) { EditorStatePtr newState(new DrawingState(toolLoop)); editor->setState(newState); static_cast<DrawingState*>(newState.get()) ->initToolLoop(editor, msg); } return true; } return true; }
static DocumentRange drop_range_op( Document* doc, Op op, const DocumentRange& from, DocumentRangePlace place, const DocumentRange& to) { if (place != kDocumentRangeBefore && place != kDocumentRangeAfter) { ASSERT(false); throw std::invalid_argument("Invalid 'place' argument"); } Sprite* sprite = doc->sprite(); // Check noop/trivial/do nothing cases, i.e., move a range to the same place. // Also check invalid cases, like moving a Background layer. switch (from.type()) { case DocumentRange::kCels: if (from == to) return from; break; case DocumentRange::kFrames: if (op == Move) { if ((to.frameBegin() >= from.frameBegin() && to.frameEnd() <= from.frameEnd()) || (place == kDocumentRangeBefore && to.frameBegin() == from.frameEnd()+1) || (place == kDocumentRangeAfter && to.frameEnd() == from.frameBegin()-1)) return from; } break; case DocumentRange::kLayers: if (op == Move) { if ((to.layerBegin() >= from.layerBegin() && to.layerEnd() <= from.layerEnd()) || (place == kDocumentRangeBefore && to.layerBegin() == from.layerEnd()+1) || (place == kDocumentRangeAfter && to.layerEnd() == from.layerBegin()-1)) return from; // We cannot move the background for (LayerIndex i = from.layerBegin(); i <= from.layerEnd(); ++i) if (sprite->indexToLayer(i)->isBackground()) throw std::runtime_error("The background layer cannot be moved"); // Before background if (place == kDocumentRangeBefore) { Layer* background = sprite->indexToLayer(to.layerBegin()); if (background && background->isBackground()) throw std::runtime_error("You cannot move something below the background layer"); } } break; } const char* undoLabel = NULL; switch (op) { case Move: undoLabel = "Move Range"; break; case Copy: undoLabel = "Copy Range"; break; default: ASSERT(false); throw std::invalid_argument("Invalid 'op' argument"); } DocumentRange resultRange; { const app::Context* context = static_cast<app::Context*>(doc->context()); const ContextReader reader(context); ContextWriter writer(reader); Transaction transaction(writer.context(), undoLabel, ModifyDocument); DocumentApi api = doc->getApi(transaction); // TODO Try to add the range with just one call to DocumentApi // methods, to avoid generating a lot of SetCelFrame undoers (see // DocumentApi::setCelFramePosition). switch (from.type()) { case DocumentRange::kCels: { std::vector<Layer*> layers; sprite->getLayersList(layers); int srcLayerBegin, srcLayerStep, srcLayerEnd; int dstLayerBegin, dstLayerStep; frame_t srcFrameBegin, srcFrameStep, srcFrameEnd; frame_t dstFrameBegin, dstFrameStep; if (to.layerBegin() <= from.layerBegin()) { srcLayerBegin = from.layerBegin(); srcLayerStep = 1; srcLayerEnd = from.layerEnd()+1; dstLayerBegin = to.layerBegin(); dstLayerStep = 1; } else { srcLayerBegin = from.layerEnd(); srcLayerStep = -1; srcLayerEnd = from.layerBegin()-1; dstLayerBegin = to.layerEnd(); dstLayerStep = -1; } if (to.frameBegin() <= from.frameBegin()) { srcFrameBegin = from.frameBegin(); srcFrameStep = frame_t(1); srcFrameEnd = from.frameEnd()+1; dstFrameBegin = to.frameBegin(); dstFrameStep = frame_t(1); } else { srcFrameBegin = from.frameEnd(); srcFrameStep = frame_t(-1); srcFrameEnd = from.frameBegin()-1; dstFrameBegin = to.frameEnd(); dstFrameStep = frame_t(-1); } for (int srcLayerIdx = srcLayerBegin, dstLayerIdx = dstLayerBegin; srcLayerIdx != srcLayerEnd; ) { for (frame_t srcFrame = srcFrameBegin, dstFrame = dstFrameBegin; srcFrame != srcFrameEnd; ) { LayerImage* srcLayer = static_cast<LayerImage*>(layers[srcLayerIdx]); LayerImage* dstLayer = static_cast<LayerImage*>(layers[dstLayerIdx]); switch (op) { case Move: api.moveCel(srcLayer, srcFrame, dstLayer, dstFrame); break; case Copy: api.copyCel(srcLayer, srcFrame, dstLayer, dstFrame); break; } srcFrame += srcFrameStep; dstFrame += dstFrameStep; } srcLayerIdx += srcLayerStep; dstLayerIdx += dstLayerStep; } resultRange = to; } break; case DocumentRange::kFrames: { frame_t srcFrameBegin = 0, srcFrameStep, srcFrameEnd = 0; frame_t dstFrameBegin = 0, dstFrameStep; switch (op) { case Move: if (place == kDocumentRangeBefore) { if (to.frameBegin() <= from.frameBegin()) { srcFrameBegin = from.frameBegin(); srcFrameStep = frame_t(1); srcFrameEnd = from.frameEnd()+1; dstFrameBegin = to.frameBegin(); dstFrameStep = frame_t(1); } else { srcFrameBegin = from.frameEnd(); srcFrameStep = frame_t(-1); srcFrameEnd = from.frameBegin()-1; dstFrameBegin = to.frameBegin(); dstFrameStep = frame_t(-1); } } else if (place == kDocumentRangeAfter) { if (to.frameEnd() <= from.frameBegin()) { srcFrameBegin = from.frameBegin(); srcFrameStep = frame_t(1); srcFrameEnd = from.frameEnd()+1; dstFrameBegin = to.frameEnd()+1; dstFrameStep = frame_t(1); } else { srcFrameBegin = from.frameEnd(); srcFrameStep = frame_t(-1); srcFrameEnd = from.frameBegin()-1; dstFrameBegin = to.frameEnd()+1; dstFrameStep = frame_t(-1); } } break; case Copy: if (place == kDocumentRangeBefore) { if (to.frameBegin() <= from.frameBegin()) { srcFrameBegin = from.frameBegin(); srcFrameStep = frame_t(2); srcFrameEnd = from.frameBegin() + 2*from.frames(); dstFrameBegin = to.frameBegin(); dstFrameStep = frame_t(1); } else { srcFrameBegin = from.frameEnd(); srcFrameStep = frame_t(-1); srcFrameEnd = from.frameBegin()-1; dstFrameBegin = to.frameBegin(); dstFrameStep = frame_t(0); } } else if (place == kDocumentRangeAfter) { if (to.frameEnd() <= from.frameBegin()) { srcFrameBegin = from.frameBegin(); srcFrameStep = frame_t(2); srcFrameEnd = from.frameBegin() + 2*from.frames(); dstFrameBegin = to.frameEnd()+1; dstFrameStep = frame_t(1); } else { srcFrameBegin = from.frameEnd(); srcFrameStep = frame_t(-1); srcFrameEnd = from.frameBegin()-1; dstFrameBegin = to.frameEnd()+1; dstFrameStep = frame_t(0); } } break; } for (frame_t srcFrame = srcFrameBegin, dstFrame = dstFrameBegin; srcFrame != srcFrameEnd; ) { switch (op) { case Move: api.moveFrame(sprite, srcFrame, dstFrame); break; case Copy: api.copyFrame(sprite, srcFrame, dstFrame); break; } srcFrame += srcFrameStep; dstFrame += dstFrameStep; } if (place == kDocumentRangeBefore) { resultRange.startRange(LayerIndex::NoLayer, frame_t(to.frameBegin()), from.type()); resultRange.endRange(LayerIndex::NoLayer, frame_t(to.frameBegin()+from.frames()-1)); } else if (place == kDocumentRangeAfter) { resultRange.startRange(LayerIndex::NoLayer, frame_t(to.frameEnd()+1), from.type()); resultRange.endRange(LayerIndex::NoLayer, frame_t(to.frameEnd()+1+from.frames()-1)); } if (op == Move && from.frameBegin() < to.frameBegin()) resultRange.displace(0, -from.frames()); } break; case DocumentRange::kLayers: { std::vector<Layer*> layers; sprite->getLayersList(layers); if (layers.empty()) break; switch (op) { case Move: if (place == kDocumentRangeBefore) { for (LayerIndex i = from.layerBegin(); i <= from.layerEnd(); ++i) { api.restackLayerBefore( layers[i], layers[to.layerBegin()]); } } else if (place == kDocumentRangeAfter) { for (LayerIndex i = from.layerEnd(); i >= from.layerBegin(); --i) { api.restackLayerAfter( layers[i], layers[to.layerEnd()]); } } break; case Copy: if (place == kDocumentRangeBefore) { for (LayerIndex i = from.layerBegin(); i <= from.layerEnd(); ++i) { api.duplicateLayerBefore( layers[i], layers[to.layerBegin()]); } } else if (place == kDocumentRangeAfter) { for (LayerIndex i = from.layerEnd(); i >= from.layerBegin(); --i) { api.duplicateLayerAfter( layers[i], layers[to.layerEnd()]); } } break; } if (place == kDocumentRangeBefore) { resultRange.startRange(LayerIndex(to.layerBegin()), frame_t(-1), from.type()); resultRange.endRange(LayerIndex(to.layerBegin()+from.layers()-1), frame_t(-1)); } else if (place == kDocumentRangeAfter) { resultRange.startRange(LayerIndex(to.layerEnd()+1), frame_t(-1), from.type()); resultRange.endRange(LayerIndex(to.layerEnd()+1+from.layers()-1), frame_t(-1)); } if (op == Move && from.layerBegin() < to.layerBegin()) resultRange.displace(-from.layers(), 0); } break; } transaction.commit(); } return resultRange; }
inline DocumentRange range(int fromLayer, frame_t fromFrNum, int toLayer, frame_t toFrNum, DocumentRange::Type type) { DocumentRange r; r.startRange(LayerIndex(fromLayer), fromFrNum, type); r.endRange(LayerIndex(toLayer), toFrNum); return r; }
inline DocumentRange range(Layer* fromLayer, frame_t fromFrNum, Layer* toLayer, frame_t toFrNum, DocumentRange::Type type) { DocumentRange r; r.startRange(fromLayer->sprite()->layerToIndex(fromLayer), fromFrNum, type); r.endRange(toLayer->sprite()->layerToIndex(toLayer), toFrNum); return r; }