/* SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup editorui * * Base class for all views (UIs to display data sets) and view items, supporting common features. * https://wiki.blender.org/wiki/Source/Interface/Views * * One of the most important responsibilities of the base class is managing reconstruction, * enabling state that is persistent over reconstructions/redraws. Other features: * - Renaming * - Custom context menus * - Notifier listening * - Drag controllers (dragging view items) * - Drop targets (dropping onto/into view items) */ #pragma once #include #include #include #include "DNA_defs.h" #include "DNA_vec_types.h" #include "BLI_span.hh" #include "BLI_string_ref.hh" #include "UI_interface.hh" struct bContext; struct uiBlock; struct uiLayout; struct uiViewItemHandle; struct ViewLink; struct wmDrag; struct wmNotifier; namespace blender::ui { class AbstractViewItem; class AbstractViewItemDropTarget; class AbstractViewItemDragController; /** The view drop target can share logic with the view item drop target for now, so just an alias. */ using AbstractViewDropTarget = AbstractViewItemDropTarget; class AbstractView { friend class AbstractViewItem; friend struct ::ViewLink; bool is_reconstructed_ = false; /** * Only one item can be renamed at a time. So rather than giving each item an own rename buffer * (which just adds unused memory in most cases), have one here that is managed by the view. * * This fixed-size buffer is needed because that's what the rename button requires. In future we * may be able to bind the button to a `std::string` or similar. */ std::unique_ptr> rename_buffer_; /* See #get_bounds(). */ std::optional bounds_; public: virtual ~AbstractView() = default; /** * If a view wants to support dropping data into it, it has to return a drop target here. * That is an object implementing #AbstractViewDropTarget. * * \note This drop target may be requested for each event. The view doesn't keep the drop target * around currently. So it cannot contain persistent state. */ virtual std::unique_ptr create_drop_target(); /** Listen to a notifier, returning true if a redraw is needed. */ virtual bool listen(const wmNotifier &) const; /** * Makes \a item valid for display in this view. Behavior is undefined for items not registered * with this. */ void register_item(AbstractViewItem &item); /** Only one item can be renamed at a time. */ bool is_renaming() const; /** \return If renaming was started successfully. */ bool begin_renaming(); void end_renaming(); Span get_rename_buffer() const; MutableSpan get_rename_buffer(); /** * Get the rectangle containing all the view items that are in the layout, in button space. * Updated as part of #UI_block_end(), before that it's unset. */ std::optional get_bounds() const; protected: AbstractView() = default; virtual void update_children_from_old(const AbstractView &old_view) = 0; /** * Match the view and its items against an earlier version of itself (if any) and copy the old UI * state (e.g. collapsed, active, selected, renaming, etc.) to the new one. See * #AbstractViewItem.update_from_old(). * After this, reconstruction is complete (see #is_reconstructed()). */ void update_from_old(uiBlock &new_block); /** * Check if the view is fully (re-)constructed. That means, both the build function and * #update_from_old() have finished. */ bool is_reconstructed() const; }; class AbstractViewItem { friend class AbstractView; friend class ViewItemAPIWrapper; protected: /** * The view this item is a part of, and was registered for using #AbstractView::register_item(). * If this wasn't done, the behavior of items is undefined. */ AbstractView *view_ = nullptr; bool is_active_ = false; bool is_renaming_ = false; public: virtual ~AbstractViewItem() = default; virtual void build_context_menu(bContext &C, uiLayout &column) const; /** * Queries if the view item supports renaming in principle. Renaming may still fail, e.g. if * another item is already being renamed. */ virtual bool supports_renaming() const; /** * Try renaming the item, or the data it represents. Can assume * #AbstractViewItem::supports_renaming() returned true. Sub-classes that override this should * usually call this, unless they have a custom #AbstractViewItem.matches() implementation. * * \return True if the renaming was successful. */ virtual bool rename(StringRefNull new_name); /** * Get the string that should be used for renaming, typically the item's label. This string will * not be modified, but if the renaming is canceled, the value will be reset to this. */ virtual StringRef get_rename_string() const; /** * If an item wants to support being dragged, it has to return a drag controller here. * That is an object implementing #AbstractViewItemDragController. */ virtual std::unique_ptr create_drag_controller() const; /** * If an item wants to support dropping data into it, it has to return a drop target here. * That is an object implementing #AbstractViewItemDropTarget. * * \note This drop target may be requested for each event. The view doesn't keep a drop target * around currently. So it can not contain persistent state. */ virtual std::unique_ptr create_drop_target(); /** Get the view this item is registered for using #AbstractView::register_item(). */ AbstractView &get_view() const; /** * Requires the view to have completed reconstruction, see #is_reconstructed(). Otherwise we * can't be sure about the item state. */ bool is_active() const; bool is_renaming() const; void begin_renaming(); void end_renaming(); void rename_apply(); template static ToType *from_item_handle(uiViewItemHandle *handle); protected: AbstractViewItem() = default; /** * Compare this item's identity to \a other to check if they represent the same data. * Implementations can assume that the types match already (caller must check). * * Used to recognize an item from a previous redraw, to be able to keep its state (e.g. active, * renaming, etc.). */ virtual bool matches(const AbstractViewItem &other) const = 0; /** * Copy persistent state (e.g. active, selection, etc.) from a matching item of * the last redraw to this item. If sub-classes introduce more advanced state they should * override this and make it update their state accordingly. * * \note Always call the base class implementation when overriding this! */ virtual void update_from_old(const AbstractViewItem &old); /** * Add a text button for renaming the item to \a block. This must be used for the built-in * renaming to work. This button is meant to appear temporarily. It is removed when renaming is * done. */ void add_rename_button(uiBlock &block); }; template ToType *AbstractViewItem::from_item_handle(uiViewItemHandle *handle) { static_assert(std::is_base_of::value, "Type must derive from and implement the AbstractViewItem interface"); return dynamic_cast(reinterpret_cast(handle)); } /* ---------------------------------------------------------------------- */ /** \name Drag 'n Drop * \{ */ /** * Class to enable dragging a view item. An item can return a drag controller for itself by * implementing #AbstractViewItem::create_drag_controller(). */ class AbstractViewItemDragController { protected: AbstractView &view_; public: AbstractViewItemDragController(AbstractView &view); virtual ~AbstractViewItemDragController() = default; virtual int get_drag_type() const = 0; virtual void *create_drag_data() const = 0; virtual void on_drag_start(); /** Request the view the item is registered for as type #ViewType. Throws a `std::bad_cast` * exception if the view is not of the requested type. */ template inline ViewType &get_view() const; }; /** * Class to define the behavior when dropping something onto/into a view item, plus the behavior * when dragging over this item. An item can return a drop target for itself via a custom * implementation of #AbstractViewItem::create_drop_target(). */ class AbstractViewItemDropTarget : public DropTargetInterface { protected: AbstractView &view_; public: AbstractViewItemDropTarget(AbstractView &view); /** Request the view the item is registered for as type #ViewType. Throws a `std::bad_cast` * exception if the view is not of the requested type. */ template inline ViewType &get_view() const; }; template ViewType &AbstractViewItemDragController::get_view() const { static_assert(std::is_base_of::value, "Type must derive from and implement the ui::AbstractView interface"); return dynamic_cast(view_); } template ViewType &AbstractViewItemDropTarget::get_view() const { static_assert(std::is_base_of::value, "Type must derive from and implement the ui::AbstractView interface"); return dynamic_cast(view_); } /** \} */ } // namespace blender::ui