Skip to content

Commit 08d10f6

Browse files
committed
feat(日志输出): 新增工具栏:清除、自动滚动、滚动到底部
1 parent a2b99ec commit 08d10f6

5 files changed

Lines changed: 206 additions & 25 deletions

File tree

ZalithLauncher/src/main/java/com/movtery/zalithlauncher/ui/screens/game/GameScreen.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,9 @@ fun GameScreen(
696696

697697
LogBox(
698698
enableLog = !viewModel.isEditingLayout && logState.value,
699+
onClose = {
700+
onLogStateChange(LogState.CLOSE)
701+
},
699702
modifier = Modifier.fillMaxSize()
700703
)
701704

ZalithLauncher/src/main/java/com/movtery/zalithlauncher/ui/screens/game/JVMScreen.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ fun JVMScreen(
8989

9090
LogBox(
9191
enableLog = logState.value,
92+
onClose = {
93+
onLogStateChange(LogState.CLOSE)
94+
},
9295
modifier = Modifier.fillMaxSize()
9396
)
9497

ZalithLauncher/src/main/java/com/movtery/zalithlauncher/ui/screens/game/elements/LogBox.kt

Lines changed: 152 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,40 @@
1818

1919
package com.movtery.zalithlauncher.ui.screens.game.elements
2020

21+
import androidx.compose.foundation.layout.Arrangement
22+
import androidx.compose.foundation.layout.Column
23+
import androidx.compose.foundation.layout.Row
24+
import androidx.compose.foundation.layout.fillMaxHeight
2125
import androidx.compose.foundation.layout.fillMaxSize
26+
import androidx.compose.foundation.layout.padding
27+
import androidx.compose.foundation.layout.size
2228
import androidx.compose.foundation.lazy.LazyColumn
2329
import androidx.compose.foundation.lazy.items
2430
import androidx.compose.foundation.lazy.rememberLazyListState
31+
import androidx.compose.foundation.rememberScrollState
32+
import androidx.compose.foundation.verticalScroll
33+
import androidx.compose.material3.Icon
34+
import androidx.compose.material3.IconButton
35+
import androidx.compose.material3.IconButtonDefaults
36+
import androidx.compose.material3.MaterialTheme
2537
import androidx.compose.material3.Surface
2638
import androidx.compose.material3.Text
39+
import androidx.compose.material3.VerticalDivider
2740
import androidx.compose.runtime.Composable
2841
import androidx.compose.runtime.LaunchedEffect
42+
import androidx.compose.runtime.getValue
2943
import androidx.compose.runtime.mutableStateListOf
44+
import androidx.compose.runtime.mutableStateOf
3045
import androidx.compose.runtime.remember
46+
import androidx.compose.runtime.setValue
3147
import androidx.compose.ui.Modifier
3248
import androidx.compose.ui.graphics.Color
49+
import androidx.compose.ui.res.painterResource
3350
import androidx.compose.ui.text.AnnotatedString
3451
import androidx.compose.ui.unit.TextUnit
3552
import androidx.compose.ui.unit.TextUnitType
53+
import androidx.compose.ui.unit.dp
54+
import com.movtery.zalithlauncher.R
3655
import com.movtery.zalithlauncher.bridge.LoggerBridge
3756
import com.movtery.zalithlauncher.setting.AllSettings
3857
import com.movtery.zalithlauncher.ui.screens.game.elements.log_parser.LogHighlighter
@@ -52,14 +71,17 @@ import java.util.Collections
5271
@Composable
5372
fun LogBox(
5473
enableLog: Boolean,
74+
onClose: () -> Unit,
5575
modifier: Modifier = Modifier
5676
) {
5777
val scrollState = rememberLazyListState()
5878

5979
val logList = remember { mutableStateListOf<AnnotatedString>() }
6080
val buffer = remember { Collections.synchronizedList(mutableListOf<AnnotatedString>()) }
81+
val scrollChannel = remember { mutableStateOf<Channel<Unit>?>(null) }
6182

6283
val logHighlighter = remember { LogHighlighter() }
84+
var autoScrollDown by remember { mutableStateOf(true) }
6385

6486
val config = remember {
6587
object {
@@ -70,7 +92,7 @@ fun LogBox(
7092

7193
LaunchedEffect(enableLog) {
7294
if (enableLog) {
73-
val scrollChannel = Channel<Unit>(capacity = 100)
95+
scrollChannel.value = Channel(capacity = 100)
7496

7597
LoggerBridge.setListener { log ->
7698
synchronized(buffer) {
@@ -98,8 +120,10 @@ fun LogBox(
98120
if (pending.isNotEmpty()) {
99121
withContext(Dispatchers.Main) {
100122
logList.addAll(pending)
101-
//尝试进行滚动
102-
scrollChannel.trySend(Unit)
123+
if (autoScrollDown) {
124+
//尝试进行滚动
125+
scrollChannel.value?.trySend(Unit)
126+
}
103127
}
104128
}
105129
} catch (_: CancellationException) {
@@ -110,7 +134,7 @@ fun LogBox(
110134

111135
//自动滚动部分
112136
launch(Dispatchers.Main) {
113-
scrollChannel.consumeAsFlow().collect {
137+
scrollChannel.value?.consumeAsFlow()?.collect {
114138
runCatching {
115139
val targetIndex = logList.lastIndex
116140
if (targetIndex >= 0 && targetIndex < logList.size) {
@@ -120,40 +144,143 @@ fun LogBox(
120144
}
121145
}
122146
} else {
147+
scrollChannel.value = null
123148
LoggerBridge.setListener(null)
124149
logList.clear()
125150
buffer.clear()
126151
}
127152
}
128153

129154
if (enableLog) {
130-
Surface(
131-
modifier = modifier.fillMaxSize(),
132-
color = Color.Black.copy(alpha = 0.5f),
133-
contentColor = Color.White
155+
Row(
156+
modifier = modifier.fillMaxSize()
134157
) {
135-
LazyColumn(
136-
modifier = Modifier.fillMaxSize(),
137-
state = scrollState
158+
Surface(
159+
modifier = modifier
160+
.weight(1f)
161+
.fillMaxHeight(),
162+
color = Color.Black.copy(alpha = 0.5f),
163+
contentColor = Color.White
138164
) {
139-
items(logList) { log ->
140-
val textSize = AllSettings.logTextSize.state
141-
val fontSize = remember(textSize) {
142-
TextUnit(textSize.toFloat(), TextUnitType.Sp)
143-
}
144-
val lineHeight = remember(textSize) {
145-
val height = textSize.toFloat() * 1.1f
146-
TextUnit(height, TextUnitType.Sp)
165+
LazyColumn(
166+
modifier = Modifier.fillMaxSize(),
167+
state = scrollState
168+
) {
169+
items(logList) { log ->
170+
val textSize = AllSettings.logTextSize.state
171+
val fontSize = remember(textSize) {
172+
TextUnit(textSize.toFloat(), TextUnitType.Sp)
173+
}
174+
val lineHeight = remember(textSize) {
175+
val height = textSize.toFloat() * 1.1f
176+
TextUnit(height, TextUnitType.Sp)
177+
}
178+
179+
Text(
180+
text = log,
181+
modifier = Modifier.fillParentMaxWidth(),
182+
fontSize = fontSize,
183+
lineHeight = lineHeight
184+
)
147185
}
186+
}
187+
}
148188

149-
Text(
150-
text = log,
151-
modifier = Modifier.fillParentMaxWidth(),
152-
fontSize = fontSize,
153-
lineHeight = lineHeight
154-
)
189+
//右侧控制区域
190+
VerticalDivider(
191+
modifier = Modifier.fillMaxHeight(),
192+
color = Color.White.copy(0.4f)
193+
)
194+
Surface(
195+
modifier = Modifier.fillMaxHeight(),
196+
color = Color.Black.copy(alpha = 0.7f),
197+
contentColor = Color.White
198+
) {
199+
Column(
200+
modifier = Modifier
201+
.padding(horizontal = 4.dp, vertical = 12.dp)
202+
.verticalScroll(rememberScrollState()),
203+
verticalArrangement = Arrangement.spacedBy(4.dp)
204+
) {
205+
//关闭
206+
LogBoxIconButton(
207+
onClick = onClose,
208+
toggle = false
209+
) {
210+
Icon(
211+
modifier = Modifier.size(18.dp),
212+
painter = painterResource(R.drawable.ic_close),
213+
contentDescription = null
214+
)
215+
}
216+
//清理
217+
LogBoxIconButton(
218+
onClick = {
219+
synchronized(buffer) {
220+
logList.clear()
221+
buffer.clear()
222+
}
223+
},
224+
toggle = false
225+
) {
226+
Icon(
227+
modifier = Modifier.size(18.dp),
228+
painter = painterResource(R.drawable.ic_delete_outlined),
229+
contentDescription = null
230+
)
231+
}
232+
//自动滚动
233+
LogBoxIconButton(
234+
onClick = {
235+
val value = !autoScrollDown
236+
autoScrollDown = value
237+
if (value) {
238+
scrollChannel.value?.trySend(Unit)
239+
}
240+
},
241+
toggle = autoScrollDown,
242+
) {
243+
Icon(
244+
modifier = Modifier.size(18.dp),
245+
painter = painterResource(R.drawable.ic_list_down),
246+
contentDescription = null
247+
)
248+
}
249+
//滚动到底部
250+
LogBoxIconButton(
251+
onClick = {
252+
scrollChannel.value?.trySend(Unit)
253+
},
254+
toggle = false
255+
) {
256+
Icon(
257+
modifier = Modifier.size(18.dp),
258+
painter = painterResource(R.drawable.ic_arrow_cool_down),
259+
contentDescription = null
260+
)
261+
}
155262
}
156263
}
157264
}
158265
}
266+
}
267+
268+
@Composable
269+
private fun LogBoxIconButton(
270+
onClick: () -> Unit,
271+
toggle: Boolean,
272+
icon: @Composable (() -> Unit)
273+
) {
274+
IconButton(
275+
onClick = onClick,
276+
colors = IconButtonDefaults.iconButtonColors(
277+
containerColor = if (toggle) {
278+
Color.White.copy(0.4f)
279+
} else {
280+
Color.Transparent
281+
}
282+
),
283+
shape = MaterialTheme.shapes.medium,
284+
content = icon
285+
)
159286
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!--
2+
~ Copyright (C) 2026 The Android Open Source Project
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
-->
16+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
17+
android:width="24dp"
18+
android:height="24dp"
19+
android:viewportWidth="960"
20+
android:viewportHeight="960">
21+
<path
22+
android:pathData="M480,880 L200,600l56,-57 184,184v-287h80v287l184,-183 56,56L480,880ZM440,360v-120h80v120h-80ZM440,160v-80h80v80h-80Z"
23+
android:fillColor="#1f1f1f"/>
24+
</vector>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!--
2+
~ Copyright (C) 2026 The Android Open Source Project
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
-->
16+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
17+
android:width="24dp"
18+
android:height="24dp"
19+
android:viewportWidth="960"
20+
android:viewportHeight="960">
21+
<path
22+
android:pathData="M240,800 L80,640l56,-56 64,62v-446h80v446l64,-62 56,56 -160,160ZM480,760v-80h400v80L480,760ZM480,520v-80h400v80L480,520ZM480,280v-80h400v80L480,280Z"
23+
android:fillColor="#1f1f1f"/>
24+
</vector>

0 commit comments

Comments
 (0)