Skip to content

Commit

Permalink
implement multiple windows/connections
Browse files Browse the repository at this point in the history
  • Loading branch information
sproctor committed Nov 17, 2024
1 parent 9667327 commit 2ec7995
Show file tree
Hide file tree
Showing 17 changed files with 196 additions and 162 deletions.
2 changes: 2 additions & 0 deletions app/src/main/kotlin/warlockfe/warlock3/app/AppMenuBar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ fun FrameWindowScope.AppMenuBar(
windowRepository: WindowRepository,
scriptEngineRegistry: ScriptManager,
runScript: (File) -> Unit,
newWindow: () -> Unit,
showSettings: () -> Unit,
disconnect: (() -> Unit)?,
warlockVersion: String,
Expand All @@ -33,6 +34,7 @@ fun FrameWindowScope.AppMenuBar(
var scriptDirectory by remember { mutableStateOf<String?>(null) }
MenuBar {
Menu("File") {
Item("New window", onClick = newWindow)
Item("Settings", onClick = showSettings)
if (characterId != null) {
Item(
Expand Down
82 changes: 65 additions & 17 deletions app/src/main/kotlin/warlockfe/warlock3/app/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package warlockfe.warlock3.app

import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
Expand All @@ -15,13 +17,18 @@ import ca.gosyer.appdirs.AppDirs
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import org.slf4j.simple.SimpleLogger.DEFAULT_LOG_LEVEL_KEY
import warlockfe.warlock3.app.di.JvmAppContainer
import warlockfe.warlock3.compose.model.GameScreen
import warlockfe.warlock3.compose.model.GameState
import warlockfe.warlock3.compose.ui.window.StreamRegistryImpl
import warlockfe.warlock3.compose.util.LocalLogger
import warlockfe.warlock3.compose.util.insertDefaultMacrosIfNeeded
import warlockfe.warlock3.core.prefs.WindowRepository
import warlockfe.warlock3.core.prefs.sql.Database
import warlockfe.warlock3.core.sge.SimuGameCredentials
import warlockfe.warlock3.core.util.WarlockDirs
Expand Down Expand Up @@ -101,29 +108,61 @@ fun main(args: Array<String>) {
val clientSettings = appContainer.clientSettings
val initialWidth = runBlocking { clientSettings.getWidth() } ?: 640
val initialHeight = runBlocking { clientSettings.getHeight() } ?: 480
val windowState = WindowState(width = initialWidth.dp, height = initialHeight.dp)

val games = mutableStateListOf<GameState>(
GameState(
windowRepository = WindowRepository(database.windowSettingsQueries, Dispatchers.IO),
streamRegistry = StreamRegistryImpl()
).apply {
if (credentials != null) {
val client = appContainer.warlockClientFactory.createStormFrontClient(
credentials,
windowRepository,
streamRegistry
)
client.connect()
val viewModel = appContainer.gameViewModelFactory.create(client, windowRepository, streamRegistry)
screen = GameScreen.ConnectedGameState(viewModel)
}
}
)
application {
CompositionLocalProvider(
LocalLogger provides logger
) {
Window(
title = "Warlock 3",
state = windowState,
icon = appIcon,
onCloseRequest = ::exitApplication,
) {
WarlockApp(
appContainer = appContainer,
credentials = credentials,
)
LaunchedEffect(windowState) {
snapshotFlow { windowState.size }
.onEach { size ->
clientSettings.putWidth(size.width.value.roundToInt())
clientSettings.putHeight(size.height.value.roundToInt())
games.forEachIndexed { index, gameState ->
val windowState = remember { WindowState(width = initialWidth.dp, height = initialHeight.dp) }
Window(
title = "Warlock 3 - ${gameState.getTitle()}",
state = windowState,
icon = appIcon,
onCloseRequest = {
games.removeAt(index)
if (games.isEmpty()) {
exitApplication()
}
.launchIn(this)
},
) {
WarlockApp(
appContainer = appContainer,
gameState = gameState,
newWindow = {
games.add(
GameState(
windowRepository = WindowRepository(database.windowSettingsQueries, Dispatchers.IO),
streamRegistry = StreamRegistryImpl()
)
)
},
)
LaunchedEffect(windowState) {
snapshotFlow { windowState.size }
.onEach { size ->
clientSettings.putWidth(size.width.value.roundToInt())
clientSettings.putHeight(size.height.value.roundToInt())
}
.launchIn(this)
}
}
}
}
Expand All @@ -144,3 +183,12 @@ private val appIcon: Painter? by lazy {
null
}
}

private fun GameState.getTitle(): String {
return when (val screen = this.screen) {
GameScreen.Dashboard -> "Dashboard"
is GameScreen.ConnectedGameState -> screen.viewModel.properties.value["character"] ?: "N/A"
is GameScreen.NewGameState -> "New game"
is GameScreen.ErrorState -> "Error"
}
}
36 changes: 11 additions & 25 deletions app/src/main/kotlin/warlockfe/warlock3/app/WarlockApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.window.FrameWindowScope
import warlockfe.warlock3.compose.AppContainer
import warlockfe.warlock3.compose.MainScreen
import warlockfe.warlock3.compose.model.GameScreen
import warlockfe.warlock3.compose.model.GameState
import warlockfe.warlock3.compose.ui.theme.AppTheme
import warlockfe.warlock3.core.client.GameCharacter
import warlockfe.warlock3.core.sge.SimuGameCredentials

@Composable
fun FrameWindowScope.WarlockApp(
appContainer: AppContainer,
credentials: SimuGameCredentials?,
gameState: GameState,
newWindow: () -> Unit,
) {
var showSettings by remember { mutableStateOf(false) }

Expand All @@ -32,34 +32,21 @@ fun FrameWindowScope.WarlockApp(
unhoverColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.12f)
)
) {
val clipboardManager = LocalClipboardManager.current
val gameState = remember {
val initialGameState = if (credentials != null) {
val client =
appContainer.warlockClientFactory.createStormFrontClient(credentials)
client.connect()
val viewModel =
appContainer.gameViewModelFactory.create(client, clipboardManager)
GameState.ConnectedGameState(viewModel)
} else {
GameState.Dashboard
}
mutableStateOf(initialGameState)
}
val characterId = when (val currentState = gameState.value) {
is GameState.ConnectedGameState -> currentState.viewModel.client.characterId.collectAsState().value
val characterId = when (val screen = gameState.screen) {
is GameScreen.ConnectedGameState -> screen.viewModel.client.characterId.collectAsState().value
else -> null
}
AppMenuBar(
characterId = characterId,
windowRepository = appContainer.windowRepository,
windowRepository = gameState.windowRepository,
scriptEngineRegistry = appContainer.scriptManager,
newWindow = newWindow,
showSettings = { showSettings = true },
disconnect = null,
runScript = {
val currentGameState = gameState.value
if (currentGameState is GameState.ConnectedGameState) {
currentGameState.viewModel.runScript(it)
val screen = gameState.screen
if (screen is GameScreen.ConnectedGameState) {
screen.viewModel.runScript(it)
}
},
warlockVersion = System.getProperty("app.version", "development")
Expand All @@ -70,8 +57,7 @@ fun FrameWindowScope.WarlockApp(
MainScreen(
sgeViewModelFactory = appContainer.sgeViewModelFactory,
dashboardViewModelFactory = appContainer.dashboardViewModelFactory,
gameState = gameState.value,
updateGameState = { gameState.value = it },
gameState = gameState,
updateCurrentCharacter = { currentCharacter = it }
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import warlockfe.warlock3.compose.AppContainer
import warlockfe.warlock3.compose.resources.MR
import warlockfe.warlock3.core.client.WarlockClient
import warlockfe.warlock3.core.client.WarlockClientFactory
import warlockfe.warlock3.core.prefs.WindowRepository
import warlockfe.warlock3.core.prefs.sql.Database
import warlockfe.warlock3.core.script.ScriptManager
import warlockfe.warlock3.core.sge.SgeClient
import warlockfe.warlock3.core.sge.SgeClientFactory
import warlockfe.warlock3.core.sge.SimuGameCredentials
import warlockfe.warlock3.core.util.WarlockDirs
import warlockfe.warlock3.core.window.StreamRegistry
import warlockfe.warlock3.scripting.WarlockScriptEngineRegistry
import warlockfe.warlock3.stormfront.network.SgeClientImpl
import warlockfe.warlock3.stormfront.network.StormfrontClient
Expand Down Expand Up @@ -39,7 +41,11 @@ class JvmAppContainer(
}
override val warlockClientFactory: WarlockClientFactory =
object : WarlockClientFactory {
override fun createStormFrontClient(credentials: SimuGameCredentials): WarlockClient {
override fun createStormFrontClient(
credentials: SimuGameCredentials,
windowRepository: WindowRepository,
streamRegistry: StreamRegistry,
): WarlockClient {
return StormfrontClient(
host = credentials.host,
port = credentials.port,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import warlockfe.warlock3.compose.components.CompassTheme
import warlockfe.warlock3.compose.ui.dashboard.DashboardViewModelFactory
import warlockfe.warlock3.compose.ui.game.GameViewModelFactory
import warlockfe.warlock3.compose.ui.sge.SgeViewModelFactory
import warlockfe.warlock3.compose.ui.window.StreamRegistryImpl
import warlockfe.warlock3.compose.util.loadCompassTheme
import warlockfe.warlock3.core.client.WarlockClientFactory
import warlockfe.warlock3.core.prefs.AccountRepository
Expand All @@ -19,7 +18,6 @@ import warlockfe.warlock3.core.prefs.MacroRepository
import warlockfe.warlock3.core.prefs.PresetRepository
import warlockfe.warlock3.core.prefs.ScriptDirRepository
import warlockfe.warlock3.core.prefs.VariableRepository
import warlockfe.warlock3.core.prefs.WindowRepository
import warlockfe.warlock3.core.prefs.sql.Database
import warlockfe.warlock3.core.script.ScriptManager
import warlockfe.warlock3.core.sge.SgeClientFactory
Expand Down Expand Up @@ -54,11 +52,6 @@ abstract class AppContainer(
database.clientSettingQueries,
ioDispatcher
)
val windowRepository =
WindowRepository(
database.windowSettingsQueries,
ioDispatcher
)
val scriptDirRepository =
ScriptDirRepository(
scriptDirQueries = database.scriptDirQueries,
Expand All @@ -70,7 +63,6 @@ abstract class AppContainer(
characterSettingsQueries = database.characterSettingQueries,
ioDispatcher = ioDispatcher
)
val streamRegistry = StreamRegistryImpl()
val aliasRepository =
AliasRepository(
database.aliasQueries,
Expand All @@ -88,7 +80,6 @@ abstract class AppContainer(
abstract val scriptManager: ScriptManager
val gameViewModelFactory by lazy {
GameViewModelFactory(
windowRepository = windowRepository,
macroRepository = macroRepository,
variableRepository = variableRepository,
scriptManager = scriptManager,
Expand All @@ -97,7 +88,6 @@ abstract class AppContainer(
presetRepository = presetRepository,
characterSettingsRepository = characterSettingsRepository,
aliasRepository = aliasRepository,
streamRegistry = streamRegistry,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager
import warlockfe.warlock3.compose.model.GameScreen
import warlockfe.warlock3.compose.model.GameState
import warlockfe.warlock3.compose.ui.dashboard.DashboardView
import warlockfe.warlock3.compose.ui.dashboard.DashboardViewModelFactory
Expand All @@ -24,56 +24,47 @@ fun MainScreen(
sgeViewModelFactory: SgeViewModelFactory,
dashboardViewModelFactory: DashboardViewModelFactory,
gameState: GameState,
updateGameState: (GameState) -> Unit,
updateCurrentCharacter: (GameCharacter?) -> Unit,
) {

when (gameState) {
GameState.Dashboard -> {
val clipboardManager = LocalClipboardManager.current
when (val screen = gameState.screen) {
GameScreen.Dashboard -> {
val viewModel = remember {
dashboardViewModelFactory.create(
updateGameState = updateGameState,
clipboardManager = clipboardManager,
)
dashboardViewModelFactory.create(gameState)
}
DashboardView(
viewModel = viewModel,
connectToSGE = { updateGameState(GameState.NewGameState) }
connectToSGE = { gameState.screen = GameScreen.NewGameState }
)
}

GameState.NewGameState -> {
val clipboardManager = LocalClipboardManager.current
GameScreen.NewGameState -> {
val viewModel = remember {
sgeViewModelFactory.create(
clipboardManager = clipboardManager,
updateGameState = updateGameState
)
sgeViewModelFactory.create(gameState)
}
SgeWizard(viewModel = viewModel, onCancel = { updateGameState(GameState.Dashboard) })
SgeWizard(viewModel = viewModel, onCancel = { gameState.screen = GameScreen.Dashboard })
}

is GameState.ConnectedGameState -> {
val character = gameState.viewModel.character.collectAsState(null).value
is GameScreen.ConnectedGameState -> {
val character = screen.viewModel.character.collectAsState(null).value
updateCurrentCharacter(character)
GameView(
viewModel = gameState.viewModel,
viewModel = screen.viewModel,
navigateToDashboard = {
updateGameState(GameState.Dashboard)
gameState.screen = GameScreen.Dashboard
}
)
}

is GameState.ErrorState -> {
is GameScreen.ErrorState -> {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(text = gameState.message)
Text(text = screen.message)
Button(
onClick = { updateGameState(GameState.NewGameState) }
onClick = { gameState.screen = GameScreen.NewGameState }
) {
Text("OK")
}
Expand Down
Loading

0 comments on commit 2ec7995

Please sign in to comment.