Skip to content

Commit

Permalink
First pass for wiring breadcrumb bar content provider
Browse files Browse the repository at this point in the history
For #8
  • Loading branch information
kirill-grouchnikov committed Jan 6, 2022
1 parent 060f37a commit 54de712
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 31 deletions.
Expand Up @@ -18,9 +18,7 @@ package org.pushingpixels.aurora.component
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.painter.Painter
Expand All @@ -32,6 +30,7 @@ import androidx.compose.ui.text.resolveDefaults
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import org.pushingpixels.aurora.common.AuroraInternalApi
import org.pushingpixels.aurora.common.withAlpha
Expand All @@ -45,11 +44,31 @@ import org.pushingpixels.aurora.theming.*

@OptIn(AuroraInternalApi::class)
@Composable
fun AuroraBreadcrumbBar(
commands: List<Command>,
fun <T> AuroraBreadcrumbBar(
contentProvider: BreadcrumbBarContentProvider<T>,
presentationModel: BreadcrumbBarPresentationModel = BreadcrumbBarPresentationModel(),
modifier: Modifier
) {
val initialized = remember { mutableStateOf(false) }

// The currently shown path of breadcrumb items.
val shownPath: MutableList<BreadcrumbItem<T>> = remember { mutableStateListOf() }
// For each item in "shownPath" this has the list of path choices - to be displayed
// in the popup for that particular item. The first item in this list has path choices
// for the root.
val shownPathChoices: MutableList<List<BreadcrumbItem<T>>> = remember { mutableStateListOf() }

val contentModel = remember { BreadcrumbBarContentModel(shownPath) }
if (!initialized.value) {
LaunchedEffect(null) {
coroutineScope {
val rootPathChoices = contentProvider.getPathChoices(emptyList())
shownPathChoices.add(rootPathChoices)
}
initialized.value = true
}
}

val colors = AuroraSkin.colors
val decorationAreaType = AuroraSkin.decorationAreaType
val density = LocalDensity.current
Expand Down Expand Up @@ -87,7 +106,12 @@ fun AuroraBreadcrumbBar(
backgroundAppearanceStrategy = presentationModel.backgroundAppearanceStrategy,
iconActiveFilterStrategy = presentationModel.iconActiveFilterStrategy,
iconEnabledFilterStrategy = presentationModel.iconEnabledFilterStrategy,
iconDisabledFilterStrategy = presentationModel.iconDisabledFilterStrategy
iconDisabledFilterStrategy = presentationModel.iconDisabledFilterStrategy,
popupMenuPresentationModel = CommandPopupMenuPresentationModel(
menuIconActiveFilterStrategy = presentationModel.iconActiveFilterStrategy,
menuIconEnabledFilterStrategy = presentationModel.iconEnabledFilterStrategy,
menuIconDisabledFilterStrategy = presentationModel.iconDisabledFilterStrategy,
)
)
val contentLayoutManager = contentPresentationModel.presentationState.createLayoutManager(
layoutDirection = layoutDirection,
Expand Down Expand Up @@ -199,6 +223,39 @@ fun AuroraBreadcrumbBar(
}
})

val commands = derivedStateOf {
val rootChoices = if (shownPathChoices.isNotEmpty()) shownPathChoices[0] else null
val rootSecondaryContentModel = rootChoices?.let {
CommandMenuContentModel(
group = CommandGroup(
title = "",
commands = it.map { rootChoice ->
Command(text = rootChoice.displayName,
icon = rootChoice.icon,
action = {
shownPath.clear()
shownPath.add(rootChoice)
})
}
)
)
}

listOf(
Command(
text = "root",
icon = null,
action = {},
secondaryContentModel = rootSecondaryContentModel
)
) + shownPath.map {
Command(text = it.displayName,
icon = it.icon,
action = { println("Act on ${it.data}") }
)
}
}

Layout(modifier = modifier.fillMaxWidth(),
content = {
// Leftwards scroller
Expand All @@ -217,7 +274,7 @@ fun AuroraBreadcrumbBar(

Box(modifier = Modifier.horizontalScroll(stateHorizontal)) {
Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
for (command in commands) {
for (command in commands.value) {
CommandButtonProjection(
contentModel = command,
presentationModel = contentPresentationModel
Expand Down Expand Up @@ -269,17 +326,29 @@ fun AuroraBreadcrumbBar(
// How much space does the scrollable content need?
var boxRequiredWidth = 0.0f
var boxHeight = 0
for (command in commands) {
if (commands.value.isNotEmpty()) {
for (command in commands.value) {
val commandPreLayoutInfo =
contentLayoutManager.getPreLayoutInfo(
command,
contentPresentationModel
)
val commandSize = contentLayoutManager.getPreferredSize(
command, contentPresentationModel, commandPreLayoutInfo
)
boxRequiredWidth += commandSize.width
boxHeight = commandSize.height.toInt()
}
} else {
val forSizing = Command(text = "sample", action = {})
val commandPreLayoutInfo =
contentLayoutManager.getPreLayoutInfo(
command,
forSizing,
contentPresentationModel
)
val commandSize = contentLayoutManager.getPreferredSize(
command, contentPresentationModel, commandPreLayoutInfo
)
boxRequiredWidth += commandSize.width
boxHeight = commandSize.height.toInt()
boxHeight = contentLayoutManager.getPreferredSize(
forSizing, contentPresentationModel, commandPreLayoutInfo
).height.toInt()
}

// Do we need to show the scrollers? If available width from constraints is enough
Expand Down
Expand Up @@ -15,8 +15,127 @@
*/
package org.pushingpixels.aurora.component.model

import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.painter.Painter
import org.pushingpixels.aurora.theming.BackgroundAppearanceStrategy
import org.pushingpixels.aurora.theming.IconFilterStrategy
import java.io.InputStream
import java.util.*

/**
* A single item in the breadcrumb bar model.
*/
data class BreadcrumbItem<T>(val displayName: String, val icon: Painter?, val data: T)

/**
* Content provider for a breadcrumb bar.
*/
interface BreadcrumbBarContentProvider<T> {
/**
* Returns the choice elements that correspond to the specified path. If the
* path is empty, `null` should be returned. If path is
* `null`, the "root" elements should be returned
*
* @param path Breadcrumb bar path.
* @return The choice elements that correspond to the specified path
*/
suspend fun getPathChoices(path: List<BreadcrumbItem<T>>): List<BreadcrumbItem<T>>

/**
* Returns the leaf elements that correspond to the specified path. If the
* path is empty, `null` should be returned. If path is
* `null`, leaf content of the "root" elements should be returned. Most probably, if
* your root is more than one element, you should be returning null in here.
*
* @param path Breadcrumb bar path.
* @return The leaf elements that correspond to the specified path
*/
suspend fun getLeaves(path: List<BreadcrumbItem<T>>): List<BreadcrumbItem<T>>

/**
* Returns the input stream with the leaf content. Some implementations may
* return `null` if this is not applicable.
*
* @param leaf Leaf.
* @return Input stream with the leaf content. May be `null` if
* this is not applicable.
*/
suspend fun getLeafContent(leaf: T): InputStream? {
return null
}
}

/**
* Model for the breadcrumb bar component.
*/
class BreadcrumbBarContentModel<T>(val items: MutableList<BreadcrumbItem<T>>) {
/**
* Returns the index of the specified item.
*
* @param item Item.
* @return Index of the item if it is in the model or -1 if it is not.
*/
fun indexOf(item: BreadcrumbItem<T>): Int {
return items.indexOf(item)
}

/**
* Removes the last item in this model.
*/
fun removeLast() {
items.removeLast()
}

/**
* Resets this model, removing all the items.
*/
fun reset() {
items.clear()
}

/**
* Returns the number of items in this model.
*
* @return Number of items in this model.
*/
val itemCount: Int = items.size

/**
* Returns the model item at the specified index.
*
* @param index Item index.
* @return The model item at the specified index. Will return
* `null` if the index is negative or larger than the
* number of items.
*/
fun getItem(index: Int): BreadcrumbItem<T>? {
if (index < 0) return null
return if (index >= itemCount) null else items[index]
}

/**
* Replaces the current item list with the specified list.
*
* @param items New contents of the model.
*/
fun replace(items: List<BreadcrumbItem<T>>) {
this.items.clear()
for (i in items.indices) {
this.items.add(items[i])
}
}

/**
* Adds the specified item at the end of the path.
*
* @param item Item to add.
*/
fun add(item: BreadcrumbItem<T>) {
items.add(item)
}
}


data class BreadcrumbBarPresentationModel(
val presentationState: CommandButtonPresentationState = CommandButtonPresentationState.Medium,
Expand Down
Expand Up @@ -22,12 +22,16 @@ import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import org.pushingpixels.aurora.component.layout.CommandButtonLayoutManager
import org.pushingpixels.aurora.component.layout.CommandButtonLayoutManagerMedium
import org.pushingpixels.aurora.theming.IconFilterStrategy
import org.pushingpixels.aurora.theming.PopupPlacementStrategy

data class CommandPopupMenuPresentationModel(
val panelPresentationModel: CommandPanelPresentationModel? = null,
val menuPresentationState: CommandButtonPresentationState =
DefaultCommandPopupMenuPresentationState,
val menuIconActiveFilterStrategy: IconFilterStrategy = IconFilterStrategy.Original,
val menuIconEnabledFilterStrategy: IconFilterStrategy = IconFilterStrategy.Original,
val menuIconDisabledFilterStrategy: IconFilterStrategy = IconFilterStrategy.ThemedFollowColorScheme,
val menuContentPadding: PaddingValues =
CommandButtonSizingConstants.CompactButtonContentPadding,
val maxVisibleMenuCommands: Int = 0,
Expand Down
Expand Up @@ -111,6 +111,9 @@ internal fun displayPopupContent(
// the popup menu presentation model configured on the top-level presentation model
val regularButtonPresentationModel = CommandButtonPresentationModel(
presentationState = presentationModel.menuPresentationState,
iconActiveFilterStrategy = presentationModel.menuIconActiveFilterStrategy,
iconEnabledFilterStrategy = presentationModel.menuIconEnabledFilterStrategy,
iconDisabledFilterStrategy = presentationModel.menuIconDisabledFilterStrategy,
popupPlacementStrategy = presentationModel.popupPlacementStrategy,
backgroundAppearanceStrategy = BackgroundAppearanceStrategy.Flat,
horizontalAlignment = HorizontalAlignment.Leading,
Expand Down Expand Up @@ -506,6 +509,9 @@ private fun PopupGeneralContent(
// the popup menu presentation model configured on the top-level presentation model
val menuButtonPresentationModel = CommandButtonPresentationModel(
presentationState = menuPresentationModel.menuPresentationState,
iconActiveFilterStrategy = menuPresentationModel.menuIconActiveFilterStrategy,
iconEnabledFilterStrategy = menuPresentationModel.menuIconEnabledFilterStrategy,
iconDisabledFilterStrategy = menuPresentationModel.menuIconDisabledFilterStrategy,
forceAllocateSpaceForIcon = atLeastOneButtonHasIcon,
popupPlacementStrategy = menuPresentationModel.popupPlacementStrategy,
backgroundAppearanceStrategy = BackgroundAppearanceStrategy.Flat,
Expand Down

0 comments on commit 54de712

Please sign in to comment.