diff --git a/src/Client/Editor/ObjectPropertyPanel.cpp b/src/Client/Editor/ObjectPropertyPanel.cpp new file mode 100644 index 0000000..0bd9afc --- /dev/null +++ b/src/Client/Editor/ObjectPropertyPanel.cpp @@ -0,0 +1,286 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +ObjectPropertyPanel::ObjectPropertyPanel() : + object(nullptr) +{ +} + +gui2::Ptr ObjectPropertyPanel::make() +{ + gui2::Ptr widget = std::make_shared(); + widget->init(); + return widget; +} + +ObjectPropertyPanel::~ObjectPropertyPanel() +{ +} + +void ObjectPropertyPanel::setObject(Object* object) +{ + this->object = object; +} + +Object* ObjectPropertyPanel::getObject() const +{ + return object; +} + +static constexpr float PROPERTY_ENTRY_HEIGHT = 24.f; +static constexpr float PROPERTY_NAME_LABEL_WIDTH = 75.f; + +void ObjectPropertyPanel::update() +{ + // Remove property entries from container. + for (auto it = propertyMap.begin(); it != propertyMap.end(); ++it) + { + propertyPanel->remove(it->second); + } + + // Remove all properties. + propertyMap.clear(); + + // Null object means empty panel. + if (object != nullptr) + { + // Get object properties. + auto objectPropertyMap = object->getAllProperties(); + + // Iterate over object properties for widget generation. + for (const auto & objectProperty : objectPropertyMap) + { + // Generate entry from property key (and object type). + gui2::Ptr entry = generatePropertyEntry(objectProperty.first, object->getType()); + + // Null entry is returned in case of invalid property key or hidden property. + if (entry != nullptr) + { + propertyMap[objectProperty.first] = entry; + } + } + + // Resize grid panel. + propertyPanel->setGridSize(1, propertyMap.size()); + + // Keep track of Y-position of grid cell to place entries in. + std::size_t placementIndex = 0; + + // Place property entries in container. + for (auto & propertyEntry : propertyMap) + { + propertyPanel->add(propertyEntry.second, 0, placementIndex++); + } + } + + // Set preferred size (width is irrelevant). + setAutomaticSize(sf::Vector2f(PROPERTY_ENTRY_HEIGHT, PROPERTY_ENTRY_HEIGHT * (propertyMap.size() + 1))); + + // Fill entries with values. + updateValues(); + + // Set caption. + if (object == nullptr) + { + caption->setEnabled(false); + caption->setText("Properties"); + } + else + { + caption->setEnabled(true); + caption->setText(capitalize(Object::getTypeName(object->getType())) + " Properties"); + } +} + +void ObjectPropertyPanel::updateValues() +{ + if (object != nullptr) + { + for (auto it = propertyMap.begin(); it != propertyMap.end(); ++it) + { + it->second->setPropertyValue(object->getPropertyString(it->first)); + } + } +} + +void ObjectPropertyPanel::init() +{ + setAutoManagingHeight(true); + gui2::Container::init(); + + propertyPanel = gui2::GridPanel::make(); + + borderPanel = gui2::BorderPanel::make(); + borderPanel->add(propertyPanel, gui2::BorderPanel::Center); + + caption = gui2::Text::make(); + caption->setTextAlignment(AlignCenter); + borderPanel->add(caption, gui2::BorderPanel::Top, PROPERTY_ENTRY_HEIGHT); + + add(borderPanel); + + update(); +} + +ObjectPropertyPanel::PropertyEntry::PropertyEntry() +{ +} + +ObjectPropertyPanel::PropertyEntry::~PropertyEntry() +{ +} + +void ObjectPropertyPanel::PropertyEntry::setPropertyName(std::string name) +{ + propertyName = name; + propertyNameText->setText(propertyName + ": "); +} + +std::string ObjectPropertyPanel::PropertyEntry::getPropertyName() const +{ + return propertyName; +} + +void ObjectPropertyPanel::PropertyEntry::init() +{ + gui2::Container::init(); + panel = gui2::BorderPanel::make(); + propertyNameText = gui2::Text::make(); + propertyNameText->setTextAlignment(AlignRight); + panel->add(propertyNameText, gui2::BorderPanel::Left, PROPERTY_NAME_LABEL_WIDTH); + add(panel); +} + +void ObjectPropertyPanel::PropertyEntry::addValueWidget(gui2::Ptr widget) +{ + panel->add(widget, gui2::BorderPanel::Center); +} + +gui2::Ptr ObjectPropertyPanel::BoolProperty::make() +{ + gui2::Ptr widget = std::make_shared(); + widget->init(); + return widget; +} + +void ObjectPropertyPanel::BoolProperty::setPropertyValue(std::string value) +{ + currentValue = cStoI(value) != 0; + checkbox->setChecked(currentValue); +} + +std::string ObjectPropertyPanel::BoolProperty::getPropertyValue() const +{ + return checkbox->isChecked() ? "1" : "0"; +} + +bool ObjectPropertyPanel::BoolProperty::wasChanged() +{ + if (changed) + { + changed = false; + return true; + } + return false; +} + +void ObjectPropertyPanel::BoolProperty::init() +{ + PropertyEntry::init(); + + currentValue = false; + changed = false; + checkbox = gui2::Checkbox::make(); + addValueWidget(checkbox); +} + +void ObjectPropertyPanel::BoolProperty::onProcessContainer(gui2::WidgetEvents& events) +{ + if (currentValue != checkbox->isChecked()) + { + currentValue = checkbox->isChecked(); + changed = true; + } +} + +gui2::Ptr ObjectPropertyPanel::StringProperty::make(bool numeric) +{ + gui2::Ptr widget = std::make_shared(); + widget->init(); + widget->setNumeric(numeric); + return widget; +} + +void ObjectPropertyPanel::StringProperty::setPropertyValue(std::string value) +{ + input->setTextSilent(value); +} + +std::string ObjectPropertyPanel::StringProperty::getPropertyValue() const +{ + return input->getText(); +} + +bool ObjectPropertyPanel::StringProperty::wasChanged() +{ + return input->wasChanged(); +} + +void ObjectPropertyPanel::StringProperty::setNumeric(bool numeric) +{ + input->setInputType(numeric ? gui2::TextField::InputInteger : gui2::TextField::InputText); +} + +bool ObjectPropertyPanel::StringProperty::isNumeric() const +{ + return input->getInputType() == gui2::TextField::InputInteger; +} + +void ObjectPropertyPanel::StringProperty::init() +{ + PropertyEntry::init(); + + input = gui2::TextField::make(); + addValueWidget(input); +} + +gui2::Ptr ObjectPropertyPanel::generatePropertyEntry(Object::Property property, + Object::Type objectType) +{ + gui2::Ptr entry; + + switch (Object::getPropertyValueType(objectType, property)) + { + case Object::PropertyValueType::String: + default: + entry = StringProperty::make(false); + break; + case Object::PropertyValueType::Int: + entry = StringProperty::make(true); + break; + case Object::PropertyValueType::Bool: + entry = BoolProperty::make(); + break; + } + + entry->setPropertyName(Object::getPropertyName(property)); + + return entry; +} + +void ObjectPropertyPanel::onProcessContainer(gui2::WidgetEvents& events) +{ + for (auto & currentEntry : propertyMap) + { + if (currentEntry.second->wasChanged()) + { + object->setPropertyString(currentEntry.first, currentEntry.second->getPropertyValue()); + } + } +} diff --git a/src/Client/Editor/ObjectPropertyPanel.hpp b/src/Client/Editor/ObjectPropertyPanel.hpp new file mode 100644 index 0000000..2b58713 --- /dev/null +++ b/src/Client/Editor/ObjectPropertyPanel.hpp @@ -0,0 +1,129 @@ +#ifndef SRC_CLIENT_EDITOR_OBJECTPROPERTYPANEL_HPP_ +#define SRC_CLIENT_EDITOR_OBJECTPROPERTYPANEL_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace gui2 +{ +class WidgetEvents; +} + +class Object; + +class ObjectPropertyPanel : public gui2::Container +{ +public: + + static gui2::Ptr make(); + + ObjectPropertyPanel(); + virtual ~ObjectPropertyPanel(); + + void setObject(Object * object); + Object * getObject() const; + + void update(); + void updateValues(); + +protected: + + virtual void init() override; + +private: + + class PropertyEntry : public gui2::Container + { + public: + PropertyEntry(); + virtual ~PropertyEntry(); + + void setPropertyName(std::string name); + std::string getPropertyName() const; + + virtual void setPropertyValue(std::string value) = 0; + virtual std::string getPropertyValue() const = 0; + + virtual bool wasChanged() = 0; + + protected: + + virtual void init() override; + + void addValueWidget(gui2::Ptr widget); + + private: + + gui2::Ptr panel; + gui2::Ptr propertyNameText; + std::string propertyName; + }; + + class BoolProperty : public PropertyEntry + { + public: + + static gui2::Ptr make(); + + virtual void setPropertyValue(std::string value) override; + virtual std::string getPropertyValue() const override; + + virtual bool wasChanged() override; + + protected: + + virtual void init() override; + + private: + + void onProcessContainer(gui2::WidgetEvents & events) override; + + gui2::Ptr checkbox; + bool currentValue; + bool changed; + }; + + class StringProperty : public PropertyEntry + { + public: + + static gui2::Ptr make(bool numeric); + + virtual void setPropertyValue(std::string value) override; + virtual std::string getPropertyValue() const override; + + virtual bool wasChanged() override; + + void setNumeric(bool numeric); + bool isNumeric() const; + + protected: + + virtual void init() override; + + private: + + gui2::Ptr input; + }; + + gui2::Ptr generatePropertyEntry(Object::Property property, Object::Type objectType); + + void onProcessContainer(gui2::WidgetEvents & events) override; + + Object * object; + + std::map> propertyMap; + + gui2::Ptr borderPanel; + gui2::Ptr propertyPanel; + gui2::Ptr caption; +}; + +#endif diff --git a/src/Client/Editor/Tools/Panels/ObjectToolPanel.cpp b/src/Client/Editor/Tools/Panels/ObjectToolPanel.cpp index 64cff40..b63a63b 100644 --- a/src/Client/Editor/Tools/Panels/ObjectToolPanel.cpp +++ b/src/Client/Editor/Tools/Panels/ObjectToolPanel.cpp @@ -9,7 +9,7 @@ #include gui2::Ptr ObjectToolPanel::make(std::vector objects, - const ObjectAppearanceManager & objectAppearance) + const ObjectAppearanceManager & objectAppearance) { gui2::Ptr widget = std::make_shared(objects, objectAppearance); widget->init(); @@ -17,7 +17,7 @@ gui2::Ptr ObjectToolPanel::make(std::vector objects, } ObjectToolPanel::ObjectToolPanel(std::vector objects, const ObjectAppearanceManager & objectAppearance) : - objectAppearance(&objectAppearance) + objectAppearance(&objectAppearance) { this->objects = std::move(objects); } @@ -39,8 +39,7 @@ static const std::vector BRUSH_MODE_NAMES = { "Replace front", "Place/replace front", "Erase all", - "Erase front" -}; + "Erase front" }; Brush::ObjectMode ObjectToolPanel::getPrimaryMode() const { @@ -76,13 +75,16 @@ void ObjectToolPanel::init() modePanel->add(primaryModeMenu); modePanel->add(secondaryModeMenu); - panel = SelectionPanel::make(); - panel->setTexture(objectAppearance->getTexture()); - panel->setMapper(mapperFactory.generateObjectMapper(this->objects), this->objects.size()); + selectionPanel = SelectionPanel::make(); + selectionPanel->setTexture(objectAppearance->getTexture()); + selectionPanel->setMapper(mapperFactory.generateObjectMapper(this->objects), this->objects.size()); + + propertyPanel = ObjectPropertyPanel::make(); auto borderPanel = gui2::BorderPanel::make(); - borderPanel->add(panel, gui2::BorderPanel::Center); + borderPanel->add(selectionPanel, gui2::BorderPanel::Center); + borderPanel->add(propertyPanel, gui2::BorderPanel::Bottom); borderPanel->add(modePanel, gui2::BorderPanel::Bottom, 50); add(borderPanel); @@ -90,17 +92,39 @@ void ObjectToolPanel::init() void ObjectToolPanel::onProcessContainer(gui2::WidgetEvents& events) { - if (panel->wasChanged()) + if (selectionPanel->wasChanged()) { - if (panel->hasSelection()) + if (selectionPanel->hasSelection()) { // Copy object into selection to allow applying properties without modifying prototype array. - selectedObject = Object(objects[panel->getSelection()]); + selectedObject = Object(objects[selectionPanel->getSelection()]); + + // Set property defaults for missing property keys. + addDefaultPropertiesToObject(); + + // Update property panel. + propertyPanel->setObject(&selectedObject); + propertyPanel->update(); } else { // Assign "none" type to mark object as invalid. selectedObject.setType(Object::Type::None); + + // Update property panel. + propertyPanel->setObject(nullptr); + propertyPanel->update(); + } + } +} + +void ObjectToolPanel::addDefaultPropertiesToObject() +{ + for (const auto & property : Object::getDefaultProperties(selectedObject.getType())) + { + if (!selectedObject.hasProperty(property.first)) + { + selectedObject.setPropertyString(property.first, property.second); } } } diff --git a/src/Client/Editor/Tools/Panels/ObjectToolPanel.hpp b/src/Client/Editor/Tools/Panels/ObjectToolPanel.hpp index d842c91..cb10011 100644 --- a/src/Client/Editor/Tools/Panels/ObjectToolPanel.hpp +++ b/src/Client/Editor/Tools/Panels/ObjectToolPanel.hpp @@ -1,8 +1,8 @@ #ifndef SRC_CLIENT_EDITOR_TOOLS_PANELS_OBJECTTOOLPANEL_HPP_ #define SRC_CLIENT_EDITOR_TOOLS_PANELS_OBJECTTOOLPANEL_HPP_ +#include #include -#include #include #include #include @@ -43,9 +43,11 @@ class ObjectToolPanel : public gui2::Container virtual void init() override; private: - + void onProcessContainer(gui2::WidgetEvents & events) override; + void addDefaultPropertiesToObject(); + const ObjectAppearanceManager * objectAppearance; std::vector objects; @@ -53,7 +55,8 @@ class ObjectToolPanel : public gui2::Container gui2::Ptr primaryModeMenu; gui2::Ptr secondaryModeMenu; - gui2::Ptr panel; + gui2::Ptr selectionPanel; + gui2::Ptr propertyPanel; }; #endif diff --git a/src/Shared/Level/Object.cpp b/src/Shared/Level/Object.cpp index 429726c..4797d96 100644 --- a/src/Shared/Level/Object.cpp +++ b/src/Shared/Level/Object.cpp @@ -226,6 +226,100 @@ Object::Property Object::getPropertyByName(const char * propName) return Property::Invalid; } +Object::PropertyValueType Object::getPropertyValueType(Type type, Property property) +{ + switch (property) + { + case Object::Property::ID: + case Object::Property::Subtype: + case Object::Property::BeatDelay: + case Object::Property::Color: + return PropertyValueType::Int; + + case Object::Property::Lord: + case Object::Property::BloodCost: + case Object::Property::SaleCost: + case Object::Property::SingleChoice: + case Object::Property::Hidden: + return PropertyValueType::Bool; + + case Object::Property::Contents: + default: + return PropertyValueType::String; + + case Object::Property::Type: + // Items store their name in the "type" field, whereas other object use a numeric ID. + return (type == Object::Type::Item ? PropertyValueType::String : PropertyValueType::Int); + } +} + +// TODO: Read from necroedit.res instead of hardcoding defaults map. +static const std::map > defaultProperties = +{ + { + Object::Type::Trap, + { + { Object::Property::Type, "1" }, + { Object::Property::Subtype, "0" } + } + }, + { + Object::Type::Enemy, + { + { Object::Property::Type, "0" }, + { Object::Property::BeatDelay, "0" }, + { Object::Property::Lord, "0" } + } + }, + { + Object::Type::Item, + { + { Object::Property::Type, "food_1" }, + { Object::Property::BloodCost, "0" }, + { Object::Property::SaleCost, "0" }, + { Object::Property::SingleChoice, "0" } + } + }, + { + Object::Type::Chest, + { + { Object::Property::Color, "1" }, + { Object::Property::Contents, "no_item" }, + { Object::Property::Hidden, "0" }, + { Object::Property::SaleCost, "0" }, + { Object::Property::SingleChoice, "0" } + } + }, + { + Object::Type::Crate, + { + { Object::Property::Type, "0" } + } + }, + { + Object::Type::Shrine, + { + { Object::Property::Type, "0" } + } + }, +}; + +const std::map& Object::getDefaultProperties(Type type) +{ + static const std::map emptyMap = {}; + + auto it = defaultProperties.find(type); + + if (it == defaultProperties.end()) + { + return emptyMap; + } + else + { + return it->second; + } +} + void Object::setID(ID id) { this->id = id; diff --git a/src/Shared/Level/Object.hpp b/src/Shared/Level/Object.hpp index 7028ac7..ef499bf 100644 --- a/src/Shared/Level/Object.hpp +++ b/src/Shared/Level/Object.hpp @@ -47,6 +47,13 @@ class Object Count, Invalid }; + + enum class PropertyValueType + { + Int, + String, + Bool + }; Object(Object && object) = default; Object & operator=(Object && object) = default; @@ -82,6 +89,9 @@ class Object static const char * getPropertyName(Property prop); static Property getPropertyByName(const char * propName); + + static PropertyValueType getPropertyValueType(Type type, Property property); + static const std::map & getDefaultProperties(Type type); private: