Skip to content

Commit

Permalink
Vertical alignment of keytips in multi-row ribbon bands
Browse files Browse the repository at this point in the history
For #56
  • Loading branch information
kirill-grouchnikov committed Sep 13, 2023
1 parent ad26290 commit 2902909
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,8 @@ internal fun <M : BaseCommandMenuContentModel,

val trackBounds = LocalRibbonTrackBounds.current && (popupMenu == null)
val trackKeyTips = LocalRibbonTrackKeyTips.current && (popupMenu == null)
val bandRowHeight = LocalRibbonBandRowHeight.current
val bandRow = LocalRibbonBandRow.current

Layout(
modifier = modifier.commandButtonLocator(
Expand Down Expand Up @@ -1607,15 +1609,23 @@ internal fun <M : BaseCommandMenuContentModel,
originalProjection,
presentationModel.actionKeyTip!!,
command.isActionEnabled,
layoutManager.getActionKeyTipAnchorCenterPoint(command, presentationModel, layoutInfo)
getAdjustedAnchor(
anchor = layoutManager.getActionKeyTipAnchorCenterPoint(command, presentationModel, layoutInfo),
row = bandRow,
rowHeight = bandRowHeight
)
)
}
if ((presentationModel.popupKeyTip != null) && !layoutInfo.popupClickArea.isEmpty) {
KeyTipTracker.trackKeyTipOffset(
originalProjection,
presentationModel.popupKeyTip!!,
command.isSecondaryEnabled,
layoutManager.getPopupKeyTipAnchorCenterPoint(command, presentationModel, layoutInfo)
getAdjustedAnchor(
anchor = layoutManager.getPopupKeyTipAnchorCenterPoint(command, presentationModel, layoutInfo),
row = bandRow,
rowHeight = bandRowHeight
)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ internal fun <C : ContentModel, P : PresentationModel> RibbonMetaComponent(

val trackBounds = LocalRibbonTrackBounds.current
val trackKeyTips = LocalRibbonTrackKeyTips.current
val bandRowHeight = LocalRibbonBandRowHeight.current
val bandRow = LocalRibbonBandRow.current

Layout(
modifier = if (trackBounds) {
Expand Down Expand Up @@ -314,7 +316,11 @@ internal fun <C : ContentModel, P : PresentationModel> RibbonMetaComponent(
originalProjection,
originalProjection.ribbonComponentPresentationModel.keyTip,
originalProjection.enabled.invoke(),
Offset(captionMid.toFloat(), height / 2.0f)
getAdjustedAnchor(
anchor = Offset(captionMid.toFloat(), height / 2.0f),
row = bandRow,
rowHeight = bandRowHeight
)
)
} else {
val componentMid = if (layoutDirection == LayoutDirection.Ltr) {
Expand All @@ -336,7 +342,11 @@ internal fun <C : ContentModel, P : PresentationModel> RibbonMetaComponent(
originalProjection,
originalProjection.ribbonComponentPresentationModel.keyTip,
originalProjection.enabled.invoke(),
Offset(componentMid, height / 2.0f)
getAdjustedAnchor(
anchor = Offset(componentMid, height / 2.0f),
row = bandRow,
rowHeight = bandRowHeight
)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,20 @@ internal fun getKeyTipSize(
return Pair(Size(tipWidth, tipHeight), paragraph.firstBaseline)
}

@OptIn(AuroraInternalApi::class)
internal fun getAdjustedAnchor(
anchor: Offset,
row: RibbonBandRow,
rowHeight: Int
): Offset {
return when (row) {
RibbonBandRow.Top -> anchor.copy(y = 0.0f)
RibbonBandRow.Middle -> anchor.copy(y = rowHeight / 2.0f)
RibbonBandRow.Bottom -> anchor.copy(y = rowHeight.toFloat())
RibbonBandRow.None -> anchor
}
}

@OptIn(AuroraInternalApi::class)
internal fun DrawScope.drawKeyTip(
keyTipInfo: KeyTipTracker.KeyTipInfo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ import androidx.compose.runtime.staticCompositionLocalOf
import org.pushingpixels.aurora.common.AuroraInternalApi

@AuroraInternalApi
val LocalRibbonBandRowHeight = staticCompositionLocalOf<Int> {
error("LocalRibbonBandRowHeight not provided")
enum class RibbonBandRow {
Top, Middle, Bottom, None
}

@AuroraInternalApi
val LocalRibbonBandRowHeight = staticCompositionLocalOf {
0
}

@AuroraInternalApi
Expand All @@ -32,3 +37,8 @@ val LocalRibbonTrackBounds = staticCompositionLocalOf {
val LocalRibbonTrackKeyTips = staticCompositionLocalOf {
false
}

@AuroraInternalApi
val LocalRibbonBandRow = staticCompositionLocalOf {
RibbonBandRow.None
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ interface RibbonBandResizePolicy {
fun getPreferredWidth(ribbonBand: AbstractRibbonBand, availableHeight: Int, gap: Int): Int
}

interface FlowRibbonBandResizePolicy : RibbonBandResizePolicy
sealed interface FlowRibbonBandResizePolicy : RibbonBandResizePolicy {
val rowCount: Int
}

abstract class CoreRibbonResizePolicy(val mapping: (PresentationPriority) -> PresentationPriority) :
RibbonBandResizePolicy
Expand Down Expand Up @@ -370,6 +372,8 @@ object CoreRibbonResizePolicies {
object Mirror : BaseCoreRibbonResizePolicy({ it })

object FlowOneRow : FlowRibbonBandResizePolicy {
override val rowCount: Int = 1

@Composable
override fun getPreferredWidth(ribbonBand: AbstractRibbonBand, availableHeight: Int, gap: Int): Int {
require(ribbonBand is FlowRibbonBand) {
Expand All @@ -391,6 +395,8 @@ object CoreRibbonResizePolicies {
}

object FlowTwoRows : FlowRibbonBandResizePolicy {
override val rowCount: Int = 2

@Composable
override fun getPreferredWidth(ribbonBand: AbstractRibbonBand, availableHeight: Int, gap: Int): Int {
require(ribbonBand is FlowRibbonBand) {
Expand Down Expand Up @@ -430,6 +436,8 @@ object CoreRibbonResizePolicies {
}

object FlowThreeRows : FlowRibbonBandResizePolicy {
override val rowCount: Int = 3

@Composable
override fun getPreferredWidth(ribbonBand: AbstractRibbonBand, availableHeight: Int, gap: Int): Int {
require(ribbonBand is FlowRibbonBand) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ import org.pushingpixels.aurora.component.projection.CommandButtonProjection
import org.pushingpixels.aurora.component.projection.LabelProjection
import org.pushingpixels.aurora.component.projection.VerticalSeparatorProjection
import org.pushingpixels.aurora.component.ribbon.*
import org.pushingpixels.aurora.component.ribbon.impl.LocalRibbonBandRowHeight
import org.pushingpixels.aurora.component.ribbon.impl.LocalRibbonTrackBounds
import org.pushingpixels.aurora.component.ribbon.impl.LocalRibbonTrackKeyTips
import org.pushingpixels.aurora.component.ribbon.impl.*
import org.pushingpixels.aurora.component.ribbon.resize.CoreRibbonResizePolicies
import org.pushingpixels.aurora.component.ribbon.resize.CoreRibbonResizePolicy
import org.pushingpixels.aurora.component.ribbon.resize.FlowRibbonBandResizePolicy
Expand Down Expand Up @@ -205,6 +203,7 @@ internal fun RibbonBands(ribbonTask: RibbonTask) {

CompositionLocalProvider(
LocalRibbonBandRowHeight provides rowHeight,
LocalRibbonBandRow provides RibbonBandRow.None,
) {
AuroraDecorationArea(decorationAreaType = DecorationAreaType.ControlPane) {
SubcomposeLayout(
Expand Down Expand Up @@ -428,6 +427,7 @@ private fun BigButtons(buttons: List<BaseCommandButtonProjection<*, *, *>>) {
}
}

@OptIn(AuroraInternalApi::class)
@Composable
private fun MediumButtons(buttons: List<BaseCommandButtonProjection<*, *, *>>) {
Row(
Expand All @@ -443,21 +443,29 @@ private fun MediumButtons(buttons: List<BaseCommandButtonProjection<*, *, *>>) {
) {
for (row in 1..3) {
if (buttonIndex < buttons.size) {
buttons[buttonIndex].reproject(modifier = Modifier,
primaryOverlay = BaseCommandButtonPresentationModel.Overlay(
presentationState = CommandButtonPresentationState.Medium,
backgroundAppearanceStrategy = BackgroundAppearanceStrategy.Flat
),
actionInteractionSource = remember { MutableInteractionSource() },
popupInteractionSource = remember { MutableInteractionSource() })
buttonIndex++
val rowEnum = when (row) {
1 -> RibbonBandRow.Top
2 -> RibbonBandRow.Middle
else -> RibbonBandRow.Bottom
}
CompositionLocalProvider(LocalRibbonBandRow provides rowEnum) {
buttons[buttonIndex].reproject(modifier = Modifier,
primaryOverlay = BaseCommandButtonPresentationModel.Overlay(
presentationState = CommandButtonPresentationState.Medium,
backgroundAppearanceStrategy = BackgroundAppearanceStrategy.Flat
),
actionInteractionSource = remember { MutableInteractionSource() },
popupInteractionSource = remember { MutableInteractionSource() })
buttonIndex++
}
}
}
}
}
}
}

@OptIn(AuroraInternalApi::class)
@Composable
private fun SmallButtons(buttons: List<BaseCommandButtonProjection<*, *, *>>) {
Row(
Expand All @@ -473,14 +481,21 @@ private fun SmallButtons(buttons: List<BaseCommandButtonProjection<*, *, *>>) {
) {
for (row in 1..3) {
if (buttonIndex < buttons.size) {
buttons[buttonIndex].reproject(modifier = Modifier,
primaryOverlay = BaseCommandButtonPresentationModel.Overlay(
presentationState = CommandButtonPresentationState.Small,
backgroundAppearanceStrategy = BackgroundAppearanceStrategy.Flat
),
actionInteractionSource = remember { MutableInteractionSource() },
popupInteractionSource = remember { MutableInteractionSource() })
buttonIndex++
val rowEnum = when (row) {
1 -> RibbonBandRow.Top
2 -> RibbonBandRow.Middle
else -> RibbonBandRow.Bottom
}
CompositionLocalProvider(LocalRibbonBandRow provides rowEnum) {
buttons[buttonIndex].reproject(modifier = Modifier,
primaryOverlay = BaseCommandButtonPresentationModel.Overlay(
presentationState = CommandButtonPresentationState.Small,
backgroundAppearanceStrategy = BackgroundAppearanceStrategy.Flat
),
actionInteractionSource = remember { MutableInteractionSource() },
popupInteractionSource = remember { MutableInteractionSource() })
buttonIndex++
}
}
}
}
Expand Down Expand Up @@ -562,6 +577,7 @@ private fun getComponentGroupContentLayoutInfo(
)
}

@OptIn(AuroraInternalApi::class)
@Composable
private fun RibbonBandComponentGroupContent(
group: RibbonBandComponentGroup,
Expand Down Expand Up @@ -592,7 +608,15 @@ private fun RibbonBandComponentGroupContent(
for (projection in group.componentProjections) {
// All components in the same column have the same (max) width
val currentColumnWidth = layoutInfo.columnWidths[currentColumnIndex]
projection.project(Modifier.width((currentColumnWidth / density.density).dp))

val rowEnum = when (currentContentRow) {
0 -> RibbonBandRow.Top
1 -> RibbonBandRow.Middle
else -> RibbonBandRow.Bottom
}
CompositionLocalProvider(LocalRibbonBandRow provides rowEnum) {
projection.project(Modifier.width((currentColumnWidth / density.density).dp))
}

currentContentRow++
if (currentContentRow == contentRows) {
Expand Down Expand Up @@ -638,6 +662,7 @@ private fun RibbonBandComponentGroupContent(
})
}

@OptIn(AuroraInternalApi::class)
@Composable
private fun FlowRibbonBandContent(
band: FlowRibbonBand,
Expand All @@ -649,32 +674,43 @@ private fun FlowRibbonBandContent(
val rowHeight = ((bandContentHeight - 4 * gap) / 3.0f).toInt()

val optimalWidth = bandResizePolicy.getPreferredWidth(band, bandContentHeight, gap)
val rowCount = bandResizePolicy.rowCount
Layout(modifier = Modifier.fillMaxHeight().width((optimalWidth / density.density).dp)
.padding(horizontal = RibbonBandContentGap),
content = {
// Project all the content
var currRow = 1
var currX = 0
val widthForContent = optimalWidth - 2 * gap
for (projection in band.flowComponentProjections) {
projection.project(Modifier)
val projectionWidth = projection.intrinsicWidth(rowHeight)
if ((currX + projectionWidth) > widthForContent) {
currX = 0
currRow++
}
currX += (projectionWidth + gap)

val rowEnum = when (rowCount) {
1 -> RibbonBandRow.Middle
2 -> if (currRow == 1) RibbonBandRow.Top else RibbonBandRow.Bottom
else -> when (currRow) {
1 -> RibbonBandRow.Top
2 -> RibbonBandRow.Middle
else -> RibbonBandRow.Bottom
}
}
CompositionLocalProvider(LocalRibbonBandRow provides rowEnum) {
projection.project(Modifier)
}
}
},
measurePolicy = { measurables, constraints ->
val width = constraints.maxWidth

val placeables = measurables.map { it.measure(Constraints()) }
// count the rows
var rows = 1
var currX = 0
for (placeable in placeables) {
if (currX + placeable.measuredWidth <= width) {
currX += (placeable.measuredWidth + gap)
} else {
currX = 0
rows++
}
}

// Compute vertical gap for balanced, vertically centered layout of the rows
val verticalGap = (constraints.maxHeight - rows * rowHeight) / (rows + 1)
val verticalGap = (constraints.maxHeight - rowCount * rowHeight) / (rowCount + 1)

layout(width = width, height = constraints.maxHeight) {
var x = 0
Expand Down

0 comments on commit 2902909

Please sign in to comment.