Refactor of Panel

- Changing Visuals as subclass of Nodes
  - Removing Visual painting in Node Class
  - New Visual Class that is part of the node module
- Moving ResizeBoundingBox from Visuals to Panel
  - Introducing a VisualContainer where the Visuals are rendered in
- Adding property window
- Removing Visual Container in Documents
This commit is contained in:
Kalle Bracht
2026-02-01 18:13:20 +01:00
parent 07a2bfae88
commit f6f6ac898a
23 changed files with 555 additions and 360 deletions

View File

@@ -18,39 +18,30 @@ void Document::load(QJsonObject content)
return;
}
Visual *Document::createVisual(VisualType type)
void Document::createVisual(VisualType type)
{
VisualContainer container;
container.type = type;
if (type == VisualType::Test) {
Label *label = new Label(this);
label->setPanel(m_panel);
label->setPanel(m_panel, QPoint(200, 200));
label->setNodeEditor(m_nodeeditor);
m_nodeeditor->addNode(label);
connect(label->getVisual()->resize_bounding_box,
/*connect(label->getVisual()->resize_bounding_box,
&ResizeBoundingBox::changedDelta,
m_panel,
&Panel::changeGeometryForSelected);
container.ptr = label->getVisual();
visual_uid_count++;
container.ptr = label->getVisual();*/
} else if (type == VisualType::Slider) {
Slider *slider = new Slider(this);
slider->setPanel(m_panel);
slider->setPanel(m_panel, QPoint(400, 200));
slider->setNodeEditor(m_nodeeditor);
m_nodeeditor->addNode(slider);
connect(slider->getVisual()->resize_bounding_box,
/*connect(slider->getVisual()->resize_bounding_box,
&ResizeBoundingBox::changedDelta,
m_panel,
&Panel::changeGeometryForSelected);
container.ptr = slider->getVisual();
visual_uid_count++;
} else {
container.ptr = nullptr;
container.ptr = slider->getVisual();*/
}
return static_cast<Visual *>(container.ptr);
}

View File

@@ -8,7 +8,8 @@
#include "nodes/label.h"
#include "nodes/slider.h"
#include "panel.h"
#include "visual.h"
enum class VisualType { Parent, Test, Slider };
class Document : QObject
{
@@ -21,14 +22,11 @@ public:
QJsonObject save();
void load(QJsonObject);
Visual *createVisual(VisualType type);
void createVisual(VisualType type);
DisplayMode display_mode = DisplayMode::Run;
private:
unsigned int visual_uid_count = 0;
QList<VisualContainer> visuals;
Panel *m_panel;
NodeEditor *m_nodeeditor;
};

View File

@@ -40,8 +40,8 @@ void MainWindow::loadInsertVisualMenu()
for (const VisualMenuAction &wraped_action : wrapped_actions) {
QAction *menu_insert_action = new QAction(wraped_action.name);
connect(menu_insert_action, &QAction::triggered, this, [this, wraped_action] {
Visual *visual = focus_document->createVisual(wraped_action.type);
connect(this, &MainWindow::modeChanged, visual, &Visual::setMode);
focus_document->createVisual(wraped_action.type);
//connect(this, &MainWindow::modeChanged, visual, &Visual::setMode);
});
menu_insert_action->setText(wraped_action.name);

View File

@@ -1,6 +1,7 @@
qt_add_library(node STATIC
node.h node.cpp
interface.h interface.cpp
nodes/visual.h nodes/visual.cpp
nodes/label.h nodes/label.cpp
nodes/slider.h nodes/slider.cpp
)

View File

@@ -4,14 +4,6 @@ Node::Node(QObject *parent)
: QObject{parent}
{}
void Node::setPanel(QWidget *panel_widget)
{
m_panel = panel_widget;
Visual *visual = new Visual(m_panel, 0);
m_visual = visual;
paintVisual(visual);
}
Interface *Node::getInterface(const QString &identifier)
{

View File

@@ -5,7 +5,6 @@
#include <QObject>
#include <QVariant>
#include "../panel/visual.h"
#include "interface.h"
class Node : public QObject
@@ -21,26 +20,15 @@ public:
QList<Interface> &getInterfaces() { return m_interfaces; }
Interface *getInterface(const QString &identifier);
//Pointer to the panel widgets. Needed for visual nodes to create their UI in the panel.
void setPanel(QWidget *panel_widget);
//Pointer to the node editor widgets. Needed for nodes to be shown in the node editor.
void setNodeEditor(QWidget *node_editor_widget) { m_nodeeditor = node_editor_widget; }
Visual *getVisual() { return m_visual; }
protected:
virtual void paintVisual(Visual *) {};
//virtual void valueChange(Interface &) ;
signals:
void changedInterfaceValue(const Interface &);
private:
QString m_name;
QWidget *m_panel = nullptr;
QWidget *m_nodeeditor = nullptr;
Visual *m_visual = nullptr;
QList<Interface> m_interfaces;
};

View File

@@ -1,7 +1,7 @@
#include "label.h"
Label::Label(QObject *parent)
: Node{parent}
: Visual{parent}
{
setProperty("name", "Label");
Interface text_interface = Interface(QVariant(0), InterfaceDirection::Input, "text");
@@ -10,17 +10,13 @@ Label::Label(QObject *parent)
getInterface("text")->setCallback([this]() { setText(); });
}
void Label::paintVisual(Visual *visual)
QWidget *Label::paintWidget(VisualContainer *visual_container)
{
visual->setGeometry(QRect(100, 100, 200, 50));
visual->setMinimumSize(QSize(200, 50));
visual->setMouseTracking(true);
//Create a label in the panel for this node.
m_label = new QLabel("Label Text", visual);
m_label = new QLabel("Label Text", visual_container);
m_label->setGeometry(10, 10, 180, 30);
m_label->show();
qDebug() << "Label created";
return m_label;
}
void Label::setText()

View File

@@ -3,9 +3,9 @@
#include <QLabel>
#include "../node.h"
#include "visual.h"
class Label : public Node
class Label : public Visual
{
Q_OBJECT
@@ -13,7 +13,7 @@ public:
explicit Label(QObject *parent = nullptr);
private:
void paintVisual(Visual *panel) override;
QWidget *paintWidget(VisualContainer *) override;
void setText();
QLabel *m_label;

View File

@@ -1,28 +1,25 @@
#include "slider.h"
Slider::Slider(QObject *parent)
: Node{parent}
: Visual{parent}
{
setInterfaces({Interface(QVariant(0), InterfaceDirection::Output, "value")});
setProperty("name", "Slider");
}
void Slider::paintVisual(Visual *visual)
QWidget *Slider::paintWidget(VisualContainer *visual_container)
{
visual->setGeometry(QRect(200, 200, 400, 100));
visual->setMinimumSize(QSize(400, 100));
visual->setMouseTracking(true);
//Create a slider in the panel for this node.
QSlider *slider = new QSlider(visual);
QSlider *slider = new QSlider(visual_container);
slider->setOrientation(Qt::Horizontal);
slider->setGeometry(10, 10, 200, 30);
//slider->setFixedSize(200, 30);
slider->setMinimum(0);
slider->setMaximum(100);
slider->setValue(50);
slider->show();
qDebug() << "Slider created";
connect(slider, &QSlider::valueChanged, this, &Slider::onSliderValueChanged);
return slider;
}
void Slider::onSliderValueChanged(int value)

View File

@@ -4,9 +4,9 @@
#include <QDebug>
#include <QSlider>
#include "../node.h"
#include "visual.h"
class Slider : public Node
class Slider : public Visual
{
Q_OBJECT
public:
@@ -16,7 +16,7 @@ private slots:
void onSliderValueChanged(int value);
private:
void paintVisual(Visual *panel) override;
QWidget *paintWidget(VisualContainer *) override;
};
#endif // SLIDER_H

28
src/node/nodes/visual.cpp Normal file
View File

@@ -0,0 +1,28 @@
#include "visual.h"
Q_DECLARE_LOGGING_CATEGORY(visual)
Q_LOGGING_CATEGORY(visual, "VISUAL")
Visual::Visual(QObject *parent)
: Node(parent)
{}
void Visual::setPanel(QWidget *panel_widget, QPoint position)
{
if (panel_widget) {
m_visual_container = new VisualContainer(panel_widget);
m_visual_container->move(position);
m_visual_container->show();
m_visual_widget = paintWidget(m_visual_container);
m_visual_widget->show();
}
}
QWidget *Visual::paintWidget(VisualContainer *visual_container)
{
QWidget *widget = new QWidget(visual_container);
widget->setGeometry(50, 50, 150, 100);
widget->setAttribute(Qt::WA_StyledBackground, true);
widget->setStyleSheet("background-color: orange; border: 1px solid gray;");
return widget;
}

26
src/node/nodes/visual.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef VISUAL_H
#define VISUAL_H
#include <QLoggingCategory>
#include "../../panel/visual_container.h"
#include "../node.h"
class Visual : public Node
{
Q_OBJECT
public:
explicit Visual(QObject *parent = nullptr);
virtual ~Visual() {};
void setPanel(QWidget *panel_widget, QPoint position = QPoint(0,0));
void setPanelPosition(QPoint);
virtual QWidget *paintWidget(VisualContainer *parent);
private:
VisualContainer *m_visual_container = nullptr;
QWidget *m_visual_widget = nullptr;
};
#endif // VISUAL_H

View File

@@ -0,0 +1,23 @@
#include "propertywindow.h"
PropertyWindow::PropertyWindow(QWidget* parent, QObject* object)
: QWidget{parent, Qt::Window}
{
if (!object)
return;
QFormLayout* layout = new QFormLayout(this);
const QMetaObject* meta = object->metaObject();
for (int i = 0; i < meta->propertyCount(); ++i) {
QMetaProperty prop = meta->property(i);
QString name = prop.name();
QVariant value = object->property(name.toLatin1());
QLabel* nameLabel = new QLabel(name);
QLabel* valueLabel = new QLabel(value.toString());
layout->addRow(nameLabel, valueLabel);
}
setLayout(layout);
setWindowTitle("Properties of " + object->objectName());
}

18
src/node/propertywindow.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef PROPERTYWINDOW_H
#define PROPERTYWINDOW_H
#include <QFormLayout>
#include <QLabel>
#include <QMetaProperty>
#include <QWidget>
class PropertyWindow : public QWidget
{
Q_OBJECT
public:
explicit PropertyWindow(QWidget *parent, QObject *object);
signals:
};
#endif // PROPERTYWINDOW_H

View File

@@ -1,7 +1,7 @@
qt_add_library(panel STATIC
visual.h visual.cpp
resizeboundingbox.h resizeboundingbox.cpp
panel.h panel.cpp
visual_container.h visual_container.cpp
)
target_link_libraries(panel PRIVATE nodeeditor PRIVATE Qt6::Widgets)

View File

@@ -8,58 +8,58 @@ Panel::Panel(QWidget *parent)
{
setMouseTracking(true);
rubber_band = new QRubberBand(QRubberBand::Rectangle, this);
m_rubber_band = new QRubberBand(QRubberBand::Rectangle, this);
}
QList<Visual *> Panel::visualChildIn(QRect rect)
QList<QWidget *> Panel::childIn(QRect section)
{
QList<Visual *> contained_visuals;
foreach (Visual *visual, findChildren<Visual *>()) {
if (rect.intersects(visual->geometry())) {
contained_visuals.append(visual);
QList<QWidget *> contained_widgets;
for (QWidget *child : findChildren<VisualContainer *>(Qt::FindDirectChildrenOnly)) {
if (section.intersects(child->geometry()))
contained_widgets.append(child);
}
}
return contained_visuals;
return contained_widgets;
}
Visual *Panel::visualChildAt(QPoint point)
QWidget *Panel::childAt(QPoint point)
{
QList<Visual *> visuals = findChildren<Visual *>();
QMutableListIterator<Visual *> visual(visuals);
QWidget *top_level_widget = nullptr;
Visual *top_level_visual = nullptr;
while (visual.hasNext()) {
if (visual.next()->geometry().contains(point)) {
top_level_visual = visual.value();
for (QWidget *child : findChildren<VisualContainer *>(Qt::FindDirectChildrenOnly)) {
if (child->geometry().contains(point)) {
top_level_widget = child;
}
}
return top_level_visual;
return top_level_widget;
}
void Panel::setMode(DisplayMode display_mode)
{
this->display_mode = display_mode;
m_display_mode = display_mode;
}
void Panel::triggeredAlign(AlignDirection direction)
{
QRect bounding_box = calculateMinimumBoundingBox(selected_visuals);
//@TODO Proper alignment of selected QWidgets
QRect minimal_rect = minimalSelectedRect();
for (Visual *visual : selected_visuals) {
alignVisualToBoundingBox(direction, visual, bounding_box);
for (const RbbWidgetPair pair : m_selection) {
alignChildToMinRect(direction, pair, minimal_rect);
}
}
QRect Panel::calculateMinimumBoundingBox(const QList<Visual *> &visuals)
QRect Panel::minimalSelectedRect()
{
int top = INT_MAX;
int left = INT_MAX;
int bottom = INT_MIN;
int right = INT_MIN;
for (const Visual *visual : visuals) {
QRect rect = visual->geometry();
for (const RbbWidgetPair pair : m_selection) {
QRect rect = pair.second->geometry();
top = std::min(top, rect.top());
left = std::min(left, rect.left());
@@ -70,74 +70,167 @@ QRect Panel::calculateMinimumBoundingBox(const QList<Visual *> &visuals)
return QRect(QPoint(left, top), QPoint(right, bottom));
}
void Panel::alignVisualToBoundingBox(AlignDirection direction, Visual *target, QRect bounding_box)
void Panel::alignChildToMinRect(AlignDirection direction,
RbbWidgetPair rbbw_pair,
QRect minimal_rect)
{
QRect new_geometry = target->geometry();
ResizeBoundingBox *rbb = rbbw_pair.first;
QWidget *widget = rbbw_pair.second;
QRect new_geometry = widget->geometry();
switch (direction) {
case AlignDirection::Top:
new_geometry.moveTop(bounding_box.top());
new_geometry.moveTop(minimal_rect.top());
break;
case AlignDirection::Right:
new_geometry.moveRight(bounding_box.right());
new_geometry.moveRight(minimal_rect.right());
break;
case AlignDirection::Bottom:
new_geometry.moveBottom(bounding_box.bottom());
new_geometry.moveBottom(minimal_rect.bottom());
break;
case AlignDirection::Left:
new_geometry.moveLeft(bounding_box.left());
new_geometry.moveLeft(minimal_rect.left());
break;
case AlignDirection::Horizontal:
new_geometry.moveCenter(QPoint(minimal_rect.center().x(), new_geometry.center().y()));
break;
case AlignDirection::Vertical:
new_geometry.moveCenter(QPoint(new_geometry.center().x(), minimal_rect.center().y()));
break;
default:
return;
}
target->setGeometry(new_geometry);
widget->setGeometry(new_geometry);
rbb->setBoxGeometry(new_geometry);
}
void Panel::select(Visual *visual)
void Panel::select(QWidget *child)
{
visual->resize_bounding_box->show();
selected_visuals.append(visual);
ResizeBoundingBox *rbb = new ResizeBoundingBox(this);
rbb->setBoxGeometry(child->geometry());
rbb->show();
connect(rbb, &ResizeBoundingBox::changedDelta, this, &Panel::changeGeometryForSelected);
m_selection.append({rbb, child});
//@TODO: Replace with actual name
qInfo() << QString("Select: IMPLEMENT NAME");
}
void Panel::deselectAll()
{
QMutableListIterator<Visual *> visual(selected_visuals);
while (visual.hasNext()) {
visual.next()->resize_bounding_box->hide();
visual.remove();
qInfo() << QString("Deselect: IMPLEMENT NAME");
QMutableListIterator<RbbWidgetPair> i(m_selection);
while (i.hasNext()) {
ResizeBoundingBox *rbb = i.next().first;
delete rbb;
i.remove();
}
//@TODO: Replace with actual name
}
void Panel::selectSingle(QPoint positon)
void Panel::selectSingle(QPoint position)
{
Visual *top_level_visual = visualChildAt(positon);
if (top_level_visual) {
QWidget *top_level_child = childAt(position);
if (top_level_child) {
bool shift_down = QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier);
if (!shift_down)
if (!shift_down) {
deselectAll();
select(top_level_visual);
}
select(top_level_child);
}
}
void Panel::selectMultiple(QList<Visual *> found_visuals)
void Panel::selectMultiple(const QList<QWidget *> &found_widgets)
{
if (!found_visuals.isEmpty()) {
foreach (Visual *visual, found_visuals) {
select(visual);
}
for (QWidget *widget : found_widgets) {
select(widget);
}
}
void Panel::changeGeometryForSelected(DragDirection active_direction, QPointF delta)
{
foreach (Visual *visual, selected_visuals) {
visual->changeGeometryByDelta(active_direction, delta);
for (RbbWidgetPair select : m_selection) {
ResizeBoundingBox *rbb = select.first;
QWidget *widget = select.second;
QRect new_geometry = calculateNewGeometry(delta,
widget->geometry(),
active_direction,
widget->minimumWidth(),
widget->minimumHeight());
widget->setGeometry(new_geometry);
rbb->setBoxGeometry(new_geometry);
}
}
QRect Panel::calculateNewGeometry(
QPointF delta, QRect old_geometry, DragDirection active_direction, int min_w, int min_h)
{
int initial_x = old_geometry.x();
int initial_y = old_geometry.y();
int initial_w = old_geometry.width();
int initial_h = old_geometry.height();
int new_x = initial_x;
int new_y = initial_y;
int new_w = initial_w;
int new_h = initial_h;
switch (active_direction) {
case DragDirection::North: // Fixed: Bottom
new_y = initial_y + delta.y();
new_h = initial_h - delta.y();
break;
case DragDirection::NorthEast: // Fixed: BottomLeft
new_y = initial_y + delta.y();
new_w = initial_w + delta.x();
new_h = initial_h - delta.y();
break;
case DragDirection::East: // Fixed: Left
new_w = initial_w + delta.x();
break;
case DragDirection::SouthEast: // Fixed: TopLeft
new_w = initial_w + delta.x();
new_h = initial_h + delta.y();
break;
case DragDirection::South: // Fixed: Top
new_h = initial_h + delta.y();
break;
case DragDirection::SouthWest: // Fixed: TopRight
new_x = initial_x + delta.x();
new_w = initial_w - delta.x();
new_h = initial_h + delta.y();
break;
case DragDirection::West: // Fixed: Right
new_x = initial_x + delta.x();
new_w = initial_w - delta.x();
break;
case DragDirection::NorthWest: // Fixed: BottomRight
new_x = initial_x + delta.x();
new_y = initial_y + delta.y();
new_w = initial_w - delta.x();
new_h = initial_h - delta.y();
break;
case DragDirection::Center:
new_x = initial_x + delta.x();
new_y = initial_y + delta.y();
break;
default: // DrageDirection::None
break;
}
if (new_w < min_w) {
new_w = initial_w;
new_x = initial_x;
}
if (new_h < min_h) {
new_h = initial_h;
new_y = initial_y;
}
return QRect(new_x, new_y, new_w, new_h);
}
bool Panel::inMouseWiggleTolerance(QSize size)
{
if (size.width() > MAX_MOUSE_WIGGLE_TOLERANCE || size.height() > MAX_MOUSE_WIGGLE_TOLERANCE) {
@@ -149,35 +242,121 @@ bool Panel::inMouseWiggleTolerance(QSize size)
void Panel::mousePressEvent(QMouseEvent *event)
{
if (display_mode == DisplayMode::Edit) {
origin = event->pos();
rubber_band->setGeometry(QRect(origin, QSize(1, 1)));
rubber_band->show();
if (m_display_mode == DisplayMode::Edit) {
m_origin = event->pos();
m_rubber_band->setGeometry(QRect(m_origin, QSize(1, 1)));
m_rubber_band->show();
}
}
void Panel::mouseMoveEvent(QMouseEvent *event)
{
if (display_mode == DisplayMode::Edit) {
rubber_band->setGeometry(QRect(origin, event->pos()).normalized());
if (m_display_mode == DisplayMode::Edit) {
m_rubber_band->setGeometry(QRect(m_origin, event->pos()).normalized());
}
}
void Panel::mouseReleaseEvent(QMouseEvent *event)
{
if (display_mode == DisplayMode::Edit) {
rubber_band->hide();
if (m_display_mode == DisplayMode::Edit) {
m_rubber_band->hide();
QList<Visual *> found_visuals = visualChildIn(rubber_band->geometry());
QList<QWidget *> found_visuals = childIn(m_rubber_band->geometry());
if (found_visuals.isEmpty()) {
deselectAll();
return;
}
if (inMouseWiggleTolerance(rubber_band->size())) {
if (inMouseWiggleTolerance(m_rubber_band->size())) {
selectSingle(event->pos());
} else { // selction via rubber band
selectMultiple(found_visuals);
}
}
}
void Panel::installEventFilterRecursively(QObject *obj)
{
if (obj->isWidgetType()) {
obj->installEventFilter(this);
// Enable mouse tracking to receive mouse move events even when no button is pressed
if (QWidget *widget = qobject_cast<QWidget *>(obj)) {
widget->setMouseTracking(true);
}
}
for (QObject *child : obj->children()) {
installEventFilterRecursively(child);
}
}
void Panel::childEvent(QChildEvent *event)
{
if (event->type() == QEvent::ChildAdded) {
installEventFilterRecursively(event->child());
}
QWidget::childEvent(event);
}
bool Panel::eventFilter(QObject *watched, QEvent *event)
{
// Install event filter on any newly added QWidget child
if (event->type() == QEvent::ChildAdded) {
QChildEvent *child_event = static_cast<QChildEvent *>(event);
if (child_event->child()->isWidgetType()) {
installEventFilterRecursively(child_event->child());
}
}
bool no_rbb = !qobject_cast<ResizeBoundingBox *>(watched);
bool mouse_event = event->type() == QEvent::MouseButtonPress
|| event->type() == QEvent::MouseMove
|| event->type() == QEvent::MouseButtonRelease;
bool forward_mouse_events = (m_display_mode == DisplayMode::Edit) && mouse_event && no_rbb;
if (forward_mouse_events) {
//@TODO: Check coordinate system of event. It might be that the coordiante system
// of the event is relative to the child widget, not the panel.
QMouseEvent *mapped_event = mapMouseEventToPanel(static_cast<QMouseEvent *>(event),
qobject_cast<QWidget *>(watched));
switch (event->type()) {
case QEvent::MouseButtonPress:
mousePressEvent(mapped_event);
delete mapped_event;
return true;
case QEvent::MouseMove:
mouseMoveEvent(mapped_event);
delete mapped_event;
return true;
case QEvent::MouseButtonRelease:
mouseReleaseEvent(mapped_event);
delete mapped_event;
return true;
default:
delete mapped_event;
break;
}
//@TODO: Crash on double click events
}
return QWidget::eventFilter(watched, event);
}
QMouseEvent *Panel::mapMouseEventToPanel(QMouseEvent *origin_event, QWidget *source_widget)
{
QPoint new_pos = origin_event->pos();
if (source_widget && source_widget != this) {
new_pos = source_widget->mapTo(this, origin_event->pos());
}
QMouseEvent *mapped_event = new QMouseEvent(origin_event->type(),
new_pos,
origin_event->globalPosition(),
origin_event->button(),
origin_event->buttons(),
origin_event->modifiers(),
origin_event->pointingDevice());
return mapped_event;
}

View File

@@ -7,48 +7,155 @@
#include <QRubberBand>
#include <QWidget>
#include "visual.h"
#include "../node/nodes/visual.h"
#include "resizeboundingbox.h"
#include "visual_container.h"
enum class AlignDirection { Top, Right, Bottom, Left, Horizontal, Vertical };
enum class DisplayMode { Run, Edit };
const int MAX_MOUSE_WIGGLE_TOLERANCE = 5; // px of size
using RbbWidgetPair = std::pair<ResizeBoundingBox *, QWidget *>;
class Panel : public QWidget
{
Q_OBJECT
public:
explicit Panel(QWidget *parent = nullptr);
public slots:
/* @brief Sets the display mode of the panel.
*
* This method changes the display mode of the panel to either Run or Edit.
* In Edit mode, users can select and manipulate visual elements within the panel.
* In Run mode, the panel behaves as a standard display without editing capabilities.
*
* @param display_mode The desired display mode (Run or Edit).
*/
void setMode(DisplayMode display_mode);
/* @brief Align selected to direction.
*
* This method aligns selected children and aligns them to a direction.
*
* @param direction Direction to align to.
*/
void triggeredAlign(AlignDirection direction);
void changeGeometryForSelected(DragDirection, QPointF);
protected:
void mousePressEvent(QMouseEvent *) override;
void mouseMoveEvent(QMouseEvent *) override;
void mouseReleaseEvent(QMouseEvent *) override;
public:
void addVisual(Visual *);
public slots:
void setMode(DisplayMode);
void triggeredAlign(AlignDirection);
void changeGeometryForSelected(DragDirection, QPointF);
bool eventFilter(QObject *, QEvent *) override;
void childEvent(QChildEvent *) override;
private:
QPoint origin;
QPointF last_global_position;
bool mouse_pressed = false;
QRubberBand *rubber_band;
DisplayMode display_mode;
QList<Visual *> selected_visuals;
/**
* @brief Makes the QWidget as selected.
*
* This method adds the given QWidget to the list of selected visuals.
* It also spawns the resize bounding box for the QWidget.
*
* @param child The QWidget to be selected.
*/
void select(QWidget *child);
void select(Visual *);
/**
* @brief Deselects all selected QWidgets.
*
* This method clears the list of selected visuals and deletes their
* resize bounding boxes.
*/
void deselectAll();
QList<Visual *> visualChildIn(QRect);
Visual *visualChildAt(QPoint);
bool inMouseWiggleTolerance(QSize);
void selectSingle(QPoint);
void selectMultiple(QList<Visual *>);
void alignVisualToBoundingBox(AlignDirection, Visual *, QRect);
QRect calculateMinimumBoundingBox(const QList<Visual *> &);
/**
* @brief Finds all QWidgets children within a given QRect.
*
* This method returns a list of QWidgets that intersect with the specified QRect.
*
* @param section The rectangle to check for intersecting QWidgets.
* @return A list of QWidgets that intersect with the given QRect.
* @see Panel::childAt(QPoint)
*/
QList<QWidget *> childIn(QRect section);
/**
* @brief Finds a QWidget child at a given QPoint.
*
* This method returns the QWidget located at the specified QPoint.
*
* @param point The point to check for a QWidget.
* @return The QWidget located at the given QPoint, or nullptr if none found.
* @see Panel::childIn(QRect)
*/
QWidget *childAt(QPoint point);
/**
* @brief Checks if mouse movement is within wiggle tolerance.
*
* This method determines if the mouse movement from the origin point
* is within the defined wiggle tolerance.
* This is useful for distinguishing between a click and a drag action.
*
* @param size The size representing the mouse movement delta.
* @return True if the movement is within wiggle tolerance, false otherwise.
*/
bool inMouseWiggleTolerance(QSize size);
/**
* @brief Selects a single QWidget with Shift key check.
*
* This method selects a single QWidget. If the Shift key is pressed,
* it adds the QWidget to the current selection for multi-selection.
* Else, it clears the current selection and selects only the given QWidget.
*
* @param position The point where the selection is made.
* @see Panel::select(QWidget*)
*/
void selectSingle(QPoint position);
/**
* @brief Selects multiple QWidgets.
*
* This method selects all QWidgets that intersect with the specified QRect.
* It supports multi-selection if the Shift key is pressed.
*
* @param found_visuals The list of visuals to select.
* @see Panel::select(QWidget*)
*/
void selectMultiple(const QList<QWidget *> &found_visuals);
void alignChildToMinRect(AlignDirection direction, RbbWidgetPair rbbw_pair, QRect minimal_rect);
QRect minimalSelectedRect();
void installEventFilterRecursively(QObject *);
QMouseEvent *mapMouseEventToPanel(QMouseEvent *, QWidget *);
/**
* @brief Calculates a new geometry.
*
* This method calculates the new geometry based of the old one and the delta.
*
* @param delta Change in geometry.
* @param old_geometry Geomtry before appling the delta.
* @param active_direction Direction to drag to.
* @param min_w Minimum width that cannot be undercut by the new geometry.
* @param min_h Minimum height that cannot be undercut by the new geometry.
*/
QRect calculateNewGeometry(
QPointF delta, QRect old_geometry, DragDirection active_direction, int min_w, int min_h);
QPoint m_origin;
QPointF m_last_global_position;
bool m_mouse_pressed = false;
QRubberBand *m_rubber_band;
DisplayMode m_display_mode;
QList<RbbWidgetPair> m_selection;
};
#endif // PANEL_H

View File

@@ -128,9 +128,10 @@ void ResizeBoundingBox::mouseMoveEvent(QMouseEvent *event)
if (mouse_pressed) {
QPointF delta = event->globalPosition() - last_global_position;
last_global_position = event->globalPosition();
emit changedDelta(active_direction, delta);
} else { // On hover
}
else { // On hover
active_direction = containesHitbox(event->pos());
setHoverCursor(active_direction);
}

View File

@@ -7,9 +7,9 @@
#include <QPainter>
#include <QWidget>
const int OUTER_HITBOX_SIZE = 10;
const QMargins WIDGET_MARGIN(20, 20, 20, 20);
const QMargins CENTER_HITBOX_MARGIN(15, 15, 15, 15);
static const int OUTER_HITBOX_SIZE = 10;
static const QMargins WIDGET_MARGIN(20, 20, 20, 20);
static const QMargins CENTER_HITBOX_MARGIN(15, 15, 15, 15);
enum class DragDirection {
NorthWest,
@@ -30,8 +30,23 @@ class ResizeBoundingBox : public QWidget
public:
explicit ResizeBoundingBox(QWidget *parent = nullptr);
void setBoxGeometry(QRect);
void setBoxMinimumSize(QSize);
/* @brief Sets the size of bounding box.
This method sets the size of the visable bounding box.
The actual Widget has an offset, so that the mouse direction ankers are drawn correctly.
@param box_geometry Geometry of the bounding box.
*/
void setBoxGeometry(QRect box_geometry);
/* @brief Sets the minimum size of the bounding box.
This method sets the minimum size of the visable bounding box.
The actual Widget has an offset, so that the mouse direction ankers are drawn correctly.
@param size Minimum size of the bounding box.
*/
void setBoxMinimumSize(QSize size);
protected:
void mousePressEvent(QMouseEvent *) override;
@@ -41,7 +56,6 @@ protected:
void paintEvent(QPaintEvent *) override;
signals:
void changedGeometry(QRect);
void changedDelta(DragDirection, QPointF);
private:

View File

@@ -1,138 +0,0 @@
#include "visual.h"
Q_DECLARE_LOGGING_CATEGORY(visual)
Q_LOGGING_CATEGORY(visual, "VISUAL")
Visual::Visual(QWidget *panel, unsigned int uid)
: QWidget(panel)
, m_uid(uid)
{
setMouseTracking(true);
resize_bounding_box = new ResizeBoundingBox(panel);
resize_bounding_box->hide();
setAttribute(Qt::WA_StyledBackground, true);
setStyleSheet("background-color: lightblue; border: 1px solid blue;");
show();
}
void Visual::setGeometry(QRect geometry)
{
resize_bounding_box->setBoxGeometry(geometry);
QWidget::setGeometry(geometry);
qInfo(visual) << "Changed geometry: " << geometry;
}
void Visual::setMode(DisplayMode display_mode)
{
m_display_mode = display_mode;
}
void Visual::setMinimumSize(QSize size)
{
resize_bounding_box->setBoxMinimumSize(size);
QWidget::setMinimumSize(size);
}
void Visual::changeGeometry(QRect geometry)
{
QWidget::setGeometry(geometry);
}
void Visual::changeGeometryByDelta(DragDirection active_direction, QPointF delta)
{
int initial_x = geometry().x();
int initial_y = geometry().y();
int initial_w = geometry().width();
int initial_h = geometry().height();
int new_x = initial_x;
int new_y = initial_y;
int new_w = initial_w;
int new_h = initial_h;
switch (active_direction) {
case DragDirection::North: // Fixed: Bottom
new_y = initial_y + delta.y();
new_h = initial_h - delta.y();
break;
case DragDirection::NorthEast: // Fixed: BottomLeft
new_y = initial_y + delta.y();
new_w = initial_w + delta.x();
new_h = initial_h - delta.y();
break;
case DragDirection::East: // Fixed: Left
new_w = initial_w + delta.x();
break;
case DragDirection::SouthEast: // Fixed: TopLeft
new_w = initial_w + delta.x();
new_h = initial_h + delta.y();
break;
case DragDirection::South: // Fixed: Top
new_h = initial_h + delta.y();
break;
case DragDirection::SouthWest: // Fixed: TopRight
new_x = initial_x + delta.x();
new_w = initial_w - delta.x();
new_h = initial_h + delta.y();
break;
case DragDirection::West: // Fixed: Right
new_x = initial_x + delta.x();
new_w = initial_w - delta.x();
break;
case DragDirection::NorthWest: // Fixed: BottomRight
new_x = initial_x + delta.x();
new_y = initial_y + delta.y();
new_w = initial_w - delta.x();
new_h = initial_h - delta.y();
break;
case DragDirection::Center:
new_x = initial_x + delta.x();
new_y = initial_y + delta.y();
break;
default: // DrageDirection::None
break;
}
if (new_w < minimumWidth()) {
new_w = initial_w;
new_x = initial_x;
}
if (new_h < minimumHeight()) {
new_h = initial_h;
new_y = initial_y;
}
QRect new_geometry(new_x, new_y, new_w, new_h);
resize_bounding_box->setBoxGeometry(new_geometry);
QWidget::setGeometry(new_geometry);
}
void Visual::logInitiation()
{
qInfo(visual) << QString("Initiated '%1'(%2) at 0x%3")
.arg("Implement again as property")
.arg(QString::number(m_uid))
.arg(reinterpret_cast<quintptr>(this), QT_POINTER_SIZE * 2, 16, QChar('0'));
}
// In Edit Mode all events in PANEL_HANDLED_ON_EDIT_MODE need to get handled by the Panel
// This could be a mouse event. The Panels needs the mouse event to handle the draging or resizing
// of the Visual. This only works if EVERY child widget ignores the given events.
bool Visual::checkForParentHandling(QEvent *event)
{
if (m_display_mode != DisplayMode::Edit) {
//qDebug() << event;
return QWidget::event(event);
}
for (QEvent::Type panel_handled : PANEL_HANDLED_ON_EDIT_MODE) {
if (event->type() == panel_handled) {
return false;
}
}
return QWidget::event(event);
}

View File

@@ -1,65 +0,0 @@
#ifndef VISUAL_H
#define VISUAL_H
#include <QDebug>
#include <QLayout>
#include <QLoggingCategory>
#include <QMouseEvent>
#include <QMoveEvent>
#include <QPoint>
#include <QResizeEvent>
#include <QSizeGrip>
#include <QString>
#include <QWidget>
#include "resizeboundingbox.h"
enum class DisplayMode { Run, Edit };
enum class VisualType { Parent, Test, Slider };
const QEvent::Type PANEL_HANDLED_ON_EDIT_MODE[] = {QEvent::MouseButtonPress,
QEvent::MouseButtonRelease,
QEvent::MouseMove};
struct VisualContainer
{
void *ptr;
VisualType type;
};
class Visual : public QWidget
{
Q_OBJECT
public:
explicit Visual(QWidget *panel = nullptr, unsigned int uid = 0);
virtual ~Visual() {};
void remove();
void setGeometry(QRect);
ResizeBoundingBox *resize_bounding_box;
public slots:
void setMode(DisplayMode);
void changeGeometry(QRect);
void changeGeometryByDelta(DragDirection, QPointF);
void setMinimumSize(QSize);
protected:
bool checkForParentHandling(QEvent *);
private:
int m_uid;
void logInitiation();
DisplayMode m_display_mode = DisplayMode::Run;
QPointF last_global_position;
void enableSizeGrip();
void disableSizeGrip();
};
#endif // VISUAL_H

View File

@@ -0,0 +1,21 @@
#include "visual_container.h"
VisualContainer::VisualContainer(QWidget *parent)
: QWidget{parent}
{
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->setSizeConstraint(QLayout::SetMinAndMaxSize);
}
void VisualContainer::childEvent(QChildEvent *event)
{
if (event->added()) {
QWidget *child_widget = qobject_cast<QWidget *>(event->child());
if (child_widget) {
layout()->addWidget(child_widget);
}
}
QWidget::childEvent(event);
}

View File

@@ -0,0 +1,18 @@
#ifndef VISUAL_CONTAINER_H
#define VISUAL_CONTAINER_H
#include <QChildEvent>
#include <QLayout>
#include <QWidget>
class VisualContainer : public QWidget
{
Q_OBJECT
public:
explicit VisualContainer(QWidget *parent = nullptr);
protected:
void childEvent(QChildEvent *) override;
};
#endif // VISUAL_CONTAINER_H