Merge pull request #12054 from kadet1090/styling-elements-and-constraints-ui

Sketcher: Apply styling to elements and constraints UI
This commit is contained in:
Chris Hennes
2024-01-23 08:40:48 -06:00
committed by GitHub
5 changed files with 381 additions and 113 deletions

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="30"
height="30"
id="svg2"
version="1.1"
viewBox="0 0 30 29.999999"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs4" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:creator>
<cc:Agent>
<dc:title>Pablo Gil</dc:title>
</cc:Agent>
</dc:creator>
<dc:subject>
<rdf:Bag>
<rdf:li>SVG</rdf:li>
<rdf:li>template</rdf:li>
</rdf:Bag>
</dc:subject>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
transform="translate(-1074.0663,-326.59799)">
<path
id="path8685"
d="m 1081.0663,345.09799 3.9999,4 12.0001,-12"
style="opacity:1;vector-effect:none;fill:none;fill-opacity:0.600939;stroke:#1b0909;stroke-width:6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.117647" />
<path
style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#555555;stroke-width:6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 1081.0663,342.59799 3.9999,4 12.0001,-12"
id="path7899" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="30"
height="30"
id="svg2"
version="1.1"
viewBox="0 0 30 29.999999"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs4" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:creator>
<cc:Agent>
<dc:title>Pablo Gil</dc:title>
</cc:Agent>
</dc:creator>
<dc:subject>
<rdf:Bag>
<rdf:li>SVG</rdf:li>
<rdf:li>template</rdf:li>
</rdf:Bag>
</dc:subject>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
transform="translate(-1074.0663,-326.59799)">
<path
id="path8685"
d="m 1081.0663,345.09799 3.9999,4 12.0001,-12"
style="opacity:1;vector-effect:none;fill:none;fill-opacity:0.600939;stroke:#1b0909;stroke-width:6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.117647" />
<path
style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#aaaaaa;stroke-width:6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 1081.0663,342.59799 3.9999,4 12.0001,-12"
id="path7899" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -262,12 +262,12 @@ public:
static QIcon snell_driven(
Gui::BitmapFactory().iconFromTheme("Constraint_SnellsLaw_Driven"));
auto selicon = [](const Sketcher::Constraint* constr,
auto selicon = [this](const Sketcher::Constraint* constr,
const QIcon& normal,
const QIcon& driven) -> QIcon {
if (!constr->isActive) {
QIcon darkIcon;
int w = QApplication::style()->pixelMetric(QStyle::PM_ListViewIconSize);
int w = listWidget()->style()->pixelMetric(QStyle::PM_ListViewIconSize);
darkIcon.addPixmap(normal.pixmap(w, w, QIcon::Disabled, QIcon::Off),
QIcon::Normal,
QIcon::Off);
@@ -466,7 +466,7 @@ protected:
QStyleOptionViewItem options = option;
initStyleOption(&options, index);
options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter);
options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter, option.widget);
ConstraintItem* item = dynamic_cast<ConstraintItem*>(view->item(index.row()));
if (!item || item->sketch->Constraints.getSize() <= item->ConstraintNbr)

View File

@@ -115,26 +115,37 @@ class ElementItemDelegate: public QStyledItemDelegate
{
Q_OBJECT
public:
/// Enum containing all controls rendered in this item. Controls in that enum MUST be in order.
enum SubControl : int {
CheckBox,
LineSelect,
StartSelect,
EndSelect,
MidSelect,
Label
};
explicit ElementItemDelegate(ElementView* parent);
~ElementItemDelegate() override;
~ElementItemDelegate() override = default;
void paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const override;
bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option,
const QModelIndex& index) override;
ElementItem* getElementtItem(const QModelIndex& index) const;
ElementItem* getElementItem(const QModelIndex& index) const;
const int border = 1; // 1px, looks good around buttons.
const int leftMargin = 4;// 4px on the left of icons, looks good.
mutable int customIconsMargin = 4;
const int textBottomMargin = 5;// 5px center the text.
QRect subControlRect(SubControl element, const QStyleOptionViewItem& option, const QModelIndex& index) const;
void drawSubControl(SubControl element, QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
const int gap = 4; // 4px of spacing between consecutive elements
Q_SIGNALS:
void itemHovered(QModelIndex);
void itemChecked(QModelIndex, Qt::CheckState state);
};
// clang-format on
// helper class to store additional information about the listWidget entry.
class ElementItem: public QListWidgetItem
{
@@ -154,8 +165,13 @@ public:
Hidden = 2,
};
ElementItem(int elementnr, int startingVertex, int midVertex, int endVertex,
Base::Type geometryType, GeometryState state, const QString& lab,
ElementItem(int elementnr,
int startingVertex,
int midVertex,
int endVertex,
Base::Type geometryType,
GeometryState state,
const QString& lab,
ViewProviderSketch* sketchView)
: ElementNbr(elementnr)
, StartingVertex(startingVertex)
@@ -177,9 +193,13 @@ public:
~ElementItem() override
{}
bool isVisible()
bool canBeHidden() const
{
return State != GeometryState::External;
}
bool isVisible() const
{
if (State != GeometryState::External) {
const auto geo = sketchView->getSketchObject()->getGeometry(ElementNbr);
if (geo) {
@@ -195,6 +215,40 @@ public:
return true;
}
QVariant data(int role) const override
{
// In order for content-box to include size of the 4 geometry icons we need to provide
// Qt with information about decoration (icon) size. This is hack to work around Qt
// limitation of not knowing about padding, border and margin boxes of stylesheets
// thus being unable to provide proper sizeHint for stylesheets to render correctly
if (role == Qt::DecorationRole) {
int size = listWidget()->style()->pixelMetric(QStyle::PM_ListViewIconSize);
return QIcon(QPixmap(QSize(size, size)));
}
return QListWidgetItem::data(role);
}
bool isGeometrySelected(Sketcher::PointPos pos) const
{
switch (pos) {
case Sketcher::PointPos::none:
return isLineSelected;
case Sketcher::PointPos::start:
return isStartingPointSelected;
case Sketcher::PointPos::end:
return isEndPointSelected;
case Sketcher::PointPos::mid:
return isMidPointSelected;
}
}
Sketcher::SketchObject* getSketchObject() const
{
return sketchView->getSketchObject();
}
int ElementNbr;
int StartingVertex;
int MidVertex;
@@ -218,6 +272,7 @@ public:
private:
ViewProviderSketch* sketchView;
};
// clang-format off
class ElementFilterList: public QListWidget
{
@@ -498,6 +553,7 @@ ElementView::~ElementView()
void ElementView::changeLayer(int layer)
{
App::Document* doc = App::GetApplication().getActiveDocument();
if (!doc)
return;
@@ -508,7 +564,6 @@ void ElementView::changeLayer(int layer)
auto geoids = getGeoIdsOfEdgesFromNames(sketchobject, ft->getSubNames());
auto geometry = sketchobject->Geometry.getValues();
auto newgeometry(geometry);
@@ -541,6 +596,47 @@ void ElementView::changeLayer(int layer)
doc->commitTransaction();
}
void ElementView::changeLayer(ElementItem* item, int layer)
{
App::Document* doc = App::GetApplication().getActiveDocument();
if (!doc) {
return;
}
doc->openTransaction("Geometry Layer Change");
auto sketchObject = item->getSketchObject();
auto geometry = sketchObject->Geometry.getValues();
auto newGeometry(geometry);
auto geoid = item->ElementNbr;
// currently only internal geometry can be changed from one layer to another
if (geoid >= 0) {
auto currentLayer = getSafeGeomLayerId(geometry[geoid]);
if (currentLayer != layer) {
auto geo = geometry[geoid]->clone();
setSafeGeomLayerId(geo, layer);
newGeometry[geoid] = geo;
sketchObject->Geometry.setValues(std::move(newGeometry));
sketchObject->solve();
}
}
else {
Gui::TranslatedUserWarning(
sketchObject,
QObject::tr("Unsupported visual layer operation"),
QObject::tr("It is currently unsupported to move external geometry to another "
"visual layer. External geometry will be omitted"));
}
doc->commitTransaction();
}
void ElementView::contextMenuEvent(QContextMenuEvent* event)
{
QMenu menu;
@@ -738,17 +834,16 @@ void ElementView::deleteSelectedItems()
void ElementView::onIndexHovered(QModelIndex index)
{
update(index);
Q_EMIT onItemHovered(itemFromIndex(index));
}
void ElementView::onIndexChecked(QModelIndex, Qt::CheckState state)
void ElementView::onIndexChecked(QModelIndex index, Qt::CheckState state)
{
if (state == Qt::Checked) {
changeLayer(static_cast<int>(ElementItem::Layer::Default));
}
else {
changeLayer(static_cast<int>(ElementItem::Layer::Hidden));
}
auto item = itemFromIndex(index);
changeLayer(item, static_cast<int>(state == Qt::Checked ? ElementItem::Layer::Default : ElementItem::Layer::Hidden));
}
ElementItem* ElementView::itemFromIndex(const QModelIndex& index)
@@ -756,154 +851,224 @@ ElementItem* ElementView::itemFromIndex(const QModelIndex& index)
return static_cast<ElementItem*>(QListWidget::itemFromIndex(index));
}
// clang-format on
/* ElementItem delegate ---------------------------------------------------- */
ElementItemDelegate::ElementItemDelegate(ElementView* parent)
: QStyledItemDelegate(parent)
{// This class relies on the parent being an ElementView, see getElementtItem
{ // This class relies on the parent being an ElementView, see getElementtItem
}
ElementItemDelegate::~ElementItemDelegate()
{}
void ElementItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
void ElementItemDelegate::paint(QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
ElementItem* item = getElementtItem(index);
ElementItem* item = getElementItem(index);
if (item) {
if (!item) {
return;
}
QStyleOptionButton checkboxstyle;
checkboxstyle.rect = option.rect;
auto style = option.widget ? option.widget->style() : QApplication::style();
checkboxstyle.state |= QStyle::State_Enabled;
QStyleOptionViewItem itemOption = option;
if (item->isVisible())
checkboxstyle.state |= QStyle::State_On;
else
checkboxstyle.state |= QStyle::State_Off;
initStyleOption(&itemOption, index);
QRect checkboxrect =
QApplication::style()->subElementRect(QStyle::SE_CheckBoxIndicator, &checkboxstyle);
if (item->isLineSelected || item->isStartingPointSelected || item->isEndPointSelected
|| item->isMidPointSelected) {
itemOption.state |= QStyle::State_Active;
}
customIconsMargin = leftMargin + checkboxrect.width();
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &itemOption, painter, option.widget);
QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkboxstyle, painter);
drawSubControl(SubControl::CheckBox, painter, option, index);
drawSubControl(SubControl::LineSelect, painter, option, index);
drawSubControl(SubControl::StartSelect, painter, option, index);
drawSubControl(SubControl::EndSelect, painter, option, index);
drawSubControl(SubControl::MidSelect, painter, option, index);
drawSubControl(SubControl::Label, painter, option, index);
}
int height = option.rect.height();
int width = height;// icons are square.
int x0 = option.rect.x() + customIconsMargin;
int iconsize = height - 2 * border;
int btny = option.rect.y() + border;
QRect ElementItemDelegate::subControlRect(SubControl element,
const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
auto itemOption = option;
if (item->isLineSelected || item->isStartingPointSelected || item->isEndPointSelected
|| item->isMidPointSelected) {// option.state & QStyle::State_Selected
auto style = option.widget ? option.widget->style() : QApplication::style();
auto unselecticon = [&](int iconnumber) {
QRect rect {x0 + border + width * iconnumber, btny, iconsize, iconsize};
painter->fillRect(rect, option.palette.base());
};
initStyleOption(&itemOption, index);
QRect selection = QRect(customIconsMargin,
option.rect.y(),
option.rect.width() - customIconsMargin,
option.rect.height());
QRect checkBoxRect =
style->subElementRect(QStyle::SE_CheckBoxIndicator, &itemOption, option.widget);
painter->fillRect(selection, option.palette.highlight());// paint the item as selected
checkBoxRect.moveTo(gap,
option.rect.top() + (option.rect.height() - checkBoxRect.height()) / 2);
// Repaint individual icons
if (!item->isLineSelected)
unselecticon(0);
if (element == SubControl::CheckBox) {
return checkBoxRect;
}
if (!item->isStartingPointSelected)
unselecticon(1);
QRect selectRect =
style->subElementRect(QStyle::SE_ItemViewItemDecoration, &itemOption, option.widget)
.translated(checkBoxRect.right() + gap, 0);
if (!item->isEndPointSelected)
unselecticon(2);
unsigned pos = element - SubControl::LineSelect;
if (!item->isMidPointSelected)
unselecticon(3);
auto rect = selectRect.translated((selectRect.width() + gap) * pos, 0);
if (element != SubControl::Label) {
return rect;
}
rect.setRight(itemOption.rect.right());
return rect;
}
void ElementItemDelegate::drawSubControl(SubControl element,
QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
auto item = getElementItem(index);
auto style = option.widget ? option.widget->style() : QApplication::style();
auto rect = subControlRect(element, option, index);
auto mousePos = option.widget->mapFromGlobal(QCursor::pos());
auto isHovered = rect.contains(mousePos);
auto drawSelectIcon = [&](Sketcher::PointPos pos) {
auto icon = ElementWidgetIcons::getIcon(item->GeometryType, pos, item->State);
auto opacity = 0.4f;
if (isHovered) {
opacity = 0.8f;
}
auto& iconEdge =
ElementWidgetIcons::getIcon(item->GeometryType, Sketcher::PointPos::none, item->State);
auto& iconStart =
ElementWidgetIcons::getIcon(item->GeometryType, Sketcher::PointPos::start, item->State);
auto& iconEnd =
ElementWidgetIcons::getIcon(item->GeometryType, Sketcher::PointPos::end, item->State);
auto& iconMid =
ElementWidgetIcons::getIcon(item->GeometryType, Sketcher::PointPos::mid, item->State);
// getIcon(item->GeometryType);
if (item->isGeometrySelected(pos)) {
opacity = 1.0f;
}
painter->drawPixmap(x0 + border, btny, iconEdge.pixmap(iconsize, iconsize));
painter->drawPixmap(x0 + border + width, btny, iconStart.pixmap(iconsize, iconsize));
painter->drawPixmap(x0 + border + width * 2, btny, iconEnd.pixmap(iconsize, iconsize));
painter->drawPixmap(x0 + border + width * 3, btny, iconMid.pixmap(iconsize, iconsize));
painter->setOpacity(opacity);
painter->drawPixmap(rect, icon.pixmap(rect.size()));
};
// Label :
painter->drawText(
x0 + width * 4 + 3 * border, option.rect.y() + height - textBottomMargin, item->label);
painter->save();
switch (element) {
case SubControl::CheckBox: {
QStyleOptionButton checkboxOption;
checkboxOption.initFrom(option.widget);
checkboxOption.rect = rect;
checkboxOption.state.setFlag(QStyle::State_Enabled, item->canBeHidden());
if (isHovered) {
checkboxOption.state |= QStyle::State_MouseOver;
}
if (item->isVisible()) {
checkboxOption.state |= QStyle::State_On;
}
else {
checkboxOption.state |= QStyle::State_Off;
}
style->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck,
&checkboxOption,
painter,
option.widget);
break;
}
case LineSelect: {
drawSelectIcon(Sketcher::PointPos::none);
break;
}
case StartSelect: {
drawSelectIcon(Sketcher::PointPos::start);
break;
}
case EndSelect: {
drawSelectIcon(Sketcher::PointPos::end);
break;
}
case MidSelect: {
drawSelectIcon(Sketcher::PointPos::mid);
break;
}
case Label: {
QRect rect = subControlRect(SubControl::Label, option, index);
auto labelBoundingBox = painter->fontMetrics().tightBoundingRect(item->label);
painter->drawText(rect.x(),
option.rect.bottom()
- (option.rect.height() - labelBoundingBox.height()) / 2,
item->label);
break;
}
}
painter->restore();
}
// clang-format off
bool ElementItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model,
const QStyleOptionViewItem& option, const QModelIndex& index)
{
auto getSubElementType = [&](ElementItem* item, int xPos, int width) {
bool label = (xPos > option.rect.x() + customIconsMargin + width * 4 + border);
auto item = getElementItem(index);
if ((xPos < option.rect.x() + customIconsMargin + width + border)
|| (item->GeometryType != Part::GeomPoint::getClassTypeId() && label))
auto getSubElementType = [&](QPoint pos) {
if (subControlRect(SubControl::LineSelect, option, index).contains(pos)) {
return SubElementType::edge;
if (xPos < option.rect.x() + customIconsMargin + width * 2 + border
|| (item->GeometryType == Part::GeomPoint::getClassTypeId() && label))
} else if (subControlRect(SubControl::StartSelect, option, index).contains(pos)) {
return SubElementType::start;
if (xPos < option.rect.x() + customIconsMargin + width * 3 + border)
} else if (subControlRect(SubControl::EndSelect, option, index).contains(pos)) {
return SubElementType::end;
else if (xPos < option.rect.x() + customIconsMargin + width * 4 + border)
} else if (subControlRect(SubControl::MidSelect, option, index).contains(pos)) {
return SubElementType::mid;
else
return SubElementType::none;
} else {
// depending on geometry type by default we select either point or edge
return item->GeometryType == Part::GeomPoint::getClassTypeId() ? SubElementType::start : SubElementType::edge;
}
};
if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick) {
QMouseEvent* mEvent = static_cast<QMouseEvent*>(event);
ElementItem* item = getElementtItem(index);
auto mouseEvent = static_cast<QMouseEvent*>(event);
int xPos = mEvent->pos().x();
int width = option.rect.height();// icons are square
item->clickedOn = getSubElementType(mouseEvent->pos());
item->rightClicked = mouseEvent->button() == Qt::RightButton;
item->clickedOn = getSubElementType(item, xPos, width);
if (item->canBeHidden()) {
QRect checkboxRect = subControlRect(SubControl::CheckBox, option, index);
if (mEvent->button() == Qt::RightButton)
item->rightClicked = true;
QRect checkboxrect = QRect(
leftMargin, option.rect.y(), customIconsMargin - leftMargin, option.rect.height());
if (mEvent->button() == Qt::LeftButton && checkboxrect.contains(mEvent->pos())) {
Q_EMIT itemChecked(index, item->isVisible() ? Qt::Unchecked : Qt::Checked);
if (mouseEvent->button() == Qt::LeftButton && checkboxRect.contains(mouseEvent->pos())) {
Q_EMIT itemChecked(index, item->isVisible() ? Qt::Unchecked : Qt::Checked);
}
}
}
else if (event->type() == QEvent::MouseMove) {
SubElementType typeUnderMouse;
QMouseEvent* mEvent = static_cast<QMouseEvent*>(event);
int xPos = mEvent->pos().x();
int width = option.rect.height();// icons are square
auto mouseEvent = static_cast<QMouseEvent*>(event);
ElementItem* item = getElementtItem(index);
item->hovered = getSubElementType(mouseEvent->pos());
typeUnderMouse = getSubElementType(item, xPos, width);
item->hovered = typeUnderMouse;
Q_EMIT itemHovered(index);
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
ElementItem* ElementItemDelegate::getElementtItem(const QModelIndex& index) const
ElementItem* ElementItemDelegate::getElementItem(const QModelIndex& index) const
{
ElementView* elementView = static_cast<ElementView*>(parent());
return elementView->itemFromIndex(index);

View File

@@ -109,6 +109,7 @@ Q_SIGNALS:
private:
void changeLayer(int layer);
void changeLayer(ElementItem* item, int layer);
};
class ElementFilterList;