From e2ddbcf3267b8faa82f82210a6179b0b2a8c495f Mon Sep 17 00:00:00 2001 From: Jussi Lind Date: Sun, 21 Apr 2024 22:06:23 +0300 Subject: [PATCH] WIP: Raise edge labels - TODO: Fix label getting under nodes on raise - TODO: Synthesize hover leave on nodes - TODO: Fix nodes not lowering on Matrix - Multiple nodes get stuck at the raised position --- src/CMakeLists.txt | 6 +- src/application/settings_proxy.cpp | 14 ++ src/application/settings_proxy.hpp | 8 + src/translations/heimer_de.ts | 32 ++-- src/translations/heimer_en.ts | 32 ++-- src/translations/heimer_es.ts | 32 ++-- src/translations/heimer_fi.ts | 32 ++-- src/translations/heimer_fr.ts | 32 ++-- src/translations/heimer_it.ts | 32 ++-- src/translations/heimer_nl.ts | 32 ++-- src/translations/heimer_zh.ts | 32 ++-- src/view/dialogs/editing_tab.cpp | 8 + src/view/dialogs/editing_tab.hpp | 2 + src/view/editor_view.cpp | 8 +- src/view/scene_items/edge.cpp | 180 ++++++++++-------- src/view/scene_items/edge.hpp | 38 ++-- src/view/scene_items/edge_text_edit.cpp | 66 ++++--- src/view/scene_items/edge_text_edit.hpp | 22 +-- src/view/scene_items/node.cpp | 49 ++--- src/view/scene_items/node.hpp | 6 +- src/view/scene_items/node_handle.hpp | 4 +- ....cpp => scene_item_base_graphics_item.cpp} | 38 ++-- ....hpp => scene_item_base_graphics_item.hpp} | 16 +- .../scene_item_base_graphics_text_item.cpp | 123 ++++++++++++ .../scene_item_base_graphics_text_item.hpp | 71 +++++++ src/view/scene_items/text_edit.cpp | 2 +- src/view/scene_items/text_edit.hpp | 5 +- 27 files changed, 636 insertions(+), 286 deletions(-) rename src/view/scene_items/{scene_item_base.cpp => scene_item_base_graphics_item.cpp} (71%) rename src/view/scene_items/{scene_item_base.hpp => scene_item_base_graphics_item.hpp} (82%) create mode 100644 src/view/scene_items/scene_item_base_graphics_text_item.cpp create mode 100644 src/view/scene_items/scene_item_base_graphics_text_item.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ed14ccd5..8f457a56 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -81,7 +81,8 @@ set(HEIMER_LIB_SRC ${HEIMER_SRC_ROOT}/view/scene_items/graphics_factory.cpp ${HEIMER_SRC_ROOT}/view/scene_items/node.cpp ${HEIMER_SRC_ROOT}/view/scene_items/node_handle.cpp - ${HEIMER_SRC_ROOT}/view/scene_items/scene_item_base.cpp + ${HEIMER_SRC_ROOT}/view/scene_items/scene_item_base_graphics_item.cpp + ${HEIMER_SRC_ROOT}/view/scene_items/scene_item_base_graphics_text_item.cpp ${HEIMER_SRC_ROOT}/view/scene_items/text_edit.cpp ${HEIMER_SRC_ROOT}/view/widgets/font_button.cpp ${HEIMER_SRC_ROOT}/view/widgets/status_label.cpp @@ -166,7 +167,8 @@ set(HEIMER_LIB_HDR ${HEIMER_SRC_ROOT}/view/scene_items/node.hpp ${HEIMER_SRC_ROOT}/view/scene_items/node_handle.hpp ${HEIMER_SRC_ROOT}/view/scene_items/node_model.hpp - ${HEIMER_SRC_ROOT}/view/scene_items/scene_item_base.hpp + ${HEIMER_SRC_ROOT}/view/scene_items/scene_item_base_graphics_item.hpp + ${HEIMER_SRC_ROOT}/view/scene_items/scene_item_base_graphics_text_item.hpp ${HEIMER_SRC_ROOT}/view/scene_items/text_edit.hpp ${HEIMER_SRC_ROOT}/view/shadow_effect_params.hpp ${HEIMER_SRC_ROOT}/view/widgets/font_button.hpp diff --git a/src/application/settings_proxy.cpp b/src/application/settings_proxy.cpp index a3bba2a2..d9cf2f07 100644 --- a/src/application/settings_proxy.cpp +++ b/src/application/settings_proxy.cpp @@ -29,6 +29,7 @@ SettingsProxy::SettingsProxy() , m_edgeWidth(Settings::Generic::getNumber(m_defaultsSettingGroup, m_edgeWidthSettingKey, Constants::Settings::defaultEdgeWidth())) , m_invertedControls(Settings::Generic::getBoolean(m_editingSettingGroup, m_invertedControlsSettingKey, false)) , m_reversedEdgeDirection(Settings::Custom::loadReversedEdgeDirection(false)) + , m_raiseEdgeLabelOnMouseHover(Settings::Generic::getBoolean(m_editingSettingGroup, m_raiseEdgeLabelOnMouseHoverKey, true)) , m_raiseNodeOnMouseHover(Settings::Generic::getBoolean(m_editingSettingGroup, m_raiseNodeOnMouseHoverKey, true)) , m_selectNodeGroupByIntersection(Settings::Custom::loadSelectNodeGroupByIntersection()) , m_textSize(static_cast(Settings::Generic::getNumber(m_defaultsSettingGroup, m_textSizeSettingKey, Constants::MindMap::defaultTextSize()))) @@ -225,11 +226,24 @@ void SettingsProxy::setReversedEdgeDirection(bool reversedEdgeDirection) } } +bool SettingsProxy::raiseEdgeLabelOnMouseHover() const +{ + return m_raiseEdgeLabelOnMouseHover; +} + bool SettingsProxy::raiseNodeOnMouseHover() const { return m_raiseNodeOnMouseHover; } +void SettingsProxy::setRaiseEdgeLabelOnMouseHover(bool raiseEdgeLabelOnMouseHover) +{ + if (m_raiseEdgeLabelOnMouseHover != raiseEdgeLabelOnMouseHover) { + m_raiseEdgeLabelOnMouseHover = raiseEdgeLabelOnMouseHover; + Settings::Generic::setBoolean(m_editingSettingGroup, m_raiseEdgeLabelOnMouseHoverKey, raiseEdgeLabelOnMouseHover); + } +} + void SettingsProxy::setRaiseNodeOnMouseHover(bool raiseNodeOnMouseHover) { if (m_raiseNodeOnMouseHover != raiseNodeOnMouseHover) { diff --git a/src/application/settings_proxy.hpp b/src/application/settings_proxy.hpp index 61bd18db..36e8fc92 100644 --- a/src/application/settings_proxy.hpp +++ b/src/application/settings_proxy.hpp @@ -84,8 +84,12 @@ class SettingsProxy void setReversedEdgeDirection(bool reversedEdgeDirection); + bool raiseEdgeLabelOnMouseHover() const; + bool raiseNodeOnMouseHover() const; + void setRaiseEdgeLabelOnMouseHover(bool raiseEdgeLabelOnMouseHover); + void setRaiseNodeOnMouseHover(bool raiseNodeOnMouseHover); bool selectNodeGroupByIntersection() const; @@ -151,6 +155,8 @@ class SettingsProxy const QString m_invertedControlsSettingKey = "invertedControls"; + const QString m_raiseEdgeLabelOnMouseHoverKey = "raiseEdgeLabelOnMouseHoverKey"; + const QString m_raiseNodeOnMouseHoverKey = "raiseNodeOnMouseHoverKey"; bool m_autoload = false; @@ -177,6 +183,8 @@ class SettingsProxy bool m_reversedEdgeDirection = false; + bool m_raiseEdgeLabelOnMouseHover = true; + bool m_raiseNodeOnMouseHover = true; bool m_selectNodeGroupByIntersection = false; diff --git a/src/translations/heimer_de.ts b/src/translations/heimer_de.ts index 974a97d2..a773372b 100644 --- a/src/translations/heimer_de.ts +++ b/src/translations/heimer_de.ts @@ -273,7 +273,7 @@ Dialogs::EditingTab - + Selecting Nodes Auswählen von Knoten @@ -287,57 +287,67 @@ Automatisches Speichern - + Autosave feature will automatically save your mind map on every modification after it has been initially saved once. Die Autosave-Funktion speichert Ihre Mindmap automatisch bei jeder Änderung, nachdem sie einmal gespeichert wurde. - + Enable autosave Automatisches Speichern einschalten - + Raise node on mouse hover - + Moving mouse cursor on a zoomed-out node will raise it for easier editing. - + Autoload feature will automatically load your recent mind map on application start. - + Enable autoload - + + Raise edge label on mouse hover + + + + The rectangle selection will select nodes also by intersection instead of inclusion only. + Moving mouse cursor on a zoomed-out edge label will raise it for easier editing. + + + + File Operations - + Controls - + Scroll the view with a modifier key pressed and select a group of items without a modifier key being pressed. - + Inverted controls diff --git a/src/translations/heimer_en.ts b/src/translations/heimer_en.ts index a3575ff7..b6c0827a 100644 --- a/src/translations/heimer_en.ts +++ b/src/translations/heimer_en.ts @@ -188,7 +188,7 @@ Dialogs::EditingTab - + Selecting Nodes @@ -198,57 +198,67 @@ - + Raise node on mouse hover - + Moving mouse cursor on a zoomed-out node will raise it for easier editing. - + Autosave feature will automatically save your mind map on every modification after it has been initially saved once. - + Enable autosave - + Autoload feature will automatically load your recent mind map on application start. - + Enable autoload - + + Raise edge label on mouse hover + + + + The rectangle selection will select nodes also by intersection instead of inclusion only. + Moving mouse cursor on a zoomed-out edge label will raise it for easier editing. + + + + File Operations - + Controls - + Scroll the view with a modifier key pressed and select a group of items without a modifier key being pressed. - + Inverted controls diff --git a/src/translations/heimer_es.ts b/src/translations/heimer_es.ts index 6e948871..d4b4cc43 100644 --- a/src/translations/heimer_es.ts +++ b/src/translations/heimer_es.ts @@ -277,7 +277,7 @@ Dialogs::EditingTab - + Selecting Nodes Selección de nodos @@ -291,57 +291,67 @@ Autoguardado - + Autosave feature will automatically save your mind map on every modification after it has been initially saved once. Tras haber sido guardado el mapa mental por primera vez, la función de autoguardado lo volverá a guardar de manera automática cada vez que se modifique. - + Enable autosave Activar autoguardado - + Raise node on mouse hover - + Moving mouse cursor on a zoomed-out node will raise it for easier editing. - + Autoload feature will automatically load your recent mind map on application start. - + Enable autoload - + + Raise edge label on mouse hover + + + + The rectangle selection will select nodes also by intersection instead of inclusion only. + Moving mouse cursor on a zoomed-out edge label will raise it for easier editing. + + + + File Operations - + Controls - + Scroll the view with a modifier key pressed and select a group of items without a modifier key being pressed. - + Inverted controls diff --git a/src/translations/heimer_fi.ts b/src/translations/heimer_fi.ts index f1cd2ca8..591ff7f8 100644 --- a/src/translations/heimer_fi.ts +++ b/src/translations/heimer_fi.ts @@ -340,7 +340,7 @@ Dialogs::EditingTab - + Selecting Nodes Solmujen Valitseminen @@ -354,12 +354,12 @@ Automaattinen tallennus - + Autosave feature will automatically save your mind map on every modification after it has been initially saved once. Automaattinen tallennus tallentaa käsitekarttasi jokaisen muutoksen jälkeen siitä lähtien kun käsitekartta on ensimmäisen kerran tallennettu. - + Enable autosave Aktivoi automaattinen tallennus @@ -368,42 +368,52 @@ Automaattinen lataaminen - + Autoload feature will automatically load your recent mind map on application start. Automaattinen lataaminen avaa viimeisimmän käsitekartan aina sovelluksen käynnistyessä. - + Enable autoload Aktivoi automaattinen lataaminen + Raise edge label on mouse hover + Kohota kaaren teksti kun hiiri on sen päällä + + + Raise node on mouse hover Kohota solmu kun hiiri on sen päällä - + The rectangle selection will select nodes also by intersection instead of inclusion only. Suorakaidevalinnan ei tarvitse sisältää valittavia solmuja kokonaan vaan se valitsee myös leikkaavat solmut. - + + Moving mouse cursor on a zoomed-out edge label will raise it for easier editing. + Hiiren siirtäminen loitonnetun kaaren tekstin päälle kohottaa sen, jotta editoiminen olisi helpompaa. + + + Moving mouse cursor on a zoomed-out node will raise it for easier editing. Hiiren siirtäminen loitonnetun solmun päälle kohottaa sen, jotta editoiminen olisi helpompaa. - + File Operations Tiedosto-operaatiot - + Controls Kontrollit - + Scroll the view with a modifier key pressed and select a group of items without a modifier key being pressed. Vieritä näkymää muokkausnäppäintä painamalla ja valitse joukko objekteja ilman muokkausnäppäimen painamista. @@ -416,7 +426,7 @@ Käänteiset kontrollit vierittää näkymää muokkausnäppäimellä (Ctrl) ja valitsee joukon objekteja ilman muokkausnäppäintä. - + Inverted controls Käänteiset kontrollit diff --git a/src/translations/heimer_fr.ts b/src/translations/heimer_fr.ts index 0620cc68..3de3754f 100644 --- a/src/translations/heimer_fr.ts +++ b/src/translations/heimer_fr.ts @@ -264,7 +264,7 @@ Dialogs::EditingTab - + Selecting Nodes @@ -274,57 +274,67 @@ - + Raise node on mouse hover - + Moving mouse cursor on a zoomed-out node will raise it for easier editing. - + Autosave feature will automatically save your mind map on every modification after it has been initially saved once. - + Enable autosave - + Autoload feature will automatically load your recent mind map on application start. - + Enable autoload - + + Raise edge label on mouse hover + + + + The rectangle selection will select nodes also by intersection instead of inclusion only. + Moving mouse cursor on a zoomed-out edge label will raise it for easier editing. + + + + File Operations - + Controls - + Scroll the view with a modifier key pressed and select a group of items without a modifier key being pressed. - + Inverted controls diff --git a/src/translations/heimer_it.ts b/src/translations/heimer_it.ts index f39d4e1c..95b337e3 100644 --- a/src/translations/heimer_it.ts +++ b/src/translations/heimer_it.ts @@ -328,7 +328,7 @@ Dialogs::EditingTab - + Selecting Nodes Selezione nodi @@ -342,12 +342,12 @@ Salvataggio automatico - + Autosave feature will automatically save your mind map on every modification after it has been initially saved once. La funzione di salvataggio automatico salverà automaticamente la tua mappa mentale su ogni modifica dopo che è stata inizialmente salvata una volta. - + Enable autosave Abilita il salvataggio automatico @@ -356,47 +356,57 @@ Caricamento automatico - + Autoload feature will automatically load your recent mind map on application start. La funzione di caricamento automatico caricherà automaticamente la tua recente mappa mentale all'avvio dell'applicazione. - + Enable autoload Abilita il caricamento automatico + Raise edge label on mouse hover + + + + Raise node on mouse hover - + The rectangle selection will select nodes also by intersection instead of inclusion only. La selezione del rettangolo selezionerà i nodi anche per intersezione anziché solo per inclusione. - + + Moving mouse cursor on a zoomed-out edge label will raise it for easier editing. + + + + Moving mouse cursor on a zoomed-out node will raise it for easier editing. - + File Operations Operazioni sui file - + Controls Controlli - + Scroll the view with a modifier key pressed and select a group of items without a modifier key being pressed. Scorri la vista con un tasto modificatore premuto e seleziona un gruppo di elementi senza che venga premuto un tasto modificatore. - + Inverted controls Controlli invertiti diff --git a/src/translations/heimer_nl.ts b/src/translations/heimer_nl.ts index bd0e7a73..d39da299 100644 --- a/src/translations/heimer_nl.ts +++ b/src/translations/heimer_nl.ts @@ -332,7 +332,7 @@ Dialogs::EditingTab - + Selecting Nodes Knopen selecteren @@ -346,57 +346,67 @@ Automatisch opslaan - + Autosave feature will automatically save your mind map on every modification after it has been initially saved once. Automatisch opslaan slaat je mindmap op telkens als je een aanpassing hebt gedaan. - + Enable autosave Automatisch opslaan inschakelen - + Raise node on mouse hover - + Moving mouse cursor on a zoomed-out node will raise it for easier editing. - + Autoload feature will automatically load your recent mind map on application start. - + Enable autoload - + + Raise edge label on mouse hover + + + + The rectangle selection will select nodes also by intersection instead of inclusion only. De rechthoekselectie selecteert knopen per intersectie. + Moving mouse cursor on a zoomed-out edge label will raise it for easier editing. + + + + File Operations Bestandsacties - + Controls Bediening - + Scroll the view with a modifier key pressed and select a group of items without a modifier key being pressed. Verschuif de weergave door een bewerktoets ingedrukt te houden en selecteer een groep items zonder indrukken. - + Inverted controls Omgekeerde bediening diff --git a/src/translations/heimer_zh.ts b/src/translations/heimer_zh.ts index 89a308d9..3c1137cf 100644 --- a/src/translations/heimer_zh.ts +++ b/src/translations/heimer_zh.ts @@ -277,7 +277,7 @@ Dialogs::EditingTab - + Selecting Nodes 选择节点 @@ -287,57 +287,67 @@ 按交点选择节点组 - + Raise node on mouse hover - + Moving mouse cursor on a zoomed-out node will raise it for easier editing. - + Autosave feature will automatically save your mind map on every modification after it has been initially saved once. - + Enable autosave - + Autoload feature will automatically load your recent mind map on application start. - + Enable autoload - + + Raise edge label on mouse hover + + + + The rectangle selection will select nodes also by intersection instead of inclusion only. + Moving mouse cursor on a zoomed-out edge label will raise it for easier editing. + + + + File Operations - + Controls - + Scroll the view with a modifier key pressed and select a group of items without a modifier key being pressed. - + Inverted controls diff --git a/src/view/dialogs/editing_tab.cpp b/src/view/dialogs/editing_tab.cpp index b7e21bef..6e0f519a 100644 --- a/src/view/dialogs/editing_tab.cpp +++ b/src/view/dialogs/editing_tab.cpp @@ -30,6 +30,7 @@ namespace Dialogs { EditingTab::EditingTab(QString name, QWidget * parent) : SettingsTabBase { name, parent } , m_selectNodeGroupByIntersectionCheckBox(new QCheckBox(tr("Select node group by intersection"))) + , m_raiseEdgeLabelOnMouseHoverCheckBox(new QCheckBox(tr("Raise edge label on mouse hover"))) , m_raiseNodeOnMouseHoverCheckBox(new QCheckBox(tr("Raise node on mouse hover"))) , m_invertedControlsCheckBox(new QCheckBox(tr("Inverted controls"))) , m_autoloadCheckBox(new QCheckBox(tr("Enable autoload"))) @@ -42,6 +43,8 @@ void EditingTab::apply() { settingsProxy()->setSelectNodeGroupByIntersection(m_selectNodeGroupByIntersectionCheckBox->isChecked()); + settingsProxy()->setRaiseEdgeLabelOnMouseHover(m_raiseEdgeLabelOnMouseHoverCheckBox->isChecked()); + settingsProxy()->setRaiseNodeOnMouseHover(m_raiseNodeOnMouseHoverCheckBox->isChecked()); settingsProxy()->setAutosave(m_autosaveCheckBox->isChecked()); @@ -61,6 +64,9 @@ void EditingTab::initWidgets() m_selectNodeGroupByIntersectionCheckBox->setToolTip(tr("The rectangle selection will select nodes also by intersection instead of inclusion only.")); editingGroupLayout->addWidget(m_selectNodeGroupByIntersectionCheckBox); + m_raiseEdgeLabelOnMouseHoverCheckBox->setToolTip(tr("Moving mouse cursor on a zoomed-out edge label will raise it for easier editing.")); + editingGroupLayout->addWidget(m_raiseEdgeLabelOnMouseHoverCheckBox); + m_raiseNodeOnMouseHoverCheckBox->setToolTip(tr("Moving mouse cursor on a zoomed-out node will raise it for easier editing.")); editingGroupLayout->addWidget(m_raiseNodeOnMouseHoverCheckBox); @@ -86,6 +92,8 @@ void EditingTab::setActiveSettings() { m_selectNodeGroupByIntersectionCheckBox->setChecked(settingsProxy()->selectNodeGroupByIntersection()); + m_raiseEdgeLabelOnMouseHoverCheckBox->setChecked(settingsProxy()->raiseEdgeLabelOnMouseHover()); + m_raiseNodeOnMouseHoverCheckBox->setChecked(settingsProxy()->raiseNodeOnMouseHover()); m_autosaveCheckBox->setChecked(settingsProxy()->autosave()); diff --git a/src/view/dialogs/editing_tab.hpp b/src/view/dialogs/editing_tab.hpp index 7ba4f3a7..fa732ecc 100644 --- a/src/view/dialogs/editing_tab.hpp +++ b/src/view/dialogs/editing_tab.hpp @@ -41,6 +41,8 @@ class EditingTab : public SettingsTabBase QCheckBox * m_selectNodeGroupByIntersectionCheckBox = nullptr; + QCheckBox * m_raiseEdgeLabelOnMouseHoverCheckBox = nullptr; + QCheckBox * m_raiseNodeOnMouseHoverCheckBox = nullptr; QCheckBox * m_invertedControlsCheckBox = nullptr; diff --git a/src/view/editor_view.cpp b/src/view/editor_view.cpp index 97d63b76..779f6ddd 100644 --- a/src/view/editor_view.cpp +++ b/src/view/editor_view.cpp @@ -42,7 +42,7 @@ #include "scene_items/edge_text_edit.hpp" #include "scene_items/node.hpp" #include "scene_items/node_handle.hpp" -#include "scene_items/scene_item_base.hpp" +#include "scene_items/scene_item_base_graphics_item.hpp" #include "widgets/status_label.hpp" #include "simple_logger.hpp" @@ -501,12 +501,12 @@ void EditorView::removeShadowEffectsDuringDrag() void EditorView::updateShadowEffectsBasedOnItemVisiblity() { - std::unordered_set enabledItems; + std::unordered_set enabledItems; const int marginFraction = 20; const int glitchMargin = rect().width() / marginFraction; for (auto && item : scene()->items(mapToScene(rect().adjusted(-glitchMargin, -glitchMargin, glitchMargin, glitchMargin)))) { - if (const auto sceneItem = dynamic_cast(item); sceneItem) { + if (const auto sceneItem = dynamic_cast(item); sceneItem) { sceneItem->enableShadowEffect(true); enabledItems.insert(sceneItem); } @@ -514,7 +514,7 @@ void EditorView::updateShadowEffectsBasedOnItemVisiblity() if (SC::instance().settingsProxy()->optimizeShadowEffects()) { for (auto && item : scene()->items()) { - if (const auto sceneItem = dynamic_cast(item); sceneItem) { + if (const auto sceneItem = dynamic_cast(item); sceneItem) { if (!enabledItems.count(sceneItem)) { sceneItem->enableShadowEffect(false); } diff --git a/src/view/scene_items/edge.cpp b/src/view/scene_items/edge.cpp index 5072b350..91d66415 100644 --- a/src/view/scene_items/edge.cpp +++ b/src/view/scene_items/edge.cpp @@ -15,6 +15,7 @@ #include "edge.hpp" +#include "../../application/application_service.hpp" #include "../../application/service_container.hpp" #include "../../application/settings_proxy.hpp" #include "../../common/test_mode.hpp" @@ -29,6 +30,7 @@ #include "simple_logger.hpp" #include +#include #include #include #include @@ -37,7 +39,6 @@ #include #include #include - #include #include @@ -47,24 +48,24 @@ namespace SceneItems { static const auto TAG = "Edge"; Edge::Edge(NodeP sourceNode, NodeP targetNode, bool enableAnimations, bool enableLabels) - : m_settingsProxy(SC::instance().settingsProxy()) - , m_edgeModel(std::make_unique(m_settingsProxy->reversedEdgeDirection(), - EdgeModel::Style { m_settingsProxy->edgeArrowMode() })) - , m_sourceNode(sourceNode) - , m_targetNode(targetNode) - , m_enableAnimations(enableAnimations) - , m_enableLabels(enableLabels) - , m_sourceDot(new EdgeDot(this)) - , m_targetDot(new EdgeDot(this)) - , m_label(new EdgeTextEdit(this)) - , m_condensedLabel(new EdgeTextEdit(this)) - , m_line(new QGraphicsLineItem(this)) - , m_arrowheadBeginLeft(new QGraphicsLineItem(this)) - , m_arrowheadBeginRight(new QGraphicsLineItem(this)) - , m_arrowheadEndLeft(new QGraphicsLineItem(this)) - , m_arrowheadEndRight(new QGraphicsLineItem(this)) - , m_sourceDotSizeAnimation(new QPropertyAnimation(m_sourceDot, "scale", this)) - , m_targetDotSizeAnimation(new QPropertyAnimation(m_targetDot, "scale", this)) + : m_settingsProxy { SC::instance().settingsProxy() } + , m_edgeModel { std::make_unique(m_settingsProxy->reversedEdgeDirection(), + EdgeModel::Style { m_settingsProxy->edgeArrowMode() }) } + , m_sourceNode { sourceNode } + , m_targetNode { targetNode } + , m_enableAnimations { enableAnimations } + , m_enableLabels { enableLabels } + , m_sourceDot { new EdgeDot(this) } + , m_targetDot { new EdgeDot(this) } + , m_label { new EdgeTextEdit(this) } + , m_condensedLabel { new EdgeTextEdit(this) } + , m_line { new QGraphicsLineItem(this) } + , m_arrowheadBeginLeft { new QGraphicsLineItem(this) } + , m_arrowheadBeginRight { new QGraphicsLineItem(this) } + , m_arrowheadEndLeft { new QGraphicsLineItem(this) } + , m_arrowheadEndRight { new QGraphicsLineItem(this) } + , m_sourceDotSizeAnimation { new QPropertyAnimation { m_sourceDot, "scale", this } } + , m_targetDotSizeAnimation { new QPropertyAnimation { m_targetDot, "scale", this } } { setAcceptHoverEvents(enableAnimations); @@ -80,18 +81,18 @@ Edge::Edge(NodeP sourceNode, NodeP targetNode, bool enableAnimations, bool enabl } Edge::Edge(NodeS sourceNode, NodeS targetNode, bool enableAnimations, bool enableLabel) - : Edge(sourceNode.get(), targetNode.get(), enableAnimations, enableLabel) + : Edge { sourceNode.get(), targetNode.get(), enableAnimations, enableLabel } { } Edge::Edge(EdgeCR other, GraphCR graph) - : Edge(graph.getNode(other.m_sourceNode->index()).get(), graph.getNode(other.m_targetNode->index()).get()) + : Edge { graph.getNode(other.m_sourceNode->index()).get(), graph.getNode(other.m_targetNode->index()).get() } { copyData(other); } Edge::Edge(EdgeCR other) - : Edge(nullptr, nullptr) + : Edge { nullptr, nullptr } { copyData(other); } @@ -100,7 +101,7 @@ void Edge::hoverEnterEvent(QGraphicsSceneHoverEvent * event) { m_labelVisibilityTimer.stop(); - setLabelVisible(true, EdgeTextEdit::VisibilityChangeReason::Focused); + setLabelFocused(); QGraphicsItem::hoverEnterEvent(event); } @@ -109,6 +110,12 @@ void Edge::hoverLeaveEvent(QGraphicsSceneHoverEvent * event) { m_labelVisibilityTimer.start(); + if (m_settingsProxy->raiseEdgeLabelOnMouseHover()) { + m_label->setParentItem(this); + m_label->setTransformOriginPoint(m_line->line().center()); + m_label->lowerWithAnimation(); + } + QGraphicsItem::hoverLeaveEvent(event); } @@ -173,13 +180,13 @@ double Edge::length() const void Edge::initializeDotAnimations() { - const int animationDurationMs = 2000; + const auto animationDuration = std::chrono::milliseconds { 2000 }; - m_sourceDotSizeAnimation->setDuration(animationDurationMs); + m_sourceDotSizeAnimation->setDuration(animationDuration.count()); m_sourceDotSizeAnimation->setStartValue(1.0); m_sourceDotSizeAnimation->setEndValue(0.0); - m_targetDotSizeAnimation->setDuration(animationDurationMs); + m_targetDotSizeAnimation->setDuration(animationDuration.count()); m_targetDotSizeAnimation->setStartValue(1.0); m_targetDotSizeAnimation->setEndValue(0.0); } @@ -216,25 +223,18 @@ void Edge::connectLabel() connect(m_label, &TextEdit::undoPointRequested, this, &Edge::undoPointRequested); - connect(m_label, &EdgeTextEdit::hoverEntered, this, [=] { - setLabelVisible(true, EdgeTextEdit::VisibilityChangeReason::Focused); - }); + connect(m_label, &EdgeTextEdit::hoverEntered, this, &Edge::setLabelFocused); - connect(m_label, &EdgeTextEdit::visibilityTimeout, this, [=] { - setLabelVisible(false); - }); + connect(m_label, &EdgeTextEdit::visibilityTimeout, this, &Edge::hideLabelOnTimeout); } void Edge::initializeLabelVisibilityTimer() { m_labelVisibilityTimer.setSingleShot(true); - const int labelDurationMs = 2000; - m_labelVisibilityTimer.setInterval(labelDurationMs); + m_labelVisibilityTimer.setInterval(std::chrono::milliseconds { 2000 }); - connect(&m_labelVisibilityTimer, &QTimer::timeout, this, [=] { - setLabelVisible(false); - }); + connect(&m_labelVisibilityTimer, &QTimer::timeout, this, &Edge::hideLabelOnTimeout); } void Edge::initializeLabels() @@ -270,17 +270,17 @@ void Edge::setArrowHeadPen(const QPen & pen) bool Edge::isEnoughSpaceForLabel() const { - return m_label->scene() && !m_label->sceneBoundingRect().intersects(sourceNode().sceneBoundingRect()) && // - !m_label->sceneBoundingRect().intersects(targetNode().sceneBoundingRect()); + return m_label->scene() && !m_label->sceneBoundingRect().intersects(sourceNode().positionedBoundingRect()) && // + !m_label->sceneBoundingRect().intersects(targetNode().positionedBoundingRect()); } bool Edge::isEnoughSpaceForCondensedLabel() const { - return m_condensedLabel->scene() && !m_condensedLabel->sceneBoundingRect().intersects(sourceNode().sceneBoundingRect()) && // - !m_condensedLabel->sceneBoundingRect().intersects(targetNode().sceneBoundingRect()); + return m_condensedLabel->scene() && !m_condensedLabel->sceneBoundingRect().intersects(sourceNode().positionedBoundingRect()) && // + !m_condensedLabel->sceneBoundingRect().intersects(targetNode().positionedBoundingRect()); } -bool Edge::isCondensedLabelTextShoterThanLabelText() const +bool Edge::isCondensedLabelTextShorterThanLabelText() const { return m_condensedLabel->text().length() < m_label->text().length(); } @@ -290,54 +290,69 @@ QPointF Edge::lineCenter() const return m_line->line().center(); } -void Edge::toggleLabelVisibilityOnGeometryChange() +void Edge::updateLabelAndCondensedLabelVisibilitiesBasedOnSpaceAvailable() { const bool isLabelVisible = isEnoughSpaceForLabel() && !m_label->text().isEmpty(); m_label->setVisible(isLabelVisible); - m_condensedLabel->setVisible(!isLabelVisible && isEnoughSpaceForCondensedLabel() && isCondensedLabelTextShoterThanLabelText()); + m_condensedLabel->setVisible(!isLabelVisible && isEnoughSpaceForCondensedLabel() && isCondensedLabelTextShorterThanLabelText()); } -void Edge::showLabelWhenFocused() +void Edge::showLabelAndHideCondensedLabel() { m_label->setVisible(true); - m_label->setParentItem(nullptr); - m_label->setGraphicsEffect(GraphicsFactory::createDropShadowEffect(m_settingsProxy->shadowEffect(), false)); m_condensedLabel->setVisible(false); } -void Edge::showOrHideLabelExplicitly(bool show) +void Edge::showLabelAndCondensedLabel() +{ + m_label->setVisible(true); + m_condensedLabel->setVisible(true); +} + +void Edge::showLabelExplicitly() { - m_label->setVisible(show); - m_condensedLabel->setVisible(show); + showLabelAndCondensedLabel(); +} + +void Edge::hideLabelExplicitly() +{ + hideLabelAndCondensedLabel(); } void Edge::hideLabelOnTimeout() +{ + hideLabelAndShowCondensedLabelIfEmptyOrNotEnoughSpace(); +} + +void Edge::hideLabelAndCondensedLabel() +{ + m_label->setVisible(false); + m_condensedLabel->setVisible(false); +} + +void Edge::hideLabelAndShowCondensedLabelIfEmptyOrNotEnoughSpace() { if ((m_label->text().isEmpty() || (!m_label->text().isEmpty() && !isEnoughSpaceForLabel())) && !m_label->hasFocus()) { m_label->setVisible(false); - m_condensedLabel->setVisible(isEnoughSpaceForCondensedLabel() && isCondensedLabelTextShoterThanLabelText()); + m_condensedLabel->setVisible(isEnoughSpaceForCondensedLabel() && isCondensedLabelTextShorterThanLabelText()); } } -void Edge::setLabelVisible(bool visible, EdgeTextEdit::VisibilityChangeReason visibilityChangeReason) +double Edge::calculateTargetScale() const { - switch (visibilityChangeReason) { - case EdgeTextEdit::VisibilityChangeReason::AvailableSpaceChanged: - toggleLabelVisibilityOnGeometryChange(); - break; - case EdgeTextEdit::VisibilityChangeReason::Explicit: - showOrHideLabelExplicitly(visible); - break; - case EdgeTextEdit::VisibilityChangeReason::Focused: - if (visible) { - showLabelWhenFocused(); - } - break; - case EdgeTextEdit::VisibilityChangeReason::Timeout: - if (!visible) { - hideLabelOnTimeout(); - } - break; + const auto minimumSizeRatio = 0.025; + const auto defaultRaisedSizeRatio = 1.1; + const auto currentSizeRatio = SC::instance().applicationService()->normalizedSizeInView(m_label->boundingRect()).height() / (m_label->text().count('\n') + 1); + return std::max(defaultRaisedSizeRatio, currentSizeRatio < minimumSizeRatio ? minimumSizeRatio / currentSizeRatio : defaultRaisedSizeRatio); +} + +void Edge::setLabelFocused() +{ + showLabelAndHideCondensedLabel(); + + if (m_settingsProxy->raiseEdgeLabelOnMouseHover()) { + m_label->setTransformOriginPoint(m_line->line().center()); + m_label->raiseWithAnimation(calculateTargetScale()); } } @@ -387,7 +402,11 @@ void Edge::setText(const QString & text) m_edgeModel->text = text; if (m_enableLabels) { m_label->setText(text); - setLabelVisible(!text.isEmpty()); + if (!text.isEmpty()) { + showLabelExplicitly(); + } else { + hideLabelExplicitly(); + } } } @@ -529,8 +548,7 @@ void Edge::updateArrowhead() void Edge::triggerAnimationOnRelativeConnectionLocationChangeAtSourcePosition() { - const auto newRelativeSourcePos = m_line->line().p1() - sourceNode().pos(); - if (m_previousRelativeSourcePos != newRelativeSourcePos) { + if (const auto newRelativeSourcePos = m_line->line().p1() - sourceNode().pos(); m_previousRelativeSourcePos != newRelativeSourcePos) { m_previousRelativeSourcePos = newRelativeSourcePos; m_sourceDotSizeAnimation->stop(); m_sourceDotSizeAnimation->start(); @@ -543,8 +561,7 @@ void Edge::triggerAnimationOnRelativeConnectionLocationChangeAtSourcePosition() void Edge::triggerAnimationOnRelativeConnectionLocationChangeAtTargetPosition() { // Trigger new animation if relative connection location has changed - const auto newRelativeTargetPos = m_line->line().p2() - targetNode().pos(); - if (m_previousRelativeTargetPos != newRelativeTargetPos) { + if (const auto newRelativeTargetPos = m_line->line().p2() - targetNode().pos(); m_previousRelativeTargetPos != newRelativeTargetPos) { m_previousRelativeTargetPos = newRelativeTargetPos; m_targetDotSizeAnimation->stop(); m_targetDotSizeAnimation->start(); @@ -562,14 +579,17 @@ void Edge::updateDots() } } -void Edge::updateLabel(LabelUpdateReason lur) +void Edge::updateLabel() { m_label->setPos(lineCenter() - QPointF(m_label->boundingRect().width(), m_label->boundingRect().height()) * 0.5); m_condensedLabel->setPos(lineCenter() - QPointF(m_condensedLabel->boundingRect().width(), m_condensedLabel->boundingRect().height()) * 0.5); - // Toggle visibility according to space available if geometry changed - if (lur == LabelUpdateReason::EdgeGeometryChanged) { - setLabelVisible(m_label->isVisible(), EdgeTextEdit::VisibilityChangeReason::AvailableSpaceChanged); - } +} + +void Edge::updateLabelOnGeometryChange() +{ + updateLabel(); + + updateLabelAndCondensedLabelVisibilitiesBasedOnSpaceAvailable(); } void Edge::setTargetNode(NodeR targetNode) @@ -679,7 +699,7 @@ void Edge::updateLine() updateArrowhead(); if (m_enableLabels) { - updateLabel(LabelUpdateReason::EdgeGeometryChanged); + updateLabelOnGeometryChange(); } } diff --git a/src/view/scene_items/edge.hpp b/src/view/scene_items/edge.hpp index 3ba3886d..aac45c93 100644 --- a/src/view/scene_items/edge.hpp +++ b/src/view/scene_items/edge.hpp @@ -23,7 +23,7 @@ #include "../../common/types.hpp" #include "edge_model.hpp" #include "edge_text_edit.hpp" -#include "scene_item_base.hpp" +#include "scene_item_base_graphics_item.hpp" class QFont; class QGraphicsEllipseItem; @@ -40,7 +40,7 @@ class EdgeDot; class Node; //! A graphic representation of a graph edge between nodes. -class Edge : public SceneItemBase +class Edge : public SceneItemBaseGraphicsItem { Q_OBJECT @@ -138,11 +138,13 @@ public slots: private: QPen buildPen(bool ignoreDashSetting = false) const; + double calculateTargetScale() const; + void connectLabel(); void copyData(EdgeCR other); - void hideLabelOnTimeout(); + void hideLabelAndShowCondensedLabelIfEmptyOrNotEnoughSpace(); void initializeDots(); @@ -156,19 +158,27 @@ public slots: bool isEnoughSpaceForCondensedLabel() const; - bool isCondensedLabelTextShoterThanLabelText() const; + bool isCondensedLabelTextShorterThanLabelText() const; QPointF lineCenter() const; void setArrowHeadPen(const QPen & pen); - void setLabelVisible(bool visible, EdgeTextEdit::VisibilityChangeReason visibilityChangeReason = EdgeTextEdit::VisibilityChangeReason::Timeout); + void setLabelFocused(); + + void updateLabelAndCondensedLabelVisibilitiesBasedOnSpaceAvailable(); + + void showLabelAndHideCondensedLabel(); - void toggleLabelVisibilityOnGeometryChange(); + void showLabelAndCondensedLabel(); - void showLabelWhenFocused(); + void showLabelExplicitly(); + + void hideLabelExplicitly(); + + void hideLabelOnTimeout(); - void showOrHideLabelExplicitly(bool show); + void hideLabelAndCondensedLabel(); void removeSelfFromNodes(); @@ -190,13 +200,9 @@ public slots: void updateLineGeometry(); - enum class LabelUpdateReason - { - Default, - EdgeGeometryChanged - }; + void updateLabel(); - void updateLabel(LabelUpdateReason lur = LabelUpdateReason::Default); + void updateLabelOnGeometryChange(); SettingsProxyS m_settingsProxy; @@ -210,9 +216,9 @@ public slots: bool m_selected = false; - bool m_enableAnimations; + const bool m_enableAnimations; - bool m_enableLabels; + const bool m_enableLabels; EdgeDot * m_sourceDot; diff --git a/src/view/scene_items/edge_text_edit.cpp b/src/view/scene_items/edge_text_edit.cpp index 11e26647..41ec2a8e 100644 --- a/src/view/scene_items/edge_text_edit.cpp +++ b/src/view/scene_items/edge_text_edit.cpp @@ -15,7 +15,6 @@ #include "edge_text_edit.hpp" -#include "../../common/constants.hpp" #include "edge.hpp" #include "graphics_factory.hpp" @@ -31,17 +30,21 @@ EdgeTextEdit::EdgeTextEdit(EdgeP parentItem) { setAcceptHoverEvents(true); + initializeVisibilityTimer(); + m_opacityAnimation.setDuration(150); QGraphicsItem::setVisible(false); + setOpacity(0); +} +void EdgeTextEdit::initializeVisibilityTimer() +{ m_visibilityTimer.setSingleShot(true); - m_visibilityTimer.setInterval(2000); + m_visibilityTimer.setInterval(1000); - connect(&m_visibilityTimer, &QTimer::timeout, this, [=] { - emit visibilityTimeout(); - }); + connect(&m_visibilityTimer, &QTimer::timeout, this, &EdgeTextEdit::visibilityTimeout); } EdgeP EdgeTextEdit::edge() const @@ -49,13 +52,6 @@ EdgeP EdgeTextEdit::edge() const return m_edge; } -QRectF EdgeTextEdit::boundingRect() const -{ - const int horPadding = 3; - const int verPadding = 1; - return QGraphicsTextItem::boundingRect().adjusted(-horPadding, -verPadding, horPadding, verPadding); -} - void EdgeTextEdit::hoverEnterEvent(QGraphicsSceneHoverEvent * event) { m_visibilityTimer.stop(); @@ -72,36 +68,46 @@ void EdgeTextEdit::hoverLeaveEvent(QGraphicsSceneHoverEvent * event) TextEdit::hoverLeaveEvent(event); } -void EdgeTextEdit::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) +void EdgeTextEdit::paintOutline(QPainter * painter, const QStyleOptionGraphicsItem * option) { - TextEdit::paint(painter, option, widget); - - // Outline QPainterPath path; path.addRect(option->rect); painter->setRenderHint(QPainter::Antialiasing); painter->strokePath(path, GraphicsFactory::createOutlinePen(backgroundColor())); } -void EdgeTextEdit::setAnimationConfig(bool visible) +void EdgeTextEdit::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) { - if (visible) { - QGraphicsItem::setVisible(true); - m_opacityAnimation.setStartValue(opacity()); - m_opacityAnimation.setEndValue(1.0); - m_opacityAnimation.stop(); - m_opacityAnimation.start(); - } else { - m_opacityAnimation.setStartValue(opacity()); - m_opacityAnimation.setEndValue(0.0); - m_opacityAnimation.stop(); - m_opacityAnimation.start(); - } + TextEdit::paint(painter, option, widget); + + paintOutline(painter, option); +} + +void EdgeTextEdit::hideAnimated() +{ + m_opacityAnimation.setStartValue(opacity()); + m_opacityAnimation.setEndValue(0.0); + m_opacityAnimation.stop(); + m_opacityAnimation.start(); +} + +void EdgeTextEdit::showAnimated() +{ + QGraphicsItem::setVisible(true); + + m_opacityAnimation.setStartValue(opacity()); + m_opacityAnimation.setEndValue(1.0); + m_opacityAnimation.stop(); + m_opacityAnimation.start(); } void EdgeTextEdit::setVisible(bool visible) { - setAnimationConfig(visible); + if (visible) { + showAnimated(); + } else { + hideAnimated(); + } } void EdgeTextEdit::updateDueToLostFocus() diff --git a/src/view/scene_items/edge_text_edit.hpp b/src/view/scene_items/edge_text_edit.hpp index 64be4144..18767d41 100644 --- a/src/view/scene_items/edge_text_edit.hpp +++ b/src/view/scene_items/edge_text_edit.hpp @@ -17,7 +17,7 @@ #define EDGE_TEXT_EDIT_HPP #include "../../common/types.hpp" -#include "scene_item_base.hpp" +#include "scene_item_base_graphics_item.hpp" #include "text_edit.hpp" #include @@ -36,14 +36,6 @@ class EdgeTextEdit : public TextEdit EdgeP edge() const; - enum class VisibilityChangeReason - { - Timeout, - Explicit, - Focused, - AvailableSpaceChanged - }; - void setVisible(bool visible); void updateDueToLostFocus(); @@ -54,17 +46,19 @@ class EdgeTextEdit : public TextEdit virtual void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) override; - //! Used to add some padding the the text field. - QRectF boundingRect() const override; - signals: - void hoverEntered(); void visibilityTimeout(); private: - void setAnimationConfig(bool visible); + void hideAnimated(); + + void showAnimated(); + + void initializeVisibilityTimer(); + + void paintOutline(QPainter * painter, const QStyleOptionGraphicsItem * option); EdgeP m_edge; diff --git a/src/view/scene_items/node.cpp b/src/view/scene_items/node.cpp index de7dc67f..9b7678fb 100644 --- a/src/view/scene_items/node.cpp +++ b/src/view/scene_items/node.cpp @@ -52,9 +52,9 @@ static const auto TAG = "Node"; NodeP Node::m_lastHoveredNode = nullptr; Node::Node() - : m_settingsProxy(SC::instance().settingsProxy()) - , m_nodeModel(std::make_unique(m_settingsProxy->nodeColor(), m_settingsProxy->nodeTextColor())) - , m_textEdit(new TextEdit(this)) + : m_settingsProxy { SC::instance().settingsProxy() } + , m_nodeModel { std::make_unique(m_settingsProxy->nodeColor(), m_settingsProxy->nodeTextColor()) } + , m_textEdit { new TextEdit(this) } { setAcceptHoverEvents(true); @@ -70,27 +70,13 @@ Node::Node() updateHandlePositions(); - initTextField(); + initializeTextField(); setSelected(false); - - connect(m_textEdit, &TextEdit::textChanged, this, [=](const QString & text) { - setText(text); - adjustSize(); - }); - - connect(m_textEdit, &TextEdit::undoPointRequested, this, &Node::undoPointRequested); - - // Set the background transparent as the TextEdit background will be rendered in Node::paint(). - // The reason for this is that TextEdit's background affects only the area that includes letters - // and we want to render a larger area. - m_textEdit->setBackgroundColor({ 0, 0, 0, 0 }); - - setTextColor(m_nodeModel->textColor); } Node::Node(NodeCR other) - : Node() + : Node {} { setColor(other.m_nodeModel->color); @@ -145,8 +131,9 @@ void Node::removeFromScene() void Node::raiseBody() { const auto currentSizeRatio = SC::instance().applicationService()->normalizedSizeInView(boundingRect()).width(); - const auto targetSizeRatio = 0.1; - const auto targetScale = currentSizeRatio < targetSizeRatio ? targetSizeRatio / currentSizeRatio : 1.1; + const auto minimumSizeRatio = 0.1; + const auto defaultRaisedSizeRatio = 1.1; + const auto targetScale = currentSizeRatio < minimumSizeRatio ? minimumSizeRatio / currentSizeRatio : defaultRaisedSizeRatio; raiseWithAnimation(targetScale); setZValue(static_cast(Layers::Last) + static_cast(Layers::Node)); } @@ -186,14 +173,14 @@ void Node::adjustSize() updateHandlePositions(); - initTextField(); + initializeTextField(); update(); } QRectF Node::boundingRect() const { - const auto size = m_nodeModel->size * targetScale(); + const auto size = m_nodeModel->size; return { -size.width() / 2, -size.height() / 2, size.width(), size.height() }; } @@ -348,7 +335,7 @@ void Node::mousePressEvent(QGraphicsSceneMouseEvent * event) } } -void Node::initTextField() +void Node::initializeTextField() { if (!TestMode::enabled()) { m_textEdit->setTextWidth(-1); @@ -357,6 +344,20 @@ void Node::initTextField() } else { TestMode::logDisabledCode("initTestField"); } + + connect(m_textEdit, &TextEdit::textChanged, this, [=](const QString & text) { + setText(text); + adjustSize(); + }); + + connect(m_textEdit, &TextEdit::undoPointRequested, this, &Node::undoPointRequested); + + // Set the background transparent as the TextEdit background will be rendered in Node::paint(). + // The reason for this is that TextEdit's background affects only the area that includes letters + // and we want to render a larger area. + m_textEdit->setBackgroundColor({ 0, 0, 0, 0 }); + + setTextColor(m_nodeModel->textColor); } void Node::addHandlesToScene() diff --git a/src/view/scene_items/node.hpp b/src/view/scene_items/node.hpp index 63adafee..64a2edf3 100644 --- a/src/view/scene_items/node.hpp +++ b/src/view/scene_items/node.hpp @@ -29,7 +29,7 @@ #include "edge.hpp" #include "edge_point.hpp" #include "node_handle.hpp" -#include "scene_item_base.hpp" +#include "scene_item_base_graphics_item.hpp" class Image; class QGraphicsTextItem; @@ -44,7 +44,7 @@ namespace SceneItems { struct NodeModel; //! Freely placeable target node. -class Node : public SceneItemBase +class Node : public SceneItemBaseGraphicsItem { Q_OBJECT @@ -159,7 +159,7 @@ class Node : public SceneItemBase QRectF expandedTextEditRect() const; - void initTextField(); + void initializeTextField(); void paintBackground(QPainter & painter); diff --git a/src/view/scene_items/node_handle.hpp b/src/view/scene_items/node_handle.hpp index 8d5133b2..2fe43bb6 100644 --- a/src/view/scene_items/node_handle.hpp +++ b/src/view/scene_items/node_handle.hpp @@ -21,13 +21,13 @@ #include #include "../../common/types.hpp" -#include "scene_item_base.hpp" +#include "scene_item_base_graphics_item.hpp" namespace SceneItems { class Node; -class NodeHandle : public SceneItemBase +class NodeHandle : public SceneItemBaseGraphicsItem { Q_OBJECT diff --git a/src/view/scene_items/scene_item_base.cpp b/src/view/scene_items/scene_item_base_graphics_item.cpp similarity index 71% rename from src/view/scene_items/scene_item_base.cpp rename to src/view/scene_items/scene_item_base_graphics_item.cpp index 1b110dd3..ebe84ff2 100644 --- a/src/view/scene_items/scene_item_base.cpp +++ b/src/view/scene_items/scene_item_base_graphics_item.cpp @@ -13,17 +13,17 @@ // You should have received a copy of the GNU General Public License // along with Heimer. If not, see . -#include "scene_item_base.hpp" +#include "scene_item_base_graphics_item.hpp" namespace SceneItems { -SceneItemBase::SceneItemBase() +SceneItemBaseGraphicsItem::SceneItemBaseGraphicsItem() : m_opacityAnimation(this, "opacity") , m_scaleAnimation(this, "scale") { } -void SceneItemBase::appearWithAnimation() +void SceneItemBaseGraphicsItem::appearWithAnimation() { m_scaleAnimation.setDuration(m_animationDuration); m_scaleAnimation.setStartValue(0.0); @@ -38,7 +38,7 @@ void SceneItemBase::appearWithAnimation() m_opacityAnimation.start(); } -void SceneItemBase::disappearWithAnimation() +void SceneItemBaseGraphicsItem::disappearWithAnimation() { m_scaleAnimation.setDuration(m_animationDuration); m_scaleAnimation.setStartValue(scale()); @@ -53,13 +53,13 @@ void SceneItemBase::disappearWithAnimation() m_opacityAnimation.start(); } -void SceneItemBase::removeFromScene() +void SceneItemBaseGraphicsItem::removeFromScene() { } -void SceneItemBase::removeFromSceneWithAnimation() +void SceneItemBaseGraphicsItem::removeFromSceneWithAnimation() { - connect(&m_scaleAnimation, &QPropertyAnimation::finished, this, &SceneItemBase::removeFromScene); + connect(&m_scaleAnimation, &QPropertyAnimation::finished, this, &SceneItemBaseGraphicsItem::removeFromScene); m_scaleAnimation.setDuration(m_animationDuration); m_scaleAnimation.setStartValue(scale()); @@ -68,7 +68,7 @@ void SceneItemBase::removeFromSceneWithAnimation() m_scaleAnimation.start(); } -void SceneItemBase::raiseWithAnimation(double targetScale) +void SceneItemBaseGraphicsItem::raiseWithAnimation(double targetScale) { m_targetScale = targetScale; m_scaleAnimation.setDuration(m_animationDuration); @@ -78,7 +78,7 @@ void SceneItemBase::raiseWithAnimation(double targetScale) m_scaleAnimation.start(); } -void SceneItemBase::lowerWithAnimation() +void SceneItemBaseGraphicsItem::lowerWithAnimation() { m_targetScale = 1.0; m_scaleAnimation.setDuration(m_animationDuration); @@ -88,25 +88,35 @@ void SceneItemBase::lowerWithAnimation() m_scaleAnimation.start(); } -void SceneItemBase::enableShadowEffect(bool) +QRectF SceneItemBaseGraphicsItem::positionedBoundingRect() const { + return boundingRect().translated(pos()); } -void SceneItemBase::setAnimationDuration(int durationMs) +void SceneItemBaseGraphicsItem::enableShadowEffect(bool) +{ +} + +bool SceneItemBaseGraphicsItem::isRaised() const +{ + return !qFuzzyCompare(scale(), 1.0); +} + +void SceneItemBaseGraphicsItem::setAnimationDuration(int durationMs) { m_animationDuration = durationMs; } -void SceneItemBase::setAnimationOpacity(qreal animationOpacity) +void SceneItemBaseGraphicsItem::setAnimationOpacity(qreal animationOpacity) { m_animationOpacity = animationOpacity; } -qreal SceneItemBase::targetScale() const +qreal SceneItemBaseGraphicsItem::targetScale() const { return m_targetScale; } -SceneItemBase::~SceneItemBase() = default; +SceneItemBaseGraphicsItem::~SceneItemBaseGraphicsItem() = default; } // namespace SceneItems diff --git a/src/view/scene_items/scene_item_base.hpp b/src/view/scene_items/scene_item_base_graphics_item.hpp similarity index 82% rename from src/view/scene_items/scene_item_base.hpp rename to src/view/scene_items/scene_item_base_graphics_item.hpp index a92d070c..ce32d2cb 100644 --- a/src/view/scene_items/scene_item_base.hpp +++ b/src/view/scene_items/scene_item_base_graphics_item.hpp @@ -13,8 +13,8 @@ // You should have received a copy of the GNU General Public License // along with Heimer. If not, see . -#ifndef SCENE_ITEM_BASE_HPP -#define SCENE_ITEM_BASE_HPP +#ifndef SCENE_ITEM_BASE_GRAPHICS_ITEM_HPP +#define SCENE_ITEM_BASE_GRAPHICS_ITEM_HPP #include #include @@ -22,7 +22,7 @@ namespace SceneItems { -class SceneItemBase : public QObject, public QGraphicsItem +class SceneItemBaseGraphicsItem : public QObject, public QGraphicsItem { Q_OBJECT Q_INTERFACES(QGraphicsItem) @@ -30,9 +30,9 @@ class SceneItemBase : public QObject, public QGraphicsItem Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity) public: - SceneItemBase(); + SceneItemBaseGraphicsItem(); - virtual ~SceneItemBase() override; + virtual ~SceneItemBaseGraphicsItem() override; virtual void appearWithAnimation(); @@ -48,6 +48,10 @@ class SceneItemBase : public QObject, public QGraphicsItem virtual void lowerWithAnimation(); + virtual QRectF positionedBoundingRect() const; + + bool isRaised() const; + void setAnimationDuration(int durationMs); void setAnimationOpacity(qreal newAnimationOpacity); @@ -68,4 +72,4 @@ class SceneItemBase : public QObject, public QGraphicsItem } // namespace SceneItems -#endif // SCENE_ITEM_BASE_HPP +#endif // SCENE_ITEM_BASE_GRAPHICS_ITEM_HPP diff --git a/src/view/scene_items/scene_item_base_graphics_text_item.cpp b/src/view/scene_items/scene_item_base_graphics_text_item.cpp new file mode 100644 index 00000000..92c25d47 --- /dev/null +++ b/src/view/scene_items/scene_item_base_graphics_text_item.cpp @@ -0,0 +1,123 @@ +// This file is part of Heimer. +// Copyright (C) 2024 Jussi Lind +// +// Heimer is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Heimer is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Heimer. If not, see . + +#include "scene_item_base_graphics_text_item.hpp" + +namespace SceneItems { + +SceneItemBaseGraphicsTextItem::SceneItemBaseGraphicsTextItem(QGraphicsItem * parentItem) + : QGraphicsTextItem(parentItem) + , m_opacityAnimation(this, "opacity") + , m_scaleAnimation(this, "scale") +{ +} + +void SceneItemBaseGraphicsTextItem::appearWithAnimation() +{ + setTransformOriginPoint(boundingRect().center()); + + m_scaleAnimation.setDuration(m_animationDuration); + m_scaleAnimation.setStartValue(0.0); + m_scaleAnimation.setEndValue(1.0); + m_scaleAnimation.stop(); + m_scaleAnimation.start(); + + m_opacityAnimation.setDuration(m_animationDuration); + m_opacityAnimation.setStartValue(0.0); + m_opacityAnimation.setEndValue(m_animationOpacity); + m_opacityAnimation.stop(); + m_opacityAnimation.start(); +} + +void SceneItemBaseGraphicsTextItem::disappearWithAnimation() +{ + setTransformOriginPoint(boundingRect().center()); + + m_scaleAnimation.setDuration(m_animationDuration); + m_scaleAnimation.setStartValue(scale()); + m_scaleAnimation.setEndValue(0.0); + m_scaleAnimation.stop(); + m_scaleAnimation.start(); + + m_opacityAnimation.setDuration(m_animationDuration); + m_opacityAnimation.setStartValue(opacity()); + m_opacityAnimation.setEndValue(0.0); + m_opacityAnimation.stop(); + m_opacityAnimation.start(); +} + +void SceneItemBaseGraphicsTextItem::removeFromScene() +{ +} + +void SceneItemBaseGraphicsTextItem::removeFromSceneWithAnimation() +{ + connect(&m_scaleAnimation, &QPropertyAnimation::finished, this, &SceneItemBaseGraphicsTextItem::removeFromScene); + + setTransformOriginPoint(boundingRect().center()); + + m_scaleAnimation.setDuration(m_animationDuration); + m_scaleAnimation.setStartValue(scale()); + m_scaleAnimation.setEndValue(0.0); + m_scaleAnimation.stop(); + m_scaleAnimation.start(); +} + +void SceneItemBaseGraphicsTextItem::raiseWithAnimation(double targetScale) +{ + setTransformOriginPoint(boundingRect().center()); + + m_targetScale = targetScale; + m_scaleAnimation.setDuration(m_animationDuration); + m_scaleAnimation.setStartValue(scale()); + m_scaleAnimation.setEndValue(targetScale); + m_scaleAnimation.stop(); + m_scaleAnimation.start(); +} + +void SceneItemBaseGraphicsTextItem::lowerWithAnimation() +{ + setTransformOriginPoint(boundingRect().center()); + + m_targetScale = 1.0; + m_scaleAnimation.setDuration(m_animationDuration); + m_scaleAnimation.setStartValue(scale()); + m_scaleAnimation.setEndValue(m_targetScale); + m_scaleAnimation.stop(); + m_scaleAnimation.start(); +} + +void SceneItemBaseGraphicsTextItem::enableShadowEffect(bool) +{ +} + +void SceneItemBaseGraphicsTextItem::setAnimationDuration(int durationMs) +{ + m_animationDuration = durationMs; +} + +void SceneItemBaseGraphicsTextItem::setAnimationOpacity(qreal animationOpacity) +{ + m_animationOpacity = animationOpacity; +} + +qreal SceneItemBaseGraphicsTextItem::targetScale() const +{ + return m_targetScale; +} + +SceneItemBaseGraphicsTextItem::~SceneItemBaseGraphicsTextItem() = default; + +} // namespace SceneItems diff --git a/src/view/scene_items/scene_item_base_graphics_text_item.hpp b/src/view/scene_items/scene_item_base_graphics_text_item.hpp new file mode 100644 index 00000000..6015bc36 --- /dev/null +++ b/src/view/scene_items/scene_item_base_graphics_text_item.hpp @@ -0,0 +1,71 @@ +// This file is part of Heimer. +// Copyright (C) 2024 Jussi Lind +// +// Heimer is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Heimer is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Heimer. If not, see . + +#ifndef SCENE_ITEM_BASE_GRAPHICS_TEXT_ITEM_HPP +#define SCENE_ITEM_BASE_GRAPHICS_TEXT_ITEM_HPP + +#include +#include +#include + +namespace SceneItems { + +class SceneItemBaseGraphicsTextItem : public QGraphicsTextItem +{ + Q_OBJECT + Q_INTERFACES(QGraphicsItem) + Q_PROPERTY(qreal scale READ scale WRITE setScale) + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity) + +public: + SceneItemBaseGraphicsTextItem(QGraphicsItem * parentItem); + + virtual ~SceneItemBaseGraphicsTextItem() override; + + virtual void appearWithAnimation(); + + virtual void disappearWithAnimation(); + + virtual void enableShadowEffect(bool enable); + + virtual void removeFromScene(); + + virtual void removeFromSceneWithAnimation(); + + virtual void raiseWithAnimation(double targetScale); + + virtual void lowerWithAnimation(); + + void setAnimationDuration(int durationMs); + + void setAnimationOpacity(qreal newAnimationOpacity); + + qreal targetScale() const; + +private: + int m_animationDuration = 75; + + qreal m_animationOpacity = 1.0; + + qreal m_targetScale = 1.0; + + QPropertyAnimation m_opacityAnimation; + + QPropertyAnimation m_scaleAnimation; +}; + +} // namespace SceneItems + +#endif // SCENE_ITEM_BASE_GRAPHICS_TEXT_ITEM_HPP diff --git a/src/view/scene_items/text_edit.cpp b/src/view/scene_items/text_edit.cpp index 65d6912e..e9a63366 100644 --- a/src/view/scene_items/text_edit.cpp +++ b/src/view/scene_items/text_edit.cpp @@ -28,7 +28,7 @@ namespace SceneItems { TextEdit::TextEdit(QGraphicsItem * parentItem) - : QGraphicsTextItem(parentItem) + : SceneItemBaseGraphicsTextItem(parentItem) , m_unselectedFormat(textCursor().charFormat()) { if (!TestMode::enabled()) { diff --git a/src/view/scene_items/text_edit.hpp b/src/view/scene_items/text_edit.hpp index 0e346184..255fc0c3 100644 --- a/src/view/scene_items/text_edit.hpp +++ b/src/view/scene_items/text_edit.hpp @@ -16,13 +16,14 @@ #ifndef TEXT_EDIT_HPP #define TEXT_EDIT_HPP +#include "scene_item_base_graphics_text_item.hpp" + #include -#include #include namespace SceneItems { -class TextEdit : public QGraphicsTextItem +class TextEdit : public SceneItemBaseGraphicsTextItem { Q_OBJECT