diff --git a/src/commonMain/kotlin/baaahs/gl/render/RenderManager.kt b/src/commonMain/kotlin/baaahs/gl/render/RenderManager.kt index c90b5ee94b..4fe1f1c8d5 100644 --- a/src/commonMain/kotlin/baaahs/gl/render/RenderManager.kt +++ b/src/commonMain/kotlin/baaahs/gl/render/RenderManager.kt @@ -13,7 +13,7 @@ import baaahs.util.Logger class RenderManager(glContext: GlContext) { private val renderEngines = CacheBuilder { fixtureType -> ModelRenderEngine( - glContext, fixtureType, resultDeliveryStrategy = pickResultDeliveryStrategy(glContext) + glContext, fixtureType, resultDeliveryStrategy = glContext.pickResultDeliveryStrategy() ) } diff --git a/src/commonMain/kotlin/baaahs/gl/render/ResultDeliveryStrategy.kt b/src/commonMain/kotlin/baaahs/gl/render/ResultDeliveryStrategy.kt index c22609c7d8..44c64bb776 100644 --- a/src/commonMain/kotlin/baaahs/gl/render/ResultDeliveryStrategy.kt +++ b/src/commonMain/kotlin/baaahs/gl/render/ResultDeliveryStrategy.kt @@ -33,4 +33,7 @@ class SyncResultDeliveryStrategy : ResultDeliveryStrategy { } } +fun GlContext.pickResultDeliveryStrategy() = + pickResultDeliveryStrategy(this) + expect fun pickResultDeliveryStrategy(gl: GlContext): ResultDeliveryStrategy \ No newline at end of file diff --git a/src/commonMain/kotlin/baaahs/ui/gridlayout/Layout.kt b/src/commonMain/kotlin/baaahs/ui/gridlayout/Layout.kt index 96c471f083..34e26cde7e 100644 --- a/src/commonMain/kotlin/baaahs/ui/gridlayout/Layout.kt +++ b/src/commonMain/kotlin/baaahs/ui/gridlayout/Layout.kt @@ -101,8 +101,8 @@ data class Layout( /** * Get all static elements. - * @param {Array} layout Array of layout objects. - * @return {Array} Array of static layout items.. + * + * @return Array of static layout items. */ private fun getStatics(): List = items.filter { l -> l.isStatic } @@ -110,21 +110,16 @@ data class Layout( /** * Move an element. Responsible for doing cascading movements of other elements. * - * Returns a new layout with moved layout items. - * - * @param {Array} layout Full layout to modify. - * @param {LayoutItem} l element to move. - * @param {Number} [x] X position in grid units. - * @param {Number} [y] Y position in grid units. + * @param item Element to move. + * @param x X position in grid units. + * @param y Y position in grid units. + * @return A new layout with moved layout items. + * @throws ImpossibleLayoutException if the move isn't possible because of collisions or constraints. */ - fun moveElement( - l: LayoutItem, - x: Int, - y: Int - ): Layout { - for (direction in Direction.rankedPushOptions(x - l.x, y - l.y)) { + fun moveElement(item: LayoutItem, x: Int, y: Int): Layout { + for (direction in Direction.rankedPushOptions(x - item.x, y - item.y)) { try { - return moveElementInternal(l, x, y, true, direction) + return moveElementInternal(item, x, y, direction) } catch (e: ImpossibleLayoutException) { // Try again. } @@ -132,13 +127,7 @@ data class Layout( throw ImpossibleLayoutException() } - private fun moveElementInternal( - l: LayoutItem, - x: Int, - y: Int, - isDirectUserAction: Boolean, - pushDirection: Direction - ): Layout { + private fun moveElementInternal(l: LayoutItem, x: Int, y: Int, pushDirection: Direction): Layout { // If this is static and not explicitly enabled as draggable, // no move is possible, so we can short-circuit this immediately. if (l.isStatic && !l.isDraggable) throw ImpossibleLayoutException() @@ -151,11 +140,15 @@ data class Layout( } val movedItem = l.movedTo(x, y) - if (outOfBounds(movedItem)) + return fitElement(movedItem, pushDirection) + } + + private fun fitElement(changedItem: LayoutItem, pushDirection: Direction): Layout { + if (outOfBounds(changedItem)) throw ImpossibleLayoutException() - var updatedLayout = updateLayout(movedItem) - val collisions = findCollisions(movedItem) + var updatedLayout = updateLayout(changedItem) + val collisions = findCollisions(changedItem) // If it collides with anything, move it (recursively). if (collisions.isNotEmpty()) { @@ -164,30 +157,39 @@ data class Layout( // nearest collision. for (collision in pushDirection.sort(collisions)) { logger.info { - "Resolving collision between ${movedItem.i} at [${movedItem.x},${movedItem.y}] and ${collision.i} at [${collision.x},${collision.y}]" + "Resolving collision between ${changedItem.i} at [${changedItem.x},${changedItem.y}] and ${collision.i} at [${collision.x},${collision.y}]" } // Short circuit so we can't infinitely loop if (collision.moved) throw ImpossibleLayoutException() updatedLayout = - updatedLayout.pushCollidingElement(movedItem, collision, isDirectUserAction, pushDirection) + updatedLayout.pushCollidingElement(changedItem, collision, pushDirection) } } return updatedLayout } + /** + * Resize an element. Responsible for doing cascading movements of other elements. + * + * @param item Element to move. + * @param w X position in grid units. + * @param y Y position in grid units. + * @return A new layout with resized and possibly moved layout items. + * @throws ImpossibleLayoutException if the move isn't possible because of collisions or constraints. + */ fun resizeElement(item: LayoutItem, w: Int, h: Int): Layout { val resizedItem = item.copy(w = w, h = h) - var updatedLayout = updateLayout(resizedItem) - val collisions = updatedLayout.findCollisions(resizedItem) - - if (collisions.isNotEmpty()) { - throw ImpossibleLayoutException() + for (direction in arrayOf(Direction.East, Direction.South)) { + try { + return fitElement(resizedItem, direction) + } catch (e: ImpossibleLayoutException) { + // Try again. + } } - - return updatedLayout + throw ImpossibleLayoutException() } private fun outOfBounds(movedItem: LayoutItem) = @@ -207,23 +209,25 @@ data class Layout( private fun pushCollidingElement( collidesWith: LayoutItem, itemToMove: LayoutItem, - isDirectUserAction: Boolean, direction: Direction ): Layout { // Don't move static items - we have to move the other element away. if (itemToMove.isStatic) { if (collidesWith.isStatic) throw ImpossibleLayoutException() - return pushCollidingElement(itemToMove, collidesWith, isDirectUserAction, direction) + return moveElementInternal( + collidesWith, + collidesWith.x + direction.xIncr, + collidesWith.y + direction.yIncr, + direction + ) } return moveElementInternal( itemToMove, itemToMove.x + direction.xIncr, itemToMove.y + direction.yIncr, - false, direction - // we're already colliding (not for static items) ) } diff --git a/src/commonMain/kotlin/baaahs/util/Stats.kt b/src/commonMain/kotlin/baaahs/util/Stats.kt index 1ff497f222..b9f5883260 100644 --- a/src/commonMain/kotlin/baaahs/util/Stats.kt +++ b/src/commonMain/kotlin/baaahs/util/Stats.kt @@ -13,11 +13,16 @@ open class Stats { ReadOnlyProperty { _, _ -> statistic } } - fun summarize(): String = statistics.values.joinToString("\n") { it.summarize() } + val all = statistics.values + + fun summarize(): String = all.joinToString("\n") { it.summarize() } class Statistic(val name: String) { var calls = 0 var elapsedTime = Interval(0) + val elapsedTimeMs get() = (elapsedTime / 1000).roundToInt() + val averageTime get() = if (calls > 0) elapsedTime / calls else null + val averageTimeMs get() = averageTime?.div(1000)?.roundToInt() fun time(block: () -> T): T { val startTime = internalTimerClock.now() @@ -40,10 +45,7 @@ open class Stats { } fun summarize(): String { - val avgTimeMs = if (calls > 0) { - (elapsedTime / calls * 1000).roundToInt() - } else "—" - return "$name: $calls calls, avg ${avgTimeMs}ms, total ${(elapsedTime * 1000).roundToInt()}ms" + return "$name: $calls calls, avg ${averageTimeMs ?: "-"}ms, total ${elapsedTimeMs}ms" } } } \ No newline at end of file diff --git a/src/jsMain/kotlin/baaahs/app/settings/UiSettings.kt b/src/jsMain/kotlin/baaahs/app/settings/UiSettings.kt index 240dd3e369..27228f0030 100644 --- a/src/jsMain/kotlin/baaahs/app/settings/UiSettings.kt +++ b/src/jsMain/kotlin/baaahs/app/settings/UiSettings.kt @@ -8,5 +8,6 @@ data class UiSettings( val darkMode: Boolean = true, val useSharedContexts: Boolean = true, val renderButtonPreviews: Boolean = true, - val appMode: AppMode = AppMode.Show + val appMode: AppMode = AppMode.Show, + val developerMode: Boolean = false ) \ No newline at end of file diff --git a/src/jsMain/kotlin/baaahs/app/ui/AppContext.kt b/src/jsMain/kotlin/baaahs/app/ui/AppContext.kt index 2290095e0e..9648ad2446 100644 --- a/src/jsMain/kotlin/baaahs/app/ui/AppContext.kt +++ b/src/jsMain/kotlin/baaahs/app/ui/AppContext.kt @@ -20,6 +20,7 @@ import baaahs.util.Clock import react.createContext val appContext = createContext() +val toolchainContext = createContext() /** * The application context. @@ -32,7 +33,6 @@ external interface AppContext { var dragNDrop: ReactBeautifulDragNDrop var webClient: WebClient.Facade var plugins: Plugins - var toolchain: Toolchain var uiSettings: UiSettings var allStyles: AllStyles var prompt: (prompt: Prompt) -> Unit diff --git a/src/jsMain/kotlin/baaahs/app/ui/AppIndex.kt b/src/jsMain/kotlin/baaahs/app/ui/AppIndex.kt index b3054dcb80..60392dd672 100644 --- a/src/jsMain/kotlin/baaahs/app/ui/AppIndex.kt +++ b/src/jsMain/kotlin/baaahs/app/ui/AppIndex.kt @@ -75,7 +75,6 @@ val AppIndex = xComponent("AppIndex") { props -> this.dragNDrop = dragNDrop this.webClient = webClient this.plugins = webClient.plugins - this.toolchain = webClient.toolchain this.uiSettings = uiSettings this.allStyles = allStyles this.prompt = { prompt = it } @@ -156,6 +155,9 @@ val AppIndex = xComponent("AppIndex") { props -> if (editMode.isOn) Styles.editModeOn else Styles.editModeOff val show = showManager.show + val toolchain = memo(webClient.toolchain, show) { + webClient.toolchain.withCache("Open Show") + } onMount(keyboard) { keyboard.listen(window) @@ -197,130 +199,134 @@ val AppIndex = xComponent("AppIndex") { props -> appContext.Provider { attrs.value = myAppContext - appGlContext.Provider { - attrs.value = myAppGlContext - - ThemeProvider { - attrs.theme = theme - CssBaseline {} - - Paper { - attrs.classes = jso { this.root = -themeStyles.appRoot and appDrawerStateStyle and editModeStyle } - - appDrawer { - attrs.open = renderAppDrawerOpen - attrs.forcedOpen = forceAppDrawerOpen - attrs.onClose = handleAppDrawerToggle - attrs.appMode = appMode - attrs.onAppModeChange = handleAppModeChange - attrs.documentManager = documentManager - attrs.onLayoutEditorDialogToggle = handleLayoutEditorDialogToggle - attrs.darkMode = darkMode - attrs.onDarkModeChange = handleDarkModeChange - attrs.onSettings = handleSettings - } - - appToolbar { - attrs.appMode = appMode - attrs.documentManager = documentManager - attrs.onMenuButtonClick = handleAppDrawerToggle - attrs.onAppModeChange = handleAppModeChange - } + toolchainContext.Provider { + attrs.value = toolchain + + appGlContext.Provider { + attrs.value = myAppGlContext + + ThemeProvider { + attrs.theme = theme + CssBaseline {} + + Paper { + attrs.classes = jso { this.root = -themeStyles.appRoot and appDrawerStateStyle and editModeStyle } + + appDrawer { + attrs.open = renderAppDrawerOpen + attrs.forcedOpen = forceAppDrawerOpen + attrs.onClose = handleAppDrawerToggle + attrs.appMode = appMode + attrs.onAppModeChange = handleAppModeChange + attrs.documentManager = documentManager + attrs.onLayoutEditorDialogToggle = handleLayoutEditorDialogToggle + attrs.darkMode = darkMode + attrs.onDarkModeChange = handleDarkModeChange + attrs.onSettings = handleSettings + } - div(+themeStyles.appContent) { - if (!webClient.isConnected) { - Paper { - attrs.classes = jso { root = -themeStyles.noShowLoadedPaper } - CircularProgress {} - icon(NotificationImportant) - typographyH6 { +"Connecting…" } - +"Attempting to connect to Sparkle Motion." - } - } else if (!webClient.serverIsOnline) { - Paper { - attrs.classes = jso { root = -themeStyles.noShowLoadedPaper } - CircularProgress {} - icon(NotificationImportant) - typographyH6 { +"Connecting…" } - +"Sparkle Motion is initializing." - } - } else { - ErrorBoundary { - attrs.FallbackComponent = ErrorDisplay + appToolbar { + attrs.appMode = appMode + attrs.documentManager = documentManager + attrs.onMenuButtonClick = handleAppDrawerToggle + attrs.onAppModeChange = handleAppModeChange + } - when (appMode) { - AppMode.Show -> { - if (!webClient.showManagerIsReady) { - Paper { - attrs.classes = jso { root = -themeStyles.noShowLoadedPaper } - CircularProgress {} - NotificationImportant {} - typographyH6 { +"Connecting…" } - +"Show manager is initializing." - } - } else if (show == null) { - Paper { - attrs.classes = jso { root = -themeStyles.noShowLoadedPaper } - NotificationImportant {} - typographyH6 { +"No open show." } - p { +"Maybe you'd like to open one? " } - } - } else if (props.webClient.isMapping) { - Backdrop { - attrs.open = true - Container { + div(+themeStyles.appContent) { + if (!webClient.isConnected) { + Paper { + attrs.classes = jso { root = -themeStyles.noShowLoadedPaper } + CircularProgress {} + icon(NotificationImportant) + typographyH6 { +"Connecting…" } + +"Attempting to connect to Sparkle Motion." + } + } else if (!webClient.serverIsOnline) { + Paper { + attrs.classes = jso { root = -themeStyles.noShowLoadedPaper } + CircularProgress {} + icon(NotificationImportant) + typographyH6 { +"Connecting…" } + +"Sparkle Motion is initializing." + } + } else { + ErrorBoundary { + attrs.FallbackComponent = ErrorDisplay + + when (appMode) { + AppMode.Show -> { + if (!webClient.showManagerIsReady) { + Paper { + attrs.classes = jso { root = -themeStyles.noShowLoadedPaper } CircularProgress {} - icon(NotificationImportant) - - typographyH6 { +"Mapper Running…" } - +"Please wait." + NotificationImportant {} + typographyH6 { +"Connecting…" } + +"Show manager is initializing." + } + } else if (show == null) { + Paper { + attrs.classes = jso { root = -themeStyles.noShowLoadedPaper } + NotificationImportant {} + typographyH6 { +"No open show." } + p { +"Maybe you'd like to open one? " } + } + } else if (props.webClient.isMapping) { + Backdrop { + attrs.open = true + Container { + CircularProgress {} + icon(NotificationImportant) + + typographyH6 { +"Mapper Running…" } + +"Please wait." + } + } + } else { + showUi { + attrs.show = showManager.openShow!! + attrs.onShowStateChange = handleShowStateChange + attrs.onLayoutEditorDialogToggle = handleLayoutEditorDialogToggle } - } - } else { - showUi { - attrs.show = showManager.openShow!! - attrs.onShowStateChange = handleShowStateChange - attrs.onLayoutEditorDialogToggle = handleLayoutEditorDialogToggle - } - if (layoutEditorDialogOpen) { - // Layout Editor dialog - layoutEditorDialog { - attrs.open = layoutEditorDialogOpen - attrs.show = show - attrs.onApply = handleLayoutEditorChange - attrs.onClose = handleLayoutEditorDialogClose + if (layoutEditorDialogOpen) { + // Layout Editor dialog + layoutEditorDialog { + attrs.open = layoutEditorDialogOpen + attrs.show = show + attrs.onApply = handleLayoutEditorChange + attrs.onClose = handleLayoutEditorDialogClose + } } } - } - if (webClient.sceneProvider.openScene == null) { - Backdrop { - attrs.open = true - attrs.sx { zIndex = 100 as ZIndex; display = Display.grid } - Container { - icon(NotificationImportant) + if (webClient.sceneProvider.openScene == null) { + Backdrop { + attrs.open = true + attrs.sx { zIndex = 100 as ZIndex; display = Display.grid } + Container { + icon(NotificationImportant) - typographyH6 { +"No scene loaded." } - +"Maybe you'd like to open one?" + typographyH6 { +"No scene loaded." } + +"Maybe you'd like to open one?" + } } } } - } - AppMode.Scene -> { - if (props.sceneManager.scene == null) { - Paper { - attrs.classes = jso { root = -themeStyles.noShowLoadedPaper } - icon(NotificationImportant) - typographyH6 { +"No open scene." } - p { +"Maybe you'd like to open one? " } - } - } else { - sceneEditor { - attrs.sceneEditorClient = props.sceneEditorClient - attrs.mapper = props.mapper - attrs.sceneManager = sceneManager + AppMode.Scene -> { + if (props.sceneManager.scene == null) { + Paper { + attrs.classes = jso { root = -themeStyles.noShowLoadedPaper } + icon(NotificationImportant) + typographyH6 { +"No open scene." } + p { +"Maybe you'd like to open one? " } + } + } else { + sceneEditor { + attrs.sceneEditorClient = props.sceneEditorClient + attrs.mapper = props.mapper + attrs.sceneManager = sceneManager + } } } } @@ -328,32 +334,32 @@ val AppIndex = xComponent("AppIndex") { props -> } } } - } - renderDialog?.invoke(this) + renderDialog?.invoke(this) - if (editMode.isAvailable) { - editableManagerUi { - attrs.editableManager = - when (appMode) { - AppMode.Show -> editableManager - AppMode.Scene -> sceneEditableManager - } + if (editMode.isAvailable) { + editableManagerUi { + attrs.editableManager = + when (appMode) { + AppMode.Show -> editableManager + AppMode.Scene -> sceneEditableManager + } + } } - } - prompt?.let { - promptDialog { - attrs.prompt = it - attrs.onClose = handlePromptClose + prompt?.let { + promptDialog { + attrs.prompt = it + attrs.onClose = handlePromptClose + } } - } - notifier { - attrs.notifier = webClient.notifier - } + notifier { + attrs.notifier = webClient.notifier + } - fileDialog {} + fileDialog {} + } } } } diff --git a/src/jsMain/kotlin/baaahs/app/ui/AppToolbar.kt b/src/jsMain/kotlin/baaahs/app/ui/AppToolbar.kt index d55e449df2..fe335bcac0 100644 --- a/src/jsMain/kotlin/baaahs/app/ui/AppToolbar.kt +++ b/src/jsMain/kotlin/baaahs/app/ui/AppToolbar.kt @@ -1,5 +1,6 @@ package baaahs.app.ui +import baaahs.app.ui.dev.devModeToolbarMenu import baaahs.app.ui.editor.SceneEditIntent import baaahs.app.ui.editor.ShowEditIntent import baaahs.client.document.DocumentManager @@ -241,6 +242,10 @@ val AppToolbar = xComponent("AppToolbar") { props -> } } + if (appContext.uiSettings.developerMode) { + devModeToolbarMenu {} + } + help { attrs.divClass = themeStyles.appToolbarHelpIcon.name attrs.inject(HelpText.appToolbar) diff --git a/src/jsMain/kotlin/baaahs/app/ui/PatchEditorAppIndexView.kt b/src/jsMain/kotlin/baaahs/app/ui/PatchEditorAppIndexView.kt index 11a49c4e1a..7882926568 100644 --- a/src/jsMain/kotlin/baaahs/app/ui/PatchEditorAppIndexView.kt +++ b/src/jsMain/kotlin/baaahs/app/ui/PatchEditorAppIndexView.kt @@ -66,7 +66,6 @@ val PatchEditorAppIndexView = xComponent("PatchEditorA val myAppContext = memo(allStyles) { jso { this.plugins = props.plugins - this.toolchain = props.toolchain this.allStyles = allStyles this.clock = JsClock this.showManager = showManager @@ -85,17 +84,21 @@ val PatchEditorAppIndexView = xComponent("PatchEditorA appContext.Provider { attrs.value = myAppContext - appGlContext.Provider { - attrs.value = myAppGlContext + toolchainContext.Provider { + attrs.value = props.toolchain - ThemeProvider { - attrs.theme = theme - CssBaseline {} + appGlContext.Provider { + attrs.value = myAppGlContext - Paper { - div { - editableManagerUi { - attrs.editableManager = editableManager + ThemeProvider { + attrs.theme = theme + CssBaseline {} + + Paper { + div { + editableManagerUi { + attrs.editableManager = editableManager + } } } } diff --git a/src/jsMain/kotlin/baaahs/app/ui/ShaderPreview.kt b/src/jsMain/kotlin/baaahs/app/ui/ShaderPreview.kt index 2baa9e3f74..499c0924ec 100644 --- a/src/jsMain/kotlin/baaahs/app/ui/ShaderPreview.kt +++ b/src/jsMain/kotlin/baaahs/app/ui/ShaderPreview.kt @@ -32,7 +32,8 @@ import styled.inlineStyles val ShaderPreview = xComponent("ShaderPreview") { props -> val appContext = useContext(appContext) val sharedGlContext = if (props.noSharedGlContext == true) null else useContext(appGlContext).sharedGlContext - val toolchain = props.toolchain ?: appContext.toolchain + val toolchain = props.toolchain + ?: run { useContext(toolchainContext) } val canvasParent = ref() var shaderPreview by state { null } diff --git a/src/jsMain/kotlin/baaahs/app/ui/StyleConstants.kt b/src/jsMain/kotlin/baaahs/app/ui/StyleConstants.kt index aa466ee0a3..27069d5463 100644 --- a/src/jsMain/kotlin/baaahs/app/ui/StyleConstants.kt +++ b/src/jsMain/kotlin/baaahs/app/ui/StyleConstants.kt @@ -4,6 +4,7 @@ object StyleConstants { object Layers { val sharedGlCanvas = 5 val aboveSharedGlCanvas = 10 - val floatingWindows = 100 + val muiDialogs = 1200 + val floatingWindows = 1300 } } \ No newline at end of file diff --git a/src/jsMain/kotlin/baaahs/app/ui/Styles.kt b/src/jsMain/kotlin/baaahs/app/ui/Styles.kt index 16f4edc27f..7924c2ef5f 100644 --- a/src/jsMain/kotlin/baaahs/app/ui/Styles.kt +++ b/src/jsMain/kotlin/baaahs/app/ui/Styles.kt @@ -244,6 +244,9 @@ class ThemeStyles(val theme: Theme) : StyleSheet("app-ui-theme", isStatic = true h4 { margin = "unset" } } + val appToolbarDevMenuIcon by css { + transform.translateY(1.em) + } val appToolbarHelpIcon by css { transform.translateY(1.em) } diff --git a/src/jsMain/kotlin/baaahs/app/ui/dev/DevModeToolbarMenuView.kt b/src/jsMain/kotlin/baaahs/app/ui/dev/DevModeToolbarMenuView.kt new file mode 100644 index 0000000000..d29886028f --- /dev/null +++ b/src/jsMain/kotlin/baaahs/app/ui/dev/DevModeToolbarMenuView.kt @@ -0,0 +1,119 @@ +package baaahs.app.ui.dev + +import baaahs.app.ui.CommonIcons +import baaahs.app.ui.appContext +import baaahs.gl.RootToolchain +import baaahs.ui.components.palette +import baaahs.ui.unaryPlus +import baaahs.ui.withMouseEvent +import baaahs.ui.xComponent +import baaahs.window +import kotlinx.html.org.w3c.dom.events.Event +import kotlinx.js.jso +import materialui.icon +import mui.material.* +import org.w3c.dom.Element +import react.Props +import react.RBuilder +import react.RHandler +import react.dom.div +import react.dom.html.TdAlign +import react.dom.onClick +import react.useContext + +private val DevModeToolbarMenuView = xComponent("DevModeToolbarMenu") { props -> + val appContext = useContext(appContext) + val styles = appContext.allStyles.appUi + + var menuAnchor by state { null } + val showMenu by mouseEventHandler { event -> menuAnchor = event.target as Element? } + val hideMenu = callback { _: Event?, _: String? -> menuAnchor = null } + + var showToolchainStats by state { false } + val handleToggleShowToolchainStats by handler { showToolchainStats = !showToolchainStats; menuAnchor = null } + + onMount(showToolchainStats) { + if (showToolchainStats) { + val stats = (appContext.webClient.toolchain as RootToolchain).stats + var lastStats = emptyList() + val callback = window.setInterval({ + val curStats = stats.all.map { it.calls } + if (curStats != lastStats) { + lastStats = curStats + forceRender() + } + }, timeout = 50) + + withCleanup { + window.clearInterval(callback) + } + } + } + + div(+styles.appToolbarDevMenuIcon) { + attrs.onClick = showMenu + icon(CommonIcons.Settings) + } + + if (menuAnchor != null) { + Menu { + attrs.anchorEl = menuAnchor.asDynamic() + attrs.anchorOrigin = jso { + horizontal = "left" + vertical = "bottom" + } + attrs.open = menuAnchor != null + attrs.onClose = hideMenu + + MenuItem { + attrs.onClick = handleToggleShowToolchainStats.withMouseEvent() + Checkbox { attrs.checked = showToolchainStats } + ListItemText { +if (showToolchainStats) "Hide Toolchain Stats" else "Show Toolchain Stats" } + } + } + } + + if (showToolchainStats) { + palette { + attrs.title = "Toolchain Stats" + attrs.initialWidth = window.innerWidth / 4 + attrs.initialHeight = window.innerHeight * 2 / 3 + attrs.onClose = handleToggleShowToolchainStats + + val stats = (appContext.webClient.toolchain as RootToolchain).stats + Table { + TableHead { + TableCell { +"Statistic" } + TableCell { +"Calls" } + TableCell { +"Average" } + TableCell { +"Total" } + } + TableBody { + stats.all.forEach { stat -> + TableRow { + TableCell { +stat.name } + TableCell { + attrs.align = TdAlign.right + +stat.calls.toString() + } + TableCell { + attrs.align = TdAlign.right + +(stat.averageTimeMs?.let { "${it}ms" } ?: "-") + } + TableCell { + attrs.align = TdAlign.right + +"${stat.elapsedTimeMs}ms" + } + } + } + } + } + } + } +} + +external interface DevModeToolbarMenuProps : Props { +} + +fun RBuilder.devModeToolbarMenu(handler: RHandler) = + child(DevModeToolbarMenuView, handler = handler) \ No newline at end of file diff --git a/src/jsMain/kotlin/baaahs/app/ui/editor/PatchEditorView.kt b/src/jsMain/kotlin/baaahs/app/ui/editor/PatchEditorView.kt index f2687a0e08..26743c4677 100644 --- a/src/jsMain/kotlin/baaahs/app/ui/editor/PatchEditorView.kt +++ b/src/jsMain/kotlin/baaahs/app/ui/editor/PatchEditorView.kt @@ -3,6 +3,7 @@ package baaahs.app.ui.editor import baaahs.app.ui.appContext import baaahs.app.ui.shaderDiagnostics import baaahs.app.ui.shaderPreview +import baaahs.app.ui.toolchainContext import baaahs.gl.preview.GadgetAdjuster import baaahs.gl.preview.PreviewShaderBuilder import baaahs.gl.withCache @@ -32,7 +33,8 @@ private val PatchEditorView = xComponent("PatchEditor") { prop val appContext = useContext(appContext) val shaderEditorStyles = appContext.allStyles.shaderEditor - val toolchain = memo { appContext.toolchain.withCache("Editor") } + val baseToolchain = useContext(toolchainContext) + val toolchain = memo(baseToolchain) { baseToolchain.withCache("Editor") } var settingsMenuAnchor by state { null } val showSettingsMenu = callback { event: Event -> settingsMenuAnchor = event.target as Element? } @@ -90,64 +92,68 @@ private val PatchEditorView = xComponent("PatchEditor") { prop settingsMenuAnchor = null } - div(+shaderEditorStyles.container) { - div(+shaderEditorStyles.shaderEditor) { - shaderEditor { - attrs.editingShader = editingShader - } - } + toolchainContext.Provider { + attrs.value = toolchain - div(+shaderEditorStyles.previewContainer) { - shaderPreview { - attrs.shader = editingShader.shaderBuilder.shader - attrs.previewShaderBuilder = editingShader.shaderBuilder - attrs.width = ShaderEditorStyles.previewWidth - attrs.height = ShaderEditorStyles.previewHeight - attrs.adjustGadgets = if (autoAdjustGadgets) { - if (fullRange) GadgetAdjuster.Mode.FULL_RANGE else GadgetAdjuster.Mode.INCREMENTAL - } else null - attrs.toolchain = toolchain + div(+shaderEditorStyles.container) { + div(+shaderEditorStyles.shaderEditor) { + shaderEditor { + attrs.editingShader = editingShader + } } - div(+shaderEditorStyles.settingsMenuAffordance) { - attrs.onClickFunction = showSettingsMenu + div(+shaderEditorStyles.previewContainer) { + shaderPreview { + attrs.shader = editingShader.shaderBuilder.shader + attrs.previewShaderBuilder = editingShader.shaderBuilder + attrs.width = ShaderEditorStyles.previewWidth + attrs.height = ShaderEditorStyles.previewHeight + attrs.adjustGadgets = if (autoAdjustGadgets) { + if (fullRange) GadgetAdjuster.Mode.FULL_RANGE else GadgetAdjuster.Mode.INCREMENTAL + } else null + attrs.toolchain = toolchain + } - icon(mui.icons.material.Settings) - } - } + div(+shaderEditorStyles.settingsMenuAffordance) { + attrs.onClickFunction = showSettingsMenu - div(+shaderEditorStyles.propsTabsAndPanels) { - Tabs { - attrs.classes = jso { this.flexContainer = -shaderEditorStyles.tabsContainer } - attrs.value = selectedTab - attrs.onChange = handleChangeTab.asDynamic() - attrs.orientation = Orientation.horizontal - attrs.variant = TabsVariant.scrollable - PageTabs.values().forEach { tab -> - Tab { - attrs.classes = jso { this.root = -shaderEditorStyles.tab } - attrs.label = buildElement { +tab.name } - attrs.value = tab.asDynamic() - attrs.sx { minWidth = Auto.auto } - } + icon(mui.icons.material.Settings) } } - div(+shaderEditorStyles.propsPanel) { - when (selectedTab) { - PageTabs.Patch -> shaderPropertiesEditor { - attrs.editableManager = props.editableManager - attrs.editingShader = editingShader - attrs.mutablePatch = props.mutablePatch - } - PageTabs.Ports -> linksEditor { - attrs.editableManager = props.editableManager - attrs.editingShader = editingShader - } - PageTabs.Gadgets -> gadgetsPreview { - attrs.editingShader = editingShader + div(+shaderEditorStyles.propsTabsAndPanels) { + Tabs { + attrs.classes = jso { this.flexContainer = -shaderEditorStyles.tabsContainer } + attrs.value = selectedTab + attrs.onChange = handleChangeTab.asDynamic() + attrs.orientation = Orientation.horizontal + attrs.variant = TabsVariant.scrollable + PageTabs.values().forEach { tab -> + Tab { + attrs.classes = jso { this.root = -shaderEditorStyles.tab } + attrs.label = buildElement { +tab.name } + attrs.value = tab.asDynamic() + attrs.sx { minWidth = Auto.auto } + } } - PageTabs.Help -> shaderHelp { + } + + div(+shaderEditorStyles.propsPanel) { + when (selectedTab) { + PageTabs.Patch -> shaderPropertiesEditor { + attrs.editableManager = props.editableManager + attrs.editingShader = editingShader + attrs.mutablePatch = props.mutablePatch + } + PageTabs.Ports -> linksEditor { + attrs.editableManager = props.editableManager + attrs.editingShader = editingShader + } + PageTabs.Gadgets -> gadgetsPreview { + attrs.editingShader = editingShader + } + PageTabs.Help -> shaderHelp { + } } } } diff --git a/src/jsMain/kotlin/baaahs/app/ui/editor/ShaderLibraryDialogView.kt b/src/jsMain/kotlin/baaahs/app/ui/editor/ShaderLibraryDialogView.kt index f4d58aa790..5a33d611b8 100644 --- a/src/jsMain/kotlin/baaahs/app/ui/editor/ShaderLibraryDialogView.kt +++ b/src/jsMain/kotlin/baaahs/app/ui/editor/ShaderLibraryDialogView.kt @@ -2,6 +2,7 @@ package baaahs.app.ui.editor import baaahs.app.ui.appContext import baaahs.app.ui.shaderCard +import baaahs.app.ui.toolchainContext import baaahs.gl.withCache import baaahs.libraries.ShaderLibrary import baaahs.show.Shader @@ -15,11 +16,11 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.css.* +import kotlinx.html.org.w3c.dom.events.Event import kotlinx.js.jso import materialui.icon import mui.icons.material.Search import mui.material.* -import kotlinx.html.org.w3c.dom.events.Event import react.* import react.dom.div import react.dom.events.FocusEvent @@ -29,7 +30,8 @@ import styled.inlineStyles private val ShaderLibraryDialogView = xComponent("ShaderLibraryDialog") { props -> val appContext = useContext(appContext) val shaderLibraries = appContext.webClient.shaderLibraries - val toolchain = appContext.toolchain.withCache("Shader Library") + val baseToolchain = useContext(toolchainContext) + val toolchain = memo(baseToolchain) { baseToolchain.withCache("Shader Library") } val handleClose = callback(props.onSelect) { _: Event, _: String -> props.onSelect(null) } @@ -81,53 +83,57 @@ private val ShaderLibraryDialogView = xComponent("Shad } - Dialog { - attrs.open = true - attrs.fullWidth = true + toolchainContext.Provider { + attrs.value = toolchain + + Dialog { + attrs.open = true + attrs.fullWidth = true // attrs.fullScreen = true - attrs.maxWidth = "xl" - attrs.scroll = DialogScroll.body - attrs.onClose = handleClose + attrs.maxWidth = "xl" + attrs.scroll = DialogScroll.body + attrs.onClose = handleClose - DialogTitle { +"Shader Library" } + DialogTitle { +"Shader Library" } - DialogContent { - FormControl { - TextField { - attrs.autoFocus = true - attrs.fullWidth = true + DialogContent { + FormControl { + TextField { + attrs.autoFocus = true + attrs.fullWidth = true // attrs.label { +props.label } - attrs.InputProps = jso { - endAdornment = buildElement { icon(Search) } - } - attrs.defaultValue = "" + attrs.InputProps = jso { + endAdornment = buildElement { icon(Search) } + } + attrs.defaultValue = "" - attrs.onChange = handleSearchChange - attrs.onBlur = handleSearchBlur + attrs.onChange = handleSearchChange + attrs.onBlur = handleSearchBlur // attrs.onKeyDownFunction = handleSearchKeyDown - attrs.onInput = handleSearchInput - } + attrs.onInput = handleSearchInput + } - FormHelperText { +"Enter stuff to search for!" } - } + FormHelperText { +"Enter stuff to search for!" } + } - Divider {} + Divider {} - sharedGlContext { - div { - inlineStyles { - display = Display.grid - gridTemplateColumns = GridTemplateColumns("repeat(auto-fit, minmax(180px, 1fr))") - gap = 1.em - } + sharedGlContext { + div { + inlineStyles { + display = Display.grid + gridTemplateColumns = GridTemplateColumns("repeat(auto-fit, minmax(180px, 1fr))") + gap = 1.em + } - matches.forEach { match -> - shaderCard { - key = match.id - attrs.mutablePatch = MutablePatch(MutableShader(match.shader)) - attrs.onSelect = handleShaderSelect[match] - attrs.onDelete = null - attrs.toolchain = toolchain + matches.forEach { match -> + shaderCard { + key = match.id + attrs.mutablePatch = MutablePatch(MutableShader(match.shader)) + attrs.onSelect = handleShaderSelect[match] + attrs.onDelete = null + attrs.toolchain = toolchain + } } } } diff --git a/src/jsMain/kotlin/baaahs/app/ui/settings/MainSettingsPanel.kt b/src/jsMain/kotlin/baaahs/app/ui/settings/MainSettingsPanel.kt index e1d2240bac..ef3b3c34bd 100644 --- a/src/jsMain/kotlin/baaahs/app/ui/settings/MainSettingsPanel.kt +++ b/src/jsMain/kotlin/baaahs/app/ui/settings/MainSettingsPanel.kt @@ -3,7 +3,10 @@ package baaahs.app.ui.settings import baaahs.app.settings.UiSettings import baaahs.app.ui.appContext import baaahs.app.ui.dialog.DialogPanel -import baaahs.ui.* +import baaahs.ui.View +import baaahs.ui.renderWrapper +import baaahs.ui.typographyH6 +import baaahs.ui.xComponent import mui.material.* import react.* @@ -24,14 +27,17 @@ private val MainSettingsPanelView = xComponent("MainSett val appContext = useContext(appContext) val uiSettings = appContext.uiSettings - val handleDarkModeChange by handler { - props.changeUiSettings { it.copy(darkMode = !it.darkMode) } + val handleDarkModeChange by switchEventHandler(uiSettings) { _, checked -> + props.changeUiSettings { it.copy(darkMode = checked) } } - val handleRenderButtonPreviewsChange by handler(uiSettings) { - props.changeUiSettings { it.copy(renderButtonPreviews = !it.renderButtonPreviews) } + val handleRenderButtonPreviewsChange by switchEventHandler(uiSettings) { _, checked -> + props.changeUiSettings { it.copy(renderButtonPreviews = checked) } } - val handleUseSharedContextsChange by handler(uiSettings) { - props.changeUiSettings { it.copy(useSharedContexts = !it.useSharedContexts) } + val handleUseSharedContextsChange by switchEventHandler(uiSettings) { _, checked -> + props.changeUiSettings { it.copy(useSharedContexts = checked) } + } + val handleDeveloperModeChange by switchEventHandler(uiSettings) { _, checked -> + props.changeUiSettings { it.copy(developerMode = checked) } } @@ -41,7 +47,7 @@ private val MainSettingsPanelView = xComponent("MainSett attrs.control = buildElement { Switch { attrs.checked = uiSettings.darkMode - attrs.onChange = handleDarkModeChange.withTChangeEvent() + attrs.onChange = handleDarkModeChange } } attrs.label = buildElement { typographyH6 { +"Dark Mode" } } @@ -57,7 +63,7 @@ private val MainSettingsPanelView = xComponent("MainSett attrs.control = buildElement { Switch { attrs.checked = uiSettings.renderButtonPreviews - attrs.onChange = handleRenderButtonPreviewsChange.withTChangeEvent() + attrs.onChange = handleRenderButtonPreviewsChange } } attrs.label = buildElement { typographyH6 { +"Render Button Previews" } } @@ -72,11 +78,29 @@ private val MainSettingsPanelView = xComponent("MainSett FormControlLabel { attrs.control = buildElement { Switch { - attrs.checked = uiSettings.useSharedContexts - attrs.onChange = handleUseSharedContextsChange.withTChangeEvent() + attrs.checked = uiSettings.developerMode + attrs.onChange = handleDeveloperModeChange + } + } + attrs.label = buildElement { typographyH6 { +"Developer Mode" } } + } + } + } + + if (uiSettings.developerMode) { + Divider {} + + List { + ListItem { + FormControlLabel { + attrs.control = buildElement { + Switch { + attrs.checked = uiSettings.useSharedContexts + attrs.onChange = handleUseSharedContextsChange + } } + attrs.label = buildElement { typographyH6 { +"Use Shared Contexts" } } } - attrs.label = buildElement { typographyH6 { +"Use Shared Contexts" } } } } } diff --git a/src/jsMain/kotlin/baaahs/gl/preview/MovingHeadPreview.kt b/src/jsMain/kotlin/baaahs/gl/preview/MovingHeadPreview.kt index 8bfc78d1c6..aebdf4114e 100644 --- a/src/jsMain/kotlin/baaahs/gl/preview/MovingHeadPreview.kt +++ b/src/jsMain/kotlin/baaahs/gl/preview/MovingHeadPreview.kt @@ -9,6 +9,7 @@ import baaahs.fixtures.ProgramRenderPlan import baaahs.gl.GlContext import baaahs.gl.glsl.GlslProgram import baaahs.gl.render.ModelRenderEngine +import baaahs.gl.render.pickResultDeliveryStrategy import baaahs.model.Model import baaahs.model.MovingHead import baaahs.plugin.core.MovingHeadParams @@ -36,7 +37,9 @@ class MovingHeadPreview( ) : ShaderPreview { private var running = false private val fixtureType = MovingHeadDevice - override val renderEngine = ModelRenderEngine(gl, fixtureType) + override val renderEngine = ModelRenderEngine( + gl, fixtureType, resultDeliveryStrategy = gl.pickResultDeliveryStrategy() + ) private var movingHeadProgram: GlslProgram? = null private val renderTargets = model.allEntities .filterIsInstance() diff --git a/src/jsMain/kotlin/baaahs/gl/preview/ProjectionPreview.kt b/src/jsMain/kotlin/baaahs/gl/preview/ProjectionPreview.kt index da84d60a98..cc4cdda9f1 100644 --- a/src/jsMain/kotlin/baaahs/gl/preview/ProjectionPreview.kt +++ b/src/jsMain/kotlin/baaahs/gl/preview/ProjectionPreview.kt @@ -11,6 +11,7 @@ import baaahs.gl.GlContext import baaahs.gl.glsl.GlslProgram import baaahs.gl.render.FixtureRenderTarget import baaahs.gl.render.ModelRenderEngine +import baaahs.gl.render.pickResultDeliveryStrategy import baaahs.gl.result.Vec2ResultType import baaahs.model.Model import baaahs.model.PixelArray @@ -32,7 +33,9 @@ class ProjectionPreview( ) : ShaderPreview { private var running = false private val fixtureType = ProjectionPreviewDevice - override val renderEngine = ModelRenderEngine(gl, fixtureType) + override val renderEngine = ModelRenderEngine( + gl, fixtureType, resultDeliveryStrategy = gl.pickResultDeliveryStrategy() + ) private var projectionProgram: GlslProgram? = null private val renderTargets: Map diff --git a/src/jsMain/kotlin/baaahs/mapper/MapperAppView.kt b/src/jsMain/kotlin/baaahs/mapper/MapperAppView.kt index 6c17bce06f..e20a4e212d 100644 --- a/src/jsMain/kotlin/baaahs/mapper/MapperAppView.kt +++ b/src/jsMain/kotlin/baaahs/mapper/MapperAppView.kt @@ -21,8 +21,11 @@ import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLImageElement import org.w3c.dom.events.KeyboardEvent import react.* -import react.dom.* +import react.dom.canvas +import react.dom.div import react.dom.html.InputType +import react.dom.i +import react.dom.img import styled.inlineStyles val MapperAppView = xComponent("baaahs.mapper.MapperAppView") { props -> @@ -110,11 +113,10 @@ val MapperAppView = xComponent("baaahs.mapper.MapperAppView" attrs.tabIndex = "-1" // So we can receive key events. palette { + attrs.title = "Mapping Tools" attrs.initialWidth = 240 attrs.initialHeight = 500 - header { +"Mapping Tools" } - div(+styles.controls) { FormControlLabel { attrs.control = buildElement { diff --git a/src/jsMain/kotlin/baaahs/ui/components/PaletteView.kt b/src/jsMain/kotlin/baaahs/ui/components/PaletteView.kt index 2aa6954c5d..a64502cc54 100644 --- a/src/jsMain/kotlin/baaahs/ui/components/PaletteView.kt +++ b/src/jsMain/kotlin/baaahs/ui/components/PaletteView.kt @@ -22,6 +22,7 @@ import react.PropsWithChildren import react.RBuilder import react.RHandler import react.dom.div +import react.dom.header import react.dom.onClick import react.useContext import styled.inlineStyles @@ -86,6 +87,10 @@ private val PaletteView = xComponent("Palette") { props -> attrs.classes = jso { root = -styles.paper } attrs.elevation = 3 + props.title?.let { + header { +it } + } + props.children() } } @@ -103,6 +108,7 @@ private fun getUiComponentStyles(): UiComponentStyles { } external interface PaletteProps : PropsWithChildren { + var title: String? var initialWidth: Int? var initialHeight: Int? var disablePortal: Boolean? diff --git a/src/jsMain/kotlin/baaahs/ui/diagnostics/DmxDiagnosticsPaletteView.kt b/src/jsMain/kotlin/baaahs/ui/diagnostics/DmxDiagnosticsPaletteView.kt index 4c50cbe7dd..1a6247d173 100644 --- a/src/jsMain/kotlin/baaahs/ui/diagnostics/DmxDiagnosticsPaletteView.kt +++ b/src/jsMain/kotlin/baaahs/ui/diagnostics/DmxDiagnosticsPaletteView.kt @@ -3,7 +3,6 @@ package baaahs.ui.diagnostics import baaahs.app.ui.appContext import baaahs.dmx.DmxUniverseListener import baaahs.ui.components.palette -import baaahs.ui.typographyH6 import baaahs.ui.withMouseEvent import baaahs.ui.xComponent import baaahs.util.globalLaunch @@ -32,12 +31,11 @@ val DmxDiagnosticsView = xComponent("DmxDiagnostics") { pro } palette { + attrs.title = "Dmx Diagnostics" attrs.initialWidth = window.innerWidth / 3 attrs.initialHeight = window.innerHeight * 2 / 3 attrs.onClose = props.onClose - typographyH6 { +"Dmx Diagnostics" } - button { attrs.onClick = handleUpdateChannels.withMouseEvent() +"Update" diff --git a/src/jsMain/kotlin/baaahs/ui/diagnostics/PatchDiagnosticsPaletteView.kt b/src/jsMain/kotlin/baaahs/ui/diagnostics/PatchDiagnosticsPaletteView.kt index b82a6ef47b..c5d3adb3ab 100644 --- a/src/jsMain/kotlin/baaahs/ui/diagnostics/PatchDiagnosticsPaletteView.kt +++ b/src/jsMain/kotlin/baaahs/ui/diagnostics/PatchDiagnosticsPaletteView.kt @@ -7,7 +7,6 @@ import baaahs.fixtures.RenderPlan import baaahs.gl.glsl.GlslProgramImpl import baaahs.ui.asTextNode import baaahs.ui.components.palette -import baaahs.ui.typographyH6 import baaahs.ui.xComponent import baaahs.util.Monitor import baaahs.window @@ -37,12 +36,11 @@ val PatchDiagnosticsView = xComponent("PatchDiagnostics") } palette { + attrs.title = "Patch Diagnostics" attrs.initialWidth = window.innerWidth / 3 attrs.initialHeight = window.innerHeight * 2 / 3 attrs.onClose = props.onClose - typographyH6 { +"Patch Diagnostics" } - if (renderPlans == null) { i { +"No patch!" } } else { diff --git a/src/jsMain/kotlin/baaahs/ui/gridlayout/GridLayout.kt b/src/jsMain/kotlin/baaahs/ui/gridlayout/GridLayout.kt index 19d82cf323..06e3b771be 100644 --- a/src/jsMain/kotlin/baaahs/ui/gridlayout/GridLayout.kt +++ b/src/jsMain/kotlin/baaahs/ui/gridlayout/GridLayout.kt @@ -287,6 +287,11 @@ class GridLayout( } } + /** + * @param i Item id. + * @param w Item width in columns. + * @param h Item height in rows. + */ private fun onResizeStart(i: String, w: Int, h: Int, gridResizeEvent: GridResizeEvent) { val e = gridResizeEvent.e val node = gridResizeEvent.node @@ -304,6 +309,11 @@ class GridLayout( props.onResizeStart(layout, l, l, null, e, node) } + /** + * @param i Item id. + * @param w Item width in columns. + * @param h Item height in rows. + */ private fun onResize(i: String, w: Int, h: Int, gridResizeEvent: GridResizeEvent) { val e = gridResizeEvent.e val node = gridResizeEvent.node @@ -327,9 +337,11 @@ class GridLayout( } } - private fun placeholderLayoutItem(l: LayoutItem) = - LayoutItem(l.x, l.y, l.w, l.h, l.i, isPlaceholder = true) - + /** + * @param i Item id. + * @param w Item width in columns. + * @param h Item height in rows. + */ private fun onResizeStop(i: String, w: Int, h: Int, gridResizeEvent: GridResizeEvent) { val e = gridResizeEvent.e val node = gridResizeEvent.node @@ -393,12 +405,15 @@ class GridLayout( } } + private fun placeholderLayoutItem(l: LayoutItem) = + LayoutItem(l.x, l.y, l.w, l.h, l.i, isPlaceholder = true) + /** * Given a grid item, set its style attributes & surround in a . * @param {Element} child React element. * @return {Element} Element wrapped in draggable and properly placed. */ - fun processGridItem( + private fun processGridItem( child: ReactElement<*>?, isDroppingItem: Boolean = false ): ReactElement<*>? {