// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT export global AppState { out property totalLights: 35; property degreesFilledWithLights: 360deg - (startAngle - endAngle); out property volume: (normalizeAngle(angle - startAngle) / degreesFilledWithLights) * (totalLights + 1); out property startAngle: 120deg; out property endAngle: 60deg; out property angleGap: startAngle - endAngle; in-out property angle: startAngle; 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 [ 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; } } } ] 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; 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 lastAngle; property newAngle; property nextAngle; property hovering: Math.pow(relativeX / 1px, 2) + Math.pow(relativeY / 1px, 2) < metalKnob.width / 2px * metalKnob.height / 2px; property touching: false; width: base.width; height: base.height; mouse-cursor: touching ? move : hovering ? grab : default; pointer-event(event) => { relativeX = ta.mouse-x - centerX; relativeY = ta.mouse-y - centerY; newAngle = AppState.normalizeAngle(atan2(relativeY / 1px, relativeX / 1px)); if event.kind == PointerEventKind.down { if hovering { touching = true; lastAngle = newAngle; } } else if event.kind == PointerEventKind.up { touching = false; } else if event.kind == PointerEventKind.move { if touching { nextAngle = AppState.normalizeAngle(AppState.angle - lastAngle + newAngle); // Check if the new angle is within the start and end angles if nextAngle >= AppState.startAngle || nextAngle <= AppState.endAngle { AppState.angle = nextAngle; } lastAngle = newAngle; } } } } metalKnob := Image { source: @image-url("images/metal-dial.png"); rotation-angle: AppState.angle; } Image { source: @image-url("images/metal-lights.png"); } Image { source: @image-url("images/dial-trim.png"); } Rectangle { x: parent.width / 2; y: parent.height / 2; Image { property r: 0.5; x: r * root.width / 2 * AppState.angle.cos(); y: r * root.height / 2 * AppState.angle.sin(); source: @image-url("images/indicator.png"); } for i in AppState.totalLights + 1: Light { index: i; } } }