diff --git a/Cargo.lock b/Cargo.lock index dbb5c53..feed7b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,6 +127,12 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + [[package]] name = "as-slice" version = "0.2.1" @@ -697,6 +703,7 @@ dependencies = [ "objc2 0.5.2", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", + "x11-clipboard", ] [[package]] @@ -809,6 +816,15 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "ctor" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83cf0d42651b16c6dfe68685716d18480d18a9c39c62d76e8cf3eb6ed5d8bcbf" +dependencies = [ + "dtor", +] + [[package]] name = "cursor-icon" version = "1.2.0" @@ -1016,6 +1032,12 @@ dependencies = [ "linux-raw-sys 0.9.4", ] +[[package]] +name = "dtor" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edf234dd1594d6dd434a8fb8cada51ddbbc593e40e4a01556a0b31c62da2775b" + [[package]] name = "either" version = "1.17.0" @@ -1488,6 +1510,16 @@ dependencies = [ "slab", ] +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.4", + "windows-link", +] + [[package]] name = "getopts" version = "0.2.24" @@ -1570,6 +1602,7 @@ dependencies = [ "cgl", "dispatch2", "glutin_egl_sys", + "glutin_glx_sys", "glutin_wgl_sys", "libloading", "objc2 0.6.4", @@ -1579,6 +1612,19 @@ dependencies = [ "once_cell", "raw-window-handle", "windows-sys 0.52.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" +dependencies = [ + "cfg_aliases", + "glutin", + "raw-window-handle", + "winit", ] [[package]] @@ -1591,6 +1637,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "glutin_glx_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" +dependencies = [ + "gl_generator", + "x11-dl", +] + [[package]] name = "glutin_wgl_sys" version = "0.6.1" @@ -1746,6 +1802,8 @@ dependencies = [ "copypasta", "derive_more", "futures", + "glutin", + "glutin-winit", "i-slint-common", "i-slint-core", "i-slint-core-macros", @@ -4281,7 +4339,9 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" dependencies = [ + "as-raw-xcb-connection", "bytemuck", + "fastrand", "js-sys", "ndk", "objc2 0.6.4", @@ -4291,10 +4351,13 @@ dependencies = [ "objc2-quartz-core 0.3.2", "raw-window-handle", "redox_syscall 0.5.18", + "rustix 1.1.4", + "tiny-xlib", "tracing", "wasm-bindgen", "web-sys", "windows-sys 0.61.2", + "x11rb", ] [[package]] @@ -4554,6 +4617,19 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tiny-xlib" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90a0ca3ee6a69f2ad28fd11621a4c3f03b371f366be500b64df260c4ffbafb4" +dependencies = [ + "as-raw-xcb-connection", + "ctor", + "libloading", + "pkg-config", + "tracing", +] + [[package]] name = "tinystr" version = "0.8.3" @@ -5317,6 +5393,7 @@ dependencies = [ "atomic-waker", "bitflags 2.13.1", "block2 0.5.1", + "bytemuck", "calloop 0.13.0", "cfg_aliases", "concurrent-queue", @@ -5332,6 +5409,7 @@ dependencies = [ "objc2-foundation 0.2.2", "objc2-ui-kit 0.2.2", "orbclient", + "percent-encoding", "pin-project", "raw-window-handle", "redox_syscall 0.4.1", @@ -5344,6 +5422,8 @@ dependencies = [ "web-sys", "web-time", "windows-sys 0.52.0", + "x11-dl", + "x11rb", "xkbcommon-dl", ] @@ -5390,6 +5470,48 @@ dependencies = [ "tap", ] +[[package]] +name = "x11-clipboard" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662d74b3d77e396b8e5beb00b9cad6a9eccf40b2ef68cc858784b14c41d535a3" +dependencies = [ + "libc", + "x11rb", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 1.1.4", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + [[package]] name = "xattr" version = "1.6.1" diff --git a/Cargo.toml b/Cargo.toml index edc53a3..0354590 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,5 +11,8 @@ framebuffer = "0.3.1" linfb = { version = "0.2.1", default-features = false } slint = { version = "1.17.1", default-features = false, features = ["std", "compat-1-2", "libm", "renderer-software", "unsafe-single-threaded"] } +[features] +dev = ["slint/backend-winit-x11"] + [build-dependencies] slint-build = "1.17.1" diff --git a/src/main.rs b/src/main.rs index 6fe9ee3..383c3f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,21 @@ mod rgba; +#[cfg(not(feature = "dev"))] mod trekstor; -use crate::trekstor::Trekstor; - slint::include_modules!(); fn main() -> Result<(), slint::PlatformError> { - slint::platform::set_platform(Box::new(Trekstor::new())).expect("set platform"); + #[cfg(not(feature = "dev"))] + slint::platform::set_platform(Box::new(crate::trekstor::Trekstor::new())) + .expect("set platform"); - let ui = AppWindow::new()?; + let ui = MainWindow::new()?; - let ui_handle = ui.as_weak(); - ui.on_request_increase_value(move || { - let ui = ui_handle.unwrap(); - ui.set_counter(ui.get_counter() + 1); - }); + // let ui_handle = ui.as_weak(); + // ui.on_request_increase_value(move || { + // let ui = ui_handle.unwrap(); + // ui.set_counter(ui.get_counter() + 1); + // }); ui.run() } diff --git a/ui/images/blue-hint.png b/ui/images/blue-hint.png new file mode 100644 index 0000000..442e17a Binary files /dev/null and b/ui/images/blue-hint.png differ diff --git a/ui/images/dial-frame.png b/ui/images/dial-frame.png new file mode 100644 index 0000000..a765a1c Binary files /dev/null and b/ui/images/dial-frame.png differ diff --git a/ui/images/dial-trim.png b/ui/images/dial-trim.png new file mode 100644 index 0000000..7548d06 Binary files /dev/null and b/ui/images/dial-trim.png differ diff --git a/ui/images/indicator.png b/ui/images/indicator.png new file mode 100644 index 0000000..aea782f Binary files /dev/null and b/ui/images/indicator.png differ diff --git a/ui/images/light-hole.png b/ui/images/light-hole.png new file mode 100644 index 0000000..0a2bb48 Binary files /dev/null and b/ui/images/light-hole.png differ diff --git a/ui/images/light.png b/ui/images/light.png new file mode 100644 index 0000000..71ba5b9 Binary files /dev/null and b/ui/images/light.png differ diff --git a/ui/images/metal-dial.png b/ui/images/metal-dial.png new file mode 100644 index 0000000..c0095ff Binary files /dev/null and b/ui/images/metal-dial.png differ diff --git a/ui/images/metal-lights.png b/ui/images/metal-lights.png new file mode 100644 index 0000000..2208500 Binary files /dev/null and b/ui/images/metal-lights.png differ diff --git a/ui/images/slint-logo-square-dark.png b/ui/images/slint-logo-square-dark.png new file mode 100644 index 0000000..c3eb018 Binary files /dev/null and b/ui/images/slint-logo-square-dark.png differ diff --git a/ui/main.slint b/ui/main.slint index daa217e..d21ec17 100644 --- a/ui/main.slint +++ b/ui/main.slint @@ -1,50 +1,130 @@ -import { Button, VerticalBox, HorizontalBox, CheckBox } from "std-widgets.slint"; +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT -export component AppWindow inherits Window { - default-font-size: 24px; - in-out property counter: 42; - callback request-increase-value(); - HorizontalBox { - VerticalBox { - width: 50%; - Text { - text: "Counter: \{root.counter}"; +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; + } } - Button { - text: "Increase value"; - clicked => { - root.request-increase-value(); + out { + animate blueLed.opacity { + duration: 600ms; + easing: ease-out-sine; } } } - VerticalBox { - width: 50%; - alignment: start; - CheckBox { - text: "Armory Light"; - } - CheckBox { - text: "Bedroom Light"; - } - CheckBox { - text: "Hallway Light"; - } - CheckBox { - text: "Kitchen Light"; - } - CheckBox { - text: "Living Room Light"; - } - CheckBox { - text: "Shower Light"; - } - CheckBox { - text: "Utilities Light"; - enabled: false; - } - CheckBox { - text: "WC Light"; + ] + + Image { + source: @image-url("images/light-hole.png"); + } + + blueLed := Image { + source: @image-url("images/light.png"); + opacity: 0; + } +} + +export component MainWindow 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: ta.mouse-x - centerX; + property relativeY: ta.mouse-y - centerY; + property lastAngle; + 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 ? grabbing : hovering ? grab : default; + + pointer-event(event) => { + let 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 { + let 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"); + transform-rotation: 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; + } + } }