Clean up code

Create separate re-usable component for `MaterialBrandSubMenu`

CURA-9522
This commit is contained in:
c.lamboo 2022-12-23 19:40:27 +01:00
parent 20fdf22e35
commit e490250d9d
3 changed files with 291 additions and 258 deletions

View File

@ -17,7 +17,11 @@ UM.MainWindow
{ {
id: base id: base
readonly property var mainWindow: base Item
{
id: mainWindow
anchors.fill: parent
}
// Cura application window title // Cura application window title
title: title:

View File

@ -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. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.7
@ -85,35 +85,15 @@ Cura.MenuItem
onTriggered: menuPopup.close() onTriggered: menuPopup.close()
} }
Popup MaterialBrandSubMenu
{ {
id: menuPopup 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. // 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) // 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) // We have to keep a count of itemHovered (instead of just a bool)
property int itemHovered: 0 property int itemHovered: 0
MouseArea MouseArea
{ {
id: submenuArea id: submenuArea
@ -123,263 +103,195 @@ Cura.MenuItem
onEntered: hideTimer.restartTimer() onEntered: hideTimer.restartTimer()
} }
background: Rectangle Column
{ {
color: UM.Theme.getColor("main_background") id: materialTypesList
border.color: UM.Theme.getColor("lining") width: UM.Theme.getSize("menu").width
border.width: UM.Theme.getSize("default_lining").width height: childrenRect.height
} spacing: 0
ScrollView property var brandMaterials: materialTypesModel.material_types
{
id: scrollViewMaterialType
width: UM.Theme.getSize("menu").width + scrollbar.width
height: parent.height
clip: true
ScrollBar.vertical: UM.ScrollBar Repeater
{ {
id: scrollbar model: parent.brandMaterials
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
}
Column //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.
id: materialTypesList delegate: Rectangle
width: UM.Theme.getSize("menu").width
height: parent.height
spacing: 0
property var brandMaterials: materialTypesModel.material_types
Repeater
{ {
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. color: materialTypeButton.containsMouse ? UM.Theme.getColor("background_2") : "transparent"
//With a custom MouseArea, we can prevent the events from being accepted.
delegate: Rectangle RowLayout
{ {
id: brandMaterialBase spacing: 0
height: UM.Theme.getSize("menu").height opacity: materialBrandMenu.enabled ? 1 : 0.5
width: UM.Theme.getSize("menu").width height: parent.height
width: parent.width
color: materialTypeButton.containsMouse ? UM.Theme.getColor("background_2") : "transparent" Item
property bool isFlipped: menuPopup.flipped
RowLayout
{ {
// 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 spacing: 0
opacity: materialBrandMenu.enabled ? 1 : 0.5
height: parent.height
width: parent.width
Item Repeater
{ {
// Spacer model: parent.brandColors
width: UM.Theme.getSize("default_margin").width
}
UM.Label delegate: Rectangle
{
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
{ {
id: scrollbar height: UM.Theme.getSize("menu").height
anchors.right: parent.right width: parent.width
anchors.top: parent.top
anchors.bottom: parent.bottom
}
Column color: materialColorButton.containsMouse ? UM.Theme.getColor("background_2") : UM.Theme.getColor("main_background")
{
id: materialColorsList
property var brandColors: model.colors
width: UM.Theme.getSize("menu").width
height: parent.height
spacing: 0
Repeater MouseArea
{ {
model: parent.brandColors id: materialColorButton
anchors.fill: parent
delegate: Rectangle hoverEnabled: true
onClicked:
{ {
height: UM.Theme.getSize("menu").height Cura.MachineManager.setMaterial(extruderIndex, model.container_node);
width: parent.width 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 //Checkmark, if the material is selected.
{ UM.ColorImage
height: parent.height {
width: parent.width id: checkmark
opacity: materialBrandMenu.enabled ? 1 : 0.5 visible: model.id === materialMenu.activeMaterialId
anchors.fill: parent 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.Label
UM.ColorImage {
{ text: model.name
id: checkmark anchors.left: parent.left
visible: model.id === materialMenu.activeMaterialId anchors.leftMargin: UM.Theme.getSize("default_margin").width + UM.Theme.getSize("default_arrow").height
height: UM.Theme.getSize("default_arrow").height anchors.verticalCenter: parent.verticalCenter
width: height anchors.right: parent.right
anchors.left: parent.left anchors.rightMargin: UM.Theme.getSize("default_margin").width
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 elide: Label.ElideRight
{ wrapMode: Text.NoWrap
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;
}
}
} }
} }
} }

View File

@ -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
}
}
}