diff --git a/3DTank/docs/SettingsStyleTable.md b/3DTank/docs/SettingsStyleTable.md index f4a387b..241d89c 100644 --- a/3DTank/docs/SettingsStyleTable.md +++ b/3DTank/docs/SettingsStyleTable.md @@ -91,4 +91,38 @@ | 滑杆进度渐变 | `hUi.tap` | `module_82347.js:200507` | | `SettingsComponentStyle-slider` | `kNi.z1jp_1` | `module_82347.js:89620` | -`ksc-130` 是 `SettingsComponentStyle-slider` 的 Emotion 生成类名;当前实现用 `data-ui="SettingsComponentStyle-slider"` 标记,等价于给滑杆挂上该样式。 +`ksc-130` 是 `SettingsComponentStyle-slider` 的 Emotion 生成类名;当前实现直接使用 `InputRangeComponentStyle-*` / `SettingsComponentStyle-slider` 语义类名,并用 `ksc-56` / `ksc-57` 标记标签与滑杆。 + +## 7. 真实 DOM 结构 + +滑杆: + +```html +
+

主音量(80%)

+
+ +
+
+``` + +开关: + +```html +
+
+
+ +
+
+ +
+
+
+``` + +当前实现已按这个结构输出;带描述的开关行增加 `has-description` 修饰类,让描述与标签对齐。 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 7597f27..4b342e5 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/LobbyScreens.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/LobbyScreens.kt @@ -24,6 +24,7 @@ import react.dom.html.ReactHTML.h3 import react.dom.html.ReactHTML.i import react.dom.html.ReactHTML.img import react.dom.html.ReactHTML.input +import react.dom.html.ReactHTML.label import react.dom.html.ReactHTML.option import react.dom.html.ReactHTML.p import react.dom.html.ReactHTML.select @@ -2356,37 +2357,6 @@ private val SettingsChevron: csstype.PropertiesBuilder.() -> Unit = { Style("flex", "0 0 0.813em") } -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("outline", "none") - Style("border-radius", "0") -} - private fun SettingsSliderGradient(Value: Int): String { val Percent = Value.coerceIn(0, 100) return "linear-gradient(to right, #ffbc09 0%, #ffbc09 $Percent%, rgba(255, 255, 255, 0.1) $Percent%, rgba(255, 255, 255, 0.1) 100%)" @@ -2430,46 +2400,6 @@ private val SettingsActionButton: csstype.PropertiesBuilder.() -> Unit = { Style("padding", "0 1em") } -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("position", "relative") - Style("display", "flex") - Style("align-items", "center") - Style("justify-content", "center") - Style("width", "2.75em") - Style("height", "1.5em") - Style("flex", "0 0 2.75em") - Style("box-sizing", "border-box") - Style("background-color", if (On) "rgba(118, 255, 51, 0.25)" else "rgba(191, 213, 255, 0.25)") - Style("border", "0.063em solid rgba(255, 255, 255, 0.2)") - Style("border-radius", "6.25rem") - Style("cursor", "pointer") - Style("padding", "0") - Style("transition", "background-color 0.2s") -} - -private fun SettingsToggleKnob(On: Boolean): csstype.PropertiesBuilder.() -> Unit = { - Style("position", "absolute") - Style("top", "50%") - Style("left", if (On) "calc(100% - 1.3em)" else "0.2em") - Style("width", "1.1em") - Style("height", "1.1em") - Style("border-radius", "50%") - Style("background-color", "white") - Style("transform", "translateY(-50%)") - Style("transition", "left 0.2s") - Style("pointer-events", "none") -} - private val SettingsKeysGrid: csstype.PropertiesBuilder.() -> Unit = { Style("display", "grid") Style("grid-template-columns", "1fr 1fr") @@ -2570,41 +2500,57 @@ private val SettingsScreen = FC { is SettingEntry.Toggle -> { val On = Toggles[Entry.Id] ?: Entry.Default div { - css(SettingsCheckboxRow) - Ui("CheckBoxComponentStyle-oneSetting") - button { - css(SettingsCheckbox(On)) - onClick = { Toggles = Toggles + (Entry.Id to !On) } - div { css(SettingsToggleKnob(On)) } - } + asDynamic()["className"] = "ksc-46" div { - css { Style("flex", "1"); Style("min-width", "0") } - div { css(SettingsLabel); +Entry.Label } - if (Entry.Desc.isNotEmpty()) div { css(SettingsDesc); +Entry.Desc } + asDynamic()["className"] = "CheckBoxComponentStyle-oneSetting" + if (Entry.Desc.isNotEmpty()) " has-description" else "" + div { + asDynamic()["className"] = "CheckBoxStyle-checkbox" + label { + input { + asDynamic()["className"] = "ksc-47" + asDynamic()["type"] = "checkbox" + asDynamic()["checked"] = On + onChange = { Toggles = Toggles + (Entry.Id to !On) } + } + span { + asDynamic()["className"] = "ksc-62 -displayFlex" + asDynamic()["data-on"] = On.toString() + } + } + } + div { + asDynamic()["className"] = "ksc-63 -flexStartAlignCenter" + if (Entry.Desc.isNotEmpty()) " has-description" else "" + label { +Entry.Label } + if (Entry.Desc.isNotEmpty()) div { css(SettingsDesc); +Entry.Desc } + } } } } is SettingEntry.Slider -> { val Value = Sliders[Entry.Id] ?: Entry.Default div { - css(SettingsSliderWrap) - Ui("InputRangeComponentStyle-blockSoundVolume") + asDynamic()["className"] = "InputRangeComponentStyle-blockSoundVolume SettingsComponentStyle-slider" + p { + +Entry.Label + span { + asDynamic()["className"] = "ksc-56" + +"($Value%)" + } + } div { - css(SettingsSliderLabel) - span { +Entry.Label } - span { +"$Value%" } - } - input { - css { - Apply(SettingsRange) - Style("background", SettingsSliderGradient(Value)) + asDynamic()["className"] = "InputRangeComponentStyle-soundVolume" + input { + asDynamic()["className"] = "ksc-57 InputRangeComponentStyle-range" + asDynamic()["type"] = "range" + asDynamic()["min"] = "0" + asDynamic()["max"] = "100" + asDynamic()["step"] = "1" + asDynamic()["value"] = "$Value" + val StyleObj = js("({})") + StyleObj["background"] = SettingsSliderGradient(Value) + asDynamic()["style"] = StyleObj + onChange = { E -> Sliders = Sliders + (Entry.Id to (E.target.value.toString().toIntOrNull() ?: Value)) } } - Ui("SettingsComponentStyle-slider") - 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)) } } } } diff --git a/TankiOffline/app/src/jsMain/resources/lobby3.css b/TankiOffline/app/src/jsMain/resources/lobby3.css index a54d7bd..4963084 100644 --- a/TankiOffline/app/src/jsMain/resources/lobby3.css +++ b/TankiOffline/app/src/jsMain/resources/lobby3.css @@ -984,4 +984,132 @@ body { margin: 0; overflow: hidden; background: #001926; color: #fff; } border: 0; border-radius: 0.25em; background: #ffbc09; +} + +/* 原版设置页语义类 */ +.-displayFlex { display: flex; } +.-flexStartAlignCenter { display: flex; align-items: center; justify-content: flex-start; } + +.InputRangeComponentStyle-blockSoundVolume { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: flex-start; + width: 31.25em; + height: 3.188em; + min-height: 3.188em; + margin-bottom: 1.75em; +} +.SettingsComponentStyle-slider { width: 31.25em; } +.InputRangeComponentStyle-blockSoundVolume p { + margin: 0; + color: #fff; + font-family: BaseFontRegular, "Segoe UI", sans-serif; + font-size: 0.9375em; + line-height: normal; +} +.InputRangeComponentStyle-blockSoundVolume p span { margin-left: 0.2em; } +.InputRangeComponentStyle-soundVolume { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + width: 100%; + height: 1.25em; +} +.InputRangeComponentStyle-range { + -webkit-appearance: none; + appearance: none; + width: 100%; + height: 0.563em; + outline: none; + cursor: pointer; + border-radius: 0; +} +.InputRangeComponentStyle-range::-webkit-slider-runnable-track { + width: 100%; + height: 0.375em; + background: transparent; +} +.InputRangeComponentStyle-range::-webkit-slider-thumb { + -webkit-appearance: none; + width: 1.875em; + height: 1em; + border-radius: 0.25em; + background: #ffbc09; + margin-top: -0.3em; +} +.InputRangeComponentStyle-range::-moz-range-track { background: transparent; } +.InputRangeComponentStyle-range::-moz-range-progress { background: #ffbc09; } +.InputRangeComponentStyle-range::-moz-range-thumb { + width: 1.875em; + height: 1em; + border: 0; + border-radius: 0.25em; + background: #ffbc09; +} + +.CheckBoxComponentStyle-oneSetting { + display: flex; + align-items: center; + justify-content: flex-start; + width: 100%; + height: 2.25em; + margin-bottom: 1.25em; +} +.CheckBoxComponentStyle-oneSetting.has-description { + height: auto; + min-height: 2.25em; + align-items: flex-start; +} +.CheckBoxStyle-checkbox { display: flex; z-index: 1; } +.CheckBoxStyle-checkbox label { + display: flex; + align-items: center; + cursor: pointer; +} +.CheckBoxStyle-checkbox input { + position: absolute; + width: 0; + height: 0; + opacity: 0; + pointer-events: none; +} +.ksc-62 { + position: relative; + display: flex; + width: 2.75em; + height: 1.5em; + border: 0.063em solid rgba(255, 255, 255, 0.2); + border-radius: 6.25rem; + background: rgba(191, 213, 255, 0.25); + transition: background-color 0.2s; +} +.ksc-62[data-on="true"] { background: rgba(118, 255, 51, 0.25); } +.ksc-62::after { + content: ""; + position: absolute; + top: 50%; + left: 0.2em; + width: 1.1em; + height: 1.1em; + border-radius: 50%; + background: #fff; + transform: translateY(-50%); + transition: left 0.2s; +} +.ksc-62[data-on="true"]::after { left: calc(100% - 1.3em); } +.ksc-63 { display: flex; align-items: center; justify-content: flex-start; } +.ksc-63.has-description { + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + gap: 0.15em; + padding-top: 0.2em; +} +.ksc-63 label { + color: rgba(255, 255, 255, 0.9); + font-family: BaseFontRegular, "Segoe UI", sans-serif; + font-size: 1em; + line-height: normal; } \ No newline at end of file