// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT export global AppState { out property totalLights: 35; property degreesFilledWithLights: 360deg - (startAngle - endAngle); in-out property volume: (normalizeAngle(angle - startAngle) / degreesFilledWithLights) * totalLights; out property startAngle: 120deg; out property endAngle: 60deg; in-out property angle: startAngle; in-out property elementRadius: 185px; pure public function normalizeAngle(angle: angle) -> angle { return (angle + 360deg).mod(360deg); } } export component Light { in property index; property gap: (360deg - (AppState.startAngle - AppState.endAngle)) / AppState.totalLights; property angle: (index * gap) + AppState.startAngle; property lightOn: index <= AppState.volume; x: AppState.elementRadius * angle.cos(); y: AppState.elementRadius * angle.sin(); width: 0; height: 0; states [ lightOff when !root.lightOn: { blueLed.opacity: 0; } lightOn when root.lightOn: { blueLed.opacity: 0.6; in { animate blueLed.opacity { duration: 100ms; easing: ease-in-sine; } } out { animate blueLed.opacity { duration: 600ms; easing: ease-out-sine; } } } ] Rectangle { Image { source: @image-url("images/light-hole.png"); } blueLed := Image { source: @image-url("images/light.png"); opacity: 0; } } } export component AppWindow inherits Window { preferred-width: 500px; preferred-height: 500px; background: #1e1d27; Rectangle { width: 425px; height: 427px; background: #1e1d27; knob := Rectangle { base := Image { source: @image-url("images/dial-frame.png"); ta := TouchArea { property centerX: self.width / 2; property centerY: self.height / 2; property relativeX; property relativeY; property newAngle; property deltaDegrees; property firstTouch: false; width: parent.width; height: parent.height; clicked => { firstTouch = false; } moved => { relativeX = ta.mouse-x - centerX; relativeY = ta.mouse-y - centerY; newAngle = AppState.normalizeAngle(atan2(relativeY / 1px, relativeX / 1px)); // on first touch work out what angle the dial is at. Then use this to create a delta // So further movement will be relative to this angle. if !firstTouch { firstTouch = true; deltaDegrees = AppState.normalizeAngle(AppState.angle - newAngle); } else { AppState.angle = AppState.normalizeAngle(deltaDegrees + newAngle); } } } } } Rectangle { width: 1px; height: 1px; x: 212px; y: 210px; metalKnob := Image { source: @image-url("images/metal-dial.png"); rotation-angle: AppState.angle; } Image { source: @image-url("images/metal-lights.png"); } Image { x: 120px * AppState.angle.cos(); y: 120px * AppState.angle.sin(); source: @image-url("images/indicator.png"); } Image { source: @image-url("images/dial-trim.png"); } lightHolder := Rectangle { x: 0px; y: 2px; for i in AppState.totalLights + 1: Light { index: i; } } } } }