[Kotlin][未完成] 用Jetpack Compose Desktop仿制VSCode顶栏
效果图
传统的Windows GUI顶栏为
图标-标题-按钮
菜单栏
在VSCode中,设计人员将菜单栏融入顶栏中,使界面显得简洁美观:
图标-菜单-标题-按钮
博主仿照VSCode的设计,用Jetpack Compose Desktop编写了类似的顶栏GUI:
代码
完整代码在:
https://gitee.com/winter_reisender/ComposeDesktopDemo
https://github.com/Winterreisender/ComposeDesktopDemo
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.window.WindowDraggableArea
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import androidx.compose.ui.window.*
import kotlin.system.exitProcess
/**
* 一套Material菜单栏组件,包含MenuBar,Menu,MenuItem三个层次
* Copyright 2022 Winterreisender.
* 许可协议: AGPL-3.0-only
* example:
* Scaffold(
* topBar = {
* MyMenuBar("Jetpack Compose Demo", windowState) {
* MyMenu("文件") {
* MyMenuItem("新建") {
* }
* MyMenuItem("打开") {
* pagination = Pages.HOME
* }
* Divider()
* MyMenuItem("退出") {
* exitApplication()
* }
* }
* }
* )
*
* @author Winterreisender
* @version 0.1.0
*/
object MyTopMenuBar {
@Composable
fun WindowScope.MyMenuBar(
title: String,
windowState: WindowState,
//scaffoldState :ScaffoldState,
modifier: Modifier = Modifier.height(32.dp),
menus: @Composable () -> Unit = {}
) = TopAppBar(modifier = modifier) {
//val coroutineScope = rememberCoroutineScope()
WindowDraggableArea {
Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
Row(horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.CenterVertically) {
IconButton(
onClick = {/*coroutineScope.launch {scaffoldState.drawerState.open()}*/
},
content = { Icon(Icons.Default.Menu, null) }
)
menus()
}
Text(title, maxLines = 1)
Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically) {
IconButton(
onClick = { windowState.isMinimized = true },
content = { Icon(Icons.Default.ArrowDropDown, null) }
)
IconButton(
onClick = { windowState.placement = if (windowState.placement == WindowPlacement.Maximized) WindowPlacement.Floating else WindowPlacement.Maximized },
content = { Icon(Icons.Default.Add, null) }
)
IconButton(
onClick = { exitProcess(0) },
content = { Icon(Icons.Default.Close, null) }
)
}
}
}
}
interface MyMenuScope : ColumnScope {
// 关闭Menu
fun collapseMenu()
}
@Composable
fun MyMenu(text: String, dropdownMenuItems: @Composable MyMenuScope.() -> Unit) {
var menuExpanded by remember { mutableStateOf(false) }
Column {
Text(text, modifier = Modifier.padding(6.dp, 0.dp).clickable { menuExpanded = true; }, maxLines = 1, fontSize = 1.em)
DropdownMenu(expanded = menuExpanded, onDismissRequest = { menuExpanded = false }, focusable = true,
//modifier = Modifier.onPointerEvent(PointerEventType.Exit) { menuExpanded = false }
) {
object : MyMenuScope, ColumnScope by this { // 委托 GREAT! C++ 可用using
override fun collapseMenu() {
menuExpanded = false;
}
}.dropdownMenuItems()
}
}
}
@Composable
fun MyMenuScope.MyMenuItem(text: String, onClick: () -> Unit) =
DropdownMenuItem(
onClick = { onClick(); collapseMenu() },
modifier = Modifier.height(28.dp)
) {
Text(text, maxLines = 1)
}
@Composable
fun MyMenuScope.MyMenuToggle(text: String, checked :Boolean, onClick: (Boolean) -> Unit) =
DropdownMenuItem(
onClick = { },
modifier = Modifier.height(28.dp)
) {
Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
Text(text, maxLines = 1)
Checkbox(checked,onClick, modifier = Modifier.padding(0.dp).size(14.dp))
}
}
interface MySubmenuScope : RowScope {
// 关闭Menu
fun collapseMenu()
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun MyMenuScope.MySubMenu(text: String, dropdownMenuItems: @Composable MySubmenuScope.() -> Unit) {
var menuExpanded by remember { mutableStateOf(false) }
DropdownMenuItem(onClick = { menuExpanded = true },modifier = Modifier.height(28.dp)
//.onPointerEvent(PointerEventType.Enter) { menuExpanded = true }
) {
Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
Text(text, maxLines = 1)
Icon(Icons.Default.KeyboardArrowRight, contentDescription = null)
}
Row {
DropdownMenu(expanded = menuExpanded, onDismissRequest = { menuExpanded = false }, focusable = true,offset = DpOffset(10.dp, (-10).dp)
//modifier = Modifier.onPointerEvent(PointerEventType.Exit) { menuExpanded = false }
) {
object : MySubmenuScope, RowScope by this@Row {
override fun collapseMenu() {
this@MySubMenu.collapseMenu();
}
}.dropdownMenuItems()
}
}
}
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun MySubmenuScope.MySubMenu(text: String, dropdownMenuItems: @Composable MySubmenuScope.() -> Unit) {
var menuExpanded by remember { mutableStateOf(false) }
DropdownMenuItem(onClick = { menuExpanded = true },modifier = Modifier.height(28.dp)
//.onPointerEvent(PointerEventType.Enter) { menuExpanded = true }
) {
Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
Text(text, maxLines = 1)
Icon(Icons.Default.KeyboardArrowRight, contentDescription = null, modifier = Modifier.fillMaxHeight())
}
Row {
DropdownMenu(expanded = menuExpanded, onDismissRequest = { menuExpanded = false }, focusable = true, offset = DpOffset(10.dp, (-10).dp)
//modifier = Modifier.onPointerEvent(PointerEventType.Exit) { menuExpanded = false }
) {
object : MySubmenuScope, RowScope by this@Row {
override fun collapseMenu() {
this@MySubMenu.collapseMenu();
}
}.dropdownMenuItems()
}
}
}
}
@Composable
fun MySubmenuScope.MyMenuItem(text: String, onClick: () -> Unit) =
DropdownMenuItem(
onClick = { onClick(); collapseMenu() },
modifier = Modifier.height(28.dp)
) {
Text(text, maxLines = 1)
}
}
(仍在施工中)