2024-06-12 09:31:13 +08:00
|
|
|
// Aseprite
|
|
|
|
// Copyright (C) 2022-2024 Igara Studio S.A.
|
|
|
|
//
|
|
|
|
// 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/writing_text_state.h"
|
|
|
|
|
|
|
|
#include "app/app.h"
|
|
|
|
#include "app/color_utils.h"
|
|
|
|
#include "app/commands/command.h"
|
|
|
|
#include "app/extra_cel.h"
|
|
|
|
#include "app/font_info.h"
|
|
|
|
#include "app/pref/preferences.h"
|
|
|
|
#include "app/site.h"
|
|
|
|
#include "app/tx.h"
|
|
|
|
#include "app/ui/context_bar.h"
|
|
|
|
#include "app/ui/editor/editor.h"
|
|
|
|
#include "app/ui/skin/skin_theme.h"
|
|
|
|
#include "app/ui/status_bar.h"
|
|
|
|
#include "app/ui_context.h"
|
|
|
|
#include "app/util/expand_cel_canvas.h"
|
|
|
|
#include "app/util/render_text.h"
|
|
|
|
#include "doc/blend_image.h"
|
|
|
|
#include "doc/blend_internals.h"
|
|
|
|
#include "doc/layer.h"
|
|
|
|
#include "render/dithering.h"
|
|
|
|
#include "render/quantization.h"
|
|
|
|
#include "render/render.h"
|
|
|
|
#include "ui/entry.h"
|
|
|
|
#include "ui/message.h"
|
|
|
|
#include "ui/paint_event.h"
|
2024-08-27 05:21:13 +08:00
|
|
|
#include "ui/system.h"
|
2024-06-12 09:31:13 +08:00
|
|
|
|
2024-06-20 03:41:28 +08:00
|
|
|
#ifdef LAF_SKIA
|
|
|
|
#include "app/util/shader_helpers.h"
|
|
|
|
#include "os/skia/skia_helpers.h"
|
|
|
|
#include "os/skia/skia_surface.h"
|
|
|
|
#endif
|
|
|
|
|
2024-06-12 09:31:13 +08:00
|
|
|
namespace app {
|
|
|
|
|
|
|
|
using namespace ui;
|
|
|
|
|
|
|
|
class WritingTextState::TextEditor : public Entry {
|
|
|
|
public:
|
|
|
|
TextEditor(Editor* editor,
|
|
|
|
const Site& site,
|
|
|
|
const gfx::Rect& bounds)
|
|
|
|
: Entry(4096, "")
|
|
|
|
, m_editor(editor)
|
|
|
|
, m_doc(site.document())
|
|
|
|
, m_extraCel(new ExtraCel) {
|
|
|
|
// We have to draw the editor as background of this ui::Entry.
|
|
|
|
setTransparent(true);
|
|
|
|
|
2024-08-27 05:21:13 +08:00
|
|
|
setPersistSelection(true);
|
|
|
|
|
2024-08-28 03:01:32 +08:00
|
|
|
createExtraCel(site, bounds);
|
2024-06-12 09:31:13 +08:00
|
|
|
renderExtraCelBase();
|
|
|
|
|
|
|
|
FontInfo fontInfo = App::instance()->contextBar()->fontInfo();
|
|
|
|
if (auto font = get_font_from_info(fontInfo))
|
|
|
|
setFont(font);
|
|
|
|
}
|
|
|
|
|
|
|
|
~TextEditor() {
|
|
|
|
m_doc->setExtraCel(ExtraCelRef(nullptr));
|
|
|
|
m_doc->generateMaskBoundaries();
|
|
|
|
}
|
|
|
|
|
2024-06-20 03:41:28 +08:00
|
|
|
// Returns the extra cel with the text rendered (but without the
|
|
|
|
// selected text highlighted).
|
|
|
|
ExtraCelRef extraCel() {
|
|
|
|
renderExtraCelBase();
|
|
|
|
renderExtraCelText(false);
|
|
|
|
return m_extraCel;
|
|
|
|
}
|
2024-06-12 09:31:13 +08:00
|
|
|
|
2024-08-28 01:38:16 +08:00
|
|
|
void setExtraCelBounds(const gfx::Rect& bounds) {
|
2024-08-28 03:01:32 +08:00
|
|
|
if (bounds.w != m_extraCel->image()->width() ||
|
|
|
|
bounds.h != m_extraCel->image()->height()) {
|
|
|
|
createExtraCel(m_editor->getSite(), bounds);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
m_extraCel->cel()->setBounds(bounds);
|
|
|
|
}
|
2024-08-28 01:38:16 +08:00
|
|
|
renderExtraCelBase();
|
|
|
|
renderExtraCelText(true);
|
|
|
|
}
|
|
|
|
|
2024-08-28 03:01:32 +08:00
|
|
|
obs::signal<void(const gfx::Size&)> NewRequiredBounds;
|
|
|
|
|
2024-06-12 09:31:13 +08:00
|
|
|
private:
|
2024-08-28 03:01:32 +08:00
|
|
|
void createExtraCel(const Site& site,
|
|
|
|
const gfx::Rect& bounds) {
|
|
|
|
m_extraCel->create(
|
|
|
|
site.tilemapMode(),
|
|
|
|
site.sprite(),
|
|
|
|
bounds,
|
|
|
|
bounds.size(),
|
|
|
|
site.frame(),
|
|
|
|
255);
|
|
|
|
|
|
|
|
m_extraCel->setType(render::ExtraType::PATCH);
|
|
|
|
m_extraCel->setBlendMode(site.layer()->isImage() ?
|
|
|
|
static_cast<LayerImage*>(site.layer())->blendMode():
|
|
|
|
doc::BlendMode::NORMAL);
|
|
|
|
}
|
|
|
|
|
2024-06-12 09:31:13 +08:00
|
|
|
bool onProcessMessage(Message* msg) override {
|
|
|
|
switch (msg->type()) {
|
|
|
|
case kMouseDownMessage:
|
|
|
|
case kMouseMoveMessage: {
|
|
|
|
auto* mouseMsg = static_cast<MouseMessage*>(msg);
|
|
|
|
// Ignore middle mouse button so we can scroll with it.
|
|
|
|
if (mouseMsg->middle()) {
|
|
|
|
auto* parent = this->parent();
|
|
|
|
MouseMessage mouseMsg2(kMouseDownMessage,
|
|
|
|
*mouseMsg,
|
|
|
|
mouseMsg->position());
|
|
|
|
mouseMsg2.setRecipient(parent);
|
|
|
|
parent->sendMessage(&mouseMsg2);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Entry::onProcessMessage(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
void onInitTheme(InitThemeEvent& ev) override {
|
|
|
|
Entry::onInitTheme(ev);
|
|
|
|
setBgColor(gfx::ColorNone);
|
|
|
|
}
|
|
|
|
|
2024-08-28 03:01:32 +08:00
|
|
|
void onSetText() override {
|
|
|
|
Entry::onSetText();
|
|
|
|
onNewTextBlob();
|
|
|
|
}
|
|
|
|
|
|
|
|
void onSetFont() override {
|
|
|
|
Entry::onSetFont();
|
|
|
|
onNewTextBlob();
|
|
|
|
}
|
|
|
|
|
2024-09-26 03:37:41 +08:00
|
|
|
text::ShaperFeatures onGetTextShaperFeatures() const override {
|
|
|
|
const FontInfo fontInfo = App::instance()->contextBar()->fontInfo();
|
|
|
|
text::ShaperFeatures features;
|
|
|
|
features.ligatures = fontInfo.ligatures();
|
|
|
|
return features;
|
|
|
|
}
|
|
|
|
|
2024-08-28 03:01:32 +08:00
|
|
|
void onNewTextBlob() {
|
|
|
|
text::TextBlobRef blob = textBlob();
|
|
|
|
if (!blob)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Notify that we could make the text editor bigger to show this
|
|
|
|
// text blob.
|
|
|
|
NewRequiredBounds(get_text_blob_required_size(blob));
|
|
|
|
}
|
|
|
|
|
2024-06-12 09:31:13 +08:00
|
|
|
void onPaint(PaintEvent& ev) override {
|
2024-06-20 03:41:28 +08:00
|
|
|
Graphics* g = ev.graphics();
|
|
|
|
|
|
|
|
// Don't paint the base Entry borders
|
|
|
|
//Entry::onPaint(ev);
|
|
|
|
|
2024-06-12 09:31:13 +08:00
|
|
|
if (!hasText())
|
|
|
|
return;
|
|
|
|
|
2024-06-20 03:41:28 +08:00
|
|
|
// Paint border
|
|
|
|
{
|
|
|
|
ui::Paint paint;
|
|
|
|
paint.style(ui::Paint::Stroke);
|
|
|
|
set_checkered_paint_mode(paint, 0,
|
|
|
|
gfx::rgba(0, 0, 0, 255),
|
|
|
|
gfx::rgba(255, 255, 255, 255));
|
|
|
|
g->drawRect(clientBounds(), paint);
|
|
|
|
}
|
|
|
|
|
2024-06-12 09:31:13 +08:00
|
|
|
try {
|
2024-06-20 03:41:28 +08:00
|
|
|
if (!textBlob()) {
|
2024-06-12 09:31:13 +08:00
|
|
|
m_doc->setExtraCel(nullptr);
|
|
|
|
m_editor->invalidate();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-06-20 03:41:28 +08:00
|
|
|
// Render extra cel with text + selected text
|
|
|
|
renderExtraCelBase();
|
|
|
|
renderExtraCelText(true);
|
|
|
|
m_doc->setExtraCel(m_extraCel);
|
2024-06-12 09:31:13 +08:00
|
|
|
|
2024-06-20 03:41:28 +08:00
|
|
|
// Paint caret
|
|
|
|
if (isCaretVisible()) {
|
|
|
|
int scroll, caret;
|
|
|
|
getEntryThemeInfo(&scroll, &caret, nullptr, nullptr);
|
2024-06-12 09:31:13 +08:00
|
|
|
|
2024-06-20 03:41:28 +08:00
|
|
|
gfx::RectF caretBounds = getCharBoxBounds(caret);
|
|
|
|
caretBounds *= gfx::SizeF(scale());
|
|
|
|
caretBounds.w = 1;
|
|
|
|
g->fillRect(gfx::rgba(0, 0, 0), caretBounds);
|
2024-06-12 09:31:13 +08:00
|
|
|
}
|
2024-06-20 03:41:28 +08:00
|
|
|
|
|
|
|
m_editor->invalidate();
|
2024-06-12 09:31:13 +08:00
|
|
|
}
|
|
|
|
catch (const std::exception& e) {
|
|
|
|
StatusBar::instance()->showTip(500, std::string(e.what()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void renderExtraCelBase() {
|
|
|
|
auto extraImg = m_extraCel->image();
|
|
|
|
extraImg->clear(extraImg->maskColor());
|
|
|
|
render::Render().renderLayer(
|
|
|
|
extraImg,
|
|
|
|
m_editor->layer(),
|
|
|
|
m_editor->frame(),
|
|
|
|
gfx::Clip(0, 0, m_extraCel->cel()->bounds()),
|
|
|
|
doc::BlendMode::SRC);
|
|
|
|
}
|
|
|
|
|
2024-06-20 03:41:28 +08:00
|
|
|
void renderExtraCelText(const bool withSelection) {
|
|
|
|
const auto textColor =
|
|
|
|
color_utils::color_for_image(
|
|
|
|
Preferences::instance().colorBar.fgColor(),
|
|
|
|
IMAGE_RGB);
|
|
|
|
|
|
|
|
text::TextBlobRef blob = textBlob();
|
|
|
|
if (!blob)
|
|
|
|
return;
|
|
|
|
|
|
|
|
doc::ImageRef image = render_text_blob(blob, textColor);
|
|
|
|
if (!image)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Invert selected range in the image
|
|
|
|
if (withSelection) {
|
|
|
|
Range range;
|
|
|
|
getEntryThemeInfo(nullptr, nullptr, nullptr, &range);
|
|
|
|
if (!range.isEmpty()) {
|
|
|
|
gfx::RectF selectedBounds =
|
|
|
|
getCharBoxBounds(range.from) |
|
|
|
|
getCharBoxBounds(range.to-1);
|
|
|
|
|
|
|
|
if (!selectedBounds.isEmpty()) {
|
|
|
|
#ifdef LAF_SKIA
|
|
|
|
sk_sp<SkSurface> skSurface = wrap_docimage_in_sksurface(image.get());
|
|
|
|
os::SurfaceRef surface = base::make_ref<os::SkiaSurface>(skSurface);
|
|
|
|
|
|
|
|
os::Paint paint;
|
|
|
|
paint.blendMode(os::BlendMode::Xor);
|
|
|
|
paint.color(textColor);
|
|
|
|
surface->drawRect(selectedBounds, paint);
|
|
|
|
#endif // LAF_SKIA
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
doc::blend_image(
|
|
|
|
m_extraCel->image(), image.get(),
|
|
|
|
gfx::Clip(image->bounds().size()),
|
|
|
|
m_doc->sprite()->palette(m_editor->frame()),
|
|
|
|
255, doc::BlendMode::NORMAL);
|
|
|
|
}
|
|
|
|
|
2024-06-12 09:31:13 +08:00
|
|
|
Editor* m_editor;
|
|
|
|
Doc* m_doc;
|
|
|
|
ExtraCelRef m_extraCel;
|
|
|
|
};
|
|
|
|
|
|
|
|
WritingTextState::WritingTextState(Editor* editor,
|
|
|
|
const gfx::Rect& bounds)
|
2024-08-28 01:38:16 +08:00
|
|
|
: m_delayedMouseMove(this, editor, 5)
|
|
|
|
, m_editor(editor)
|
2024-06-12 09:31:13 +08:00
|
|
|
, m_bounds(bounds)
|
|
|
|
, m_entry(new TextEditor(editor, editor->getSite(), bounds))
|
|
|
|
{
|
|
|
|
m_beforeCmdConn =
|
|
|
|
UIContext::instance()->BeforeCommandExecution.connect(
|
|
|
|
&WritingTextState::onBeforeCommandExecution, this);
|
|
|
|
|
|
|
|
m_fontChangeConn =
|
|
|
|
App::instance()->contextBar()->FontChange.connect(
|
|
|
|
&WritingTextState::onFontChange, this);
|
|
|
|
|
2024-08-28 03:01:32 +08:00
|
|
|
m_entry->NewRequiredBounds.connect([this](const gfx::Size& blobSize) {
|
|
|
|
if (m_bounds.w < blobSize.w ||
|
|
|
|
m_bounds.h < blobSize.h) {
|
|
|
|
m_bounds.w = std::max(m_bounds.w, blobSize.w);
|
|
|
|
m_bounds.h = std::max(m_bounds.h, blobSize.h);
|
|
|
|
m_entry->setExtraCelBounds(m_bounds);
|
|
|
|
m_entry->setBounds(calcEntryBounds());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-06-20 03:41:28 +08:00
|
|
|
onEditorResize(editor);
|
2024-06-12 09:31:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
WritingTextState::~WritingTextState()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WritingTextState::onMouseDown(Editor* editor, MouseMessage* msg)
|
|
|
|
{
|
2024-10-15 00:32:43 +08:00
|
|
|
if (!editor->hasCapture())
|
|
|
|
m_delayedMouseMove.reset();
|
|
|
|
|
2024-08-28 01:38:16 +08:00
|
|
|
m_delayedMouseMove.onMouseDown(msg);
|
|
|
|
m_hit = calcHit(editor, msg->position());
|
|
|
|
|
2024-06-12 09:31:13 +08:00
|
|
|
if (msg->left()) {
|
2024-08-28 01:38:16 +08:00
|
|
|
if (m_hit == Hit::Edges) {
|
|
|
|
m_movingBounds = true;
|
|
|
|
m_cursorStart = editor->screenToEditorF(msg->position());
|
|
|
|
m_boundsOrigin = m_bounds.origin();
|
|
|
|
|
|
|
|
editor->captureMouse();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-10-15 00:32:43 +08:00
|
|
|
// On mouse down with the left button, we just drop the text
|
|
|
|
// directly when we click outside the edges.
|
2024-06-12 09:31:13 +08:00
|
|
|
drop();
|
2024-08-28 01:38:16 +08:00
|
|
|
return true;
|
2024-06-12 09:31:13 +08:00
|
|
|
}
|
|
|
|
else if (msg->right()) {
|
|
|
|
cancel();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return StandbyState::onMouseDown(editor, msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WritingTextState::onMouseUp(Editor* editor, MouseMessage* msg)
|
|
|
|
{
|
2024-08-28 01:38:16 +08:00
|
|
|
m_delayedMouseMove.onMouseUp(msg);
|
|
|
|
|
2024-06-12 09:31:13 +08:00
|
|
|
const bool result = StandbyState::onMouseUp(editor, msg);
|
2024-10-15 00:32:43 +08:00
|
|
|
if (m_movingBounds)
|
2024-08-28 01:38:16 +08:00
|
|
|
m_movingBounds = false;
|
|
|
|
|
2024-10-15 00:32:43 +08:00
|
|
|
// Drop if the user just clicked (so other text box is created)
|
|
|
|
if (m_delayedMouseMove.canInterpretMouseMovementAsJustOneClick()) {
|
|
|
|
drop();
|
2024-06-12 09:31:13 +08:00
|
|
|
}
|
2024-08-28 01:38:16 +08:00
|
|
|
|
2024-06-12 09:31:13 +08:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-08-28 01:38:16 +08:00
|
|
|
bool WritingTextState::onMouseMove(Editor* editor, ui::MouseMessage* msg)
|
2024-06-12 09:31:13 +08:00
|
|
|
{
|
2024-08-28 01:38:16 +08:00
|
|
|
m_delayedMouseMove.onMouseMove(msg);
|
|
|
|
|
|
|
|
// Use StandbyState implementation
|
|
|
|
return StandbyState::onMouseMove(editor, msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WritingTextState::onCommitMouseMove(Editor* editor,
|
|
|
|
const gfx::PointF& spritePos)
|
|
|
|
{
|
|
|
|
if (!m_movingBounds)
|
|
|
|
return;
|
|
|
|
|
|
|
|
gfx::Point delta(spritePos - m_cursorStart);
|
|
|
|
if (delta.x == 0 && delta.y == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_bounds.setOrigin(gfx::Point(delta + m_boundsOrigin));
|
|
|
|
m_entry->setExtraCelBounds(m_bounds);
|
|
|
|
m_entry->setBounds(calcEntryBounds());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WritingTextState::onSetCursor(Editor* editor,
|
|
|
|
const gfx::Point& mouseScreenPos)
|
|
|
|
{
|
|
|
|
if (calcHit(editor, mouseScreenPos) == Hit::Edges) {
|
|
|
|
editor->showMouseCursor(kMoveCursor);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-06-12 09:31:13 +08:00
|
|
|
editor->showMouseCursor(kArrowCursor);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WritingTextState::onKeyDown(Editor*, KeyMessage*)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WritingTextState::onKeyUp(Editor*, KeyMessage* msg)
|
|
|
|
{
|
|
|
|
// Cancel loop pressing Esc key
|
|
|
|
if (msg->scancode() == ui::kKeyEsc) {
|
|
|
|
cancel();
|
|
|
|
}
|
|
|
|
// Drop text pressing Enter key
|
|
|
|
else if (msg->scancode() == ui::kKeyEnter) {
|
|
|
|
drop();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-08-27 05:21:13 +08:00
|
|
|
void WritingTextState::onEditorGotFocus(Editor* editor)
|
|
|
|
{
|
|
|
|
// Focus the entry when we focus the editor, it happens when we
|
|
|
|
// change the font settings, so we keep the focus in the entry
|
|
|
|
// field.
|
|
|
|
if (m_entry)
|
|
|
|
m_entry->requestFocus();
|
|
|
|
}
|
|
|
|
|
2024-06-20 03:41:28 +08:00
|
|
|
void WritingTextState::onEditorResize(Editor* editor)
|
2024-06-12 09:31:13 +08:00
|
|
|
{
|
2024-06-20 03:41:28 +08:00
|
|
|
const gfx::PointF scale(editor->projection().scaleX(),
|
|
|
|
editor->projection().scaleY());
|
|
|
|
m_entry->setScale(scale);
|
2024-06-12 09:31:13 +08:00
|
|
|
m_entry->setBounds(calcEntryBounds());
|
|
|
|
}
|
|
|
|
|
|
|
|
gfx::Rect WritingTextState::calcEntryBounds()
|
|
|
|
{
|
|
|
|
const View* view = View::getView(m_editor);
|
|
|
|
const gfx::Rect vp = view->viewportBounds();
|
|
|
|
const gfx::Point scroll = view->viewScroll();
|
|
|
|
const auto& padding = m_editor->padding();
|
|
|
|
const auto& proj = m_editor->projection();
|
|
|
|
gfx::Point pt1(m_bounds.origin());
|
|
|
|
gfx::Point pt2(m_bounds.point2());
|
|
|
|
pt1.x = vp.x - scroll.x + padding.x + proj.applyX(pt1.x);
|
|
|
|
pt1.y = vp.y - scroll.y + padding.y + proj.applyY(pt1.y);
|
|
|
|
pt2.x = vp.x - scroll.x + padding.x + proj.applyX(pt2.x);
|
|
|
|
pt2.y = vp.y - scroll.y + padding.y + proj.applyY(pt2.y);
|
|
|
|
return gfx::Rect(pt1, pt2);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WritingTextState::onEnterState(Editor* editor)
|
|
|
|
{
|
|
|
|
StandbyState::onEnterState(editor);
|
|
|
|
|
|
|
|
editor->invalidate();
|
|
|
|
|
|
|
|
editor->addChild(m_entry.get());
|
|
|
|
m_entry->requestFocus();
|
|
|
|
}
|
|
|
|
|
|
|
|
EditorState::LeaveAction WritingTextState::onLeaveState(Editor* editor, EditorState* newState)
|
|
|
|
{
|
|
|
|
if (!newState || !newState->isTemporalState()) {
|
|
|
|
if (!m_discarded) {
|
|
|
|
// Paints the text in the active layer/sprite creating an
|
|
|
|
// undoable transaction.
|
|
|
|
Site site = m_editor->getSite();
|
|
|
|
ExtraCelRef extraCel = m_entry->extraCel();
|
|
|
|
Tx tx(site.document(), "Text Tool");
|
|
|
|
ExpandCelCanvas expand(
|
|
|
|
site, site.layer(),
|
|
|
|
TiledMode::NONE, tx,
|
|
|
|
ExpandCelCanvas::None);
|
|
|
|
|
|
|
|
expand.validateDestCanvas(
|
|
|
|
gfx::Region(extraCel->cel()->bounds()));
|
|
|
|
|
2024-10-11 03:48:51 +08:00
|
|
|
expand.getDestCanvas()->copy(
|
2024-06-12 09:31:13 +08:00
|
|
|
extraCel->image(),
|
|
|
|
gfx::Clip(extraCel->cel()->position(),
|
2024-10-11 03:48:51 +08:00
|
|
|
extraCel->image()->bounds()));
|
2024-06-12 09:31:13 +08:00
|
|
|
|
|
|
|
expand.commit();
|
|
|
|
tx.commit();
|
|
|
|
}
|
|
|
|
m_editor->releaseMouse();
|
|
|
|
m_editor->document()->notifyGeneralUpdate();
|
|
|
|
return DiscardState;
|
|
|
|
}
|
|
|
|
|
|
|
|
editor->releaseMouse();
|
|
|
|
return KeepState;
|
|
|
|
}
|
|
|
|
|
|
|
|
void WritingTextState::onBeforePopState(Editor* editor)
|
|
|
|
{
|
|
|
|
editor->removeChild(m_entry.get());
|
|
|
|
m_beforeCmdConn.disconnect();
|
|
|
|
m_fontChangeConn.disconnect();
|
|
|
|
|
|
|
|
StandbyState::onBeforePopState(editor);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WritingTextState::onBeforeCommandExecution(CommandExecutionEvent& ev)
|
|
|
|
{
|
|
|
|
if (// Undo/Redo/Cancel will cancel this state
|
|
|
|
ev.command()->id() == CommandId::Undo() ||
|
|
|
|
ev.command()->id() == CommandId::Redo() ||
|
|
|
|
ev.command()->id() == CommandId::Cancel()) {
|
|
|
|
cancel();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
drop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-23 01:32:33 +08:00
|
|
|
void WritingTextState::onFontChange(const FontInfo& fontInfo,
|
|
|
|
FontEntry::From fromField)
|
2024-06-12 09:31:13 +08:00
|
|
|
{
|
|
|
|
if (auto font = get_font_from_info(fontInfo)) {
|
|
|
|
m_entry->setFont(font);
|
|
|
|
m_entry->invalidate();
|
|
|
|
m_editor->invalidate();
|
2024-08-27 04:20:05 +08:00
|
|
|
|
|
|
|
// This is useful to show changes to the anti-alias option
|
|
|
|
// immediately.
|
|
|
|
auto dummy = m_entry->extraCel();
|
2024-08-27 05:21:13 +08:00
|
|
|
|
2024-10-23 01:32:33 +08:00
|
|
|
if (fromField == FontEntry::From::Popup) {
|
2024-08-27 05:21:13 +08:00
|
|
|
if (m_entry)
|
|
|
|
m_entry->requestFocus();
|
2024-10-23 01:32:33 +08:00
|
|
|
}
|
2024-06-12 09:31:13 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void WritingTextState::cancel()
|
|
|
|
{
|
|
|
|
m_discarded = true;
|
|
|
|
|
|
|
|
m_editor->backToPreviousState();
|
|
|
|
m_editor->invalidate();
|
|
|
|
}
|
|
|
|
|
|
|
|
void WritingTextState::drop()
|
|
|
|
{
|
|
|
|
m_editor->backToPreviousState();
|
|
|
|
m_editor->invalidate();
|
|
|
|
}
|
|
|
|
|
2024-08-28 01:38:16 +08:00
|
|
|
WritingTextState::Hit WritingTextState::calcHit(Editor* editor,
|
|
|
|
const gfx::Point& mouseScreenPos)
|
|
|
|
{
|
|
|
|
auto edges = editor->editorToScreen(m_bounds);
|
|
|
|
if (!edges.contains(mouseScreenPos) &&
|
|
|
|
edges.enlarge(32*guiscale()).contains(mouseScreenPos)) {
|
|
|
|
return Hit::Edges;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Hit::Normal;
|
|
|
|
}
|
|
|
|
|
2024-06-12 09:31:13 +08:00
|
|
|
} // namespace app
|