feat: add QFont type editor with FontBox and fix property name top-alignment
Add FontBox QML editor for QFont properties — collapsed display shows "family, size pt", expanded inline dropdown provides family ComboBox (populated from QFontDatabase), compact +/- size buttons, and Bold/Italic/Underline toggles. QFont is a Q_GADGET type classified as Object by parserType(), so it requires both a PropertyTypeCustomization_Font (overrides customizeHeaderRow to create the value editor, customizeChildren as no-op) and a mMetaTypeCustomizationMap entry — the Object branch of getCustomPropertyType() is extended to check this map first. Also fix property name column to always top-align (anchors.top + implicitHeight) so it stays fixed when inline editors like FontBox expand vertically, rather than drifting to row center. Files (8 changed): - resources/Qml/ValueEditor/FontBox.qml (new) - source/src/Customization/PropertyTypeCustomization_Font.h (new) - source/src/Customization/PropertyTypeCustomization_Font.cpp (new) - resources.qrc - source/src/QQuickDetailsViewBasicTypeEditor.cpp - source/src/QQuickDetailsViewMananger.cpp - source/src/QQuickDetailsViewLayoutBuilder.cpp - source/src/PropertyHandleImpl/IPropertyHandleImpl.cpp
This commit is contained in:
parent
8ea5033f0d
commit
2e93156093
|
|
@ -23,5 +23,6 @@
|
|||
<file>resources/Qml/ValueEditor/FileSelector.qml</file>
|
||||
<file>resources/Qml/ValueEditor/LineTextBox.qml</file>
|
||||
<file>resources/Qml/ValueEditor/MultiLineTextBox.qml</file>
|
||||
<file>resources/Qml/ValueEditor/FontBox.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,355 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import ColorPalette
|
||||
|
||||
Item {
|
||||
id: control
|
||||
property font value
|
||||
property var fontFamilies: []
|
||||
property bool expanded: false
|
||||
property bool internalUpdate: false
|
||||
property int sizeValue: control.value.pointSize
|
||||
|
||||
readonly property int kRowHeight: 25
|
||||
readonly property int kEditRowCount: 5
|
||||
readonly property int kExpandedHeight: kRowHeight + kEditRowCount * kRowHeight + (kEditRowCount - 1) * 2
|
||||
|
||||
implicitHeight: height
|
||||
implicitWidth: 100
|
||||
height: expanded ? kExpandedHeight : kRowHeight
|
||||
|
||||
signal asValueChanged(value: var)
|
||||
|
||||
function setValue(newValue: var) {
|
||||
if (internalUpdate) return
|
||||
if (newValue !== value) {
|
||||
internalUpdate = true
|
||||
value = newValue
|
||||
sizeValue = value.pointSize
|
||||
internalUpdate = false
|
||||
asValueChanged(value)
|
||||
}
|
||||
}
|
||||
|
||||
function applySizeEdit() {
|
||||
if (control.internalUpdate) return
|
||||
control.internalUpdate = true
|
||||
control.value.pointSize = sizeValue
|
||||
control.internalUpdate = false
|
||||
control.asValueChanged(control.value)
|
||||
}
|
||||
|
||||
// Header row
|
||||
Rectangle {
|
||||
id: headerRow
|
||||
height: kRowHeight
|
||||
width: parent.width
|
||||
color: ColorPalette.theme.textBoxBackground
|
||||
border.color: expanded ? ColorPalette.theme.comboBoxItemBackgroundHover : ColorPalette.theme.textBoxBackground
|
||||
border.width: 1
|
||||
|
||||
Text {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 5
|
||||
anchors.rightMargin: 20
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: value.family + ", " + value.pointSize + "pt"
|
||||
color: ColorPalette.theme.textPrimary
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: expanded ? "▲" : "▼"
|
||||
color: ColorPalette.theme.labelPrimary
|
||||
font.pixelSize: 10
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: control.expanded = !control.expanded
|
||||
}
|
||||
}
|
||||
|
||||
// Edit pane — 5 rows
|
||||
Column {
|
||||
id: editPane
|
||||
visible: control.expanded
|
||||
anchors.top: headerRow.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: 2
|
||||
|
||||
// Row 1: Font Family
|
||||
ComboBox {
|
||||
id: familyCombo
|
||||
width: parent.width
|
||||
height: kRowHeight
|
||||
model: control.fontFamilies.length > 0 ? control.fontFamilies : []
|
||||
currentIndex: -1
|
||||
clip: true
|
||||
|
||||
background: Rectangle {
|
||||
color: ColorPalette.theme.comboBoxBackground
|
||||
border.color: familyCombo.activeFocus ? ColorPalette.theme.comboBoxItemBackgroundHover : ColorPalette.theme.rowBorder
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: familyCombo.currentIndex >= 0 ? familyCombo.currentText : value.family
|
||||
color: ColorPalette.theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
leftPadding: 6
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
y: familyCombo.height
|
||||
width: familyCombo.width
|
||||
implicitHeight: Math.min(contentItem.contentHeight, 200)
|
||||
padding: 1
|
||||
|
||||
contentItem: ListView {
|
||||
clip: true
|
||||
implicitHeight: Math.min(contentHeight, 200)
|
||||
model: familyCombo.delegateModel
|
||||
currentIndex: familyCombo.highlightedIndex
|
||||
ScrollIndicator.vertical: ScrollIndicator {}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: ColorPalette.theme.comboBoxItemBackground
|
||||
border.color: ColorPalette.theme.rowBorder
|
||||
}
|
||||
}
|
||||
|
||||
// Sync ComboBox selection when fontFamilies arrives (C++ sets it after value)
|
||||
function syncComboIndex() {
|
||||
if (control.fontFamilies.length > 0 && value.family) {
|
||||
var idx = find(value.family)
|
||||
if (idx >= 0) currentIndex = idx
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: syncComboIndex()
|
||||
onModelChanged: syncComboIndex()
|
||||
|
||||
onActivated: control.applyEdit()
|
||||
}
|
||||
|
||||
// Row 2: Font Size — left spacer aligns "Size" with checkbox text below
|
||||
RowLayout {
|
||||
width: parent.width
|
||||
height: kRowHeight
|
||||
spacing: 0
|
||||
|
||||
// Spacer to align "Size" with checkbox text (indicator 14 + spacing 6 = 20)
|
||||
Item { Layout.preferredWidth: 20 }
|
||||
|
||||
Text {
|
||||
text: "Size"
|
||||
color: ColorPalette.theme.labelPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.preferredWidth: 28
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
// Decrease button
|
||||
Rectangle {
|
||||
implicitWidth: 22; implicitHeight: kRowHeight
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: sizeDownMa.pressed ? ColorPalette.theme.comboBoxItemBackgroundHover : "transparent"
|
||||
border.color: ColorPalette.theme.rowBorder
|
||||
border.width: 0.5
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "−"
|
||||
font.pixelSize: 12
|
||||
color: ColorPalette.theme.labelPrimary
|
||||
}
|
||||
MouseArea {
|
||||
id: sizeDownMa
|
||||
anchors.fill: parent
|
||||
onClicked: { if (sizeValue > 1) sizeValue--; applySizeEdit() }
|
||||
}
|
||||
}
|
||||
|
||||
// Number input
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
implicitHeight: kRowHeight
|
||||
color: ColorPalette.theme.comboBoxBackground
|
||||
border.color: sizeInput.activeFocus ? ColorPalette.theme.comboBoxItemBackgroundHover : ColorPalette.theme.rowBorder
|
||||
border.width: 1
|
||||
TextInput {
|
||||
id: sizeInput
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 4
|
||||
anchors.rightMargin: 4
|
||||
color: ColorPalette.theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
validator: IntValidator { bottom: 1; top: 512 }
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
text: sizeValue
|
||||
onEditingFinished: { sizeValue = parseInt(text) || 1; applySizeEdit() }
|
||||
}
|
||||
}
|
||||
|
||||
// Increase button
|
||||
Rectangle {
|
||||
implicitWidth: 22; implicitHeight: kRowHeight
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: sizeUpMa.pressed ? ColorPalette.theme.comboBoxItemBackgroundHover : "transparent"
|
||||
border.color: ColorPalette.theme.rowBorder
|
||||
border.width: 0.5
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "+"
|
||||
font.pixelSize: 12
|
||||
color: ColorPalette.theme.labelPrimary
|
||||
}
|
||||
MouseArea {
|
||||
id: sizeUpMa
|
||||
anchors.fill: parent
|
||||
onClicked: { if (sizeValue < 512) sizeValue++; applySizeEdit() }
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "pt"
|
||||
color: ColorPalette.theme.labelPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.preferredWidth: 20
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: 2
|
||||
}
|
||||
}
|
||||
|
||||
// Row 3: Bold
|
||||
CheckBox {
|
||||
id: boldCheck
|
||||
text: "Bold"
|
||||
height: kRowHeight
|
||||
checked: control.value.bold
|
||||
leftPadding: 0
|
||||
spacing: 6
|
||||
|
||||
indicator: Rectangle {
|
||||
implicitWidth: 14; implicitHeight: 14
|
||||
x: boldCheck.leftPadding
|
||||
y: (boldCheck.height - height) / 2
|
||||
color: boldCheck.checked ? ColorPalette.theme.comboBoxItemBackgroundHover : "transparent"
|
||||
border.color: boldCheck.checked ? ColorPalette.theme.comboBoxItemBackgroundHover : ColorPalette.theme.rowBorder
|
||||
border.width: 1.5
|
||||
radius: 2
|
||||
Rectangle {
|
||||
visible: boldCheck.checked
|
||||
anchors.centerIn: parent
|
||||
width: 8; height: 8
|
||||
color: ColorPalette.theme.labelPrimary
|
||||
radius: 1
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: boldCheck.text
|
||||
color: ColorPalette.theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
leftPadding: boldCheck.indicator.width + boldCheck.spacing
|
||||
}
|
||||
|
||||
onToggled: control.applyEdit()
|
||||
}
|
||||
|
||||
// Row 4: Italic
|
||||
CheckBox {
|
||||
id: italicCheck
|
||||
text: "Italic"
|
||||
height: kRowHeight
|
||||
checked: control.value.italic
|
||||
leftPadding: 0
|
||||
spacing: 6
|
||||
|
||||
indicator: Rectangle {
|
||||
implicitWidth: 14; implicitHeight: 14
|
||||
x: italicCheck.leftPadding
|
||||
y: (italicCheck.height - height) / 2
|
||||
color: italicCheck.checked ? ColorPalette.theme.comboBoxItemBackgroundHover : "transparent"
|
||||
border.color: italicCheck.checked ? ColorPalette.theme.comboBoxItemBackgroundHover : ColorPalette.theme.rowBorder
|
||||
border.width: 1.5
|
||||
radius: 2
|
||||
Rectangle {
|
||||
visible: italicCheck.checked
|
||||
anchors.centerIn: parent
|
||||
width: 8; height: 8
|
||||
color: ColorPalette.theme.labelPrimary
|
||||
radius: 1
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: italicCheck.text
|
||||
color: ColorPalette.theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
leftPadding: italicCheck.indicator.width + italicCheck.spacing
|
||||
}
|
||||
|
||||
onToggled: control.applyEdit()
|
||||
}
|
||||
|
||||
// Row 5: Underline
|
||||
CheckBox {
|
||||
id: underlineCheck
|
||||
text: "Underline"
|
||||
height: kRowHeight
|
||||
checked: control.value.underline
|
||||
leftPadding: 0
|
||||
spacing: 6
|
||||
|
||||
indicator: Rectangle {
|
||||
implicitWidth: 14; implicitHeight: 14
|
||||
x: underlineCheck.leftPadding
|
||||
y: (underlineCheck.height - height) / 2
|
||||
color: underlineCheck.checked ? ColorPalette.theme.comboBoxItemBackgroundHover : "transparent"
|
||||
border.color: underlineCheck.checked ? ColorPalette.theme.comboBoxItemBackgroundHover : ColorPalette.theme.rowBorder
|
||||
border.width: 1.5
|
||||
radius: 2
|
||||
Rectangle {
|
||||
visible: underlineCheck.checked
|
||||
anchors.centerIn: parent
|
||||
width: 8; height: 8
|
||||
color: ColorPalette.theme.labelPrimary
|
||||
radius: 1
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: underlineCheck.text
|
||||
color: ColorPalette.theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
leftPadding: underlineCheck.indicator.width + underlineCheck.spacing
|
||||
}
|
||||
|
||||
onToggled: control.applyEdit()
|
||||
}
|
||||
}
|
||||
|
||||
function applyEdit() {
|
||||
if (control.internalUpdate) return
|
||||
control.internalUpdate = true
|
||||
control.value = Qt.font({
|
||||
family: familyCombo.currentText || value.family,
|
||||
pointSize: sizeValue,
|
||||
bold: boldCheck.checked,
|
||||
italic: italicCheck.checked,
|
||||
underline: underlineCheck.checked
|
||||
})
|
||||
control.internalUpdate = false
|
||||
control.asValueChanged(control.value)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#include "PropertyTypeCustomization_Font.h"
|
||||
#include "QQuickDetailsViewLayoutBuilder.h"
|
||||
#include "QQuickDetailsViewMananger.h"
|
||||
#include "QPropertyHandle.h"
|
||||
|
||||
void PropertyTypeCustomization_Font::customizeHeaderRow(QPropertyHandle* inPropertyHandle, QQuickDetailsViewRowBuilder* inBuilder)
|
||||
{
|
||||
// Same as makePropertyRow but uses the manager's registered type editor
|
||||
// instead of the Object impl's createValueEditor (which returns nullptr).
|
||||
auto slotPair = inBuilder->makeNameValueSlot();
|
||||
|
||||
QQuickItem* nameEditor = inPropertyHandle->setupNameEditor(slotPair.first);
|
||||
QQuickItem* valueEditor = QQuickDetailsViewManager::Get()->createValueEditor(inPropertyHandle, slotPair.second);
|
||||
|
||||
QQmlEngine* engine = qmlEngine(inBuilder->rootItem());
|
||||
QQmlContext* context = qmlContext(inBuilder->rootItem());
|
||||
context->parentContext()->setContextProperty("heightProxy", valueEditor ? valueEditor : nameEditor);
|
||||
}
|
||||
|
||||
void PropertyTypeCustomization_Font::customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder)
|
||||
{
|
||||
// QFont has no children — don't expand as tree
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
#ifndef PropertyTypeCustomization_Font_h__
|
||||
#define PropertyTypeCustomization_Font_h__
|
||||
|
||||
#include "IPropertyTypeCustomization.h"
|
||||
|
||||
class PropertyTypeCustomization_Font : public IPropertyTypeCustomization {
|
||||
protected:
|
||||
virtual void customizeHeaderRow(QPropertyHandle* inPropertyHandle, QQuickDetailsViewRowBuilder* inBuilder) override;
|
||||
virtual void customizeChildren(QPropertyHandle* inPropertyHandle, QQuickDetailsViewLayoutBuilder* inBuilder) override;
|
||||
};
|
||||
|
||||
#endif // PropertyTypeCustomization_Font_h__
|
||||
|
|
@ -18,7 +18,7 @@ QQuickItem* IPropertyHandleImpl::createNameEditor(QQuickItem* inParent)
|
|||
Item{
|
||||
implicitHeight: 25
|
||||
width: parent.width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.top: parent.top
|
||||
Text {
|
||||
anchors.fill: parent
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
#include "QQuickFunctionLibrary.h"
|
||||
#include <QRegularExpression>
|
||||
#include <QDir>
|
||||
#include <QFont>
|
||||
#include <QFontDatabase>
|
||||
#include <QMetaType>
|
||||
|
||||
#define REGINTER_NUMBER_EDITOR_CREATOR(TypeName, DefaultPrecision) \
|
||||
|
|
@ -235,4 +237,33 @@ void QQuickDetailsViewManager::RegisterBasicTypeEditor() {
|
|||
connect(handle, SIGNAL(asRequestRollback(QVariant)), valueEditor, SLOT(setValue(QVariant)));
|
||||
return valueEditor;
|
||||
});
|
||||
|
||||
registerTypeEditor(QMetaType::fromType<QFont>(), [](QPropertyHandle* handle, QQuickItem* parent)->QQuickItem* {
|
||||
QQmlEngine* engine = qmlEngine(parent);
|
||||
QQmlContext* context = qmlContext(parent);
|
||||
QQmlComponent comp(engine);
|
||||
comp.setData(R"(
|
||||
import QtQuick;
|
||||
import QtQuick.Controls;
|
||||
import "qrc:/resources/Qml/ValueEditor"
|
||||
FontBox{
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width
|
||||
}
|
||||
)", QUrl());
|
||||
QVariantMap initialProperties;
|
||||
initialProperties["parent"] = QVariant::fromValue(parent);
|
||||
auto valueEditor = qobject_cast<QQuickItem*>(comp.createWithInitialProperties(initialProperties, context));
|
||||
if (!comp.errors().isEmpty()) {
|
||||
qDebug() << comp.errorString();
|
||||
}
|
||||
valueEditor->setParentItem(parent);
|
||||
valueEditor->setProperty("value", handle->getVar());
|
||||
|
||||
valueEditor->setProperty("fontFamilies", QFontDatabase::families());
|
||||
|
||||
connect(valueEditor, SIGNAL(asValueChanged(QVariant)), handle, SLOT(setVar(QVariant)));
|
||||
connect(handle, SIGNAL(asRequestRollback(QVariant)), valueEditor, SLOT(setValue(QVariant)));
|
||||
return valueEditor;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,11 +50,11 @@ QPair<QQuickItem*, QQuickItem*> QQuickDetailsViewRowBuilder::makeNameValueSlot()
|
|||
}
|
||||
Item{
|
||||
id: nameEditorContent
|
||||
height: parent.height
|
||||
implicitHeight: 25
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: padding + (detailsDelegate.isTreeNode ? (detailsDelegate.depth + 1) * detailsDelegate.indent : 0)
|
||||
anchors.right: splitter.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
Item{
|
||||
id: valueEditorContent
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@
|
|||
#include "Customization/PropertyTypeCustomization_Associative.h"
|
||||
#include "Customization/PropertyTypeCustomization_ObjectDefault.h"
|
||||
#include "Customization/PropertyTypeCustomization_Matrix4x4.h"
|
||||
#include "Customization/PropertyTypeCustomization_Font.h"
|
||||
#include <QtQuickControls2/QQuickStyle>
|
||||
#include <QMatrix4x4>
|
||||
#include <QFont>
|
||||
|
||||
QQuickDetailsViewManager* QQuickDetailsViewManager::Get()
|
||||
{
|
||||
|
|
@ -87,6 +89,10 @@ QSharedPointer<IPropertyTypeCustomization> QQuickDetailsViewManager::getCustomPr
|
|||
return QSharedPointer<PropertyTypeCustomization_Associative>::create();
|
||||
}
|
||||
else if (inHandle->getPropertyType() == QPropertyHandle::Object) {
|
||||
// Check metaType-level customizations first (for Q_GADGET types like QFont)
|
||||
if (auto it = mMetaTypeCustomizationMap.find(inHandle->getType()); it != mMetaTypeCustomizationMap.end()) {
|
||||
return it.value()();
|
||||
}
|
||||
const QMetaObject* metaObject = inHandle->metaObject();
|
||||
for (const auto& It : mClassCustomizationMap.asKeyValueRange()) {
|
||||
if (It.first == metaObject) {
|
||||
|
|
@ -134,5 +140,9 @@ QQuickDetailsViewManager::QQuickDetailsViewManager()
|
|||
RegisterBasicTypeEditor();
|
||||
|
||||
registerPropertyTypeCustomization<QMatrix4x4, PropertyTypeCustomization_Matrix4x4>();
|
||||
|
||||
mMetaTypeCustomizationMap.insert(QMetaType::fromType<QFont>(), []() {
|
||||
return QSharedPointer<PropertyTypeCustomization_Font>::create();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue