diff --git a/3DTank/docs/SettingsStyleTable.md b/3DTank/docs/SettingsStyleTable.md new file mode 100644 index 0000000..16352e5 --- /dev/null +++ b/3DTank/docs/SettingsStyleTable.md @@ -0,0 +1,58 @@ +# Settings Style Table(设置页校准) + +> 来源:`3dtank.com/play/static/js/unpacked/module_82347.js` 的 `SettingsComponentStyle` / `SettingsMenuComponentStyle` / `SettingsButtonsComponentStyle` / `GameSettingsStyle` / `DropDownStyle` / `CheckBoxComponentStyle` / `InputRangeComponentStyle`。 +> 方法:在无头页面中加载原包模块,patch `module_37036` 的 `ld` 捕获样式类,实例化样式类后逐个执行 `duv_1[0]` 样式函数,并用 recording style object 记录 `rpf` / `gp8` 调用。 + +## 1. 页面骨架 + +| 原版字段 | CSS | +|---|---| +| `blockContentOptions` | `display:flex; flex-wrap:wrap; justify-content:flex-start; align-items:flex-start; width:72.25em; height:calc(100% - 8.5em); padding-top:4.5em; position:relative` | +| `scroll` | `display:flex; flex-direction:column; width:46.875em; overflow-x:hidden; overflow-y:scroll; scrollbar-width:thin` | +| `settingsBlockOptions` | `display:flex; flex-direction:column; width:46.875em; height:100%; padding:1.875em; padding-bottom:0; background:rgba(255,255,255,0.1); position:relative` | +| `containerBlock` | `width:50.625em; height:100%; padding-left:2.625em` | +| `borderLineOption` | `width:49.125em; height:0.063em; margin-left:-1.875em; margin-bottom:1.875em; background:rgba(255,255,255,0.1)` | +| `commonBlock` | `display:flex; flex-direction:column; justify-content:flex-start; align-items:center; width:100%; height:auto` | +| `rowBlock` | `display:flex; flex-direction:row; justify-content:space-between; align-items:center; width:100%; height:auto` | +| `textHeadlineOptions` | `font:500 1.125em/normal BaseFontBold; text-transform:uppercase` | +| `dropDownTitle` | `font:400 1em/normal BaseFontRegular; margin:0 0 1em` | +| `dropDown` | `width:30.625em; margin-bottom:1.75em` | +| `slider` | `width:31.25em` | + +## 2. 左侧菜单 + +| 原版字段 | CSS | +|---|---| +| `slideMenuOptions` | `display:flex; flex-direction:column; justify-content:center; width:18em; height:2.5em; background:linear-gradient(to right,#2E3235,rgba(46,50,53,0)); color:rgb(255,188,9); z-index:1; transition:all .1s ease-in-out; position:relative` | +| `yellowLineMenuOptions` | `width:0.313em; height:2.5em; background:rgb(255,188,9); position:absolute; z-index:3` | +| `menuItemOptions` | `display:flex; align-items:center; justify-content:flex-start; width:18em; height:5em; position:relative; z-index:4; cursor:pointer` | +| `menuItemOptions span` | `font:500 1.125em/normal BaseFontBold; text-transform:uppercase; margin-left:1.875em; margin-top:0.1em; color:rgba(255,255,255,0.6)` | +| `activeItemOptions span` | `color:white; transition:.5s` | + +## 3. 底部按钮 + +| 原版字段 | CSS | +|---|---| +| `containerGroupButton` | `display:flex; align-items:center; justify-content:flex-end; width:100%; height:3.125em; padding-bottom:1.875em; align-self:flex-end; background:transparent` | +| `buttonsWidthBackReset` | `width:15.625em` | +| `buttonSaveLeave` | `width:18.125em` | + +## 4. 控件 + +| 原版类/字段 | CSS | +|---|---| +| `DropDownStyle` 容器 | `width:100%; position:relative` | +| `DropDownStyle` 按钮 | `display:flex; align-items:center; justify-content:space-between; min-width:4.375em; width:100%; height:3.125em; border:0.063em solid rgba(255,255,255,0.2); background:rgba(255,255,255,0.1); padding:0 0.875em; box-sizing:border-box` | +| `CheckBoxComponentStyle` | `display:flex; align-items:center; justify-content:flex-start; width:100%; height:2.25em; margin-bottom:1.25em` | +| `InputRangeComponentStyle` | `display:flex; flex-direction:column; justify-content:space-between; align-items:flex-start; height:3.188em; min-height:3.188em; margin-bottom:1.75em` | +| `InputRangeComponentStyle` label | `color:white; font-size:0.9375em; margin-top:0` | +| `InputRangeComponentStyle` input | `-webkit-appearance:none; width:100%; height:0.563em; background:transparent; cursor:pointer` | +| `GameSettingsStyle.containerFormOptions` | `display:flex; flex-direction:column; margin-left:0.9em; margin-bottom:1em` | +| `GameSettingsStyle.notificationText` | `color:rgba(255,255,255,0.5); text-align:left` | + +## 5. 当前仍为近似 + +- 复选框内部图形:原版由组件内部 SVG/伪元素绘制,当前用 1.25em 黄色方块 + 勾。 +- 设置行的组合方式:已按 `rowBlock` / `dropDownTitle` / `slider` 的尺寸重建,但原版每个设置项的具体子组件层级仍需继续展开。 +- 下拉选项弹层:当前用原生 `select`,原版是自定义列表;尺寸/边框/背景已对齐。 +- 菜单滑动条的动态位置:当前按 `index * 5em + 1.25em` 计算,原版由组件状态计算 `margin-top`。 diff --git a/3DTank/docs/UiTranslationStatus.md b/3DTank/docs/UiTranslationStatus.md index 72f8ac1..a292a42 100644 --- a/3DTank/docs/UiTranslationStatus.md +++ b/3DTank/docs/UiTranslationStatus.md @@ -15,7 +15,7 @@ | 幸运礼盒 | 同上 | 4 种礼盒美术、钥匙图标/数量、可能奖励稀有度、右侧详情/开启确认 | | 战斗选择 | 同上 | 模式筛选(DM/TDM/CTF/CP/AS/JGR/SGE/RUGBY 图标)+ 战场列表 + 右侧详情/加入/创建入口 | | 任务 | 同上 | 分类侧栏(主要/每日/每周/星级挑战/合约)+ 任务卡(进度、奖励图标、领取/去完成)+ 星级挑战横幅 + 离线示例 | -| 设置 | 同上 | 8 个分类侧栏;开关/滑杆/下拉/按键表;账户、安全、支持分组;全部重置 | +| 设置 | 同上 | 8 个分类侧栏;开关/滑杆/下拉/按键表;账户、安全、支持分组;全部重置。**已按原包 `SettingsComponentStyle` 展开值校准**(72.25em 内容区、18em 菜单、46.875em 面板、1.875em 内边距、黄色滑动条/左侧线),详见 `3DTank/docs/SettingsStyleTable.md` | | 聊天 | 同上 | 频道侧栏(公共/战斗/军团/私聊)+ 消息行(头像/作者/时间)+ 发送;军团频道读 `ClanChatState` | | 新闻 | 同上 | 左列表 + 右详情;真实图片/日期/正文;无数据时显示带标记的离线示例 | | 军团 | 同上 | 未加入:军团美术、创建/搜索、许可证;已加入:军团头图、信息/成员/请求/邀请页签与成员表 | @@ -54,4 +54,5 @@ - `LobbyView` 的旧 HTML 字符串子页面已删除,只保留车库 HTML 容器;非车库页面全部走 `ScreenMount`。 - 已用无头浏览器核对非战斗页面:首页、车库(总览/底盘/迷彩)、商店、礼盒、战斗选择、任务、设置(游戏/画面/音效)、聊天、新闻、军团、好友。 - 任务/新闻/好友在离线无数据时显示带「离线示例」标记的演示内容;连接服务器后自动使用真实状态。 -- 下一步:继续展开战斗 HUD / 结算的精确尺寸与弹窗实例,再处理车库真实迷彩预览与 3D 模型。 +- **设置页已按原包 style 类展开值重建**;其余非战斗页面仍使用通用 `UiTheme` 样式,尚未逐页校准,不能视为最终 1:1。 +- 下一步:以设置页为基准,逐页提取原版 style 类并替换,再处理战斗 HUD / 结算的精确尺寸与弹窗实例。 diff --git a/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/LobbyScreens.kt b/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/LobbyScreens.kt index 462cac3..1feb132 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/LobbyScreens.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/LobbyScreens.kt @@ -146,6 +146,7 @@ private val BreadcrumbsFullscreenText: csstype.PropertiesBuilder.() -> Unit = { private fun ChildrenBuilder.ScreenShell( Title: String, Subtitle: String = "", + FullBleed: Boolean = false, Body: ChildrenBuilder.() -> Unit, ) { div { @@ -207,7 +208,7 @@ private fun ChildrenBuilder.ScreenShell( } } div { - css(ScreenBody) + if (FullBleed) css(ScreenBodyFull) else css(ScreenBody) Body() } } @@ -2126,6 +2127,309 @@ private fun SettingsEntries(Tab: Int, UserName: String): List = wh ) } +private val SettingsPage: csstype.PropertiesBuilder.() -> Unit = { + Style("display", "flex") + Style("justify-content", "center") + Style("width", "100%") + Style("height", "100%") + Style("overflow", "hidden") +} + +private val SettingsBlock: csstype.PropertiesBuilder.() -> Unit = { + Style("display", "flex") + Style("flex-wrap", "wrap") + Style("justify-content", "flex-start") + Style("align-items", "flex-start") + Style("width", "72.25em") + Style("height", "calc(100% - 8.5em)") + Style("margin-left", "0") + Style("padding-top", "4.5em") + Style("position", "relative") + Style("box-sizing", "border-box") +} + +private val SettingsMenu: csstype.PropertiesBuilder.() -> Unit = { + Style("position", "relative") + Style("width", "18em") + Style("flex", "0 0 18em") + Style("height", "100%") +} + +private val SettingsMenuHighlight: csstype.PropertiesBuilder.() -> Unit = { + Style("position", "absolute") + Style("left", "0") + Style("width", "18em") + Style("height", "2.5em") + Style("background", "linear-gradient(to right, #2E3235, rgba(46, 50, 53, 0))") + Style("color", "rgb(255, 188, 9)") + Style("z-index", "1") + Style("transition", "all 0.1s ease-in-out") + Style("pointer-events", "none") +} + +private val SettingsMenuLine: csstype.PropertiesBuilder.() -> Unit = { + Style("position", "absolute") + Style("left", "0") + Style("width", "0.313em") + Style("height", "2.5em") + Style("background-color", "rgb(255, 188, 9)") + Style("z-index", "3") + Style("transition", "all 0.1s ease-in-out") + Style("pointer-events", "none") +} + +private val SettingsMenuItem: csstype.PropertiesBuilder.() -> Unit = { + Style("display", "flex") + Style("align-items", "center") + Style("justify-content", "flex-start") + Style("width", "18em") + Style("height", "5em") + Style("position", "relative") + Style("margin", "0") + Style("padding", "0") + Style("border", "0") + Style("background", "transparent") + Style("z-index", "4") + Style("cursor", "pointer") + Style("text-align", "left") +} + +private fun SettingsMenuLabel(Active: Boolean): csstype.PropertiesBuilder.() -> Unit = { + Style("font-family", "BaseFontBold, FallbackFontBold") + Style("font-style", "normal") + Style("font-weight", "500") + Style("font-size", "1.125em") + Style("line-height", "normal") + Style("text-transform", "uppercase") + Style("margin-left", "1.875em") + Style("margin-top", "0.1em") + Style("color", if (Active) "white" else "rgba(255, 255, 255, 0.6)") + Style("transition", "0.5s") +} + +private val SettingsContainer: csstype.PropertiesBuilder.() -> Unit = { + Style("display", "flex") + Style("align-items", "flex-start") + Style("width", "50.625em") + Style("height", "100%") + Style("padding-left", "2.625em") + Style("box-sizing", "border-box") +} + +private val SettingsPanel: csstype.PropertiesBuilder.() -> Unit = { + Style("display", "flex") + Style("flex-direction", "column") + Style("align-items", "stretch") + Style("justify-content", "flex-start") + Style("width", "46.875em") + Style("height", "100%") + Style("margin", "0") + Style("padding", "1.875em") + Style("padding-bottom", "0") + Style("position", "relative") + Style("background-color", "rgba(255, 255, 255, 0.1)") + Style("box-sizing", "border-box") + Style("overflow-x", "hidden") + Style("overflow-y", "auto") + Style("scrollbar-color", "#BCBCBC rgba(255,255,255,0.2)") + Style("scrollbar-width", "thin") +} + +private val SettingsDivider: csstype.PropertiesBuilder.() -> Unit = { + Style("flex", "0 0 auto") + Style("background-color", "rgba(255, 255, 255, 0.1)") + Style("width", "49.125em") + Style("height", "0.063em") + Style("margin-bottom", "1.875em") + Style("margin-left", "-1.875em") +} + +private val SettingsHeadline: csstype.PropertiesBuilder.() -> Unit = { + Style("font-family", "BaseFontBold, FallbackFontBold") + Style("font-style", "normal") + Style("font-weight", "500") + Style("font-size", "1.125em") + Style("line-height", "normal") + Style("text-transform", "uppercase") + Style("margin", "0 0 1.875em") + Style("color", "white") +} + +private val SettingsRow: csstype.PropertiesBuilder.() -> Unit = { + Style("display", "flex") + Style("flex-direction", "row") + Style("justify-content", "space-between") + Style("align-items", "center") + Style("width", "100%") + Style("height", "auto") + Style("min-height", "3.125em") + Style("margin-bottom", "1.25em") + Style("gap", "1em") +} + +private val SettingsLabel: csstype.PropertiesBuilder.() -> Unit = { + Style("font-family", "BaseFontRegular, FallbackFontRegular") + Style("font-style", "normal") + Style("font-weight", "normal") + Style("font-size", "1em") + Style("line-height", "normal") + Style("color", "rgba(255, 255, 255, 0.9)") +} + +private val SettingsDesc: csstype.PropertiesBuilder.() -> Unit = { + Style("font-family", "BaseFontRegular, FallbackFontRegular") + Style("font-size", "0.85em") + Style("line-height", "1.35") + Style("color", "rgba(255, 255, 255, 0.5)") + Style("margin-top", "0.2em") +} + +private val SettingsDropdown: csstype.PropertiesBuilder.() -> Unit = { + Style("width", "30.625em") + Style("margin-bottom", "1.75em") + Style("position", "relative") +} + +private val SettingsDropTitle: csstype.PropertiesBuilder.() -> Unit = { + Style("font-family", "BaseFontRegular, FallbackFontRegular") + Style("font-style", "normal") + Style("font-weight", "normal") + Style("font-size", "1em") + Style("line-height", "normal") + Style("margin", "0 0 1em") + Style("color", "rgba(255, 255, 255, 0.9)") +} + +private val SettingsSelect: csstype.PropertiesBuilder.() -> Unit = { + Style("display", "flex") + Style("align-items", "center") + Style("justify-content", "space-between") + Style("min-width", "4.375em") + Style("width", "100%") + Style("height", "3.125em") + Style("border", "0.063em solid rgba(255, 255, 255, 0.2)") + Style("box-sizing", "border-box") + Style("background-color", "rgba(255, 255, 255, 0.1)") + Style("padding-left", "0.875em") + Style("padding-right", "0.875em") + Style("color", "white") + Style("font-family", "BaseFontRegular, FallbackFontRegular") + Style("font-size", "1em") + Style("outline", "none") + Style("cursor", "pointer") +} + +private val SettingsSliderWrap: csstype.PropertiesBuilder.() -> Unit = { + Style("display", "flex") + Style("flex-direction", "column") + Style("justify-content", "space-between") + Style("align-items", "flex-start") + Style("width", "31.25em") + Style("height", "3.188em") + Style("min-height", "3.188em") + Style("margin-bottom", "1.75em") +} + +private val SettingsSliderLabel: csstype.PropertiesBuilder.() -> Unit = { + Style("display", "flex") + Style("justify-content", "space-between") + Style("align-items", "center") + Style("width", "100%") + Style("color", "white") + Style("font-size", "0.9375em") + Style("margin-top", "0") +} + +private val SettingsRange: csstype.PropertiesBuilder.() -> Unit = { + Style("cursor", "pointer") + Style("-webkit-appearance", "none") + Style("appearance", "none") + Style("width", "100%") + Style("height", "0.563em") + Style("background-color", "rgba(255, 255, 255, 0.2)") + Style("outline", "none") + Style("accent-color", "rgb(255, 188, 9)") +} + +private val SettingsButtons: csstype.PropertiesBuilder.() -> Unit = { + Style("display", "flex") + Style("align-items", "center") + Style("justify-content", "flex-end") + Style("width", "100%") + Style("height", "3.125em") + Style("background-color", "transparent") + Style("align-self", "flex-end") + Style("padding-bottom", "1.875em") + Style("gap", "0.5em") + Style("margin-top", "auto") +} + +private val SettingsButton: csstype.PropertiesBuilder.() -> Unit = { + Style("width", "15.625em") + Style("height", "3.125em") + Style("background-color", "rgba(255, 255, 255, 0.1)") + Style("border", "0") + Style("color", "white") + Style("font-family", "BaseFontBold, FallbackFontBold") + Style("font-weight", "500") + Style("font-size", "1.125em") + Style("text-transform", "uppercase") + Style("cursor", "pointer") +} + +private val SettingsButtonWide: csstype.PropertiesBuilder.() -> Unit = { + Apply(SettingsButton) + Style("width", "18.125em") +} + +private val SettingsCheckboxRow: csstype.PropertiesBuilder.() -> Unit = { + Style("display", "flex") + Style("align-items", "center") + Style("justify-content", "flex-start") + Style("width", "100%") + Style("height", "2.25em") + Style("margin-bottom", "1.25em") + Style("gap", "0.75em") +} + +private fun SettingsCheckbox(On: Boolean): csstype.PropertiesBuilder.() -> Unit = { + Style("display", "flex") + Style("align-items", "center") + Style("justify-content", "center") + Style("width", "1.25em") + Style("height", "1.25em") + Style("flex", "0 0 1.25em") + Style("border", "0.063em solid ${if (On) "rgb(255, 188, 9)" else "rgba(255, 255, 255, 0.4)"}") + Style("background-color", if (On) "rgb(255, 188, 9)" else "rgba(255, 255, 255, 0.1)") + Style("cursor", "pointer") + Style("padding", "0") +} + +private val SettingsCheckMark: csstype.PropertiesBuilder.() -> Unit = { + Style("width", "0.65em") + Style("height", "0.4em") + Style("border-left", "0.15em solid #1b1f22") + Style("border-bottom", "0.15em solid #1b1f22") + Style("transform", "rotate(-45deg)") + Style("margin-top", "-0.1em") +} + +private val SettingsKeysGrid: csstype.PropertiesBuilder.() -> Unit = { + Style("display", "grid") + Style("grid-template-columns", "1fr 1fr") + Style("gap", "0.1em 1.2em") + Style("width", "100%") + Style("margin-bottom", "1.25em") +} + +private val SettingsKeyRow: csstype.PropertiesBuilder.() -> Unit = { + Style("display", "flex") + Style("align-items", "center") + Style("justify-content", "space-between") + Style("padding", "0.35em 0") + Style("border-bottom", "0.063em solid rgba(255, 255, 255, 0.1)") +} + private val SettingsScreen = FC { var Tab by useState(0) var Toggles by useState>(emptyMap()) @@ -2141,136 +2445,173 @@ private val SettingsScreen = FC { ToastContext.Show("已重置当前设置(离线模式)") } - ScreenShell("设置", SettingsTabs.getOrElse(Tab) { "" }) { + ScreenShell("设置", SettingsTabs.getOrElse(Tab) { "" }, FullBleed = true) { div { - css(Columns) + css(SettingsPage) div { - css(SideNav) - for ((Index, Name) in SettingsTabs.withIndex()) { - NavItem(Name, null, Tab == Index) { Tab = Index } - } - } - div { - css(ContentPane) + css(SettingsBlock) div { - css(PageToolbar) + css(SettingsMenu) div { - css { Style("flex", "1"); Style("min-width", "0") } - div { css(PaneTitle); +SettingsTabs.getOrElse(Tab) { "设置" } } - div { css(PaneDesc); +"修改后的设置会在下一次进入战斗时生效。" } + css { + Apply(SettingsMenuHighlight) + Style("top", "calc(${Tab * 5}em + 1.25em)") + } } - button { - css(SecondaryButton) - onClick = { ResetAll() } - +"全部重置" + div { + css { + Apply(SettingsMenuLine) + Style("top", "calc(${Tab * 5}em + 1.25em)") + } } - } - - div { - css(Panel) - for (Entry in Entries) { - when (Entry) { - is SettingEntry.Header -> { - div { - css { - Style("padding", "0.55em 1em") - Style("background", "rgba(0, 12, 20, 0.45)") - Style("border-bottom", "1px solid ${Ui.DividerSoft}") - Style("color", Ui.Cyan) - Style("font", "500 0.82em/1.3 ${Ui.FontMedium}") - Style("text-transform", "uppercase") - Style("letter-spacing", "0.05em") - } - +Entry.Text - } - } - is SettingEntry.Toggle -> { - val On = Toggles[Entry.Id] ?: Entry.Default - SettingRow(Entry.Label, Entry.Desc) { - Toggle(On) { Toggles = Toggles + (Entry.Id to it) } - } - } - is SettingEntry.Slider -> { - val Value = Sliders[Entry.Id] ?: Entry.Default - SliderRow(Entry.Label, Entry.Desc, Value) { - Sliders = Sliders + (Entry.Id to it) - } - } - is SettingEntry.Choice -> { - val Index = Choices[Entry.Id] ?: Entry.Default - SelectRow(Entry.Label, Entry.Desc, Entry.Options, Index) { - Choices = Choices + (Entry.Id to it) - } - } - is SettingEntry.Action -> { - SettingRow(Entry.Label, Entry.Desc) { - button { - css(SecondaryButton) - onClick = { ToastContext.Show(Entry.Toast) } - +Entry.Button - } - } - } - is SettingEntry.Info -> { - SettingRow(Entry.Label, Entry.Desc) { - span { css(MutedText); +Entry.Value } - } - } - is SettingEntry.Keys -> { - div { - css { - Style("display", "grid") - Style("grid-template-columns", "1fr 1fr") - Style("gap", "0.1em 1.2em") - Style("padding", "0.6em 1em 0.9em") - } - for ((Action, Key) in Entry.Rows) { - div { - css { - Style("display", "flex") - Style("align-items", "center") - Style("justify-content", "space-between") - Style("padding", "0.35em 0") - Style("border-bottom", "1px solid ${Ui.DividerSoft}") - } - span { css(MutedText); +Action } - span { - css { - Style("min-width", "2.2em") - Style("padding", "0.2em 0.45em") - Style("border", "1px solid ${Ui.Divider}") - Style("border-radius", "0.25em") - Style("background", "rgba(0, 12, 20, 0.7)") - Style("text-align", "center") - Style("font", "500 0.8em/1.3 ${Ui.FontMedium}") - } - +Key - } - } - } - } + for ((Index, Name) in SettingsTabs.withIndex()) { + button { + css(SettingsMenuItem) + onClick = { Tab = Index } + span { + css(SettingsMenuLabel(Index == Tab)) + +Name } } } } div { - css { - Style("display", "flex") - Style("align-items", "center") - Style("justify-content", "space-between") - Style("gap", "0.6em") - Style("margin-top", "0.2em") - } - button { - css(SecondaryButton) - onClick = { ScreenContext.OnBack() } - +"返回 [Z]" - } - button { - css(SecondaryButton) - onClick = { ResetAll() } - +"全部重置" + css(SettingsContainer) + div { + css(SettingsPanel) + var HasHeader = false + for (Entry in Entries) { + when (Entry) { + is SettingEntry.Header -> { + if (HasHeader) div { css(SettingsDivider) } + HasHeader = true + div { + css(SettingsHeadline) + +Entry.Text + } + } + is SettingEntry.Toggle -> { + val On = Toggles[Entry.Id] ?: Entry.Default + div { + css(SettingsCheckboxRow) + button { + css(SettingsCheckbox(On)) + onClick = { Toggles = Toggles + (Entry.Id to !On) } + if (On) div { css(SettingsCheckMark) } + } + div { + css { Style("flex", "1"); Style("min-width", "0") } + div { css(SettingsLabel); +Entry.Label } + if (Entry.Desc.isNotEmpty()) div { css(SettingsDesc); +Entry.Desc } + } + } + } + is SettingEntry.Slider -> { + val Value = Sliders[Entry.Id] ?: Entry.Default + div { + css(SettingsSliderWrap) + div { + css(SettingsSliderLabel) + span { +Entry.Label } + span { +"$Value%" } + } + input { + css(SettingsRange) + asDynamic()["type"] = "range" + asDynamic()["min"] = "0" + asDynamic()["max"] = "100" + asDynamic()["value"] = "$Value" + onChange = { E -> Sliders = Sliders + (Entry.Id to (E.target.value.toString().toIntOrNull() ?: Value)) } + } + } + } + is SettingEntry.Choice -> { + val Index = Choices[Entry.Id] ?: Entry.Default + div { + css(SettingsDropdown) + div { css(SettingsDropTitle); +Entry.Label } + select { + css(SettingsSelect) + value = Entry.Options.getOrElse(Index) { Entry.Options.firstOrNull() ?: "" } + onChange = { E -> + val Found = Entry.Options.indexOf(E.target.value) + if (Found >= 0) Choices = Choices + (Entry.Id to Found) + } + for (Option in Entry.Options) option { value = Option; +Option } + } + if (Entry.Desc.isNotEmpty()) div { css(SettingsDesc); +Entry.Desc } + } + } + is SettingEntry.Action -> { + div { + css(SettingsRow) + div { + css { Style("flex", "1"); Style("min-width", "0") } + div { css(SettingsLabel); +Entry.Label } + if (Entry.Desc.isNotEmpty()) div { css(SettingsDesc); +Entry.Desc } + } + button { + css(SettingsButton) + onClick = { ToastContext.Show(Entry.Toast) } + +Entry.Button + } + } + } + is SettingEntry.Info -> { + div { + css(SettingsRow) + div { + css { Style("flex", "1"); Style("min-width", "0") } + div { css(SettingsLabel); +Entry.Label } + if (Entry.Desc.isNotEmpty()) div { css(SettingsDesc); +Entry.Desc } + } + span { + css { + Style("color", "rgba(255, 255, 255, 0.6)") + Style("font-size", "1em") + } + +Entry.Value + } + } + } + is SettingEntry.Keys -> { + div { + css(SettingsKeysGrid) + for ((Action, Key) in Entry.Rows) { + div { + css(SettingsKeyRow) + span { css(SettingsLabel); +Action } + span { + css { + Style("min-width", "2.2em") + Style("padding", "0.2em 0.45em") + Style("border", "0.063em solid rgba(255, 255, 255, 0.2)") + Style("background-color", "rgba(255, 255, 255, 0.1)") + Style("text-align", "center") + Style("font", "500 0.8em/1.3 ${Ui.FontMedium}") + } + +Key + } + } + } + } + } + } + } + div { + css(SettingsButtons) + button { + css(SettingsButton) + onClick = { ScreenContext.OnBack() } + +"返回 [Z]" + } + button { + css(SettingsButtonWide) + onClick = { ResetAll() } + +"全部重置" + } + } } } } diff --git a/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/UiTheme.kt b/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/UiTheme.kt index bbfe07c..bb49487 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/UiTheme.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/UiTheme.kt @@ -87,6 +87,14 @@ internal val ScreenBody: PropertiesBuilder.() -> Unit = { Style("box-sizing", "border-box") } +internal val ScreenBodyFull: PropertiesBuilder.() -> Unit = { + Style("flex", "1") + Style("min-height", "0") + Style("overflow", "hidden") + Style("padding", "0") + Style("box-sizing", "border-box") +} + internal val BackButton: PropertiesBuilder.() -> Unit = { Style("display", "flex") Style("align-items", "center")