From e490250d9d2c809c83f07551a044172e1dee02c4 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Fri, 23 Dec 2022 19:40:27 +0100 Subject: [PATCH] Clean up code Create separate re-usable component for `MaterialBrandSubMenu` CURA-9522 --- resources/qml/Cura.qml | 6 +- resources/qml/Menus/MaterialBrandMenu.qml | 426 ++++++++----------- resources/qml/Menus/MaterialBrandSubMenu.qml | 117 +++++ 3 files changed, 291 insertions(+), 258 deletions(-) create mode 100644 resources/qml/Menus/MaterialBrandSubMenu.qml diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index cb570fce46..669fd1041e 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -17,7 +17,11 @@ UM.MainWindow { id: base - readonly property var mainWindow: base + Item + { + id: mainWindow + anchors.fill: parent + } // Cura application window title title: diff --git a/resources/qml/Menus/MaterialBrandMenu.qml b/resources/qml/Menus/MaterialBrandMenu.qml index b0d09eacae..e514de04a4 100644 --- a/resources/qml/Menus/MaterialBrandMenu.qml +++ b/resources/qml/Menus/MaterialBrandMenu.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Ultimaker B.V. +// Copyright (c) 2022 UltiMaker // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.7 @@ -85,35 +85,15 @@ Cura.MenuItem onTriggered: menuPopup.close() } - Popup + MaterialBrandSubMenu { id: menuPopup - width: materialTypesList.width + padding * 2 - height: materialTypesList.height + padding * 2 - property var flipped: false - - onOpened: - { - var popupHeight = materialTypesModel.material_types.count * UM.Theme.getSize("menu").height - var parentGlobalY = parent.mapToItem(null, 0, 0).y - var overflowY = (parentGlobalY + popupHeight) - mainWindow.height - menuPopup.y = overflowY > 0 ? -overflowY : 0 - - var defaultX = parent.width - UM.Theme.getSize("default_lining").width - var parentGlobalX = parent.mapToItem(null, 0, 0).x - var overflowX = (parentGlobalX + defaultX + menuPopup.width) - mainWindow.width - menuPopup.x = overflowX > 0 ? overflowX : defaultX - - scrollViewMaterialType.height = popupHeight > mainWindow.height ? mainWindow.height : popupHeight - menuPopup.height = popupHeight > mainWindow.height ? mainWindow.heigh : popupHeight - } - - padding: background.border.width // Nasty hack to ensure that we can keep track if the popup contains the mouse. // Since we also want a hover for the sub items (and these events are sent async) // We have to keep a count of itemHovered (instead of just a bool) property int itemHovered: 0 + MouseArea { id: submenuArea @@ -123,263 +103,195 @@ Cura.MenuItem onEntered: hideTimer.restartTimer() } - background: Rectangle + Column { - color: UM.Theme.getColor("main_background") - border.color: UM.Theme.getColor("lining") - border.width: UM.Theme.getSize("default_lining").width - } + id: materialTypesList + width: UM.Theme.getSize("menu").width + height: childrenRect.height + spacing: 0 - ScrollView - { - id: scrollViewMaterialType - width: UM.Theme.getSize("menu").width + scrollbar.width - height: parent.height - clip: true + property var brandMaterials: materialTypesModel.material_types - ScrollBar.vertical: UM.ScrollBar + Repeater { - id: scrollbar - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - } + model: parent.brandMaterials - Column - { - id: materialTypesList - width: UM.Theme.getSize("menu").width - height: parent.height - spacing: 0 - - property var brandMaterials: materialTypesModel.material_types - - Repeater + //Use a MouseArea and Rectangle, not a button, because the button grabs mouse events which makes the parent pop-up think it's no longer being hovered. + //With a custom MouseArea, we can prevent the events from being accepted. + delegate: Rectangle { - model: parent.brandMaterials + id: brandMaterialBase + height: UM.Theme.getSize("menu").height + width: UM.Theme.getSize("menu").width - //Use a MouseArea and Rectangle, not a button, because the button grabs mouse events which makes the parent pop-up think it's no longer being hovered. - //With a custom MouseArea, we can prevent the events from being accepted. - delegate: Rectangle + color: materialTypeButton.containsMouse ? UM.Theme.getColor("background_2") : "transparent" + + RowLayout { - id: brandMaterialBase - height: UM.Theme.getSize("menu").height - width: UM.Theme.getSize("menu").width + spacing: 0 + opacity: materialBrandMenu.enabled ? 1 : 0.5 + height: parent.height + width: parent.width - color: materialTypeButton.containsMouse ? UM.Theme.getColor("background_2") : "transparent" - - property bool isFlipped: menuPopup.flipped - - RowLayout + Item { + // Spacer + width: UM.Theme.getSize("default_margin").width + } + + UM.Label + { + text: model.name + Layout.fillWidth: true + Layout.fillHeight: true + elide: Label.ElideRight + wrapMode: Text.NoWrap + } + + Item + { + Layout.fillWidth: true + } + + UM.ColorImage + { + height: UM.Theme.getSize("default_arrow").height + width: UM.Theme.getSize("default_arrow").width + color: UM.Theme.getColor("setting_control_text") + source: UM.Theme.getIcon("ChevronSingleRight") + } + + Item + { + // Right side margin + width: UM.Theme.getSize("default_margin").width + } + } + + MouseArea + { + id: materialTypeButton + anchors.fill: parent + + hoverEnabled: true + acceptedButtons: Qt.NoButton + + onEntered: + { + menuPopup.itemHovered += 1; + showSubTimer.restartTimer(); + } + onExited: + { + menuPopup.itemHovered -= 1; + hideSubTimer.restartTimer(); + } + } + Timer + { + id: showSubTimer + interval: 250 + function restartTimer() + { + restart(); + running = Qt.binding(function() { return materialTypeButton.containsMouse; }); + hideSubTimer.running = false; + } + onTriggered: colorPopup.open() + } + Timer + { + id: hideSubTimer + interval: 250 + function restartTimer() //Restart but re-evaluate the running property then. + { + restart(); + running = Qt.binding(function() { return !materialTypeButton.containsMouse && !colorPopup.itemHovered > 0; }); + showSubTimer.running = false; + } + onTriggered: colorPopup.close() + } + + MaterialBrandSubMenu + { + id: colorPopup + property int itemHovered: 0 + + Column + { + id: materialColorsList + property var brandColors: model.colors + width: UM.Theme.getSize("menu").width + height: childrenRect.height spacing: 0 - opacity: materialBrandMenu.enabled ? 1 : 0.5 - height: parent.height - width: parent.width - Item + Repeater { - // Spacer - width: UM.Theme.getSize("default_margin").width - } + model: parent.brandColors - UM.Label - { - text: model.name - Layout.fillWidth: true - Layout.fillHeight: true - elide: Label.ElideRight - wrapMode: Text.NoWrap - } - - Item - { - Layout.fillWidth: true - } - - UM.ColorImage - { - height: UM.Theme.getSize("default_arrow").height - width: UM.Theme.getSize("default_arrow").width - color: UM.Theme.getColor("setting_control_text") - source: UM.Theme.getIcon("ChevronSingleRight") - } - - Item - { - // Right side margin - width: UM.Theme.getSize("default_margin").width - } - } - - MouseArea - { - id: materialTypeButton - anchors.fill: parent - - hoverEnabled: true - acceptedButtons: Qt.NoButton - - onEntered: - { - menuPopup.itemHovered += 1; - showSubTimer.restartTimer(); - } - onExited: - { - menuPopup.itemHovered -= 1; - hideSubTimer.restartTimer(); - } - } - Timer - { - id: showSubTimer - interval: 250 - function restartTimer() - { - restart(); - running = Qt.binding(function() { return materialTypeButton.containsMouse; }); - hideSubTimer.running = false; - } - onTriggered: colorPopup.open() - } - Timer - { - id: hideSubTimer - interval: 250 - function restartTimer() //Restart but re-evaluate the running property then. - { - restart(); - running = Qt.binding(function() { return !materialTypeButton.containsMouse && !colorPopup.itemHovered > 0; }); - showSubTimer.running = false; - } - onTriggered: colorPopup.close() - } - - Popup - { - id: colorPopup - width: materialColorsList.width + padding * 2 - height: materialColorsList.height + padding * 2 - onOpened: - { - // This will be resolved before opening the popup if directly assigned to the properties - // This forces these values to update whenever a popup is opened - var popupHeight = model.colors.count * UM.Theme.getSize("menu").height - var parentGlobalY = parent.mapToItem(null, 0, 0).y - var overflowY = (parentGlobalY + popupHeight) - mainWindow.height - colorPopup.y = overflowY > 0 ? - overflowY - UM.Theme.getSize("default_lining").height : -UM.Theme.getSize("default_lining").height - - var parentGlobalX = materialTypesList.mapToItem(null, 0, 0).x - var overflowX = (parentGlobalX + parent.width + colorPopup.width) - mainWindow.width - colorPopup.x = overflowX > 0 ? parent.width - overflowX : parent.width - - scrollView.height = popupHeight > mainWindow.height ? mainWindow.height : popupHeight - colorPopup.height = popupHeight > mainWindow.height ? mainWindow.height : popupHeight - } - - property int itemHovered: 0 - padding: background.border.width - - background: Rectangle - { - color: UM.Theme.getColor("main_background") - border.color: UM.Theme.getColor("lining") - border.width: UM.Theme.getSize("default_lining").width - } - ScrollView - { - id: scrollView - width: UM.Theme.getSize("menu").width + scrollbar.width - height: parent.height - clip: true - - ScrollBar.vertical: UM.ScrollBar + delegate: Rectangle { - id: scrollbar - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - } + height: UM.Theme.getSize("menu").height + width: parent.width - Column - { - id: materialColorsList - property var brandColors: model.colors - width: UM.Theme.getSize("menu").width - height: parent.height - spacing: 0 + color: materialColorButton.containsMouse ? UM.Theme.getColor("background_2") : UM.Theme.getColor("main_background") - Repeater + MouseArea { - model: parent.brandColors - - delegate: Rectangle + id: materialColorButton + anchors.fill: parent + hoverEnabled: true + onClicked: { - height: UM.Theme.getSize("menu").height - width: parent.width + Cura.MachineManager.setMaterial(extruderIndex, model.container_node); + menuPopup.close(); + colorPopup.close(); + materialMenu.close(); + } + onEntered: + { + menuPopup.itemHovered += 1; + colorPopup.itemHovered += 1; + } + onExited: + { + menuPopup.itemHovered -= 1; + colorPopup.itemHovered -= 1; + } + } - color: materialColorButton.containsMouse ? UM.Theme.getColor("background_2") : "transparent" + Item + { + height: parent.height + width: parent.width + opacity: materialBrandMenu.enabled ? 1 : 0.5 + anchors.fill: parent - Item - { - height: parent.height - width: parent.width - opacity: materialBrandMenu.enabled ? 1 : 0.5 - anchors.fill: parent + //Checkmark, if the material is selected. + UM.ColorImage + { + id: checkmark + visible: model.id === materialMenu.activeMaterialId + height: UM.Theme.getSize("default_arrow").height + width: height + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("default_margin").width + anchors.verticalCenter: parent.verticalCenter + source: UM.Theme.getIcon("Check", "low") + color: UM.Theme.getColor("setting_control_text") + } - //Checkmark, if the material is selected. - UM.ColorImage - { - id: checkmark - visible: model.id === materialMenu.activeMaterialId - height: UM.Theme.getSize("default_arrow").height - width: height - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("default_margin").width - anchors.verticalCenter: parent.verticalCenter - source: UM.Theme.getIcon("Check", "low") - color: UM.Theme.getColor("setting_control_text") - } + UM.Label + { + text: model.name + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("default_margin").width + UM.Theme.getSize("default_arrow").height + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: UM.Theme.getSize("default_margin").width - UM.Label - { - text: model.name - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("default_margin").width + UM.Theme.getSize("default_arrow").height - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - anchors.rightMargin: UM.Theme.getSize("default_margin").width - - elide: Label.ElideRight - wrapMode: Text.NoWrap - } - } - - MouseArea - { - id: materialColorButton - anchors.fill: parent - - hoverEnabled: true - onClicked: - { - Cura.MachineManager.setMaterial(extruderIndex, model.container_node); - menuPopup.close(); - colorPopup.close(); - materialMenu.close(); - } - onEntered: - { - menuPopup.itemHovered += 1; - colorPopup.itemHovered += 1; - } - onExited: - { - menuPopup.itemHovered -= 1; - colorPopup.itemHovered -= 1; - } - } + elide: Label.ElideRight + wrapMode: Text.NoWrap } } } diff --git a/resources/qml/Menus/MaterialBrandSubMenu.qml b/resources/qml/Menus/MaterialBrandSubMenu.qml new file mode 100644 index 0000000000..787a945766 --- /dev/null +++ b/resources/qml/Menus/MaterialBrandSubMenu.qml @@ -0,0 +1,117 @@ +// Copyright (c) 2022 UltiMaker +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.7 +import QtQuick.Controls 2.4 +import QtQuick.Layouts 2.7 + +import UM 1.5 as UM +import Cura 1.7 as Cura + +Popup +{ + id: materialBrandSubMenu + + bottomPadding: UM.Theme.getSize("thin_margin").height + topPadding: UM.Theme.getSize("thin_margin").height + + implicitWidth: scrollViewContent.width + scrollbar.width + leftPadding + rightPadding + implicitHeight: scrollViewContent.height + bottomPadding + topPadding + + // offset position relative to the parent + property int implicitX: parent.width + property int implicitY: -UM.Theme.getSize("thin_margin").height + + default property alias contents: scrollViewContent.children + + x: implicitX + y: implicitY + + // needed for the `mapToItem` function to work; apparently a Popup is not an Item + Item + { + id: materialBrandSubMenu + anchors.fill: parent + } + + onOpened: + { + // we want to make sure here that the popup never goes out side the window so we adjust the x and y position + // based on the width/height of the mainWindow/popup. QML is a bit weird here though, as the globalPosition + // is in absolute coordinates relative to the origin of the mainWindow while setting the x and y coordinates + // of the popup only changes the position relative to the parent. + + // reset position, the remainder of the function asumes this position and size + materialBrandSubMenu.x = implicitX; + materialBrandSubMenu.y = implicitY; + materialBrandSubMenu.width = implicitWidth; + materialBrandSubMenu.height = implicitHeight; + + const globalPosition = materialBrandSubMenu.mapToItem(null, 0, 0); + + if (globalPosition.y > mainWindow.height - materialBrandSubMenu.height) + { + if (mainWindow.height > materialBrandSubMenu.height) + { + const targetY = mainWindow.height - materialBrandSubMenu.height; + const deltaY = globalPosition.y - targetY; + materialBrandSubMenu.y = implicitY - deltaY; + } + else + { + // if popup is taller then the the component, limit + // the components height and set the position to + // y = 0 (in absolute coordinates) + materialBrandSubMenu.y = implicitY - globalPosition.y; + materialBrandSubMenu.height = mainWindow.height; + } + } + + if (globalPosition.x > mainWindow.width - materialBrandSubMenu.width) + { + if (mainWindow.width > materialBrandSubMenu.width) + { + const targetY = mainWindow.width - materialBrandSubMenu.width; + const deltaX = globalPosition.x - targetY; + materialBrandSubMenu.x = implicitX - deltaX; + } + else + { + materialBrandSubMenu.x = implicitX - globalPosition.x; + materialBrandSubMenu.width = mainWindow.width; + } + } + } + + padding: background.border.width + + background: Rectangle + { + color: UM.Theme.getColor("main_background") + border.color: UM.Theme.getColor("lining") + border.width: UM.Theme.getSize("default_lining").width + } + + ScrollView + { + id: scrollView + anchors.fill: parent + contentHeight: scrollViewContent.height + clip: true + + ScrollBar.vertical: UM.ScrollBar + { + id: scrollbar + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + } + + Rectangle + { + id: scrollViewContent + width: childrenRect.width + height: childrenRect.height + } + } +} \ No newline at end of file