From ad262909388b1598bf14d905902128e60f4bb874 Mon Sep 17 00:00:00 2001 From: Kirill Grouchnikov Date: Wed, 13 Sep 2023 14:31:36 -0400 Subject: [PATCH] Wire keytips to taskbar content Also support disabled look for keytips. For #56 --- .../org/pushingpixels/aurora/common/Utils.kt | 4 + .../aurora/component/AuroraCommandButton.kt | 43 +++++++--- .../component/ribbon/RibbonMetaComponent.kt | 3 + .../component/ribbon/impl/KeyTipTracker.kt | 54 ++++++++----- .../ribbon/impl/RibbonTaskbarImpl.kt | 81 +++++++++++++++---- .../aurora/window/AuroraRibbonWindow.kt | 3 +- .../window/ribbon/RibbonTaskToggleButton.kt | 3 + 7 files changed, 141 insertions(+), 50 deletions(-) diff --git a/common/src/commonMain/kotlin/org/pushingpixels/aurora/common/Utils.kt b/common/src/commonMain/kotlin/org/pushingpixels/aurora/common/Utils.kt index 57c0142a..71b56b26 100644 --- a/common/src/commonMain/kotlin/org/pushingpixels/aurora/common/Utils.kt +++ b/common/src/commonMain/kotlin/org/pushingpixels/aurora/common/Utils.kt @@ -31,6 +31,10 @@ fun AuroraRect.contains(x: Float, y: Float): Boolean { (y < (this.y + this.height)) } +@AuroraInternalApi +val AuroraRect.isEmpty: Boolean + get() = (this.width == 0.0f) && (this.height == 0.0f) + @AuroraInternalApi fun AuroraOffset.asOffset(density: Density): Offset { return Offset(x / density.density, y / density.density) diff --git a/component/src/desktopMain/kotlin/org/pushingpixels/aurora/component/AuroraCommandButton.kt b/component/src/desktopMain/kotlin/org/pushingpixels/aurora/component/AuroraCommandButton.kt index c0d50201..33a3e1db 100644 --- a/component/src/desktopMain/kotlin/org/pushingpixels/aurora/component/AuroraCommandButton.kt +++ b/component/src/desktopMain/kotlin/org/pushingpixels/aurora/component/AuroraCommandButton.kt @@ -75,7 +75,7 @@ private class CommandButtonDrawingCache( isDark = false ), val markPath: Path = Path() -): DrawingCache +) : DrawingCache private fun Modifier.commandButtonActionHoverable( interactionSource: MutableInteractionSource, @@ -882,6 +882,8 @@ internal fun , + val originalProjection: BaseCommandButtonProjection<*, *, *>, + val command: BaseCommand, + val presentationModel: BaseCommandButtonPresentationModel, val topLeftOffset: AuroraOffset, val size: MutableState, val trackBounds: Boolean, @@ -2082,21 +2089,23 @@ private class CommandButtonLocator( height = coordinates.size.height.toFloat() ) if (trackBounds) { - BoundsTracker.trackBounds(projection, bounds) + BoundsTracker.trackBounds(originalProjection, bounds) } if (trackKeyTips) { - if (projection.presentationModel.actionKeyTip != null) { + if (presentationModel.actionKeyTip != null) { KeyTipTracker.trackKeyTipBase( - projection, - projection.presentationModel.actionKeyTip!!, + originalProjection, + presentationModel.actionKeyTip!!, + command.isActionEnabled, bounds ) } - if (projection.presentationModel.popupKeyTip != null) { + if (presentationModel.popupKeyTip != null) { KeyTipTracker.trackKeyTipBase( - projection, - projection.presentationModel.popupKeyTip!!, + originalProjection, + presentationModel.popupKeyTip!!, + command.isSecondaryEnabled, bounds ) } @@ -2107,9 +2116,21 @@ private class CommandButtonLocator( @OptIn(AuroraInternalApi::class) @Composable private fun Modifier.commandButtonLocator( - projection: BaseCommandButtonProjection<*, *, *>, + originalProjection: BaseCommandButtonProjection<*, *, *>, + command: BaseCommand, + presentationModel: BaseCommandButtonPresentationModel, topLeftOffset: AuroraOffset, size: MutableState, trackBounds: Boolean, trackKeyTips: Boolean -) = this.then(CommandButtonLocator(projection, topLeftOffset, size, trackBounds, trackKeyTips)) +) = this.then( + CommandButtonLocator( + originalProjection, + command, + presentationModel, + topLeftOffset, + size, + trackBounds, + trackKeyTips + ) +) diff --git a/component/src/desktopMain/kotlin/org/pushingpixels/aurora/component/ribbon/RibbonMetaComponent.kt b/component/src/desktopMain/kotlin/org/pushingpixels/aurora/component/ribbon/RibbonMetaComponent.kt index 42780139..680cca95 100644 --- a/component/src/desktopMain/kotlin/org/pushingpixels/aurora/component/ribbon/RibbonMetaComponent.kt +++ b/component/src/desktopMain/kotlin/org/pushingpixels/aurora/component/ribbon/RibbonMetaComponent.kt @@ -313,6 +313,7 @@ internal fun RibbonMetaComponent( KeyTipTracker.trackKeyTipOffset( originalProjection, originalProjection.ribbonComponentPresentationModel.keyTip, + originalProjection.enabled.invoke(), Offset(captionMid.toFloat(), height / 2.0f) ) } else { @@ -334,6 +335,7 @@ internal fun RibbonMetaComponent( KeyTipTracker.trackKeyTipOffset( originalProjection, originalProjection.ribbonComponentPresentationModel.keyTip, + originalProjection.enabled.invoke(), Offset(componentMid, height / 2.0f) ) } @@ -394,6 +396,7 @@ private class MetaComponentLocator( KeyTipTracker.trackKeyTipBase( projection, projection.presentationModel.ribbonComponentPresentationModel.keyTip!!, + projection.enabled.invoke(), bounds ) } diff --git a/component/src/desktopMain/kotlin/org/pushingpixels/aurora/component/ribbon/impl/KeyTipTracker.kt b/component/src/desktopMain/kotlin/org/pushingpixels/aurora/component/ribbon/impl/KeyTipTracker.kt index f72d0b69..8a39e184 100644 --- a/component/src/desktopMain/kotlin/org/pushingpixels/aurora/component/ribbon/impl/KeyTipTracker.kt +++ b/component/src/desktopMain/kotlin/org/pushingpixels/aurora/component/ribbon/impl/KeyTipTracker.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.unit.* import org.jetbrains.skia.* import org.pushingpixels.aurora.common.AuroraInternalApi import org.pushingpixels.aurora.common.AuroraRect +import org.pushingpixels.aurora.common.isEmpty import org.pushingpixels.aurora.component.model.ContentModel import org.pushingpixels.aurora.component.model.PresentationModel import org.pushingpixels.aurora.component.projection.Projection @@ -52,6 +53,7 @@ object KeyTipTracker { data class KeyTipInfo( var projection: Projection, var keyTip: String, + var isEnabled: Boolean, var screenRect: AuroraRect, var anchor: Offset ) @@ -61,6 +63,7 @@ object KeyTipTracker { fun trackKeyTipBase( projection: Projection, keyTip: String, + isEnabled: Boolean, screenRect: AuroraRect ) { val existing = keyTips.find { @@ -69,13 +72,14 @@ object KeyTipTracker { if (existing != null) { existing.screenRect = screenRect.copy() } else { - keyTips.add(KeyTipInfo(projection, keyTip, screenRect, Offset.Zero)) + keyTips.add(KeyTipInfo(projection, keyTip, isEnabled, screenRect, Offset.Zero)) } } fun trackKeyTipOffset( projection: Projection, keyTip: String, + isEnabled: Boolean, anchor: Offset ) { val existing = keyTips.find { @@ -84,7 +88,12 @@ object KeyTipTracker { if (existing != null) { existing.anchor = anchor.copy() } else { - keyTips.add(KeyTipInfo(projection, keyTip, AuroraRect(0.0f, 0.0f, 0.0f, 0.0f), anchor.copy())) + keyTips.add( + KeyTipInfo( + projection, keyTip, isEnabled, + AuroraRect(0.0f, 0.0f, 0.0f, 0.0f), anchor.copy() + ) + ) } } @@ -123,18 +132,20 @@ fun RibbonKeyTipOverlay(modifier: Modifier, insets: Dp) { Canvas(modifier = modifier) { for (tracked in KeyTipTracker.getKeyTips()) { - drawKeyTip( - tracked, - textStyle, - density, - fontFamilyResolver, - layoutDirection, - insets, - drawingCache, - decorationAreaType, - skinColors, - painters - ) + if (!tracked.screenRect.isEmpty) { + drawKeyTip( + tracked, + textStyle, + density, + fontFamilyResolver, + layoutDirection, + insets, + drawingCache, + decorationAreaType, + skinColors, + painters + ) + } } } } @@ -145,7 +156,7 @@ internal fun getKeyTipSize( density: Density, fontFamilyResolver: FontFamily.Resolver, layoutDirection: LayoutDirection -) : Pair { +): Pair { val leftPadding = KeyTipPaddingValues.calculateLeftPadding(layoutDirection) val rightPadding = KeyTipPaddingValues.calculateRightPadding(layoutDirection) val topPadding = KeyTipPaddingValues.calculateTopPadding() @@ -157,7 +168,8 @@ internal fun getKeyTipSize( density = density, maxLines = 1, fontFamilyResolver = fontFamilyResolver ) - val tipWidth = leftPadding.value * density.density + paragraph.maxIntrinsicWidth + rightPadding.value * density.density + val tipWidth = + leftPadding.value * density.density + paragraph.maxIntrinsicWidth + rightPadding.value * density.density val tipHeight = topPadding.value * density.density + paragraph.height + bottomPadding.value * density.density return Pair(Size(tipWidth, tipHeight), paragraph.firstBaseline) @@ -183,12 +195,10 @@ internal fun DrawScope.drawKeyTip( val leftPadding = KeyTipPaddingValues.calculateLeftPadding(layoutDirection) val topPadding = KeyTipPaddingValues.calculateTopPadding() - val fillScheme = skinColors.getColorScheme(decorationAreaType, ComponentState.Enabled) - val borderScheme = skinColors.getColorScheme( - decorationAreaType, - ColorSchemeAssociationKind.Border, ComponentState.Enabled - ) - val alpha = skinColors.getAlpha(decorationAreaType, ComponentState.Enabled) + val state = if (keyTipInfo.isEnabled) ComponentState.Enabled else ComponentState.DisabledUnselected + val fillScheme = skinColors.getColorScheme(decorationAreaType, state) + val borderScheme = skinColors.getColorScheme(decorationAreaType, ColorSchemeAssociationKind.Border, state) + val alpha = skinColors.getAlpha(decorationAreaType, state) val fillPainter = painters.fillPainter val borderPainter = painters.borderPainter val buttonShaper = ClassicButtonShaper.Instance diff --git a/component/src/desktopMain/kotlin/org/pushingpixels/aurora/component/ribbon/impl/RibbonTaskbarImpl.kt b/component/src/desktopMain/kotlin/org/pushingpixels/aurora/component/ribbon/impl/RibbonTaskbarImpl.kt index 19de1117..b59a1e15 100644 --- a/component/src/desktopMain/kotlin/org/pushingpixels/aurora/component/ribbon/impl/RibbonTaskbarImpl.kt +++ b/component/src/desktopMain/kotlin/org/pushingpixels/aurora/component/ribbon/impl/RibbonTaskbarImpl.kt @@ -38,10 +38,7 @@ import org.pushingpixels.aurora.component.popup.BaseCascadingCommandMenuPopupLay import org.pushingpixels.aurora.component.popup.CascadingCommandMenuHandler import org.pushingpixels.aurora.component.projection.BaseCommandButtonProjection import org.pushingpixels.aurora.component.projection.CommandButtonProjection -import org.pushingpixels.aurora.component.ribbon.RibbonTaskbarCommand -import org.pushingpixels.aurora.component.ribbon.RibbonTaskbarComponent -import org.pushingpixels.aurora.component.ribbon.RibbonTaskbarElement -import org.pushingpixels.aurora.component.ribbon.RibbonTaskbarGallery +import org.pushingpixels.aurora.component.ribbon.* import org.pushingpixels.aurora.component.utils.getEndwardDoubleArrowIcon import org.pushingpixels.aurora.theming.* import org.pushingpixels.aurora.theming.colorscheme.AuroraColorSchemeBundle @@ -68,14 +65,19 @@ private data class TaskbarExpandCommand( private data class TaskbarExpandMenuContentModel( override val onActivatePopup: (() -> Unit)? = null, override val onDeactivatePopup: (() -> Unit)? = null, - val elements: List + val elements: List, + val taskbarKeyTipPolicy: RibbonTaskbarKeyTipPolicy, + val taskbarKeyTipPolicyStartElement: () -> Int ) : BaseCommandMenuContentModel private data class TaskbarExpandCommandPopupMenuPresentationModel( val combinedWidths: Int ) : BaseCommandPopupMenuPresentationModel -private class TaskbarExpandCommandButtonPresentationModel(val combinedWidths: Int) : +private class TaskbarExpandCommandButtonPresentationModel( + val combinedWidths: Int, + override val popupKeyTip: String +) : BaseCommandButtonPresentationModel { override val presentationState = CommandButtonPresentationState.Small override val backgroundAppearanceStrategy = BackgroundAppearanceStrategy.Always @@ -98,7 +100,6 @@ private class TaskbarExpandCommandButtonPresentationModel(val combinedWidths: In override val textOverflow: TextOverflow = TextOverflow.Clip override val popupPlacementStrategy: PopupPlacementStrategy = PopupPlacementStrategy.Downward.HAlignStart override val toDismissPopupsOnActivation: Boolean = true - override val popupKeyTip: String? = null override val popupMenuPresentationModel: TaskbarExpandCommandPopupMenuPresentationModel = TaskbarExpandCommandPopupMenuPresentationModel(combinedWidths = combinedWidths) override val minWidth: Dp = 0.dp @@ -114,7 +115,7 @@ private class TaskbarExpandCommandButtonPresentationModel(val combinedWidths: In override val sides: Sides = Sides.ClosedRectangle override fun overlayWith(overlay: BaseCommandButtonPresentationModel.Overlay): TaskbarExpandCommandButtonPresentationModel { - return TaskbarExpandCommandButtonPresentationModel(this.combinedWidths) + return TaskbarExpandCommandButtonPresentationModel(this.combinedWidths, this.popupKeyTip) } } @@ -164,7 +165,11 @@ private object TaskbarExpandCommandMenuPopupHandler : CascadingCommandMenuHandle Layout(modifier = Modifier.auroraBackgroundNoOverlays() .padding(TaskbarExpandPopupContentPadding), content = { - TaskbarContent(menuContentModel.elements) + TaskbarContent( + menuContentModel.elements, + menuContentModel.taskbarKeyTipPolicy, + menuContentModel.taskbarKeyTipPolicyStartElement.invoke() + ) }, measurePolicy = { measurables, _ -> val height = TaskbarExpandPopupHeight.toPx().toInt() @@ -261,7 +266,8 @@ private class TaskbarExpandCommandButtonProjection( @Composable fun RibbonTaskbar( modifier: Modifier, - elements: List + elements: List, + taskbarKeyTipPolicy: RibbonTaskbarKeyTipPolicy ) { val colors = AuroraSkin.colors val decorationAreaType = AuroraSkin.decorationAreaType @@ -272,7 +278,7 @@ fun RibbonTaskbar( Layout(modifier = modifier, content = { - TaskbarContent(elements) + TaskbarContent(elements, taskbarKeyTipPolicy, 1) TaskbarExpandCommandButtonProjection( contentModel = TaskbarExpandCommand( @@ -283,10 +289,41 @@ fun RibbonTaskbar( density = density ), secondaryContentModel = TaskbarExpandMenuContentModel( - elements = overflowElements + elements = overflowElements, + taskbarKeyTipPolicy = taskbarKeyTipPolicy, + taskbarKeyTipPolicyStartElement = { + // Go over elements displayed in the taskbar (not overflow) + var taken = 0 + for (index in 0 until (elements.count() - overflowElements.count())) { + when (val element = elements[index]) { + is RibbonTaskbarCommand -> { + val needsActionTip = (element.commandProjection.contentModel.action != null) && + element.commandProjection.contentModel.isActionEnabled + val needsPopupTip = (element.commandProjection.contentModel.secondaryContentModel != null) && + element.commandProjection.contentModel.isSecondaryEnabled + if (needsActionTip) { + taken++ + } + if (needsPopupTip) { + taken++ + } + } + + is RibbonTaskbarGallery -> { + taken++ + } + + else -> {} + } + } + taken + 1 + } ) ), - presentationModel = TaskbarExpandCommandButtonPresentationModel(combinedWidths = overflowCombinedWidth) + presentationModel = TaskbarExpandCommandButtonPresentationModel( + combinedWidths = overflowCombinedWidth, + popupKeyTip = taskbarKeyTipPolicy.overflowButtonKeyTip + ) ).project() }, measurePolicy = { measurables, constraints -> @@ -375,15 +412,26 @@ fun RibbonTaskbar( } @Composable -private fun TaskbarContent(elements: List) { +private fun TaskbarContent( + elements: List, + taskbarKeyTipPolicy: RibbonTaskbarKeyTipPolicy, + startContentIndex: Int +) { + var contentIndex = startContentIndex for (element in elements) { when (element) { is RibbonTaskbarCommand -> { + val needsActionTip = (element.commandProjection.contentModel.action != null) && + element.commandProjection.contentModel.isActionEnabled + val needsPopupTip = (element.commandProjection.contentModel.secondaryContentModel != null) && + element.commandProjection.contentModel.isSecondaryEnabled element.commandProjection.reproject( modifier = Modifier, primaryOverlay = BaseCommandButtonPresentationModel.Overlay( presentationState = CommandButtonPresentationState.Small, - backgroundAppearanceStrategy = BackgroundAppearanceStrategy.Flat + backgroundAppearanceStrategy = BackgroundAppearanceStrategy.Flat, + actionKeyTip = if (needsActionTip) taskbarKeyTipPolicy.getContentKeyTip(contentIndex++) else null, + popupKeyTip = if (needsPopupTip) taskbarKeyTipPolicy.getContentKeyTip(contentIndex++) else null ), actionInteractionSource = remember { MutableInteractionSource() }, popupInteractionSource = remember { MutableInteractionSource() } @@ -424,7 +472,8 @@ private fun TaskbarContent(elements: List) { commandPopupFireTrigger = galleryPresentationModel.commandPopupFireTrigger, commandSelectedStateHighlight = galleryPresentationModel.commandSelectedStateHighlight ) - ) + ), + popupKeyTip = taskbarKeyTipPolicy.getContentKeyTip(contentIndex++) ), secondaryOverlays = element.galleryProjection.secondaryOverlays ).project() diff --git a/window/src/desktopMain/kotlin/org/pushingpixels/aurora/window/AuroraRibbonWindow.kt b/window/src/desktopMain/kotlin/org/pushingpixels/aurora/window/AuroraRibbonWindow.kt index 8bdfbe0f..d2c58b4d 100644 --- a/window/src/desktopMain/kotlin/org/pushingpixels/aurora/window/AuroraRibbonWindow.kt +++ b/window/src/desktopMain/kotlin/org/pushingpixels/aurora/window/AuroraRibbonWindow.kt @@ -142,7 +142,8 @@ internal fun AuroraWindowScope.RibbonWindowTitlePaneMainContent( ) { RibbonTaskbar( modifier = Modifier.padding(TaskbarContentPadding), - elements = ribbon.taskbarElements + elements = ribbon.taskbarElements, + taskbarKeyTipPolicy = ribbon.taskbarKeyTipPolicy ) } diff --git a/window/src/desktopMain/kotlin/org/pushingpixels/aurora/window/ribbon/RibbonTaskToggleButton.kt b/window/src/desktopMain/kotlin/org/pushingpixels/aurora/window/ribbon/RibbonTaskToggleButton.kt index a7002698..cbef880f 100644 --- a/window/src/desktopMain/kotlin/org/pushingpixels/aurora/window/ribbon/RibbonTaskToggleButton.kt +++ b/window/src/desktopMain/kotlin/org/pushingpixels/aurora/window/ribbon/RibbonTaskToggleButton.kt @@ -564,6 +564,7 @@ internal fun RibbonTaskToggleButton( KeyTipTracker.trackKeyTipOffset( originalProjection, presentationModel.actionKeyTip!!, + command.isActionEnabled, layoutManager.getActionKeyTipAnchorCenterPoint(command, presentationModel, layoutInfo) ) } @@ -734,6 +735,7 @@ private class RibbonTaskToggleButtonLocator( KeyTipTracker.trackKeyTipBase( projection, projection.presentationModel.actionKeyTip!!, + projection.contentModel.isActionEnabled, bounds ) } @@ -741,6 +743,7 @@ private class RibbonTaskToggleButtonLocator( KeyTipTracker.trackKeyTipBase( projection, projection.presentationModel.popupKeyTip!!, + projection.contentModel.isSecondaryEnabled, bounds ) }