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.

This commit is contained in:
2026-02-18 12:49:27 +01:00
parent d5a377e974
commit bc4062b916
13 changed files with 438 additions and 506 deletions

View File

@@ -1,5 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="JS" type="JavascriptDebugType" engineId="98ca6316-2f89-46d9-a9e5-fa9e2b0625b3" uri="http://localhost:9999"> <configuration default="false" name="JS" type="JavascriptDebugType" engineId="98ca6316-2f89-46d9-a9e5-fa9e2b0625b3" uri="http://localhost:9999">
<mapping url="http://localhost:9999/vst-ui-base.css" local-file="$PROJECT_DIR$/vst-ui-base/src/jvmMain/resources/vst-ui-base.css" />
<method v="2"> <method v="2">
<option name="Gradle.BeforeRunTask" enabled="true" tasks="jsBrowserDevelopmentExecutableDistribution" externalProjectPath="$PROJECT_DIR$/test-app" vmOptions="" scriptParameters="" /> <option name="Gradle.BeforeRunTask" enabled="true" tasks="jsBrowserDevelopmentExecutableDistribution" externalProjectPath="$PROJECT_DIR$/test-app" vmOptions="" scriptParameters="" />
</method> </method>

View File

@@ -2,7 +2,7 @@ project.extra.set("devMode", true)
project.extra.set("enabledSourceMaps", true) project.extra.set("enabledSourceMaps", true)
group = "nl.astraeus" group = "nl.astraeus"
version = "2.2.3" version = "2.2.4"
allprojects { allprojects {
repositories { repositories {

View File

@@ -2,25 +2,29 @@
package nl.astraeus.vst.ui.view 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.onChangeFunction
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import kotlinx.html.js.onInputFunction
import kotlinx.html.option
import kotlinx.html.org.w3c.dom.events.Event import kotlinx.html.org.w3c.dom.events.Event
import nl.astraeus.css.properties.* import kotlinx.html.select
import nl.astraeus.css.style.Style import kotlinx.html.span
import nl.astraeus.css.style.cls import kotlinx.html.style
import nl.astraeus.komp.HtmlBuilder import nl.astraeus.komp.HtmlBuilder
import nl.astraeus.komp.Komponent import nl.astraeus.komp.Komponent
import nl.astraeus.vst.ui.components.ExpKnobComponent import nl.astraeus.vst.ui.components.ExpKnobComponent
import nl.astraeus.vst.ui.components.KeyboardComponent import nl.astraeus.vst.ui.components.KeyboardComponent
import nl.astraeus.vst.ui.components.KnobComponent 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.CssName
import nl.astraeus.vst.ui.css.hover
import nl.astraeus.vst.ui.ws.WebsocketClient import nl.astraeus.vst.ui.ws.WebsocketClient
import org.w3c.dom.HTMLInputElement import org.w3c.dom.HTMLInputElement
import org.w3c.dom.events.KeyboardEvent
import org.w3c.files.File import org.w3c.files.File
import org.w3c.files.FileList import org.w3c.files.FileList
import org.w3c.files.get import org.w3c.files.get
@@ -29,6 +33,17 @@ class MainView : Komponent() {
private var messages: MutableList<String> = ArrayList() private var messages: MutableList<String> = ArrayList()
var started = true var started = true
var keyboardWidth = 500 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) { fun addMessage(message: String) {
messages.add(message) messages.add(message)
@@ -45,7 +60,7 @@ class MainView : Komponent() {
override fun HtmlBuilder.render() { override fun HtmlBuilder.render() {
div(MainDivCss.name) { div(MainDivCss.name) {
style = "transform: scale(0.5);" //style = "transform: scale(0.5);"
if (!started) { if (!started) {
div(StartSplashCss.name) { div(StartSplashCss.name) {
div(StartBoxCss.name) { div(StartBoxCss.name) {
@@ -89,26 +104,27 @@ class MainView : Komponent() {
} }
span { span {
+"channel:" +"channel:"
/*
input { input {
type = InputType.number type = InputType.number
value = VstChipWorklet.midiChannel.toString() value = "1"
min = "1"
max = "16"
onInputFunction = { event -> onInputFunction = { event ->
val target = event.target as HTMLInputElement val target = event.target as HTMLInputElement
kotlin.io.println("onInput channel: $target") println("onInput channel: $target")
VstChipWorklet.midiChannel = target.value.toInt() //VstChipWorklet.midiChannel = target.value.toInt()
} }
} }
*/
} }
span { }
+"Upload file: " div {
input { +"Upload file: "
type = InputType.file input {
onChangeFunction = { type = InputType.file
fileInputSelectHandler(it) onChangeFunction = {
requestUpdate() fileInputSelectHandler(it)
} requestUpdate()
} }
} }
} }
@@ -133,12 +149,7 @@ class MainView : Komponent() {
hr {} hr {}
div { div {
include( include(
KeyboardComponent( keyboard
keyboardWidth = keyboardWidth,
keyboardHeight = keyboardWidth / 2,
onNoteDown = { println("Note down: $it") },
onNoteUp = { println("Note up: $it") },
)
) )
} }
/* /*
@@ -226,132 +237,10 @@ class MainView : Komponent() {
companion object MainViewCss : CssName() { companion object MainViewCss : CssName() {
object MainDivCss : CssName() object MainDivCss : CssName()
object ActiveCss : CssName()
object ButtonCss : CssName()
object ButtonBarCss : CssName()
object SelectedCss : CssName()
object NoteBarCss : CssName()
object StartSplashCss : CssName() object StartSplashCss : CssName()
object StartBoxCss : CssName() object StartBoxCss : CssName()
object StartButtonCss : CssName() object StartButtonCss : CssName()
object ControlsCss : 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())
}
}
} }
} }

View File

@@ -33,11 +33,7 @@ kotlin {
jvm {} jvm {}
sourceSets { sourceSets {
val commonMain by getting { val commonMain by getting
dependencies {
api("nl.astraeus:kotlin-css-generator:1.0.10")
}
}
val jsMain by getting { val jsMain by getting {
dependencies { dependencies {
api("nl.astraeus:kotlin-komponent:1.2.8") api("nl.astraeus:kotlin-komponent:1.2.8")
@@ -52,8 +48,8 @@ kotlin {
dependencies { dependencies {
api("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0") api("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
api("io.undertow:undertow-core:2.3.21.Final") api("io.undertow:undertow-core:2.3.23.Final")
implementation("io.undertow:undertow-websockets-jsr:2.3.21.Final") implementation("io.undertow:undertow-websockets-jsr:2.3.23.Final")
//implementation("org.jboss.xnio:xnio-nio:3.8.16.Final") //implementation("org.jboss.xnio:xnio-nio:3.8.16.Final")
implementation("org.xerial:sqlite-jdbc:3.46.0.0") implementation("org.xerial:sqlite-jdbc:3.46.0.0")

View File

@@ -1,20 +1,19 @@
package nl.astraeus.vst.ui.components package nl.astraeus.vst.ui.components
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.html.* import kotlinx.html.classes
import kotlinx.html.div
import kotlinx.html.js.onMouseDownFunction import kotlinx.html.js.onMouseDownFunction
import kotlinx.html.js.onMouseMoveFunction import kotlinx.html.js.onMouseMoveFunction
import kotlinx.html.js.onMouseUpFunction import kotlinx.html.js.onMouseUpFunction
import kotlinx.html.js.onWheelFunction import kotlinx.html.js.onWheelFunction
import kotlinx.html.org.w3c.dom.events.Event import kotlinx.html.org.w3c.dom.events.Event
import nl.astraeus.css.properties.* import kotlinx.html.span
import nl.astraeus.css.style.cls import kotlinx.html.style
import kotlinx.html.svg
import nl.astraeus.komp.HtmlBuilder import nl.astraeus.komp.HtmlBuilder
import nl.astraeus.komp.Komponent import nl.astraeus.komp.Komponent
import nl.astraeus.komp.currentElement 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.CssId
import nl.astraeus.vst.ui.css.CssName import nl.astraeus.vst.ui.css.CssName
import nl.astraeus.vst.ui.util.arc import nl.astraeus.vst.ui.util.arc
@@ -327,6 +326,7 @@ open class BaseKnobComponent(
} }
companion object : CssId("knob") { companion object : CssId("knob") {
object ActiveCls : CssName( "active")
object KnobCls : CssName() object KnobCls : CssName()
object KnobSvgCls : CssName() object KnobSvgCls : CssName()
object KnobTextCls : CssName() object KnobTextCls : CssName()
@@ -334,69 +334,6 @@ open class BaseKnobComponent(
object KnobVolumeCls : CssName() object KnobVolumeCls : CssName()
object KnobVolumeBackgroundCls : 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")
}
}
}
} }
} }

View File

@@ -7,35 +7,66 @@ import kotlinx.html.js.onMouseLeaveFunction
import kotlinx.html.js.onMouseUpFunction import kotlinx.html.js.onMouseUpFunction
import kotlinx.html.style import kotlinx.html.style
import kotlinx.html.svg 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.HtmlBuilder
import nl.astraeus.komp.Komponent 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.CssId
import nl.astraeus.vst.ui.css.CssName import nl.astraeus.vst.ui.css.CssName
import nl.astraeus.vst.ui.util.height import nl.astraeus.vst.ui.util.height
import nl.astraeus.vst.ui.util.rect import nl.astraeus.vst.ui.util.rect
import nl.astraeus.vst.ui.util.width import nl.astraeus.vst.ui.util.width
import org.w3c.dom.events.KeyboardEvent
import org.w3c.dom.events.MouseEvent 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 * The keyboard component shows 1 octabe of a (piano) keyboard and
* calls the noteDown and noteUp methods when the keys are clicked * calls the noteDown and noteUp methods when the keys are clicked
*/ */
class KeyboardComponent( class KeyboardComponent(
val title: String = "Keyboard", val title: String = "Keyboard",
initialOctave: Int = 4, val initialOctave: Int = 4,
val keyboardWidth: Int = 210, val keyboardWidth: Int = 210,
val keyboardHeight: Int = keyboardWidth / 2, val keyboardHeight: Int = keyboardWidth / 2,
val rounding: Int = 4, val rounding: Int = 4,
@@ -95,6 +126,13 @@ class KeyboardComponent(
requestUpdate() 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() { private fun releaseAllNotes() {
// Create a copy of the set to avoid concurrent modification // Create a copy of the set to avoid concurrent modification
val notesToRelease = state.pressedNotes.toSet() val notesToRelease = state.pressedNotes.toSet()
@@ -274,91 +312,5 @@ class KeyboardComponent(
// MIDI note numbers for C5 to B5 (one octave) // 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_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# 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")
}
}
}
} }
} }

View File

@@ -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<DescriptionProvider, ConditionalStyle.() -> 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)
}
}
}
}

View File

@@ -1,8 +1,5 @@
package nl.astraeus.vst.ui.css 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]") private val CAPITAL_LETTER = Regex("[A-Z]")
fun String.hyphenize(): String { fun String.hyphenize(): String {
@@ -39,6 +36,19 @@ private fun nextShortId(): String {
return result.toString() 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( abstract class CssName(
overrideName: String? = null overrideName: String? = null
) : DescriptionProvider { ) : DescriptionProvider {
@@ -61,6 +71,7 @@ abstract class CssName(
override fun description() = name override fun description() = name
} }
open class CssId(name: String) : DescriptionProvider { open class CssId(name: String) : DescriptionProvider {
val name: String = if (CssSettings.shortId) { val name: String = if (CssSettings.shortId) {
nextShortId() nextShortId()

View File

@@ -3,21 +3,8 @@ package nl.astraeus.vst.ui.view
import kotlinx.html.div import kotlinx.html.div
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import kotlinx.html.org.w3c.dom.events.Event 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.HtmlBuilder
import nl.astraeus.komp.Komponent 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.CssId
import nl.astraeus.vst.ui.css.CssName import nl.astraeus.vst.ui.css.CssName
@@ -53,46 +40,6 @@ class BaseVstView(
object StartSplashCss : CssName() object StartSplashCss : CssName()
object StartBoxCss : CssName() object StartBoxCss : CssName()
object StartButtonCss : 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")
}
}
}
}
}
} }
} }

View File

@@ -1,7 +1,13 @@
package nl.astraeus.vst.base.web 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.stream.appendHTML
import kotlinx.html.title
fun generateIndex( fun generateIndex(
title: String, title: String,
@@ -15,6 +21,9 @@ fun generateIndex(
result.appendHTML(true).html { result.appendHTML(true).html {
head { head {
title { +title } title { +title }
link(rel = "stylesheet", href = "/vst-ui-base.css") {
type = "text/css"
}
} }
body { body {
script { script {
@@ -26,7 +35,7 @@ fun generateIndex(
} else { } else {
result.appendHTML(true).html { result.appendHTML(true).html {
head { head {
title { +"VST Chip" } title { +title }
meta { meta {
httpEquiv = "refresh" httpEquiv = "refresh"
content = "0; url=/patch/$patch" content = "0; url=/patch/$patch"

View File

@@ -44,6 +44,7 @@ class RequestHandler(
pathHandler.addExactPath("/", patchHandler) pathHandler.addExactPath("/", patchHandler)
pathHandler.addExactPath("/index.html", patchHandler) pathHandler.addExactPath("/index.html", patchHandler)
pathHandler.addPrefixPath("/patch", patchHandler) pathHandler.addPrefixPath("/patch", patchHandler)
pathHandler.addPrefixPath("/vst-ui-base.css", WebResourceHandler("text/css", "vst-ui-base.css"))
//pathHandler.addPrefixPath("/data", urlDataHandler) //pathHandler.addPrefixPath("/data", urlDataHandler)
pathHandler.addExactPath("/ws", WebsocketConnectHandler) pathHandler.addExactPath("/ws", WebsocketConnectHandler)
} }

View File

@@ -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)
}
}
}

View File

@@ -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;
}