From e8716cbb6e12116eefbb09c3c5eabd8758562858 Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 7 May 2019 10:28:37 -0300 Subject: [PATCH] Improve context bar for slice tool (combobox of slices + action buttons) --- src/app/cmd/add_slice.cpp | 3 +- src/app/cmd/set_slice_name.cpp | 23 +- src/app/cmd/set_slice_name.h | 6 +- src/app/commands/cmd_goto_frame.cpp | 2 +- src/app/commands/cmd_options.cpp | 2 +- src/app/doc_observer.h | 3 + src/app/ui/context_bar.cpp | 302 ++++++++++++++++++++++++- src/app/ui/context_bar.h | 16 +- src/app/ui/dithering_selector.cpp | 2 +- src/app/ui/doc_observer_widget.h | 90 ++++++++ src/app/ui/doc_view.cpp | 10 + src/app/ui/editor/editor.cpp | 36 ++- src/app/ui/editor/editor.h | 5 +- src/app/ui/editor/editor_state.h | 2 +- src/app/ui/editor/select_box_state.cpp | 2 +- src/app/ui/editor/select_box_state.h | 2 +- src/app/ui/editor/standby_state.cpp | 1 + src/app/ui/file_selector.cpp | 6 +- src/app/ui/status_bar.cpp | 43 +--- src/app/ui/status_bar.h | 12 +- src/ui/combobox.cpp | 43 +++- src/ui/combobox.h | 19 +- 22 files changed, 546 insertions(+), 84 deletions(-) create mode 100644 src/app/ui/doc_observer_widget.h diff --git a/src/app/cmd/add_slice.cpp b/src/app/cmd/add_slice.cpp index fd046403b..e6fead4e0 100644 --- a/src/app/cmd/add_slice.cpp +++ b/src/app/cmd/add_slice.cpp @@ -34,8 +34,7 @@ void AddSlice::onExecute() Sprite* sprite = this->sprite(); Slice* slice = this->slice(); - sprite->slices().add(slice); - sprite->incrementVersion(); + addSlice(sprite, slice); } void AddSlice::onUndo() diff --git a/src/app/cmd/set_slice_name.cpp b/src/app/cmd/set_slice_name.cpp index 105bcc23b..2775cf834 100644 --- a/src/app/cmd/set_slice_name.cpp +++ b/src/app/cmd/set_slice_name.cpp @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2017-2018 David Capello // // This program is distributed under the terms of @@ -10,6 +11,7 @@ #include "app/cmd/set_slice_name.h" +#include "app/doc.h" #include "app/doc_event.h" #include "doc/document.h" #include "doc/slice.h" @@ -27,14 +29,27 @@ SetSliceName::SetSliceName(Slice* slice, const std::string& name) void SetSliceName::onExecute() { - slice()->setName(m_newName); - slice()->incrementVersion(); + Slice* slice = this->slice(); + slice->setName(m_newName); + slice->incrementVersion(); } void SetSliceName::onUndo() { - slice()->setName(m_oldName); - slice()->incrementVersion(); + Slice* slice = this->slice(); + slice->setName(m_oldName); + slice->incrementVersion(); +} + +void SetSliceName::onFireNotifications() +{ + Slice* slice = this->slice(); + Sprite* sprite = slice->owner()->sprite(); + Doc* doc = static_cast(sprite->document()); + DocEvent ev(doc); + ev.sprite(sprite); + ev.slice(slice); + doc->notify_observers(&DocObserver::onSliceNameChange, ev); } } // namespace cmd diff --git a/src/app/cmd/set_slice_name.h b/src/app/cmd/set_slice_name.h index 6e7d14c8d..7caaa341b 100644 --- a/src/app/cmd/set_slice_name.h +++ b/src/app/cmd/set_slice_name.h @@ -25,11 +25,13 @@ namespace cmd { protected: void onExecute() override; void onUndo() override; + void onFireNotifications() override; size_t onMemSize() const override { - return sizeof(*this); + return sizeof(*this) + + m_oldName.size() + + m_newName.size(); } - private: std::string m_oldName; std::string m_newName; }; diff --git a/src/app/commands/cmd_goto_frame.cpp b/src/app/commands/cmd_goto_frame.cpp index b9c8e1f59..afea84a76 100644 --- a/src/app/commands/cmd_goto_frame.cpp +++ b/src/app/commands/cmd_goto_frame.cpp @@ -182,7 +182,7 @@ private: private: void fill(bool all) { - removeAllItems(); + deleteAllItems(); MatchWords match(getEntryWidget()->text()); diff --git a/src/app/commands/cmd_options.cpp b/src/app/commands/cmd_options.cpp index 56fb3436a..343d82e6f 100644 --- a/src/app/commands/cmd_options.cpp +++ b/src/app/commands/cmd_options.cpp @@ -898,7 +898,7 @@ private: } void refillLanguages() { - language()->removeAllItems(); + language()->deleteAllItems(); loadLanguages(); } diff --git a/src/app/doc_observer.h b/src/app/doc_observer.h index c9fa1f9d7..4a7be7b26 100644 --- a/src/app/doc_observer.h +++ b/src/app/doc_observer.h @@ -72,6 +72,9 @@ namespace app { virtual void onSelectionChanged(DocEvent& ev) { } virtual void onSelectionBoundariesChanged(DocEvent& ev) { } + // Slices + virtual void onSliceNameChange(DocEvent& ev) { } + // Called to destroy the observable. (Here you could call "delete this".) virtual void dispose() { } }; diff --git a/src/app/ui/context_bar.cpp b/src/app/ui/context_bar.cpp index d365d541f..6853bcccd 100644 --- a/src/app/ui/context_bar.cpp +++ b/src/app/ui/context_bar.cpp @@ -18,9 +18,13 @@ #include "app/commands/commands.h" #include "app/commands/quick_command.h" #include "app/doc.h" +#include "app/doc_event.h" #include "app/ini_file.h" +#include "app/match_words.h" +#include "app/modules/editors.h" #include "app/pref/preferences.h" #include "app/shade.h" +#include "app/site.h" #include "app/tools/active_tool.h" #include "app/tools/controller.h" #include "app/tools/ink.h" @@ -34,18 +38,22 @@ #include "app/ui/color_button.h" #include "app/ui/color_shades.h" #include "app/ui/dithering_selector.h" +#include "app/ui/editor/editor.h" #include "app/ui/icon_button.h" #include "app/ui/keyboard_shortcuts.h" #include "app/ui/selection_mode_field.h" #include "app/ui/skin/skin_theme.h" #include "app/ui_context.h" #include "base/bind.h" +#include "base/fs.h" #include "base/scoped_value.h" #include "doc/brush.h" #include "doc/conversion_to_surface.h" #include "doc/image.h" #include "doc/palette.h" #include "doc/remap.h" +#include "doc/selected_objects.h" +#include "doc/slice.h" #include "obs/connection.h" #include "os/surface.h" #include "os/system.h" @@ -56,6 +64,7 @@ #include "ui/combobox.h" #include "ui/int_entry.h" #include "ui/label.h" +#include "ui/listbox.h" #include "ui/listitem.h" #include "ui/menu.h" #include "ui/message.h" @@ -66,6 +75,8 @@ #include "ui/theme.h" #include "ui/tooltips.h" +#include + namespace app { using namespace app::skin; @@ -1139,8 +1150,247 @@ private: } }; +class ContextBar::SliceFields : public HBox { + class Item : public ListItem { + public: + Item(const doc::Slice* slice) + : ListItem(slice->name()) + , m_slice(slice) { } + const doc::Slice* slice() const { return m_slice; } + private: + const doc::Slice* m_slice; + }; + + class Combo : public ComboBox { + SliceFields* m_sliceFields; + public: + Combo(SliceFields* sliceFields) + : m_sliceFields(sliceFields) { + } + protected: + void onChange() override { + ComboBox::onChange(); + m_sliceFields->onSelectSliceFromComboBox(); + } + void onEntryChange() override { + ComboBox::onEntryChange(); + m_sliceFields->onComboBoxEntryChange(); + } + void onBeforeOpenListBox() override { + ComboBox::onBeforeOpenListBox(); + m_sliceFields->fillSlices(); + } + void onEnterOnEditableEntry() override { + ComboBox::onEnterOnEditableEntry(); + + const Slice* slice = nullptr; + if (auto item = dynamic_cast(getSelectedItem())) { + if (item->slice()->name() == getValue()) { + slice = item->slice(); + } + } + if (!slice && current_editor) + slice = current_editor->sprite()->slices().getByName(getValue()); + if (slice) + m_sliceFields->scrollToSlice(slice); + + closeListBox(); + } + }; + +public: + + SliceFields() + : m_doc(nullptr) + , m_sel(2) + , m_combobox(this) + , m_action(2) + { + SkinTheme* theme = SkinTheme::instance(); + + m_sel.addItem("All"); + m_sel.addItem("None"); + m_sel.ItemChange.connect( + [this](ButtonSet::Item* item){ + onSelAction(m_sel.selectedItem()); + }); + + m_combobox.setEditable(true); + m_combobox.setExpansive(true); + m_combobox.setMinSize(gfx::Size(256*guiscale(), 0)); + + m_action.addItem(theme->parts.iconUserData())->setMono(true); + m_action.addItem(theme->parts.iconClose())->setMono(true); + m_action.ItemChange.connect( + [this](ButtonSet::Item* item){ + onAction(m_action.selectedItem()); + }); + + addChild(&m_sel); + addChild(&m_combobox); + addChild(&m_action); + + m_combobox.setVisible(false); + m_action.setVisible(false); + } + + void setupTooltips(TooltipManager* tooltipManager) { + tooltipManager->addTooltipFor(m_sel.at(0), "Select All Slices", BOTTOM); + tooltipManager->addTooltipFor(m_sel.at(1), "Deselect Slices", BOTTOM); + tooltipManager->addTooltipFor(m_action.at(0), "Slice Properties", BOTTOM); + tooltipManager->addTooltipFor(m_action.at(1), "Delete Slice", BOTTOM); + } + + void setDoc(Doc* doc) { + m_doc = doc; + } + + void addSlice(const doc::Slice* slice) { + m_changeFromEntry = true; + m_combobox.setValue(slice->name()); + updateLayout(); + m_changeFromEntry = false; + } + + void removeSlice(const doc::Slice* slice) { + m_combobox.setValue(std::string()); + updateLayout(); + } + + void updateSlice(const doc::Slice* slice) { + m_combobox.setValue(slice->name()); + updateLayout(); + } + + void selectSlices(const doc::Sprite* sprite, + const doc::SelectedObjects& slices) { + if (!slices.empty()) { + auto selected = slices.frontAs(); + m_combobox.setValue(selected->name()); + } + else { + m_combobox.setValue(std::string()); + } + updateLayout(); + } + + void closeComboBox() { + m_combobox.closeListBox(); + } + +private: + void onVisible(bool visible) override { + HBox::onVisible(visible); + m_combobox.closeListBox(); + } + + void fillSlices() { + m_combobox.deleteAllItems(); + if (m_doc && m_doc->sprite()) { + MatchWords match(m_filter); + + std::vector slices; + for (auto slice : m_doc->sprite()->slices()) { + if (match(slice->name())) + slices.push_back(slice); + } + std::sort(slices.begin(), slices.end(), + [](const doc::Slice* a, const doc::Slice* b){ + return (base::compare_filenames(a->name(), b->name()) < 0); + }); + + for (auto slice : slices) { + Item* item = new Item(slice); + m_combobox.addItem(item); + } + } + } + + void scrollToSlice(const Slice* slice) { + if (current_editor && slice) { + if (const SliceKey* key = slice->getByFrame(current_editor->frame())) { + current_editor->centerInSpritePoint(key->bounds().center()); + } + } + } + + void updateLayout() { + const bool visible = (m_doc && !m_doc->sprite()->slices().empty()); + m_combobox.setVisible(visible); + m_action.setVisible(visible); + + parent()->layout(); + } + + void onSelAction(const int item) { + m_sel.deselectItems(); + switch (item) { + case 0: + if (current_editor) + current_editor->selectAllSlices(); + break; + case 1: + if (current_editor) + current_editor->clearSlicesSelection(); + break; + } + } + + void onSelectSliceFromComboBox() { + if (m_changeFromEntry) + return; + + m_filter.clear(); + + if (auto item = dynamic_cast(m_combobox.getSelectedItem())) { + if (current_editor) { + const doc::Slice* slice = item->slice(); + current_editor->clearSlicesSelection(); + current_editor->selectSlice(slice); + } + } + } + + void onComboBoxEntryChange() { + m_changeFromEntry = true; + m_combobox.closeListBox(); + + m_filter = m_combobox.getValue(); + + m_combobox.openListBox(); + m_changeFromEntry = false; + } + + void onAction(const int item) { + m_action.deselectItems(); + + Command* cmd = nullptr; + Params params; + + switch (item) { + case 0: + cmd = Commands::instance()->byId(CommandId::SliceProperties()); + break; + case 1: + cmd = Commands::instance()->byId(CommandId::RemoveSlice()); + break; + } + + if (cmd) + UIContext::instance()->executeCommand(cmd, params); + + updateLayout(); + } + + Doc* m_doc; + ButtonSet m_sel; + Combo m_combobox; + ButtonSet m_action; + bool m_changeFromEntry; + std::string m_filter; +}; + ContextBar::ContextBar(TooltipManager* tooltipManager) - : Box(HORIZONTAL) { addChild(m_selectionOptionsBox = new HBox()); m_selectionOptionsBox->addChild(m_dropPixels = new DropPixelsField()); @@ -1174,10 +1424,6 @@ ContextBar::ContextBar(TooltipManager* tooltipManager) addChild(m_autoSelectLayer = new AutoSelectLayerField()); - // addChild(new InkChannelTargetField()); - // addChild(new InkShadeField()); - // addChild(new InkSelectionField()); - addChild(m_sprayBox = new HBox()); m_sprayBox->addChild(m_sprayLabel = new Label("Spray:")); m_sprayBox->addChild(m_sprayWidth = new SprayWidthField()); @@ -1191,9 +1437,12 @@ ContextBar::ContextBar(TooltipManager* tooltipManager) addChild(m_symmetry = new SymmetryField()); m_symmetry->setVisible(Preferences::instance().symmetryMode.enabled()); + addChild(m_sliceFields = new SliceFields); + setupTooltips(tooltipManager); App::instance()->activeToolManager()->add_observer(this); + UIContext::instance()->add_observer(this); auto& pref = Preferences::instance(); pref.symmetryMode.enabled.AfterChange.connect( @@ -1216,6 +1465,7 @@ ContextBar::ContextBar(TooltipManager* tooltipManager) ContextBar::~ContextBar() { + UIContext::instance()->remove_observer(this); App::instance()->activeToolManager()->remove_observer(this); } @@ -1265,6 +1515,40 @@ void ContextBar::onToolSetContiguous() } } +void ContextBar::onActiveSiteChange(const Site& site) +{ + DocObserverWidget::onActiveSiteChange(site); + if (site.sprite()) + m_sliceFields->selectSlices(site.sprite(), + site.selectedSlices()); + else + m_sliceFields->closeComboBox(); +} + +void ContextBar::onDocChange(Doc* doc) +{ + DocObserverWidget::onDocChange(doc); + m_sliceFields->setDoc(doc); +} + +void ContextBar::onAddSlice(DocEvent& ev) +{ + if (ev.slice()) + m_sliceFields->addSlice(ev.slice()); +} + +void ContextBar::onRemoveSlice(DocEvent& ev) +{ + if (ev.slice()) + m_sliceFields->removeSlice(ev.slice()); +} + +void ContextBar::onSliceNameChange(DocEvent& ev) +{ + if (ev.slice()) + m_sliceFields->updateSlice(ev.slice()); +} + void ContextBar::onBrushSizeChange() { if (m_activeBrush->type() != kImageBrushType) @@ -1438,6 +1722,11 @@ void ContextBar::updateForTool(tools::Tool* tool) (tool->getInk(0)->isCelMovement() || tool->getInk(1)->isCelMovement()); + // True if the current tool is slice tool. + const bool isSlice = tool && + (tool->getInk(0)->isSlice() || + tool->getInk(1)->isSlice()); + // True if the current tool is floodfill const bool isFloodfill = tool && (tool->getPointShape(0)->isFloodFill() || @@ -1502,6 +1791,8 @@ void ContextBar::updateForTool(tools::Tool* tool) (isPaint || isEffect || hasSelectOptions)); m_symmetry->updateWithCurrentDocument(); + m_sliceFields->setVisible(isSlice); + // Update ink shades with the current selected palette entries if (updateShade) m_inkShades->updateShadeFromColorBarPicks(); @@ -1815,6 +2106,7 @@ void ContextBar::setupTooltips(TooltipManager* tooltipManager) m_gradientType->setupTooltips(tooltipManager); m_dropPixels->setupTooltips(tooltipManager); m_symmetry->setupTooltips(tooltipManager); + m_sliceFields->setupTooltips(tooltipManager); } void ContextBar::registerCommands() diff --git a/src/app/ui/context_bar.h b/src/app/ui/context_bar.h index fc360dce4..7882f81c6 100644 --- a/src/app/ui/context_bar.h +++ b/src/app/ui/context_bar.h @@ -15,6 +15,7 @@ #include "app/tools/ink_type.h" #include "app/tools/tool_loop_modifiers.h" #include "app/ui/context_bar_observer.h" +#include "app/ui/doc_observer_widget.h" #include "doc/brush.h" #include "obs/connection.h" #include "obs/observable.h" @@ -51,7 +52,7 @@ namespace app { class DitheringSelector; class GradientTypeSelector; - class ContextBar : public ui::Box + class ContextBar : public DocObserverWidget , public obs::observable , public tools::ActiveToolObserver { public: @@ -97,6 +98,17 @@ namespace app { void onToolSetFreehandAlgorithm(); void onToolSetContiguous(); + // ContextObserver impl + void onActiveSiteChange(const Site& site) override; + + // DocObserverWidget overrides + void onDocChange(Doc* doc) override; + + // DocObserver impl + void onAddSlice(DocEvent& ev) override; + void onRemoveSlice(DocEvent& ev) override; + void onSliceNameChange(DocEvent& ev) override; + private: void onBrushSizeChange(); void onBrushAngleChange(); @@ -135,6 +147,7 @@ namespace app { class DropPixelsField; class AutoSelectLayerField; class SymmetryField; + class SliceFields; ZoomButtons* m_zoomButtons; BrushBackField* m_brushBack; @@ -169,6 +182,7 @@ namespace app { doc::BrushRef m_activeBrush; ui::Label* m_selectBoxHelp; SymmetryField* m_symmetry; + SliceFields* m_sliceFields; obs::scoped_connection m_sizeConn; obs::scoped_connection m_angleConn; obs::scoped_connection m_opacityConn; diff --git a/src/app/ui/dithering_selector.cpp b/src/app/ui/dithering_selector.cpp index 3d4ca84ab..7a0cddcb5 100644 --- a/src/app/ui/dithering_selector.cpp +++ b/src/app/ui/dithering_selector.cpp @@ -181,7 +181,7 @@ DitheringSelector::DitheringSelector(Type type) void DitheringSelector::regenerate() { - removeAllItems(); + deleteAllItems(); Extensions& extensions = App::instance()->extensions(); auto ditheringMatrices = extensions.ditheringMatrices(); diff --git a/src/app/ui/doc_observer_widget.h b/src/app/ui/doc_observer_widget.h new file mode 100644 index 000000000..55a4e2500 --- /dev/null +++ b/src/app/ui/doc_observer_widget.h @@ -0,0 +1,90 @@ +// Aseprite +// Copyright (C) 2019 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifndef APP_UI_DOC_OBSERVER_WIDGET_H_INCLUDED +#define APP_UI_DOC_OBSERVER_WIDGET_H_INCLUDED +#pragma once + +#include "app/context_observer.h" +#include "app/doc.h" +#include "app/doc_observer.h" +#include "app/docs_observer.h" +#include "app/site.h" +#include "app/ui_context.h" +#include "base/debug.h" + +namespace app { + + // A widget that observes the active document. + template + class DocObserverWidget : public BaseWidget + , public ContextObserver + , public DocsObserver + , public DocObserver { + public: + template + DocObserverWidget(Args&&... args) + : BaseWidget(std::forward(args)...) + , m_doc(nullptr) { + UIContext::instance()->add_observer(this); + UIContext::instance()->documents().add_observer(this); + } + + ~DocObserverWidget() { + ASSERT(!m_doc); + UIContext::instance()->documents().remove_observer(this); + UIContext::instance()->remove_observer(this); + } + + protected: + Doc* doc() const { return m_doc; } + + virtual void onDocChange(Doc* doc) { + } + + // ContextObserver impl + void onActiveSiteChange(const Site& site) override { + if (m_doc && site.document() != m_doc) { + m_doc->remove_observer(this); + m_doc = nullptr; + } + + if (site.document() && site.sprite()) { + if (!m_doc) { + m_doc = const_cast(site.document()); + m_doc->add_observer(this); + + onDocChange(m_doc); + } + else { + ASSERT(m_doc == site.document()); + } + } + else { + ASSERT(m_doc == nullptr); + } + } + + // DocsObservers impl + void onRemoveDocument(Doc* doc) override { + if (m_doc && + m_doc == doc) { + m_doc->remove_observer(this); + m_doc = nullptr; + + onDocChange(nullptr); + } + } + + // The DocObserver impl will be in the derived class. + + private: + Doc* m_doc; + }; + +} // namespace app + +#endif diff --git a/src/app/ui/doc_view.cpp b/src/app/ui/doc_view.cpp index 544baa17e..2d305fe65 100644 --- a/src/app/ui/doc_view.cpp +++ b/src/app/ui/doc_view.cpp @@ -559,6 +559,16 @@ bool DocView::onPaste(Context* ctx) bool DocView::onClear(Context* ctx) { + // First we check if there is a selected slice, so we'll delete + // those slices. + Site site = ctx->activeSite(); + if (!site.selectedSlices().empty()) { + Command* removeSlices = Commands::instance()->byId(CommandId::RemoveSlice()); + ctx->executeCommand(removeSlices); + return true; + } + + // In other case we delete the mask or the cel. ContextWriter writer(ctx); Doc* document = writer.document(); bool visibleMask = document->isMaskVisible(); diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index bc18808fb..548058a28 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -380,8 +380,10 @@ void Editor::getSite(Site* site) const site->sprite(m_sprite); site->layer(m_layer); site->frame(m_frame); - if (!m_selectedSlices.empty()) + if (!m_selectedSlices.empty() && + getCurrentEditorInk()->isSlice()) { site->selectedSlices(m_selectedSlices); + } } Site Editor::getSite() const @@ -1308,12 +1310,12 @@ gfx::Point Editor::autoScroll(MouseMessage* msg, AutoScroll dir) return mousePos; } -tools::Tool* Editor::getCurrentEditorTool() +tools::Tool* Editor::getCurrentEditorTool() const { return App::instance()->activeTool(); } -tools::Ink* Editor::getCurrentEditorInk() +tools::Ink* Editor::getCurrentEditorInk() const { tools::Ink* ink = m_state->getStateInk(); if (ink) @@ -1629,8 +1631,13 @@ bool Editor::isSliceSelected(const doc::Slice* slice) const void Editor::clearSlicesSelection() { - m_selectedSlices.clear(); - invalidate(); + if (!m_selectedSlices.empty()) { + m_selectedSlices.clear(); + invalidate(); + + if (isActive()) + UIContext::instance()->notifyActiveSiteChanged(); + } } void Editor::selectSlice(const doc::Slice* slice) @@ -1638,6 +1645,9 @@ void Editor::selectSlice(const doc::Slice* slice) ASSERT(slice); m_selectedSlices.insert(slice->id()); invalidate(); + + if (isActive()) + UIContext::instance()->notifyActiveSiteChanged(); } bool Editor::selectSliceBox(const gfx::Rect& box) @@ -1649,12 +1659,26 @@ bool Editor::selectSliceBox(const gfx::Rect& box) m_selectedSlices.insert(slice->id()); } invalidate(); + + if (isActive()) + UIContext::instance()->notifyActiveSiteChanged(); + return !m_selectedSlices.empty(); } +void Editor::selectAllSlices() +{ + for (auto slice : m_sprite->slices()) + m_selectedSlices.insert(slice->id()); + invalidate(); + + if (isActive()) + UIContext::instance()->notifyActiveSiteChanged(); +} + void Editor::cancelSelections() { - m_selectedSlices.clear(); + clearSlicesSelection(); } ////////////////////////////////////////////////////////////////////// diff --git a/src/app/ui/editor/editor.h b/src/app/ui/editor/editor.h index 7752c8d30..5274dddee 100644 --- a/src/app/ui/editor/editor.h +++ b/src/app/ui/editor/editor.h @@ -201,8 +201,8 @@ namespace app { // Control scroll when cursor goes out of the editor viewport. gfx::Point autoScroll(ui::MouseMessage* msg, AutoScroll dir); - tools::Tool* getCurrentEditorTool(); - tools::Ink* getCurrentEditorInk(); + tools::Tool* getCurrentEditorTool() const; + tools::Ink* getCurrentEditorInk() const; tools::ToolLoopModifiers getToolLoopModifiers() const { return m_toolLoopModifiers; } bool isAutoSelectLayer(); @@ -283,6 +283,7 @@ namespace app { void clearSlicesSelection(); void selectSlice(const doc::Slice* slice); bool selectSliceBox(const gfx::Rect& box); + void selectAllSlices(); bool hasSelectedSlices() const { return !m_selectedSlices.empty(); } // Called by DocView's InputChainElement::onCancel() impl when Esc diff --git a/src/app/ui/editor/editor_state.h b/src/app/ui/editor/editor_state.h index 8dfed778d..c7344f2c7 100644 --- a/src/app/ui/editor/editor_state.h +++ b/src/app/ui/editor/editor_state.h @@ -123,7 +123,7 @@ namespace app { virtual bool acceptQuickTool(tools::Tool* tool) { return true; } // Custom ink in this state. - virtual tools::Ink* getStateInk() { return nullptr; } + virtual tools::Ink* getStateInk() const { return nullptr; } // Called when a tag is deleted. virtual void onRemoveFrameTag(Editor* editor, doc::FrameTag* tag) { } diff --git a/src/app/ui/editor/select_box_state.cpp b/src/app/ui/editor/select_box_state.cpp index 7bd4d98b4..f7b32c32c 100644 --- a/src/app/ui/editor/select_box_state.cpp +++ b/src/app/ui/editor/select_box_state.cpp @@ -303,7 +303,7 @@ bool SelectBoxState::requireBrushPreview() return false; } -tools::Ink* SelectBoxState::getStateInk() +tools::Ink* SelectBoxState::getStateInk() const { if (hasFlag(Flags::QuickBox)) return App::instance()->toolBox()->getInkById( diff --git a/src/app/ui/editor/select_box_state.h b/src/app/ui/editor/select_box_state.h index 4de6c2c13..db78d06c1 100644 --- a/src/app/ui/editor/select_box_state.h +++ b/src/app/ui/editor/select_box_state.h @@ -98,7 +98,7 @@ namespace app { virtual bool onKeyDown(Editor* editor, ui::KeyMessage* msg) override; virtual bool acceptQuickTool(tools::Tool* tool) override; virtual bool requireBrushPreview() override; - virtual tools::Ink* getStateInk() override; + virtual tools::Ink* getStateInk() const override; // EditorDecorator overrides virtual void postRenderDecorator(EditorPostRender* render) override; diff --git a/src/app/ui/editor/standby_state.cpp b/src/app/ui/editor/standby_state.cpp index 321c1ab2c..a3261fe72 100644 --- a/src/app/ui/editor/standby_state.cpp +++ b/src/app/ui/editor/standby_state.cpp @@ -242,6 +242,7 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg) // If we click outside all slices, we clear the selection of slices. if (!hit.slice() || !site.selectedSlices().contains(hit.slice()->id())) { editor->clearSlicesSelection(); + editor->selectSlice(hit.slice()); site = Site(); editor->getSite(&site); diff --git a/src/app/ui/file_selector.cpp b/src/app/ui/file_selector.cpp index 4c42e80c4..4ff72ce36 100644 --- a/src/app/ui/file_selector.cpp +++ b/src/app/ui/file_selector.cpp @@ -127,7 +127,7 @@ protected: if (m_fileList->multipleSelection()) m_fileList->deselectedFileItems(); - removeAllItems(); + deleteAllItems(); // String to be autocompleted std::string left_part = getEntryWidget()->text(); @@ -413,7 +413,7 @@ bool FileSelector::show( updateNavigationButtons(); // fill file-type combo-box - fileType()->removeAllItems(); + fileType()->deleteAllItems(); // Get the default extension from the given initial file name if (m_defExtension.empty()) @@ -662,7 +662,7 @@ void FileSelector::updateLocation() } // Clear all the items from the combo-box - location()->removeAllItems(); + location()->deleteAllItems(); // Add item by item (from root to the specific current folder) int level = 0; diff --git a/src/app/ui/status_bar.cpp b/src/app/ui/status_bar.cpp index 2b13d12ec..0f8180f51 100644 --- a/src/app/ui/status_bar.cpp +++ b/src/app/ui/status_bar.cpp @@ -542,7 +542,6 @@ StatusBar::StatusBar(TooltipManager* tooltipManager) : m_timeout(0) , m_indicators(new Indicators) , m_docControls(new HBox) - , m_doc(nullptr) , m_tipwindow(nullptr) , m_snapToGridWindow(nullptr) { @@ -586,8 +585,6 @@ StatusBar::StatusBar(TooltipManager* tooltipManager) tooltipManager->addTooltipFor(m_zoomEntry, "Zoom Level", BOTTOM); tooltipManager->addTooltipFor(m_newFrame, "New Frame", BOTTOM); - UIContext::instance()->add_observer(this); - UIContext::instance()->documents().add_observer(this); App::instance()->activeToolManager()->add_observer(this); initTheme(); @@ -596,8 +593,6 @@ StatusBar::StatusBar(TooltipManager* tooltipManager) StatusBar::~StatusBar() { App::instance()->activeToolManager()->remove_observer(this); - UIContext::instance()->documents().remove_observer(this); - UIContext::instance()->remove_observer(this); delete m_tipwindow; // widget delete m_snapToGridWindow; @@ -719,9 +714,10 @@ void StatusBar::showTool(int msecs, tools::Tool* tool) void StatusBar::showSnapToGridWarning(bool state) { if (state) { - // m_doc can be null if "snap to grid" command is pressed without - // an opened document. (E.g. to change the default setting) - if (!m_doc) + // this->doc() can be nullptr if "snap to grid" command is pressed + // without an opened document. (E.g. to change the default + // setting) + if (!doc()) return; if (!m_snapToGridWindow) @@ -733,7 +729,7 @@ void StatusBar::showSnapToGridWarning(bool state) updateSnapToGridWindowPosition(); } - m_snapToGridWindow->setDocument(m_doc); + m_snapToGridWindow->setDocument(doc()); } else { if (m_snapToGridWindow) @@ -768,7 +764,7 @@ void StatusBar::onInitTheme(ui::InitThemeEvent& ev) void StatusBar::onResize(ResizeEvent& ev) { Rect rc = ev.bounds(); - m_docControls->setVisible(m_doc && rc.w > 300*ui::guiscale()); + m_docControls->setVisible(doc() && rc.w > 300*ui::guiscale()); HBox::onResize(ev); @@ -779,21 +775,10 @@ void StatusBar::onResize(ResizeEvent& ev) void StatusBar::onActiveSiteChange(const Site& site) { - if (m_doc && site.document() != m_doc) { - m_doc->remove_observer(this); - m_doc = nullptr; - } + DocObserverWidget::onActiveSiteChange(site); - if (site.document() && site.sprite()) { - if (!m_doc) { - m_doc = const_cast(site.document()); - m_doc->add_observer(this); - } - else { - ASSERT(m_doc == site.document()); - } - - auto& docPref = Preferences::instance().document(m_doc); + if (doc()) { + auto& docPref = Preferences::instance().document(doc()); m_docControls->setVisible(true); showSnapToGridWarning(docPref.grid.snap()); @@ -803,22 +788,12 @@ void StatusBar::onActiveSiteChange(const Site& site) "%d", site.frame()+docPref.timeline.firstFrame()); } else { - ASSERT(m_doc == nullptr); m_docControls->setVisible(false); showSnapToGridWarning(false); } layout(); } -void StatusBar::onRemoveDocument(Doc* doc) -{ - if (m_doc && - m_doc == doc) { - m_doc->remove_observer(this); - m_doc = nullptr; - } -} - void StatusBar::onPixelFormatChanged(DocEvent& ev) { // If this is called from the non-UI thread it means that the pixel diff --git a/src/app/ui/status_bar.h b/src/app/ui/status_bar.h index 6409fb24b..82d3b5c6d 100644 --- a/src/app/ui/status_bar.h +++ b/src/app/ui/status_bar.h @@ -11,9 +11,8 @@ #include "app/color.h" #include "app/context_observer.h" -#include "app/doc_observer.h" -#include "app/docs_observer.h" #include "app/tools/active_tool_observer.h" +#include "app/ui/doc_observer_widget.h" #include "base/time.h" #include "ui/base.h" #include "ui/box.h" @@ -43,10 +42,7 @@ namespace app { class Tool; } - class StatusBar : public ui::HBox - , public ContextObserver - , public DocsObserver - , public DocObserver + class StatusBar : public DocObserverWidget , public tools::ActiveToolObserver { static StatusBar* m_instance; public: @@ -79,9 +75,6 @@ namespace app { // ContextObserver impl void onActiveSiteChange(const Site& site) override; - // DocObservers impl - void onRemoveDocument(Doc* doc) override; - // DocObserver impl void onPixelFormatChanged(DocEvent& ev) override; @@ -108,7 +101,6 @@ namespace app { ui::Entry* m_currentFrame; // Current frame and go to frame entry ui::Button* m_newFrame; // Button to create a new frame ZoomEntry* m_zoomEntry; - Doc* m_doc; // Document used to show the cel slider // Tip window class CustomizedTipWindow; diff --git a/src/ui/combobox.cpp b/src/ui/combobox.cpp index 3930846a8..c32214407 100644 --- a/src/ui/combobox.cpp +++ b/src/ui/combobox.cpp @@ -1,5 +1,5 @@ // Aseprite UI Library -// Copyright (C) 2018 Igara Studio S.A. +// Copyright (C) 2018-2019 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This file is released under the terms of the MIT license. @@ -48,6 +48,7 @@ public: protected: bool onProcessMessage(Message* msg) override; void onPaint(PaintEvent& ev) override; + void onChange() override; private: ComboBox* m_comboBox; @@ -112,7 +113,7 @@ ComboBox::ComboBox() ComboBox::~ComboBox() { removeMessageFilters(); - removeAllItems(); + deleteAllItems(); } void ComboBox::setEditable(bool state) @@ -186,7 +187,7 @@ void ComboBox::removeItem(Widget* item) // Do not delete the given "item" } -void ComboBox::removeItem(int itemIndex) +void ComboBox::deleteItem(int itemIndex) { ASSERT(itemIndex >= 0 && (std::size_t)itemIndex < m_items.size()); @@ -196,7 +197,7 @@ void ComboBox::removeItem(int itemIndex) delete item; } -void ComboBox::removeAllItems() +void ComboBox::deleteAllItems() { for (Widget* item : m_items) delete item; // widget @@ -210,7 +211,7 @@ int ComboBox::getItemCount() const return m_items.size(); } -Widget* ComboBox::getItem(int itemIndex) +Widget* ComboBox::getItem(const int itemIndex) const { if (itemIndex >= 0 && (std::size_t)itemIndex < m_items.size()) { return m_items[itemIndex]; @@ -268,7 +269,7 @@ int ComboBox::findItemIndexByValue(const std::string& value) const Widget* ComboBox::getSelectedItem() const { - return (!m_items.empty() ? m_items[m_selected]: NULL); + return getItem(m_selected); } void ComboBox::setSelectedItem(Widget* item) @@ -467,6 +468,10 @@ bool ComboBoxEntry::onProcessMessage(Message* msg) return true; } } + else if (scancode == kKeyEnter || + scancode == kKeyEnterPad) { + m_comboBox->onEnterOnEditableEntry(); + } } } break; @@ -538,6 +543,15 @@ void ComboBoxEntry::onPaint(PaintEvent& ev) theme()->paintComboBoxEntry(ev); } +void ComboBoxEntry::onChange() +{ + Entry::onChange(); + if (m_comboBox && + m_comboBox->isEditable()) { + m_comboBox->onEntryChange(); + } +} + bool ComboBoxListBox::onProcessMessage(Message* msg) { switch (msg->type()) { @@ -591,6 +605,8 @@ void ComboBox::openListBox() if (!isEnabled() || m_window) return; + onBeforeOpenListBox(); + m_window = new Window(Window::WithoutTitleBar); View* view = new View(); m_listbox = new ComboBoxListBox(this); @@ -678,6 +694,16 @@ void ComboBox::onChange() Change(); } +void ComboBox::onEntryChange() +{ + // Do nothing +} + +void ComboBox::onBeforeOpenListBox() +{ + // Do nothing +} + void ComboBox::onOpenListBox() { OpenListBox(); @@ -688,6 +714,11 @@ void ComboBox::onCloseListBox() CloseListBox(); } +void ComboBox::onEnterOnEditableEntry() +{ + // Do nothing +} + void ComboBox::filterMessages() { if (!m_filtering) { diff --git a/src/ui/combobox.h b/src/ui/combobox.h index 53f91fcce..9e4ba8d04 100644 --- a/src/ui/combobox.h +++ b/src/ui/combobox.h @@ -1,4 +1,5 @@ // Aseprite UI Library +// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This file is released under the terms of the MIT license. @@ -57,13 +58,13 @@ namespace ui { void removeItem(Widget* item); // Removes and deletes the given item. - void removeItem(int itemIndex); + void deleteItem(int itemIndex); - void removeAllItems(); + void deleteAllItems(); int getItemCount() const; - Widget* getItem(int itemIndex); + Widget* getItem(const int itemIndex) const; const std::string& getItemText(int itemIndex) const; void setItemText(int itemIndex, const std::string& text); int findItemIndex(const std::string& text) const; @@ -96,9 +97,21 @@ namespace ui { void onResize(ResizeEvent& ev) override; void onSizeHint(SizeHintEvent& ev) override; void onInitTheme(InitThemeEvent& ev) override; + + // When the selected item is changed. virtual void onChange(); + + // When the text of an editable ComboBox is changed. + virtual void onEntryChange(); + + // Before we open the list box, we can fill the combobox with the + // items to show. TODO replace all this with a MVC-like combobox + // model so we request items only when it's required. + virtual void onBeforeOpenListBox(); + virtual void onOpenListBox(); virtual void onCloseListBox(); + virtual void onEnterOnEditableEntry(); private: void onButtonClick(Event& ev);