diff --git a/CMakeLists.txt b/CMakeLists.txt index c63bdc110..dc8d9bd64 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -811,6 +811,8 @@ if(WITH_QT_EDITOR) src/editor/C4ConsoleQt.h src/editor/C4ConsoleQtState.cpp src/editor/C4ConsoleQtState.h + src/editor/C4ConsoleQtShapes.cpp + src/editor/C4ConsoleQtShapes.h src/editor/C4ConsoleQtPropListViewer.cpp src/editor/C4ConsoleQtPropListViewer.h src/editor/C4ConsoleQtObjectListViewer.cpp diff --git a/src/editor/C4ConsoleGUI.h b/src/editor/C4ConsoleGUI.h index b71a19a04..05303e433 100644 --- a/src/editor/C4ConsoleGUI.h +++ b/src/editor/C4ConsoleGUI.h @@ -87,6 +87,10 @@ public: void OnStartGame(); void ClearGamePointers(); + // TODO some qt editor stuff is in state and needs to be public + // Once other editors are removed, C4ConsoleGUI, C4ConsoleQt and C4ConsoleQtState should be reorganized + State *GetState() const { return state; } + friend class C4ConsoleQtMainWindow; friend class C4ToolsDlg; #else diff --git a/src/editor/C4ConsoleQt.cpp b/src/editor/C4ConsoleQt.cpp index 6b074f5a3..bf5ce5ec1 100644 --- a/src/editor/C4ConsoleQt.cpp +++ b/src/editor/C4ConsoleQt.cpp @@ -353,10 +353,10 @@ bool C4ConsoleGUI::CreateNewScenario(StdStrBuf *out_filename) state->SetObjectSelection(selection); } - void C4ConsoleGUI::ClearGamePointers() - { +void C4ConsoleGUI::ClearGamePointers() +{ state->ClearGamePointers(); - } +} void C4ToolsDlg::UpdateToolCtrls() { diff --git a/src/editor/C4ConsoleQtPropListViewer.cpp b/src/editor/C4ConsoleQtPropListViewer.cpp index b417967cf..b6ddc4e1a 100644 --- a/src/editor/C4ConsoleQtPropListViewer.cpp +++ b/src/editor/C4ConsoleQtPropListViewer.cpp @@ -17,6 +17,7 @@ #include "C4Include.h" #include "script/C4Value.h" #include "editor/C4ConsoleQtPropListViewer.h" +#include "editor/C4ConsoleQtState.h" #include "editor/C4Console.h" #include "object/C4Object.h" #include "object/C4DefList.h" @@ -127,7 +128,7 @@ void C4PropertyDelegateInt::SetEditorData(QWidget *editor, const C4Value &val) c spinBox->setValue(val.getInt()); } -void C4PropertyDelegateInt::SetModelData(QWidget *editor, const C4PropertyPath &property_path) const +void C4PropertyDelegateInt::SetModelData(QObject *editor, const C4PropertyPath &property_path) const { QSpinBox *spinBox = static_cast(editor); spinBox->interpretText(); @@ -188,7 +189,7 @@ void C4PropertyDelegateColor::SetEditorData(QWidget *aeditor, const C4Value &val editor->last_value = val; } -void C4PropertyDelegateColor::SetModelData(QWidget *aeditor, const C4PropertyPath &property_path) const +void C4PropertyDelegateColor::SetModelData(QObject *aeditor, const C4PropertyPath &property_path) const { Editor *editor = static_cast(aeditor); property_path.SetProperty(editor->last_value); @@ -199,7 +200,7 @@ QWidget *C4PropertyDelegateColor::CreateEditor(const class C4PropertyDelegateFac Editor *editor; std::unique_ptr peditor((editor = new Editor(parent))); connect(editor->button, &QPushButton::pressed, this, [editor, this]() { - QColor clr = QColorDialog::getColor(QColor(editor->last_value.getInt())); + QColor clr = QColorDialog::getColor(QColor(editor->last_value.getInt()), editor, QString(), QColorDialog::ShowAlphaChannel); editor->last_value.SetInt(clr.rgba()); this->SetEditorData(editor, editor->last_value); // force update on display emit EditingDoneSignal(editor); @@ -209,7 +210,7 @@ QWidget *C4PropertyDelegateColor::CreateEditor(const class C4PropertyDelegateFac QString C4PropertyDelegateColor::GetDisplayString(const C4Value &v, C4Object *obj) const { - return QString("#%1").arg(v.getInt(), 8, 16, QChar('0')); + return QString("#%1").arg(uint32_t(v.getInt()), 8, 16, QChar('0')); } QColor C4PropertyDelegateColor::GetDisplayTextColor(const C4Value &val, class C4Object *obj) const @@ -329,8 +330,7 @@ void C4PropertyDelegateEnum::UpdateEditorParameter(C4PropertyDelegateEnum::Edito if (idx < 0 || idx >= options.size()) return; const Option &option = options[idx]; // Lazy-resolve parameter delegate - if (!option.adelegate && option.adelegate_val.GetType() != C4V_Nil) - option.adelegate = factory->GetDelegateByValue(option.adelegate_val); + EnsureOptionDelegateResolved(option); // Create editor if needed if (option.adelegate) { @@ -375,7 +375,7 @@ void C4PropertyDelegateEnum::SetEditorData(QWidget *aeditor, const C4Value &val) editor->updating = false; } -void C4PropertyDelegateEnum::SetModelData(QWidget *aeditor, const C4PropertyPath &property_path) const +void C4PropertyDelegateEnum::SetModelData(QObject *aeditor, const C4PropertyPath &property_path) const { // Fetch value from editor Editor *editor = static_cast(aeditor); @@ -428,6 +428,13 @@ void C4PropertyDelegateEnum::UpdateOptionIndex(C4PropertyDelegateEnum::Editor *e emit EditorValueChangedSignal(editor); } +void C4PropertyDelegateEnum::EnsureOptionDelegateResolved(const Option &option) const +{ + // Lazy-resolve parameter delegate + if (!option.adelegate && option.adelegate_val.GetType() != C4V_Nil) + option.adelegate = factory->GetDelegateByValue(option.adelegate_val); +} + QString C4PropertyDelegateEnum::GetDisplayString(const C4Value &v, class C4Object *obj) const { // Display string from value @@ -440,11 +447,10 @@ QString C4PropertyDelegateEnum::GetDisplayString(const C4Value &v, class C4Objec else { // Value found: Display option string plus parameter - Option option = options[idx]; + const Option &option = options[idx]; QString result = option.name->GetCStr(); // Lazy-resolve parameter delegate - if (!option.adelegate && option.adelegate_val.GetType() != C4V_Nil) - option.adelegate = factory->GetDelegateByValue(option.adelegate_val); + EnsureOptionDelegateResolved(option); if (option.adelegate) { C4Value param_val = v; @@ -460,6 +466,23 @@ QString C4PropertyDelegateEnum::GetDisplayString(const C4Value &v, class C4Objec } } +const C4PropertyDelegateShape *C4PropertyDelegateEnum::GetShapeDelegate(const C4Value &val) const +{ + // Does this delegate own a shape? Forward decision into selected option. + int32_t option_idx = GetOptionByValue(val); + if (option_idx < 0) return nullptr; + const Option &option = options[option_idx]; + EnsureOptionDelegateResolved(option); + if (!option.adelegate) return nullptr; + C4Value param_val = val; + if (option.value_key.Get()) + { + C4PropList *vp = val.getPropList(); + if (vp) vp->GetPropertyByS(option.value_key, ¶m_val); + } + return option.adelegate->GetShapeDelegate(param_val); +} + C4PropertyDelegateDef::C4PropertyDelegateDef(const C4PropertyDelegateFactory *factory, C4PropList *props) : C4PropertyDelegateEnum(factory, props) { @@ -544,7 +567,7 @@ void C4PropertyDelegateC4ValueInput::SetEditorData(QWidget *aeditor, const C4Val editor->edit->setText(val.GetDataString().getData()); } -void C4PropertyDelegateC4ValueInput::SetModelData(QWidget *aeditor, const C4PropertyPath &property_path) const +void C4PropertyDelegateC4ValueInput::SetModelData(QObject *aeditor, const C4PropertyPath &property_path) const { // Only set model data when pressing Enter explicitely; not just when leaving Editor *editor = static_cast(aeditor); @@ -578,6 +601,50 @@ QWidget *C4PropertyDelegateC4ValueInput::CreateEditor(const class C4PropertyDele } +/* Areas shown in viewport */ + +C4PropertyDelegateShape::C4PropertyDelegateShape(const class C4PropertyDelegateFactory *factory, C4PropList *props) + : C4PropertyDelegate(factory, props), clr(0xffff0000), can_move_center(false) +{ + if (props) + { + shape_type = props->GetPropertyStr(P_Type); + clr = props->GetPropertyInt(P_Color) | 0xff000000; + can_move_center = props->GetPropertyBool(P_CanMoveCenter); + } +} + +void C4PropertyDelegateShape::SetModelData(QObject *editor, const C4PropertyPath &property_path) const +{ + C4ConsoleQtShape *shape = static_cast(editor); + property_path.SetProperty(shape->GetValue()); +} + +void C4PropertyDelegateShape::Paint(QPainter *painter, const QStyleOptionViewItem &option, const C4Value &val) const +{ + // Background color + if (option.state & QStyle::State_Selected) + painter->fillRect(option.rect, option.palette.highlight()); + else + painter->fillRect(option.rect, option.palette.base()); + // Draw a frame in shape color + painter->save(); + QColor frame_color = QColor(QRgb(clr & 0xffffff)); + int32_t width = Clamp(option.rect.height() / 8, 2, 6) &~1; + QPen rect_pen(QBrush(frame_color), width, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin); + painter->setPen(rect_pen); + QRect inner_rect = option.rect.adjusted(width / 2, width / 2, -width / 2, -width / 2); + if (shape_type && shape_type->GetData() == "circle") + { + painter->drawEllipse(inner_rect); + if (can_move_center) painter->drawPoint(inner_rect.center()); + } + else + { + painter->drawRect(inner_rect); + } + painter->restore(); +} /* Delegate factory: Create delegates based on the C4Value type */ @@ -594,6 +661,7 @@ C4PropertyDelegate *C4PropertyDelegateFactory::CreateDelegateByString(const C4St if (str->GetData() == "bool") return new C4PropertyDelegateBool(this, props); if (str->GetData() == "has_effect") return new C4PropertyDelegateHasEffect(this, props); if (str->GetData() == "c4valueenum") return new C4PropertyDelegateC4ValueEnum(this, props); + if (str->GetData() == "rect" || str->GetData() == "circle") return new C4PropertyDelegateShape(this, props); if (str->GetData() == "any") return new C4PropertyDelegateC4ValueInput(this, props); // unknown type return NULL; @@ -679,18 +747,23 @@ void C4PropertyDelegateFactory::setModelData(QWidget *editor, QAbstractItemModel C4PropertyDelegate *d = GetDelegateByIndex(index); if (!d) return; C4ConsoleQtPropListModel::Property *prop = static_cast(index.internalPointer()); - C4PropList *props = prop->parent_proplist.getPropList(); - if (props) - { - // Compose set command - C4PropertyPath path(prop->parent_proplist.GetDataString().getData()); - C4PropertyPath subpath; - if (d->GetSetFunction()) - subpath = C4PropertyPath(path, d->GetSetFunction(), C4PropertyPath::PPT_SetFunction); - else - subpath = C4PropertyPath(path, prop->key->GetCStr()); - d->SetModelData(editor, subpath); - } + SetPropertyData(d, editor, prop); +} + +void C4PropertyDelegateFactory::SetPropertyData(const C4PropertyDelegate *d, QObject *editor, C4ConsoleQtPropListModel::Property *editor_prop) const +{ + // Safety: Ensure target properties still exist + C4PropList *target_props = editor_prop->parent_proplist.getPropList(); + if (!target_props) return; + // Compose set command + C4PropertyPath path(editor_prop->parent_proplist.GetDataString().getData()); + C4PropertyPath subpath; + if (d->GetSetFunction()) + subpath = C4PropertyPath(path, d->GetSetFunction(), C4PropertyPath::PPT_SetFunction); + else + subpath = C4PropertyPath(path, editor_prop->key->GetCStr()); + // Set according to delegate + d->SetModelData(editor, subpath); } QWidget *C4PropertyDelegateFactory::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const @@ -700,14 +773,17 @@ QWidget *C4PropertyDelegateFactory::createEditor(QWidget *parent, const QStyleOp C4ConsoleQtPropListModel::Property *prop = static_cast(index.internalPointer()); prop->about_to_edit = true; QWidget *editor = d->CreateEditor(this, parent, option); - // Connect value change signals + // Connect value change signals (if editing is possible for this property) // For some reason, commitData needs a non-const pointer - connect(d, &C4PropertyDelegate::EditorValueChangedSignal, editor, [editor, this](QWidget *signal_editor) { - if (signal_editor == editor) const_cast(this)->EditorValueChanged(editor); - }); - connect(d, &C4PropertyDelegate::EditingDoneSignal, editor, [editor, this](QWidget *signal_editor) { - if (signal_editor == editor) const_cast(this)->EditingDone(editor); - }); + if (editor) + { + connect(d, &C4PropertyDelegate::EditorValueChangedSignal, editor, [editor, this](QWidget *signal_editor) { + if (signal_editor == editor) const_cast(this)->EditorValueChanged(editor); + }); + connect(d, &C4PropertyDelegate::EditingDoneSignal, editor, [editor, this](QWidget *signal_editor) { + if (signal_editor == editor) const_cast(this)->EditingDone(editor); + }); + } return editor; } @@ -724,6 +800,26 @@ QSize C4PropertyDelegateFactory::sizeHint(const QStyleOptionViewItem &option, co return QSize(100, height); } +void C4PropertyDelegateFactory::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + // Delegate has custom painting? + C4ConsoleQtPropListModel::Property *prop = static_cast(index.internalPointer()); + C4PropertyDelegate *d = GetDelegateByIndex(index); + if (d && prop && d->HasCustomPaint()) + { + C4Value val; + C4PropList *props = prop->parent_proplist.getPropList(); + if (props) + { + d->GetPropertyValue(props, prop->key, &val); + d->Paint(painter, option, val); + return; + } + } + // Otherwise use default paint implementation + QStyledItemDelegate::paint(painter, option, index); +} + /* Proplist table view */ @@ -749,23 +845,41 @@ bool C4ConsoleQtPropListModel::AddPropertyGroup(C4PropList *add_proplist, int32_ properties.props.resize(new_properties.size()); for (int32_t i = 0; i < new_properties.size(); ++i) { - properties.props[i].parent_proplist.SetPropList(target_proplist); - properties.props[i].key = NULL; - properties.props[i].display_name = NULL; - properties.props[i].delegate_info.Set0(); // default C4Value delegate - properties.props[i].delegate = NULL; // init when needed - properties.props[i].group_idx = group_index; + Property *prop = &properties.props[i]; + prop->parent_proplist.SetPropList(target_proplist); + prop->key = NULL; + prop->display_name = NULL; + prop->delegate_info.Set0(); // default C4Value delegate + prop->group_idx = group_index; C4Value published_prop_val; add_proplist->GetPropertyByS(new_properties[i], &published_prop_val); C4PropList *published_prop = published_prop_val.getPropList(); if (published_prop) { - properties.props[i].key = published_prop->GetPropertyStr(P_Key); - properties.props[i].display_name = published_prop->GetPropertyStr(P_Name); - properties.props[i].delegate_info.SetPropList(published_prop); + prop->key = published_prop->GetPropertyStr(P_Key); + prop->display_name = published_prop->GetPropertyStr(P_Name); + prop->delegate_info.SetPropList(published_prop); + } + if (!prop->key) properties.props[i].key = ::Strings.RegString(new_properties[i]->GetCStr() + strlen(editor_prop_prefix)); + if (!prop->display_name) properties.props[i].display_name = ::Strings.RegString(new_properties[i]->GetCStr() + strlen(editor_prop_prefix)); + prop->delegate = delegate_factory->GetDelegateByValue(prop->delegate_info); + C4Value v; + prop->delegate->GetPropertyValue(target_proplist, prop->key, &v); + // Connect editable shape to property + const C4PropertyDelegateShape *new_shape_delegate = prop->delegate->GetShapeDelegate(v); + if (new_shape_delegate != prop->shape_delegate) + { + prop->shape_delegate = new_shape_delegate; + if (new_shape_delegate) + { + C4ConsoleQtShape *shape = ::Console.EditCursor.GetShapes()->CreateShape(target_proplist->GetObject(), published_prop, v); + C4PropertyDelegateFactory *factory = this->delegate_factory; + connect(shape, &C4ConsoleQtShape::ShapeDragged, new_shape_delegate, [factory, new_shape_delegate, shape, prop]() { + factory->SetPropertyData(new_shape_delegate, shape, prop); + }); + prop->shape.Set(shape); + } } - if (!properties.props[i].key) properties.props[i].key = ::Strings.RegString(new_properties[i]->GetCStr() + strlen(editor_prop_prefix)); - if (!properties.props[i].display_name) properties.props[i].display_name = ::Strings.RegString(new_properties[i]->GetCStr() + strlen(editor_prop_prefix)); } return true; } @@ -821,6 +935,8 @@ void C4ConsoleQtPropListModel::SetPropList(class C4PropList *new_proplist) internal_properties.props[i].delegate_info.Set0(); // default C4Value delegate internal_properties.props[i].delegate = NULL; // init when needed internal_properties.props[i].group_idx = num_groups; + internal_properties.props[i].shape.Clear(); + internal_properties.props[i].shape_delegate = nullptr; } ++num_groups; } diff --git a/src/editor/C4ConsoleQtPropListViewer.h b/src/editor/C4ConsoleQtPropListViewer.h index 8b7a0226a..8bcfe338f 100644 --- a/src/editor/C4ConsoleQtPropListViewer.h +++ b/src/editor/C4ConsoleQtPropListViewer.h @@ -23,8 +23,12 @@ #include "C4Include.h" // needed for automoc #include "editor/C4ConsoleGUI.h" // for glew.h #include "editor/C4ConsoleQt.h" +#include "editor/C4ConsoleQtShapes.h" #include "script/C4Value.h" +class C4ConsoleQtPropListModel; +struct C4ConsoleQtPropListModelProperty; + // Path to a property, like e.g. Object(123).foo.bar[456].baz // Used to allow proper synchronization of property setting class C4PropertyPath @@ -64,15 +68,17 @@ public: virtual ~C4PropertyDelegate() { } virtual void SetEditorData(QWidget *editor, const C4Value &val) const = 0; - virtual void SetModelData(QWidget *editor, const C4PropertyPath &property_path) const = 0; + virtual void SetModelData(QObject *editor, const C4PropertyPath &property_path) const = 0; virtual QWidget *CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option) const = 0; virtual void UpdateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option) const; virtual bool GetPropertyValue(C4PropList *props, C4String *key, C4Value *out_val) const; virtual QString GetDisplayString(const C4Value &val, class C4Object *obj) const; virtual QColor GetDisplayTextColor(const C4Value &val, class C4Object *obj) const; virtual QColor GetDisplayBackgroundColor(const C4Value &val, class C4Object *obj) const; - - const char *GetSetFunction() const { return set_function.Get() ? set_function->GetCStr() : NULL; } // get name of setter function for this property + const char *GetSetFunction() const { return set_function.Get() ? set_function->GetCStr() : nullptr; } // get name of setter function for this property + virtual const class C4PropertyDelegateShape *GetShapeDelegate(const C4Value &val) const { return nullptr; } + virtual bool HasCustomPaint() const { return false; } + virtual void Paint(QPainter *painter, const QStyleOptionViewItem &option, const C4Value &val) const { } signals: void EditorValueChangedSignal(QWidget *editor) const; @@ -87,7 +93,7 @@ public: C4PropertyDelegateInt(const class C4PropertyDelegateFactory *factory, C4PropList *props); void SetEditorData(QWidget *editor, const C4Value &val) const override; - void SetModelData(QWidget *editor, const C4PropertyPath &property_path) const override; + void SetModelData(QObject *editor, const C4PropertyPath &property_path) const override; QWidget *CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option) const override; }; @@ -113,7 +119,7 @@ public: C4PropertyDelegateColor(const class C4PropertyDelegateFactory *factory, C4PropList *props); void SetEditorData(QWidget *editor, const C4Value &val) const override; - void SetModelData(QWidget *editor, const C4PropertyPath &property_path) const override; + void SetModelData(QObject *editor, const C4PropertyPath &property_path) const override; QWidget *CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option) const override; QString GetDisplayString(const C4Value &v, C4Object *obj) const override; QColor GetDisplayTextColor(const C4Value &val, class C4Object *obj) const override; @@ -175,13 +181,15 @@ public: void AddConstOption(C4String *name, const C4Value &val); void SetEditorData(QWidget *editor, const C4Value &val) const override; - void SetModelData(QWidget *editor, const C4PropertyPath &property_path) const override; + void SetModelData(QObject *editor, const C4PropertyPath &property_path) const override; QWidget *CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option) const override; QString GetDisplayString(const C4Value &val, class C4Object *obj) const override; + const class C4PropertyDelegateShape *GetShapeDelegate(const C4Value &val) const override; // Forward to parameter of selected option private: int32_t GetOptionByValue(const C4Value &val) const; void UpdateEditorParameter(C4PropertyDelegateEnum::Editor *editor) const; + void EnsureOptionDelegateResolved(const Option &option) const; public slots: void UpdateOptionIndex(Editor *editor, int idx) const; @@ -244,10 +252,27 @@ public: C4PropertyDelegateC4ValueInput(const C4PropertyDelegateFactory *factory, C4PropList *props) : C4PropertyDelegate(factory, props) { } void SetEditorData(QWidget *editor, const C4Value &val) const override; - void SetModelData(QWidget *editor, const C4PropertyPath &property_path) const override; + void SetModelData(QObject *editor, const C4PropertyPath &property_path) const override; QWidget *CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option) const override; }; +// areas shown in viewport +class C4PropertyDelegateShape : public C4PropertyDelegate +{ + C4RefCntPointer shape_type; + uint32_t clr; + bool can_move_center; +public: + C4PropertyDelegateShape(const class C4PropertyDelegateFactory *factory, C4PropList *props); + + void SetEditorData(QWidget *editor, const C4Value &val) const override { } // TODO maybe implement update? + void SetModelData(QObject *editor, const C4PropertyPath &property_path) const override; + QWidget *CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option) const override { return NULL; } + const C4PropertyDelegateShape *GetShapeDelegate(const C4Value &val) const override { return this; } + bool HasCustomPaint() const override { return true; } + void Paint(QPainter *painter, const QStyleOptionViewItem &option, const C4Value &val) const override; +}; + class C4PropertyDelegateFactory : public QStyledItemDelegate { Q_OBJECT @@ -264,17 +289,41 @@ public: C4PropertyDelegate *GetDelegateByValue(const C4Value &val) const; void ClearDelegates(); + void SetPropertyData(const C4PropertyDelegate *d, QObject *editor, C4ConsoleQtPropListModelProperty *editor_prop) const; private: void EditorValueChanged(QWidget *editor); void EditingDone(QWidget *editor); protected: + // Model callbacks forwarded to actual delegates void setEditorData(QWidget *editor, const QModelIndex &index) const override; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; +}; + +// One property in the prop list model view +struct C4ConsoleQtPropListModelProperty +{ + C4PropertyPath property_path; + C4Value parent_proplist; + C4RefCntPointer display_name; + C4RefCntPointer key; + C4Value delegate_info; + C4PropertyDelegate *delegate; + bool about_to_edit; + + // Parent group index + int32_t group_idx; + + // Each property may be connected to one shape shown in the viewport for editing + C4ConsoleQtShapeHolder shape; + const C4PropertyDelegate *shape_delegate; + + C4ConsoleQtPropListModelProperty() : delegate(nullptr), about_to_edit(false), group_idx(-1), shape_delegate(nullptr) {} }; // Prop list view implemented as a model view @@ -283,19 +332,7 @@ class C4ConsoleQtPropListModel : public QAbstractItemModel Q_OBJECT public: - struct Property - { - C4PropertyPath property_path; - C4Value parent_proplist; - C4RefCntPointer display_name; - C4RefCntPointer key; - C4Value delegate_info; - C4PropertyDelegate *delegate; - bool about_to_edit; - int32_t group_idx; - - Property() : delegate(NULL), about_to_edit(false), group_idx(-1) {} - }; + typedef C4ConsoleQtPropListModelProperty Property; struct PropertyGroup { QString name; diff --git a/src/editor/C4ConsoleQtShapes.cpp b/src/editor/C4ConsoleQtShapes.cpp new file mode 100644 index 000000000..f3cdcdc1e --- /dev/null +++ b/src/editor/C4ConsoleQtShapes.cpp @@ -0,0 +1,369 @@ +/* +* OpenClonk, http://www.openclonk.org +* +* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ +* Copyright (c) 2013, The OpenClonk Team and contributors +* +* Distributed under the terms of the ISC license; see accompanying file +* "COPYING" for details. +* +* "Clonk" is a registered trademark of Matthes Bender, used with permission. +* See accompanying file "TRADEMARK" for details. +* +* To redistribute this file separately, substitute the full license texts +* for the above references. +*/ + +/* Editable shapes in the viewports (like e.g. AI guard range rectangles) */ + +#include "C4Include.h" +#include "editor/C4Console.h" +#include "editor/C4ConsoleQtState.h" +#include "editor/C4ConsoleQtShapes.h" +#include "graphics/C4FacetEx.h" +#include "object/C4Object.h" + +/* Generic shape */ + +C4ConsoleQtShape::C4ConsoleQtShape(C4Object *for_obj, C4PropList *props) + : is_relative(false), dragging_border(-1), border_color(0xffff0000) +{ + rel_obj.SetPropList(for_obj); + if (props) + { + is_relative = props->GetPropertyBool(P_Relative); + border_color = props->GetPropertyInt(P_Color) | 0xff000000; + } +} + +uint32_t C4ConsoleQtShape::GetBorderColor(int32_t border_index, bool dragging_border_is_bitmask) const +{ + // Return shape color, or dragged border color if index is the border currently being dragged + if (IsDragging()) + if ((dragging_border == border_index) || (dragging_border_is_bitmask && (dragging_border & border_index))) + return 0xffffffff; + return border_color; +} + +int32_t C4ConsoleQtShape::AbsX(int32_t rel_x) const +{ + if (is_relative) + { + C4Object *obj = rel_obj.getObj(); + if (obj) rel_x += obj->GetX(); + } + return rel_x; +} + +int32_t C4ConsoleQtShape::AbsY(int32_t rel_y) const +{ + if (is_relative) + { + C4Object *obj = rel_obj.getObj(); + if (obj) rel_y += obj->GetY(); + } + return rel_y; +} + + +/* Rectangular shape*/ + +C4ConsoleQtRect::C4ConsoleQtRect(C4Object *for_obj, C4PropList *props, const C4Value &val) + : C4ConsoleQtShape(for_obj, props), left(0), top(0), right(10), bottom(10) +{ + // Expect rect to be given as [left,top,width,height] + C4ValueArray *varr = val.getArray(); + if (varr && varr->GetSize() >= 4) + { + left = varr->GetItem(0).getInt(); + top = varr->GetItem(1).getInt(); + right = left + varr->GetItem(2).getInt()-1; // right/bottom borders are drawn inclusively + bottom = top + varr->GetItem(3).getInt()-1; + } +} + +bool C4ConsoleQtRect::IsHit(int32_t x, int32_t y, int32_t hit_range, Qt::CursorShape *drag_cursor, int32_t *drag_border) +{ + // Current border pos + int32_t left = AbsX(this->left), top = AbsY(this->top); + int32_t right = AbsX(this->right), bottom = AbsY(this->bottom); + // Distance to each border + int32_t dleft = Abs(left - x); + int32_t dtop = Abs(top - y); + int32_t dright = Abs(right - x); + int32_t dbottom = Abs(bottom - y); + // In box at all? + if (x < left - hit_range || y < top - hit_range || x > right + hit_range || y > bottom + hit_range) + return false; + // Border hit? + bool hit_left = (dleft <= hit_range && dleft < dright); + bool hit_top = (dtop <= hit_range && dtop < dbottom); + bool hit_right = (!hit_left && dright <= hit_range); + bool hit_bottom = (!hit_top && dbottom <= hit_range); + // Compose cursor and drag border + int32_t idrag_border = (hit_left * CNAT_Left) + (hit_top * CNAT_Top) + (hit_right * CNAT_Right) + (hit_bottom * CNAT_Bottom); + if (idrag_border) *drag_border = idrag_border; + if (hit_left || hit_right) + if (hit_top || hit_bottom) + *drag_cursor = (hit_left == hit_top) ? Qt::SizeFDiagCursor : Qt::SizeBDiagCursor; + else + *drag_cursor = Qt::SizeHorCursor; + else if (hit_top || hit_bottom) + *drag_cursor = Qt::SizeVerCursor; + return !!idrag_border; +} + +void C4ConsoleQtRect::Draw(class C4TargetFacet &cgo, float line_width) +{ + float left = float(AbsX(this->left)) + cgo.X - cgo.TargetX; + float top = float(AbsY(this->top)) + cgo.Y - cgo.TargetY; + float right = float(AbsX(this->right)) + cgo.X - cgo.TargetX; + float bottom = float(AbsY(this->bottom)) + cgo.Y - cgo.TargetY; + pDraw->DrawLineDw(cgo.Surface, left, top, right, top, GetBorderColor(CNAT_Top, true), line_width); + pDraw->DrawLineDw(cgo.Surface, right, top, right, bottom, GetBorderColor(CNAT_Right, true), line_width); + pDraw->DrawLineDw(cgo.Surface, right, bottom, left, bottom, GetBorderColor(CNAT_Bottom, true), line_width); + pDraw->DrawLineDw(cgo.Surface, left, bottom, left, top, GetBorderColor(CNAT_Left, true), line_width); +} + +void C4ConsoleQtRect::Drag(int32_t x, int32_t y, int32_t dx, int32_t dy) +{ + if (dragging_border & CNAT_Left) left += dx; + if (dragging_border & CNAT_Top) top += dy; + if (dragging_border & CNAT_Right) right += dx; + if (dragging_border & CNAT_Bottom) bottom += dy; + if (left > right) std::swap(left, right); + if (top > bottom) std::swap(top, bottom); +} + +C4Value C4ConsoleQtRect::GetValue() const +{ + // Return array: Convert left/top/right/bottom (inclusive) to left/top/width/height + C4ValueArray *pos_array = new C4ValueArray(4); + pos_array->SetItem(0, C4VInt(left)); + pos_array->SetItem(1, C4VInt(top)); + pos_array->SetItem(2, C4VInt(right - left + 1)); + pos_array->SetItem(3, C4VInt(bottom - top + 1)); + return C4VArray(pos_array); +} + + +/* Circle shape */ + +C4ConsoleQtCircle::C4ConsoleQtCircle(class C4Object *for_obj, C4PropList *props, const C4Value &val) + : C4ConsoleQtShape(for_obj, props), radius(10), cx(0), cy(0), can_move_center(false) +{ + if (props) + { + can_move_center = props->GetPropertyBool(P_CanMoveCenter); + } + // If center is moveable, expect value as [radius, center_x, center_y] + // Otherwise just radius + if (can_move_center) + { + C4ValueArray *aval = val.getArray(); + if (aval && aval->GetSize() == 3) + { + radius = aval->GetItem(0).getInt(); + cx = aval->GetItem(1).getInt(); + cy = aval->GetItem(2).getInt(); + } + } + else + { + radius = val.getInt(); + } +} + +bool C4ConsoleQtCircle::IsHit(int32_t x, int32_t y, int32_t hit_range, Qt::CursorShape *drag_cursor, int32_t *drag_border) +{ + // Get relative circle center pos + x -= AbsX(cx); + y -= AbsY(cy); + int32_t r = x*x + y*y; + // Is on circle border? (Higher priority than center to allow resizing circle from 0 radius) + if (Inside(r, (radius - hit_range)*(radius - hit_range), (radius + hit_range)*(radius + hit_range))) + { + // Cursor by position on 60 deg circle segments + if (x * 58 / 100 / (y+!y)) // tan(30) ~= 0.58 + *drag_cursor = Qt::CursorShape::SizeHorCursor; + else if (y * 58 / 100 / (x+!x)) + *drag_cursor = Qt::CursorShape::SizeVerCursor; + else if (x*y > 0) + *drag_cursor = Qt::CursorShape::SizeFDiagCursor; + else + *drag_cursor = Qt::CursorShape::SizeBDiagCursor; + *drag_border = 0; + return true; + } + // Circle center? + if (can_move_center && r <= hit_range*hit_range) + { + *drag_cursor = Qt::CursorShape::SizeAllCursor; + *drag_border = 1; + return true; + } + return false; +} + +void C4ConsoleQtCircle::Draw(class C4TargetFacet &cgo, float line_width) +{ + // Circle + pDraw->DrawCircleDw(cgo.Surface, AbsX(cx) + cgo.X - cgo.TargetX, AbsY(cy) + cgo.Y - cgo.TargetY, radius, GetBorderColor(0, false), line_width); + // Center if moveable + if (can_move_center) + pDraw->DrawCircleDw(cgo.Surface, AbsX(cx) + cgo.X - cgo.TargetX, AbsY(cy) + cgo.Y - cgo.TargetY, line_width*3, GetBorderColor(1, false), line_width); +} + +void C4ConsoleQtCircle::C4ConsoleQtCircle::Drag(int32_t x, int32_t y, int32_t dx, int32_t dy) +{ + if (dragging_border == 0) + { + x -= AbsX(cx); + y -= AbsY(cy); + radius = int32_t(sqrt(double(x*x + y*y))); + } + else if (dragging_border == 1) + { + cx += dx; + cy += dy; + } +} + +C4Value C4ConsoleQtCircle::GetValue() const +{ + // Return single value for non-center-adjustable circles; return [radius, cx, cy] otherwise + if (can_move_center) + { + C4ValueArray *pos_array = new C4ValueArray(3); + pos_array->SetItem(0, C4VInt(radius)); + pos_array->SetItem(1, C4VInt(cx)); + pos_array->SetItem(2, C4VInt(cy)); + return C4VArray(pos_array); + } + else + { + return C4VInt(radius); + } +} + + +/* Shape list */ + +C4ConsoleQtShape *C4ConsoleQtShapes::CreateShape(class C4Object *for_obj, C4PropList *props, const C4Value &val) +{ + C4String *type = props->GetPropertyStr(P_Type); + if (!type) return nullptr; + C4ConsoleQtShape *shape = nullptr; + if (type->GetData() == "rect") shape = new C4ConsoleQtRect(for_obj, props, val); + else if (type->GetData() == "circle") shape = new C4ConsoleQtCircle(for_obj, props, val); + return shape; +} + +void C4ConsoleQtShapes::AddShape(C4ConsoleQtShape *shape) +{ + if (shape) shapes.emplace_back(shape); +} + +void C4ConsoleQtShapes::RemoveShape(C4ConsoleQtShape *shape) +{ + // Remove from list and currently moving shape + shapes.remove_if([shape](auto &it) { return it.get() == shape; }); + if (dragging_shape == shape) dragging_shape = NULL; +} + +void C4ConsoleQtShapes::ClearShapes() +{ + shapes.clear(); + dragging_shape = NULL; + drag_cursor = Qt::CursorShape::ArrowCursor; +} + +void C4ConsoleQtShapes::Draw(C4TargetFacet &cgo) +{ + // Draw all shapes with at least 1px line width + ZoomDataStackItem zdsi(cgo.X, cgo.Y, cgo.Zoom); + float line_width = std::max(1.0f, 1.0f / cgo.Zoom); + for (auto &shape : shapes) shape->Draw(cgo, line_width); +} + +bool C4ConsoleQtShapes::MouseDown(float x, float y, float hit_range) +{ + // Check for shape hit and start dragging if a shape is in hit range + int32_t hit_range_int = std::max(int32_t(hit_range + 0.5f), 1); // Using integer hit ranges for now + // Ensure no leftover other shape + if (dragging_shape) MouseUp(x, y); + int32_t drag_border=-1; + for (auto &shape : shapes) + { + if (shape->IsHit(x, y, hit_range_int, &drag_cursor, &drag_border)) + { + dragging_shape = shape.get(); + dragging_shape->StartDragging(drag_border); + drag_x = x; + drag_y = y; + return true; + } + } + return false; +} + +void C4ConsoleQtShapes::MouseMove(float x, float y, bool left_down, float hit_range) +{ + // Check for shape hit and start dragging if a shape is in hit range + int32_t hit_range_int = std::max(int32_t(hit_range + 0.5f), 1); // Using integer hit ranges for now + // move down move: Execute shape dragging (full pixels only) + if (dragging_shape && left_down) + { + int32_t dx = int32_t(round(x - drag_x)), + dy = int32_t(round(y - drag_y)); + if (dx || dy) + { + drag_x += dx; + drag_y += dy; + dragging_shape->Drag(drag_x, drag_y, dx, dy); + } + } + else if (!left_down) + { + // Just moving around: Update cursor + drag_cursor = Qt::CursorShape::ArrowCursor; + int32_t ignored; + for (auto &shape : shapes) if (shape->IsHit(x, y, hit_range_int, &drag_cursor, &ignored)) break; + } + else + { + // Regular move: Reset drag cursor + drag_cursor = Qt::CursorShape::ArrowCursor; + } +} + +void C4ConsoleQtShapes::MouseUp(float x, float y) +{ + // Stop dragging + if (dragging_shape) + { + dragging_shape->emit ShapeDragged(); + dragging_shape->StopDragging(); + dragging_shape = NULL; + drag_cursor = Qt::CursorShape::ArrowCursor; + } +} + + +/* Shape pointer holder class */ + +void C4ConsoleQtShapeHolder::Clear() +{ + if (shape) + { + ::Console.EditCursor.GetShapes()->RemoveShape(shape); + shape = nullptr; + } +} + +void C4ConsoleQtShapeHolder::Set(C4ConsoleQtShape *new_shape) +{ + Clear(); + shape = new_shape; + if (shape) ::Console.EditCursor.GetShapes()->AddShape(shape); +} diff --git a/src/editor/C4ConsoleQtShapes.h b/src/editor/C4ConsoleQtShapes.h new file mode 100644 index 000000000..10f25d454 --- /dev/null +++ b/src/editor/C4ConsoleQtShapes.h @@ -0,0 +1,139 @@ +/* +* OpenClonk, http://www.openclonk.org +* +* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ +* Copyright (c) 2013, The OpenClonk Team and contributors +* +* Distributed under the terms of the ISC license; see accompanying file +* "COPYING" for details. +* +* "Clonk" is a registered trademark of Matthes Bender, used with permission. +* See accompanying file "TRADEMARK" for details. +* +* To redistribute this file separately, substitute the full license texts +* for the above references. +*/ + +/* Editable shapes in the viewports (like e.g. AI guard range rectangles) */ + +#ifndef INC_C4ConsoleQtShapes +#define INC_C4ConsoleQtShapes +#ifdef WITH_QT_EDITOR + +#include "editor/C4ConsoleGUI.h" // for glew.h +#include "editor/C4ConsoleQt.h" +#include "script/C4Value.h" + +// Shape base class +class C4ConsoleQtShape : public QObject +{ + Q_OBJECT +protected: + C4Value rel_obj; // Object relative to which shape is defined + bool is_relative; + int32_t dragging_border; + uint32_t border_color; + +protected: + // Return shape color, or dragged border color if index is the border currently being dragged + uint32_t GetBorderColor(int32_t border_index, bool dragging_border_is_bitmask) const; +public: + C4ConsoleQtShape(class C4Object *for_obj, C4PropList *props); + + virtual bool IsHit(int32_t x, int32_t y, int32_t hit_range, Qt::CursorShape *drag_cursor, int32_t *drag_border) = 0; + virtual void Draw(class C4TargetFacet &cgo, float line_width) = 0; + + // Coordinate transform: Add object + int32_t AbsX(int32_t rel_x) const; + int32_t AbsY(int32_t rel_x) const; + + // Start/stop dragging + void StartDragging(int32_t border) { dragging_border = border; } + void StopDragging() { dragging_border = -1; } + virtual void Drag(int32_t x, int32_t y, int32_t dx, int32_t dy) = 0; + bool IsDragging() const { return dragging_border != -1; } + + // Return current shape as C4Value to be stored back to property + virtual C4Value GetValue() const = 0; + +signals: + void ShapeDragged(); +}; + +// Rectangular shape +class C4ConsoleQtRect : public C4ConsoleQtShape +{ +private: + int32_t left, top, right, bottom; +public: + C4ConsoleQtRect(class C4Object *for_obj, C4PropList *props, const C4Value &val); + + bool IsHit(int32_t x, int32_t y, int32_t hit_range, Qt::CursorShape *drag_cursor, int32_t *drag_border) override; + void Draw(class C4TargetFacet &cgo, float line_width) override; + void Drag(int32_t x, int32_t y, int32_t dx, int32_t dy) override; + + C4Value GetValue() const override; +}; + +// Circular shape +class C4ConsoleQtCircle : public C4ConsoleQtShape +{ +private: + int32_t radius; + int32_t cx, cy; + bool can_move_center; +public: + C4ConsoleQtCircle(class C4Object *for_obj, C4PropList *props, const C4Value &val); + + bool IsHit(int32_t x, int32_t y, int32_t hit_range, Qt::CursorShape *drag_cursor, int32_t *drag_border) override; + void Draw(class C4TargetFacet &cgo, float line_width) override; + void Drag(int32_t x, int32_t y, int32_t dx, int32_t dy) override; + + C4Value GetValue() const override; +}; + +/* List of all current editable Qt shapes */ +class C4ConsoleQtShapes +{ + typedef std::list > ShapeList; + ShapeList shapes; + C4ConsoleQtShape *dragging_shape; + Qt::CursorShape drag_cursor; + float drag_x, drag_y; +public: + C4ConsoleQtShapes() : dragging_shape(nullptr), drag_x(0), drag_y(0), drag_cursor(Qt::CursorShape::ArrowCursor) { } + + C4ConsoleQtShape *CreateShape(class C4Object *for_obj, C4PropList *props, const C4Value &val); + void AddShape(C4ConsoleQtShape *shape); + void RemoveShape(C4ConsoleQtShape *shape); + void ClearShapes(); + + // Mouse callbacks from viewports to execute shape dragging + bool MouseDown(float x, float y, float hit_range); // return true if a shape was hit + void MouseMove(float x, float y, bool left_down, float hit_range); // move move: Execute shape dragging + void MouseUp(float x, float y); + + void Draw(C4TargetFacet &cgo); + + // Dragging info + bool HasDragCursor() const { return drag_cursor != Qt::CursorShape::ArrowCursor; } + Qt::CursorShape GetDragCursor() const { return drag_cursor; } +}; + +/* Shape holder class: Handles adding/removal of shape to shapes list */ +class C4ConsoleQtShapeHolder +{ + C4ConsoleQtShape *shape; + +public: + C4ConsoleQtShapeHolder() : shape(nullptr) {} + ~C4ConsoleQtShapeHolder() { Clear(); } + + void Clear(); + void Set(C4ConsoleQtShape *new_shape); + C4ConsoleQtShape *Get() const { return shape; } +}; + + +#endif // WITH_QT_EDITOR +#endif // INC_C4ConsoleQtShapes \ No newline at end of file diff --git a/src/editor/C4ConsoleQtState.cpp b/src/editor/C4ConsoleQtState.cpp index dd7975385..2ff219623 100644 --- a/src/editor/C4ConsoleQtState.cpp +++ b/src/editor/C4ConsoleQtState.cpp @@ -23,6 +23,7 @@ #include "editor/C4ConsoleQtDefinitionListViewer.h" #include "editor/C4ConsoleQtNewScenario.h" #include "editor/C4ConsoleQtViewport.h" +#include "editor/C4ConsoleQtShapes.h" #include "editor/C4Console.h" #include "platform/StdRegistry.h" #include "landscape/C4Landscape.h" diff --git a/src/editor/C4ConsoleQtState.h b/src/editor/C4ConsoleQtState.h index 011f6c6ca..c68b5b0f2 100644 --- a/src/editor/C4ConsoleQtState.h +++ b/src/editor/C4ConsoleQtState.h @@ -88,6 +88,7 @@ public: C4ConsoleQtMainWindow(class C4AbstractApp *app, class C4ConsoleGUIState *state); void closeEvent(class QCloseEvent *event) override; + class C4ConsoleGUIState *GetConsoleState() const { return state; } public slots: // Toolbar items @@ -220,6 +221,8 @@ public: void HideWelcomeScreen(); void ClearGamePointers(); + + void Draw(C4TargetFacet &cgo); }; class C4ConsoleGUI::State : public C4ConsoleGUIState diff --git a/src/editor/C4ConsoleQtViewport.cpp b/src/editor/C4ConsoleQtViewport.cpp index a7aad8f67..4f9731020 100644 --- a/src/editor/C4ConsoleQtViewport.cpp +++ b/src/editor/C4ConsoleQtViewport.cpp @@ -20,6 +20,8 @@ #include "script/C4Value.h" #include "editor/C4ConsoleQtViewport.h" #include "editor/C4ConsoleQtState.h" +#include "editor/C4Console.h" +#include "editor/C4ConsoleQtShapes.h" #include "game/C4Viewport.h" #include "editor/C4ViewportWindow.h" #include "editor/C4Console.h" @@ -67,8 +69,8 @@ void C4ConsoleQtViewportView::mouseMoveEvent(QMouseEvent *eventMove) } else { - this->setCursor(Qt::CrossCursor); cvp->pWindow->EditCursorMove(eventMove->x(), eventMove->y(), GetShiftWParam()); + this->setCursor(::Console.EditCursor.GetShapes()->HasDragCursor() ? ::Console.EditCursor.GetShapes()->GetDragCursor() : Qt::CrossCursor); } } diff --git a/src/editor/C4EditCursor.cpp b/src/editor/C4EditCursor.cpp index f4e24d5ef..32c986568 100644 --- a/src/editor/C4EditCursor.cpp +++ b/src/editor/C4EditCursor.cpp @@ -32,6 +32,9 @@ #include "game/C4Game.h" #include "object/C4GameObjects.h" #include "control/C4GameControl.h" +#ifdef WITH_QT_EDITOR +#include "editor/C4ConsoleQtShapes.h" +#endif #ifdef _WIN32 #include "res/resource.h" @@ -124,7 +127,7 @@ int32_t C4EditCursorSelection::ObjectCount() const } -C4EditCursor::C4EditCursor() +C4EditCursor::C4EditCursor() : shapes(new C4ConsoleQtShapes()) { Default(); } @@ -223,8 +226,11 @@ bool C4EditCursor::Move(float iX, float iY, DWORD dwKeyState) { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - case C4CNS_ModeEdit: +#ifdef WITH_QT_EDITOR + shapes->MouseMove(X, Y, Hold, 3.0f /* TODO: Depend on zoom */); +#endif // Hold - if (!DragFrame && Hold) + if (!DragFrame && Hold && !DragShape) { MoveSelection(ftofix(xoff),ftofix(yoff)); UpdateDropTarget(dwKeyState); @@ -361,6 +367,14 @@ bool C4EditCursor::LeftButtonDown(DWORD dwKeyState) } else { + // Click on shape? +#ifdef WITH_QT_EDITOR + if (shapes->MouseDown(X, Y, 3.0f /* TODO: Depend on zoom */)) + { + DragShape = true; + break; + } +#endif // Click on unselected: select single if (Target) { @@ -475,9 +489,13 @@ bool C4EditCursor::LeftButtonUp(DWORD dwKeyState) } // Release +#ifdef WITH_QT_EDITOR + shapes->MouseUp(X, Y); +#endif Hold=false; DragFrame=false; DragLine=false; + DragShape = false; DropTarget=NULL; // Update UpdateStatusBar(); @@ -596,6 +614,10 @@ void C4EditCursor::Draw(C4TargetFacet &cgo) { ZoomDataStackItem zdsi(cgo.X, cgo.Y, cgo.Zoom); float line_width = std::max(1.0f, 1.0f / cgo.Zoom); +#ifdef WITH_QT_EDITOR + // Draw shapes of selection + shapes->Draw(cgo); +#endif // Draw selection marks for (C4Value &obj : selection) { @@ -747,7 +769,7 @@ void C4EditCursor::Default() #ifdef USE_WIN32_WINDOWS hMenu=NULL; #endif - Hold=DragFrame=DragLine=false; + Hold=DragFrame=DragLine=DragShape=false; selection.clear(); creator_def = NULL; creator_overlay = NULL; @@ -765,6 +787,9 @@ void C4EditCursor::Clear() selection.clear(); Console.PropertyDlgUpdate(selection, false); creator_overlay.reset(NULL); +#ifdef WITH_QT_EDITOR + shapes->ClearShapes(); // Should really be empty already +#endif } bool C4EditCursor::SetMode(int32_t iMode) diff --git a/src/editor/C4EditCursor.h b/src/editor/C4EditCursor.h index 3b98a5a44..41489a175 100644 --- a/src/editor/C4EditCursor.h +++ b/src/editor/C4EditCursor.h @@ -56,7 +56,7 @@ protected: bool has_mouse_hover; int32_t Mode; float X,Y,X2,Y2; - bool Hold,DragFrame,DragLine; + bool Hold,DragFrame,DragLine,DragShape; C4Object *Target,*DropTarget; class C4Def *creator_def; std::unique_ptr creator_overlay; @@ -82,6 +82,9 @@ protected: #endif // Selection may either be any number of objects or a single non-object prop list C4EditCursorSelection selection; +#ifdef WITH_QT_EDITOR + std::unique_ptr shapes; +#endif public: void Default(); void Clear(); @@ -112,6 +115,7 @@ public: bool AltDown(); bool AltUp(); void SetMouseHover(bool h) { has_mouse_hover = h; } + class C4ConsoleQtShapes *GetShapes() const { return shapes.get(); } protected: void UpdateStatusBar(); void ApplyCreateObject(bool contained); diff --git a/src/editor/C4ViewportWindow.cpp b/src/editor/C4ViewportWindow.cpp index 22cc129c9..fd85ebe57 100644 --- a/src/editor/C4ViewportWindow.cpp +++ b/src/editor/C4ViewportWindow.cpp @@ -219,5 +219,5 @@ void C4ViewportWindow::Close() } void C4ViewportWindow::EditCursorMove(int X, int Y, uint32_t state) { - Console.EditCursor.Move(cvp->GetViewX() + X / cvp->Zoom, cvp->GetViewY() + Y / cvp->Zoom, state); + Console.EditCursor.Move(cvp->WindowToGameX(X), cvp->WindowToGameY(Y), state); } diff --git a/src/game/C4Viewport.cpp b/src/game/C4Viewport.cpp index f2ea205c4..1eb4d20ec 100644 --- a/src/game/C4Viewport.cpp +++ b/src/game/C4Viewport.cpp @@ -347,7 +347,7 @@ void C4Viewport::Draw(C4TargetFacet &cgo0, bool fDrawGame, bool fDrawOverlay) // Draw overlay C4ST_STARTNEW(OvrStat, "C4Viewport::Draw: Overlay") - if (Application.isEditor) Console.EditCursor.Draw(cgo); + if (Application.isEditor) ::Console.EditCursor.Draw(cgo); // Game messages C4ST_STARTNEW(MsgStat, "C4Viewport::DrawOverlay: Messages") diff --git a/src/game/C4Viewport.h b/src/game/C4Viewport.h index a1efbb423..52bb8e7ba 100644 --- a/src/game/C4Viewport.h +++ b/src/game/C4Viewport.h @@ -77,6 +77,10 @@ public: /** Return y-position of the center of viewport in landscape coordinates */ float GetViewCenterY() { return viewY + ViewHgt/Zoom/2; } + // Convert window coordinates to game coordinates + float WindowToGameX(int32_t win_x) { return GetViewX() + float(win_x) / Zoom; } + float WindowToGameY(int32_t win_y) { return GetViewY() + float(win_y) / Zoom; } + /** Scroll the viewport by x,y */ void ScrollView(float byX, float byY); /** Set the view position. */ diff --git a/src/gui/C4KeyboardInput.cpp b/src/gui/C4KeyboardInput.cpp index 4d1693cd3..5e134e465 100644 --- a/src/gui/C4KeyboardInput.cpp +++ b/src/gui/C4KeyboardInput.cpp @@ -606,7 +606,7 @@ C4CustomKey::C4CustomKey(const CodeList &rDefCodes, const char *szName, C4KeySco } C4CustomKey::C4CustomKey(const C4CustomKey &rCpy, bool fCopyCallbacks) - : Codes(rCpy.Codes), DefaultCodes(rCpy.DefaultCodes), Scope(rCpy.Scope), Name(), uiPriority(rCpy.uiPriority), iRef(0) + : Codes(rCpy.Codes), DefaultCodes(rCpy.DefaultCodes), Scope(rCpy.Scope), Name(), uiPriority(rCpy.uiPriority), iRef(0), is_down(false) { Name.Copy(rCpy.GetName()); if (fCopyCallbacks) diff --git a/src/object/C4Object.cpp b/src/object/C4Object.cpp index cff948e28..e784d97b3 100644 --- a/src/object/C4Object.cpp +++ b/src/object/C4Object.cpp @@ -5061,7 +5061,7 @@ void C4Object::ResetProperty(C4String * k) return C4PropListNumbered::ResetProperty(k); } -bool C4Object::GetPropertyByS(C4String *k, C4Value *pResult) const +bool C4Object::GetPropertyByS(const C4String *k, C4Value *pResult) const { if (k >= &Strings.P[0] && k < &Strings.P[P_LAST]) { diff --git a/src/object/C4Object.h b/src/object/C4Object.h index 3ff14550d..737a49cc3 100644 --- a/src/object/C4Object.h +++ b/src/object/C4Object.h @@ -398,7 +398,7 @@ public: virtual C4Object const * GetObject() const { return this; } virtual void SetPropertyByS(C4String * k, const C4Value & to); virtual void ResetProperty(C4String * k); - virtual bool GetPropertyByS(C4String *k, C4Value *pResult) const; + virtual bool GetPropertyByS(const C4String *k, C4Value *pResult) const; virtual C4ValueArray * GetProperties() const; }; diff --git a/src/script/C4Effect.cpp b/src/script/C4Effect.cpp index 6d7965a06..fd16d7066 100644 --- a/src/script/C4Effect.cpp +++ b/src/script/C4Effect.cpp @@ -535,7 +535,7 @@ void C4Effect::ResetProperty(C4String * k) C4PropListNumbered::ResetProperty(k); } -bool C4Effect::GetPropertyByS(C4String *k, C4Value *pResult) const +bool C4Effect::GetPropertyByS(const C4String *k, C4Value *pResult) const { if (k >= &Strings.P[0] && k < &Strings.P[P_LAST]) { diff --git a/src/script/C4Effect.h b/src/script/C4Effect.h index 05d388021..5e0119cd0 100644 --- a/src/script/C4Effect.h +++ b/src/script/C4Effect.h @@ -130,7 +130,7 @@ public: virtual C4Effect * GetEffect() { return this; } virtual void SetPropertyByS(C4String * k, const C4Value & to); virtual void ResetProperty(C4String * k); - virtual bool GetPropertyByS(C4String *k, C4Value *pResult) const; + virtual bool GetPropertyByS(const C4String *k, C4Value *pResult) const; virtual C4ValueArray * GetProperties() const; protected: diff --git a/src/script/C4PropList.cpp b/src/script/C4PropList.cpp index a76abe145..5ec13e5e5 100644 --- a/src/script/C4PropList.cpp +++ b/src/script/C4PropList.cpp @@ -587,9 +587,8 @@ C4Effect * C4PropList::GetEffect() return 0; } - template<> template<> -unsigned int C4Set::Hash(C4String * const & e) +unsigned int C4Set::Hash(C4String const * const & e) { assert(e); unsigned int hash = 4, tmp; @@ -606,6 +605,18 @@ unsigned int C4Set::Hash(C4String * const & e) return hash; } +template<> template<> +unsigned int C4Set::Hash(C4String * const & e) +{ + return Hash(e); +} + +template<> template<> +bool C4Set::Equals(C4Property const & a, C4String const * const & b) +{ + return a.Key == b; +} + template<> template<> bool C4Set::Equals(C4Property const & a, C4String * const & b) { @@ -618,7 +629,7 @@ unsigned int C4Set::Hash(C4Property const & p) return C4Set::Hash(p.Key); } -bool C4PropList::GetPropertyByS(C4String * k, C4Value *pResult) const +bool C4PropList::GetPropertyByS(const C4String * k, C4Value *pResult) const { if (Properties.Has(k)) { @@ -723,6 +734,20 @@ C4PropertyName C4PropList::GetPropertyP(C4PropertyName n) const return P_LAST; } +int32_t C4PropList::GetPropertyBool(C4PropertyName n) const +{ + C4String * k = &Strings.P[n]; + if (Properties.Has(k)) + { + return Properties.Get(k).Value.getBool(); + } + if (GetPrototype()) + { + return GetPrototype()->GetPropertyBool(n); + } + return false; +} + int32_t C4PropList::GetPropertyInt(C4PropertyName n, int32_t default_val) const { C4String * k = &Strings.P[n]; diff --git a/src/script/C4PropList.h b/src/script/C4PropList.h index 813231aa8..4f07a21ef 100644 --- a/src/script/C4PropList.h +++ b/src/script/C4PropList.h @@ -92,7 +92,7 @@ public: // These four operate on properties as seen by script, which can be dynamic // or reflect C++ variables - virtual bool GetPropertyByS(C4String *k, C4Value *pResult) const; + virtual bool GetPropertyByS(const C4String *k, C4Value *pResult) const; virtual C4ValueArray * GetProperties() const; // not allowed on frozen proplists virtual void SetPropertyByS(C4String * k, const C4Value & to); @@ -113,6 +113,7 @@ public: C4Value Call(C4String * k, C4AulParSet *pPars=0, bool fPassErrors=false); C4Value Call(const char * k, C4AulParSet *pPars=0, bool fPassErrors=false); C4PropertyName GetPropertyP(C4PropertyName k) const; + int32_t GetPropertyBool(C4PropertyName n) const; int32_t GetPropertyInt(C4PropertyName k, int32_t default_val = 0) const; C4PropList *GetPropertyPropList(C4PropertyName k) const; bool HasProperty(C4String * k) const { return Properties.Has(k); } diff --git a/src/script/C4StringTable.cpp b/src/script/C4StringTable.cpp index 713c9d28b..ac252f0bb 100644 --- a/src/script/C4StringTable.cpp +++ b/src/script/C4StringTable.cpp @@ -257,6 +257,8 @@ C4StringTable::C4StringTable() P[P_Key] = "Key"; P[P_Effect] = "Effect"; P[P_AsyncGet] = "AsyncGet"; + P[P_Relative] = "Relative"; + P[P_CanMoveCenter] = "CanMoveCenter"; P[DFA_WALK] = "WALK"; P[DFA_FLIGHT] = "FLIGHT"; P[DFA_KNEEL] = "KNEEL"; diff --git a/src/script/C4StringTable.h b/src/script/C4StringTable.h index bd2f3cc18..e2e637ca5 100644 --- a/src/script/C4StringTable.h +++ b/src/script/C4StringTable.h @@ -481,6 +481,8 @@ enum C4PropertyName P_Key, P_Effect, P_AsyncGet, + P_Relative, + P_CanMoveCenter, // Default Action Procedures DFA_WALK, DFA_FLIGHT,