From bc4062b916d55f7245b158180d2e7e51309ba2ef Mon Sep 17 00:00:00 2001 From: rnentjes Date: Wed, 18 Feb 2026 12:49:27 +0100 Subject: [PATCH] Refactor `vst-ui-base` and `test-app`: remove inline CSS definitions in Kotlin, migrate styles to external `vst-ui-base.css`. Add `WebResourceHandler` for serving CSS. Enhance `KeyboardComponent` with keyboard event mapping. Increment version to 2.2.4. --- .run/JS.run.xml | 1 + common.gradle.kts | 2 +- .../nl/astraeus/vst/ui/view/MainView.kt | 191 +++--------- vst-ui-base/build.gradle.kts | 10 +- .../vst/ui/components/BaseKnobComponent.kt | 75 +---- .../vst/ui/components/KeyboardComponent.kt | 152 ++++------ .../kotlin/nl/astraeus/vst/ui/css/Css.kt | 120 -------- .../kotlin/nl/astraeus/vst/ui/css/CssName.kt | 17 +- .../nl/astraeus/vst/ui/view/BaseVstView.kt | 53 ---- .../kotlin/nl/astraeus/vst/base/web/Index.kt | 13 +- .../astraeus/vst/base/web/RequestHandler.kt | 1 + .../vst/base/web/WebResourceHandler.kt | 27 ++ .../src/jvmMain/resources/vst-ui-base.css | 282 ++++++++++++++++++ 13 files changed, 438 insertions(+), 506 deletions(-) delete mode 100644 vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/css/Css.kt create mode 100644 vst-ui-base/src/jvmMain/kotlin/nl/astraeus/vst/base/web/WebResourceHandler.kt create mode 100644 vst-ui-base/src/jvmMain/resources/vst-ui-base.css diff --git a/.run/JS.run.xml b/.run/JS.run.xml index 4a0a39d..7783b34 100644 --- a/.run/JS.run.xml +++ b/.run/JS.run.xml @@ -1,5 +1,6 @@ + diff --git a/common.gradle.kts b/common.gradle.kts index 7a7748e..ff0954d 100644 --- a/common.gradle.kts +++ b/common.gradle.kts @@ -2,7 +2,7 @@ project.extra.set("devMode", true) project.extra.set("enabledSourceMaps", true) group = "nl.astraeus" -version = "2.2.3" +version = "2.2.4" allprojects { repositories { diff --git a/test-app/src/jsMain/kotlin/nl/astraeus/vst/ui/view/MainView.kt b/test-app/src/jsMain/kotlin/nl/astraeus/vst/ui/view/MainView.kt index a400b5b..9e1a8b8 100644 --- a/test-app/src/jsMain/kotlin/nl/astraeus/vst/ui/view/MainView.kt +++ b/test-app/src/jsMain/kotlin/nl/astraeus/vst/ui/view/MainView.kt @@ -2,25 +2,29 @@ package nl.astraeus.vst.ui.view -import kotlinx.html.* +import kotlinx.browser.document +import kotlinx.html.InputType +import kotlinx.html.div +import kotlinx.html.h1 +import kotlinx.html.hr +import kotlinx.html.input import kotlinx.html.js.onChangeFunction import kotlinx.html.js.onClickFunction +import kotlinx.html.js.onInputFunction +import kotlinx.html.option import kotlinx.html.org.w3c.dom.events.Event -import nl.astraeus.css.properties.* -import nl.astraeus.css.style.Style -import nl.astraeus.css.style.cls +import kotlinx.html.select +import kotlinx.html.span +import kotlinx.html.style import nl.astraeus.komp.HtmlBuilder import nl.astraeus.komp.Komponent import nl.astraeus.vst.ui.components.ExpKnobComponent import nl.astraeus.vst.ui.components.KeyboardComponent import nl.astraeus.vst.ui.components.KnobComponent -import nl.astraeus.vst.ui.css.Css -import nl.astraeus.vst.ui.css.Css.defineCss -import nl.astraeus.vst.ui.css.Css.noTextSelect import nl.astraeus.vst.ui.css.CssName -import nl.astraeus.vst.ui.css.hover import nl.astraeus.vst.ui.ws.WebsocketClient import org.w3c.dom.HTMLInputElement +import org.w3c.dom.events.KeyboardEvent import org.w3c.files.File import org.w3c.files.FileList import org.w3c.files.get @@ -29,6 +33,17 @@ class MainView : Komponent() { private var messages: MutableList = ArrayList() var started = true var keyboardWidth = 500 + val keyboard = KeyboardComponent( + keyboardWidth = keyboardWidth, + keyboardHeight = keyboardWidth / 2, + onNoteDown = { println("Note down: $it") }, + onNoteUp = { println("Note up: $it") }, + ) + + init { + document.body?.addEventListener("keydown", { event -> keyboard.handleKeyboardEvent(event as KeyboardEvent) }) + document.body?.addEventListener("keyup", { event -> keyboard.handleKeyboardEvent(event as KeyboardEvent) }) + } fun addMessage(message: String) { messages.add(message) @@ -45,7 +60,7 @@ class MainView : Komponent() { override fun HtmlBuilder.render() { div(MainDivCss.name) { - style = "transform: scale(0.5);" + //style = "transform: scale(0.5);" if (!started) { div(StartSplashCss.name) { div(StartBoxCss.name) { @@ -89,26 +104,27 @@ class MainView : Komponent() { } span { +"channel:" - /* input { type = InputType.number - value = VstChipWorklet.midiChannel.toString() + value = "1" + min = "1" + max = "16" onInputFunction = { event -> val target = event.target as HTMLInputElement - kotlin.io.println("onInput channel: $target") - VstChipWorklet.midiChannel = target.value.toInt() + println("onInput channel: $target") + //VstChipWorklet.midiChannel = target.value.toInt() } } -*/ + } - span { - +"Upload file: " - input { - type = InputType.file - onChangeFunction = { - fileInputSelectHandler(it) - requestUpdate() - } + } + div { + +"Upload file: " + input { + type = InputType.file + onChangeFunction = { + fileInputSelectHandler(it) + requestUpdate() } } } @@ -133,12 +149,7 @@ class MainView : Komponent() { hr {} div { include( - KeyboardComponent( - keyboardWidth = keyboardWidth, - keyboardHeight = keyboardWidth / 2, - onNoteDown = { println("Note down: $it") }, - onNoteUp = { println("Note up: $it") }, - ) + keyboard ) } /* @@ -226,132 +237,10 @@ class MainView : Komponent() { companion object MainViewCss : CssName() { object MainDivCss : CssName() - object ActiveCss : CssName() - object ButtonCss : CssName() - object ButtonBarCss : CssName() - object SelectedCss : CssName() - object NoteBarCss : CssName() object StartSplashCss : CssName() object StartBoxCss : CssName() object StartButtonCss : CssName() object ControlsCss : CssName() - - init { - defineCss { - select("*") { - select("*:before") { - select("*:after") { - boxSizing(BoxSizing.borderBox) - } - } - } - select("html", "body") { - margin(0.px) - padding(0.px) - height(100.prc) - } - select("html", "body") { - backgroundColor(Css.currentStyle.mainBackgroundColor) - color(Css.currentStyle.mainFontColor) - - fontFamily("JetbrainsMono, monospace") - fontSize(14.px) - fontWeight(FontWeight.bold) - - //transition() - noTextSelect() - } - select("input", "textarea") { - backgroundColor(Css.currentStyle.inputBackgroundColor) - color(Css.currentStyle.mainFontColor) - border("none") - } - select(cls(ButtonCss)) { - margin(1.rem) - commonButton() - } - select(cls(ButtonBarCss)) { - margin(1.rem, 0.px) - commonButton() - } - select(cls(ActiveCss)) { - //backgroundColor(Css.currentStyle.selectedBackgroundColor) - } - select(cls(NoteBarCss)) { - minHeight(4.rem) - } - select(cls(MainDivCss)) { - margin(1.rem) - } - select("select") { - plain("appearance", "none") - border("0") - outline("0") - width(20.rem) - padding(0.5.rem, 2.rem, 0.5.rem, 0.5.rem) - backgroundImage("url('https://upload.wikimedia.org/wikipedia/commons/9/9d/Caret_down_font_awesome_whitevariation.svg')") - background("right 0.8em center/1.4em") - backgroundColor(Css.currentStyle.inputBackgroundColor) - color(Css.currentStyle.mainFontColor) - borderRadius(0.25.em) - } - select(cls(StartSplashCss)) { - position(Position.fixed) - left(0.px) - top(0.px) - width(100.vw) - height(100.vh) - zIndex(100) - backgroundColor(hsla(32, 0, 5, 0.65)) - - select(cls(StartBoxCss)) { - position(Position.relative) - left(25.vw) - top(25.vh) - width(50.vw) - height(50.vh) - backgroundColor(hsla(239, 50, 10, 1.0)) - borderColor(Css.currentStyle.mainFontColor) - borderWidth(2.px) - - select(cls(StartButtonCss)) { - position(Position.absolute) - left(50.prc) - top(50.prc) - transform(Transform("translate(-50%, -50%)")) - padding(1.rem) - backgroundColor(Css.currentStyle.buttonBackgroundColor) - cursor("pointer") - } - } - } - select(ControlsCss.cls()) { - display(Display.flex) - flexDirection(FlexDirection.row) - justifyContent(JustifyContent.flexStart) - alignItems(AlignItems.center) - margin(1.rem) - padding(1.rem) - backgroundColor(Css.currentStyle.mainBackgroundColor) - } - } - } - - private fun Style.commonButton() { - display(Display.inlineBlock) - padding(1.rem) - backgroundColor(Css.currentStyle.buttonBackgroundColor) - borderColor(Css.currentStyle.buttonBorderColor) - borderWidth(Css.currentStyle.buttonBorderWidth) - color(Css.currentStyle.mainFontColor) - - hover { - backgroundColor(Css.currentStyle.buttonBackgroundColor.hover()) - } - and(SelectedCss.cls()) { - backgroundColor(Css.currentStyle.buttonBackgroundColor.hover().hover().hover()) - } - } } -} \ No newline at end of file +} diff --git a/vst-ui-base/build.gradle.kts b/vst-ui-base/build.gradle.kts index 8a279f2..9ac8398 100644 --- a/vst-ui-base/build.gradle.kts +++ b/vst-ui-base/build.gradle.kts @@ -33,11 +33,7 @@ kotlin { jvm {} sourceSets { - val commonMain by getting { - dependencies { - api("nl.astraeus:kotlin-css-generator:1.0.10") - } - } + val commonMain by getting val jsMain by getting { dependencies { api("nl.astraeus:kotlin-komponent:1.2.8") @@ -52,8 +48,8 @@ kotlin { dependencies { api("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0") - api("io.undertow:undertow-core:2.3.21.Final") - implementation("io.undertow:undertow-websockets-jsr:2.3.21.Final") + api("io.undertow:undertow-core:2.3.23.Final") + implementation("io.undertow:undertow-websockets-jsr:2.3.23.Final") //implementation("org.jboss.xnio:xnio-nio:3.8.16.Final") implementation("org.xerial:sqlite-jdbc:3.46.0.0") diff --git a/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/components/BaseKnobComponent.kt b/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/components/BaseKnobComponent.kt index 79b766c..e38ce89 100644 --- a/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/components/BaseKnobComponent.kt +++ b/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/components/BaseKnobComponent.kt @@ -1,20 +1,19 @@ package nl.astraeus.vst.ui.components import kotlinx.browser.document -import kotlinx.html.* +import kotlinx.html.classes +import kotlinx.html.div import kotlinx.html.js.onMouseDownFunction import kotlinx.html.js.onMouseMoveFunction import kotlinx.html.js.onMouseUpFunction import kotlinx.html.js.onWheelFunction import kotlinx.html.org.w3c.dom.events.Event -import nl.astraeus.css.properties.* -import nl.astraeus.css.style.cls +import kotlinx.html.span +import kotlinx.html.style +import kotlinx.html.svg import nl.astraeus.komp.HtmlBuilder import nl.astraeus.komp.Komponent import nl.astraeus.komp.currentElement -import nl.astraeus.vst.ui.css.ActiveCls -import nl.astraeus.vst.ui.css.Css -import nl.astraeus.vst.ui.css.Css.defineCss import nl.astraeus.vst.ui.css.CssId import nl.astraeus.vst.ui.css.CssName import nl.astraeus.vst.ui.util.arc @@ -327,6 +326,7 @@ open class BaseKnobComponent( } companion object : CssId("knob") { + object ActiveCls : CssName( "active") object KnobCls : CssName() object KnobSvgCls : CssName() object KnobTextCls : CssName() @@ -334,69 +334,6 @@ open class BaseKnobComponent( object KnobVolumeCls : CssName() object KnobVolumeBackgroundCls : CssName() - - init { - defineCss { - select(cls(KnobCls)) { - position(Position.relative) - margin(5.px) - - and(cls(ActiveCls)) { - backgroundColor(hsla(178, 70, 55, 0.1)) - borderRadius(0.5.rem) - } - - select(cls(KnobSvgCls)) { - plain("stroke", "none") - plain("stroke-opacity", "1.0") - plain("fill", "none") - plain("fill-opacity", "0.0") - position(Position.absolute) - backgroundColor(Color.transparent) - - and(cls(ActiveCls)) { - color(Css.currentStyle.mainFontColor) - borderRadius(4.px) - } - } - - select(cls(KnobTextCls)) { - position(Position.absolute) - width(100.prc) - textAlign(TextAlign.center) - fontSize(1.0.em) - color(Css.currentStyle.mainFontColor) - } - - select(cls(KnobValueCls)) { - position(Position.absolute) - width(100.prc) - top((48).prc) - textAlign(TextAlign.center) - fontSize(1.1.em) - color(Css.currentStyle.mainFontColor) - } - } - - select(cls(KnobVolumeCls)) { - plain("fill", Color.transparent) - plain("stroke", Css.currentStyle.mainFontColor) - color(Css.currentStyle.mainFontColor) - plain("stroke-width", "8") - //plain("stroke-dasharray", "4") - plain("fill-opacity", "0.5") - } - - select(cls(KnobVolumeBackgroundCls)) { - plain("fill", Color.transparent) - plain("stroke", Css.currentStyle.mainFontColor.darken(40)) - color(Css.currentStyle.mainFontColor.darken(20)) - plain("stroke-width", "5") - //plain("stroke-dasharray", "4") - plain("fill-opacity", "0.5") - } - } - } } } diff --git a/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/components/KeyboardComponent.kt b/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/components/KeyboardComponent.kt index 05bc8df..cb8fa12 100644 --- a/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/components/KeyboardComponent.kt +++ b/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/components/KeyboardComponent.kt @@ -7,35 +7,66 @@ import kotlinx.html.js.onMouseLeaveFunction import kotlinx.html.js.onMouseUpFunction import kotlinx.html.style import kotlinx.html.svg -import nl.astraeus.css.properties.AlignItems -import nl.astraeus.css.properties.Display -import nl.astraeus.css.properties.FlexDirection -import nl.astraeus.css.properties.FontWeight -import nl.astraeus.css.properties.JustifyContent -import nl.astraeus.css.properties.Position -import nl.astraeus.css.properties.TextAlign -import nl.astraeus.css.properties.prc -import nl.astraeus.css.properties.px -import nl.astraeus.css.properties.rem -import nl.astraeus.css.style.cls import nl.astraeus.komp.HtmlBuilder import nl.astraeus.komp.Komponent -import nl.astraeus.vst.ui.css.Css -import nl.astraeus.vst.ui.css.Css.defineCss import nl.astraeus.vst.ui.css.CssId import nl.astraeus.vst.ui.css.CssName import nl.astraeus.vst.ui.util.height import nl.astraeus.vst.ui.util.rect import nl.astraeus.vst.ui.util.width +import org.w3c.dom.events.KeyboardEvent import org.w3c.dom.events.MouseEvent +private val KEYBOARD_MAPPING = mapOf( + "KeyZ" to 60, + "KeyS" to 61, + "KeyX" to 62, + "KeyD" to 63, + "KeyC" to 64, + "KeyV" to 65, + "KeyG" to 66, + "KeyB" to 67, + "KeyH" to 68, + "KeyN" to 69, + "KeyJ" to 70, + "KeyM" to 71, // b + "Comma" to 72, + "KeyL" to 73, + "Period" to 74, + "Semicolon" to 75, + "Slash" to 76, + + "KeyQ" to 72, + "Digit2" to 73, + "KeyW" to 74, + "Digit3" to 75, + "KeyE" to 76, + "KeyR" to 77, + "Digit5" to 78, + "KeyT" to 79, + "Digit6" to 80, + "KeyY" to 81, + "Digit7" to 82, + "KeyU" to 83, // b + "KeyI" to 84, + "Digit9" to 85, + "KeyO" to 86, + "Digit0" to 87, + "KeyP" to 88, + "BracketLeft" to 89, + "Equal" to 90, + "BracketRight" to 91, +) + + + /** * The keyboard component shows 1 octabe of a (piano) keyboard and * calls the noteDown and noteUp methods when the keys are clicked */ class KeyboardComponent( val title: String = "Keyboard", - initialOctave: Int = 4, + val initialOctave: Int = 4, val keyboardWidth: Int = 210, val keyboardHeight: Int = keyboardWidth / 2, val rounding: Int = 4, @@ -95,6 +126,13 @@ class KeyboardComponent( requestUpdate() } + fun handleKeyboardEvent(event: KeyboardEvent) { + KEYBOARD_MAPPING[event.code]?.also { midi -> + val transpose = (state.octave - initialOctave) * 12 + console.log("Keyboard event: ", event, midi + transpose) + } + } + private fun releaseAllNotes() { // Create a copy of the set to avoid concurrent modification val notesToRelease = state.pressedNotes.toSet() @@ -274,91 +312,5 @@ class KeyboardComponent( // MIDI note numbers for C5 to B5 (one octave) private val BASE_WHITE_KEYS = listOf(60, 62, 64, 65, 67, 69, 71) // C, D, E, F, G, A, B private val BASE_BLACK_KEYS = listOf(61, 63, 66, 68, 70) // C#, D#, F#, G#, A# - - init { - defineCss { - select(cls(KeyboardCls)) { - position(Position.relative) - margin(5.px) - - select(cls(KeyboardControlsCls)) { - position(Position.relative) - display(Display.flex) - flexDirection(FlexDirection.row) - justifyContent(JustifyContent.spaceBetween) - alignItems(AlignItems.center) - width(100.prc) - height(50.px) - marginBottom(10.px) - } - - select(cls(KeyboardInfoCls)) { - display(Display.flex) - flexDirection(FlexDirection.column) - alignItems(AlignItems.center) - justifyContent(JustifyContent.center) - flex("1") - } - - select(cls(KeyboardTitleCls)) { - textAlign(TextAlign.center) - fontSize(1.2.rem) - color(Css.currentStyle.mainFontColor) - } - - select(cls(KeyboardOctaveCls)) { - textAlign(TextAlign.center) - fontSize(1.0.rem) - color(Css.currentStyle.mainFontColor) - marginTop(5.px) - } - - select(cls(OctaveButtonCls)) { - height(50.px) - plain("background-color", Css.currentStyle.buttonBackgroundColor.toString()) - color(Css.currentStyle.mainFontColor) - border("1px solid ${Css.currentStyle.buttonBorderColor}") - textAlign(TextAlign.center) - lineHeight(50.px) - fontSize(1.5.rem) - fontWeight(FontWeight.bold) - cursor("pointer") - plain("-webkit-touch-callout", "none") - plain("-webkit-user-select", "none") - plain("-moz-user-select", "none") - plain("-ms-user-select", "none") - plain("user-select", "none") - } - - select(cls(KeyboardKeysCls)) { - position(Position.relative) - } - } - - select(cls(WhiteKeyCls)) { - plain("fill", "#FFFFFF") - plain("stroke", "#000000") - plain("stroke-width", "1") - } - - select(cls(WhiteKeyPressedCls)) { - plain("fill", "#E6E6E6") // 10% darker than white - plain("stroke", "#000000") - plain("stroke-width", "1") - } - - select(cls(BlackKeyCls)) { - plain("fill", "#000000") - plain("stroke", "#000000") - plain("stroke-width", "1") - } - - select(cls(BlackKeyPressedCls)) { - plain("fill", "#333333") // 10% lighter than black - plain("stroke", "#000000") - plain("stroke-width", "1") - } - } - } } } diff --git a/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/css/Css.kt b/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/css/Css.kt deleted file mode 100644 index a107c78..0000000 --- a/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/css/Css.kt +++ /dev/null @@ -1,120 +0,0 @@ -package nl.astraeus.vst.ui.css - -import kotlinx.browser.document -import nl.astraeus.css.properties.Color -import nl.astraeus.css.properties.Measurement -import nl.astraeus.css.properties.UserSelect -import nl.astraeus.css.properties.hsl -import nl.astraeus.css.properties.hsla -import nl.astraeus.css.properties.px -import nl.astraeus.css.style -import nl.astraeus.css.style.ConditionalStyle -import nl.astraeus.css.style.DescriptionProvider -import nl.astraeus.css.style.Style - -class StyleDefinition( - val mainFontColor: Color = hsla(178, 70, 55, 1.0), - val mainBackgroundColor: Color = hsl(239, 50, 10), - //val entryFontColor: Color = hsl(Css.mainFontColorNumber, 70, 55), - val inputBackgroundColor : Color = mainBackgroundColor.lighten(15), - val buttonBackgroundColor : Color = mainBackgroundColor.lighten(15), - val buttonBorderColor : Color = mainFontColor.changeAlpha(0.25), - val buttonBorderWidth : Measurement = 1.px, -) - -object NoTextSelectCls : CssName("no-text-select") -object SelectedCls : CssName("selected") -object ActiveCls : CssName( "active") - -fun Color.hover(): Color = if (Css.currentStyle == Css.darkStyle) { - this.lighten(15) -} else { - this.darken(15) -} - -object Css { - var dynamicStyles = mutableMapOf Unit>() - - fun DescriptionProvider.defineCss(conditionalStyle: ConditionalStyle.() -> Unit) { - check(!dynamicStyles.containsKey(this)) { - "CssId with name ${this.description()} already defined!" - } - - updateCss(conditionalStyle) - } - - private fun DescriptionProvider.updateCss(conditionalStyle: ConditionalStyle.() -> Unit) { - val elementId = this.description() - var dynamicStyleElement = document.getElementById(elementId) - - dynamicStyles[this] = conditionalStyle - - if (dynamicStyleElement == null) { - dynamicStyleElement = document.createElement("style") - dynamicStyleElement.id = elementId - - document.head?.append(dynamicStyleElement) - } - - val css = style(conditionalStyle) - - dynamicStyleElement.innerHTML = css.generateCss(minified = CssSettings.minified) - } - - var darkStyle = StyleDefinition( - ) - - var lightStyle = StyleDefinition( - mainBackgroundColor = hsl(239+180, 50, 15), - ) - - var currentStyle: StyleDefinition = darkStyle - - fun updateStyle() { - for ((cssId, dynStyle) in dynamicStyles) { - cssId.apply { - updateCss(dynStyle) - } - } - } - - fun switchLayout() { - currentStyle = if (currentStyle == darkStyle) { - lightStyle - } else { - darkStyle - } - - updateStyle() - } - - fun Style.transition() { - transition("all 0.5s ease") - } - - fun Style.noTextSelect() { - plain("-webkit-touch-callout", "none") - plain("-webkit-user-select", "none") - plain("-moz-user-select", "none") - plain("-ms-user-select", "none") - - userSelect(UserSelect.none) - - select("::selection") { - background("none") - } - } - - object GenericCss : CssId("generic") { - init { - fun generateStyle(): String { - val css = style { - - } - - return css.generateCss(minified = CssSettings.minified) - } - } - } - -} diff --git a/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/css/CssName.kt b/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/css/CssName.kt index 42452c7..92dcb2e 100644 --- a/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/css/CssName.kt +++ b/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/css/CssName.kt @@ -1,8 +1,5 @@ package nl.astraeus.vst.ui.css -import nl.astraeus.css.style.DescriptionProvider -import nl.astraeus.css.style.cls - private val CAPITAL_LETTER = Regex("[A-Z]") fun String.hyphenize(): String { @@ -39,6 +36,19 @@ private fun nextShortId(): String { return result.toString() } +interface DescriptionProvider { + fun description(): String +} + +class ValueDescriptionProvider( + val value: String +) : DescriptionProvider { + override fun description() = value +} + +fun cls(name: String): DescriptionProvider = ValueDescriptionProvider(".$name") +fun cls(name: DescriptionProvider): DescriptionProvider = ValueDescriptionProvider(".$name") + abstract class CssName( overrideName: String? = null ) : DescriptionProvider { @@ -61,6 +71,7 @@ abstract class CssName( override fun description() = name } + open class CssId(name: String) : DescriptionProvider { val name: String = if (CssSettings.shortId) { nextShortId() diff --git a/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/view/BaseVstView.kt b/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/view/BaseVstView.kt index f69c072..b1d9938 100644 --- a/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/view/BaseVstView.kt +++ b/vst-ui-base/src/jsMain/kotlin/nl/astraeus/vst/ui/view/BaseVstView.kt @@ -3,21 +3,8 @@ package nl.astraeus.vst.ui.view import kotlinx.html.div import kotlinx.html.js.onClickFunction import kotlinx.html.org.w3c.dom.events.Event -import nl.astraeus.css.properties.Display -import nl.astraeus.css.properties.FlexDirection -import nl.astraeus.css.properties.Position -import nl.astraeus.css.properties.Transform -import nl.astraeus.css.properties.hsla -import nl.astraeus.css.properties.prc -import nl.astraeus.css.properties.px -import nl.astraeus.css.properties.rem -import nl.astraeus.css.properties.vh -import nl.astraeus.css.properties.vw -import nl.astraeus.css.style.cls import nl.astraeus.komp.HtmlBuilder import nl.astraeus.komp.Komponent -import nl.astraeus.vst.ui.css.Css -import nl.astraeus.vst.ui.css.Css.defineCss import nl.astraeus.vst.ui.css.CssId import nl.astraeus.vst.ui.css.CssName @@ -53,46 +40,6 @@ class BaseVstView( object StartSplashCss : CssName() object StartBoxCss : CssName() object StartButtonCss : CssName() - - init { - defineCss { - select(BaseVstCss.cls()) { - display(Display.flex) - flexDirection(FlexDirection.column) - } - - select(cls(StartSplashCss)) { - position(Position.fixed) - left(0.px) - top(0.px) - width(100.vw) - height(100.vh) - zIndex(100) - backgroundColor(hsla(32, 0, 5, 0.65)) - - select(cls(StartBoxCss)) { - position(Position.relative) - left(25.vw) - top(25.vh) - width(50.vw) - height(50.vh) - backgroundColor(hsla(239, 50, 10, 1.0)) - borderColor(Css.currentStyle.mainFontColor) - borderWidth(2.px) - - select(cls(StartButtonCss)) { - position(Position.absolute) - left(50.prc) - top(50.prc) - transform(Transform("translate(-50%, -50%)")) - padding(1.rem) - backgroundColor(Css.currentStyle.buttonBackgroundColor) - cursor("pointer") - } - } - } - } - } } } diff --git a/vst-ui-base/src/jvmMain/kotlin/nl/astraeus/vst/base/web/Index.kt b/vst-ui-base/src/jvmMain/kotlin/nl/astraeus/vst/base/web/Index.kt index 88936cc..b634ab6 100644 --- a/vst-ui-base/src/jvmMain/kotlin/nl/astraeus/vst/base/web/Index.kt +++ b/vst-ui-base/src/jvmMain/kotlin/nl/astraeus/vst/base/web/Index.kt @@ -1,7 +1,13 @@ package nl.astraeus.vst.base.web -import kotlinx.html.* +import kotlinx.html.body +import kotlinx.html.head +import kotlinx.html.html +import kotlinx.html.link +import kotlinx.html.meta +import kotlinx.html.script import kotlinx.html.stream.appendHTML +import kotlinx.html.title fun generateIndex( title: String, @@ -15,6 +21,9 @@ fun generateIndex( result.appendHTML(true).html { head { title { +title } + link(rel = "stylesheet", href = "/vst-ui-base.css") { + type = "text/css" + } } body { script { @@ -26,7 +35,7 @@ fun generateIndex( } else { result.appendHTML(true).html { head { - title { +"VST Chip" } + title { +title } meta { httpEquiv = "refresh" content = "0; url=/patch/$patch" diff --git a/vst-ui-base/src/jvmMain/kotlin/nl/astraeus/vst/base/web/RequestHandler.kt b/vst-ui-base/src/jvmMain/kotlin/nl/astraeus/vst/base/web/RequestHandler.kt index 9659f3b..fbed5d2 100644 --- a/vst-ui-base/src/jvmMain/kotlin/nl/astraeus/vst/base/web/RequestHandler.kt +++ b/vst-ui-base/src/jvmMain/kotlin/nl/astraeus/vst/base/web/RequestHandler.kt @@ -44,6 +44,7 @@ class RequestHandler( pathHandler.addExactPath("/", patchHandler) pathHandler.addExactPath("/index.html", patchHandler) pathHandler.addPrefixPath("/patch", patchHandler) + pathHandler.addPrefixPath("/vst-ui-base.css", WebResourceHandler("text/css", "vst-ui-base.css")) //pathHandler.addPrefixPath("/data", urlDataHandler) pathHandler.addExactPath("/ws", WebsocketConnectHandler) } diff --git a/vst-ui-base/src/jvmMain/kotlin/nl/astraeus/vst/base/web/WebResourceHandler.kt b/vst-ui-base/src/jvmMain/kotlin/nl/astraeus/vst/base/web/WebResourceHandler.kt new file mode 100644 index 0000000..290ff6f --- /dev/null +++ b/vst-ui-base/src/jvmMain/kotlin/nl/astraeus/vst/base/web/WebResourceHandler.kt @@ -0,0 +1,27 @@ +package nl.astraeus.vst.base.web + +import io.undertow.server.HttpHandler +import io.undertow.server.HttpServerExchange +import io.undertow.util.Headers +import io.undertow.util.StatusCodes +import java.nio.ByteBuffer + +class WebResourceHandler( + val mimeType: String, + val resourceName: String, +) : HttpHandler { + + override fun handleRequest(exchange: HttpServerExchange) { + val stream = Thread.currentThread().contextClassLoader.getResourceAsStream(resourceName) + + if (stream != null) { + exchange.responseHeaders.put(Headers.CONTENT_TYPE, mimeType) + exchange.responseSender.send(ByteBuffer.wrap(stream.readBytes())) + } else { + exchange.responseHeaders.put(Headers.CONTENT_TYPE, "text/plain") + exchange.statusCode = StatusCodes.NOT_FOUND + exchange.responseSender.send("Not found", Charsets.UTF_8) + } + } + +} diff --git a/vst-ui-base/src/jvmMain/resources/vst-ui-base.css b/vst-ui-base/src/jvmMain/resources/vst-ui-base.css new file mode 100644 index 0000000..70c4426 --- /dev/null +++ b/vst-ui-base/src/jvmMain/resources/vst-ui-base.css @@ -0,0 +1,282 @@ +:root { + --vst-main-font-color: hsla(178, 70%, 55%, 1); + --vst-main-background-color: hsl(239, 50%, 10%); + --vst-input-background-color: hsl(239, 50%, 25%); + --vst-button-background-color: hsl(239, 50%, 25%); + --vst-button-border-color: hsla(178, 70%, 55%, 0.25); + --vst-button-border-width: 1px; + --vst-main-font-color-darken-20: hsl(178, 70%, 35%); + --vst-main-font-color-darken-40: hsl(178, 70%, 15%); +} + +*, *:before,*:after { + box-sizing: border-box; +} + +html,body { + margin: 0; + padding: 0; + height: 100%; + + background-color: var(--vst-main-background-color); + color: var(--vst-main-font-color); + + font-family: JetbrainsMono, monospace; + font-size: 14px; + font-weight: bold; + + //transition() + //noTextSelect() +} + +input, textarea { + background-color: var(--vst-input-background-color); + color: var(--vst-main-font-color); + border: none; +} + +.vst-main-div-css { + margin: 1rem; +} + +select { + appearance: none; + border: 0; + outline: 0; + width: 20rem; + padding: 0.5rem 2rem 0.5rem 0.5rem; + color: var(--vst-main-font-color); + border-radius: 0.25rem; + background: var(--vst-input-background-color); +} + +/* + + select(cls(ButtonCss)) { + margin(1.rem) + commonButton() + } + select(cls(ButtonBarCss)) { + margin(1.rem, 0.px) + commonButton() + } + select(cls(NoteBarCss)) { + minHeight(4.rem) + } + select(cls(StartSplashCss)) { + position(Position.fixed) + left(0.px) + top(0.px) + width(100.vw) + height(100.vh) + zIndex(100) + backgroundColor(hsla(32, 0, 5, 0.65)) + + select(cls(StartBoxCss)) { + position(Position.relative) + left(25.vw) + top(25.vh) + width(50.vw) + height(50.vh) + backgroundColor(hsla(239, 50, 10, 1.0)) + borderColor(Css.currentStyle.mainFontColor) + borderWidth(2.px) + + select(cls(StartButtonCss)) { + position(Position.absolute) + left(50.prc) + top(50.prc) + transform(Transform("translate(-50%, -50%)")) + padding(1.rem) + backgroundColor(Css.currentStyle.buttonBackgroundColor) + cursor("pointer") + } + } + } + select(ControlsCss.cls()) { + display(Display.flex) + flexDirection(FlexDirection.row) + justifyContent(JustifyContent.flexStart) + alignItems(AlignItems.center) + margin(1.rem) + padding(1.rem) + backgroundColor(Css.currentStyle.mainBackgroundColor) + } + } +} +*/ + +.vst-base-vst-css { + display: flex; + flex-direction: column; +} + +.vst-start-splash-css { + position: fixed; + left: 0; + top: 0; + width: 100vw; + height: 100vh; + z-index: 100; + background-color: hsla(32, 0%, 5%, 0.65); +} + +.vst-start-splash-css .vst-start-box-css { + position: relative; + left: 25vw; + top: 25vh; + width: 50vw; + height: 50vh; + background-color: hsla(239, 50%, 10%, 1); + border-color: var(--vst-main-font-color); + border-width: 2px; +} + +.vst-start-splash-css .vst-start-box-css .vst-start-button-css { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + padding: 1rem; + background-color: var(--vst-button-background-color); + cursor: pointer; +} + +.vst-knob-cls { + position: relative; + margin: 5px; +} + +.vst-knob-cls.vst-active { + background-color: hsla(178, 70%, 55%, 0.1); + border-radius: 0.5rem; +} + +.vst-knob-cls .vst-knob-svg-cls { + stroke: none; + stroke-opacity: 1; + fill: none; + fill-opacity: 0; + position: absolute; + background-color: transparent; +} + +.vst-knob-cls .vst-knob-svg-cls.vst-active { + color: var(--vst-main-font-color); + border-radius: 4px; +} + +.vst-knob-cls .vst-knob-text-cls { + position: absolute; + width: 100%; + text-align: center; + font-size: 1em; + color: var(--vst-main-font-color); +} + +.vst-knob-cls .vst-knob-value-cls { + position: absolute; + width: 100%; + top: 48%; + text-align: center; + font-size: 1.1em; + color: var(--vst-main-font-color); +} + +.vst-knob-volume-cls { + fill: transparent; + stroke: var(--vst-main-font-color); + color: var(--vst-main-font-color); + stroke-width: 8; + fill-opacity: 0.5; +} + +.vst-knob-volume-background-cls { + fill: transparent; + stroke: var(--vst-main-font-color-darken-40); + color: var(--vst-main-font-color-darken-20); + stroke-width: 5; + fill-opacity: 0.5; +} + +.vst-keyboard-cls { + position: relative; + margin: 5px; +} + +.vst-keyboard-cls .vst-keyboard-controls-cls { + position: relative; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + width: 100%; + height: 50px; + margin-bottom: 10px; +} + +.vst-keyboard-cls .vst-keyboard-info-cls { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex: 1; +} + +.vst-keyboard-cls .vst-keyboard-title-cls { + text-align: center; + font-size: 1.2rem; + color: var(--vst-main-font-color); +} + +.vst-keyboard-cls .vst-keyboard-octave-cls { + text-align: center; + font-size: 1rem; + color: var(--vst-main-font-color); + margin-top: 5px; +} + +.vst-keyboard-cls .vst-octave-button-cls { + height: 50px; + background-color: var(--vst-button-background-color); + color: var(--vst-main-font-color); + border: 1px solid var(--vst-button-border-color); + text-align: center; + line-height: 50px; + font-size: 1.5rem; + font-weight: bold; + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.vst-keyboard-cls .vst-keyboard-keys-cls { + position: relative; +} + +.vst-white-key-cls { + fill: #ffffff; + stroke: #000000; + stroke-width: 1; +} + +.vst-white-key-pressed-cls { + fill: #e6e6e6; + stroke: #000000; + stroke-width: 1; +} + +.vst-black-key-cls { + fill: #000000; + stroke: #000000; + stroke-width: 1; +} + +.vst-black-key-pressed-cls { + fill: #333333; + stroke: #000000; + stroke-width: 1; +}