mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-07 10:21:30 +00:00
Merge branch 'main' into beta
This commit is contained in:
commit
684d06ede8
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -1279,8 +1279,8 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void reloadThemes() {
|
void reloadThemes() {
|
||||||
while (themeList()->firstChild())
|
while (auto child = themeList()->lastChild())
|
||||||
delete themeList()->lastChild();
|
delete child;
|
||||||
|
|
||||||
loadThemes();
|
loadThemes();
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===============================================================
|
// ===============================================================
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
57
src/ui/widget_ui_tests.cpp
Normal file
57
src/ui/widget_ui_tests.cpp
Normal 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());
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user