Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New properties framework #4045

Draft
wants to merge 51 commits into
base: master
Choose a base branch
from

Conversation

bjorn
Copy link
Member

@bjorn bjorn commented Aug 30, 2024

I've started implementing a new properties framework in an attempt to improve some existing usability issues as well as to make it easier to add support for list properties (#4002).

This is a work-in-progress! It its current state the Properties view can only edit a few built-in map properties.

I'm sharing this here for the curious and to gather feedback.

@bjorn
Copy link
Member Author

bjorn commented Aug 30, 2024

Here's a preview of what it looked like at some point, when it wasn't actually functional but had all Map properties represented:

image

And here's a video of the current state, with only three properties, but they are fully implemented:

resize-map-button.mp4

Main advantages are currently:

  • The widgets can be directly interacted with.
  • They can respond to layout (W/H presented horizontally or vertically based on width of view).
  • Easy to add buttons like "Resize Map" that are not hidden.
  • Huge reduction in complexity / code size compared to existing properties framework (but still lacking features, like nested properties, selection, etc.).

@dogboydog
Copy link
Contributor

I got a chance to try this out and look at the code. I like it. It's nice to have the resize map button more accessible on the map properties.

As far as the code, I'm definitely less equipped than you to judge it but I had a few thoughts.

At first I wondered if there might be a way to define these editable properties and the widgets that would be produced on the edited objects themselves (worlds, maps, etc.) but I guess that's really only used in this property editor component, so there's no need.

The properties have to take the editor factory in as a constructor argument, and currently you are creating a subclass for each type of property. I'm wondering about if there's a way to make it so that only the PropertiesWidget would even need to know about the factory. I feel like the factory could just be a way to generate editor widgets for well-known types of properties that don't need customization. Or maybe the factory just becomes a parameter to addProperty() instead and you only need to provide one if you need to customize the produced widget.

Also, it might be nice if there was a lighter syntax for adding a property that didn't entail creating a class for each property. Part of what makes me think this is the current need to store the editor factory in each property which creates a little more boilerplate for each property, while classes that customize the widget like MapSizeProperty don't actually need the factory at all from what I can see. Being able to just pass in lambdas or something for getting/setting the value to addProperty() could be nice. I would think there would be a lot of properties where you would just be calling existing getters and setters, but maybe I'm wrong.

Feel free to disregard any of this, though, just some initial thoughts that may be misinformed

@bjorn
Copy link
Member Author

bjorn commented Sep 2, 2024

@dogboydog Thanks for writing down your thoughts on the new properties implementation! They've been the exact things I'm also wondering about / struggling with. There may not be one simple solution for everything, but indeed this "factory" argument is quite annoying, especially when passing in that "default factory" most of the time anyway.

I've thought about creating the properties through the factory instead. So far there are ValueTypeEditorFactory::createProperty (to create a ValueProperty) and createQObjectProperty. But for now both have remained unused because they didn't quite fit either:

  • The ValueProperty keeps a local value, which I thought would be a good way to avoid subclassing. Of course, then signals need to be connected to keep that value updated and respond to changes. I still need to try this approach.

  • The QObjectProperty could be even more automatic, since it wraps Qt's meta object system to directly work on a declared Q_PROPERTY. However, currently none of the relevant properties are exposed as a Q_PROPERTY. The closest are those on the EditableMap class, but they're lacking a NOTIFY signal which means the widget's value can't update when needed. I'm also not entirely sure about using EditableMap since it's the scripting API, but it could work out and some people could use the change signals in the API too anyway.

When not using QObjectProperty, another thing I'm not happy about is that each property needs to do its own change signal handling. Hence I'm looking into introducing a kind of MapProperties class, which would own all the map's properties and connect to the relevant change signals instead.

src/tiled/propertieswidget.cpp Outdated Show resolved Hide resolved
@bjorn
Copy link
Member Author

bjorn commented Sep 11, 2024

Small update on the current state. Last week I've finished implementing the built-in properties for all data types, minus a few that will need a custom widget:

image

In the above we see a lacking widget for setting "Allowed Transformations" as well as for changing tileset parameters (which will use a button for now, similar to "Resize Map").

In the past days I've addressed many loose ends, like setting minimum/maximum values, step sizes and file dialog filters. I've also improved the widget for setting up the font of text objects, so it takes up less vertical space:

image

The biggest task remaining is to add support for editing, adding and removing Custom Properties, and also to make the headers collapsible. Other open tasks are restoring support for changing the properties of multiple objects at once and displaying "inherited" values like the class set on a tile when a tile object is selected.

@bjorn
Copy link
Member Author

bjorn commented Sep 27, 2024

Screenshot showing some of the recent changes:

image

  • Collapsible headers.
  • Support for editing custom properties, including support for expandable custom classes.
  • Boolean properties showing their name on the right side of the checkbox rather than in the left column (test).
  • Color properties displaying the "Unset" state again (as in old properties framework).
  • "Remove" buttons to remove top-level properties.
  • "Reset" buttons to reset class members to their default value, and indicating whether a class value is set with bold font.

@bjorn
Copy link
Member Author

bjorn commented Oct 10, 2024

After the above change, inherited properties are now shown in disabled state, with a '+' button that allows adding those properties:

image

One issue that remains to be addressed is when the removal of a property exposes an inherited property with the same name, but with a different type. The relevant property will need to be re-created in this case.

* DoubleSpinBox precision logic ported from existing property editor
* Allow spin boxes and combo boxes to shrink horizontally
* Elide property names when there isn't enough space
* Added QPointF and QColor editor factories
* Added test with all built-in map properties
* Added headers (not collapsible for now).
* Added separators.
* Added support for non-consecutive enums in EnumEditorFactory.
* Stretch name and editor widgets by a fixed 2:3 ratio and ignore their
  size hint.
* Some start on possible API for setting/getting the value.
It adjusts the layout based on its width.

Also made a few other visual tweaks.
Introduced Property class and changed from using editor factories to
having the property itself create its widget.
* Map size is read-only but features a "Resize Map" button that triggers
  the resize dialog.

* Both map size and tile size are implemented based on the new
  "ValueTypeEditorFactory" which selects an editor based on the value
  type. This functionality is moved out of the VariantEditor.

* Introduced AbstractProperty, ValueProperty and QObjectProperty in an
  attempt to provide some convenience on top of the Property interface.

I'm not entirely convinced it was a good idea to put the "createEditor"
function on the Property. It makes it easy to do custom widgets for a
property, but annoying to have it use a type-based editor.
* Introduced MapProperties class to make it easier to emit valueChanged
  for all the map's properties, as well to move their creation out of
  PropertiesWidget::currentObjectChanged.

* Added GetSetProperty that makes it easier to define a property in
  terms of a given getter and setter function, since it avoids the need
  for a specific Property subclass.

* Finished implementation of the BoolEditorFactory (signals connected).

* Moved property edit widgets to their own file.
* Introduced a few helper functions to reduce code duplication, like
  MapProperties::push.

* Disabled properties when they are irrelevant.

* Finished connecting the signals for the remaining editor factories:
  StringEditorFactory, IntEditorFactory, FloatEditorFactory,
  PointEditorFactory, PointFEditorFactory, RectFEditorFactory and
  ColorEditorFactory.
Not sure why Qt 6 didn't need these missing metatype declarations.
This property uses an editable combo box with the valid classes set up for
the given object type.
Still needs special handling for:

* Color (requires handling invalid/unset values)
* Widget for editing allowed transformations
* Widget for showing tileset parameters (and trigger edit dialog)
* Specifying 1 as minimum value for column count
Some details remain to be done:

* Changes made to layer properties should apply to all selected layers
* Opacity should probably be a slider now and have appropriate limits
* Step size for opacity and parallax factor is too big
Added support for editing QUrl values using UrlEditorFactory that
creates a FileEdit.
Also in this change:

* Made SpinBox and DoubleSpinBox don't respond to changing from
  keyboard typing immediately.

* Share a single implementation of wrapping label/widget pairs for
  SizeEdit, SizeFEdit, PointEdit, PointFEdit and RectFEdit.

* Added widget factories for QFont, QSizeF and Qt::Alignment.

* Allow setting suffix on custom FloatEditorFactory, used for adding
  degree symbol to rotation values.

A few things remain to be done:

* Custom widget for editing tile object flipping flags
* Try reducing size of font editor with style toggle buttons
Now all built-in properties are present, apart from some loose ends.

* Added RectEdit / RectEditorFactory (QRect values)
* Added Property::toolTip (set on label and editor, but currently not
  functional on label due to the eliding tool tip logic)
* Added change events to fix updating of WangSet and WangColor properties
  (was already broken with old framework)
Instead there are now typed Property subclasses similar to the generic
GetSetProperty.
This name is more appropriate for what it actually does. It also no
longer derives from EditorFactory.

PropertyFactory::createQObjectProperty should be functional again. It
now returns an appropriate property, and the QObjectProperty class is
gone. This approach is still not used, however.
* Removed EditorFactory, EnumEditorFactory, AbstractProperty,
  ValueProperty and GetSetProperty.

* EnumProperty now derives from IntProperty and retrieves the enum
  meta-data based on its template argument.

* Use the typed properties to avoid QVariant when synchronizing between
  the created editor.

For the built-in properties, this definitely simplifies things. But it
remains to be seen how custom properties are best handled.

Also due to the lack of registering editor factories, QObjectProperty
is now broken for enums (but it's unused).
Not everything was deleted from the layout because the deletion was
lacking support for handling nested layouts.

Also removed the only remaining case where a nested layout was used at
the top-level.
* Added "px" suffixes to various pixel values.

* Added support for custom enum properties (top-level only so far,
  because there is no support for class properties yet). It's amazing
  how trivial this was with the new approach (though, it does not
  support enums with values as flags yet).

* Enabled editing of tileset image parameters through a button and made
  some progress towards allowing a GroupProperty to appear like a normal
  property (currently it can, but then it no longer shows its children).
It creates a checkbox for each flag. They will need to be able to
collapse in case there are lots of flags, but this works for now.
Also when it isn't displayed as a header. Also introduced a "level"
member of VariantEditor which is passed on to the PropertyLabel, which
is used to indent expanded child properties.
Including changing of nested values. However, several things remain to be
done, including:

* When the custom properties are refreshed, all properties (and their
  widgets) are re-created. There should be a way to update their value
  instead.

* There is no way to see yet whether a property is set, nor can its value
  be reset.
For map objects, which can inherit their class from their tile or their
template.
* Top-level custom properties can now be removed.
* Class members can be reset.

A "modified" state was added to Property, which is updated as necessary
and indicated by using bold font for the name.

Further tweaks to the spacing between property widgets and draw line to
separate property group headers.

A generic VariantMapProperty was split off from the CustomProperties
class, since the former can also be a base for editing class members.

The ColorButton widget now replaces its icon with "Unset" text when an
invalid color is set.

When changing the "Class" it now affects all selected objects.

Fixed layout flicker when collapsing nested class members.
In the previous property editor, overriding happened automatically as
soon as you changed a value. Now that the edit widgets are always
visible, I've instead opted to show a disabled widget for inherited
properties along with a '+' button for adding (overriding) that
property.

I think this helps unintensionally setting a value, and the disabled
state.

Currently the VariantMapProperty does not yet handle a possible change
of property type when a removed property has a different type than the
inherited property.
* Now properties that have the same name and type are re-used, avoiding
  re-creation of editors for most property changes. This also avoids
  collapsing of nested properties in those cases.

* Properties that change type, are re-created.

We can still optimise the case where the current object changes further,
because in this case all property widgets are still re-created because
the CustomProperties object is re-added to the top-level VariantEditor
instance.
It was only used for calculating the size hint, which is now done with
some code copied and adjusted from QLineEdit::sizeHint.
For now it still does this for some common built-in properties, but no
longer for custom properties.
Might be necessary for performance reasons, because the amount of
widgets created could quickly get out of hand.
Now any properties using custom types are re-created when any change is
made to the custom types.
In order to allow them to be easily added, when they are not set on the
current object.
Same for the more specific object layer and image layer properties.
Using two toggle buttons rather than checkboxes, similar to the "Allowed
Transformations" tileset property.

Also changed the stretch factor of the property labels column to be the
same as the property editor column. It looks more balanced overall and
helps to keep the labels readable when propeties are nested a few
levels.
Also made the changes to layer offset and object flipping more specific,
only changing the actually changing axis for all selected layers or
objects respectively.

The same still needs to be done for changing object position or size.
@bjorn
Copy link
Member Author

bjorn commented Oct 14, 2024

Latest changes address various features that already existed with the old properties solution:

  • Handling changes in the custom property type definitions.
  • Showing suggested properties from all selected objects.
  • Applying changes to all selected objects or layers

Also there is now a custom widget for the tile object flipping states:

image

And there is a tool tip showing the type of each custom property, including the type for class members:

image

Also fixed some issues in left-to-right mode.
The expanded state is now remembered for each "path", meaning you can
select different objects, and when they happen to have the same
properties the expanded state applies to all of them.

For now the state is lost when Tiled is closed. It might be nice to
store the list of expanded property paths in the session.
Currently providing access to the existing Add, Remove and Reset actions
as well as "Expand All" and "Collapse All" for custom classes.
@bjorn
Copy link
Member Author

bjorn commented Oct 17, 2024

Today I've added a context menu with some useful actions:

image

Also, the expanded state is now remembered (by property name, so it applies to that property regardless of which object is selected).

@bjorn
Copy link
Member Author

bjorn commented Oct 20, 2024

Reported feedback so far:

  • Icons for the add/remove/reset buttons are lacking a fallback so they only work with a system icon theme.
  • Scroll wheel on some edit widgets triggers focus and value change (affects spin boxes and combo boxes at least).
  • Undo shortcut is eaten by local widget's undo support, but expected is generally the global undo.
  • Consider moving "Output Chunk Size" together with "Infinite" because the latter determines whether the former is enabled.
  • Consider moving "Compression Level" together with "Layer Data Format", for same reason.
  • It's hard to unfocus an editor (previously could be done by clicking anywhere outside of it).
  • Repeated changes to Map properties are not grouped together.
  • Collapsed state of headers isn't remembered.
  • If Map/Tileset/Layer/etc Properties are collapsed and one tries to access them through the menu, they should expand (see also Nothing happens when I click [Map > Map Properties...] #3806).
  • Consider adding Map/Tileset Properties buttons to the bottom of the properties panel (and to the document tab context menu) (see Tileset and Map Properties could be easier to access #3101)
  • The Add/Remove/Reset buttons take up a lot of space in a narrow view. Would be good to add an option to hide them (the actions are also available from the context menu, anyway).
  • Boolean labels True/False may be preferred to On/Off.

Many thanks to @eishiya for all their feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants