Merge branch 'main' into beta

This commit is contained in:
David Capello 2022-05-23 18:04:55 -03:00
commit 684d06ede8
27 changed files with 576 additions and 222 deletions

View File

@ -1236,10 +1236,12 @@ keep_selection_after_clear_tooltip = <<<END
Check this if you want to keep the selection Check this if you want to keep the selection
after deleting it. after deleting it.
END END
auto_show_selection_edges = Automatically show selection edges after modifying the selection auto_show_selection_edges = Show selection edges automatically when the selection is modified
auto_show_selection_edges_tooltip = <<<END auto_show_selection_edges_tooltip = <<<END
Uncheck this if you want to keep the When checked, the View > Show > Selection Edges option will be enabled
selection edges option as you wish. each time we modify the selection. Uncheck this in case that you want
to keep selection edges hidden when View > Show > Selection Edges
option is disabled, e.g. to avoid visual noise or performance issues.
END END
move_edges = Allow moving selection edges move_edges = Allow moving selection edges
move_edges_tooltip = <<<END move_edges_tooltip = <<<END

View File

@ -1,9 +1,8 @@
<!-- Aseprite --> <!-- Aseprite -->
<!-- Copyright (C) 2022 by Igara Studio S.A. -->
<!-- Copyright (C) 2015-2016 by David Capello --> <!-- Copyright (C) 2015-2016 by David Capello -->
<gui> <gui>
<window id="undo_history" text="@.title"> <window id="undo_history" text="@.title">
<view id="view" expansive="true" width="80" height="100"> <view id="view" expansive="true" width="80" height="100" />
<listbox id="actions" />
</view>
</window> </window>
</gui> </gui>

View File

@ -657,8 +657,7 @@ private:
if (m_headerItem.parent() == listbox) if (m_headerItem.parent() == listbox)
listbox->removeChild(&m_headerItem); listbox->removeChild(&m_headerItem);
while (listbox->lastChild()) { while (auto item = listbox->lastChild()) {
Widget* item = listbox->lastChild();
listbox->removeChild(item); listbox->removeChild(item);
delete item; delete item;
} }

View File

@ -1279,8 +1279,8 @@ private:
} }
void reloadThemes() { void reloadThemes() {
while (themeList()->firstChild()) while (auto child = themeList()->lastChild())
delete themeList()->lastChild(); delete child;
loadThemes(); loadThemes();
} }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2020 Igara Studio S.A. // Copyright (C) 2020-2022 Igara Studio S.A.
// Copyright (C) 2015-2018 David Capello // Copyright (C) 2015-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -15,52 +15,292 @@
#include "app/context.h" #include "app/context.h"
#include "app/context_observer.h" #include "app/context_observer.h"
#include "app/doc.h" #include "app/doc.h"
#include "app/doc_access.h"
#include "app/doc_undo.h" #include "app/doc_undo.h"
#include "app/doc_undo_observer.h" #include "app/doc_undo_observer.h"
#include "app/docs_observer.h" #include "app/docs_observer.h"
#include "app/doc_access.h"
#include "app/modules/gui.h" #include "app/modules/gui.h"
#include "app/modules/palettes.h" #include "app/modules/palettes.h"
#include "app/site.h" #include "app/site.h"
#include "app/ui/skin/skin_theme.h"
#include "base/mem_utils.h" #include "base/mem_utils.h"
#include "fmt/format.h"
#include "ui/init_theme_event.h"
#include "ui/listitem.h" #include "ui/listitem.h"
#include "ui/message.h" #include "ui/message.h"
#include "ui/paint_event.h"
#include "ui/size_hint_event.h"
#include "ui/view.h"
#include "undo/undo_state.h" #include "undo/undo_state.h"
#include "undo_history.xml.h" #include "undo_history.xml.h"
namespace app { namespace app {
using namespace app::skin;
class UndoHistoryWindow : public app::gen::UndoHistory, class UndoHistoryWindow : public app::gen::UndoHistory,
public ContextObserver, public ContextObserver,
public DocsObserver, public DocsObserver,
public DocUndoObserver { public DocUndoObserver {
public: public:
class Item : public ui::ListItem { class ActionsList final : public ui::Widget {
public: public:
Item(const undo::UndoState* state) ActionsList(UndoHistoryWindow* window)
: ui::ListItem( : m_window(window) {
(state ? setFocusStop(true);
static_cast<Cmd*>(state->cmd())->label() initTheme();
#if _DEBUG
+ std::string(" ") + base::get_pretty_memory_size(static_cast<Cmd*>(state->cmd())->memSize())
#endif
: std::string("Initial State"))),
m_state(state) {
} }
const undo::UndoState* state() { return m_state; }
void setUndoHistory(DocUndo* history) {
m_undoHistory = history;
invalidate();
}
void selectState(const undo::UndoState* state) {
auto view = ui::View::getView(this);
if (!view)
return;
invalidate();
gfx::Point scroll = view->viewScroll();
if (state) {
const gfx::Rect vp = view->viewportBounds();
const gfx::Rect bounds = this->bounds();
gfx::Rect itemBounds(bounds.x, bounds.y, bounds.w, m_itemHeight);
// Jump first "Initial State"
itemBounds.y += itemBounds.h;
const undo::UndoState* s = m_undoHistory->firstState();
while (s) {
if (s == state)
break;
itemBounds.y += itemBounds.h;
s = s->next();
}
if (itemBounds.y < vp.y)
scroll.y = itemBounds.y - bounds.y;
else if (itemBounds.y > vp.y + vp.h - itemBounds.h)
scroll.y = (itemBounds.y - bounds.y - vp.h + itemBounds.h);
}
else {
scroll = gfx::Point(0, 0);
}
view->setViewScroll(scroll);
}
obs::signal<void(const undo::UndoState*)> Change;
protected:
void onInitTheme(ui::InitThemeEvent& ev) override {
Widget::onInitTheme(ev);
auto theme = SkinTheme::get(this);
m_itemHeight =
textHeight() +
theme->calcBorder(this, theme->styles.listItem()).height();
}
bool onProcessMessage(ui::Message* msg) override {
switch (msg->type()) {
case ui::kMouseDownMessage:
captureMouse();
case ui::kMouseMoveMessage:
if (hasCapture()) {
auto mouseMsg = static_cast<ui::MouseMessage*>(msg);
const gfx::Rect bounds = this->bounds();
// Mouse position in client coordinates
const gfx::Point mousePos = mouseMsg->position();
gfx::Rect itemBounds(bounds.x, bounds.y, bounds.w, m_itemHeight);
// First state
if (itemBounds.contains(mousePos)) {
Change(nullptr);
break;
}
itemBounds.y += itemBounds.h;
const undo::UndoState* state = m_undoHistory->firstState();
while (state) {
if (itemBounds.contains(mousePos)) {
Change(state);
break;
}
itemBounds.y += itemBounds.h;
state = state->next();
}
}
break;
case ui::kMouseUpMessage:
releaseMouse();
break;
case ui::kMouseWheelMessage: {
auto view = ui::View::getView(this);
if (view) {
auto mouseMsg = static_cast<ui::MouseMessage*>(msg);
gfx::Point scroll = view->viewScroll();
if (mouseMsg->preciseWheel())
scroll += mouseMsg->wheelDelta();
else
scroll += mouseMsg->wheelDelta() * 3*(m_itemHeight+4*ui::guiscale());
view->setViewScroll(scroll);
}
break;
}
case ui::kKeyDownMessage:
if (hasFocus() && m_undoHistory) {
const undo::UndoState* current = m_undoHistory->currentState();
const undo::UndoState* select = current;
auto view = ui::View::getView(this);
const gfx::Rect vp = view->viewportBounds();
ui::KeyMessage* keymsg = static_cast<ui::KeyMessage*>(msg);
ui::KeyScancode scancode = keymsg->scancode();
if (keymsg->onlyCmdPressed()) {
if (scancode == ui::kKeyUp) scancode = ui::kKeyHome;
if (scancode == ui::kKeyDown) scancode = ui::kKeyEnd;
}
switch (scancode) {
case ui::kKeyUp:
if (select)
select = select->prev();
else
select = m_undoHistory->lastState();
break;
case ui::kKeyDown:
if (select)
select = select->next();
else
select = m_undoHistory->firstState();
break;
case ui::kKeyHome:
select = nullptr;
break;
case ui::kKeyEnd:
select = m_undoHistory->lastState();
break;
case ui::kKeyPageUp:
for (int i=0; select && i<vp.h / m_itemHeight; ++i)
select = select->prev();
break;
case ui::kKeyPageDown: {
int i = 0;
if (!select) {
select = m_undoHistory->firstState();
i = 1;
}
for (; select && i<vp.h / m_itemHeight; ++i)
select = select->next();
break;
}
default:
return Widget::onProcessMessage(msg);
}
if (select != current)
Change(select);
return true;
}
break;
}
return Widget::onProcessMessage(msg);
}
void onPaint(ui::PaintEvent& ev) override {
ui::Graphics* g = ev.graphics();
auto theme = SkinTheme::get(this);
gfx::Rect bounds = clientBounds();
g->fillRect(theme->colors.background(), bounds);
if (!m_undoHistory)
return;
const undo::UndoState* currentState = m_undoHistory->currentState();
gfx::Rect itemBounds(bounds.x, bounds.y, bounds.w, m_itemHeight);
// First state
{
const bool selected = (currentState == nullptr);
paintItem(g, theme, nullptr, itemBounds, selected);
itemBounds.y += itemBounds.h;
}
const undo::UndoState* state = m_undoHistory->firstState();
while (state) {
const bool selected = (state == currentState);
paintItem(g, theme, state, itemBounds, selected);
itemBounds.y += itemBounds.h;
state = state->next();
}
}
void onSizeHint(ui::SizeHintEvent& ev) override {
if (m_window->m_nitems == 0) {
int size = 0;
if (m_undoHistory) {
++size;
const undo::UndoState* state = m_undoHistory->firstState();
while (state) {
++size;
state = state->next();
}
}
m_window->m_nitems = size;
}
ev.setSizeHint(gfx::Size(1, m_itemHeight * m_window->m_nitems));
}
private: private:
const undo::UndoState* m_state; void paintItem(ui::Graphics* g,
SkinTheme* theme,
const undo::UndoState* state,
const gfx::Rect& itemBounds,
const bool selected) {
const std::string itemText =
(state ? static_cast<Cmd*>(state->cmd())->label()
#if _DEBUG
+ std::string(" ") + base::get_pretty_memory_size(static_cast<Cmd*>(state->cmd())->memSize())
#endif
: std::string("Initial State"));
if ((g->getClipBounds() & itemBounds).isEmpty())
return;
auto style = theme->styles.listItem();
ui::PaintWidgetPartInfo info;
info.text = &itemText;
info.styleFlags = (selected ? ui::Style::Layer::kSelected: 0);
theme->paintWidgetPart(g, style, itemBounds, info);
}
UndoHistoryWindow* m_window;
DocUndo* m_undoHistory = nullptr;
int m_itemHeight;
}; };
UndoHistoryWindow(Context* ctx) UndoHistoryWindow(Context* ctx)
: m_ctx(ctx) : m_ctx(ctx)
, m_document(nullptr) { , m_doc(nullptr)
, m_actions(this) {
m_title = text(); m_title = text();
actions()->Change.connect(&UndoHistoryWindow::onChangeAction, this); m_actions.Change.connect(&UndoHistoryWindow::onChangeAction, this);
} view()->attachToView(&m_actions);
~UndoHistoryWindow() {
} }
private: private:
@ -77,12 +317,14 @@ private:
attachDocument(m_ctx->activeDocument()); attachDocument(m_ctx->activeDocument());
} }
view()->invalidate();
break; break;
case ui::kCloseMessage: case ui::kCloseMessage:
save_window_pos(this, "UndoHistory"); save_window_pos(this, "UndoHistory");
if (m_document) if (m_doc)
detachDocument(); detachDocument();
m_ctx->documents().remove_observer(this); m_ctx->documents().remove_observer(this);
m_ctx->remove_observer(this); m_ctx->remove_observer(this);
@ -91,25 +333,21 @@ private:
return app::gen::UndoHistory::onProcessMessage(msg); return app::gen::UndoHistory::onProcessMessage(msg);
} }
void onChangeAction() { void onChangeAction(const undo::UndoState* state) {
Item* item = static_cast<Item*>( if (m_doc && m_doc->undoHistory()->currentState() != state) {
actions()->getSelectedChild());
if (m_document &&
m_document->undoHistory()->currentState() != item->state()) {
try { try {
DocWriter writer(m_document, 100); DocWriter writer(m_doc, 100);
m_document->undoHistory()->moveToState(item->state()); m_doc->undoHistory()->moveToState(state);
m_document->generateMaskBoundaries(); m_doc->generateMaskBoundaries();
// TODO this should be an observer of the current document palette // TODO this should be an observer of the current document palette
set_current_palette(m_document->sprite()->palette(m_frame), set_current_palette(m_doc->sprite()->palette(m_frame), false);
false);
m_document->notifyGeneralUpdate(); m_doc->notifyGeneralUpdate();
m_actions.invalidate();
} }
catch (const std::exception& ex) { catch (const std::exception& ex) {
selectState(m_document->undoHistory()->currentState()); selectState(m_doc->undoHistory()->currentState());
Console::showException(ex); Console::showException(ex);
} }
} }
@ -119,7 +357,7 @@ private:
void onActiveSiteChange(const Site& site) override { void onActiveSiteChange(const Site& site) override {
m_frame = site.frame(); m_frame = site.frame();
if (m_document == site.document()) if (m_doc == site.document())
return; return;
attachDocument(const_cast<Doc*>(site.document())); attachDocument(const_cast<Doc*>(site.document()));
@ -127,33 +365,25 @@ private:
// DocsObserver // DocsObserver
void onRemoveDocument(Doc* doc) override { void onRemoveDocument(Doc* doc) override {
if (m_document && m_document == doc) if (m_doc && m_doc == doc)
detachDocument(); detachDocument();
} }
// DocUndoObserver // DocUndoObserver
void onAddUndoState(DocUndo* history) override { void onAddUndoState(DocUndo* history) override {
ASSERT(history->currentState()); ASSERT(history->currentState());
Item* item = new Item(history->currentState());
actions()->addChild(item); ++m_nitems;
actions()->layout();
m_actions.invalidate();
view()->updateView(); view()->updateView();
actions()->selectChild(item);
selectState(history->currentState());
} }
void onDeleteUndoState(DocUndo* history, void onDeleteUndoState(DocUndo* history,
undo::UndoState* state) override { undo::UndoState* state) override {
for (auto child : actions()->children()) { --m_nitems;
Item* item = static_cast<Item*>(child);
if (item->state() == state) {
actions()->removeChild(item);
item->deferDelete();
break;
}
}
actions()->layout();
view()->updateView();
} }
void onCurrentUndoStateChange(DocUndo* history) override { void onCurrentUndoStateChange(DocUndo* history) override {
@ -161,92 +391,71 @@ private:
} }
void onClearRedo(DocUndo* history) override { void onClearRedo(DocUndo* history) override {
refillList(history); setUndoHistory(history);
} }
void onTotalUndoSizeChange(DocUndo* history) override { void onTotalUndoSizeChange(DocUndo* history) override {
updateTitle(); updateTitle();
} }
void attachDocument(Doc* document) { void attachDocument(Doc* doc) {
detachDocument(); if (m_doc == doc)
m_document = document;
if (!document)
return; return;
DocUndo* history = m_document->undoHistory(); detachDocument();
m_doc = doc;
if (!doc)
return;
DocUndo* history = m_doc->undoHistory();
history->add_observer(this); history->add_observer(this);
refillList(history); setUndoHistory(history);
updateTitle(); updateTitle();
} }
void detachDocument() { void detachDocument() {
if (!m_document) if (!m_doc)
return; return;
clearList(); m_doc->undoHistory()->remove_observer(this);
m_document->undoHistory()->remove_observer(this); m_doc = nullptr;
m_document = nullptr;
setUndoHistory(nullptr);
updateTitle(); updateTitle();
} }
void clearList() { void setUndoHistory(DocUndo* history) {
ui::Widget* child; m_nitems = 0;
while ((child = actions()->firstChild())) m_actions.setUndoHistory(history);
delete child;
actions()->layout();
view()->updateView(); view()->updateView();
}
void refillList(DocUndo* history) { if (history)
clearList(); m_actions.selectState(history->currentState());
// Create an item to reference the initial state (undo state == nullptr)
Item* current = new Item(nullptr);
actions()->addChild(current);
const undo::UndoState* state = history->firstState();
while (state) {
Item* item = new Item(state);
actions()->addChild(item);
if (state == history->currentState())
current = item;
state = state->next();
}
actions()->layout();
view()->updateView();
if (current)
actions()->selectChild(current);
} }
void selectState(const undo::UndoState* state) { void selectState(const undo::UndoState* state) {
for (auto child : actions()->children()) { m_actions.selectState(state);
Item* item = static_cast<Item*>(child);
if (item->state() == state) {
actions()->selectChild(item);
break;
}
}
} }
void updateTitle() { void updateTitle() {
if (!m_document) if (!m_doc)
setText(m_title); setText(m_title);
else else {
setTextf("%s (%s)", setText(
m_title.c_str(), fmt::format(
base::get_pretty_memory_size(m_document->undoHistory()->totalUndoSize()).c_str()); "{} ({})",
m_title,
base::get_pretty_memory_size(m_doc->undoHistory()->totalUndoSize())));
}
} }
Context* m_ctx; Context* m_ctx;
Doc* m_document; Doc* m_doc;
doc::frame_t m_frame; doc::frame_t m_frame;
std::string m_title; std::string m_title;
ActionsList m_actions;
int m_nitems = 0;
}; };
class UndoHistoryCommand : public Command { class UndoHistoryCommand : public Command {

View File

@ -310,7 +310,7 @@ public:
} }
void clear() { void clear() {
while (auto item = firstChild()) { while (auto item = lastChild()) {
removeChild(item); removeChild(item);
item->deferDelete(); item->deferDelete();
} }
@ -730,7 +730,7 @@ private:
} }
void clearLocals() { void clearLocals() {
while (auto item = locals()->firstChild()) { while (auto item = locals()->lastChild()) {
locals()->removeChild(item); locals()->removeChild(item);
item->deferDelete(); item->deferDelete();
} }

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2022 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -131,6 +132,10 @@ void DocUndo::redo()
void DocUndo::clearRedo() void DocUndo::clearRedo()
{ {
// Do nothing
if (currentState() == lastState())
return;
m_undoHistory.clearRedo(); m_undoHistory.clearRedo();
notify_observers(&DocUndoObserver::onClearRedo, this); notify_observers(&DocUndoObserver::onClearRedo, this);
} }

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2022 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -59,7 +60,8 @@ namespace app {
int* savedCounter() { return &m_savedCounter; } int* savedCounter() { return &m_savedCounter; }
const undo::UndoState* firstState() const { return m_undoHistory.firstState(); } const undo::UndoState* firstState() const { return m_undoHistory.firstState(); }
const undo::UndoState* lastState() const { return m_undoHistory.lastState(); }
const undo::UndoState* currentState() const { return m_undoHistory.currentState(); } const undo::UndoState* currentState() const { return m_undoHistory.currentState(); }
void moveToState(const undo::UndoState* state); void moveToState(const undo::UndoState* state);

View File

@ -29,9 +29,9 @@ namespace {
int Cel_eq(lua_State* L) int Cel_eq(lua_State* L)
{ {
const auto a = get_docobj<Cel>(L, 1); const auto a = may_get_docobj<Cel>(L, 1);
const auto b = get_docobj<Cel>(L, 2); const auto b = may_get_docobj<Cel>(L, 2);
lua_pushboolean(L, a->id() == b->id()); lua_pushboolean(L, (!a && !b) || (a && b && a->id() == b->id()));
return 1; return 1;
} }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A. // Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2018 David Capello // Copyright (C) 2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -33,9 +33,9 @@ namespace {
int Layer_eq(lua_State* L) int Layer_eq(lua_State* L)
{ {
const auto a = get_docobj<Layer>(L, 1); const auto a = may_get_docobj<Layer>(L, 1);
const auto b = get_docobj<Layer>(L, 2); const auto b = may_get_docobj<Layer>(L, 2);
lua_pushboolean(L, a->id() == b->id()); lua_pushboolean(L, (!a && !b) || (a && b && a->id() == b->id()));
return 1; return 1;
} }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018 Igara Studio S.A. // Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2018 David Capello // Copyright (C) 2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -28,9 +28,9 @@ namespace {
int Slice_eq(lua_State* L) int Slice_eq(lua_State* L)
{ {
const auto a = get_docobj<Slice>(L, 1); const auto a = may_get_docobj<Slice>(L, 1);
const auto b = get_docobj<Slice>(L, 2); const auto b = may_get_docobj<Slice>(L, 2);
lua_pushboolean(L, a->id() == b->id()); lua_pushboolean(L, (!a && !b) || (a && b && a->id() == b->id()));
return 1; return 1;
} }

View File

@ -140,9 +140,9 @@ int Sprite_new(lua_State* L)
int Sprite_eq(lua_State* L) int Sprite_eq(lua_State* L)
{ {
const auto a = get_docobj<Sprite>(L, 1); const auto a = may_get_docobj<Sprite>(L, 1);
const auto b = get_docobj<Sprite>(L, 2); const auto b = may_get_docobj<Sprite>(L, 2);
lua_pushboolean(L, a->id() == b->id()); lua_pushboolean(L, (!a && !b) || (a && b && a->id() == b->id()));
return 1; return 1;
} }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2020 Igara Studio S.A. // Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2018 David Capello // Copyright (C) 2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -30,9 +30,9 @@ namespace {
int Tag_eq(lua_State* L) int Tag_eq(lua_State* L)
{ {
const auto a = get_docobj<Tag>(L, 1); const auto a = may_get_docobj<Tag>(L, 1);
const auto b = get_docobj<Tag>(L, 2); const auto b = may_get_docobj<Tag>(L, 2);
lua_pushboolean(L, a->id() == b->id()); lua_pushboolean(L, (!a && !b) || (a && b && a->id() == b->id()));
return 1; return 1;
} }

View File

@ -273,8 +273,8 @@ private:
void clear() { void clear() {
// Delete all children // Delete all children
while (firstChild()) while (auto child = lastChild())
delete firstChild(); delete child;
} }
void processNode(cmark_node* root, void processNode(cmark_node* root,

View File

@ -204,8 +204,8 @@ void NewsListBox::reload()
if (m_loader || m_timer.isRunning()) if (m_loader || m_timer.isRunning())
return; return;
while (lastChild()) while (auto child = lastChild())
removeChild(lastChild()); removeChild(child);
View* view = View::getView(this); View* view = View::getView(this);
if (view) if (view)

View File

@ -242,8 +242,7 @@ RecentListBox::RecentListBox()
void RecentListBox::rebuildList() void RecentListBox::rebuildList()
{ {
while (lastChild()) { while (auto child = lastChild()) {
auto child = lastChild();
removeChild(child); removeChild(child);
child->deferDelete(); child->deferDelete();
} }

View File

@ -1,5 +1,5 @@
// Aseprite Document Library // Aseprite Document Library
// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello // Copyright (C) 2001-2016 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -69,6 +69,10 @@ namespace doc {
return *m_boundsF; return *m_boundsF;
} }
bool hasBoundsF() const {
return m_boundsF != nullptr;
}
virtual int getMemSize() const override { virtual int getMemSize() const override {
ASSERT(m_image); ASSERT(m_image);
return sizeof(CelData) + m_image->getMemSize(); return sizeof(CelData) + m_image->getMemSize();

View File

@ -1,4 +1,5 @@
// Aseprite Document Library // Aseprite Document Library
// Copyright (c) 2022 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello // Copyright (c) 2001-2018 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -14,12 +15,15 @@
#include "doc/cel_data.h" #include "doc/cel_data.h"
#include "doc/subobjects_io.h" #include "doc/subobjects_io.h"
#include "doc/user_data_io.h" #include "doc/user_data_io.h"
#include "fixmath/fixmath.h"
#include <iostream> #include <iostream>
#include <memory> #include <memory>
namespace doc { namespace doc {
#define HAS_BOUNDS_F 1
using namespace base::serialization; using namespace base::serialization;
using namespace base::serialization::little_endian; using namespace base::serialization::little_endian;
@ -33,6 +37,17 @@ void write_celdata(std::ostream& os, const CelData* celdata)
write8(os, celdata->opacity()); write8(os, celdata->opacity());
write32(os, celdata->image()->id()); write32(os, celdata->image()->id());
write_user_data(os, celdata->userData()); write_user_data(os, celdata->userData());
if (celdata->hasBoundsF()) { // Reference layer
write32(os, HAS_BOUNDS_F);
write32(os, fixmath::ftofix(celdata->boundsF().x));
write32(os, fixmath::ftofix(celdata->boundsF().y));
write32(os, fixmath::ftofix(celdata->boundsF().w));
write32(os, fixmath::ftofix(celdata->boundsF().h));
}
else {
write32(os, 0);
}
} }
CelData* read_celdata(std::istream& is, SubObjectsIO* subObjects, bool setId) CelData* read_celdata(std::istream& is, SubObjectsIO* subObjects, bool setId)
@ -45,6 +60,22 @@ CelData* read_celdata(std::istream& is, SubObjectsIO* subObjects, bool setId)
int opacity = read8(is); int opacity = read8(is);
ObjectId imageId = read32(is); ObjectId imageId = read32(is);
UserData userData = read_user_data(is); UserData userData = read_user_data(is);
gfx::RectF boundsF;
// Extra fields
int flags = read32(is);
if (flags & HAS_BOUNDS_F) {
fixmath::fixed x = read32(is);
fixmath::fixed y = read32(is);
fixmath::fixed w = read32(is);
fixmath::fixed h = read32(is);
if (w && h) {
boundsF = gfx::RectF(fixmath::fixtof(x),
fixmath::fixtof(y),
fixmath::fixtof(w),
fixmath::fixtof(h));
}
}
ImageRef image(subObjects->getImageRef(imageId)); ImageRef image(subObjects->getImageRef(imageId));
if (!image) if (!image)
@ -56,6 +87,8 @@ CelData* read_celdata(std::istream& is, SubObjectsIO* subObjects, bool setId)
celdata->setUserData(userData); celdata->setUserData(userData);
if (setId) if (setId)
celdata->setId(id); celdata->setId(id);
if (!boundsF.isEmpty())
celdata->setBoundsF(boundsF);
return celdata.release(); return celdata.release();
} }

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // Aseprite UI Library
// Copyright (C) 2018-2021 Igara Studio S.A. // Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -203,8 +203,12 @@ void ComboBox::deleteItem(int itemIndex)
void ComboBox::deleteAllItems() void ComboBox::deleteAllItems()
{ {
for (Widget* item : m_items) // Delete all items back to front, in this way Widget::removeChild()
delete item; // widget // doesn't have to use linear search to update m_parentIndex of all
// other children.
auto end = m_items.rend();
for (auto it=m_items.rbegin(); it != end; ++it)
delete *it; // widget
m_items.clear(); m_items.clear();
m_selected = -1; m_selected = -1;

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // Aseprite UI Library
// Copyright (C) 2020-2021 Igara Studio S.A. // Copyright (C) 2020-2022 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -15,32 +15,31 @@
#include "ui/widget.h" #include "ui/widget.h"
#include "ui/window.h" #include "ui/window.h"
#include <list> #include <memory>
#include <set>
namespace ui { namespace ui {
namespace details { namespace details {
static std::list<Widget*>* widgets; static std::unique_ptr<std::set<Widget*>> widgets;
void initWidgets() void initWidgets()
{ {
assert_ui_thread(); assert_ui_thread();
widgets = std::make_unique<std::set<Widget*>>();
widgets = new std::list<Widget*>;
} }
void exitWidgets() void exitWidgets()
{ {
assert_ui_thread(); assert_ui_thread();
widgets.reset();
delete widgets;
} }
void addWidget(Widget* widget) void addWidget(Widget* widget)
{ {
assert_ui_thread(); assert_ui_thread();
widgets->push_back(widget); widgets->insert(widget);
} }
void removeWidget(Widget* widget) void removeWidget(Widget* widget)
@ -49,11 +48,12 @@ void removeWidget(Widget* widget)
ASSERT(!Manager::widgetAssociatedToManager(widget)); ASSERT(!Manager::widgetAssociatedToManager(widget));
auto it = std::find(widgets->begin(), widgets->end(), widget); widgets->erase(widget);
if (it != widgets->end())
widgets->erase(it);
} }
// TODO we should be able to re-initialize all widgets without using
// this global "widgets" set, so we don't have to keep track of
// all widgets globally
void reinitThemeForAllWidgets() void reinitThemeForAllWidgets()
{ {
assert_ui_thread(); assert_ui_thread();

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // Aseprite UI Library
// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -71,25 +71,6 @@ int ListBox::getSelectedIndex()
return -1; return -1;
} }
int ListBox::getChildIndex(Widget* item)
{
const WidgetsList& children = this->children();
auto it = std::find(children.begin(), children.end(), item);
if (it != children.end())
return it - children.begin();
else
return -1;
}
Widget* ListBox::getChildByIndex(int index)
{
const WidgetsList& children = this->children();
if (index >= 0 && index < int(children.size()))
return children[index];
else
return nullptr;
}
void ListBox::selectChild(Widget* item, Message* msg) void ListBox::selectChild(Widget* item, Message* msg)
{ {
bool didChange = false; bool didChange = false;
@ -153,7 +134,10 @@ void ListBox::selectChild(Widget* item, Message* msg)
void ListBox::selectIndex(int index, Message* msg) void ListBox::selectIndex(int index, Message* msg)
{ {
Widget* child = getChildByIndex(index); if (index < 0 || index >= int(children().size()))
return;
Widget* child = at(index);
if (child) if (child)
selectChild(child, msg); selectChild(child, msg);
} }
@ -460,8 +444,8 @@ int ListBox::advanceIndexThroughVisibleItems(
index = 0-sgn; index = 0-sgn;
cycle = true; cycle = true;
} }
else { else if (index >= 0 && index < children().size()) {
Widget* item = getChildByIndex(index); Widget* item = at(index);
if (item && if (item &&
!item->hasFlags(HIDDEN) && !item->hasFlags(HIDDEN) &&
// We can completely ignore separators from navigation // We can completely ignore separators from navigation

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // Aseprite UI Library
// Copyright (C) 2020 Igara Studio S.A. // Copyright (C) 2020-2022 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -49,9 +49,6 @@ namespace ui {
virtual void onChange(); virtual void onChange();
virtual void onDoubleClickItem(); virtual void onDoubleClickItem();
int getChildIndex(Widget* item);
Widget* getChildByIndex(int index);
int advanceIndexThroughVisibleItems( int advanceIndexThroughVisibleItems(
int startIndex, int delta, const bool loop); int startIndex, int delta, const bool loop);

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // Aseprite UI Library
// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -60,8 +60,16 @@ void ListItem::onSizeHint(SizeHintEvent& ev)
int w = 0, h = 0; int w = 0, h = 0;
Size maxSize; Size maxSize;
if (hasText()) if (hasText()) {
maxSize = textSize(); if (m_textLength >= 0) {
maxSize.w = m_textLength;
maxSize.h = textHeight();
}
else {
maxSize = textSize();
m_textLength = maxSize.w;
}
}
else else
maxSize.w = maxSize.h = 0; maxSize.w = maxSize.h = 0;
@ -78,4 +86,16 @@ void ListItem::onSizeHint(SizeHintEvent& ev)
ev.setSizeHint(Size(w, h)); ev.setSizeHint(Size(w, h));
} }
void ListItem::onInitTheme(InitThemeEvent& ev)
{
Widget::onInitTheme(ev);
m_textLength = -1;
}
void ListItem::onSetText()
{
Widget::onSetText();
m_textLength = -1;
}
} // namespace ui } // namespace ui

View File

@ -1,4 +1,5 @@
// Aseprite UI Library // Aseprite UI Library
// Copyright (C) 2022 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -26,9 +27,12 @@ namespace ui {
bool onProcessMessage(Message* msg) override; bool onProcessMessage(Message* msg) override;
void onResize(ResizeEvent& ev) override; void onResize(ResizeEvent& ev) override;
void onSizeHint(SizeHintEvent& ev) override; void onSizeHint(SizeHintEvent& ev) override;
void onInitTheme(InitThemeEvent& ev) override;
void onSetText() override;
private: private:
std::string m_value; std::string m_value;
int m_textLength = -1;
}; };
} // namespace ui } // namespace ui

View File

@ -66,6 +66,7 @@ Widget::Widget(WidgetType type)
, m_bgColor(gfx::ColorNone) , m_bgColor(gfx::ColorNone)
, m_bounds(0, 0, 0, 0) , m_bounds(0, 0, 0, 0)
, m_parent(nullptr) , m_parent(nullptr)
, m_parentIndex(-1)
, m_sizeHint(nullptr) , m_sizeHint(nullptr)
, m_mnemonic(0) , m_mnemonic(0)
, m_minSize(0, 0) , m_minSize(0, 0)
@ -432,11 +433,17 @@ Display* Widget::display() const
int Widget::getChildIndex(Widget* child) int Widget::getChildIndex(Widget* child)
{ {
auto it = std::find(m_children.begin(), m_children.end(), child); if (!child)
if (it != m_children.end())
return it - m_children.begin();
else
return -1; return -1;
#ifdef _DEBUG
ASSERT(child->parent() == this);
auto it = std::find(m_children.begin(), m_children.end(), child);
ASSERT(it != m_children.end());
ASSERT(child->parentIndex() == (it - m_children.begin()));
#endif
return child->parentIndex();
} }
Widget* Widget::nextSibling() Widget* Widget::nextSibling()
@ -446,12 +453,9 @@ Widget* Widget::nextSibling()
if (!m_parent) if (!m_parent)
return nullptr; return nullptr;
WidgetsList::iterator begin = m_parent->m_children.begin(); auto begin = m_parent->m_children.begin();
WidgetsList::iterator end = m_parent->m_children.end(); auto end = m_parent->m_children.end();
WidgetsList::iterator it = std::find(begin, end, this); auto it = begin + m_parentIndex;
if (it == end)
return nullptr;
if (++it == end) if (++it == end)
return nullptr; return nullptr;
@ -466,11 +470,10 @@ Widget* Widget::previousSibling()
if (!m_parent) if (!m_parent)
return nullptr; return nullptr;
WidgetsList::iterator begin = m_parent->m_children.begin(); auto begin = m_parent->m_children.begin();
WidgetsList::iterator end = m_parent->m_children.end(); auto it = begin + m_parentIndex;
WidgetsList::iterator it = std::find(begin, end, this);
if (it == begin || it == end) if (it == begin)
return nullptr; return nullptr;
return *(--it); return *(--it);
@ -553,18 +556,23 @@ void Widget::addChild(Widget* child)
if (child->m_parent) if (child->m_parent)
child->m_parent->removeChild(child); child->m_parent->removeChild(child);
int i = int(m_children.size());
m_children.push_back(child); m_children.push_back(child);
child->m_parent = this; child->m_parent = this;
child->m_parentIndex = i;
} }
void Widget::removeChild(WidgetsList::iterator& it) void Widget::removeChild(const WidgetsList::iterator& it)
{ {
Widget* child = nullptr; Widget* child = nullptr;
ASSERT(it != m_children.end()); ASSERT(it != m_children.end());
if (it != m_children.end()) { if (it != m_children.end()) {
child = *it; child = *it;
m_children.erase(it);
auto it2 = m_children.erase(it);
for (auto end=m_children.end(); it2!=end; ++it2)
--(*it2)->m_parentIndex;
ASSERT(child); ASSERT(child);
if (!child) if (!child)
@ -578,17 +586,16 @@ void Widget::removeChild(WidgetsList::iterator& it)
man->freeWidget(child); man->freeWidget(child);
child->m_parent = nullptr; child->m_parent = nullptr;
child->m_parentIndex = -1;
} }
void Widget::removeChild(Widget* child) void Widget::removeChild(Widget* child)
{ {
ASSERT_VALID_WIDGET(this); ASSERT_VALID_WIDGET(this);
ASSERT_VALID_WIDGET(child); ASSERT_VALID_WIDGET(child);
ASSERT(child->parent() == this);
auto it = std::find(m_children.begin(), m_children.end(), child); if (child->parent() == this)
ASSERT(it != m_children.end()); removeChild(m_children.begin() + child->m_parentIndex);
if (it != m_children.end())
removeChild(it);
} }
void Widget::removeAllChildren() void Widget::removeAllChildren()
@ -602,18 +609,29 @@ void Widget::replaceChild(Widget* oldChild, Widget* newChild)
ASSERT_VALID_WIDGET(oldChild); ASSERT_VALID_WIDGET(oldChild);
ASSERT_VALID_WIDGET(newChild); ASSERT_VALID_WIDGET(newChild);
WidgetsList::iterator before = #if _DEBUG
std::find(m_children.begin(), m_children.end(), oldChild); {
if (before == m_children.end()) { auto it = std::find(m_children.begin(), m_children.end(), oldChild);
ASSERT(it != m_children.end());
ASSERT(oldChild->m_parentIndex == (it - m_children.begin()));
}
#endif
if (oldChild->parent() != this) {
ASSERT(false); ASSERT(false);
return; return;
} }
int index = before - m_children.begin(); int index = oldChild->m_parentIndex;
removeChild(oldChild); removeChild(oldChild);
m_children.insert(m_children.begin()+index, newChild); auto it = m_children.begin() + index;
it = m_children.insert(it, newChild);
for (auto end=m_children.end(); it!=end; ++it)
++(*it)->m_parentIndex;
newChild->m_parent = this; newChild->m_parent = this;
newChild->m_parentIndex = index;
} }
void Widget::insertChild(int index, Widget* child) void Widget::insertChild(int index, Widget* child)
@ -621,21 +639,37 @@ void Widget::insertChild(int index, Widget* child)
ASSERT_VALID_WIDGET(this); ASSERT_VALID_WIDGET(this);
ASSERT_VALID_WIDGET(child); ASSERT_VALID_WIDGET(child);
m_children.insert(m_children.begin()+index, child); index = base::clamp(index, 0, int(m_children.size()));
auto it = m_children.begin() + index;
it = m_children.insert(it, child);
++it;
for (auto end=m_children.end(); it!=end; ++it)
++(*it)->m_parentIndex;
child->m_parent = this; child->m_parent = this;
child->m_parentIndex = index;
} }
void Widget::moveChildTo(Widget* thisChild, Widget* toThisPosition) void Widget::moveChildTo(Widget* thisChild, Widget* toThisPosition)
{ {
auto itA = std::find(m_children.begin(), m_children.end(), thisChild); ASSERT(thisChild->parent() == this);
auto itB = std::find(m_children.begin(), m_children.end(), toThisPosition); ASSERT(toThisPosition->parent() == this);
if (itA == m_children.end()) {
ASSERT(false); const int from = thisChild->m_parentIndex;
return; const int to = toThisPosition->m_parentIndex;
}
int index = itB - m_children.begin(); auto it = m_children.begin() + from;
m_children.erase(itA); it = m_children.erase(it);
m_children.insert(m_children.begin() + index, thisChild); auto end = m_children.end();
for (; it!=end; ++it)
--(*it)->m_parentIndex;
it = m_children.begin() + to;
it = m_children.insert(it, thisChild);
thisChild->m_parentIndex = to;
for (++it, end=m_children.end(); it!=end; ++it)
++(*it)->m_parentIndex;
} }
// =============================================================== // ===============================================================

View File

@ -161,6 +161,7 @@ namespace ui {
Window* window() const; Window* window() const;
Widget* parent() const { return m_parent; } Widget* parent() const { return m_parent; }
int parentIndex() const { return m_parentIndex; }
Manager* manager() const; Manager* manager() const;
Display* display() const; Display* display() const;
@ -413,7 +414,7 @@ namespace ui {
virtual double onGetTextDouble() const; virtual double onGetTextDouble() const;
private: private:
void removeChild(WidgetsList::iterator& it); void removeChild(const WidgetsList::iterator& it);
void paint(Graphics* graphics, void paint(Graphics* graphics,
const gfx::Region& drawRegion, const gfx::Region& drawRegion,
const bool isBg); const bool isBg);
@ -433,6 +434,7 @@ namespace ui {
gfx::Region m_updateRegion; // Region to be redrawed. gfx::Region m_updateRegion; // Region to be redrawed.
WidgetsList m_children; // Sub-widgets WidgetsList m_children; // Sub-widgets
Widget* m_parent; // Who is the parent? Widget* m_parent; // Who is the parent?
int m_parentIndex; // Location/index of this widget in the parent's Widget::m_children vector
gfx::Size* m_sizeHint; gfx::Size* m_sizeHint;
int m_mnemonic; // Keyboard shortcut to access this widget like Alt+mnemonic int m_mnemonic; // Keyboard shortcut to access this widget like Alt+mnemonic

View File

@ -0,0 +1,57 @@
// Aseprite UI Library
// Copyright (C) 2022 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#define TEST_GUI
#include "tests/app_test.h"
using namespace ui;
TEST(Widget, ParentIndex)
{
Widget a, b, c, d, e;
EXPECT_EQ(-1, b.parentIndex());
a.addChild(&b);
a.addChild(&c);
a.addChild(&d);
EXPECT_EQ(0, b.parentIndex());
EXPECT_EQ(1, c.parentIndex());
EXPECT_EQ(2, d.parentIndex());
a.removeChild(&c);
EXPECT_EQ(0, b.parentIndex());
EXPECT_EQ(1, d.parentIndex());
EXPECT_EQ(-1, c.parentIndex());
a.replaceChild(&b, &c);
EXPECT_EQ(0, c.parentIndex());
EXPECT_EQ(1, d.parentIndex());
EXPECT_EQ(-1, b.parentIndex());
a.insertChild(1, &e);
EXPECT_EQ(0, c.parentIndex());
EXPECT_EQ(1, e.parentIndex());
EXPECT_EQ(2, d.parentIndex());
EXPECT_EQ(-1, b.parentIndex());
a.insertChild(10, &b);
EXPECT_EQ(0, c.parentIndex());
EXPECT_EQ(1, e.parentIndex());
EXPECT_EQ(2, d.parentIndex());
EXPECT_EQ(3, b.parentIndex());
a.moveChildTo(&c, &b);
EXPECT_EQ(0, e.parentIndex());
EXPECT_EQ(1, d.parentIndex());
EXPECT_EQ(2, b.parentIndex());
EXPECT_EQ(3, c.parentIndex());
a.moveChildTo(&b, &e);
EXPECT_EQ(0, b.parentIndex());
EXPECT_EQ(1, e.parentIndex());
EXPECT_EQ(2, d.parentIndex());
EXPECT_EQ(3, c.parentIndex());
}