import QtQuick import QtQuick.Window import Weave.Controls import Weave.Templates as T import "internal" T.Flyout { id: flyout implicitWidth: Math.max(implicitContentWidth + leftPadding + rightPadding, implicitBackgroundWidth + leftInset + rightInset) implicitHeight: Math.max(implicitContentHeight + topPadding + bottomPadding, implicitBackgroundHeight + topInset + bottomInset) x: { if (!parent) { return 0 } else if (nubAlignment & T.Flyout.EdgeLeft) { return parent.width } else if (nubAlignment & T.Flyout.EdgeRight) { return -width } else if (nubAlignment & T.Flyout.AlignLeft) { return 0 } else if (nubAlignment & T.Flyout.AlignRight) { return parent.width - width } else { return (parent.width/2) - (width/2) } } y: { if (!parent) { return 0 } else if (nubAlignment & T.Flyout.EdgeTop) { return parent.height } else if (nubAlignment & T.Flyout.EdgeBottom) { return -height } else if (nubAlignment & T.Flyout.AlignTop) { return 0 } else if (nubAlignment & T.Flyout.AlignBottom) { return parent.height - height } else { return (parent.height/2) - (height/2) } } // TODO incorrect theme value Theme.component.flyout.positionTop.arrowContainer.paddingBottom // Work around QTBUG-119261 by "manually emitting". topInset: { Qt.callLater(function() { flyout.topInsetChanged() }); return (nubAlignment & T.Flyout.EdgeTop) ? Theme.component.flyout.arrow.height + Theme.component.flyout.positionBottom.arrowContainer.paddingTop : 0 } bottomInset: { Qt.callLater(function() { flyout.bottomInsetChanged() }); return (nubAlignment & T.Flyout.EdgeBottom) ? Theme.component.flyout.arrow.height + Theme.component.flyout.positionBottom.arrowContainer.paddingTop : 0 } leftInset: { Qt.callLater(function() { flyout.leftInsetChanged() }); return (nubAlignment & T.Flyout.EdgeLeft) ? Theme.component.flyout.arrow.height + Theme.component.flyout.positionLeft.arrowContainer.paddingRight : 0 } rightInset: { Qt.callLater(function() { flyout.rightInsetChanged() }); return (nubAlignment & T.Flyout.EdgeRight) ? Theme.component.flyout.arrow.height + Theme.component.flyout.positionRight.arrowContainer.paddingLeft : 0 } topPadding: topInset bottomPadding: bottomInset leftPadding: leftInset rightPadding: rightInset nubAlignment: { let preferred = preferredAlignment const windowItem = parent ? parent.Window.contentItem : null if (windowItem) { // Global position changes don't notify, so we capture the visible property to force // reevaluation of this binding when the popup is shown. visible; // Note: The preferredAlignment is the position of the flyout relative to the parent // item, and the nubAlignment is the position of the nub on the flyout. const flyoutWidth = width const flyoutHeight = height const topLeft = parent.mapToItem(windowItem, 0, 0) const bottomRight = parent.mapToItem(windowItem, parent.width, parent.height) const left = topLeft.x const top = topLeft.y const right = windowItem.width - bottomRight.x const bottom = windowItem.height - bottomRight.y // If the flyout won't fit above or below the parent item. Put it beside it. // Then lower probability; if it won't fit beside move it top or bottom. // Finally in the absence of any hint narrow the choice to an axis. if ((preferred === T.Flyout.NoPreference || preferred === T.Flyout.PreferVertical || preferred === T.Flyout.PreferTop || preferred === T.Flyout.PreferTopLeft || preferred === T.Flyout.PreferTopRight || preferred === T.Flyout.PreferBottom || preferred === T.Flyout.PreferBottomLeft || preferred === T.Flyout.PreferBottomRight) && top < flyoutHeight && bottom < flyoutHeight) { if (preferred === T.Flyout.PreferTopLeft || preferred === T.Flyout.PreferBottomLeft) { preferred = T.Flyout.PreferLeft } else if (preferred === T.Flyout.PreferTopRight || preferred === T.Flyout.PreferBottomRight) { preferred = T.Flyout.PreferRight } else { preferred = T.Flyout.PreferHorizontal } } else if ((preferred === T.Flyout.NoPreference || preferred === T.Flyout.PreferHorizontal || preferred === T.Flyout.PreferLeft || preferred === T.Flyout.PreferLeftTop || preferred === T.Flyout.PreferLeftBottom || preferred === T.Flyout.PreferRight || preferred === T.Flyout.PreferRightTop || preferred === T.Flyout.PreferRightBottom) && left < flyoutWidth && right < flyoutWidth) { if (preferred === T.Flyout.PreferLeftTop || preferred === T.Flyout.PreferRightTop) { preferred = T.Flyout.PreferTop } else if (preferred === T.Flyout.PreferLeftBottom || preferred === T.Flyout.PreferRightBottom) { preferred = T.Flyout.PreferBottom } else { preferred = T.Flyout.PreferVertical } } else if (preferred === T.Flyout.NoPreference) { preferred = Math.min(left, right) < Math.min(top, bottom) ? T.Flyout.PreferHorizontal : T.Flyout.PreferVertical } // If the hint is limited to an axis pick the side with more space. if (preferred === T.Flyout.PreferHorizontal) { preferred = left < right ? T.Flyout.PreferRight : T.Flyout.PreferLeft if (top < flyoutHeight / 2) { preferred = T.Flyout.PreferLeft ? T.Flyout.PreferLeftBottom : T.Flyout.PreferRightBottom } else if (bottom < flyoutHeight / 2) { preferred = T.Flyout.PreferLeft ? T.Flyout.PreferLeftTop : T.Flyout.PreferRightTop } } else if (preferred === T.Flyout.PreferVertical) { preferred = top < bottom ? T.Flyout.PreferBottom : T.Flyout.PreferTop if (left < flyoutWidth / 2) { preferred = T.Flyout.PreferTop ? T.Flyout.PreferTopRight : T.Flyout.PreferBottomRight } else if (right < flyoutWidth / 2) { preferred = T.Flyout.PreferTop ? T.Flyout.PreferTopLeft : T.Flyout.PreferBottomLeft } } // If the flyout won't fit on the hinted side, // but will fit on the opposite side, pick that instead. if (preferred === T.Flyout.PreferRight && right < flyoutWidth && left >= flyoutWidth) { preferred = T.Flyout.PreferLeft } else if (preferred === T.Flyout.PreferRightTop && right < flyoutWidth && left >= flyoutWidth) { preferred = T.Flyout.PreferLeftTop } else if (preferred === T.Flyout.PreferRightBottom && right < flyoutWidth && left >= flyoutWidth) { preferred = T.Flyout.PreferLeftBottom } else if (preferred === T.Flyout.PreferLeft && left < flyoutWidth && right >= flyoutWidth) { preferred = T.Flyout.PreferRight } else if (preferred === T.Flyout.PreferLeftTop && left < flyoutWidth && right >= flyoutWidth) { preferred = T.Flyout.PreferRightTop } else if (preferred === T.Flyout.PreferLeftBottom && left < flyoutWidth && right >= flyoutWidth) { preferred = T.Flyout.PreferRightBottom } else if (preferred === T.Flyout.PreferTop && top < flyoutHeight && bottom >= flyoutHeight) { preferred = T.Flyout.PreferBottom } else if (preferred === T.Flyout.PreferTopLeft && top < flyoutHeight && bottom >= flyoutHeight) { preferred = T.Flyout.PreferBottomLeft } else if (preferred === T.Flyout.PreferTopRight && top < flyoutHeight && bottom >= flyoutHeight) { preferred = T.Flyout.PreferBottomRight } else if (preferred === T.Flyout.PreferBottom && bottom < flyoutHeight && top >= flyoutHeight) { preferred = T.Flyout.PreferTop } else if (preferred === T.Flyout.PreferBottomLeft && bottom < flyoutHeight && top >= flyoutHeight) { preferred = T.Flyout.PreferTopLeft } else if (preferred === T.Flyout.PreferBottomRight && bottom < flyoutHeight && top >= flyoutHeight) { preferred = T.Flyout.PreferTopRight } } // given the calculated preferred flyout alignment, // determine the appropriate nub position. switch (preferred) { case T.Flyout.PreferHorizontal: // fall through case T.Flyout.PreferLeft: return T.Flyout.RightCenter case T.Flyout.PreferRight: return T.Flyout.LeftCenter case T.Flyout.PreferTop: return T.Flyout.BottomCenter case T.Flyout.PreferLeftTop: return T.Flyout.RightTop case T.Flyout.PreferLeftBottom: return T.Flyout.RightBottom case T.Flyout.PreferRightTop: return T.Flyout.LeftTop case T.Flyout.PreferRightBottom: return T.Flyout.LeftBottom case T.Flyout.PreferTopLeft: return T.Flyout.BottomLeft case T.Flyout.PreferTopRight: return T.Flyout.BottomRight case T.Flyout.PreferBottomLeft: return T.Flyout.TopLeft case T.Flyout.PreferBottomRight: return T.Flyout.TopRight case T.Flyout.NoPreference: // fall through case T.Flyout.PreferVertical: // fall through case T.Flyout.PreferBottom: // fall through default: return T.Flyout.TopCenter } } nubPosition.x: { if (nubAlignment & T.Flyout.EdgeLeft) { // not "leftInset-arrow.height" because the nub is a child of the background, not the flyout. return -Theme.component.flyout.arrow.height // arrow.height token defines the short axis. } else if (nubAlignment & T.Flyout.EdgeRight) { return background.width } else if (nubAlignment & T.Flyout.AlignLeft) { return Theme.component.flyout.arrow.width } else if (nubAlignment & T.Flyout.AlignRight) { return background.width - Theme.component.flyout.arrow.width*2 } else { return (background.width/2) - (Theme.component.flyout.arrow.width/2) } } nubPosition.y: { if (nubAlignment & T.Flyout.EdgeTop) { // not "topInset-arrow.height" because the nub is a child of the background, not the flyout. return -Theme.component.flyout.arrow.height // arrow.height token defines the short axis } else if (nubAlignment & T.Flyout.EdgeBottom) { return background.height } else if (nubAlignment & T.Flyout.AlignTop) { return Theme.component.flyout.arrow.width } else if (nubAlignment & T.Flyout.AlignBottom) { return background.height - Theme.component.flyout.arrow.width*2 } else { return (background.height/2) - (Theme.component.flyout.arrow.width/2) } } background: FlyoutBackground { flyoutItem: flyout nubAlignment: flyout.nubAlignment } }