diff --git a/.gitignore b/.gitignore index fedaa2b..f88e412 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target .env +*.old diff --git a/Cargo.lock b/Cargo.lock index 70aed36..87a1596 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -614,6 +614,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link", ] @@ -702,6 +703,35 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" +dependencies = [ + "cookie", + "document-features", + "idna", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + [[package]] name = "copypasta" version = "0.10.2" @@ -898,6 +928,7 @@ dependencies = [ name = "dashboard" version = "0.1.0" dependencies = [ + "chrono", "dotenvy", "embedded-graphics", "embedded-graphics-core", @@ -905,10 +936,12 @@ dependencies = [ "framebuffer", "hass-rs", "linfb", + "serde", "serde_json", "slint", "slint-build", "tokio", + "ureq", ] [[package]] @@ -923,6 +956,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + [[package]] name = "derive_builder" version = "0.7.2" @@ -1028,6 +1067,15 @@ dependencies = [ "libloading", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -1593,6 +1641,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if 1.0.4", + "libc", + "wasi", +] + [[package]] name = "getrandom" version = "0.3.4" @@ -2679,6 +2738,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -2983,6 +3048,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-derive" version = "0.4.2" @@ -3654,6 +3725,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -4054,6 +4131,20 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if 1.0.4", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rowan" version = "0.16.1" @@ -4135,6 +4226,41 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.23" @@ -4571,6 +4697,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "svgtypes" version = "0.16.1" @@ -4751,6 +4883,36 @@ dependencies = [ "zune-jpeg", ] +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tiny-skia" version = "0.12.0" @@ -5082,12 +5244,49 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "unty" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" +[[package]] +name = "ureq" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" +dependencies = [ + "base64", + "cookie_store", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +dependencies = [ + "base64", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -5128,6 +5327,12 @@ dependencies = [ "xmlwriter", ] +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -5307,6 +5512,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weezl" version = "0.1.12" @@ -5949,6 +6163,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + [[package]] name = "zerotrie" version = "0.2.4" diff --git a/Cargo.toml b/Cargo.toml index 404897c..e29bdbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +chrono = { version = "0.4.45", features = ["serde"] } dotenvy = "0.15.7" embedded-graphics = "0.8.2" embedded-graphics-core = "0.4.1" @@ -11,9 +12,11 @@ evdev = "0.13.2" framebuffer = "0.3.1" hass-rs = "0.5.0" linfb = { version = "0.2.1", default-features = false } +serde = { version = "1.0.229", features = ["derive"] } serde_json = "1.0.151" slint = { version = "1.17.1", default-features = false, features = ["std", "compat-1-2", "libm", "renderer-software", "unsafe-single-threaded"] } tokio = { version = "1.53.1", features = ["full"] } +ureq = { version = "3.3.0", default-features = false, features = ["json", "rustls"] } [features] dev = ["slint/backend-winit-x11"] diff --git a/src/ha.rs b/src/ha.rs new file mode 100644 index 0000000..86f2c4b --- /dev/null +++ b/src/ha.rs @@ -0,0 +1,412 @@ +use std::{collections::HashMap, time::Duration}; + +use chrono::{DateTime, Local, NaiveDate}; +use serde::Deserialize; +use serde_json::json; + +use crate::state::{DashboardSnapshot, LIGHTS, THERMOSTATS}; + +const REQUEST_TIMEOUT: Duration = Duration::from_secs(12); + +#[derive(Clone)] +pub struct HaClient { + pub agent: ureq::Agent, + pub base_url: String, + pub token: String, +} + +#[derive(Deserialize)] +pub struct HaState { + pub entity_id: String, + pub state: String, + #[serde(default)] + pub attributes: HaAttributes, +} + +#[derive(Default, Deserialize)] +pub struct HaAttributes { + pub current_temperature: Option, + pub temperature: Option, + pub hvac_action: Option, + #[serde(default)] + pub hvac_modes: Vec, + pub fan_mode: Option, + #[serde(default)] + pub fan_modes: Vec, +} + +#[derive(Deserialize)] +pub struct HaForecastServiceResponse { + pub service_response: HashMap, +} + +#[derive(Deserialize)] +pub struct HaForecast { + #[serde(default)] + pub forecast: Vec, +} + +#[derive(Deserialize)] +pub struct HaForecastItem { + pub temperature: Option, +} + +#[derive(Deserialize)] +pub struct HaCalendar { + pub entity_id: String, + pub name: String, +} + +#[derive(Deserialize)] +pub struct HaCalendarEvent { + pub summary: String, + pub start: HaCalendarTime, +} + +#[derive(Deserialize)] +pub struct HaCalendarTime { + #[serde(rename = "dateTime")] + pub date_time: Option, + pub date: Option, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ClimateState { + pub entity_id: String, + pub name: String, + pub current: Option, + pub target: Option, + pub mode: String, + pub action: String, + pub available: bool, + pub supports_heat: bool, + pub supports_cool: bool, + pub fan_mode: String, + pub fan_modes: Vec, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct LightState { + pub entity_id: String, + pub name: String, + pub on: bool, + pub available: bool, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CalendarEvent { + pub summary: String, + pub date: String, + pub time: String, + pub calendar: String, + pub sort_epoch: i64, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct BatteryState { + pub level: u8, + pub kind: &'static str, +} + +impl HaClient { + pub fn from_environment() -> Result { + let base_url = std::env::var("HASS_URL") + .map_err(|_| "HASS_URL is not configured".to_string())? + .trim_end_matches('/') + .to_string(); + let token = + std::env::var("HASS_TOKEN").map_err(|_| "HASS_TOKEN is not configured".to_string())?; + let agent = ureq::Agent::new_with_config( + ureq::config::Config::builder() + .timeout_send_request(Some(REQUEST_TIMEOUT)) + .timeout_recv_response(Some(REQUEST_TIMEOUT)) + .timeout_send_body(Some(REQUEST_TIMEOUT)) + .build(), + ); + Ok(Self { + agent, + base_url, + token, + }) + } + + pub fn get_all_states(&self) -> Result, String> { + self.agent + .get(&format!("{}/api/states", self.base_url)) + .header("Authorization", &format!("Bearer {}", self.token)) + .header("Accept", "application/json") + .call() + .map_err(|error| format!("states: {error}"))? + .body_mut() + .read_json() + .map_err(|error| format!("states: invalid response: {error}")) + } + + pub fn get_calendars(&self) -> Result, String> { + self.agent + .get(&format!("{}/api/calendars", self.base_url)) + .header("Authorization", &format!("Bearer {}", self.token)) + .header("Accept", "application/json") + .call() + .map_err(|error| format!("calendars: {error}"))? + .body_mut() + .read_json() + .map_err(|error| format!("calendars: invalid response: {error}")) + } + + pub fn get_calendar_events( + &self, + entity_id: &str, + start: &str, + end: &str, + ) -> Result, String> { + self.agent + .get(&format!("{}/api/calendars/{entity_id}", self.base_url)) + .query("start", start) + .query("end", end) + .header("Authorization", &format!("Bearer {}", self.token)) + .header("Accept", "application/json") + .call() + .map_err(|error| format!("{entity_id}: {error}"))? + .body_mut() + .read_json() + .map_err(|error| format!("{entity_id}: invalid response: {error}")) + } + + pub fn fetch_calendar_events(&self) -> Result, String> { + let now = Local::now(); + let start = now.to_rfc3339(); + let end = (now + chrono::Duration::days(30)).to_rfc3339(); + let mut upcoming = Vec::new(); + + for calendar in self.get_calendars()? { + let Ok(events) = self.get_calendar_events(&calendar.entity_id, &start, &end) else { + continue; + }; + for event in events { + let Some((sort_epoch, date, time)) = format_calendar_time(&event.start) else { + continue; + }; + upcoming.push(CalendarEvent { + summary: event.summary, + date, + time, + calendar: calendar.name.clone(), + sort_epoch, + }); + } + } + upcoming.sort_by_key(|event| event.sort_epoch); + upcoming.truncate(7); + Ok(upcoming) + } + + pub fn call_service( + &self, + domain: &str, + service: &str, + body: serde_json::Value, + ) -> Result<(), String> { + self.agent + .post(&format!( + "{}/api/services/{domain}/{service}", + self.base_url + )) + .header("Authorization", &format!("Bearer {}", self.token)) + .header("Accept", "application/json") + .send_json(body) + .map_err(|error| format!("{domain}.{service}: {error}"))?; + Ok(()) + } + + pub fn get_daily_high(&self, entity_id: &str) -> Result, String> { + let response: HaForecastServiceResponse = self + .agent + .post(&format!( + "{}/api/services/weather/get_forecasts?return_response", + self.base_url + )) + .header("Authorization", &format!("Bearer {}", self.token)) + .header("Accept", "application/json") + .send_json(json!({ "entity_id": entity_id, "type": "daily" })) + .map_err(|error| format!("weather.get_forecasts: {error}"))? + .body_mut() + .read_json() + .map_err(|error| format!("weather.get_forecasts: invalid response: {error}"))?; + + Ok(response + .service_response + .get(entity_id) + .and_then(|weather| weather.forecast.first()) + .and_then(|forecast| forecast.temperature)) + } + + pub fn set_temperature(&self, entity_id: &str, temperature: f64) -> Result<(), String> { + self.call_service( + "climate", + "set_temperature", + json!({ "entity_id": entity_id, "temperature": temperature }), + ) + } + + pub fn set_mode(&self, entity_id: &str, mode: &str) -> Result<(), String> { + self.call_service( + "climate", + "set_hvac_mode", + json!({ "entity_id": entity_id, "hvac_mode": mode }), + ) + } + + pub fn set_fan_mode(&self, entity_id: &str, fan_mode: &str) -> Result<(), String> { + self.call_service( + "climate", + "set_fan_mode", + json!({ "entity_id": entity_id, "fan_mode": fan_mode }), + ) + } + + pub fn set_light(&self, entity_id: &str, on: bool) -> Result<(), String> { + self.call_service( + "light", + if on { "turn_on" } else { "turn_off" }, + json!({ "entity_id": entity_id }), + ) + } + + pub fn fetch_snapshot(&self) -> Result { + let states: HashMap = self + .get_all_states()? + .into_iter() + .map(|state| (state.entity_id.clone(), state)) + .collect(); + + let weather = states + .get("weather.forecast_home") + .or_else(|| states.get("weather.kmco")) + .ok_or_else(|| "weather entity unavailable".to_string())?; + let is_night = states + .get("sun.sun") + .is_some_and(|sun| sun.state == "below_horizon"); + let outside_high = self + .get_daily_high(&weather.entity_id) + .unwrap_or_else(|error| { + eprintln!("Daily forecast refresh failed: {error}"); + None + }); + + let thermostats = THERMOSTATS + .iter() + .map(|(entity_id, name)| match states.get(*entity_id) { + Some(state) if state.state != "unavailable" && state.state != "unknown" => { + let action = state + .attributes + .hvac_action + .clone() + .unwrap_or_else(|| state.state.clone()); + ClimateState { + entity_id: (*entity_id).into(), + name: (*name).into(), + current: state.attributes.current_temperature, + target: state.attributes.temperature, + mode: state.state.clone(), + action, + available: true, + supports_heat: state + .attributes + .hvac_modes + .iter() + .any(|mode| mode == "heat"), + supports_cool: state + .attributes + .hvac_modes + .iter() + .any(|mode| mode == "cool"), + fan_mode: state.attributes.fan_mode.clone().unwrap_or_default(), + fan_modes: state.attributes.fan_modes.clone(), + } + } + _ => ClimateState { + entity_id: (*entity_id).into(), + name: (*name).into(), + current: None, + target: None, + mode: "unavailable".into(), + action: "unavailable".into(), + available: false, + supports_heat: false, + supports_cool: false, + fan_mode: String::new(), + fan_modes: Vec::new(), + }, + }) + .collect(); + + let lights = LIGHTS + .iter() + .map(|(entity_id, name)| match states.get(*entity_id) { + Some(state) if state.state != "unavailable" && state.state != "unknown" => { + LightState { + entity_id: (*entity_id).into(), + name: (*name).into(), + on: state.state == "on", + available: true, + } + } + _ => LightState { + entity_id: (*entity_id).into(), + name: (*name).into(), + on: false, + available: false, + }, + }) + .collect(); + + let calendar_events = self.fetch_calendar_events().unwrap_or_else(|error| { + eprintln!("Calendar refresh failed: {error}"); + Vec::new() + }); + + Ok(DashboardSnapshot { + outside_temperature: weather.attributes.temperature, + outside_high, + outside_condition: weather.state.clone(), + weather_kind: weather_kind(&weather.state, is_night).into(), + thermostats, + lights, + calendar_events, + }) + } +} + +pub fn format_calendar_time(value: &HaCalendarTime) -> Option<(i64, String, String)> { + if let Some(date_time) = value.date_time.as_deref() { + let parsed = DateTime::parse_from_rfc3339(date_time).ok()?; + return Some(( + parsed.timestamp(), + parsed.format("%a %b %-d").to_string().to_uppercase(), + parsed.format("%-I:%M %p").to_string().to_uppercase(), + )); + } + let date = NaiveDate::parse_from_str(value.date.as_deref()?, "%Y-%m-%d").ok()?; + let sort_epoch = date.and_hms_opt(0, 0, 0)?.and_utc().timestamp(); + Some(( + sort_epoch, + date.format("%a %b %-d").to_string().to_uppercase(), + "ALL DAY".into(), + )) +} + +pub fn weather_kind(condition: &str, is_night: bool) -> &'static str { + match condition { + "sunny" => "sun", + "clear-night" => "moon", + "partlycloudy" if is_night => "cloud-moon", + "partlycloudy" => "cloud-sun", + "rainy" => "cloud-rain", + "pouring" | "windy" | "windy-variant" => "cloud-rain-wind", + "lightning" | "lightning-rainy" => "cloud-lightning", + "snowy" | "snowy-rainy" | "hail" => "snowflake", + "fog" => "cloud-fog", + _ => "cloud", + } +} diff --git a/src/main.rs b/src/main.rs index 7fbbfc5..45388c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,193 +1,338 @@ -use hass_rs::{HassClient, HassEntity}; -use slint::{ModelRc, VecModel}; -use std::{ - collections::HashMap, - env::var, - sync::{Arc, Mutex}, - thread::sleep, - time::Duration, -}; - slint::include_modules!(); -mod rgba; -#[cfg(not(feature = "dev"))] +mod ha; +mod state; mod trekstor; +mod utils; -// #[derive(Clone, Debug, PartialEq)] -// struct LightState { -// entity_id: String, -// name: String, -// on: bool, -// available: bool, -// } +use std::sync::{Arc, Mutex}; -struct Database { - client: HassClient, - entities: HashMap, -} +use chrono::Local; -impl Database { - pub async fn update_light(&mut self, light: &str, checked: bool) { - self.client - .call_service( - "light".to_string(), - if checked { - "turn_on".to_string() - } else { - "turn_off".to_string() - }, - Some(serde_json::json!({ - "entity_id": light.to_string() - })), - ) - .await - .ok(); - } - - pub fn get_lights(&self) -> Vec { - let mut hidden = vec![]; - let lights = self - .entities - .iter() - .filter_map(|(id, e)| { - if id.starts_with("light.") { - if let Some(children) = e.attributes["entity_id"].as_array() { - for child in children { - hidden.push(child.as_str().unwrap().to_string()); - } - } - return Some(LightData { - available: e.state != "unavailable", - entity_id: e.entity_id.clone().into(), - name: e.attributes["friendly_name"].as_str().unwrap().into(), - on: e.state == "on", - }); - } - - None - }) - .collect::>(); - - let mut lights = lights - .iter() - .filter(|e| { - !e.entity_id.ends_with("screen") && !hidden.contains(&e.entity_id.to_string()) - }) - .map(|e| e.to_owned()) - .collect::>(); - lights.sort_by(|a, b| a.name.cmp(&b.name)); - lights - } -} - -type DB = Arc>; +use crate::{ha::HaClient, state::DashboardSnapshot, utils::*}; #[tokio::main] -async fn main() -> Result<(), slint::PlatformError> { +async fn main() -> Result<(), Box> { dotenvy::dotenv().ok(); #[cfg(not(feature = "dev"))] slint::platform::set_platform(Box::new(crate::trekstor::Trekstor::new())) .expect("set platform"); - let main_window = MainWindow::new()?; + let app = AppWindow::new()?; + app.on_quit(|| std::process::exit(0)); - // start home_assistant webscoket - let db = home_assisstant_websocket_client().await; + let state = Arc::new(Mutex::new(DashboardSnapshot::loading())); + apply_snapshot(&app, &state.lock().expect("dashboard state poisoned"), None); - let db_weak = db.clone(); - let weak_window = main_window.as_weak(); - main_window.on_toggle_light(move |light, checked| { - let db_weaker = db_weak.clone(); - slint::spawn_local(async move { - db_weaker - .lock() - .unwrap() - .update_light(&light.entity_id.to_string(), checked) - .await; - }) - .ok(); - - let lights = db_weak.lock().unwrap().get_lights(); - weak_window - .upgrade_in_event_loop(move |ui| { - ui.set_lights(ModelRc::new(VecModel::from(lights))); - }) - .ok(); - // println!("{} {}", entity_id, checked); - }); - - let weak_window = main_window.as_weak(); - tokio::spawn(async move { - loop { - let lights = db.lock().unwrap().get_lights(); - weak_window - .upgrade_in_event_loop(move |ui| { - ui.set_lights(ModelRc::new(VecModel::from(lights))); - }) - .ok(); - sleep(Duration::from_secs(1)); + app.on_select_room({ + let weak = app.as_weak(); + let state = state.clone(); + move |index| { + let Some(app) = weak.upgrade() else { return }; + app.set_selected_index(index); + let snapshot = state.lock().expect("dashboard state poisoned"); + apply_selected(&app, &snapshot); + app.set_action_status("USE - / + OR SELECT A MODE".into()); } }); - main_window.run() + match HaClient::from_environment() { + Ok(client) => { + app.on_change_target({ + let weak = app.as_weak(); + let state = state.clone(); + let client = client.clone(); + move |delta| { + let Some(app) = weak.upgrade() else { return }; + let index = app.get_selected_index().max(0) as usize; + let (entity_id, target, snapshot) = { + let mut snapshot = state.lock().expect("dashboard state poisoned"); + let Some(thermostat) = snapshot.thermostats.get_mut(index) else { + return; + }; + if !thermostat.available { + app.set_action_status("THERMOSTAT UNAVAILABLE".into()); + return; + } + let target = thermostat.target.or(thermostat.current).unwrap_or(68.0) + + f64::from(delta); + thermostat.target = Some(target); + (thermostat.entity_id.clone(), target, snapshot.clone()) + }; + apply_snapshot(&app, &snapshot, None); + app.set_action_status("SAVING SET POINT".into()); + + let weak = weak.clone(); + let client = client.clone(); + std::thread::spawn(move || match client.set_temperature(&entity_id, target) { + Ok(()) => set_action_status(&weak, "SET POINT SAVED".into()), + Err(error) => { + eprintln!("Set temperature failed: {error}"); + set_action_status(&weak, "SET POINT FAILED".into()); + } + }); + } + }); + + app.on_set_mode({ + let weak = app.as_weak(); + let state = state.clone(); + let client = client.clone(); + move |mode| { + let Some(app) = weak.upgrade() else { return }; + let index = app.get_selected_index().max(0) as usize; + let (entity_id, mode_text, snapshot) = { + let mut snapshot = state.lock().expect("dashboard state poisoned"); + let Some(thermostat) = snapshot.thermostats.get_mut(index) else { + return; + }; + if !thermostat.available { + app.set_action_status("THERMOSTAT UNAVAILABLE".into()); + return; + } + let mode_text = mode.to_string(); + let supported = mode_text == "off" + || (mode_text == "heat" && thermostat.supports_heat) + || (mode_text == "cool" && thermostat.supports_cool); + if !supported { + app.set_action_status("MODE NOT SUPPORTED".into()); + return; + } + thermostat.mode = mode_text.clone(); + thermostat.action = mode_text.clone(); + (thermostat.entity_id.clone(), mode_text, snapshot.clone()) + }; + apply_snapshot(&app, &snapshot, None); + app.set_action_status("SAVING MODE".into()); + + let weak = weak.clone(); + let client = client.clone(); + std::thread::spawn(move || match client.set_mode(&entity_id, &mode_text) { + Ok(()) => set_action_status(&weak, "MODE SAVED".into()), + Err(error) => { + eprintln!("Set mode failed: {error}"); + set_action_status(&weak, "MODE FAILED".into()); + } + }); + } + }); + + app.on_set_fan_mode({ + let weak = app.as_weak(); + let state = state.clone(); + let client = client.clone(); + move |requested_mode| { + let Some(app) = weak.upgrade() else { return }; + let index = app.get_selected_index().max(0) as usize; + let (entity_id, fan_mode, snapshot) = { + let mut snapshot = state.lock().expect("dashboard state poisoned"); + let Some(thermostat) = snapshot.thermostats.get_mut(index) else { + return; + }; + if !thermostat.available { + app.set_action_status("THERMOSTAT UNAVAILABLE".into()); + return; + } + let Some(fan_mode) = thermostat + .fan_modes + .iter() + .find(|mode| mode.eq_ignore_ascii_case(requested_mode.as_str())) + .cloned() + else { + app.set_action_status("FAN SPEED NOT SUPPORTED".into()); + return; + }; + thermostat.fan_mode = fan_mode.clone(); + (thermostat.entity_id.clone(), fan_mode, snapshot.clone()) + }; + apply_snapshot(&app, &snapshot, None); + app.set_action_status("SAVING FAN SPEED".into()); + + let weak = weak.clone(); + let client = client.clone(); + std::thread::spawn(move || match client.set_fan_mode(&entity_id, &fan_mode) { + Ok(()) => set_action_status(&weak, "FAN SPEED SAVED".into()), + Err(error) => { + eprintln!("Set fan mode failed: {error}"); + set_action_status(&weak, "FAN SPEED FAILED".into()); + } + }); + } + }); + + app.on_toggle_light({ + let weak = app.as_weak(); + let state = state.clone(); + let client = client.clone(); + move |index| { + let index = index.max(0) as usize; + let (entity_id, turn_on, snapshot) = { + let mut snapshot = state.lock().expect("dashboard state poisoned"); + let Some(light) = snapshot.lights.get_mut(index) else { + return; + }; + if !light.available { + return; + } + light.on = !light.on; + (light.entity_id.clone(), light.on, snapshot.clone()) + }; + if let Some(app) = weak.upgrade() { + apply_snapshot(&app, &snapshot, None); + } + + let weak = weak.clone(); + let client = client.clone(); + std::thread::spawn(move || match client.set_light(&entity_id, turn_on) { + Ok(()) => set_action_status(&weak, "LIGHT UPDATED".into()), + Err(error) => { + eprintln!("Set light failed: {error}"); + set_action_status(&weak, "LIGHT UPDATE FAILED".into()); + } + }); + } + }); + + app.on_refresh({ + let weak = app.as_weak(); + let state = state.clone(); + let client = client.clone(); + move || { + let Some(app) = weak.upgrade() else { return }; + app.set_action_status("REFRESHING".into()); + let weak = weak.clone(); + let state = state.clone(); + let client = client.clone(); + std::thread::spawn(move || fetch_once(&client, &state, &weak)); + } + }); + + spawn_poll_loop(client, state, app.as_weak()); + } + Err(error) => { + eprintln!("Home Assistant configuration error: {error}"); + app.set_action_status("HOME ASSISTANT CONFIG MISSING".into()); + } + } + + app.run()?; + + Ok(()) } -// todo: add channel to send actions to HA, maybe on the db object? prolly not, right? -async fn home_assisstant_websocket_client() -> DB { - let hass_ws_url = var("HASS_WS_URL").expect("HASS_WS_URL not set in environment"); - let hass_token = var("HASS_TOKEN").expect("HA_TOKEN not set in environment"); - - let mut client = HassClient::new(&hass_ws_url) - .await - .expect("Failed to connect"); - - client - .auth_with_longlivedtoken(&hass_token) - .await - .expect("Not able to authenticate"); - - let db = Arc::new(Mutex::new(Database { - client, - entities: Default::default(), - })); - - let mut event_receiver = db - .lock() - .unwrap() - .client - .subscribe_event("state_changed") - .await - .expect("Failed to subscribe"); - - let states = db - .lock() - .unwrap() - .client - .get_states() - .await - .expect("unable to get states"); - states.iter().for_each(|entity| { - db.lock() - .expect("Database poisoned") - .entities - .insert(entity.entity_id.clone(), entity.clone()); +fn spawn_poll_loop( + client: HaClient, + state: Arc>, + weak: slint::Weak, +) { + std::thread::spawn(move || { + loop { + fetch_once(&client, &state, &weak); + std::thread::sleep(std::time::Duration::from_secs(5)); + } }); +} - let db_weak = db.clone(); - tokio::spawn(async move { - while let Some(message) = event_receiver.recv().await { - if let Some(entity) = &message.event.data.new_state { - println!("{}", entity); - db_weak - .lock() - .expect("Database poisoned") - .entities - .insert(entity.entity_id.clone(), entity.clone()); +fn fetch_once( + client: &HaClient, + state: &Arc>, + weak: &slint::Weak, +) { + match client.fetch_snapshot() { + Ok(snapshot) => { + let changed = { + let mut current = state.lock().expect("dashboard state poisoned"); + if *current == snapshot { + false + } else { + *current = snapshot.clone(); + true + } + }; + if changed { + let weak = weak.clone(); + let updated = format!("UPDATED {}", Local::now().format("%-I:%M %p")); + let _ = slint::invoke_from_event_loop(move || { + if let Some(app) = weak.upgrade() { + apply_snapshot(&app, &snapshot, Some(updated)); + } + }); } } - }); - - db + Err(error) => { + eprintln!("Home Assistant refresh failed: {error}"); + set_action_status(weak, "HOME ASSISTANT UNAVAILABLE".into()); + } + } +} + +fn apply_selected(app: &AppWindow, snapshot: &DashboardSnapshot) { + let index = app.get_selected_index().max(0) as usize; + let Some(thermostat) = snapshot.thermostats.get(index) else { + return; + }; + app.set_selected_name(thermostat.name.as_str().into()); + app.set_selected_target(format_temperature(thermostat.target).into()); + app.set_selected_mode(pretty_label(&thermostat.mode).to_uppercase().into()); + app.set_selected_supports_heat(thermostat.supports_heat); + app.set_selected_supports_cool(thermostat.supports_cool); + app.set_selected_supports_fan(!thermostat.fan_modes.is_empty()); + app.set_selected_fan_mode(pretty_label(&thermostat.fan_mode).to_uppercase().into()); +} + +fn apply_snapshot(app: &AppWindow, snapshot: &DashboardSnapshot, updated_text: Option) { + app.set_outside_temperature(format_temperature(snapshot.outside_temperature).into()); + app.set_outside_high( + snapshot + .outside_high + .map(|high| format!("HIGH {}", format_temperature(Some(high)))) + .unwrap_or_else(|| "HIGH --".into()) + .into(), + ); + app.set_outside_condition( + pretty_label(&snapshot.outside_condition) + .to_uppercase() + .into(), + ); + app.set_weather_kind(snapshot.weather_kind.as_str().into()); + // app.set_living_fan_on(snapshot.living_fan.on); + // app.set_living_fan_available(snapshot.living_fan.available); + if let Some(event) = snapshot.calendar_events.first() { + app.set_next_event_label( + format!("NEXT / {} / {}", event.date, event.calendar.to_uppercase()).into(), + ); + app.set_next_event_title(event.summary.as_str().into()); + app.set_next_event_time(event.time.as_str().into()); + } else { + app.set_next_event_label("NEXT EVENT".into()); + app.set_next_event_title("NO UPCOMING EVENTS".into()); + app.set_next_event_time("".into()); + } + + app.set_thermostats(snapshot.into()); + app.set_lights(snapshot.into()); + app.set_calendar_events(snapshot.into()); + let on_count = snapshot.lights.iter().filter(|light| light.on).count(); + let available = snapshot + .lights + .iter() + .filter(|light| light.available) + .count(); + app.set_lights_summary(format!("{on_count} ON / {available} AVAILABLE").into()); + apply_selected(app, snapshot); + + if let Some(updated_text) = updated_text { + app.set_updated_text(updated_text.clone().into()); + app.set_action_status(updated_text.into()); + } +} + +fn set_action_status(weak: &slint::Weak, message: String) { + let weak = weak.clone(); + let _ = slint::invoke_from_event_loop(move || { + if let Some(app) = weak.upgrade() { + app.set_action_status(message.into()); + } + }); } diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..be7dfbe --- /dev/null +++ b/src/state.rs @@ -0,0 +1,112 @@ +use slint::{ModelRc, SharedString, VecModel}; + +use crate::{CalendarData, LightData, ThermostatData, ha::*, utils::*}; + +pub const THERMOSTATS: [(&str, &str); 1] = [("climate.thermostat", "Thermostat")]; + +pub const LIGHTS: [(&str, &str); 8] = [ + ("light.armory_light", "Armory"), + ("light.bedroom_light", "Bedroom"), + ("light.hallway_light", "Hallway"), + ("light.kitchen_light", "Kitchen"), + ("light.living_room", "Living Room"), + ("light.shower_light", "Shower"), + ("light.utilities", "Utilities"), + ("light.light.wc_light", "WC"), +]; + +#[derive(Clone, Debug, PartialEq)] +pub struct DashboardSnapshot { + pub outside_temperature: Option, + pub outside_high: Option, + pub outside_condition: String, + pub weather_kind: String, + pub thermostats: Vec, + pub lights: Vec, + pub calendar_events: Vec, +} + +impl DashboardSnapshot { + pub fn loading() -> Self { + Self { + outside_temperature: None, + outside_high: None, + outside_condition: "Connecting".into(), + weather_kind: "cloud".into(), + thermostats: THERMOSTATS + .iter() + .map(|(entity_id, name)| ClimateState { + entity_id: (*entity_id).into(), + name: (*name).into(), + current: None, + target: None, + mode: "unknown".into(), + action: "unknown".into(), + available: false, + supports_heat: false, + supports_cool: false, + fan_mode: String::new(), + fan_modes: Vec::new(), + }) + .collect(), + lights: LIGHTS + .iter() + .map(|(entity_id, name)| LightState { + entity_id: (*entity_id).into(), + name: (*name).into(), + on: false, + available: false, + }) + .collect(), + calendar_events: Vec::new(), + } + } +} + +impl From<&DashboardSnapshot> for ModelRc { + fn from(value: &DashboardSnapshot) -> Self { + let rows = value + .thermostats + .iter() + .map(|thermostat| ThermostatData { + name: SharedString::from(thermostat.name.as_str()), + current: SharedString::from(format_temperature(thermostat.current)), + target: SharedString::from(format_temperature(thermostat.target)), + state: SharedString::from(pretty_label(&thermostat.action).to_uppercase()), + available: thermostat.available, + }) + .collect::>(); + ModelRc::new(VecModel::from(rows)) + } +} + +impl From<&DashboardSnapshot> for ModelRc { + fn from(value: &DashboardSnapshot) -> Self { + let rows = value + .lights + .iter() + .map(|light| LightData { + name: SharedString::from(light.name.as_str()), + on: light.on, + available: light.available, + }) + .collect::>(); + ModelRc::new(VecModel::from(rows)) + } +} + +impl From<&DashboardSnapshot> for ModelRc { + fn from(value: &DashboardSnapshot) -> Self { + let rows = value + .calendar_events + .iter() + .map(|event| CalendarData { + date: SharedString::from(event.date.as_str()), + time: SharedString::from(event.time.as_str()), + summary: SharedString::from(event.summary.as_str()), + calendar: SharedString::from(event.calendar.as_str()), + }) + .collect::>(); + ModelRc::new(VecModel::from(rows)) + } +} diff --git a/src/trekstor.rs b/src/trekstor.rs index 0082858..ecb453b 100644 --- a/src/trekstor.rs +++ b/src/trekstor.rs @@ -1,3 +1,9 @@ +mod input; +mod rgba; +mod screen; + +pub use rgba::Rgba; + use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; @@ -11,9 +17,6 @@ use slint::{EventLoopError, LogicalPosition, PhysicalSize, Rgb8Pixel, WindowSize use self::input::InputState; use self::screen::Screen; -mod input; -mod screen; - pub struct Context { pub screen: Screen, pub input: InputState, diff --git a/src/rgba.rs b/src/trekstor/rgba.rs similarity index 100% rename from src/rgba.rs rename to src/trekstor/rgba.rs diff --git a/src/trekstor/screen.rs b/src/trekstor/screen.rs index 53c6a80..9dc1b44 100644 --- a/src/trekstor/screen.rs +++ b/src/trekstor/screen.rs @@ -1,7 +1,7 @@ use embedded_graphics::{pixelcolor::Rgb888, prelude::*, primitives::Rectangle}; use linfb::{Framebuffer, shape::Color}; -use crate::rgba::Rgba; +use crate::trekstor::Rgba; pub struct Screen { pub framebuffer: Framebuffer, diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..e415179 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,24 @@ +pub fn pretty_label(value: &str) -> String { + let value = match value { + "partlycloudy" => "partly cloudy", + other => other, + }; + value + .split(['_', '-', ' ']) + .filter(|word| !word.is_empty()) + .map(|word| { + let mut chars = word.chars(); + match chars.next() { + Some(first) => first.to_uppercase().collect::() + chars.as_str(), + None => String::new(), + } + }) + .collect::>() + .join(" ") +} + +pub fn format_temperature(value: Option) -> String { + value + .map(|temperature| format!("{temperature:.0}°")) + .unwrap_or_else(|| "--".into()) +} diff --git a/ui/components/component_card.slint b/ui/components/component_card.slint deleted file mode 100644 index c0f602b..0000000 --- a/ui/components/component_card.slint +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { - MaterialText, - OutlinedCard, - MaterialTypography, - MaterialStyleMetrics -} from "../material.slint"; - -export component ComponentCard { - in property title; - - HorizontalLayout { - alignment: center; - - VerticalLayout { - spacing: 4px; - alignment: start; - - MaterialText { - horizontal_alignment: center; - text: root.title; - style: MaterialTypography.title_medium; - } - - OutlinedCard { - min-width: 360px; - - VerticalLayout { - padding_left: 2px; - padding_right: self.padding_left; - padding_top: MaterialStyleMetrics.padding_8; - padding_bottom: self.padding_top; - spacing: MaterialStyleMetrics.spacing_8; - - @children - } - } - } - } -} diff --git a/ui/components/group.slint b/ui/components/group.slint deleted file mode 100644 index d07ed90..0000000 --- a/ui/components/group.slint +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { - MaterialText, - MaterialPalette, - MaterialTypography -} from "../material.slint"; - -export component Group { - in property title; - - Rectangle { - border_radius: 12px; - background: MaterialPalette.surface_bright; - - VerticalLayout { - alignment: start; - padding: 8px; - spacing: 16px; - - MaterialText { - horizontal_alignment: center; - text: root.title; - style: MaterialTypography.headline_small; - } - - @children - } - } -} diff --git a/ui/components/text_icon_button.slint b/ui/components/text_icon_button.slint deleted file mode 100644 index f51d7b4..0000000 --- a/ui/components/text_icon_button.slint +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { - IconButton, - MaterialText -} from "../material.slint"; - -export component TextIconButton { - in property icon <=> button.icon; - in property text <=> label.text; - - VerticalLayout { - HorizontalLayout { - alignment: center; - - button := IconButton {} - } - - label := MaterialText { - horizontal_alignment: center; - } - } -} diff --git a/ui/fonts/Roboto-VariableFont.ttf b/ui/fonts/Roboto-VariableFont.ttf deleted file mode 100644 index bba55f6..0000000 Binary files a/ui/fonts/Roboto-VariableFont.ttf and /dev/null differ diff --git a/ui/fonts/Roboto-VariableFont.ttf.license b/ui/fonts/Roboto-VariableFont.ttf.license deleted file mode 100644 index 7b98a7a..0000000 --- a/ui/fonts/Roboto-VariableFont.ttf.license +++ /dev/null @@ -1,3 +0,0 @@ -SPDX-FileCopyrightText: Google Inc. - -SPDX-License-Identifier: Apache-2.0 diff --git a/ui/icons.slint b/ui/icons.slint deleted file mode 100644 index b7819b2..0000000 --- a/ui/icons.slint +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -export global FilledIcons { - out property account_box: @image-url("./icons/filled/account_box.svg"); - out property calendar_month: @image-url("./icons/filled/calendar_month.svg"); - out property close: @image-url("./icons/filled/close.svg"); - out property delete: @image-url("./icons/filled/delete.svg"); - out property favorite: @image-url("./icons/filled/favorite.svg"); - out property inbox: @image-url("./icons/filled/inbox.svg"); - out property music_note: @image-url("./icons/filled/music_note.svg"); - out property play_arrow: @image-url("./icons/filled/play_arrow.svg"); - out property today: @image-url("./icons/filled/today.svg"); - out property add: @image-url("./icons/filled/add.svg"); - out property chat_bubble: @image-url("./icons/filled/chat_bubble.svg"); - out property edit: @image-url("./icons/filled/edit.svg"); - out property group: @image-url("./icons/filled/group.svg"); - out property mail: @image-url("./icons/filled/mail.svg"); - out property outbox: @image-url("./icons/filled/outbox.svg"); - out property search: @image-url("./icons/filled/search.svg"); - out property video_chat: @image-url("./icons/filled/video_call.svg"); - out property bookmark: @image-url("./icons/filled/bookmark.svg"); - out property check: @image-url("./icons/filled/check.svg"); - out property explore: @image-url("./icons/filled/explore.svg"); - out property image: @image-url("./icons/filled/image.svg"); - out property more_vert: @image-url("./icons/filled/more_vert.svg"); - out property pets: @image-url("./icons/filled/pets.svg"); - out property settings: @image-url("./icons/filled/settings.svg"); - out property view_week: @image-url("./icons/filled/view_week.svg"); - out property light_mode: @image-url("./icons/filled/light_mode.svg"); - out property dark_mode: @image-url("./icons/filled/dark_mode.svg"); - out property dashboard: @image-url("./icons/filled/dashboard.svg"); - out property pause: @image-url("./icons/filled/pause.svg"); - out property archive: @image-url("./icons/filled/archive.svg"); - out property share: @image-url("./icons/filled/share.svg"); - out property menu: @image-url("./icons/filled/menu.svg"); - out property account_circle: @image-url("./icons/filled/account_circle.svg"); -} - -export global OutlinedIcons { - out property account_box: @image-url("./icons/outlined/account_box.svg"); - out property calendar_month: @image-url("./icons/outlined/calendar_month.svg"); - out property delete: @image-url("./icons/outlined/delete.svg"); - out property favorite: @image-url("./icons/outlined/favorite.svg"); - out property inbox: @image-url("./icons/outlined/inbox.svg"); - out property music_note: @image-url("./icons/outlined/music_note.svg"); - out property play_arrow: @image-url("./icons/outlined/play_arrow.svg"); - out property today: @image-url("./icons/outlined/today.svg"); - out property add: @image-url("./icons/outlined/add.svg"); - out property chat_bubble: @image-url("./icons/outlined/chat_bubble.svg"); - out property edit: @image-url("./icons/outlined/edit.svg"); - out property group: @image-url("./icons/outlined/group.svg"); - out property mail: @image-url("./icons/outlined/mail.svg"); - out property outbox: @image-url("./icons/outlined/outbox.svg"); - out property search: @image-url("./icons/outlined/search.svg"); - out property video_chat: @image-url("./icons/outlined/video_chat.svg"); - out property bookmark: @image-url("./icons/outlined/bookmark.svg"); - out property check: @image-url("./icons/outlined/check.svg"); - out property explore: @image-url("./icons/outlined/explore.svg"); - out property image: @image-url("./icons/outlined/image.svg"); - out property more_vert: @image-url("./icons/outlined/more_vert.svg"); - out property pets: @image-url("./icons/outlined/pets.svg"); - out property settings: @image-url("./icons/outlined/settings.svg"); - out property view_week: @image-url("./icons/outlined/view_week.svg"); - out property light_mode: @image-url("./icons/outlined/light_mode.svg"); - out property dark_mode: @image-url("./icons/outlined/dark_mode.svg"); - out property dashboard: @image-url("./icons/outlined/dashboard.svg"); - out property pause: @image-url("./icons/outlined/pause.svg"); - out property archive: @image-url("./icons/outlined/archive.svg"); - out property share: @image-url("./icons/outlined/share.svg"); - out property schedule: @image-url("./icons/outlined/schedule.svg"); - out property palette: @image-url("./icons/outlined/palette.svg"); - } diff --git a/ui/icons/NOTICE.txt b/ui/icons/NOTICE.txt new file mode 100644 index 0000000..504b532 --- /dev/null +++ b/ui/icons/NOTICE.txt @@ -0,0 +1 @@ +Icons are from Lucide (https://lucide.dev), licensed under the ISC License. diff --git a/ui/icons/battery-charging.svg b/ui/icons/battery-charging.svg new file mode 100644 index 0000000..61dd829 --- /dev/null +++ b/ui/icons/battery-charging.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/ui/icons/battery-full.svg b/ui/icons/battery-full.svg new file mode 100644 index 0000000..4e759e7 --- /dev/null +++ b/ui/icons/battery-full.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/ui/icons/battery-low.svg b/ui/icons/battery-low.svg new file mode 100644 index 0000000..b82aa82 --- /dev/null +++ b/ui/icons/battery-low.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/icons/battery-medium.svg b/ui/icons/battery-medium.svg new file mode 100644 index 0000000..602814f --- /dev/null +++ b/ui/icons/battery-medium.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/ui/icons/battery.svg b/ui/icons/battery.svg new file mode 100644 index 0000000..f147888 --- /dev/null +++ b/ui/icons/battery.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/icons/cloud-fog.svg b/ui/icons/cloud-fog.svg new file mode 100644 index 0000000..34ac0c7 --- /dev/null +++ b/ui/icons/cloud-fog.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/icons/cloud-lightning.svg b/ui/icons/cloud-lightning.svg new file mode 100644 index 0000000..5c1d330 --- /dev/null +++ b/ui/icons/cloud-lightning.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/icons/cloud-moon.svg b/ui/icons/cloud-moon.svg new file mode 100644 index 0000000..4474c3a --- /dev/null +++ b/ui/icons/cloud-moon.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/icons/cloud-rain-wind.svg b/ui/icons/cloud-rain-wind.svg new file mode 100644 index 0000000..f34f405 --- /dev/null +++ b/ui/icons/cloud-rain-wind.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/icons/cloud-rain.svg b/ui/icons/cloud-rain.svg new file mode 100644 index 0000000..75e32f3 --- /dev/null +++ b/ui/icons/cloud-rain.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/icons/cloud-sun.svg b/ui/icons/cloud-sun.svg new file mode 100644 index 0000000..131c462 --- /dev/null +++ b/ui/icons/cloud-sun.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/icons/cloud.svg b/ui/icons/cloud.svg new file mode 100644 index 0000000..6cf5f81 --- /dev/null +++ b/ui/icons/cloud.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/icons/filled/account_box.svg b/ui/icons/filled/account_box.svg deleted file mode 100644 index e74d64c..0000000 --- a/ui/icons/filled/account_box.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/account_circle.svg b/ui/icons/filled/account_circle.svg deleted file mode 100644 index ce42e4f..0000000 --- a/ui/icons/filled/account_circle.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/ui/icons/filled/add.svg b/ui/icons/filled/add.svg deleted file mode 100644 index 58f73e9..0000000 --- a/ui/icons/filled/add.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/archive.svg b/ui/icons/filled/archive.svg deleted file mode 100644 index 024f790..0000000 --- a/ui/icons/filled/archive.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/bookmark.svg b/ui/icons/filled/bookmark.svg deleted file mode 100644 index 1d507a6..0000000 --- a/ui/icons/filled/bookmark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/calendar_month.svg b/ui/icons/filled/calendar_month.svg deleted file mode 100644 index e8fa87b..0000000 --- a/ui/icons/filled/calendar_month.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/chat_bubble.svg b/ui/icons/filled/chat_bubble.svg deleted file mode 100644 index d447be3..0000000 --- a/ui/icons/filled/chat_bubble.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/check.svg b/ui/icons/filled/check.svg deleted file mode 100644 index 8676ca3..0000000 --- a/ui/icons/filled/check.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/close.svg b/ui/icons/filled/close.svg deleted file mode 100644 index 373c01a..0000000 --- a/ui/icons/filled/close.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/ui/icons/filled/dark_mode.svg b/ui/icons/filled/dark_mode.svg deleted file mode 100644 index 3d141c4..0000000 --- a/ui/icons/filled/dark_mode.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/dashboard.svg b/ui/icons/filled/dashboard.svg deleted file mode 100644 index 1682295..0000000 --- a/ui/icons/filled/dashboard.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/delete.svg b/ui/icons/filled/delete.svg deleted file mode 100644 index 0969a4d..0000000 --- a/ui/icons/filled/delete.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/edit.svg b/ui/icons/filled/edit.svg deleted file mode 100644 index a2841f9..0000000 --- a/ui/icons/filled/edit.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/explore.svg b/ui/icons/filled/explore.svg deleted file mode 100644 index 4243343..0000000 --- a/ui/icons/filled/explore.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/favorite.svg b/ui/icons/filled/favorite.svg deleted file mode 100644 index 80f9a05..0000000 --- a/ui/icons/filled/favorite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/group.svg b/ui/icons/filled/group.svg deleted file mode 100644 index bd9a133..0000000 --- a/ui/icons/filled/group.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/image.svg b/ui/icons/filled/image.svg deleted file mode 100644 index 6ccc16a..0000000 --- a/ui/icons/filled/image.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/inbox.svg b/ui/icons/filled/inbox.svg deleted file mode 100644 index 92dbdab..0000000 --- a/ui/icons/filled/inbox.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/light_mode.svg b/ui/icons/filled/light_mode.svg deleted file mode 100644 index 4a29693..0000000 --- a/ui/icons/filled/light_mode.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/mail.svg b/ui/icons/filled/mail.svg deleted file mode 100644 index 876dbcd..0000000 --- a/ui/icons/filled/mail.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/menu.svg b/ui/icons/filled/menu.svg deleted file mode 100644 index 8c03678..0000000 --- a/ui/icons/filled/menu.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/ui/icons/filled/more_vert.svg b/ui/icons/filled/more_vert.svg deleted file mode 100644 index f2427f8..0000000 --- a/ui/icons/filled/more_vert.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/music_note.svg b/ui/icons/filled/music_note.svg deleted file mode 100644 index cdc42fc..0000000 --- a/ui/icons/filled/music_note.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/outbox.svg b/ui/icons/filled/outbox.svg deleted file mode 100644 index 351e92d..0000000 --- a/ui/icons/filled/outbox.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/pause.svg b/ui/icons/filled/pause.svg deleted file mode 100644 index c20e156..0000000 --- a/ui/icons/filled/pause.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/pets.svg b/ui/icons/filled/pets.svg deleted file mode 100644 index e4d7d48..0000000 --- a/ui/icons/filled/pets.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/play_arrow.svg b/ui/icons/filled/play_arrow.svg deleted file mode 100644 index c5e1a4c..0000000 --- a/ui/icons/filled/play_arrow.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/search.svg b/ui/icons/filled/search.svg deleted file mode 100644 index cd9fd53..0000000 --- a/ui/icons/filled/search.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/settings.svg b/ui/icons/filled/settings.svg deleted file mode 100644 index f255a58..0000000 --- a/ui/icons/filled/settings.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/share.svg b/ui/icons/filled/share.svg deleted file mode 100644 index ed3e530..0000000 --- a/ui/icons/filled/share.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/today.svg b/ui/icons/filled/today.svg deleted file mode 100644 index 43abd72..0000000 --- a/ui/icons/filled/today.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/video_call.svg b/ui/icons/filled/video_call.svg deleted file mode 100644 index e1a21e1..0000000 --- a/ui/icons/filled/video_call.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/filled/view_week.svg b/ui/icons/filled/view_week.svg deleted file mode 100644 index d26bb06..0000000 --- a/ui/icons/filled/view_week.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/moon.svg b/ui/icons/moon.svg new file mode 100644 index 0000000..edb7977 --- /dev/null +++ b/ui/icons/moon.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/icons/outlined/account_box.svg b/ui/icons/outlined/account_box.svg deleted file mode 100644 index 3875711..0000000 --- a/ui/icons/outlined/account_box.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/add.svg b/ui/icons/outlined/add.svg deleted file mode 100644 index 58f73e9..0000000 --- a/ui/icons/outlined/add.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/archive.svg b/ui/icons/outlined/archive.svg deleted file mode 100644 index 7318216..0000000 --- a/ui/icons/outlined/archive.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/bookmark.svg b/ui/icons/outlined/bookmark.svg deleted file mode 100644 index 812bffe..0000000 --- a/ui/icons/outlined/bookmark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/calendar_month.svg b/ui/icons/outlined/calendar_month.svg deleted file mode 100644 index c35f75e..0000000 --- a/ui/icons/outlined/calendar_month.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/chat_bubble.svg b/ui/icons/outlined/chat_bubble.svg deleted file mode 100644 index d447be3..0000000 --- a/ui/icons/outlined/chat_bubble.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/check.svg b/ui/icons/outlined/check.svg deleted file mode 100644 index 5e3f01e..0000000 --- a/ui/icons/outlined/check.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/dark_mode.svg b/ui/icons/outlined/dark_mode.svg deleted file mode 100644 index c38c55e..0000000 --- a/ui/icons/outlined/dark_mode.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/dashboard.svg b/ui/icons/outlined/dashboard.svg deleted file mode 100644 index 01ef592..0000000 --- a/ui/icons/outlined/dashboard.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/delete.svg b/ui/icons/outlined/delete.svg deleted file mode 100644 index 14508a2..0000000 --- a/ui/icons/outlined/delete.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/edit.svg b/ui/icons/outlined/edit.svg deleted file mode 100644 index 01f34c7..0000000 --- a/ui/icons/outlined/edit.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/explore.svg b/ui/icons/outlined/explore.svg deleted file mode 100644 index b62e5ed..0000000 --- a/ui/icons/outlined/explore.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/favorite.svg b/ui/icons/outlined/favorite.svg deleted file mode 100644 index 80f9a05..0000000 --- a/ui/icons/outlined/favorite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/group.svg b/ui/icons/outlined/group.svg deleted file mode 100644 index 7fa6132..0000000 --- a/ui/icons/outlined/group.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/image.svg b/ui/icons/outlined/image.svg deleted file mode 100644 index 5400d38..0000000 --- a/ui/icons/outlined/image.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/inbox.svg b/ui/icons/outlined/inbox.svg deleted file mode 100644 index fc55d23..0000000 --- a/ui/icons/outlined/inbox.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/light_mode.svg b/ui/icons/outlined/light_mode.svg deleted file mode 100644 index d66b3ab..0000000 --- a/ui/icons/outlined/light_mode.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/mail.svg b/ui/icons/outlined/mail.svg deleted file mode 100644 index f3809a5..0000000 --- a/ui/icons/outlined/mail.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/more_vert.svg b/ui/icons/outlined/more_vert.svg deleted file mode 100644 index f2427f8..0000000 --- a/ui/icons/outlined/more_vert.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/music_note.svg b/ui/icons/outlined/music_note.svg deleted file mode 100644 index e7be7bf..0000000 --- a/ui/icons/outlined/music_note.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/outbox.svg b/ui/icons/outlined/outbox.svg deleted file mode 100644 index ae8bda3..0000000 --- a/ui/icons/outlined/outbox.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/palette.svg b/ui/icons/outlined/palette.svg deleted file mode 100644 index 6475eb3..0000000 --- a/ui/icons/outlined/palette.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/pause.svg b/ui/icons/outlined/pause.svg deleted file mode 100644 index c20e156..0000000 --- a/ui/icons/outlined/pause.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/pets.svg b/ui/icons/outlined/pets.svg deleted file mode 100644 index e4d7d48..0000000 --- a/ui/icons/outlined/pets.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/play_arrow.svg b/ui/icons/outlined/play_arrow.svg deleted file mode 100644 index 842d32f..0000000 --- a/ui/icons/outlined/play_arrow.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/schedule.svg b/ui/icons/outlined/schedule.svg deleted file mode 100644 index 6ed61e0..0000000 --- a/ui/icons/outlined/schedule.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/ui/icons/outlined/search.svg b/ui/icons/outlined/search.svg deleted file mode 100644 index cd9fd53..0000000 --- a/ui/icons/outlined/search.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/settings.svg b/ui/icons/outlined/settings.svg deleted file mode 100644 index b36758c..0000000 --- a/ui/icons/outlined/settings.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/share.svg b/ui/icons/outlined/share.svg deleted file mode 100644 index b3295db..0000000 --- a/ui/icons/outlined/share.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/today.svg b/ui/icons/outlined/today.svg deleted file mode 100644 index b3faffc..0000000 --- a/ui/icons/outlined/today.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/video_chat.svg b/ui/icons/outlined/video_chat.svg deleted file mode 100644 index 798cd1e..0000000 --- a/ui/icons/outlined/video_chat.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/outlined/view_week.svg b/ui/icons/outlined/view_week.svg deleted file mode 100644 index e7a9e7d..0000000 --- a/ui/icons/outlined/view_week.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/icons/plug.svg b/ui/icons/plug.svg new file mode 100644 index 0000000..d8e0420 --- /dev/null +++ b/ui/icons/plug.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/ui/icons/refresh-ccw-dot.svg b/ui/icons/refresh-ccw-dot.svg new file mode 100644 index 0000000..59cf7cc --- /dev/null +++ b/ui/icons/refresh-ccw-dot.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/ui/icons/snowflake.svg b/ui/icons/snowflake.svg new file mode 100644 index 0000000..f044805 --- /dev/null +++ b/ui/icons/snowflake.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/icons/sun.svg b/ui/icons/sun.svg new file mode 100644 index 0000000..9563744 --- /dev/null +++ b/ui/icons/sun.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/main.slint b/ui/main.slint index f4d1727..7d8d81d 100644 --- a/ui/main.slint +++ b/ui/main.slint @@ -1,56 +1,333 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { LineEdit } from "std-widgets.slint"; -import { ComponentCard } from "./components/component_card.slint"; - -import { Horizontal, Vertical, Switch } from "./material.slint"; - -import "./fonts/Roboto-VariableFont.ttf"; -import { FilledIcons, OutlinedIcons } from "./icons.slint"; +export struct ThermostatData { + name: string, + current: string, + target: string, + state: string, + available: bool, +} export struct LightData { name: string, - entity_id: string, on: bool, available: bool, } -component LightSwitch inherits Horizontal { - in property light; - callback toggled(LightData, bool); +export struct CalendarData { + date: string, + time: string, + summary: string, + calendar: string, +} - alignment: end; - label := Text { - text: root.light.name; - vertical-alignment: center; - horizontal-alignment: right; - font-size: 24px; - font-weight: root.light.on ? 700 : 500; +component ControlButton inherits Rectangle { + in property label; + in property active: false; + in property enabled: true; + callback activated(); + + border-width: 2px; + border-color: black; + background: root.enabled && (touch.pressed || root.active) ? black : white; + + touch := TouchArea { + clicked => { if root.enabled { root.activated(); } } } - switch := Switch { - enabled: root.light.available; - checked: root.light.on; - checked_state_changed(checked) => { root.toggled(root.light, checked) } + + Text { + text: root.label; + color: root.enabled && (touch.pressed || root.active) ? white : black; + font-size: root.label == "−" || root.label == "+" ? 48px : 16px; + font-weight: 700; + horizontal-alignment: center; + vertical-alignment: center; } } +component NavigationButton inherits Rectangle { + in property label; + in property active: false; + callback activated(); -// The window the user interacts with. Edits to the inputs propagate to -// the SystemTrayIcon instance via the host language's callbacks. -export component MainWindow inherits Window { - title: "Dashboard"; - preferred-width: 1024px; - preferred-height: 600px; - default-font-family: "Roboto"; + border-width: 2px; + border-color: black; + background: root.active || touch.pressed ? black : white; + touch := TouchArea { + clicked => { root.activated(); } + } + + Text { + text: root.label; + color: root.active || touch.pressed ? white : black; + font-size: 17px; + font-weight: 700; + letter-spacing: 1px; + horizontal-alignment: center; + vertical-alignment: center; + } +} + +component ScreenRefreshButton inherits Rectangle { + callback activated(); + + border-width: touch.pressed ? 4px : 2px; + border-color: black; + background: white; + + touch := TouchArea { + clicked => { root.activated(); } + } + + Image { + x: 12px; + y: 14px; + width: 32px; + height: 32px; + source: @image-url("icons/refresh-ccw-dot.svg"); + image-fit: contain; + } + + Text { + x: 48px; + width: parent.width - 54px; + height: parent.height; + text: "CLEAR"; + color: black; + font-size: 14px; + font-weight: 700; + horizontal-alignment: center; + vertical-alignment: center; + } +} + +component ThermostatRow inherits Rectangle { + in property room; + in property row-index; + in property selected: false; + callback row-activated(int); + + background: root.selected ? black : white; + + TouchArea { + clicked => { root.row-activated(root.row-index); } + } + + Rectangle { + x: 0; + y: parent.height - 1px; + width: parent.width; + height: 1px; + background: root.selected ? white : black; + } + + Text { + x: 16px; + y: 8px; + width: 330px; + height: 34px; + text: root.room.name; + color: root.selected ? white : black; + font-size: 25px; + font-weight: 700; + vertical-alignment: center; + } + + Text { + x: 17px; + y: 46px; + width: 330px; + height: 20px; + text: root.room.available ? root.room.state : "UNAVAILABLE"; + color: root.selected ? white : black; + font-size: 14px; + font-weight: 700; + vertical-alignment: center; + } + + Text { + x: 365px; + y: 2px; + width: 135px; + height: 70px; + text: root.room.current; + color: root.selected ? white : black; + font-size: 52px; + font-weight: 700; + horizontal-alignment: center; + vertical-alignment: center; + } + + Text { + x: 530px; + y: 5px; + width: 136px; + height: 66px; + text: root.room.target; + color: root.selected ? white : black; + font-size: 39px; + font-weight: 700; + horizontal-alignment: center; + vertical-alignment: center; + } +} + +component LightTile inherits Rectangle { + in property light; + in property light-index; + callback toggled(int); + + border-width: 2px; + border-color: black; + background: root.light.on ? black : white; + + TouchArea { + clicked => { if root.light.available { root.toggled(root.light-index); } } + } + + Text { + x: 14px; + y: 7px; + width: parent.width - 88px; + height: 29px; + text: root.light.name; + color: root.light.on ? white : black; + font-size: 18px; + font-weight: 700; + overflow: elide; + vertical-alignment: center; + } + + Text { + x: 14px; + y: 37px; + width: parent.width - 88px; + height: 21px; + text: !root.light.available ? "UNAVAILABLE" : root.light.on ? "ON" : "OFF"; + color: root.light.on ? white : black; + font-size: 14px; + font-weight: 700; + vertical-alignment: center; + } + + Text { + x: parent.width - 56px; + y: 8px; + width: 38px; + height: parent.height - 16px; + text: root.light.on ? "●" : "○"; + color: root.light.on ? white : black; + font-size: 25px; + horizontal-alignment: center; + vertical-alignment: center; + } +} + +component CalendarRow inherits Rectangle { + in property event; + + background: white; + + Rectangle { + x: 0; + y: parent.height - 2px; + width: parent.width; + height: 2px; + background: black; + } + + Text { + x: 0; + y: 14px; + width: 150px; + height: 27px; + text: root.event.date; + color: black; + font-size: 18px; + font-weight: 700; + } + + Text { + x: 0; + y: 47px; + width: 150px; + height: 24px; + text: root.event.time; + color: black; + font-size: 16px; + font-weight: 700; + } + + Text { + x: 175px; + y: 11px; + width: parent.width - 175px; + height: 39px; + text: root.event.summary; + color: black; + font-size: 24px; + font-weight: 700; + overflow: elide; + vertical-alignment: center; + } + + Text { + x: 175px; + y: 55px; + width: parent.width - 175px; + height: 23px; + text: root.event.calendar; + color: black; + font-size: 15px; + font-weight: 700; + vertical-alignment: center; + } +} + +export component AppWindow inherits Window { + width: 1024px; + height: 600px; + background: white; + + in property <[ThermostatData]> thermostats; in property <[LightData]> lights; - callback toggle-light(LightData, bool); - + in property <[CalendarData]> calendar-events; in-out property current-page: 0; + in-out property selected-index: 0; + in property selected-name: "--"; + in property selected-target: "--"; + in property selected-mode: "--"; + in property selected-supports-heat: false; + in property selected-supports-cool: false; + in property selected-supports-fan: false; + in property selected-fan-mode: "--"; + in property outside-temperature: "--"; + in property outside-high: "HIGH --"; + in property outside-condition: "CONNECTING"; + in property weather-kind: "cloud"; in property next-event-label: "NEXT EVENT"; in property next-event-title: "LOADING CALENDAR"; - in property next-event-time: "6:00 pm"; + in property next-event-time: "--"; + in property battery-level: "--%"; + in property battery-kind: "battery"; + in property updated-text: "WAITING FOR HOME ASSISTANT"; + in property action-status: "TAP A ROOM TO CONTROL"; + in property lights-summary: "LOADING LIGHTS"; + in property living-fan-on: false; + in property living-fan-available: false; + in-out property sleeping: false; + + callback select-room(int); + callback change-target(int); + callback set-mode(string); + callback set-fan-mode(string); + callback toggle-living-fan(); + callback toggle-light(int); + callback refresh(); + callback clean-screen(); + callback user-activity(); + callback enter-sleep(); + callback wake-from-sleep(); + callback quit(); Rectangle { x: 0; @@ -60,28 +337,21 @@ export component MainWindow inherits Window { background: black; } - Rectangle { - x: 0; - y: 0; - width: parent.width / 2; - height: parent.height; - background: black; - } - TouchArea { x: 0; y: 5px; width: parent.width - 78px; height: 78px; - clicked => { root.current-page = 2; } + clicked => { root.user-activity(); root.current-page = 2; } } Text { x: 38px; y: 12px; - width: (parent.width / 2) - (38px * 2) - 100px; + width: parent.width - 128px; height: 18px; text: root.next-event-label; + color: black; font-size: 14px; font-weight: 700; } @@ -89,9 +359,10 @@ export component MainWindow inherits Window { Text { x: 38px; y: 32px; - width: (parent.width / 2) - (38px * 2) - 100px; + width: 390px; height: 36px; text: root.next-event-title; + color: black; font-size: 22px; font-weight: 700; overflow: elide; @@ -99,40 +370,268 @@ export component MainWindow inherits Window { } Text { - x: (root.width / 2) - self.width - 38px; + x: 438px; y: 32px; width: 100px; height: 36px; text: root.next-event-time; + color: black; font-size: 18px; font-weight: 700; horizontal-alignment: right; vertical-alignment: center; } - Rectangle { - x: 0px; - y: 32px + 36px + 5px; - width: (root.width / 2); - height: root.height - (32px + 36px + 5px); + if root.battery-kind == "battery-charging": Image { image-rendering: smooth; x: 552px; y: 34px; width: 34px; height: 30px; source: @image-url("icons/battery-charging.svg"); image-fit: contain; } + if root.battery-kind == "battery-full": Image { image-rendering: smooth; x: 552px; y: 34px; width: 34px; height: 30px; source: @image-url("icons/battery-full.svg"); image-fit: contain; } + if root.battery-kind == "battery-medium": Image { image-rendering: smooth; x: 552px; y: 34px; width: 34px; height: 30px; source: @image-url("icons/battery-medium.svg"); image-fit: contain; } + if root.battery-kind == "battery-low": Image { image-rendering: smooth; x: 552px; y: 34px; width: 34px; height: 30px; source: @image-url("icons/battery-low.svg"); image-fit: contain; } + if root.battery-kind == "battery": Image { image-rendering: smooth; x: 552px; y: 34px; width: 34px; height: 30px; source: @image-url("icons/battery.svg"); image-fit: contain; } + if root.battery-kind == "plug": Image { image-rendering: smooth; x: 552px; y: 34px; width: 34px; height: 30px; source: @image-url("icons/plug.svg"); image-fit: contain; } + + Text { + x: 590px; + y: 32px; + width: 66px; + height: 36px; + text: root.battery-level; + color: black; + font-size: 17px; + font-weight: 700; + horizontal-alignment: right; + vertical-alignment: center; } Rectangle { - x: (root.width / 2); - y: 5px; - width: (root.width / 2); - height: root.height - 10px; + x: parent.width - 66px; + y: 19px; + width: 44px; + height: 44px; + border-width: 2px; + border-color: black; + background: close-touch.pressed ? black : white; - ComponentCard { - title: "Lights"; + close-touch := TouchArea { + clicked => { root.quit(); } + } - Vertical { - alignment: start; + Text { + text: "×"; + color: close-touch.pressed ? white : black; + font-size: 29px; + horizontal-alignment: center; + vertical-alignment: center; + } + } - for light_data in root.lights: LightSwitch { - light: light_data; - toggled(light, checked) => { root.toggle-light(light, checked); } - } + if root.current-page == 0: Rectangle { + x: 0; + y: 84px; + width: root.width; + height: root.height - 84px; + background: white; + + Text { + x: 38px; + y: 10px; + width: 180px; + height: 21px; + text: "OUTSIDE"; + color: black; + font-size: 16px; + font-weight: 700; + letter-spacing: 2px; + } + + Text { + x: 33px; + y: 28px; + width: 300px; + height: 116px; + text: root.outside-temperature; + color: black; + font-size: 104px; + font-weight: 700; + vertical-alignment: center; + } + + Text { + x: 260px; + y: 101px; + width: 150px; + height: 32px; + text: root.outside-high; + color: black; + font-size: 20px; + font-weight: 700; + vertical-alignment: center; + } + + if root.weather-kind == "sun": Image { image-rendering: smooth; x: 520px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/sun.svg"); image-fit: contain; } + if root.weather-kind == "cloud-sun": Image { image-rendering: smooth; x: 520px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/cloud-sun.svg"); image-fit: contain; } + if root.weather-kind == "cloud-moon": Image { image-rendering: smooth; x: 520px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/cloud-moon.svg"); image-fit: contain; } + if root.weather-kind == "cloud": Image { image-rendering: smooth; x: 520px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/cloud.svg"); image-fit: contain; } + if root.weather-kind == "cloud-rain": Image { image-rendering: smooth; x: 520px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/cloud-rain.svg"); image-fit: contain; } + if root.weather-kind == "cloud-rain-wind": Image { image-rendering: smooth; x: 520px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/cloud-rain-wind.svg"); image-fit: contain; } + if root.weather-kind == "cloud-lightning": Image { image-rendering: smooth; x: 520px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/cloud-lightning.svg"); image-fit: contain; } + if root.weather-kind == "snowflake": Image { image-rendering: smooth; x: 520px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/snowflake.svg"); image-fit: contain; } + if root.weather-kind == "cloud-fog": Image { image-rendering: smooth; x: 520px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/cloud-fog.svg"); image-fit: contain; } + if root.weather-kind == "moon": Image { image-rendering: smooth; x: 520px; y: 20px; width: 120px; height: 105px; source: @image-url("icons/moon.svg"); image-fit: contain; } + + Text { + x: 392px; + y: 126px; + width: parent.width - 430px; + height: 22px; + text: root.outside-condition; + color: black; + font-size: 16px; + font-weight: 700; + horizontal-alignment: right; + } + + Rectangle { x: 38px; y: 151px; width: parent.width - 76px; height: 3px; background: black; } + + Text { x: 38px; y: 173px; width: 260px; height: 24px; text: "THERMOSTATS"; color: black; font-size: 17px; font-weight: 700; letter-spacing: 2px; } + Text { x: 403px; y: 175px; width: 135px; height: 20px; text: "NOW"; color: black; font-size: 14px; font-weight: 700; horizontal-alignment: center; } + Text { x: 568px; y: 175px; width: 136px; height: 20px; text: "SET"; color: black; font-size: 14px; font-weight: 700; horizontal-alignment: center; } + Rectangle { x: 38px; y: 205px; width: parent.width - 76px; height: 1px; background: black; } + + for room[index] in root.thermostats: ThermostatRow { + x: 38px; + y: 206px + index * 80px; + width: root.width - 76px; + height: 80px; + room: room; + row-index: index; + selected: root.selected-index == index; + row-activated(index) => { root.user-activity(); root.select-room(index); } + } + + Rectangle { x: 38px; y: 627px; width: parent.width - 76px; height: 4px; background: black; } + Text { x: 38px; y: 642px; width: 280px; height: 22px; text: root.selected-name + " / " + root.selected-mode; color: black; font-size: 15px; font-weight: 700; overflow: elide; } + Text { x: 34px; y: 661px; width: 190px; height: 78px; text: root.selected-target; color: black; font-size: 62px; font-weight: 700; vertical-alignment: center; } + + ControlButton { x: 236px; y: 662px; width: 78px; height: 78px; label: "−"; activated => { root.user-activity(); root.change-target(-1); } } + ControlButton { x: 324px; y: 662px; width: 78px; height: 78px; label: "+"; activated => { root.user-activity(); root.change-target(1); } } + ControlButton { x: 412px; y: 662px; width: 92px; height: 78px; label: "HEAT"; enabled: root.selected-supports-heat; active: root.selected-mode == "HEAT" || root.selected-mode == "HEATING"; activated => { root.user-activity(); root.set-mode("heat"); } } + ControlButton { x: 514px; y: 662px; width: 92px; height: 78px; label: "COOL"; enabled: root.selected-supports-cool; active: root.selected-mode == "COOL" || root.selected-mode == "COOLING"; activated => { root.user-activity(); root.set-mode("cool"); } } + ControlButton { x: 616px; y: 662px; width: 104px; height: 78px; label: "OFF"; active: root.selected-mode == "OFF"; activated => { root.user-activity(); root.set-mode("off"); } } + + Text { x: 38px; y: 754px; width: 92px; height: 54px; text: "FAN\nSPEED"; color: black; font-size: 14px; font-weight: 700; vertical-alignment: center; } + ControlButton { x: 138px; y: 752px; width: 76px; height: 58px; label: "AUTO"; enabled: root.selected-supports-fan; active: root.selected-fan-mode == "AUTO"; activated => { root.user-activity(); root.set-fan-mode("Auto"); } } + ControlButton { x: 222px; y: 752px; width: 76px; height: 58px; label: "LOW"; enabled: root.selected-supports-fan; active: root.selected-fan-mode == "LOW"; activated => { root.user-activity(); root.set-fan-mode("Low"); } } + ControlButton { x: 306px; y: 752px; width: 76px; height: 58px; label: "MED"; enabled: root.selected-supports-fan; active: root.selected-fan-mode == "MED"; activated => { root.user-activity(); root.set-fan-mode("Med"); } } + ControlButton { x: 390px; y: 752px; width: 76px; height: 58px; label: "HIGH"; enabled: root.selected-supports-fan; active: root.selected-fan-mode == "HIGH"; activated => { root.user-activity(); root.set-fan-mode("High"); } } + ControlButton { x: 482px; y: 752px; width: 238px; height: 58px; label: root.living-fan-available ? root.living-fan-on ? "LIVING FAN ON" : "LIVING FAN OFF" : "LIVING FAN --"; enabled: root.living-fan-available; active: root.living-fan-on; activated => { root.user-activity(); root.toggle-living-fan(); } } + + Text { x: 38px; y: 822px; width: 540px; height: 24px; text: root.action-status; color: black; font-size: 15px; font-weight: 700; overflow: elide; } + Rectangle { + x: parent.width - 152px; y: 814px; width: 114px; height: 40px; border-width: 2px; border-color: black; background: refresh-touch.pressed ? black : white; + refresh-touch := TouchArea { clicked => { root.user-activity(); root.refresh(); } } + Text { text: "REFRESH"; color: refresh-touch.pressed ? white : black; font-size: 14px; font-weight: 700; horizontal-alignment: center; vertical-alignment: center; } + } + } + + if root.current-page == 1: Rectangle { + x: 0; + y: 84px; + width: root.width; + height: root.height - 84px; + background: white; + + Text { x: 38px; y: 18px; width: 230px; height: 30px; text: "LIGHTS"; color: black; font-size: 22px; font-weight: 700; letter-spacing: 2px; } + Text { x: 330px; y: 21px; width: 390px; height: 24px; text: root.lights-summary; color: black; font-size: 15px; font-weight: 700; horizontal-alignment: right; } + Rectangle { x: 38px; y: 55px; width: parent.width - 76px; height: 3px; background: black; } + + for light[index] in root.lights: LightTile { + // x: index < 10 ? 38px : 390px; + // y: 72px + (index < 10 ? index : index - 10) * 74px; + x: Math.mod(index, 2) == 0 ? 38px : 390px; + y: 72px + Math.floor(index / 2) * 74px; + width: 330px; + height: 66px; + light: light; + light-index: index; + toggled(index) => { root.user-activity(); root.toggle-light(index); } + } + } + + if root.current-page == 2: Rectangle { + x: 0; + y: 84px; + width: root.width; + height: root.height - 84px; + background: white; + + Text { x: 38px; y: 18px; width: 260px; height: 30px; text: "CALENDAR"; color: black; font-size: 22px; font-weight: 700; letter-spacing: 2px; } + Text { x: 400px; y: 21px; width: 320px; height: 24px; text: "NEXT 30 DAYS"; color: black; font-size: 15px; font-weight: 700; horizontal-alignment: right; } + Rectangle { x: 38px; y: 55px; width: parent.width - 76px; height: 3px; background: black; } + + for event[index] in root.calendar-events: CalendarRow { + x: 38px; + y: 68px + index * 110px; + width: root.width - 76px; + height: 110px; + event: event; + } + + if root.calendar-events.length == 0: Text { + x: 38px; y: 105px; width: parent.width - 76px; height: 50px; text: "NO UPCOMING EVENTS"; color: black; font-size: 24px; font-weight: 700; + } + } + + NavigationButton { x: 38px; y: parent.height - 80px; width: 168px; height: 60px; label: "CLIMATE"; active: root.current-page == 0; activated => { root.user-activity(); root.current-page = 0; } } + NavigationButton { x: 222px; y: parent.height - 80px; width: 168px; height: 60px; label: "LIGHTS"; active: root.current-page == 1; activated => { root.user-activity(); root.current-page = 1; } } + NavigationButton { x: 406px; y: parent.height - 80px; width: 168px; height: 60px; label: "CALENDAR"; active: root.current-page == 2; activated => { root.user-activity(); root.current-page = 2; } } + ScreenRefreshButton { x: 608px; y: parent.height - 80px; width: 112px; height: 60px; activated => { root.user-activity(); root.clean-screen(); } } + + if root.sleeping: Rectangle { + x: 0; + y: 0; + width: root.width; + height: root.height; + background: #eeeeee; + + Rectangle { + x: 129px; + y: 400px; + width: 500px; + height: 224px; + background: white; + border-width: 3px; + border-color: black; + + Text { + y: 48px; + width: parent.width; + height: 70px; + text: "DISPLAY RESTING"; + color: black; + font-size: 34px; + font-weight: 800; + horizontal-alignment: center; + vertical-alignment: center; + } + + Text { + y: 124px; + width: parent.width; + height: 48px; + text: "TAP TO WAKE"; + color: black; + font-size: 22px; + font-weight: 600; + horizontal-alignment: center; + vertical-alignment: center; + } + } + + TouchArea { + clicked => { + root.sleeping = false; + root.wake-from-sleep(); + root.user-activity(); } } } diff --git a/ui/material.slint b/ui/material.slint deleted file mode 100644 index 4b5316f..0000000 --- a/ui/material.slint +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -// components -export { AppBar, SmallAppBar, MediumAppBar, LargeAppBar } from "./ui/components/app_bar.slint"; -export { Badge } from "./ui/components/badge.slint"; -export { BottomAppBar } from "./ui/components/bottom_app_bar.slint"; -export { CheckState, CheckBox, CheckBoxTile } from "./ui/components/check_box.slint"; -export { ActionChip, FilterChip, InputChip } from "./ui/components/chip.slint"; -export { DatePickerPopup, DatePickerAdapter } from "./ui/components/date_picker.slint"; -export { Dialog, FullscreenDialog } from "./ui/components/dialog.slint"; -export { Drawer, ModalDrawer } from "./ui/components/drawer.slint"; -export { DropDownMenu } from "./ui/components/drop_down_menu.slint"; -export { VerticalDivider, HorizontalDivider } from "./ui/components/divider.slint"; -export { ModalBottomSheet } from "./ui/components/bottom_sheet.slint"; -export { ElevatedCard, FilledCard, OutlinedCard } from "./ui/components/card.slint"; -export { ElevatedButton } from "./ui/components/elevated_button.slint"; -export { Elevation } from "./ui/components/elevation.slint"; -export { ExtendedTouchArea } from "./ui/components/extended_touch_area.slint"; -export { FilledButton } from "./ui/components/filled_button.slint"; -export { FilledIconButton } from "./ui/components/filled_icon_button.slint"; -export { FloatingActionButton, FABStyle } from "./ui/components/floating_action_button.slint"; -export { Grid } from "./ui/components/grid.slint"; -export { Horizontal } from "./ui/components/horizontal.slint"; -export { Icon } from "./ui/components/icon.slint"; -export { IconButton } from "./ui/components/icon_button.slint"; -export { OutlineButton } from "./ui/components/outline_button.slint"; -export { OutlineIconButton } from "./ui/components/outline_icon_button.slint"; -export { Avatar, ListTile } from "./ui/components/list.slint"; -export { ListView } from "./ui/components/list_view.slint"; -export { MaterialText } from "./ui/components/material_text.slint"; -export { MaterialWindow, MaterialWindowAdapter } from "./ui/components/material_window.slint"; -export { PopupMenu } from "./ui/components/menu.slint"; -export { NavigationBar } from "./ui/components/navigation_bar.slint"; -export { NavigationDrawer, ModalNavigationDrawer } from "./ui/components/navigation_drawer.slint"; -export { NavigationRail } from "./ui/components/navigation_rail.slint"; -export { CircularProgressIndicator, LinearProgressIndicator } from "./ui/components/progress_indicator.slint"; -export { RadioButton, RadioButtonTile } from "./ui/components/radio_button.slint"; -export { SearchBar } from "./ui/components/search_bar.slint"; -export { ScrollView } from "./ui/components/scroll_view.slint"; -export { Slider } from "./ui/components/slider.slint"; -export { SnackBar } from "./ui/components/snack_bar.slint"; -export { StateLayerArea, StateLayer, Ripple } from "./ui/components/state_layer.slint"; -export { SegmentedButton } from "./ui/components/segmented_button.slint"; -export { Switch } from "./ui/components/switch.slint"; -export { TabBar, SecondaryTabBar } from "./ui/components/tab_bar.slint"; -export { TextButton } from "./ui/components/text_button.slint"; -export { TextField } from "./ui/components/text_field.slint"; -export { TimePickerPopup, Time } from "./ui/components/time_picker.slint"; -export { TonalButton } from "./ui/components/tonal_button.slint"; -export { TonalIconButton } from "./ui/components/tonal_icon_button.slint"; -export { ToolTip } from "./ui/components/tooltip.slint"; -export { Vertical } from "./ui/components/vertical.slint"; -export { Modal } from "./ui/components/modal.slint"; - -// items -export { ListItem } from "./ui/items/list_item.slint"; -export { NavigationItem, NavigationGroup } from "./ui/items/navigation_item.slint"; -export { MenuItem } from "./ui/items/menu_item.slint"; - -// styles -export { MaterialAnimations } from "./ui/styling/material_animations.slint"; -export { MaterialScheme, MaterialSchemes } from "./ui/styling/material_schemes.slint"; -export { MaterialStyleMetrics } from "./ui/styling/material_style_metrics.slint"; -export { MaterialPalette } from "./ui/styling/material_palette.slint"; -export { MaterialTypography } from "./ui/styling/material_typography.slint"; diff --git a/ui/themes/material_green_theme.json b/ui/themes/material_green_theme.json deleted file mode 100644 index 7cd677f..0000000 --- a/ui/themes/material_green_theme.json +++ /dev/null @@ -1,418 +0,0 @@ -{ - "description": "TYPE: CUSTOM\nMaterial Theme Builder export 2025-08-29 10:20:31", - "seed": "#63A002", - "coreColors": { - "primary": "#63A002" - }, - "extendedColors": [], - "schemes": { - "light": { - "primary": "#4C662B", - "surfaceTint": "#4C662B", - "onPrimary": "#FFFFFF", - "primaryContainer": "#CDEDA3", - "onPrimaryContainer": "#354E16", - "secondary": "#586249", - "onSecondary": "#FFFFFF", - "secondaryContainer": "#DCE7C8", - "onSecondaryContainer": "#404A33", - "tertiary": "#386663", - "onTertiary": "#FFFFFF", - "tertiaryContainer": "#BCECE7", - "onTertiaryContainer": "#1F4E4B", - "error": "#BA1A1A", - "onError": "#FFFFFF", - "errorContainer": "#FFDAD6", - "onErrorContainer": "#93000A", - "background": "#F9FAEF", - "onBackground": "#1A1C16", - "surface": "#F9FAEF", - "onSurface": "#1A1C16", - "surfaceVariant": "#E1E4D5", - "onSurfaceVariant": "#44483D", - "outline": "#75796C", - "outlineVariant": "#C5C8BA", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#2F312A", - "inverseOnSurface": "#F1F2E6", - "inversePrimary": "#B1D18A", - "primaryFixed": "#CDEDA3", - "onPrimaryFixed": "#102000", - "primaryFixedDim": "#B1D18A", - "onPrimaryFixedVariant": "#354E16", - "secondaryFixed": "#DCE7C8", - "onSecondaryFixed": "#151E0B", - "secondaryFixedDim": "#BFCBAD", - "onSecondaryFixedVariant": "#404A33", - "tertiaryFixed": "#BCECE7", - "onTertiaryFixed": "#00201E", - "tertiaryFixedDim": "#A0D0CB", - "onTertiaryFixedVariant": "#1F4E4B", - "surfaceDim": "#DADBD0", - "surfaceBright": "#F9FAEF", - "surfaceContainerLowest": "#FFFFFF", - "surfaceContainerLow": "#F3F4E9", - "surfaceContainer": "#EEEFE3", - "surfaceContainerHigh": "#E8E9DE", - "surfaceContainerHighest": "#E2E3D8" - }, - "light-medium-contrast": { - "primary": "#253D05", - "surfaceTint": "#4C662B", - "onPrimary": "#FFFFFF", - "primaryContainer": "#5A7539", - "onPrimaryContainer": "#FFFFFF", - "secondary": "#303924", - "onSecondary": "#FFFFFF", - "secondaryContainer": "#667157", - "onSecondaryContainer": "#FFFFFF", - "tertiary": "#083D3A", - "onTertiary": "#FFFFFF", - "tertiaryContainer": "#477572", - "onTertiaryContainer": "#FFFFFF", - "error": "#740006", - "onError": "#FFFFFF", - "errorContainer": "#CF2C27", - "onErrorContainer": "#FFFFFF", - "background": "#F9FAEF", - "onBackground": "#1A1C16", - "surface": "#F9FAEF", - "onSurface": "#0F120C", - "surfaceVariant": "#E1E4D5", - "onSurfaceVariant": "#34382D", - "outline": "#505449", - "outlineVariant": "#6B6F62", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#2F312A", - "inverseOnSurface": "#F1F2E6", - "inversePrimary": "#B1D18A", - "primaryFixed": "#5A7539", - "onPrimaryFixed": "#FFFFFF", - "primaryFixedDim": "#425C23", - "onPrimaryFixedVariant": "#FFFFFF", - "secondaryFixed": "#667157", - "onSecondaryFixed": "#FFFFFF", - "secondaryFixedDim": "#4E5840", - "onSecondaryFixedVariant": "#FFFFFF", - "tertiaryFixed": "#477572", - "onTertiaryFixed": "#FFFFFF", - "tertiaryFixedDim": "#2E5C59", - "onTertiaryFixedVariant": "#FFFFFF", - "surfaceDim": "#C6C7BD", - "surfaceBright": "#F9FAEF", - "surfaceContainerLowest": "#FFFFFF", - "surfaceContainerLow": "#F3F4E9", - "surfaceContainer": "#E8E9DE", - "surfaceContainerHigh": "#DCDED3", - "surfaceContainerHighest": "#D1D3C8" - }, - "light-high-contrast": { - "primary": "#1C3200", - "surfaceTint": "#4C662B", - "onPrimary": "#FFFFFF", - "primaryContainer": "#375018", - "onPrimaryContainer": "#FFFFFF", - "secondary": "#262F1A", - "onSecondary": "#FFFFFF", - "secondaryContainer": "#434C35", - "onSecondaryContainer": "#FFFFFF", - "tertiary": "#003230", - "onTertiary": "#FFFFFF", - "tertiaryContainer": "#21504E", - "onTertiaryContainer": "#FFFFFF", - "error": "#600004", - "onError": "#FFFFFF", - "errorContainer": "#98000A", - "onErrorContainer": "#FFFFFF", - "background": "#F9FAEF", - "onBackground": "#1A1C16", - "surface": "#F9FAEF", - "onSurface": "#000000", - "surfaceVariant": "#E1E4D5", - "onSurfaceVariant": "#000000", - "outline": "#2A2D24", - "outlineVariant": "#474B40", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#2F312A", - "inverseOnSurface": "#FFFFFF", - "inversePrimary": "#B1D18A", - "primaryFixed": "#375018", - "onPrimaryFixed": "#FFFFFF", - "primaryFixedDim": "#213903", - "onPrimaryFixedVariant": "#FFFFFF", - "secondaryFixed": "#434C35", - "onSecondaryFixed": "#FFFFFF", - "secondaryFixedDim": "#2C3620", - "onSecondaryFixedVariant": "#FFFFFF", - "tertiaryFixed": "#21504E", - "onTertiaryFixed": "#FFFFFF", - "tertiaryFixedDim": "#033937", - "onTertiaryFixedVariant": "#FFFFFF", - "surfaceDim": "#B8BAAF", - "surfaceBright": "#F9FAEF", - "surfaceContainerLowest": "#FFFFFF", - "surfaceContainerLow": "#F1F2E6", - "surfaceContainer": "#E2E3D8", - "surfaceContainerHigh": "#D4D5CA", - "surfaceContainerHighest": "#C6C7BD" - }, - "dark": { - "primary": "#B1D18A", - "surfaceTint": "#B1D18A", - "onPrimary": "#1F3701", - "primaryContainer": "#354E16", - "onPrimaryContainer": "#CDEDA3", - "secondary": "#BFCBAD", - "onSecondary": "#2A331E", - "secondaryContainer": "#404A33", - "onSecondaryContainer": "#DCE7C8", - "tertiary": "#A0D0CB", - "onTertiary": "#003735", - "tertiaryContainer": "#1F4E4B", - "onTertiaryContainer": "#BCECE7", - "error": "#FFB4AB", - "onError": "#690005", - "errorContainer": "#93000A", - "onErrorContainer": "#FFDAD6", - "background": "#12140E", - "onBackground": "#E2E3D8", - "surface": "#12140E", - "onSurface": "#E2E3D8", - "surfaceVariant": "#44483D", - "onSurfaceVariant": "#C5C8BA", - "outline": "#8F9285", - "outlineVariant": "#44483D", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#E2E3D8", - "inverseOnSurface": "#2F312A", - "inversePrimary": "#4C662B", - "primaryFixed": "#CDEDA3", - "onPrimaryFixed": "#102000", - "primaryFixedDim": "#B1D18A", - "onPrimaryFixedVariant": "#354E16", - "secondaryFixed": "#DCE7C8", - "onSecondaryFixed": "#151E0B", - "secondaryFixedDim": "#BFCBAD", - "onSecondaryFixedVariant": "#404A33", - "tertiaryFixed": "#BCECE7", - "onTertiaryFixed": "#00201E", - "tertiaryFixedDim": "#A0D0CB", - "onTertiaryFixedVariant": "#1F4E4B", - "surfaceDim": "#12140E", - "surfaceBright": "#383A32", - "surfaceContainerLowest": "#0C0F09", - "surfaceContainerLow": "#1A1C16", - "surfaceContainer": "#1E201A", - "surfaceContainerHigh": "#282B24", - "surfaceContainerHighest": "#33362E" - }, - "dark-medium-contrast": { - "primary": "#C7E79E", - "surfaceTint": "#B1D18A", - "onPrimary": "#172B00", - "primaryContainer": "#7D9A59", - "onPrimaryContainer": "#000000", - "secondary": "#D5E1C2", - "onSecondary": "#1F2814", - "secondaryContainer": "#8A9579", - "onSecondaryContainer": "#000000", - "tertiary": "#B5E6E1", - "onTertiary": "#002B29", - "tertiaryContainer": "#6B9995", - "onTertiaryContainer": "#000000", - "error": "#FFD2CC", - "onError": "#540003", - "errorContainer": "#FF5449", - "onErrorContainer": "#000000", - "background": "#12140E", - "onBackground": "#E2E3D8", - "surface": "#12140E", - "onSurface": "#FFFFFF", - "surfaceVariant": "#44483D", - "onSurfaceVariant": "#DBDECF", - "outline": "#B0B3A6", - "outlineVariant": "#8E9285", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#E2E3D8", - "inverseOnSurface": "#282B24", - "inversePrimary": "#364F17", - "primaryFixed": "#CDEDA3", - "onPrimaryFixed": "#081400", - "primaryFixedDim": "#B1D18A", - "onPrimaryFixedVariant": "#253D05", - "secondaryFixed": "#DCE7C8", - "onSecondaryFixed": "#0B1403", - "secondaryFixedDim": "#BFCBAD", - "onSecondaryFixedVariant": "#303924", - "tertiaryFixed": "#BCECE7", - "onTertiaryFixed": "#001413", - "tertiaryFixedDim": "#A0D0CB", - "onTertiaryFixedVariant": "#083D3A", - "surfaceDim": "#12140E", - "surfaceBright": "#43453D", - "surfaceContainerLowest": "#060804", - "surfaceContainerLow": "#1C1E18", - "surfaceContainer": "#262922", - "surfaceContainerHigh": "#31342C", - "surfaceContainerHighest": "#3C3F37" - }, - "dark-high-contrast": { - "primary": "#DAFBB0", - "surfaceTint": "#B1D18A", - "onPrimary": "#000000", - "primaryContainer": "#ADCD86", - "onPrimaryContainer": "#050E00", - "secondary": "#E9F4D5", - "onSecondary": "#000000", - "secondaryContainer": "#BCC7A9", - "onSecondaryContainer": "#060D01", - "tertiary": "#C9F9F5", - "onTertiary": "#000000", - "tertiaryContainer": "#9CCCC7", - "onTertiaryContainer": "#000E0D", - "error": "#FFECE9", - "onError": "#000000", - "errorContainer": "#FFAEA4", - "onErrorContainer": "#220001", - "background": "#12140E", - "onBackground": "#E2E3D8", - "surface": "#12140E", - "onSurface": "#FFFFFF", - "surfaceVariant": "#44483D", - "onSurfaceVariant": "#FFFFFF", - "outline": "#EEF2E2", - "outlineVariant": "#C1C4B6", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#E2E3D8", - "inverseOnSurface": "#000000", - "inversePrimary": "#364F17", - "primaryFixed": "#CDEDA3", - "onPrimaryFixed": "#000000", - "primaryFixedDim": "#B1D18A", - "onPrimaryFixedVariant": "#081400", - "secondaryFixed": "#DCE7C8", - "onSecondaryFixed": "#000000", - "secondaryFixedDim": "#BFCBAD", - "onSecondaryFixedVariant": "#0B1403", - "tertiaryFixed": "#BCECE7", - "onTertiaryFixed": "#000000", - "tertiaryFixedDim": "#A0D0CB", - "onTertiaryFixedVariant": "#001413", - "surfaceDim": "#12140E", - "surfaceBright": "#4F5149", - "surfaceContainerLowest": "#000000", - "surfaceContainerLow": "#1E201A", - "surfaceContainer": "#2F312A", - "surfaceContainerHigh": "#3A3C35", - "surfaceContainerHighest": "#454840" - } - }, - "palettes": { - "primary": { - "0": "#000000", - "5": "#081400", - "10": "#102000", - "15": "#172B00", - "20": "#1F3700", - "25": "#264300", - "30": "#2F4F00", - "35": "#375C00", - "40": "#3F6900", - "50": "#518500", - "60": "#64A104", - "70": "#7DBD2A", - "80": "#97D945", - "90": "#B2F65F", - "95": "#D2FF9B", - "98": "#F0FFD7", - "99": "#F9FFE9", - "100": "#FFFFFF" - }, - "secondary": { - "0": "#000000", - "5": "#081400", - "10": "#121F04", - "15": "#1C2A0B", - "20": "#263515", - "25": "#31401F", - "30": "#3C4C2A", - "35": "#485734", - "40": "#54643F", - "50": "#6C7D56", - "60": "#85976E", - "70": "#A0B187", - "80": "#BBCDA1", - "90": "#D7E9BB", - "95": "#E5F7C9", - "98": "#F0FFD7", - "99": "#F9FFE9", - "100": "#FFFFFF" - }, - "tertiary": { - "0": "#000000", - "5": "#001413", - "10": "#00201E", - "15": "#002B29", - "20": "#003735", - "25": "#004340", - "30": "#00504C", - "35": "#005D59", - "40": "#046A66", - "50": "#30837F", - "60": "#4D9D98", - "70": "#69B8B3", - "80": "#85D4CF", - "90": "#A1F1EB", - "95": "#B0FFF9", - "98": "#E3FFFC", - "99": "#F2FFFD", - "100": "#FFFFFF" - }, - "neutral": { - "0": "#000000", - "5": "#10110D", - "10": "#1B1C18", - "15": "#252622", - "20": "#30312C", - "25": "#3B3C37", - "30": "#464742", - "35": "#52534D", - "40": "#5E5F59", - "50": "#777771", - "60": "#91918B", - "70": "#ABACA5", - "80": "#C7C7C0", - "90": "#E3E3DB", - "95": "#F2F1E9", - "98": "#FAFAF2", - "99": "#FDFCF5", - "100": "#FFFFFF" - }, - "neutral-variant": { - "0": "#000000", - "5": "#0E120A", - "10": "#191D14", - "15": "#23271D", - "20": "#2E3228", - "25": "#393D32", - "30": "#44483D", - "35": "#505449", - "40": "#5C6054", - "50": "#75796C", - "60": "#8F9285", - "70": "#A9AD9F", - "80": "#C5C8BA", - "90": "#E1E4D5", - "95": "#EFF2E3", - "98": "#F8FBEB", - "99": "#FBFEEE", - "100": "#FFFFFF" - } - } -} \ No newline at end of file diff --git a/ui/themes/material_purple_theme.json b/ui/themes/material_purple_theme.json deleted file mode 100644 index 4fa176b..0000000 --- a/ui/themes/material_purple_theme.json +++ /dev/null @@ -1,429 +0,0 @@ -{ - "description": "TYPE: CUSTOM\nMaterial Theme Builder export 2025-08-28 04:01:30", - "seed": "#6750A4", - "coreColors": { - "primary": "#6750A4", - "secondary": "#958DA4", - "tertiary": "#B58392", - "neutral": "#938F94", - "neutralVariant": "#948F99" - }, - "extendedColors": [ - { - "name": "Custom Color 1", - "color": "#166EA6", - "description": "", - "harmonized": false - } - ], - "schemes": { - "light": { - "primary": "#65558F", - "surfaceTint": "#65558F", - "onPrimary": "#FFFFFF", - "primaryContainer": "#E9DDFF", - "onPrimaryContainer": "#4D3D75", - "secondary": "#65558F", - "onSecondary": "#FFFFFF", - "secondaryContainer": "#E9DDFF", - "onSecondaryContainer": "#4D3D75", - "tertiary": "#8B4A61", - "onTertiary": "#FFFFFF", - "tertiaryContainer": "#FFD9E3", - "onTertiaryContainer": "#6F3349", - "error": "#BA1A1A", - "onError": "#FFFFFF", - "errorContainer": "#FFDAD6", - "onErrorContainer": "#93000A", - "background": "#FDF7FF", - "onBackground": "#1D1B20", - "surface": "#FEF7FF", - "onSurface": "#1D1B20", - "surfaceVariant": "#E7E0EB", - "onSurfaceVariant": "#49454E", - "outline": "#7A757F", - "outlineVariant": "#CAC4CF", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#322F35", - "inverseOnSurface": "#F5EFF7", - "inversePrimary": "#CFBDFE", - "primaryFixed": "#E9DDFF", - "onPrimaryFixed": "#201047", - "primaryFixedDim": "#CFBDFE", - "onPrimaryFixedVariant": "#4D3D75", - "secondaryFixed": "#E9DDFF", - "onSecondaryFixed": "#210F47", - "secondaryFixedDim": "#D0BCFE", - "onSecondaryFixedVariant": "#4D3D75", - "tertiaryFixed": "#FFD9E3", - "onTertiaryFixed": "#3A071E", - "tertiaryFixedDim": "#FFB0C9", - "onTertiaryFixedVariant": "#6F3349", - "surfaceDim": "#DED8E0", - "surfaceBright": "#FEF7FF", - "surfaceContainerLowest": "#FFFFFF", - "surfaceContainerLow": "#F8F1FA", - "surfaceContainer": "#F2ECF4", - "surfaceContainerHigh": "#ECE6EE", - "surfaceContainerHighest": "#E7E0E8" - }, - "light-medium-contrast": { - "primary": "#3C2D63", - "surfaceTint": "#65558F", - "onPrimary": "#FFFFFF", - "primaryContainer": "#74649F", - "onPrimaryContainer": "#FFFFFF", - "secondary": "#3C2C63", - "onSecondary": "#FFFFFF", - "secondaryContainer": "#74649E", - "onSecondaryContainer": "#FFFFFF", - "tertiary": "#5B2238", - "onTertiary": "#FFFFFF", - "tertiaryContainer": "#9C5870", - "onTertiaryContainer": "#FFFFFF", - "error": "#740006", - "onError": "#FFFFFF", - "errorContainer": "#CF2C27", - "onErrorContainer": "#FFFFFF", - "background": "#FDF7FF", - "onBackground": "#1D1B20", - "surface": "#FEF7FF", - "onSurface": "#121016", - "surfaceVariant": "#E7E0EB", - "onSurfaceVariant": "#38353D", - "outline": "#55515A", - "outlineVariant": "#706B75", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#322F35", - "inverseOnSurface": "#F5EFF7", - "inversePrimary": "#CFBDFE", - "primaryFixed": "#74649F", - "onPrimaryFixed": "#FFFFFF", - "primaryFixedDim": "#5B4C84", - "onPrimaryFixedVariant": "#FFFFFF", - "secondaryFixed": "#74649E", - "onSecondaryFixed": "#FFFFFF", - "secondaryFixedDim": "#5B4B84", - "onSecondaryFixedVariant": "#FFFFFF", - "tertiaryFixed": "#9C5870", - "onTertiaryFixed": "#FFFFFF", - "tertiaryFixedDim": "#804057", - "onTertiaryFixedVariant": "#FFFFFF", - "surfaceDim": "#CAC5CC", - "surfaceBright": "#FEF7FF", - "surfaceContainerLowest": "#FFFFFF", - "surfaceContainerLow": "#F8F1FA", - "surfaceContainer": "#ECE6EE", - "surfaceContainerHigh": "#E1DBE3", - "surfaceContainerHighest": "#D5D0D8" - }, - "light-high-contrast": { - "primary": "#312259", - "surfaceTint": "#65558F", - "onPrimary": "#FFFFFF", - "primaryContainer": "#4F4078", - "onPrimaryContainer": "#FFFFFF", - "secondary": "#322258", - "onSecondary": "#FFFFFF", - "secondaryContainer": "#504078", - "onSecondaryContainer": "#FFFFFF", - "tertiary": "#4F182E", - "onTertiary": "#FFFFFF", - "tertiaryContainer": "#72354C", - "onTertiaryContainer": "#FFFFFF", - "error": "#600004", - "onError": "#FFFFFF", - "errorContainer": "#98000A", - "onErrorContainer": "#FFFFFF", - "background": "#FDF7FF", - "onBackground": "#1D1B20", - "surface": "#FEF7FF", - "onSurface": "#000000", - "surfaceVariant": "#E7E0EB", - "onSurfaceVariant": "#000000", - "outline": "#2E2B33", - "outlineVariant": "#4B4851", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#322F35", - "inverseOnSurface": "#FFFFFF", - "inversePrimary": "#CFBDFE", - "primaryFixed": "#4F4078", - "onPrimaryFixed": "#FFFFFF", - "primaryFixedDim": "#382960", - "onPrimaryFixedVariant": "#FFFFFF", - "secondaryFixed": "#504078", - "onSecondaryFixed": "#FFFFFF", - "secondaryFixedDim": "#38295F", - "onSecondaryFixedVariant": "#FFFFFF", - "tertiaryFixed": "#72354C", - "onTertiaryFixed": "#FFFFFF", - "tertiaryFixedDim": "#571F35", - "onTertiaryFixedVariant": "#FFFFFF", - "surfaceDim": "#BCB7BF", - "surfaceBright": "#FEF7FF", - "surfaceContainerLowest": "#FFFFFF", - "surfaceContainerLow": "#F5EFF7", - "surfaceContainer": "#E7E0E8", - "surfaceContainerHigh": "#D8D2DA", - "surfaceContainerHighest": "#CAC5CC" - }, - "dark": { - "primary": "#CFBDFE", - "surfaceTint": "#CFBDFE", - "onPrimary": "#36275D", - "primaryContainer": "#4D3D75", - "onPrimaryContainer": "#E9DDFF", - "secondary": "#D0BCFE", - "onSecondary": "#36265D", - "secondaryContainer": "#4D3D75", - "onSecondaryContainer": "#E9DDFF", - "tertiary": "#FFB0C9", - "onTertiary": "#541D33", - "tertiaryContainer": "#6F3349", - "onTertiaryContainer": "#FFD9E3", - "error": "#FFB4AB", - "onError": "#690005", - "errorContainer": "#93000A", - "onErrorContainer": "#FFDAD6", - "background": "#141218", - "onBackground": "#E6E0E9", - "surface": "#141218", - "onSurface": "#E7E0E8", - "surfaceVariant": "#49454E", - "onSurfaceVariant": "#CAC4CF", - "outline": "#948F99", - "outlineVariant": "#49454E", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#E7E0E8", - "inverseOnSurface": "#322F35", - "inversePrimary": "#65558F", - "primaryFixed": "#E9DDFF", - "onPrimaryFixed": "#201047", - "primaryFixedDim": "#CFBDFE", - "onPrimaryFixedVariant": "#4D3D75", - "secondaryFixed": "#E9DDFF", - "onSecondaryFixed": "#210F47", - "secondaryFixedDim": "#D0BCFE", - "onSecondaryFixedVariant": "#4D3D75", - "tertiaryFixed": "#FFD9E3", - "onTertiaryFixed": "#3A071E", - "tertiaryFixedDim": "#FFB0C9", - "onTertiaryFixedVariant": "#6F3349", - "surfaceDim": "#141218", - "surfaceBright": "#3B383E", - "surfaceContainerLowest": "#0F0D13", - "surfaceContainerLow": "#1D1B20", - "surfaceContainer": "#211F24", - "surfaceContainerHigh": "#2B292F", - "surfaceContainerHighest": "#36343A" - }, - "dark-medium-contrast": { - "primary": "#E3D6FF", - "surfaceTint": "#CFBDFE", - "onPrimary": "#2B1B52", - "primaryContainer": "#9887C5", - "onPrimaryContainer": "#000000", - "secondary": "#E3D6FF", - "onSecondary": "#2B1B51", - "secondaryContainer": "#9987C5", - "onSecondaryContainer": "#000000", - "tertiary": "#FFD0DD", - "onTertiary": "#471228", - "tertiaryContainer": "#C57B93", - "onTertiaryContainer": "#000000", - "error": "#FFD2CC", - "onError": "#540003", - "errorContainer": "#FF5449", - "onErrorContainer": "#000000", - "background": "#141218", - "onBackground": "#E6E0E9", - "surface": "#141218", - "onSurface": "#FFFFFF", - "surfaceVariant": "#49454E", - "onSurfaceVariant": "#E0DAE5", - "outline": "#B5B0BB", - "outlineVariant": "#938E99", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#E7E0E8", - "inverseOnSurface": "#2B292F", - "inversePrimary": "#4E3F77", - "primaryFixed": "#E9DDFF", - "onPrimaryFixed": "#16033D", - "primaryFixedDim": "#CFBDFE", - "onPrimaryFixedVariant": "#3C2D63", - "secondaryFixed": "#E9DDFF", - "onSecondaryFixed": "#16033D", - "secondaryFixedDim": "#D0BCFE", - "onSecondaryFixedVariant": "#3C2C63", - "tertiaryFixed": "#FFD9E3", - "onTertiaryFixed": "#2B0013", - "tertiaryFixedDim": "#FFB0C9", - "onTertiaryFixedVariant": "#5B2238", - "surfaceDim": "#141218", - "surfaceBright": "#46434A", - "surfaceContainerLowest": "#08070B", - "surfaceContainerLow": "#1F1D22", - "surfaceContainer": "#29272D", - "surfaceContainerHigh": "#343138", - "surfaceContainerHighest": "#3F3C43" - }, - "dark-high-contrast": { - "primary": "#F5EDFF", - "surfaceTint": "#CFBDFE", - "onPrimary": "#000000", - "primaryContainer": "#CBB9FA", - "onPrimaryContainer": "#0F0033", - "secondary": "#F5EDFF", - "onSecondary": "#000000", - "secondaryContainer": "#CCB9FA", - "onSecondaryContainer": "#100032", - "tertiary": "#FFEBEF", - "onTertiary": "#000000", - "tertiaryContainer": "#FEABC5", - "onTertiaryContainer": "#20000D", - "error": "#FFECE9", - "onError": "#000000", - "errorContainer": "#FFAEA4", - "onErrorContainer": "#220001", - "background": "#141218", - "onBackground": "#E6E0E9", - "surface": "#141218", - "onSurface": "#FFFFFF", - "surfaceVariant": "#49454E", - "onSurfaceVariant": "#FFFFFF", - "outline": "#F4EDF9", - "outlineVariant": "#C6C0CB", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#E7E0E8", - "inverseOnSurface": "#000000", - "inversePrimary": "#4E3F77", - "primaryFixed": "#E9DDFF", - "onPrimaryFixed": "#000000", - "primaryFixedDim": "#CFBDFE", - "onPrimaryFixedVariant": "#16033D", - "secondaryFixed": "#E9DDFF", - "onSecondaryFixed": "#000000", - "secondaryFixedDim": "#D0BCFE", - "onSecondaryFixedVariant": "#16033D", - "tertiaryFixed": "#FFD9E3", - "onTertiaryFixed": "#000000", - "tertiaryFixedDim": "#FFB0C9", - "onTertiaryFixedVariant": "#2B0013", - "surfaceDim": "#141218", - "surfaceBright": "#524F55", - "surfaceContainerLowest": "#000000", - "surfaceContainerLow": "#211F24", - "surfaceContainer": "#322F35", - "surfaceContainerHigh": "#3D3A40", - "surfaceContainerHighest": "#49454C" - } - }, - "palettes": { - "primary": { - "0": "#000000", - "5": "#160041", - "10": "#22005D", - "15": "#2D1067", - "20": "#381E72", - "25": "#432B7E", - "30": "#4F378A", - "35": "#5B4397", - "40": "#6750A4", - "50": "#8069BF", - "60": "#9A83DB", - "70": "#B69DF7", - "80": "#CFBCFF", - "90": "#E9DDFF", - "95": "#F6EEFF", - "98": "#FDF7FF", - "99": "#FFFBFF", - "100": "#FFFFFF" - }, - "secondary": { - "0": "#000000", - "5": "#130E1F", - "10": "#1E192B", - "15": "#282335", - "20": "#332D40", - "25": "#3E384C", - "30": "#4A4458", - "35": "#564F64", - "40": "#625B70", - "50": "#7B748A", - "60": "#958DA4", - "70": "#B0A7BF", - "80": "#CCC2DB", - "90": "#E8DEF8", - "95": "#F6EDFF", - "98": "#FEF7FF", - "99": "#FFFBFF", - "100": "#FFFFFF" - }, - "tertiary": { - "0": "#000000", - "5": "#240612", - "10": "#31101D", - "15": "#3D1B27", - "20": "#4A2532", - "25": "#56303D", - "30": "#633B48", - "35": "#704654", - "40": "#7E5260", - "50": "#996A78", - "60": "#B58392", - "70": "#D29DAC", - "80": "#EFB8C8", - "90": "#FFD9E3", - "95": "#FFECF0", - "98": "#FFF8F8", - "99": "#FFFBFF", - "100": "#FFFFFF" - }, - "neutral": { - "0": "#000000", - "5": "#111111", - "10": "#1C1B1B", - "15": "#262526", - "20": "#313030", - "25": "#3C3B3B", - "30": "#484646", - "35": "#545252", - "40": "#605E5E", - "50": "#797676", - "60": "#939090", - "70": "#AEAAAA", - "80": "#C9C6C5", - "90": "#E6E1E1", - "95": "#F4F0EF", - "98": "#FDF8F8", - "99": "#FFFBFF", - "100": "#FFFFFF" - }, - "neutral-variant": { - "0": "#000000", - "5": "#111112", - "10": "#1C1B1C", - "15": "#262526", - "20": "#313031", - "25": "#3C3B3C", - "30": "#484647", - "35": "#545253", - "40": "#605E5F", - "50": "#797677", - "60": "#939091", - "70": "#AEAAAB", - "80": "#C9C5C6", - "90": "#E6E1E2", - "95": "#F4F0F0", - "98": "#FDF8F9", - "99": "#FFFBFF", - "100": "#FFFFFF" - } - } -} \ No newline at end of file diff --git a/ui/themes/material_red_theme.json b/ui/themes/material_red_theme.json deleted file mode 100644 index 7b21f1d..0000000 --- a/ui/themes/material_red_theme.json +++ /dev/null @@ -1,418 +0,0 @@ -{ - "description": "TYPE: CUSTOM\nMaterial Theme Builder export 2025-08-29 10:19:37", - "seed": "#B33B15", - "coreColors": { - "primary": "#B33B15" - }, - "extendedColors": [], - "schemes": { - "light": { - "primary": "#8F4C38", - "surfaceTint": "#8F4C38", - "onPrimary": "#FFFFFF", - "primaryContainer": "#FFDBD1", - "onPrimaryContainer": "#723523", - "secondary": "#77574E", - "onSecondary": "#FFFFFF", - "secondaryContainer": "#FFDBD1", - "onSecondaryContainer": "#5D4037", - "tertiary": "#6C5D2F", - "onTertiary": "#FFFFFF", - "tertiaryContainer": "#F5E1A7", - "onTertiaryContainer": "#534619", - "error": "#BA1A1A", - "onError": "#FFFFFF", - "errorContainer": "#FFDAD6", - "onErrorContainer": "#93000A", - "background": "#FFF8F6", - "onBackground": "#231917", - "surface": "#FFF8F6", - "onSurface": "#231917", - "surfaceVariant": "#F5DED8", - "onSurfaceVariant": "#53433F", - "outline": "#85736E", - "outlineVariant": "#D8C2BC", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#392E2B", - "inverseOnSurface": "#FFEDE8", - "inversePrimary": "#FFB5A0", - "primaryFixed": "#FFDBD1", - "onPrimaryFixed": "#3A0B01", - "primaryFixedDim": "#FFB5A0", - "onPrimaryFixedVariant": "#723523", - "secondaryFixed": "#FFDBD1", - "onSecondaryFixed": "#2C150F", - "secondaryFixedDim": "#E7BDB2", - "onSecondaryFixedVariant": "#5D4037", - "tertiaryFixed": "#F5E1A7", - "onTertiaryFixed": "#231B00", - "tertiaryFixedDim": "#D8C58D", - "onTertiaryFixedVariant": "#534619", - "surfaceDim": "#E8D6D2", - "surfaceBright": "#FFF8F6", - "surfaceContainerLowest": "#FFFFFF", - "surfaceContainerLow": "#FFF1ED", - "surfaceContainer": "#FCEAE5", - "surfaceContainerHigh": "#F7E4E0", - "surfaceContainerHighest": "#F1DFDA" - }, - "light-medium-contrast": { - "primary": "#5D2514", - "surfaceTint": "#8F4C38", - "onPrimary": "#FFFFFF", - "primaryContainer": "#A15A45", - "onPrimaryContainer": "#FFFFFF", - "secondary": "#4B2F28", - "onSecondary": "#FFFFFF", - "secondaryContainer": "#87655C", - "onSecondaryContainer": "#FFFFFF", - "tertiary": "#41350A", - "onTertiary": "#FFFFFF", - "tertiaryContainer": "#7B6C3C", - "onTertiaryContainer": "#FFFFFF", - "error": "#740006", - "onError": "#FFFFFF", - "errorContainer": "#CF2C27", - "onErrorContainer": "#FFFFFF", - "background": "#FFF8F6", - "onBackground": "#231917", - "surface": "#FFF8F6", - "onSurface": "#180F0D", - "surfaceVariant": "#F5DED8", - "onSurfaceVariant": "#41332F", - "outline": "#5F4F4A", - "outlineVariant": "#7B6964", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#392E2B", - "inverseOnSurface": "#FFEDE8", - "inversePrimary": "#FFB5A0", - "primaryFixed": "#A15A45", - "onPrimaryFixed": "#FFFFFF", - "primaryFixedDim": "#84422F", - "onPrimaryFixedVariant": "#FFFFFF", - "secondaryFixed": "#87655C", - "onSecondaryFixed": "#FFFFFF", - "secondaryFixedDim": "#6D4D45", - "onSecondaryFixedVariant": "#FFFFFF", - "tertiaryFixed": "#7B6C3C", - "onTertiaryFixed": "#FFFFFF", - "tertiaryFixedDim": "#615426", - "onTertiaryFixedVariant": "#FFFFFF", - "surfaceDim": "#D4C3BE", - "surfaceBright": "#FFF8F6", - "surfaceContainerLowest": "#FFFFFF", - "surfaceContainerLow": "#FFF1ED", - "surfaceContainer": "#F7E4E0", - "surfaceContainerHigh": "#EBD9D4", - "surfaceContainerHighest": "#DFCEC9" - }, - "light-high-contrast": { - "primary": "#501B0B", - "surfaceTint": "#8F4C38", - "onPrimary": "#FFFFFF", - "primaryContainer": "#753725", - "onPrimaryContainer": "#FFFFFF", - "secondary": "#3F261E", - "onSecondary": "#FFFFFF", - "secondaryContainer": "#60423A", - "onSecondaryContainer": "#FFFFFF", - "tertiary": "#362B02", - "onTertiary": "#FFFFFF", - "tertiaryContainer": "#55481C", - "onTertiaryContainer": "#FFFFFF", - "error": "#600004", - "onError": "#FFFFFF", - "errorContainer": "#98000A", - "onErrorContainer": "#FFFFFF", - "background": "#FFF8F6", - "onBackground": "#231917", - "surface": "#FFF8F6", - "onSurface": "#000000", - "surfaceVariant": "#F5DED8", - "onSurfaceVariant": "#000000", - "outline": "#372925", - "outlineVariant": "#554641", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#392E2B", - "inverseOnSurface": "#FFFFFF", - "inversePrimary": "#FFB5A0", - "primaryFixed": "#753725", - "onPrimaryFixed": "#FFFFFF", - "primaryFixedDim": "#592111", - "onPrimaryFixedVariant": "#FFFFFF", - "secondaryFixed": "#60423A", - "onSecondaryFixed": "#FFFFFF", - "secondaryFixedDim": "#472C24", - "onSecondaryFixedVariant": "#FFFFFF", - "tertiaryFixed": "#55481C", - "onTertiaryFixed": "#FFFFFF", - "tertiaryFixedDim": "#3D3206", - "onTertiaryFixedVariant": "#FFFFFF", - "surfaceDim": "#C6B5B1", - "surfaceBright": "#FFF8F6", - "surfaceContainerLowest": "#FFFFFF", - "surfaceContainerLow": "#FFEDE8", - "surfaceContainer": "#F1DFDA", - "surfaceContainerHigh": "#E2D1CC", - "surfaceContainerHighest": "#D4C3BE" - }, - "dark": { - "primary": "#FFB5A0", - "surfaceTint": "#FFB5A0", - "onPrimary": "#561F0F", - "primaryContainer": "#723523", - "onPrimaryContainer": "#FFDBD1", - "secondary": "#E7BDB2", - "onSecondary": "#442A22", - "secondaryContainer": "#5D4037", - "onSecondaryContainer": "#FFDBD1", - "tertiary": "#D8C58D", - "onTertiary": "#3B2F05", - "tertiaryContainer": "#534619", - "onTertiaryContainer": "#F5E1A7", - "error": "#FFB4AB", - "onError": "#690005", - "errorContainer": "#93000A", - "onErrorContainer": "#FFDAD6", - "background": "#1A110F", - "onBackground": "#F1DFDA", - "surface": "#1A110F", - "onSurface": "#F1DFDA", - "surfaceVariant": "#53433F", - "onSurfaceVariant": "#D8C2BC", - "outline": "#A08C87", - "outlineVariant": "#53433F", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#F1DFDA", - "inverseOnSurface": "#392E2B", - "inversePrimary": "#8F4C38", - "primaryFixed": "#FFDBD1", - "onPrimaryFixed": "#3A0B01", - "primaryFixedDim": "#FFB5A0", - "onPrimaryFixedVariant": "#723523", - "secondaryFixed": "#FFDBD1", - "onSecondaryFixed": "#2C150F", - "secondaryFixedDim": "#E7BDB2", - "onSecondaryFixedVariant": "#5D4037", - "tertiaryFixed": "#F5E1A7", - "onTertiaryFixed": "#231B00", - "tertiaryFixedDim": "#D8C58D", - "onTertiaryFixedVariant": "#534619", - "surfaceDim": "#1A110F", - "surfaceBright": "#423734", - "surfaceContainerLowest": "#140C0A", - "surfaceContainerLow": "#231917", - "surfaceContainer": "#271D1B", - "surfaceContainerHigh": "#322825", - "surfaceContainerHighest": "#3D322F" - }, - "dark-medium-contrast": { - "primary": "#FFD2C6", - "surfaceTint": "#FFB5A0", - "onPrimary": "#481506", - "primaryContainer": "#CB7C65", - "onPrimaryContainer": "#000000", - "secondary": "#FED3C7", - "onSecondary": "#381F18", - "secondaryContainer": "#AE887E", - "onSecondaryContainer": "#000000", - "tertiary": "#EFDBA1", - "onTertiary": "#2F2500", - "tertiaryContainer": "#A0905C", - "onTertiaryContainer": "#000000", - "error": "#FFD2CC", - "onError": "#540003", - "errorContainer": "#FF5449", - "onErrorContainer": "#000000", - "background": "#1A110F", - "onBackground": "#F1DFDA", - "surface": "#1A110F", - "onSurface": "#FFFFFF", - "surfaceVariant": "#53433F", - "onSurfaceVariant": "#EED7D1", - "outline": "#C2ADA8", - "outlineVariant": "#A08C87", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#F1DFDA", - "inverseOnSurface": "#322825", - "inversePrimary": "#743624", - "primaryFixed": "#FFDBD1", - "onPrimaryFixed": "#280500", - "primaryFixedDim": "#FFB5A0", - "onPrimaryFixedVariant": "#5D2514", - "secondaryFixed": "#FFDBD1", - "onSecondaryFixed": "#200B06", - "secondaryFixedDim": "#E7BDB2", - "onSecondaryFixedVariant": "#4B2F28", - "tertiaryFixed": "#F5E1A7", - "onTertiaryFixed": "#171100", - "tertiaryFixedDim": "#D8C58D", - "onTertiaryFixedVariant": "#41350A", - "surfaceDim": "#1A110F", - "surfaceBright": "#4E423F", - "surfaceContainerLowest": "#0D0604", - "surfaceContainerLow": "#251B19", - "surfaceContainer": "#302623", - "surfaceContainerHigh": "#3B302D", - "surfaceContainerHighest": "#463B38" - }, - "dark-high-contrast": { - "primary": "#FFECE7", - "surfaceTint": "#FFB5A0", - "onPrimary": "#000000", - "primaryContainer": "#FFAF98", - "onPrimaryContainer": "#1E0300", - "secondary": "#FFECE7", - "onSecondary": "#000000", - "secondaryContainer": "#E3B9AE", - "onSecondaryContainer": "#190603", - "tertiary": "#FFEFC4", - "onTertiary": "#000000", - "tertiaryContainer": "#D4C289", - "onTertiaryContainer": "#100B00", - "error": "#FFECE9", - "onError": "#000000", - "errorContainer": "#FFAEA4", - "onErrorContainer": "#220001", - "background": "#1A110F", - "onBackground": "#F1DFDA", - "surface": "#1A110F", - "onSurface": "#FFFFFF", - "surfaceVariant": "#53433F", - "onSurfaceVariant": "#FFFFFF", - "outline": "#FFECE7", - "outlineVariant": "#D4BEB8", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#F1DFDA", - "inverseOnSurface": "#000000", - "inversePrimary": "#743624", - "primaryFixed": "#FFDBD1", - "onPrimaryFixed": "#000000", - "primaryFixedDim": "#FFB5A0", - "onPrimaryFixedVariant": "#280500", - "secondaryFixed": "#FFDBD1", - "onSecondaryFixed": "#000000", - "secondaryFixedDim": "#E7BDB2", - "onSecondaryFixedVariant": "#200B06", - "tertiaryFixed": "#F5E1A7", - "onTertiaryFixed": "#000000", - "tertiaryFixedDim": "#D8C58D", - "onTertiaryFixedVariant": "#171100", - "surfaceDim": "#1A110F", - "surfaceBright": "#5A4D4A", - "surfaceContainerLowest": "#000000", - "surfaceContainerLow": "#271D1B", - "surfaceContainer": "#392E2B", - "surfaceContainerHigh": "#443936", - "surfaceContainerHighest": "#504441" - } - }, - "palettes": { - "primary": { - "0": "#000000", - "5": "#280500", - "10": "#3B0900", - "15": "#4D0F00", - "20": "#5F1500", - "25": "#731B00", - "30": "#862200", - "35": "#9A2902", - "40": "#AB350F", - "50": "#CD4D26", - "60": "#EF663D", - "70": "#FF8B69", - "80": "#FFB5A0", - "90": "#FFDBD1", - "95": "#FFEDE8", - "98": "#FFF8F6", - "99": "#FFFBFF", - "100": "#FFFFFF" - }, - "secondary": { - "0": "#000000", - "5": "#240803", - "10": "#311309", - "15": "#3E1C13", - "20": "#4A271C", - "25": "#573126", - "30": "#643C31", - "35": "#72483C", - "40": "#7F5347", - "50": "#9B6C5E", - "60": "#B88576", - "70": "#D59F8F", - "80": "#F2B9A9", - "90": "#FFDBD1", - "95": "#FFEDE8", - "98": "#FFF8F6", - "99": "#FFFBFF", - "100": "#FFFFFF" - }, - "tertiary": { - "0": "#000000", - "5": "#161000", - "10": "#231B00", - "15": "#2F2500", - "20": "#3B2F00", - "25": "#483A00", - "30": "#564500", - "35": "#635109", - "40": "#705D16", - "50": "#8A752D", - "60": "#A58F44", - "70": "#C1AA5C", - "80": "#DEC574", - "90": "#FCE18D", - "95": "#FFF0C8", - "98": "#FFF8F0", - "99": "#FFFBFF", - "100": "#FFFFFF" - }, - "neutral": { - "0": "#000000", - "5": "#15100E", - "10": "#201A18", - "15": "#2B2422", - "20": "#362F2D", - "25": "#413A38", - "30": "#4D4543", - "35": "#59514E", - "40": "#655C5A", - "50": "#7F7572", - "60": "#998E8C", - "70": "#B4A9A6", - "80": "#D0C4C1", - "90": "#EDE0DC", - "95": "#FBEEEB", - "98": "#FFF8F6", - "99": "#FFFBFF", - "100": "#FFFFFF" - }, - "neutral-variant": { - "0": "#000000", - "5": "#190E0B", - "10": "#251915", - "15": "#30231F", - "20": "#3B2D29", - "25": "#473834", - "30": "#53433F", - "35": "#5F4F4A", - "40": "#6C5B56", - "50": "#85736E", - "60": "#A08C87", - "70": "#BCA7A1", - "80": "#D8C2BC", - "90": "#F5DED8", - "95": "#FFEDE8", - "98": "#FFF8F6", - "99": "#FFFBFF", - "100": "#FFFFFF" - } - } -} \ No newline at end of file diff --git a/ui/themes/material_slint_theme.json b/ui/themes/material_slint_theme.json deleted file mode 100644 index 8204324..0000000 --- a/ui/themes/material_slint_theme.json +++ /dev/null @@ -1,418 +0,0 @@ -{ - "description": "TYPE: CUSTOM\nMaterial Theme Builder export 2025-08-29 10:22:12", - "seed": "#2379F4", - "coreColors": { - "primary": "#2379F4" - }, - "extendedColors": [], - "schemes": { - "light": { - "primary": "#445E91", - "surfaceTint": "#445E91", - "onPrimary": "#FFFFFF", - "primaryContainer": "#D8E2FF", - "onPrimaryContainer": "#2B4678", - "secondary": "#575E71", - "onSecondary": "#FFFFFF", - "secondaryContainer": "#DBE2F9", - "onSecondaryContainer": "#3F4759", - "tertiary": "#715573", - "onTertiary": "#FFFFFF", - "tertiaryContainer": "#FCD7FB", - "onTertiaryContainer": "#583E5B", - "error": "#BA1A1A", - "onError": "#FFFFFF", - "errorContainer": "#FFDAD6", - "onErrorContainer": "#93000A", - "background": "#F9F9FF", - "onBackground": "#1A1B20", - "surface": "#F9F9FF", - "onSurface": "#1A1B20", - "surfaceVariant": "#E1E2EC", - "onSurfaceVariant": "#44474F", - "outline": "#75777F", - "outlineVariant": "#C4C6D0", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#2F3036", - "inverseOnSurface": "#F0F0F7", - "inversePrimary": "#ADC6FF", - "primaryFixed": "#D8E2FF", - "onPrimaryFixed": "#001A42", - "primaryFixedDim": "#ADC6FF", - "onPrimaryFixedVariant": "#2B4678", - "secondaryFixed": "#DBE2F9", - "onSecondaryFixed": "#141B2C", - "secondaryFixedDim": "#BFC6DC", - "onSecondaryFixedVariant": "#3F4759", - "tertiaryFixed": "#FCD7FB", - "onTertiaryFixed": "#29132D", - "tertiaryFixedDim": "#DEBCDF", - "onTertiaryFixedVariant": "#583E5B", - "surfaceDim": "#D9D9E0", - "surfaceBright": "#F9F9FF", - "surfaceContainerLowest": "#FFFFFF", - "surfaceContainerLow": "#F3F3FA", - "surfaceContainer": "#EEEDF4", - "surfaceContainerHigh": "#E8E7EF", - "surfaceContainerHighest": "#E2E2E9" - }, - "light-medium-contrast": { - "primary": "#183566", - "surfaceTint": "#445E91", - "onPrimary": "#FFFFFF", - "primaryContainer": "#536DA1", - "onPrimaryContainer": "#FFFFFF", - "secondary": "#2E3647", - "onSecondary": "#FFFFFF", - "secondaryContainer": "#656D80", - "onSecondaryContainer": "#FFFFFF", - "tertiary": "#462D49", - "onTertiary": "#FFFFFF", - "tertiaryContainer": "#816383", - "onTertiaryContainer": "#FFFFFF", - "error": "#740006", - "onError": "#FFFFFF", - "errorContainer": "#CF2C27", - "onErrorContainer": "#FFFFFF", - "background": "#F9F9FF", - "onBackground": "#1A1B20", - "surface": "#F9F9FF", - "onSurface": "#0F1116", - "surfaceVariant": "#E1E2EC", - "onSurfaceVariant": "#33363E", - "outline": "#50525A", - "outlineVariant": "#6A6D75", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#2F3036", - "inverseOnSurface": "#F0F0F7", - "inversePrimary": "#ADC6FF", - "primaryFixed": "#536DA1", - "onPrimaryFixed": "#FFFFFF", - "primaryFixedDim": "#3A5487", - "onPrimaryFixedVariant": "#FFFFFF", - "secondaryFixed": "#656D80", - "onSecondaryFixed": "#FFFFFF", - "secondaryFixedDim": "#4D5567", - "onSecondaryFixedVariant": "#FFFFFF", - "tertiaryFixed": "#816383", - "onTertiaryFixed": "#FFFFFF", - "tertiaryFixedDim": "#674C69", - "onTertiaryFixedVariant": "#FFFFFF", - "surfaceDim": "#C6C6CD", - "surfaceBright": "#F9F9FF", - "surfaceContainerLowest": "#FFFFFF", - "surfaceContainerLow": "#F3F3FA", - "surfaceContainer": "#E8E7EF", - "surfaceContainerHigh": "#DCDCE3", - "surfaceContainerHighest": "#D1D1D8" - }, - "light-high-contrast": { - "primary": "#0A2B5B", - "surfaceTint": "#445E91", - "onPrimary": "#FFFFFF", - "primaryContainer": "#2E487A", - "onPrimaryContainer": "#FFFFFF", - "secondary": "#242C3D", - "onSecondary": "#FFFFFF", - "secondaryContainer": "#41495B", - "onSecondaryContainer": "#FFFFFF", - "tertiary": "#3B233F", - "onTertiary": "#FFFFFF", - "tertiaryContainer": "#5A405D", - "onTertiaryContainer": "#FFFFFF", - "error": "#600004", - "onError": "#FFFFFF", - "errorContainer": "#98000A", - "onErrorContainer": "#FFFFFF", - "background": "#F9F9FF", - "onBackground": "#1A1B20", - "surface": "#F9F9FF", - "onSurface": "#000000", - "surfaceVariant": "#E1E2EC", - "onSurfaceVariant": "#000000", - "outline": "#292C33", - "outlineVariant": "#464951", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#2F3036", - "inverseOnSurface": "#FFFFFF", - "inversePrimary": "#ADC6FF", - "primaryFixed": "#2E487A", - "onPrimaryFixed": "#FFFFFF", - "primaryFixedDim": "#143162", - "onPrimaryFixedVariant": "#FFFFFF", - "secondaryFixed": "#41495B", - "onSecondaryFixed": "#FFFFFF", - "secondaryFixedDim": "#2B3344", - "onSecondaryFixedVariant": "#FFFFFF", - "tertiaryFixed": "#5A405D", - "onTertiaryFixed": "#FFFFFF", - "tertiaryFixedDim": "#422A46", - "onTertiaryFixedVariant": "#FFFFFF", - "surfaceDim": "#B8B8BF", - "surfaceBright": "#F9F9FF", - "surfaceContainerLowest": "#FFFFFF", - "surfaceContainerLow": "#F0F0F7", - "surfaceContainer": "#E2E2E9", - "surfaceContainerHigh": "#D4D4DB", - "surfaceContainerHighest": "#C6C6CD" - }, - "dark": { - "primary": "#ADC6FF", - "surfaceTint": "#ADC6FF", - "onPrimary": "#112F60", - "primaryContainer": "#2B4678", - "onPrimaryContainer": "#D8E2FF", - "secondary": "#BFC6DC", - "onSecondary": "#293041", - "secondaryContainer": "#3F4759", - "onSecondaryContainer": "#DBE2F9", - "tertiary": "#DEBCDF", - "onTertiary": "#402843", - "tertiaryContainer": "#583E5B", - "onTertiaryContainer": "#FCD7FB", - "error": "#FFB4AB", - "onError": "#690005", - "errorContainer": "#93000A", - "onErrorContainer": "#FFDAD6", - "background": "#111318", - "onBackground": "#E2E2E9", - "surface": "#111318", - "onSurface": "#E2E2E9", - "surfaceVariant": "#44474F", - "onSurfaceVariant": "#C4C6D0", - "outline": "#8E9099", - "outlineVariant": "#44474F", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#E2E2E9", - "inverseOnSurface": "#2F3036", - "inversePrimary": "#445E91", - "primaryFixed": "#D8E2FF", - "onPrimaryFixed": "#001A42", - "primaryFixedDim": "#ADC6FF", - "onPrimaryFixedVariant": "#2B4678", - "secondaryFixed": "#DBE2F9", - "onSecondaryFixed": "#141B2C", - "secondaryFixedDim": "#BFC6DC", - "onSecondaryFixedVariant": "#3F4759", - "tertiaryFixed": "#FCD7FB", - "onTertiaryFixed": "#29132D", - "tertiaryFixedDim": "#DEBCDF", - "onTertiaryFixedVariant": "#583E5B", - "surfaceDim": "#111318", - "surfaceBright": "#37393E", - "surfaceContainerLowest": "#0C0E13", - "surfaceContainerLow": "#1A1B20", - "surfaceContainer": "#1E1F25", - "surfaceContainerHigh": "#282A2F", - "surfaceContainerHighest": "#33353A" - }, - "dark-medium-contrast": { - "primary": "#CFDCFF", - "surfaceTint": "#ADC6FF", - "onPrimary": "#012454", - "primaryContainer": "#7790C7", - "onPrimaryContainer": "#000000", - "secondary": "#D5DCF3", - "onSecondary": "#1E2636", - "secondaryContainer": "#8990A5", - "onSecondaryContainer": "#000000", - "tertiary": "#F5D1F5", - "onTertiary": "#341D38", - "tertiaryContainer": "#A687A7", - "onTertiaryContainer": "#000000", - "error": "#FFD2CC", - "onError": "#540003", - "errorContainer": "#FF5449", - "onErrorContainer": "#000000", - "background": "#111318", - "onBackground": "#E2E2E9", - "surface": "#111318", - "onSurface": "#FFFFFF", - "surfaceVariant": "#44474F", - "onSurfaceVariant": "#DADCE6", - "outline": "#B0B1BB", - "outlineVariant": "#8E9099", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#E2E2E9", - "inverseOnSurface": "#282A2F", - "inversePrimary": "#2D4779", - "primaryFixed": "#D8E2FF", - "onPrimaryFixed": "#00102E", - "primaryFixedDim": "#ADC6FF", - "onPrimaryFixedVariant": "#183566", - "secondaryFixed": "#DBE2F9", - "onSecondaryFixed": "#091121", - "secondaryFixedDim": "#BFC6DC", - "onSecondaryFixedVariant": "#2E3647", - "tertiaryFixed": "#FCD7FB", - "onTertiaryFixed": "#1E0822", - "tertiaryFixedDim": "#DEBCDF", - "onTertiaryFixedVariant": "#462D49", - "surfaceDim": "#111318", - "surfaceBright": "#43444A", - "surfaceContainerLowest": "#06070C", - "surfaceContainerLow": "#1C1D22", - "surfaceContainer": "#26282D", - "surfaceContainerHigh": "#313238", - "surfaceContainerHighest": "#3C3D43" - }, - "dark-high-contrast": { - "primary": "#ECEFFF", - "surfaceTint": "#ADC6FF", - "onPrimary": "#000000", - "primaryContainer": "#A9C2FC", - "onPrimaryContainer": "#000A22", - "secondary": "#ECEFFF", - "onSecondary": "#000000", - "secondaryContainer": "#BBC2D8", - "onSecondaryContainer": "#040B1B", - "tertiary": "#FFEAFC", - "onTertiary": "#000000", - "tertiaryContainer": "#DAB8DB", - "onTertiaryContainer": "#17031C", - "error": "#FFECE9", - "onError": "#000000", - "errorContainer": "#FFAEA4", - "onErrorContainer": "#220001", - "background": "#111318", - "onBackground": "#E2E2E9", - "surface": "#111318", - "onSurface": "#FFFFFF", - "surfaceVariant": "#44474F", - "onSurfaceVariant": "#FFFFFF", - "outline": "#EEEFF9", - "outlineVariant": "#C1C2CC", - "shadow": "#000000", - "scrim": "#000000", - "inverseSurface": "#E2E2E9", - "inverseOnSurface": "#000000", - "inversePrimary": "#2D4779", - "primaryFixed": "#D8E2FF", - "onPrimaryFixed": "#000000", - "primaryFixedDim": "#ADC6FF", - "onPrimaryFixedVariant": "#00102E", - "secondaryFixed": "#DBE2F9", - "onSecondaryFixed": "#000000", - "secondaryFixedDim": "#BFC6DC", - "onSecondaryFixedVariant": "#091121", - "tertiaryFixed": "#FCD7FB", - "onTertiaryFixed": "#000000", - "tertiaryFixedDim": "#DEBCDF", - "onTertiaryFixedVariant": "#1E0822", - "surfaceDim": "#111318", - "surfaceBright": "#4E5056", - "surfaceContainerLowest": "#000000", - "surfaceContainerLow": "#1E1F25", - "surfaceContainer": "#2F3036", - "surfaceContainerHigh": "#3A3B41", - "surfaceContainerHighest": "#45474C" - } - }, - "palettes": { - "primary": { - "0": "#000000", - "5": "#00102D", - "10": "#001A42", - "15": "#002455", - "20": "#002E6A", - "25": "#00397F", - "30": "#004495", - "35": "#004FAB", - "40": "#005AC2", - "50": "#1572ED", - "60": "#4D8EFF", - "70": "#81AAFF", - "80": "#ADC6FF", - "90": "#D8E2FF", - "95": "#EDF0FF", - "98": "#F9F9FF", - "99": "#FEFBFF", - "100": "#FFFFFF" - }, - "secondary": { - "0": "#000000", - "5": "#031029", - "10": "#0E1B34", - "15": "#19253F", - "20": "#24304A", - "25": "#2F3B56", - "30": "#3A4762", - "35": "#46526E", - "40": "#525E7B", - "50": "#6A7794", - "60": "#8491AF", - "70": "#9EABCB", - "80": "#BAC6E7", - "90": "#D8E2FF", - "95": "#EDF0FF", - "98": "#F9F9FF", - "99": "#FEFBFF", - "100": "#FFFFFF" - }, - "tertiary": { - "0": "#000000", - "5": "#23012A", - "10": "#2F0C36", - "15": "#3A1741", - "20": "#46224C", - "25": "#522D58", - "30": "#5F3964", - "35": "#6B4471", - "40": "#78507D", - "50": "#936897", - "60": "#AE82B2", - "70": "#CB9CCE", - "80": "#E7B6EB", - "90": "#FED6FF", - "95": "#FFEBFC", - "98": "#FFF7FA", - "99": "#FFFBFF", - "100": "#FFFFFF" - }, - "neutral": { - "0": "#000000", - "5": "#101114", - "10": "#1B1B1F", - "15": "#252629", - "20": "#303034", - "25": "#3B3B3F", - "30": "#46464A", - "35": "#525256", - "40": "#5E5E62", - "50": "#77777A", - "60": "#919094", - "70": "#ABABAF", - "80": "#C7C6CA", - "90": "#E3E2E6", - "95": "#F2F0F4", - "98": "#FAF8FD", - "99": "#FEFBFF", - "100": "#FFFFFF" - }, - "neutral-variant": { - "0": "#000000", - "5": "#0E1118", - "10": "#191B22", - "15": "#23262D", - "20": "#2E3038", - "25": "#393B43", - "30": "#44474F", - "35": "#50525A", - "40": "#5C5E66", - "50": "#75777F", - "60": "#8E9099", - "70": "#A9ABB4", - "80": "#C4C6D0", - "90": "#E1E2EC", - "95": "#EFF0FA", - "98": "#F9F9FF", - "99": "#FEFBFF", - "100": "#FFFFFF" - } - } -} \ No newline at end of file diff --git a/ui/ui/components/app_bar.slint b/ui/ui/components/app_bar.slint deleted file mode 100644 index 774e15e..0000000 --- a/ui/ui/components/app_bar.slint +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { IconButton } from "./icon_button.slint"; -import { MaterialText } from "./material_text.slint"; -import { MaterialTypography, TextStyle } from "../styling/material_typography.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { IconButtonItem } from "./bottom_app_bar.slint"; - -component BaseAppBar { - in property title; - in property leading_button; - in property show_background; - in property <[IconButtonItem]> trailing_buttons; - in property horizontal_alignment; - in property spacing; - in property vertical_spacing; - in property two_rows; - in property text_style; - in property text_padding; - in property appbar_padding_right: 0; - in property appbar_padding_top: 0; - in property appbar_padding_bottom: 0; - - callback leading_button_clicked(); - callback trailing_button_clicked(index: int); - - min_width: layout.min_width; - min_height: layout.min_height; - - Rectangle { - background: root.show_background ? MaterialPalette.surface_container : MaterialPalette.surface; - - layout := VerticalLayout { - padding_top: root.appbar_padding_top; - padding_bottom: root.appbar_padding_bottom; - min_width: top_layout.min_width; - spacing: root.vertical_spacing; - - top_layout := HorizontalLayout { - padding_left: root.spacing; - padding_right: root.appbar_padding_right; - spacing: MaterialStyleMetrics.spacing_6; - - if root.leading_button.icon.width > 0 && root.leading_button.icon.height > 0 : VerticalLayout { - alignment: center; - - IconButton { - icon: root.leading_button.icon; - enabled: root.leading_button.enabled; - tooltip: root.leading_button.tooltip; - - clicked => { - root.leading_button_clicked(); - } - } - } - - if !root.two_rows : MaterialText { - horizontal_stretch: 1; - text: root.title; - vertical_alignment: center; - horizontal_alignment: root.horizontal_alignment; - color: MaterialPalette.on_surface; - overflow: elide; - style: root.text_style; - } - - // spacer - if root.two_rows : Rectangle { - horizontal_stretch: 1; - } - - if root.trailing_buttons.length > 0 : VerticalLayout { - alignment: center; - - HorizontalLayout { - spacing: top_layout.spacing; - - for item[index] in root.trailing_buttons : IconButton { - icon: item.icon; - enabled: item.enabled; - tooltip: item.tooltip; - - clicked => { - root.trailing_button_clicked(index); - } - } - } - } - } - - if root.two_rows && root.title != "" : HorizontalLayout { - padding_left: root.text_padding; - padding_right: self.padding_left; - - MaterialText { - text: root.title; - horizontal_alignment: root.horizontal_alignment; - color: MaterialPalette.on_surface; - overflow: elide; - style: root.text_style; - } - } - } - } -} - -export component AppBar { - in property title; - in property leading_button; - in property trailing_button; - in property show_background; - - callback leading_button_clicked(); - callback trailing_button_clicked(); - - min_height: max(MaterialStyleMetrics.size_64, base.min_height); - - HorizontalLayout { - base := BaseAppBar { - title: root.title; - horizontal_alignment: center; - - leading_button: root.leading_button; - trailing_buttons: [root.trailing_button]; - show_background: root.show_background; - - appbar_padding_right: self.padding_left; - appbar_padding_top: MaterialStyleMetrics.padding_8; - appbar_padding_bottom: self.padding_top; - spacing: MaterialStyleMetrics.spacing_6; - text_style: MaterialTypography.title_large; - - leading_button_clicked => { - root.leading_button_clicked(); - } - - trailing_button_clicked(index) => { - root.trailing_button_clicked(); - } - } - } -} - -export component SmallAppBar { - in property title; - in property leading_button; - in property <[IconButtonItem]> trailing_buttons; - in property show_background; - - callback leading_button_clicked(); - callback trailing_button_clicked(index: int); - - min_height: max(MaterialStyleMetrics.size_64, base.min_height); - - HorizontalLayout { - base := BaseAppBar { - title: root.title; - horizontal_alignment: left; - leading_button: root.leading_button; - trailing_buttons: root.trailing_buttons; - show_background: root.show_background; - appbar_padding_right: self.padding_left; - appbar_padding_top: MaterialStyleMetrics.padding_8; - appbar_padding_bottom: self.padding_top; - spacing: MaterialStyleMetrics.spacing_4; - text_style: MaterialTypography.title_large; - - leading_button_clicked => { - root.leading_button_clicked(); - } - - trailing_button_clicked(index) => { - root.trailing_button_clicked(index); - } - } - } -} - -export component MediumAppBar { - in property title; - in property leading_button; - in property <[IconButtonItem]> trailing_buttons; - in property show_background; - - callback leading_button_clicked(); - callback trailing_button_clicked(index: int); - - min_height: max(MaterialStyleMetrics.size_64, base.min_height); - - HorizontalLayout { - base := BaseAppBar { - title: root.title; - horizontal_alignment: left; - leading_button: root.leading_button; - trailing_buttons: root.trailing_buttons; - show_background: root.show_background; - appbar_padding_right: self.padding_left; - appbar_padding_top: MaterialStyleMetrics.padding_8; - appbar_padding_bottom: MaterialStyleMetrics.padding_24; - vertical_spacing: MaterialStyleMetrics.spacing_4; - spacing: MaterialStyleMetrics.spacing_4; - two_rows: true; - text_style: MaterialTypography.headline_small; - text_padding: MaterialStyleMetrics.padding_16; - - leading_button_clicked => { - root.leading_button_clicked(); - } - - trailing_button_clicked(index) => { - root.trailing_button_clicked(index); - } - } - } -} - -export component LargeAppBar { - in property title; - in property leading_button; - in property <[IconButtonItem]> trailing_buttons; - in property show_background; - - callback leading_button_clicked(); - callback trailing_button_clicked(index: int); - - min_height: max(MaterialStyleMetrics.size_64, base.min_height); - - HorizontalLayout { - base := BaseAppBar { - title: root.title; - horizontal_alignment: left; - leading_button: root.leading_button; - trailing_buttons: root.trailing_buttons; - show_background: root.show_background; - appbar_padding_right: self.padding_left; - appbar_padding_top: MaterialStyleMetrics.padding_8; - appbar_padding_bottom: MaterialStyleMetrics.padding_28; - vertical_spacing: MaterialStyleMetrics.spacing_40; - spacing: MaterialStyleMetrics.spacing_4; - two_rows: true; - text_style: MaterialTypography.headline_medium; - text_padding: MaterialStyleMetrics.padding_16; - - leading_button_clicked => { - root.leading_button_clicked(); - } - - trailing_button_clicked(index) => { - root.trailing_button_clicked(index); - } - } - } -} diff --git a/ui/ui/components/badge.slint b/ui/ui/components/badge.slint deleted file mode 100644 index eacae3e..0000000 --- a/ui/ui/components/badge.slint +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialText } from "material_text.slint"; - -export component Badge { - in property text; - - width: max(MaterialStyleMetrics.size_16, label.width + 2 * MaterialStyleMetrics.padding_4); - height: max(MaterialStyleMetrics.size_16, label.height); - - background_layer := Rectangle { - width: parent.width; - height: parent.height; - border_radius: self.height / 2; - background: MaterialPalette.error; - } - - label := MaterialText { - text: root.text; - color: MaterialPalette.on_error; - vertical_alignment: center; - horizontal_alignment: center; - style: MaterialTypography.label_small; - } - - states [ - small when root.text == "" : { - background_layer.width: MaterialStyleMetrics.size_6; - background_layer.height: MaterialStyleMetrics.size_6; - } - ] -} diff --git a/ui/ui/components/base_button.slint b/ui/ui/components/base_button.slint deleted file mode 100644 index 950e734..0000000 --- a/ui/ui/components/base_button.slint +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { StateLayerArea } from "./state_layer.slint"; -import { MaterialText } from "./material_text.slint"; -import { Icon } from "./icon.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { Avatar } from "./list.slint"; - -export component BaseButton inherits StateLayerArea { - in property icon; - in property icon_color: self.color; - in property text; - in property button_horizontal_padding: MaterialStyleMetrics.padding_24; - in property button_vertical_padding: MaterialStyleMetrics.padding_10; - in property button_padding_left: self.button_horizontal_padding; - in property button_padding_right: self.button_horizontal_padding; - in property button_padding_top: self.button_vertical_padding; - in property button_padding_bottom: self.button_vertical_padding; - in property spacing: MaterialStyleMetrics.spacing_8; - in property min_layout_width: MaterialStyleMetrics.size_40; - in property min_layout_height: MaterialStyleMetrics.size_40; - in property icon_size: MaterialStyleMetrics.icon_size_18; - in property avatar_icon; - in property avatar_size; - in property avatar_background: #00000000; - out property has_icon: root.icon.width > 0 && root.icon.height > 0; - out property has_avatar: root.avatar_icon.width > 0 && root.avatar_icon.height > 0; - - min_width: max(root.min_layout_width, layout.min_width); - min_height: max(root.min_layout_height, layout.min_height); - tooltip_offset: root.height / 2 + root.icon_size / 2 + MaterialStyleMetrics.padding_14; - - layout := HorizontalLayout { - padding_left: root.button_padding_left; - padding_right: root.button_padding_right; - padding_top: root.button_padding_top; - padding_bottom: root.button_padding_bottom; - spacing: root.spacing; - alignment: center; - - if root.has_icon || root.has_avatar : VerticalLayout { - alignment: center; - - if root.has_avatar && root.avatar_size > 0 : Avatar { - width: root.avatar_size; - height: self.width; - image: root.avatar_icon; - background: root.avatar_background; - } - - if root.has_icon : Icon { - source: root.icon; - colorize: root.icon_color; - opacity: root.enabled ? 100% : 38%; - width: root.icon_size; - } - } - - if root.text != "" : MaterialText { - text: root.text; - style: MaterialTypography.label_large; - color: root.color; - opacity: root.enabled ? 100% : 38%; - overflow: clip; - vertical_alignment: center; - } - - @children - } -} diff --git a/ui/ui/components/base_navigation.slint b/ui/ui/components/base_navigation.slint deleted file mode 100644 index a8a4100..0000000 --- a/ui/ui/components/base_navigation.slint +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { NavigationItem } from "../items/navigation_item.slint"; - -export component BaseNavigationItemTemplate { - in property icon; - in property selected_icon; - in property text; - in property index; - in property selected; - in property show_badge; - in property badge; - - callback clicked; - callback pointer_event(event: PointerEvent, position: Point); - - accessible-role: tab; - accessible-label: root.text; - accessible-item-index: root.index; - accessible-item-selectable: true; - accessible-item-selected: root.selected; - accessible-action-default => { self.clicked(); } - - @children - -} - -export component BaseNavigation { - in property <[NavigationItem]> items; - in_out property current_index; - - callback index_changed(index: int); - - accessible-role: tab-list; - // accessible-delegate-focus: root.current-focused >= 0 ? root.current-focused : root.current-index; - // accessible-label: root.title; - accessible-item-count: root.items.length; - - @children - - protected function select(index: int) { - if index < 0 || index >= root.items.length { - return; - } - - root.current_index = index; - root.index_changed(index); - } - -} diff --git a/ui/ui/components/bottom_app_bar.slint b/ui/ui/components/bottom_app_bar.slint deleted file mode 100644 index daea6ac..0000000 --- a/ui/ui/components/bottom_app_bar.slint +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { IconButton } from "./icon_button.slint"; -import { FloatingActionButton, FABStyle } from "./floating_action_button.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; - -export struct IconButtonItem { - icon: image, - tooltip: string, - enabled: bool -} - -export component BottomAppBar { - in property <[IconButtonItem]> icon_buttons; - in property fab_icon; - - callback fab_clicked(); - callback icon_button_clicked(index: int); - - min_height: max(MaterialStyleMetrics.size_80, layout.min_height); - - Rectangle { - background: MaterialPalette.surface_container; - - layout := HorizontalLayout { - padding_left: MaterialStyleMetrics.padding_4; - padding_right: MaterialStyleMetrics.padding_16; - - VerticalLayout { - alignment: center; - - HorizontalLayout { - for button[index] in root.icon_buttons : IconButton { - icon: button.icon; - enabled: button.enabled; - tooltip: button.tooltip; - - clicked => { - root.icon_button_clicked(index); - } - } - } - } - - // spacer - Rectangle {} - - if root.fab_icon.width > 0 && root.fab_icon.height > 0 : VerticalLayout { - alignment: center; - - FloatingActionButton { - icon: root.fab_icon; - style: FABStyle.standard; - - clicked => { - root.fab_clicked(); - } - } - } - } - } -} diff --git a/ui/ui/components/bottom_sheet.slint b/ui/ui/components/bottom_sheet.slint deleted file mode 100644 index 0883352..0000000 --- a/ui/ui/components/bottom_sheet.slint +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { Elevation } from "./elevation.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { Horizontal } from "./horizontal.slint"; -import { MaterialAnimations } from "../styling/material_animations.slint"; -import { Modal } from "./modal.slint"; - -component BottomSheet { - callback drag_moved(offset_y: length); - callback pressed(); - callback released(); - - elevation := Elevation { - width: 100%; - height: 100%; - level: 3; - border_radius: MaterialStyleMetrics.border_radius_16; - } - - TouchArea { - Rectangle { - background: MaterialPalette.surface_container_low; - border_top_left_radius: MaterialStyleMetrics.border_radius_16; - border_top_right_radius: self.border_top_left_radius; - - VerticalLayout { - alignment: start; - - drag_handle := TouchArea { - height: MaterialStyleMetrics.size_36; - - Rectangle { - width: MaterialStyleMetrics.size_32; - height: MaterialStyleMetrics.size_4; - background: MaterialPalette.outline; - border_radius: self.height / 2; - } - - moved => { - root.drag_moved(self.pressed_y - self.mouse_y); - } - - changed pressed => { - if self.pressed { - root.pressed(); - return; - } - - root.released(); - } - } - - Horizontal { - @children - } - } - } - } -} - -export component ModalBottomSheet inherits PopupWindow { - close_policy: no_auto_close; - - property drag_margin: MaterialStyleMetrics.padding_56; - - modal := Modal { - width: 100%; - height: 100%; - - sheet := BottomSheet { - property y_duration: MaterialAnimations.standard_accelerate_duration; - property y_drag_start; - property height_drag_start; - - x: (parent.width - self.width) / 2; - y: parent.height; - width: min(MaterialStyleMetrics.size_640, parent.width - 2 * MaterialStyleMetrics.padding_56); - - @children - - drag_moved(offset_y) => { - self.y_duration = 0; - self.y = max(self.y - offset_y, self.y_drag_start); - self.height += offset_y; - } - - pressed => { - self.y_drag_start = self.y; - self.height_drag_start = self.height; - } - - released => { - if self.y >= root.height - root.drag_margin { - root.close(); - return; - } - - self.y_duration = MaterialAnimations.standard_accelerate_duration; - self.height = self.height_drag_start; - self.y = self.y_drag_start; - } - - animate y, height { - duration: self.y_duration; - easing: MaterialAnimations.standard_easing; - } - } - - clicked => { - root.close(); - } - } - - Timer { - interval: 50ms; - - triggered => { - sheet.y = modal.height - sheet.height; - self.running = false; - } - } -} diff --git a/ui/ui/components/card.slint b/ui/ui/components/card.slint deleted file mode 100644 index 6afebef..0000000 --- a/ui/ui/components/card.slint +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { Elevation } from "./elevation.slint"; -import { StateLayerArea } from "./state_layer.slint"; - -component BaseCard { - in property clickable; - in property has_elevation; - in property background; - in property border_color; - in property border_width; - - callback clicked(); - - forward_focus: state_layer; - - if root.has_elevation : Elevation { - width: 100%; - height: 100%; - border_radius: background_layer.border_radius; - level: 1; - } - - background_layer := Rectangle { - background: root.background; - border_radius: MaterialStyleMetrics.border_radius_12; - border_width: root.border_width; - border_color: root.border_color; - } - - state_layer := StateLayerArea { - width: 100%; - height: 100%; - border_radius: background_layer.border_radius; - visible: root.clickable; - enabled: root.visible; - - clicked => { - root.clicked(); - } - } - - Rectangle { - clip: true; - border_radius: MaterialStyleMetrics.border_radius_12; - - @children - } -} - -export component ElevatedCard { - in property clickable <=> base.clickable; - - callback clicked <=> base.clicked; - - forward_focus: base; - accessible-role: button; - accessible-enabled: root.clickable; - accessible-action-default => { base.clicked(); } - - base := BaseCard { - width: 100%; - height: 100%; - has_elevation: true; - background: MaterialPalette.surface; - - @children - } -} - -export component FilledCard { - in property clickable <=> base.clickable; - - callback clicked <=> base.clicked; - - forward_focus: base; - accessible-role: button; - accessible-enabled: root.clickable; - accessible-action-default => { base.clicked(); } - - base := BaseCard { - width: 100%; - height: 100%; - background: MaterialPalette.surface_container_highest; - - @children - } -} - -export component OutlinedCard { - in property clickable <=> base.clickable; - - callback clicked <=> base.clicked; - - forward_focus: base; - accessible-role: button; - accessible-enabled: root.clickable; - accessible-action-default => { base.clicked(); } - - base := BaseCard { - width: 100%; - height: 100%; - background: MaterialPalette.surface_container_low; - border_width: 1px; - border_color: MaterialPalette.outline; - - @children - } -} diff --git a/ui/ui/components/check_box.slint b/ui/ui/components/check_box.slint deleted file mode 100644 index 9d5311b..0000000 --- a/ui/ui/components/check_box.slint +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialAnimations } from "../styling/material_animations.slint"; -import { Icons } from "../icons/icons.slint"; -import { ListTile } from "./list.slint"; -import { StateLayerArea } from "./state_layer.slint"; -import { Icon } from "./icon.slint"; - -export enum CheckState { - unchecked, - partially_checked, - checked, -} - -export component CheckBox { - - in_out property check_state; - in_out property tristate; - in property has_error; - in property enabled <=> state_area.enabled; - - property checked: root.check_state == CheckState.checked || root.check_state == CheckState.partially_checked; - - callback checked_state_changed(check_state: CheckState); - - min_width: MaterialStyleMetrics.size_48; - min_height: self.min_width; - accessible-enabled: root.enabled; - accessible-checkable: true; - accessible-checked <=> root.checked; - accessible-role: checkbox; - accessible-action-default => { state_area.clicked(); } - forward_focus: state_area; - - init() => { - // Initialize tristate as well - if self.check_state == CheckState.partially-checked { - root.tristate = true; - } - } - - state_area := StateLayerArea { - width: 100%; - height: 100%; - border_radius: max(self.width, self.height) / 2; - color: MaterialPalette.on_surface; - - background_layer := Rectangle { - width: MaterialStyleMetrics.size_18; - height: self.width; - border_radius: MaterialStyleMetrics.border_radius_2; - border_width: root.checked ? 0 : 2px; - border_color: root.has_error ? MaterialPalette.error : MaterialPalette.on_surface_variant; - - if root.checked : Icon { - colorize: MaterialPalette.on_primary; - source: Icons.check; - - states [ - partial_checked when root.check_state == CheckState.partially-checked : { - source: Icons.remove; - } - ] - } - - animate background, border_width { - duration: MaterialAnimations.opacity_duration; - easing: MaterialAnimations.opacity_easing; - } - } - - clicked => { - root.toggle(); - } - } - - changed check_state => { - if self.check_state == CheckState.partially-checked { - root.tristate = true; - } - root.checked_state_changed(root.check_state); - } - - public function toggle() { - if !root.tristate { - root.check_state = root.check_state != CheckState.checked ? CheckState.checked : CheckState.unchecked; - return; - } - - root.check_state = root.check_state == CheckState.checked ? CheckState.partially_checked : - root.check_state == CheckState.partially_checked ? CheckState.unchecked : CheckState.checked; - } - - // Keep for backward compatibility - public function set_check_state(check_state: CheckState) { - root.check_state = check_state; - } - - states [ - disabled when !root.enabled : { - state_area.display_background: false; - background_layer.border_color: root.checked ? transparent : MaterialPalette.on_surface.with_alpha(38%); - background_layer.background: root.checked ? MaterialPalette.on_surface.with_alpha(38%) : transparent; - } - checked when root.checked : { - background_layer.border_width: 0; - background_layer.background: root.has_error ? MaterialPalette.error : MaterialPalette.primary; - state_area.color: background_layer.background; - } - ] -} - -export component CheckBoxTile inherits ListTile { - in_out property check_state <=> check_box.check_state; - in_out property tristate <=> check_box.tristate; - in property has_error <=> check_box.has_error; - - callback checked_state_changed <=> check_box.checked_state_changed; - - Rectangle { - horizontal-stretch: 0; - - check_box := CheckBox { - enabled: root.enabled; - } - } - - // When clicked on this, but not on the checkbox - // apply the click on the checkbox manually - // So the user can click anywhere in the tile to toggle - // the checkbox - clicked => { - check_box.toggle(); - } - - // Keep function for backward compatibility - public function set_check_state(check-state: CheckState) { - check_box.set_check_state(check_state); - } -} diff --git a/ui/ui/components/chip.slint b/ui/ui/components/chip.slint deleted file mode 100644 index 4182279..0000000 --- a/ui/ui/components/chip.slint +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { BaseButton } from "./base_button.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { Icons } from "../icons/icons.slint"; -import { MaterialAnimations } from "../styling/material_animations.slint"; -import { IconButton } from "icon_button.slint"; - -export component ActionChip { - in property icon <=> base.icon; - in property text <=> base.text; - in property enabled <=> base.enabled; - in property tooltip <=> base.tooltip; - in property avatar_icon <=> base.avatar-icon; - in property avatar_background <=> base.avatar_background; - - callback clicked <=> base.clicked; - - forward-focus: base; - accessible-role: button; - accessible-enabled: root.enabled; - accessible-label: root.text == "" ? root.tooltip : root.text; - accessible-action-default => { base.clicked(); } - - - border := Rectangle { - border_radius: base.border_radius; - border_width: 1px; - border_color: MaterialPalette.outline; - - base := BaseButton { - min_layout_height: MaterialStyleMetrics.size_32; - border_radius: MaterialStyleMetrics.border_radius_8; - color: MaterialPalette.on_surface; - icon_color: MaterialPalette.primary; - spacing: MaterialStyleMetrics.spacing_8; - button_padding_left: self.has_icon ? MaterialStyleMetrics.padding_8 : MaterialStyleMetrics.padding_16; - button_padding_right:MaterialStyleMetrics.padding_16; - button_vertical_padding: MaterialStyleMetrics.padding_6; - avatar_size: MaterialStyleMetrics.size_18; - } - } - - states [ - disabled when !root.enabled : { - base.display_background: false; - base.icon_color: MaterialPalette.on_surface; - } - ] -} - -export component FilterChip { - in property icon <=> base.icon; - in property text <=> base.text; - in property enabled <=> base.enabled; - in property tooltip <=> base.tooltip; - in_out property checked; - - forward-focus: base; - accessible-role: button; - accessible-enabled: root.enabled; - accessible-label: root.text == "" ? root.tooltip : root.text; - accessible-checkable: true; - accessible-checked: root.checked; - accessible-action-default => { base.clicked(); } - - border := Rectangle { - border_radius: base.border_radius; - border_width: 1px; - border_color: MaterialPalette.outline; - - - base := BaseButton { - min_layout_height: MaterialStyleMetrics.size_32; - border_radius: MaterialStyleMetrics.border_radius_8; - color: MaterialPalette.on_surface; - icon_color: MaterialPalette.primary; - spacing: MaterialStyleMetrics.spacing_8; - button_padding_left: self.has_icon ? MaterialStyleMetrics.padding_8 : MaterialStyleMetrics.padding_16; - button_padding_right:MaterialStyleMetrics.padding_16; - button_vertical_padding: MaterialStyleMetrics.padding_6; - avatar_size: MaterialStyleMetrics.size_18; - - clicked => { - root.toggle(); - } - } - - states [ - checked when root.checked : { - base.icon: Icons.check; - base.icon_color: MaterialPalette.on_secondary_container; - base.color: MaterialPalette.on_secondary_container; - border.border_width: 0; - border.background: MaterialPalette.secondary_container; - } - ] - - animate width { duration: MaterialAnimations.standard_accelerate_duration; easing: MaterialAnimations.standard_easing; } - } - - function toggle() { - root.checked = !root.checked; - } - - states [ - disabled when !root.enabled : { - base.display_background: false; - base.icon_color: MaterialPalette.on_surface; - } - ] -} - -export component InputChip { - in property leading_icon <=> base.icon; - in property trailing_icon; - in property avatar <=> base.avatar-icon; - in property avatar_background <=> base.avatar_background; - in property text <=> base.text; - in property enabled <=> base.enabled; - in property tooltip <=> base.tooltip; - in property checkable; - in_out property checked; - - property has_avatar: root.avatar.width > 0 && root.avatar.height > 0; - - callback clicked(); - callback trailing_icon_clicked(); - - forward-focus: base; - accessible-role: button; - accessible-enabled: root.enabled; - accessible-label: root.text == "" ? root.tooltip : root.text; - accessible-checkable: root.checkable; - accessible-checked: root.checked; - accessible-action-default => { base.clicked(); } - - border := Rectangle { - border_radius: base.border_radius; - border_width: 1px; - border_color: MaterialPalette.outline; - clip: true; - - base := BaseButton { - min_layout_height: MaterialStyleMetrics.size_32; - border_radius: root.has_avatar ? self.height / 2 : MaterialStyleMetrics.border_radius_8; - color: MaterialPalette.on_surface; - icon_color: MaterialPalette.primary; - spacing: MaterialStyleMetrics.spacing_8; - button_padding_left: root.has_avatar ? MaterialStyleMetrics.padding_4 : self.has_icon ? MaterialStyleMetrics.padding_8 : MaterialStyleMetrics.padding_12; - button_padding_right: root.trailing_icon.width > 0 && root.trailing_icon.height > 0 ? MaterialStyleMetrics.padding_8 : MaterialStyleMetrics.padding_12; - button_vertical_padding: MaterialStyleMetrics.padding_6; - avatar_size: MaterialStyleMetrics.size_18; - - if root.trailing_icon.width > 0 && root.trailing_icon.height > 0 : VerticalLayout { - alignment: center; - - IconButton { - icon: root.trailing_icon; - inline: true; - enabled: root.enabled; - - clicked => { - root.trailing_icon_clicked(); - } - } - } - - clicked => { - root.clicked(); - root.toggle(); - } - } - - states [ - checked when root.checked : { - base.icon_color: MaterialPalette.on_secondary_container; - base.color: MaterialPalette.on_secondary_container; - border.border_width: 0; - border.background: MaterialPalette.secondary_container; - } - ] - - animate width { duration: MaterialAnimations.standard_accelerate_duration; easing: MaterialAnimations.standard_easing; } - } - - function toggle() { - if !root.checkable { - return; - } - - root.checked = !root.checked; - } - - states [ - disabled when !root.enabled : { - base.display_background: false; - base.icon_color: MaterialPalette.on_surface; - } - ] -} - diff --git a/ui/ui/components/date_picker.slint b/ui/ui/components/date_picker.slint deleted file mode 100644 index 4b9538b..0000000 --- a/ui/ui/components/date_picker.slint +++ /dev/null @@ -1,566 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { BaseDialog } from "./dialog.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { Icons } from "../icons/icons.slint"; -import { MaterialText } from "./material_text.slint"; -import { StateLayer } from "./state_layer.slint"; -import { ScrollView } from "./scroll_view.slint"; -import { IconButton } from "./icon_button.slint"; -import { TextField } from "./text_field.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { ExtendedTouchArea } from "./extended_touch_area.slint"; - -// defines the interface to get the data for the DatePicker from native code. -export global DatePickerAdapter { - // returns the number of days for the given month in the given year. - pure callback month_day_count(month: int, year: int) -> int; - - // return the numbers of day to the first monday of the month. - pure callback month_offset(month: int, year: int) -> int; - - // used to format a date that is defined by day month and year. - pure callback format_date(format: string, day: int, month: int, year: int) -> string; - - // parses the given date string and returns a list of day, month and year. - pure callback parse_date(date: string, format: string) -> [int]; - - // returns true if the given date is valid. - pure callback valid_date(date: string, format: string) -> bool; - - // returns the current date as list of day, month and year. - pure callback date_now() -> [int]; -} - -export struct Date { - year: int, - month: int, - day: int, -} - -component CalendarHeaderDelegate { - in property text <=> text_label.text; - - min_width: max(MaterialStyleMetrics.size_40, content_layer.min_width); - min_height: max(MaterialStyleMetrics.size_40, content_layer.min_height); - - content_layer := VerticalLayout { - text_label := MaterialText { - vertical_alignment: center; - horizontal_alignment: center; - style: MaterialTypography.body_large; - color: MaterialPalette.on_surface; - } - } -} - -component CalendarDelegate { - in property selected; - in property today; - in property text <=> text_label.text; - in property enabled: true; - - callback clicked <=> touch_area.clicked; - - min_width: max(MaterialStyleMetrics.size_40, content_layer.min_width); - min_height: max(MaterialStyleMetrics.size_40, content_layer.min_height); - forward_focus: focus_scope; - - accessible_role: button; - accessible_checkable: true; - accessible_checked: root.selected; - accessible_label: root.text; - accessible_action_default => { touch_area.clicked(); } - - touch_area := TouchArea { - enabled: root.enabled; - } - - focus_scope := FocusScope { - width: 0px; - enabled: root.enabled; - - key_pressed(event) => { - if (event.text == " " || event.text == "\n") { - touch_area.clicked(); - return accept; - } - - return reject; - } - } - - background_layer := Rectangle { - border_radius: self.height / 2; - } - - content_layer := HorizontalLayout { - text_label := MaterialText { - vertical_alignment: center; - horizontal_alignment: center; - style: MaterialTypography.body_large; - color: MaterialPalette.on_surface; - } - } - - state_layer := StateLayer { - enabled: root.enabled; - border_radius: background_layer.border_radius; - pressed: touch_area.pressed; - has_hover: touch_area.has_hover; - has_focus: focus_scope.has_focus; - } - - states [ - // FIXME: states - disabled when !root.enabled : { - root.opacity: 0.38; - } - selected when root.selected : { - background_layer.background: MaterialPalette.primary; - text_label.color: MaterialPalette.on_primary; - } - today when root.today : { - background_layer.border_width: 1px; - background_layer.border_color: MaterialPalette.primary; - } - ] -} - -export component Calendar { - in property column_count; - in property row_count; - in property delegate_size; - in property start_column; - in property <[string]> header_model; - in property month_count; - in property today; - in property selected_date; - in property display_month; - in property display_year; - - callback select_date(date: Date); - - // header - for day[index] in root.header_model : CalendarHeaderDelegate { - x: root.delegate_x(index); - y: root.delegate_y(index); - text: day; - } - - // items - for index in root.month_count : CalendarDelegate { - property d: { day: index + 1, month: root.display_month, year: root.display_year }; - - x: root.delegate_x(root.index_on_calendar(index)); - y: root.delegate_y(root.index_on_calendar(index)); - width: root.delegate_size; - height: root.delegate_size; - text: index + 1; - selected: root.selected_date == self.d; - today: root.today == self.d; - - clicked => { - root.select_date(self.d); - } - } - - function index_on_calendar(index: int) -> int { - // add column count because items starts after header row - root.column_count + root.start_column + index - } - - function row_for_index(index: int) -> int { - floor(index / root.column_count) - } - - function column_for_index(index: int) -> int { - mod(index, root.column_count) - } - - function delegate_x(index: int) -> length { - root.column_for_index(index) * root.delegate_size + root.column_for_index(index) * 1px /* * root.style.spacing */ - } - - function delegate_y(index: int) -> length { - root.row_for_index(index) * root.delegate_size + root.row_for_index(index) * 1px /* * root.style.spacing */ - } -} - -component YearSelection { - in property <[int]> model; - in property spacing; - in property visible_row_count; - in property column_count; - in property delegate_width; - in property delegate_height; - in property selected_year; - in property today_year; - - callback select_year(year: int); - - property row_height: root.height / root.visible_row_count; - property row_count: root.model.length / root.column_count; - property viewport_height: root.row_count * root.row_height; - property start_x: root.width / (root.column_count + 1); - property start_y: root.height / (root.visible_row_count + 1); - - ScrollView { - width: 100%; - height: 100%; - viewport_width: root.width; - viewport_height: root.viewport_height; - - for year[index] in root.model: CalendarDelegate { - x: root.delegate_center_x(index) - self.width / 2; - y: root.delegate_center_y(index) - self.height / 2; - width: root.delegate_width; - height: root.delegate_height; - text: year; - selected: year == root.selected_year; - today: year == root.today_year; - - clicked => { - root.select_year(year); - } - } - } - - function delegate_center_x(index: int) -> length { - root.start_x * (root.column_for_index(index) + 1) - } - - function delegate_center_y(index: int) -> length { - root.start_y * (root.row_for_index(index) + 1) - } - - function row_for_index(index: int) -> int { - floor(index / root.column_count) - } - - function column_for_index(index: int) -> int { - mod(index, root.column_count) - } -} - -export component SelectionButton { - in property text <=> text-label.text; - in property icon <=> icon-image.source; - in property enabled: true; - in-out property checked; - - callback clicked(); - - min-width: content-layer.min-width; - min-height: max(40px, content-layer.min-height); - accessible-label: root.text; - accessible-role: button; - accessible-checkable: true; - accessible-checked: root.checked; - accessible-action-default => { touch-area.clicked(); } - forward-focus: touch-area; - - touch-area := ExtendedTouchArea { - width: 100%; - height: 100%; - enabled: root.enabled; - - clicked => { - root.checked = !root.checked; - root.clicked(); - } - } - - state-layer := StateLayer { - enabled: root.enabled; - pressed: touch-area.pressed; - has-hover: touch-area.has-hover; - has-focus: touch-area.has-focus; - background: text_label.color; - } - - content-layer := HorizontalLayout { - alignment: center; - padding-left: 8px; - padding-right: 8px; - spacing: 8px; - - text-label := MaterialText { - vertical-alignment: center; - color: MaterialPalette.on_surface_variant; - style: MaterialTypography.label_large; - } - - icon-image := Image { - y: (parent.height - self.height) / 2; - width: MaterialStyleMetrics.size_18; - colorize: MaterialPalette.on_surface_variant; - transform-rotation: root.checked ? 180deg : 0; - - animate transform-rotation { duration: 250ms; } - } - } - - states [ - disabled when !root.enabled : { - root.opacity: 0.38; - } - ] -} - - -export component DatePickerPopup inherits PopupWindow { - in property title; - in property date : { day: today[0], month: today[1], year: today[2] }; - - callback canceled(); - callback accepted(date: Date); - - // this is used for the navigation between months - property input_title: @tr("Enter date"); - property input_placeholder_text: "mm/dd/yyyy"; - property input_format: "%m/%d/%Y"; - property cancel_text: @tr("Cancel"); - property ok_text: @tr("Ok"); - property display_date: root.date; - property current_date: root.date; - property delegate_size: 40px; - property year_delegate_width: 72px; - property calendar_column_count: 7; - property calendar_row_count: 6; - property calendar_min_width: root.delegate_size * root.calendar_column_count + (root.calendar_column_count - 1) * 1px; - property calendar_min_height: root.delegate_size *(root.calendar_row_count + 1) + (root.calendar_row_count - 1) * 1px; - property year_selection_column_count: 3; - property year_selection_row_count: 5; - property year_selection; - property selection_mode: true; - property current_input; - - property calendar_month_count: DatePickerAdapter.month_day_count(root.display_date.month, root.display_date.year); - property <[string]> calendar_header_model: [ - @tr("One-letter abbrev for Sunday" => "S"), - @tr("One-letter abbrev for Monday" => "M"), - @tr("One-letter abbrev for Tuesday" => "T"), - @tr("One-letter abbrev for Wednesday" => "W"), - @tr("One-letter abbrev for Thursday" => "T"), - @tr("One-letter abbrev for Friday" => "F"), - @tr("One-letter abbrev for Saturday" => "S"), - ]; - property <[int]> today: DatePickerAdapter.date_now(); - property start_column: DatePickerAdapter.month_offset(root.display_date.month, root.display_date.year); - property current_month: DatePickerAdapter.format_date("%B %Y", root.display_date.day, root.display_date.month, root.display_date.year); - property <[int]> year_model: [2024, 2025, 2026, 2027, 2028, 2029, 2031, 2032, 2033, 2034, 2035, 2036, 2037, 2038, 2039, 2040, 2041, 2042, 2043]; - property selected_year: root.display_date.year; - property today_year: root.today[2]; - property current_day: DatePickerAdapter.format_date("%a, %b %d", root.current_date.day, root.current_date.month, root.current_date.year); - property <[int]> input_formatted: DatePickerAdapter.parse_date(root.current_input, root.input_format); - - close_policy: no_auto_close; - forward_focus: base; - - base := BaseDialog { - width: 100%; - height: 100%; - title: root.title; - actions: [ - root.cancel_text, - root.ok_text - ]; - - content_layer := VerticalLayout { - spacing: MaterialStyleMetrics.spacing_12; - - header := HorizontalLayout { - MaterialText { - text: root.selection_mode ? root.current_day : root.input_title; - horizontal_alignment: left; - vertical_alignment: center; - style: MaterialTypography.headline_large; - } - - if root.selection_mode : IconButton { - icon: Icons.edit; - accessible_label: "Toggle selection mode"; - - clicked => { - root.toggle_selection_mode(); - } - } - } - - Rectangle { - height: 1px; - background: MaterialPalette.outline; - } - - if root.selection_mode : HorizontalLayout { - // FIXME: spacing - - VerticalLayout { - horizontal_stretch: 0; - alignment: center; - - SelectionButton { - text: root.current_month; - icon: Icons.arrow_drop_down; - checked <=> root.year_selection; - } - } - - Rectangle {} - - IconButton { - icon: Icons.chevron_backward; - accessible_label: "Previous month"; - - clicked => { - root.show_previous(); - } - } - - IconButton { - icon: Icons.chevron_forward; - accessible_label: "Next month"; - - clicked => { - root.show_next(); - } - } - } - - if root.selection_mode : VerticalLayout { - if !root.year_selection : Calendar { - min_width: root.calendar_min_width; - min_height: root.calendar_min_height; - column_count: root.calendar_column_count; - row_count: root.calendar_row_count; - delegate_size: root.delegate_size; - header_model: root.calendar_header_model; - month_count: root.calendar_month_count; - today: { day: root.today[0], month: root.today[1], year: root.today[2] }; - selected_date <=> root.current_date; - start_column: root.start_column; - display_month: root.display_date.month; - display_year: root.display_date.year; - - select_date(date) => { - root.select_date(date); - } - } - - if root.year_selection : YearSelection { - min_width: root.calendar_min_width; - min_height: root.calendar_min_height; - column_count: root.year_selection_column_count; - visible_row_count: root.year_selection_row_count; - delegate_width: root.year_delegate_width; - delegate_height: root.delegate_size; - model: root.year_model; - selected_year: root.selected_year; - today_year: root.today_year; - - select_year(year) => { - root.select_year(year); - } - } - } - - Rectangle { - height: 1px; - visible: root.year_selection; - background: MaterialPalette.outline; - } - - if !root.selection_mode : TextField { - text <=> root.current_input; - placeholder_text: root.input_placeholder_text; - trailing_icon: Icons.calendar_today; - - trailing_icon_clicked => { - root.toggle_selection_mode(); - } - } - } - - action_clicked(index) => { - // cancel - if index == 0 { - root.close(); - root.canceled(); - } else if index == 1 { - root.accepted(root.date); - } - } - - close => { - root.close(); - } - } - - changed date => { - root.display_date = root.date; - root.current_date = root.date; - } - - changed selection_mode => { - // check switch from input mode if input is valid - if root.selection_mode && root.current_input_valid() { - root.current_date = root.input_as_date(); - root.display_date = root.current_date; - } - } - - pure public function ok_enabled() -> bool { - root.selection_mode || root.current_input_valid() - } - - public function get_current_date() -> Date { - if root.selection_mode { - return root.current_date; - } - - root.input_as_date() - } - - pure function current_input_valid() -> bool { - DatePickerAdapter.valid_date(root.current_input, root.input_format) - } - - pure function input_as_date() -> Date { - { day: root.input_formatted[0], month: root.input_formatted[1], year: root.input_formatted[2] } - } - - function select_date(date: Date) { - root.current_date = date; - } - - function select_year(year: int) { - root.current_date = { day: 1, month: 1, year: year }; - root.display_date = root.current_date; - root.year_selection = false; - } - - function show_next() { - if root.display_date.month >= 12 { - root.display_date = { day: 1, month: 1, year: root.display_date.year + 1 }; - return; - } - - root.display_date = { day: 1, month: root.display_date.month + 1, year: root.display_date.year }; - } - - function show_previous() { - if root.display_date.month <= 1 { - root.display_date = { day: 1, month: 12, year: root.display_date.year - 1 }; - return; - } - - root.display_date = { day: 1, month: root.display_date.month - 1, year: root.display_date.year }; - } - - function toggle_selection_mode() { - root.selection_mode = !root.selection_mode; - } -} diff --git a/ui/ui/components/dialog.slint b/ui/ui/components/dialog.slint deleted file mode 100644 index fcdd916..0000000 --- a/ui/ui/components/dialog.slint +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { TextButton } from "./text_button.slint"; -import { FilledButton } from "./filled_button.slint"; -import { MaterialText } from "./material_text.slint"; -import { MaterialAnimations } from "../styling/material_animations.slint"; -import { Modal } from "./modal.slint"; -import { Icon } from "./icon.slint"; -import { IconButton } from "./icon_button.slint"; -import { Icons } from "../icons/icons.slint"; - -export component FullscreenDialog inherits PopupWindow { - in property title; - in property <[string]> actions; - - callback action_clicked(index: int); - - close_policy: no_auto_close; - forward_focus: focus_scope; - - focus_scope := FocusScope { - x: 0; - width: 0; - - key_pressed(event) => { - if event.text == Key.Escape { - root.close(); - return accept; - } - - reject - } - } - - background_layer := Rectangle { - width: 100%; - height: 100%; - opacity: 0; - background: MaterialPalette.surface; - - VerticalLayout { - spacing: MaterialStyleMetrics.spacing_16; - - HorizontalLayout { - padding: MaterialStyleMetrics.padding_24; - spacing: MaterialStyleMetrics.spacing_16; - vertical_stretch: 0; - - IconButton { - icon: Icons.close; - - clicked => { - root.close(); - } - } - - MaterialText { - text: root.title; - style: MaterialTypography.headline_small; - color: MaterialPalette.on_surface; - vertical_alignment: center; - } - - for action[index] in root.actions : TextButton { - text: action; - - clicked => { - root.action_clicked(index); - } - } - } - - @children - - } - } - - Timer { - interval: 50ms; - - triggered => { - background_layer.opacity = 1; - self.running = false; - } - } -} - -export component BaseDialog { - in property title; - in property icon; - in property <[image]> action_button_icons; - in property default_action_text; - in property <[string]> actions; - - property has_icon: root.icon.width > 0 && root.icon.height > 0; - - callback default_action_clicked(); - callback action_button_clicked(index: int); - callback action_clicked(index: int); - callback close(); - - forward_focus: focus_scope; - - focus_scope := FocusScope { - x: 0; - width: 0; - - key_pressed(event) => { - if event.text == Key.Return && root.default_action_text != "" { - root.default_action_clicked(); - return accept; - } - - if event.text == Key.Escape { - root.close(); - return accept; - } - - reject - } - } - - modal := Modal { - width: 100%; - height: 100%; - - background_layer := Rectangle { - x: (parent.width - self.width) / 2; - y: (parent.height - self.height) / 2; - width: layout.min_width; - height: layout.min_height; - opacity: 0; - border_radius: MaterialStyleMetrics.border_radius_28; - background: MaterialPalette.surface_container_high; - - TouchArea { - layout := VerticalLayout { - padding: MaterialStyleMetrics.padding_24; - spacing: MaterialStyleMetrics.spacing_16; - - if root.has_icon : Icon { - x: (parent.width - self.width) / 2; - colorize: MaterialPalette.on_surface; - source: root.icon; - } - - if root.title != "" : MaterialText { - horizontal_alignment: root.has_icon ? center : left; - text: root.title; - style: MaterialTypography.headline_small; - color: MaterialPalette.on_surface; - } - - @children - - HorizontalLayout { - spacing: MaterialStyleMetrics.spacing_8; - - for action_button[index] in root.action_button_icons : IconButton { - icon: action_button; - - clicked => { - root.action_button_clicked(index); - } - } - - // Spacer - Rectangle {} - - for action[index] in root.actions : TextButton { - text: action; - - clicked => { - root.action_clicked(index); - } - } - - if root.default_action_text != "" : FilledButton { - text: root.default_action_text; - - clicked => { - root.default_action_clicked(); - } - } - } - } - } - - animate opacity { duration: MaterialAnimations.opacity_duration; easing: MaterialAnimations.opacity_easing; } - } - - clicked => { - root.close(); - } - } - - Timer { - interval: 50ms; - - triggered => { - background_layer.opacity = 1; - self.running = false; - } - } -} - -export component Dialog inherits PopupWindow { - in property title <=> base.title; - in property icon <=> base.icon; - in property default_action_text <=> base.default_action_text; - in property <[string]> actions <=> base.actions; - - callback default_action_clicked <=> base.default_action_clicked; - callback action_clicked <=> base.action_clicked; - - close_policy: no_auto_close; - forward_focus: base; - - base := BaseDialog { - width: 100%; - height: 100%; - - @children - - close => { - root.close(); - } - } -} diff --git a/ui/ui/components/divider.slint b/ui/ui/components/divider.slint deleted file mode 100644 index c7b9498..0000000 --- a/ui/ui/components/divider.slint +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialPalette } from "../styling/material_palette.slint"; - -component BaseDivider { - Rectangle { - background: MaterialPalette.outline_variant; - } -} - -export component VerticalDivider { - width: 1px; - - BaseDivider { - width: 100%; - height: 100%; - } -} - -export component HorizontalDivider { - height: 1px; - - BaseDivider { - width: 100%; - height: 100%; - } -} diff --git a/ui/ui/components/drawer.slint b/ui/ui/components/drawer.slint deleted file mode 100644 index 5938d44..0000000 --- a/ui/ui/components/drawer.slint +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialText } from "./material_text.slint"; -import { MaterialAnimations } from "../styling/material_animations.slint"; -import { Modal } from "./modal.slint"; - -export component DrawerHeader { - in property title; - - min_height: max(MaterialStyleMetrics.size_56, title_label.min_width); - - title_label := MaterialText { - x: MaterialStyleMetrics.padding_16; - width: root.width - MaterialStyleMetrics.padding_16; - text: root.title; - color: MaterialPalette.on_surface_variant; - vertical_alignment: center; - style: MaterialTypography.title_small; - } -} - -export component Drawer { - in property title; - - min_width: max(MaterialStyleMetrics.size_360, layout.min_width); - - TouchArea { - - Rectangle { - background: MaterialPalette.surface_container_low; - border_radius: MaterialStyleMetrics.border_radius_16; - - layout := VerticalLayout { - alignment: start; - padding: MaterialStyleMetrics.padding_12; - - if root.title != "" : DrawerHeader { - width: 100%; - title: root.title; - } - - @children - } - } - } -} - -export component ModalDrawer inherits PopupWindow { - in property position_right; - in property title; - - close_policy: no_auto_close; - - modal := Modal { - width: 100%; - height: 100%; - - drawer := Drawer { - x: root.position_right ? parent.width : -self.width; - y: 0; - height: 100%; - title: root.title; - - @children - - animate x { - duration: MaterialAnimations.standard_accelerate_duration; - easing: MaterialAnimations.standard_easing; - } - } - - clicked => { - root.close(); - } - } - - Timer { - interval: 50ms; - - triggered => { - drawer.x = root.position_right ? root.width - drawer.width : 0; - self.running = false; - } - } -} diff --git a/ui/ui/components/drop_down_menu.slint b/ui/ui/components/drop_down_menu.slint deleted file mode 100644 index 7f96101..0000000 --- a/ui/ui/components/drop_down_menu.slint +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { TextField } from "./text_field.slint"; -import { MenuItem } from "../items/menu_item.slint"; -import { Icons } from "../icons/icons.slint"; -import { MenuInner } from "./menu.slint"; -import { StateLayer } from "./state_layer.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { ScrollView } from "./scroll_view.slint"; - -export component DropDownMenu { - in property label <=> text_field.label; - in property enabled <=> text_field.enabled; - in property leading_icon <=> text_field.leading_icon; - in property <[MenuItem]> items; - in_out property current_index: -1; - - callback selected(index: int); - - property item_height: MaterialStyleMetrics.size_56; - property max_displayed_items: 6; - property text <=> text_field.text; - - min_width: text_field.min_width; - min_height: text_field.min_height; - forward_focus: text_field; accessible-role: combobox; - accessible_enabled: root.enabled; - accessible_expandable: true; - accessible_value <=> text_field.text; - accessible_action_expand => { menu.show(); } - - text_field := TextField { - width: 100%; - height: 100%; - trailing_icon: Icons.arrow_drop_down; - read_only: true; - } - - state_layer := StateLayer { - width: 100%; - height: 100%; - background: MaterialPalette.on_surface; - has_hover: touch_area.has_hover; - enabled: root.enabled; - } - - touch_area := TouchArea { - width: 100%; - height: 100%; - enabled: root.enabled; - - clicked => { - menu.show(); - } - } - - menu := PopupWindow { - x: 0; - width: root.width; - close_policy: close_on_click_outside; - forward_focus: inner_text_field; - - VerticalLayout { - alignment: start; - - inner_text_field := TextField { - width: root.width; - leading_icon: root.leading_icon; - trailing_icon: Icons.arrow_drop_up; - text: root.text; - label: root.label; - read_only: true; - - leading_icon_clicked => { - menu.close(); - } - - trailing_icon_clicked => { - menu.close(); - } - } - - Rectangle { - width: 100%; - height: min(max(root.item_height, items.length * root.item_height), root.max_displayed_items * root.item_height); - background: MaterialPalette.surface_container; - - ScrollView { - VerticalLayout { - menu_inner := MenuInner { - current_index: root.current_index; - items: root.items; - - activated(index) => { - root.update_selection(index); - } - } - } - } - } - } - } - - function update_selection(index: int) { - root.update_text(index); - root.current_index = index; - menu.close(); - root.selected(index); - } - - function update_text(index: int) { - if index < 0 || root.current_index >= root.items.length { - text_field.text = ""; - return; - } - - text_field.text = root.items[index].text; - } - - changed current_index => { - root.update_text(root.current_index); - } -} diff --git a/ui/ui/components/elevated_button.slint b/ui/ui/components/elevated_button.slint deleted file mode 100644 index fbfb809..0000000 --- a/ui/ui/components/elevated_button.slint +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { Elevation } from "./elevation.slint"; -import { BaseButton } from "./base_button.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; - -export component ElevatedButton { - in property icon <=> base.icon; - in property text <=> base.text; - in property tooltip <=> base.tooltip; - in property enabled <=> base.enabled; - - callback clicked <=> base.clicked; - - accessible-role: button; - accessible-enabled: root.enabled; - accessible-label: root.text == "" ? root.tooltip : root.text; - accessible-action-default => { base.clicked(); } - - elevation := Elevation { - border_radius: root.height / 2; - background: MaterialPalette.surface_container_low; - level: 1; - width: 100%; - height: 100%; - } - - base := BaseButton { - border_radius: elevation.border_radius; - color: MaterialPalette.primary; - } - - states [ - disabled when !root.enabled : { - elevation.background: transparent; - elevation.level: 0; - base.color: MaterialPalette.on_surface; - } - hover when base.has_hover && !base.pressed && !base.enter_pressed : { - elevation.level: 3; - } - ] -} diff --git a/ui/ui/components/elevation.slint b/ui/ui/components/elevation.slint deleted file mode 100644 index 9fc8596..0000000 --- a/ui/ui/components/elevation.slint +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { Palette } from "std-widgets.slint"; -import { MaterialWindow } from "material_window.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; - -export component Elevation { - in property background; - in property border_radius; - in property level; - - property dark: Palette.color_scheme == ColorScheme.dark; - - outer_shadow := Rectangle { - border_radius: root.border_radius; - - inner_shadow := Rectangle { - border_radius: root.border_radius; - background: root.background; - - @children - } - } - - states [ - level_1_light when root.level == 1 && !root.dark: { - outer_shadow.drop_shadow_offset_y: 1px; - outer_shadow.drop_shadow_blur: 2px; - outer_shadow.drop_shadow_color: MaterialPalette.shadow_30; - inner_shadow.drop_shadow_offset_y: 1px; - inner_shadow.drop_shadow_blur: 2px; - inner_shadow.drop_shadow_color: MaterialPalette.shadow_15; - } - level_2_light when root.level == 2 && !root.dark: { - outer_shadow.drop_shadow_offset_y: 1px; - outer_shadow.drop_shadow_blur: 2px; - outer_shadow.drop_shadow_color: MaterialPalette.shadow_30; - inner_shadow.drop_shadow_offset_y: 2px; - inner_shadow.drop_shadow_blur: 6px; - inner_shadow.drop_shadow_color: MaterialPalette.shadow_15; - } - level_3_light when root.level == 3 && !root.dark: { - outer_shadow.drop_shadow_offset_y: 4px; - outer_shadow.drop_shadow_blur: 8px; - outer_shadow.drop_shadow_color: MaterialPalette.shadow_15; - inner_shadow.drop_shadow_offset_y: 1px; - inner_shadow.drop_shadow_blur: 3px; - inner_shadow.drop_shadow_color: MaterialPalette.shadow_30; - } - level_4_light when root.level == 4 && !root.dark: { - outer_shadow.drop_shadow_offset_y: 6px; - outer_shadow.drop_shadow_blur: 10px; - outer_shadow.drop_shadow_color: MaterialPalette.shadow_15; - inner_shadow.drop_shadow_offset_y: 2px; - inner_shadow.drop_shadow_blur: 3px; - inner_shadow.drop_shadow_color: MaterialPalette.shadow_30; - } - level_5_light when root.level == 5 && !root.dark: { - outer_shadow.drop_shadow_offset_y: 8px; - outer_shadow.drop_shadow_blur: 12px; - outer_shadow.drop_shadow_color: MaterialPalette.shadow_15; - inner_shadow.drop_shadow_offset_y: 4px; - inner_shadow.drop_shadow_blur: 4px; - inner_shadow.drop_shadow_color: MaterialPalette.shadow_30; - } - level_1_dark when root.level == 1 && root.dark: { - outer_shadow.drop_shadow_offset_y: 1px; - outer_shadow.drop_shadow_blur: 3px; - outer_shadow.drop_shadow_color: MaterialPalette.shadow_15; - inner_shadow.drop_shadow_offset_y: 1px; - inner_shadow.drop_shadow_blur: 2px; - inner_shadow.drop_shadow_color: MaterialPalette.shadow_30; - } - level_2_dark when root.level == 2 && root.dark: { - outer_shadow.drop_shadow_offset_y: 2px; - outer_shadow.drop_shadow_blur: 6px; - outer_shadow.drop_shadow_color: MaterialPalette.shadow_15; - inner_shadow.drop_shadow_offset_y: 1px; - inner_shadow.drop_shadow_blur: 2px; - inner_shadow.drop_shadow_color: MaterialPalette.shadow_30; - } - level_3_dark when root.level == 3 && root.dark: { - outer_shadow.drop_shadow_offset_y: 4px; - outer_shadow.drop_shadow_blur: 8px; - outer_shadow.drop_shadow_color: MaterialPalette.shadow_15; - inner_shadow.drop_shadow_offset_y: 1px; - inner_shadow.drop_shadow_blur: 3px; - inner_shadow.drop_shadow_color: MaterialPalette.shadow_30; - } - level_4_dark when root.level == 4 && root.dark: { - outer_shadow.drop_shadow_offset_y: 6px; - outer_shadow.drop_shadow_blur: 10px; - outer_shadow.drop_shadow_color: MaterialPalette.shadow_15; - inner_shadow.drop_shadow_offset_y: 2px; - inner_shadow.drop_shadow_blur: 3px; - inner_shadow.drop_shadow_color: MaterialPalette.shadow_30; - } - level_5_dark when root.level == 5 && root.dark: { - outer_shadow.drop_shadow_offset_y: 8px; - outer_shadow.drop_shadow_blur: 12px; - outer_shadow.drop_shadow_color: MaterialPalette.shadow_15; - inner_shadow.drop_shadow_offset_y: 4px; - inner_shadow.drop_shadow_blur: 4px; - inner_shadow.drop_shadow_color: MaterialPalette.shadow_30; - } - ] -} diff --git a/ui/ui/components/extended_touch_area.slint b/ui/ui/components/extended_touch_area.slint deleted file mode 100644 index 5108c7c..0000000 --- a/ui/ui/components/extended_touch_area.slint +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialAnimations } from "../styling/material_animations.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { ToolTip } from "tooltip.slint"; - -export component ExtendedTouchArea inherits TouchArea { - in property tooltip; - in property tooltip_offset; - out property has_focus <=> focus_scope.has_focus; - out property enter_pressed; - - callback key_pressed(KeyEvent) -> EventResult; - - forward-focus: focus-scope; - - focus_scope := FocusScope { - x: 0; - width: 0px; - enabled: root.enabled; - - key_pressed(event) => { - if !root.enabled { - return reject; - } - - if (event.text == " " || event.text == "\n") && !root.enter_pressed { - root.enter_pressed = true; - root.clicked(); - return accept; - } - - root.key_pressed(event) - } - - key_released(event) => { - if !root.enabled { - return reject; - } - - if (event.text == " " || event.text == "\n") && root.enter_pressed { - root.enter_pressed = false; - return accept; - } - - reject - } - } - - @children - - if root.tooltip != "" && root.has-hover && !root.pressed : ToolTip { - y: root.tooltip_offset; - text: root.tooltip; - } -} diff --git a/ui/ui/components/filled_button.slint b/ui/ui/components/filled_button.slint deleted file mode 100644 index e2b503f..0000000 --- a/ui/ui/components/filled_button.slint +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { Elevation } from "./elevation.slint"; -import { BaseButton } from "./base_button.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; - -export component FilledButton { - in property icon <=> base.icon; - in property text <=> base.text; - in property tooltip <=> base.tooltip; - in property enabled <=> base.enabled; - - callback clicked <=> base.clicked; - - accessible-role: button; - accessible-enabled: root.enabled; - accessible-label: root.text == "" ? root.tooltip : root.text; - accessible-action-default => { base.clicked(); } - - elevation := Elevation { - border_radius: root.height / 2; - background: MaterialPalette.primary; - width: 100%; - height: 100%; - } - - base := BaseButton { - border_radius: elevation.border_radius; - color: MaterialPalette.on_primary; - } - - states [ - disabled when !root.enabled : { - elevation.background: transparent; - base.color: MaterialPalette.on_surface; - } - hover when base.has_hover && !base.pressed && !base.enter_pressed : { - elevation.level: 1; - } - ] -} diff --git a/ui/ui/components/filled_icon_button.slint b/ui/ui/components/filled_icon_button.slint deleted file mode 100644 index 644202e..0000000 --- a/ui/ui/components/filled_icon_button.slint +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { Elevation } from "./elevation.slint"; -import { BaseButton } from "./base_button.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; - -export component FilledIconButton { - in property icon; - in property checked_icon: root.icon; - in property checkable; - in_out property checked; - in property tooltip <=> base.tooltip; - in property enabled <=> base.enabled; - - callback clicked(); - - accessible-role: button; - accessible-enabled: root.enabled; - accessible-label: root.tooltip; - accessible-checkable: root.checkable; - accessible-checked: root.checked; - accessible-action-default => { base.clicked(); } - - background_layer := Rectangle { - width: 100%; - height: 100%; - border_radius: base.border_radius; - background: root.checkable ? MaterialPalette.surface_container_highest : MaterialPalette.primary; - visible: root.enabled; - } - - base := BaseButton { - icon: root.checked ? root.checked_icon : root.icon; - border_radius: self.height / 2; - color: !root.enabled ? MaterialPalette.on_surface_variant : root.checkable ? MaterialPalette.primary : MaterialPalette.on_primary; - button_horizontal_padding: 0; - button_vertical_padding: 0; - width: self.height; - icon_size: MaterialStyleMetrics.icon_size_24; - - clicked => { - root.toggle(); - root.clicked(); - } - } - - function toggle() { - if !root.checkable { - return; - } - - root.checked = !root.checked; - } - - states [ - checked when root.enabled && root.checked : { - base.color: MaterialPalette.on_primary; - background_layer.background: MaterialPalette.primary; - } - ] -} diff --git a/ui/ui/components/floating_action_button.slint b/ui/ui/components/floating_action_button.slint deleted file mode 100644 index 32077f7..0000000 --- a/ui/ui/components/floating_action_button.slint +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { Elevation } from "./elevation.slint"; -import { BaseButton } from "./base_button.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; - -export enum FABStyle { - small, - standard, - large -} - -export component FloatingActionButton { - in property icon <=> base.icon; - in property text <=> base.text; - in property tooltip <=> base.tooltip; - in property style; - - callback clicked <=> base.clicked; - - accessible-role: button; - accessible-enabled: true; - accessible-label: root.text == "" ? root.tooltip : root.text; - accessible-action-default => { base.clicked(); } - - elevation := Elevation { - border_radius: root.style == FABStyle.small ? MaterialStyleMetrics.border_radius_12 : - root.style == FABStyle.standard ? MaterialStyleMetrics.border_radius_16 : MaterialStyleMetrics.border_radius_28; - background: MaterialPalette.primary; - level: 3; - width: 100%; - height: 100%; - } - - base := BaseButton { - border_radius: elevation.border_radius; - color: MaterialPalette.on_primary; - button_vertical_padding: root.style == FABStyle.small ? MaterialStyleMetrics.padding_10 : - root.style == FABStyle.standard ? MaterialStyleMetrics.padding_14 : MaterialStyleMetrics.padding_30; - button_horizontal_padding: self.button_vertical_padding; - min_layout_width: self.min_layout_height; - min_layout_height: root.style == FABStyle.small ? MaterialStyleMetrics.size_40 : - root.style == FABStyle.standard ? MaterialStyleMetrics.size_56 : MaterialStyleMetrics.size_90; - icon_size: root.style != FABStyle.large ? MaterialStyleMetrics.icon_size_24 : MaterialStyleMetrics.icon_size_36; - } - - states [ - hover when base.has_hover : { - elevation.level: 4; - } - ] -} diff --git a/ui/ui/components/grid.slint b/ui/ui/components/grid.slint deleted file mode 100644 index 9c82f56..0000000 --- a/ui/ui/components/grid.slint +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; - -export component Grid inherits GridLayout { - padding: MaterialStyleMetrics.padding_8; - spacing_vertical: MaterialStyleMetrics.spacing_8; - spacing_horizontal: MaterialStyleMetrics.spacing_8; -} diff --git a/ui/ui/components/horizontal.slint b/ui/ui/components/horizontal.slint deleted file mode 100644 index bddf801..0000000 --- a/ui/ui/components/horizontal.slint +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; - -export component Horizontal inherits HorizontalLayout { - padding: MaterialStyleMetrics.padding_8; - spacing: MaterialStyleMetrics.spacing_8; -} diff --git a/ui/ui/components/icon.slint b/ui/ui/components/icon.slint deleted file mode 100644 index 4d23105..0000000 --- a/ui/ui/components/icon.slint +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; - -export component Icon inherits Image { - width: MaterialStyleMetrics.icon_size_18; - height: self.width; - colorize: MaterialPalette.on_background; -} diff --git a/ui/ui/components/icon_button.slint b/ui/ui/components/icon_button.slint deleted file mode 100644 index 2acae9d..0000000 --- a/ui/ui/components/icon_button.slint +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { Elevation } from "./elevation.slint"; -import { BaseButton } from "./base_button.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; - -export component IconButton { - in property icon; - in property checked_icon: root.icon; - in property checkable; - in_out property checked; - in property tooltip <=> base.tooltip; - in property enabled <=> base.enabled; - in property inverse; - in property inline: false; - in property has_error; - - callback clicked(); - - accessible-role: button; - accessible-enabled: root.enabled; - accessible-label: root.tooltip; - accessible-checkable: root.checkable; - accessible-checked: root.checked; - accessible-action-default => { base.clicked(); } - - base := BaseButton { - icon: root.checked ? root.checked_icon : root.icon; - border_radius: self.height / 2; - color: root.has_error ? MaterialPalette.error : root.inverse ? MaterialPalette.inverse_on_surface : MaterialPalette.on_surface_variant; - button_horizontal_padding: 0; - button_vertical_padding: 0; - width: self.height; - display_background: false; - icon_size: root.inline ? MaterialStyleMetrics.icon_size_18 : MaterialStyleMetrics.icon_size_24; - min_layout_width: root.inline ? MaterialStyleMetrics.icon_size_18 : MaterialStyleMetrics.size_40; - min_layout_height: self.min_layout_width; - clip_ripple: !root.inline; - - clicked => { - root.toggle(); - root.clicked(); - } - } - - function toggle() { - if !root.checkable { - return; - } - - root.checked = !root.checked; - } - - states [ - checked when root.enabled && root.checked : { - base.color: MaterialPalette.primary; - } - ] -} diff --git a/ui/ui/components/list.slint b/ui/ui/components/list.slint deleted file mode 100644 index 8e7bd68..0000000 --- a/ui/ui/components/list.slint +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { StateLayerArea } from "./state_layer.slint"; -import { MaterialText } from "./material_text.slint"; -import { Icon } from "./icon.slint"; - -export component Avatar { - in property image; - in property background: MaterialPalette.primary; - - width: MaterialStyleMetrics.size_32; - height: self.width; - - Rectangle { - width: 100%; - height: 100%; - border_radius: self.width / 2; - background: root.background; - clip: true; - - Image { - source: root.image; - } - } -} - -export component ListTile { - in property text; - in property supporting_text; - in property avatar_icon; - in property avatar_text; - in property avatar_background; - in property avatar_foreground; - in property enabled <=> state_layer.enabled; - - property color: MaterialPalette.on_surface; - - callback clicked <=> state_layer.clicked; - - min_height: max(MaterialStyleMetrics.size_72, layout.min_height); - - state_layer := StateLayerArea { - color: root.color; - - layout := HorizontalLayout { - padding_left: MaterialStyleMetrics.padding_16; - padding_right: MaterialStyleMetrics.padding_24; - padding_top: MaterialStyleMetrics.padding_8; - padding_bottom: self.padding_top; - spacing: MaterialStyleMetrics.spacing_16; - - if root.avatar_background != #00000000 || root.avatar_text != "" || (root.avatar_icon.width > 0 && root.avatar_icon.height > 0) : Rectangle { - width: self.height; - border_radius: self.height / 2; - background: root.avatar_background; - clip: true; - - if root.avatar_text != "" : MaterialText { - text: root.avatar_text; - vertical_alignment: center; - horizontal_alignment: center; - color: root.avatar_foreground; - style: MaterialTypography.title_medium; - } - - if root.avatar_icon.width > 0 && root.avatar_icon.height > 0 : Icon { - width: parent.width; - source: root.avatar_icon; - colorize: root.avatar_foreground; - } - } - - title_layout := VerticalLayout { - alignment: center; - horizontal_stretch: 1; - - MaterialText { - text: root.text; - color: root.color; - overflow: elide; - style: MaterialTypography.body_large; - } - - if root.supporting_text != "" : MaterialText { - text: root.supporting_text; - color: root.color; - overflow: elide; - style: MaterialTypography.body_medium; - } - } - - @children - } - } - - states [ - disabled when !root.enabled : { - state_layer.display_background: false; - title_layout.opacity: 38%; - } - ] -} - diff --git a/ui/ui/components/list_view.slint b/ui/ui/components/list_view.slint deleted file mode 100644 index aeb847f..0000000 --- a/ui/ui/components/list_view.slint +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { ScrollView } from "./scroll_view.slint"; - -export component ListView inherits ScrollView { - -} diff --git a/ui/ui/components/material_text.slint b/ui/ui/components/material_text.slint deleted file mode 100644 index 4aa5c20..0000000 --- a/ui/ui/components/material_text.slint +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialTypography, TextStyle } from "../styling/material_typography.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; - -export component MaterialText inherits Text { - in property style: MaterialTypography.body_medium; - - font_size: root.style.font_size; - font_weight: root.style.font_weight; - color: MaterialPalette.on_background; - overflow: elide; -} diff --git a/ui/ui/components/material_window.slint b/ui/ui/components/material_window.slint deleted file mode 100644 index a861ac0..0000000 --- a/ui/ui/components/material_window.slint +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialPalette } from "../styling/material_palette.slint"; - -export global MaterialWindowAdapter { - in property disable_hover; -} - -export component MaterialWindow inherits Window { - in property disable_hover; - - background: MaterialPalette.background; - - changed disable_hover => { - MaterialWindowAdapter.disable_hover = root.disable_hover; - } -} diff --git a/ui/ui/components/menu.slint b/ui/ui/components/menu.slint deleted file mode 100644 index f04da7a..0000000 --- a/ui/ui/components/menu.slint +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { Elevation } from "elevation.slint"; -import { StateLayerArea } from "./state_layer.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { Icon } from "./icon.slint"; -import { MenuItem } from "../items/menu_item.slint"; -import { MaterialText } from "./material_text.slint"; -import { Icons } from "../icons/icons.slint"; - -component MenuItemTemplate { - in property icon; - in property text; - in property trailing_text; - in property enabled <=> state_area.enabled; - in property selected; - - callback clicked(); - - property has_icon: root.icon.width > 0 && root.icon.height > 0; - property color: MaterialPalette.on_surface; - property color_variant: MaterialPalette.on_surface_variant; - - min_height: max(MaterialStyleMetrics.size_56, layout.min_height); - - background_layer := Rectangle { - state_area := StateLayerArea { - color: root.color; - - layout := HorizontalLayout { - padding_left: MaterialStyleMetrics.padding_12; - padding_right: self.padding_left; - padding_top: MaterialStyleMetrics.padding_8; - padding_bottom: self.padding_top; - spacing: MaterialStyleMetrics.spacing_12; - - if root.has_icon : VerticalLayout { - alignment: center; - - Icon { - source: root.icon; - colorize: root.color; - } - } - - MaterialText { - horizontal_stretch: 1; - text: root.text; - style: MaterialTypography.body_large; - color: root.color; - vertical_alignment: center; - } - - if root.trailing_text != "" : MaterialText { - text: root.trailing_text; - style: MaterialTypography.body_large; - color: root.color_variant; - vertical_alignment: center; - } - } - - clicked => { - root.clicked(); - } - } - } - - states [ - disabled when !root.enabled : { - state_area.opacity: MaterialPalette.disable_opacity; - } - selected when root.selected : { - background_layer.background: MaterialPalette.surface_container_highest; - } - ] -} - -export component MenuInner { - in property <[MenuItem]> items; - in_out property current_index: -1; - - callback activated(index: int); - - // used to fix clipping of shadows - out property elevation: 2; - - menu := Elevation { - width: 100%; - level: root.elevation; - height: layout.min_height + root.elevation * 1px; - border_radius: MaterialStyleMetrics.border_radius_4; - background: MaterialPalette.surface_container; - - layout := VerticalLayout { - width: root.width; - padding_top: MaterialStyleMetrics.padding_8; - padding_bottom: self.padding_top; - - for item[index] in root.items : MenuItemTemplate { - icon: item.icon; - text: item.text; - trailing_text: item.trailing_text; - enabled: item.enabled; - selected: index == root.current_index; - - clicked => { - root.activated(index); - } - } - } - } -} - -export component PopupMenu inherits PopupWindow { - in property <[MenuItem]> items; - - callback activated(index: int); - - width: MaterialStyleMetrics.size_200; - close_policy: close_on_click_outside; - - VerticalLayout { - padding: inner.elevation * 1px; - - inner := MenuInner { - items: root.items; - - activated(index) => { - root.activated(index); - } - } - } -} diff --git a/ui/ui/components/modal.slint b/ui/ui/components/modal.slint deleted file mode 100644 index b14a4a6..0000000 --- a/ui/ui/components/modal.slint +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialAnimations } from "../styling/material_animations.slint"; - -export component Modal { - callback clicked <=> touch_area.clicked; - - background_layer := Rectangle { - clip: true; - - touch_area := TouchArea { - @children - } - - animate background { duration: MaterialAnimations.opacity_duration; easing: MaterialAnimations.opacity_easing; } - } - - Timer { - interval: 50ms; - - triggered => { - background_layer.background = MaterialPalette.background_modal; - self.running = false; - } - } -} diff --git a/ui/ui/components/navigation_bar.slint b/ui/ui/components/navigation_bar.slint deleted file mode 100644 index 2705ef3..0000000 --- a/ui/ui/components/navigation_bar.slint +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { Badge } from "./badge.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { MaterialText } from "./material_text.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { ExtendedTouchArea } from "./extended_touch_area.slint"; -import { StateLayer, Ripple } from "./state_layer.slint"; -import { Icon } from "./icon.slint"; -import { NavigationItem } from "../items/navigation_item.slint"; -import { BaseNavigationItemTemplate, BaseNavigation } from "./base_navigation.slint"; - -export component NavigationItemTemplate inherits BaseNavigationItemTemplate { - property has_icon: root.icon.width > 0 && root.icon.height > 0; - property has_selected_icon: root.selected_icon.width > 0 || root.selected_icon.height > 0; - property color: MaterialPalette.on_surface; - - in property navitem_padding_top: MaterialStyleMetrics.padding_12; - in property navitem_padding_bottom: MaterialStyleMetrics.padding_16; - - Rectangle { - width: 100%; - height: 100%; - } - - touch_area := ExtendedTouchArea { - HorizontalLayout { - alignment: center; - - VerticalLayout { - alignment: start; - spacing: MaterialStyleMetrics.spacing_4; - padding_top: root.navitem_padding_top; - padding_bottom: root.navitem_padding_bottom; - - HorizontalLayout { - alignment: center; - - background_layer := Rectangle { - min_width: state_layout.min_width; - min_height: max(MaterialStyleMetrics.size_32, state_layout.min_height); - border_radius: self.height / 2; - - state_layer := StateLayer { - background: root.color; - border_radius: parent.border_radius; - enabled: true; - has_focus: touch_area.has_focus; - has_hover: touch_area.has_hover; - pressed: touch_area.pressed || touch_area.enter_pressed; - width: 100%; - height: 100%; - - if touch_area.pressed && !root.selected : Ripple { - width: root.width; - height: root.height; - pressed_x: touch_area.pressed_x; - pressed_y: touch_area.pressed_y; - color: root.color; - } - - state_layout := HorizontalLayout { - padding_left: MaterialStyleMetrics.padding_20; - padding_right: self.padding_left; - alignment: center; - - VerticalLayout { - alignment: center; - - if !root.has_icon && !root.has_selected_icon : Rectangle { - width: 12px; - height: self.width; - border_radius: self.width / 2; - background: root.color; - } - - if root.has_icon || root.has_selected_icon : Icon { - source: root.icon; - colorize: root.color; - - states [ - selected when root.selected && root.has_selected_icon : { - source: root.selected_icon; - } - ] - } - } - } - } - - if root.show_badge || root.badge != "" : Badge { - x: parent.width / 2; - y: 0; - text: root.badge; - } - } - } - - label := MaterialText { - text: root.text; - style: MaterialTypography.label_medium; - color: root.color; - horizontal_alignment: center; - } - } - } - - clicked => { - root.clicked(); - } - } - - states [ - selected when root.selected : { - background_layer.background: MaterialPalette.secondary_container; - state_layer.background: transparent; - label.style: MaterialTypography.label_medium_prominent; - } - ] -} - -export component NavigationBar inherits BaseNavigation { - min_height: max(MaterialStyleMetrics.size_80, layout.min_height); - - Rectangle { - background: MaterialPalette.surface_container; - - layout := HorizontalLayout { - padding_left: MaterialStyleMetrics.padding_8; - padding_right: self.padding_left; - spacing: MaterialStyleMetrics.spacing_8; - - for item[index] in root.items : NavigationItemTemplate { - icon: item.icon; - selected_icon: item.selected_icon; - text: item.text; - index: index; - selected: index == root.current_index; - show_badge: item.show_badge; - badge: item.badge; - - clicked => { - root.select(index); - } - } - } - } -} diff --git a/ui/ui/components/navigation_drawer.slint b/ui/ui/components/navigation_drawer.slint deleted file mode 100644 index 51c92b7..0000000 --- a/ui/ui/components/navigation_drawer.slint +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { Drawer, ModalDrawer, DrawerHeader } from "./drawer.slint"; -import { NavigationItem, NavigationGroup } from "../items/navigation_item.slint"; -import { HorizontalDivider } from "./divider.slint"; -import { StateLayerArea } from "./state_layer.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { Icon } from "./icon.slint"; -import { MaterialText } from "./material_text.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { MaterialAnimations } from "../styling/material_animations.slint"; -import { BaseNavigationItemTemplate } from "./base_navigation.slint"; - -component NavigationItemTemplate inherits BaseNavigationItemTemplate { - property has_icon: root.icon.width > 0 && root.icon.height > 0; - property has_selected_icon: root.selected_icon.width > 0 || root.selected_icon.height > 0; - property color: MaterialPalette.on_surface_variant; - - min_height: max(MaterialStyleMetrics.size_56, layout.min_height); - - background_layer := Rectangle { - border_radius: self.height / 2; - - state_layer := StateLayerArea { - border_radius: parent.border_radius; - color: root.selected ? transparent : root.color; - - layout := HorizontalLayout { - padding_left: MaterialStyleMetrics.padding_16; - padding_right: MaterialStyleMetrics.padding_24; - padding_top: MaterialStyleMetrics.padding_16; - padding_bottom: self.padding_top; - spacing: MaterialStyleMetrics.spacing_12; - - VerticalLayout { - alignment: center; - - if !root.has_icon && !root.has_selected_icon : Rectangle { - width: MaterialStyleMetrics.icon_size_24; - height: self.width; - - Rectangle { - width: 12px; - height: self.width; - border_radius: self.width / 2; - background: root.color; - } - } - - if root.has_icon || root.has_selected_icon : Icon { - source: root.icon; - colorize: root.color; - - states [ - selected when root.selected && root.has_selected_icon : { - source: root.selected_icon; - } - ] - } - } - - MaterialText { - horizontal_stretch: 1; - text: root.text; - style: MaterialTypography.label_large; - color: root.color; - vertical_alignment: center; - } - - if root.badge != "" : MaterialText { - text: root.badge; - style: MaterialTypography.label_large; - color: root.color; - vertical_alignment: center; - } - } - - clicked => { - root.clicked(); - } - - pointer_event(event) => { - root.pointer_event(event, { - x: self.mouse_x, - y: self.mouse_y, - }); - } - } - - animate background { duration: MaterialAnimations.opacity_duration; easing: MaterialAnimations.opacity_easing; } - } - - states [ - selected when root.selected : { - background_layer.background: MaterialPalette.secondary_container; - root.color: MaterialPalette.on_secondary_container; - } - ] - - animate color { duration: MaterialAnimations.opacity_duration; easing: MaterialAnimations.opacity_easing; } -} - -component NavigationGroupTemplate { - in property title; - in property <[NavigationItem]> items; - in property current_index: -1; - in property has_divider; - - callback select(index: int); - callback item_pointer_event(index: int, event: PointerEvent, position: Point); - - VerticalLayout { - alignment: start; - - if root.title != 0 : DrawerHeader { - title: root.title; - } - - for item[index] in root.items : NavigationItemTemplate { - icon: item.icon; - selected_icon: item.selected_icon; - text: item.text; - badge: item.badge; - selected: index == root.current_index; - index: index; - - clicked => { - root.select(index); - } - pointer_event(event, position) => { - root.item_pointer_event(index, event, { - x: self.x + position.x, - y: self.y + position.y, - }); - } - } - - if root.has_divider : HorizontalDivider {} - } -} - -export component NavigationDrawer inherits Drawer { - callback selected(group-index: int, item-index: int); - callback item_pointer_event(group_index: int, item_index: int, event: PointerEvent, position: Point); - in property <[NavigationGroup]> groups; - in_out property current_group; - in_out property current_index; - - accessible-role: tab-list; - // accessible-delegate-focus: root.current-focused >= 0 ? root.current-focused : root.current-index; - // accessible-label: root.title; - accessible-item-count: root.groups.length; - - for group[group_index] in root.groups : NavigationGroupTemplate { - title: group.title; - items: group.items; - current_index: group_index == root.current_group ? root.current_index : -1; - has_divider: root.groups.length > 1 && group_index < root.groups.length - 1; - - select(index) => { - root.select(group_index, index); - } - item_pointer_event(index, event, position) => { - root.item_pointer_event(group_index, index, event, { x: self.x + position.x, y: self.y + position.y }); - } - } - - public function select(group_index: int, item_index: int) { - if group_index < 0 || group_index >= root.groups.length || item_index < 0 || item_index >= root.groups[group_index].items.length { - return; - } - - root.current_group = group_index; - root.current_index = item_index; - root.selected(group_index, item_index); - } -} - -export component ModalNavigationDrawer inherits ModalDrawer { - callback selected(group-index: int, item-index: int); - callback item_pointer_event(group_index: int, item_index: int, event: PointerEvent, position: Point); - in property <[NavigationGroup]> groups; - in_out property current_group; - in_out property current_index; - - accessible-role: tab-list; - // accessible-delegate-focus: root.current-focused >= 0 ? root.current-focused : root.current-index; - // accessible-label: root.title; - accessible-item-count: root.groups.length; - - // Workaround for issue #9766 - close_timer := Timer { - interval: 0ms; - running: false; - triggered => { - root.close(); - self.stop(); - } - } - - for group[group_index] in root.groups : NavigationGroupTemplate { - title: group.title; - items: group.items; - current_index: group_index == root.current_group ? root.current_index : -1; - has_divider: root.groups.length > 1 && group_index < root.groups.length - 1; - - select(index) => { - root.select(group_index, index); - } - item_pointer_event(index, event, position) => { - root.item_pointer_event(group_index, index, event, { x: self.x + position.x, y: self.y + position.y }); - } - } - - public function select(group_index: int, item_index: int) { - if group_index < 0 || group_index >= root.groups.length || item_index < 0 || item_index >= root.groups[group_index].items.length { - return; - } - - root.current_group = group_index; - root.current_index = item_index; - root.selected(group_index, item_index); - close_timer.start(); - } -} diff --git a/ui/ui/components/navigation_rail.slint b/ui/ui/components/navigation_rail.slint deleted file mode 100644 index 4ab1a1f..0000000 --- a/ui/ui/components/navigation_rail.slint +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { NavigationItem } from "../items/navigation_item.slint"; -import { FloatingActionButton, FABStyle } from "./floating_action_button.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { IconButton } from "icon_button.slint"; -import { Icons } from "../icons/icons.slint"; -import { NavigationItemTemplate } from "navigation_bar.slint"; -import { BaseNavigation } from "./base_navigation.slint"; - -export component NavigationRail inherits BaseNavigation { - in property has_menu; - in property fab_icon; - in property alignment; - - callback menu_clicked(); - callback fab_clicked(); - - min_width: max(MaterialStyleMetrics.size_80, layout.min_width); - - Rectangle { - background: MaterialPalette.surface; - - layout := VerticalLayout { - padding_top: MaterialStyleMetrics.padding_44; - padding_bottom: MaterialStyleMetrics.padding_56; - spacing: MaterialStyleMetrics.spacing_4; - - if root.has_menu : IconButton { - icon: Icons.menu; - - clicked => { - root.menu_clicked(); - } - } - - - if root.fab_icon.width > 0 && root.fab_icon.height > 0 : HorizontalLayout { - alignment: center; - - FloatingActionButton { - icon: root.fab_icon; - style: FABStyle.standard; - - clicked => { - root.fab_clicked(); - } - } - } - - // helper to place fab at the top if no items are present - if root.items.length == 0 : Rectangle { - horizontal_stretch: 1; - } - - HorizontalLayout { - vertical_stretch: 1; - alignment: center; - - VerticalLayout { - alignment: root.alignment; - - for item[index] in root.items : NavigationItemTemplate { - icon: item.icon; - selected_icon: item.selected_icon; - text: item.text; - index: index; - selected: index == root.current_index; - show_badge: item.show_badge; - badge: item.badge; - navitem_padding_top: 0; - navitem_padding_bottom: MaterialStyleMetrics.padding_4; - - clicked => { - root.select(index); - } - } - } - } - } - } -} diff --git a/ui/ui/components/outline_button.slint b/ui/ui/components/outline_button.slint deleted file mode 100644 index e40db92..0000000 --- a/ui/ui/components/outline_button.slint +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { BaseButton } from "./base_button.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; - -export component OutlineButton { - in property icon <=> base.icon; - in property text <=> base.text; - in property enabled <=> base.enabled; - in property tooltip <=> base.tooltip; - - callback clicked <=> base.clicked; - - accessible-role: button; - accessible-enabled: root.enabled; - accessible-label: root.text == "" ? root.tooltip : root.text; - accessible-action-default => { base.clicked(); } - - base := BaseButton { - border_radius: border.border_radius; - color: MaterialPalette.primary; - } - - border := Rectangle { - border_radius: root.height / 2; - border_width: 1px; - border_color: MaterialPalette.outline; - width: 100%; - height: 100%; - } - - states [ - disabled when !root.enabled : { - base.color: MaterialPalette.on_surface; - base.transparent_background: true; - border.border_color: MaterialPalette.on_surface; - border.opacity: MaterialPalette.state_layer_opacity_hover; - } - ] -} diff --git a/ui/ui/components/outline_icon_button.slint b/ui/ui/components/outline_icon_button.slint deleted file mode 100644 index 263df34..0000000 --- a/ui/ui/components/outline_icon_button.slint +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { Elevation } from "./elevation.slint"; -import { BaseButton } from "./base_button.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; - -export component OutlineIconButton { - in property icon; - in property checked_icon: root.icon; - in property checkable; - in_out property checked; - in property tooltip <=> base.tooltip; - in property enabled <=> base.enabled; - - callback clicked(); - - accessible-role: button; - accessible-enabled: root.enabled; - accessible-label: root.tooltip; - accessible-checkable: root.checkable; - accessible-checked: root.checked; - accessible-action-default => { base.clicked(); } - - background_layer := Rectangle { - width: 100%; - height: 100%; - border_radius: base.border_radius; - border_width: 1px; - border_color: MaterialPalette.outline; - } - - base := BaseButton { - icon: root.checked ? root.checked_icon : root.icon; - border_radius: self.height / 2; - color: !root.enabled ? MaterialPalette.on_surface_variant : MaterialPalette.on_surface_variant; - button_horizontal_padding: 0; - button_vertical_padding: 0; - width: self.height; - icon_size: MaterialStyleMetrics.icon_size_24; - display_background: false; - - clicked => { - root.toggle(); - root.clicked(); - } - } - - function toggle() { - if !root.checkable { - return; - } - - root.checked = !root.checked; - } - - states [ - disabled when !root.enabled : { - base.color: MaterialPalette.on_surface; - base.transparent_background: true; - background_layer.border_color: root.checked ? transparent : MaterialPalette.on_surface; - background_layer.background: root.checked ? MaterialPalette.on_surface : transparent; - background_layer.opacity: MaterialPalette.state_layer_opacity_hover; - } - checked when root.enabled && root.checked : { - base.color: MaterialPalette.inverse_on_surface; - background_layer.background: MaterialPalette.inverse_surface; - background_layer.border_width: 0; - } - checked_disabled when root.enabled && root.checked : { - base.color: MaterialPalette.inverse_on_surface; - background_layer.background: MaterialPalette.inverse_on_surface; - background_layer.border_width: 0; - } - ] -} diff --git a/ui/ui/components/progress_indicator.slint b/ui/ui/components/progress_indicator.slint deleted file mode 100644 index 92ded2c..0000000 --- a/ui/ui/components/progress_indicator.slint +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; - -export component LinearProgressIndicator { - in property progress; - in property indeterminate; - - accessible-role: progress-indicator; - accessible-value: !root.indeterminate ? root.progress : ""; - accessible-value-minimum: 0.0; - accessible-value-maximum: 1.0; - height: MaterialStyleMetrics.size_4; - - Rectangle { - width: 100%; - height: 100%; - clip: true; - background: MaterialPalette.primary_container; - border_radius: MaterialStyleMetrics.border_radius_2; - - track := Rectangle { - x: 0; - y: 0; - width: parent.width * root.progress; - height: 100%; - border_radius: parent.border_radius; - background: MaterialPalette.primary; - } - } -} - -export component CircularProgressIndicator { - in property progress; - in property indeterminate; - - property bar_height: MaterialStyleMetrics.size_4; - - accessible-role: progress-indicator; - accessible-value: !root.indeterminate ? root.progress : ""; - accessible-value-minimum: 0.0; - accessible-value-maximum: 1.0; - width: self.height; - min_height: MaterialStyleMetrics.size_40; - - background_layer := Rectangle { - width: root.width; - height: root.height; - border_width: root.bar_height; - border_color: root.progress == 1 ? MaterialPalette.primary : MaterialPalette.primary_container; - border_radius: max(self.width, self.height) / 2; - } - - track := Path { - property radius: min(self.viewbox_width, self.viewbox_height) / 2; - property start_x: self.viewbox_width / 2; - property start_y: self.viewbox_height / 2; - property inner_radius: self.radius - (root.bar_height * (self.viewbox_height / self.height)); - property start: !root.indeterminate ? 0 : 1 * mod(animation-tick(), 1.5s) / 1.5s; - property progress: !root.indeterminate ? root.progress : 0.5; - - x: background_layer.x; - y: background_layer.y; - viewbox_width: 100; - viewbox_height: 100; - width: background_layer.width; - height: background_layer.height; - fill: MaterialPalette.primary; - visible: self.progress > 0 && self.progress < 1; - - MoveTo { - x: !root.indeterminate ? track.start_x : track.start_x - track.radius * sin(-track.start * 360deg); - y: !root.indeterminate ? 0 : track.start_y - track.radius * cos(-track.start * 360deg); - } - - ArcTo { - radius_x: 1; - radius_y: 1; - x: !root.indeterminate ? track.start_x : track.start_x - track.inner-radius * sin(-track.start * 360deg); - y: !root.indeterminate ? root.bar_height * (track.viewbox_height / track.height) : track.start_y - track.inner_radius * cos(-track.start * 360deg); - } - - ArcTo { - radius_x: track.inner_radius; - radius_y: track.inner_radius; - x: start_x - track.inner_radius * sin(-(track.start + track.progress) * 360deg); - y: start_y - track.inner_radius * cos(-(track.start + track.progress) * 360deg); - sweep: track.progress > 0; - large-arc: track.progress > 0.5; - } - - ArcTo { - radius_x: 1; - radius_y: 1; - x: start_x - radius * sin(-(track.start + track.progress) * 360deg); - y: start_y - radius * cos(-(track.start + track.progress) * 360deg); - } - - ArcTo { - radius_x: radius; - radius_y: radius; - x: start_x - radius * sin(-track.start * 360deg); - y: start_y - radius * cos(-track.start * 360deg); - sweep: track.progress < 0; - large-arc: track.progress > 0.5; - } - - LineTo { - x: start_x - radius * sin(-track.start * 360deg); - y: start_y - radius * cos(-track.start * 360deg); - } - } -} diff --git a/ui/ui/components/radio_button.slint b/ui/ui/components/radio_button.slint deleted file mode 100644 index 2d6a1b1..0000000 --- a/ui/ui/components/radio_button.slint +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialAnimations } from "../styling/material_animations.slint"; -import { StateLayerArea } from "./state_layer.slint"; -import { ListTile } from "./list.slint"; -import { ListView } from "./list_view.slint"; - -export component RadioButton { - in property checked; - in property enabled; - - callback clicked(); - - min_width: MaterialStyleMetrics.size_40; - min_height: self.min_width; - accessible-enabled: root.enabled; - accessible-checkable: true; - accessible-checked <=> root.checked; - accessible-role: radio-button; - accessible-action-default => { state_area.clicked(); } - forward_focus: state_area; - - state_area := StateLayerArea { - width: 100%; - height: 100%; - border_radius: max(self.width, self.height) / 2; - color: MaterialPalette.on_surface; - - border := Rectangle { - width: MaterialStyleMetrics.size_20; - height: self.width; - border_radius: max(self.width, self.height) / 2; - border_color: MaterialPalette.on_surface_variant; - border_width: MaterialStyleMetrics.size_2; - - indicator := Rectangle { - width: parent.width / 2; - height: self.width; - border_radius: max(self.width, self.height) / 2; - background: MaterialPalette.primary; - opacity: !root.checked ? 0 : 1; - - animate opacity { - duration: MaterialAnimations.opacity_duration; - easing: MaterialAnimations.opacity_easing; - } - } - - animate border_color { - duration: MaterialAnimations.opacity_duration; - easing: MaterialAnimations.opacity_easing; - } - } - - clicked => { - root.clicked(); - } - } - - states [ - disabled when !root.enabled : { - border.border_color: MaterialPalette.on_surface; - border.opacity: MaterialPalette.disable_opacity; - indicator.background: MaterialPalette.on_surface; - indicator.opacity: root.checked ? MaterialPalette.disable_opacity : 0; - } - highlighted when !root.checked && (state_area.pressed || state_area.has_focus || state_area.has_hover) : { - border.border_color: MaterialPalette.on_surface; - } - checked when root.checked : { - border.border_color: MaterialPalette.primary; - } - ] -} - -export component RadioButtonTile inherits ListTile { - in_out property checked <=> radio_button.checked; - - callback radio_button_clicked(); - - Rectangle { - horizontal_stretch: 0; - - radio_button := RadioButton { - enabled: root.enabled; - - clicked => { - root.radio_button_clicked(); - } - } - } - - clicked => { - radio_button.clicked(); - } -} diff --git a/ui/ui/components/scroll_view.slint b/ui/ui/components/scroll_view.slint deleted file mode 100644 index dd783c3..0000000 --- a/ui/ui/components/scroll_view.slint +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialPalette } from "../styling/material_palette.slint"; - -export component ScrollBar { - in property enabled; - out property has_hover: touch_area.has_hover; - in_out property horizontal; - in_out property maximum; - in_out property page_size; - in_out property value; - in property policy: ScrollBarPolicy.as_needed; - - property track_size: root.horizontal ? root.width - 2 * root.offset : root.height - 2 * offset; - property step_size: 10px; - property offset: 2px; - property state_opacity: 40%; - - visible: (self.policy == ScrollBarPolicy.always_on) || (self.policy == ScrollBarPolicy.as_needed && self.maximum > 0); - - Rectangle { - clip: true; - - thumb := Rectangle { - width: !root.horizontal ? parent.width : root.maximum <= 0phx ? 0phx : max(min(32px, root.width), root.track-size * root.page-size / (root.maximum + root.page-size)); - height: root.horizontal ? parent.height : root.maximum <= 0phx ? 0phx : max(min(32px, root.height), root.track-size * (root.page-size / (root.maximum + root.page-size))); - x: !root.horizontal ? (parent.width - self.width) / 2 : root.offset + (root.track-size - thumb.width) * (-root.value / root.maximum); - y: root.horizontal ? (parent.height - self.height) / 2 : root.offset + (root.track-size - thumb.height) * (-root.value / root.maximum); - border-radius: (root.horizontal ? self.height : self.width) / 2; - background: MaterialPalette.on_background; - opacity: hide_timer.running || touch_area.has_hover || touch_area.pressed ? root.state_opacity : 0; - - animate opacity { duration: 150ms; } - } - } - - touch_area := TouchArea { - property pressed_value; - - width: parent.width; - height: parent.height; - - pointer-event(event) => { - if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) { - self.pressed-value = -root.value; - } - } - - moved => { - if (self.enabled && self.pressed) { - root.value = -max(0px, min(root.maximum, self.pressed-value + ( - root.horizontal ? (touch-area.mouse-x - touch-area.pressed-x) * (root.maximum / (root.track-size - thumb.width)) - : (touch-area.mouse-y - touch-area.pressed-y) * (root.maximum / (root.track-size - thumb.height)) - ))); - } - } - - scroll-event(event) => { - if (root.horizontal && event.delta-x != 0) { - root.value = max(-root.maximum, min(0px, root.value + event.delta-x)); - return accept; - } else if (!root.horizontal && event.delta-y != 0) { - root.value = max(-root.maximum, min(0px, root.value + event.delta-y)); - return accept; - } - - reject - } - - changed has_hover => { - if !self.has_hover && !hide_timer.running { - hide_timer.running = true; - } - } - - changed pressed => { - if self.pressed && hide_timer.running { - hide_timer.running = false; - } - } - } - - changed value => { - if !self.has_hover && !hide_timer.running { - hide_timer.running = true; - } - } - - hide_timer := Timer { - interval: 1.5s; - running: false; - - triggered => { - self.running = false; - } - } - - states [ - pressed when touch_area.pressed : { - root.state_opacity: 80%; - } - hover when root.has_hover : { - root.state_opacity: 70%; - } - ] -} - -export component ScrollView { - in property enabled: true; - out property visible_width <=> flickable.width; - out property visible_height <=> flickable.height; - in_out property viewport_width <=> flickable.viewport_width; - in_out property viewport_height <=> flickable.viewport_height; - in_out property viewport_x <=> flickable.viewport_x; - in_out property viewport_y <=> flickable.viewport_y; - in property vertical_scrollbar_policy <=> vertical_bar.policy; - in property horizontal_scrollbar_policy <=> horizontal_bar.policy; - // FIXME: remove. This property is currently set by the ListView and is used by the native style to draw the scrollbar differently when it has focus - in_out property has_focus; - - callback scrolled <=> flickable.flicked; - - property scroll_bar_size: 8px; - property scroll_bar_padding: 1px; - - min_height: 50px; - min_width: 50px; - horizontal_stretch: 1; - vertical_stretch: 1; - preferred_height: 100%; - preferred_width: 100%; - - flickable := Flickable { - x: 0; - y: 0; - viewport_y <=> vertical_bar.value; - viewport_x <=> horizontal_bar.value; - width: root.width - (root.vertical_scrollbar_policy != ScrollBarPolicy.always_off ? root.scroll_bar_size - 2 * root.scroll_bar_padding : 0); - height: root.height - (root.horizontal_scrollbar_policy != ScrollBarPolicy.always_off ? root.scroll_bar_size : 0); - - @children - } - - vertical_bar := ScrollBar { - enabled: root.enabled; - x: flickable.width + root.scroll_bar_padding; - y: 0; - width: root.visible ? root.scroll_bar_size : 0; - height: root.vertical_scrollbar_policy != ScrollBarPolicy.always_off ? parent.height - horizontal_bar.height : parent.height; - horizontal: false; - maximum: flickable.viewport_height - flickable.height; - page-size: flickable.height; - } - - horizontal_bar := ScrollBar { - enabled: root.enabled; - width: vertical_bar.visible ? parent.width - vertical_bar.width : parent.width; - height: root.horizontal_scrollbar_policy != ScrollBarPolicy.always_off ? root.scroll_bar_size : 0; - x: 0; - y: flickable.height + root.scroll_bar_padding; - horizontal: true; - maximum: flickable.viewport_width - flickable.width; - page_size: flickable.width; - } -} diff --git a/ui/ui/components/search_bar.slint b/ui/ui/components/search_bar.slint deleted file mode 100644 index 211a436..0000000 --- a/ui/ui/components/search_bar.slint +++ /dev/null @@ -1,324 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { ListItem } from "../items/list_item.slint"; -import { Avatar, ListTile } from "./list.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { StateLayerArea } from "./state_layer.slint"; -import { MaterialText } from "./material_text.slint"; -import { Icon } from "./icon.slint"; -import { IconButton } from "./icon_button.slint"; -import { Icons } from "../icons/icons.slint"; -import { HorizontalDivider } from "./divider.slint"; -import { ListView } from "./list_view.slint"; -import { MaterialAnimations } from "../styling/material_animations.slint"; - -component SearchTile inherits ListTile { - in property action_button_icon; - - callback action_button_clicked(); - - if root.action_button_icon.width > 0 && root.action_button_icon.height > 0 : IconButton { - icon: root.action_button_icon; - - clicked => { - root.action_button_clicked(); - } - } -} - -component SearchIcon { - in property icon; - in property color; - - VerticalLayout { - alignment: center; - padding_left: MaterialStyleMetrics.padding_8; - padding_right: self.padding_left; - - Icon { - source: root.icon; - colorize: root.color; - } - } -} - -component SearchTextInput { - in_out property text <=> text_input.text; - in property placeholder_text; - property computed_x; - - callback accepted(text: string); - callback edited(text: string); - callback key_pressed(event: KeyEvent) -> EventResult; - callback key_released(event: KeyEvent) -> EventResult; - - forward_focus: text_input; - horizontal_stretch: 1; - - Rectangle { - clip: true; - - text_input := TextInput { - x: min(0px, max(parent.width - self.width - self.text_cursor_width, root.computed_x)); - width: max(parent.width - self.text-cursor-width, self.preferred-width); - height: 100%; - font_size: MaterialTypography.body_large.font_size; - font_weight: MaterialTypography.body_large.font_weight; - vertical_alignment: center; - single_line: true; - color: MaterialPalette.on_surface; - selection_background_color: MaterialPalette.secondary_container; - selection_foreground_color: self.color; - // Disable TextInput's built-in accessibility support as the component takes care of that. - accessible-role: none; - - cursor_position_changed(cursor_position) => { - if cursor_position.x + root.computed_x < 0 { - root.computed_x = - cursor_position.x; - } else if cursor-position.x + root.computed_x > parent.width - self.text-cursor-width { - root.computed_x = parent.width - cursor_position.x - self.text-cursor-width; - } - } - - accepted => { - root.accepted(self.text); - } - - edited => { - root.edited(self.text); - } - - key_pressed(event) => { - root.key_pressed(event) - } - - key_released(event) => { - root.key_released(event) - } - - init => { - if root.text.character_count > 0 { - self.set_selection_offsets(root.text.character_count, root.text.character_count); - } - } - } - if root.text == "" && root.placeholder_text != "" : MaterialText { - width: 100%; - height: 100%; - text: root.placeholder_text; - color: MaterialPalette.on_surface_variant; - vertical_alignment: center; - style: MaterialTypography.body_large; - } - } -} - -export component SearchBar { - in property leading_icon: Icons.menu; - in property trailing_icon; - in property avatar_icon; - in property avatar_background: #00000000; - in property placeholder_text; - in property empty_text; - in_out property text; - in_out property current_index; - in property <[ListItem]> items; - - callback accepted(text: string); - callback edited(text: string); - callback action_button_clicked(index: int); - callback key_pressed(event: KeyEvent) -> EventResult; - callback key_released(event: KeyEvent) -> EventResult; - callback item-activated(index: int) -> bool; - - property color: MaterialPalette.on_surface_variant; - property item_height: MaterialStyleMetrics.size_72; - - public function close-popup() { - popup.close(); - } - - min_height: max(MaterialStyleMetrics.size_56, layout.min_height); - forward_focus: state_layer; - - Rectangle { - background: MaterialPalette.surface_container_high; - border_radius: MaterialStyleMetrics.border_radius_28; - - state_layer := StateLayerArea { - border_radius: parent.border_radius; - color: root.color; - - layout := HorizontalLayout { - padding: MaterialStyleMetrics.padding_4; - spacing: MaterialStyleMetrics.spacing_4; - - SearchIcon { - icon: root.leading_icon; - color: root.color; - } - - MaterialText { - text: root.text; - font_size: MaterialTypography.body_large.font_size; - font_weight: MaterialTypography.body_large.font_weight; - vertical_alignment: center; - color: MaterialPalette.on_surface; - - states [ - placeholder when root.text == "" : { - text: root.placeholder_text; - color: MaterialPalette.on_surface; - } - ] - } - - SearchIcon { - icon: root.trailing_icon; - color: root.color; - } - - if (root.avatar_icon.width > 0 && root.avatar_icon.height > 0) || root.avatar_background != #00000000 : VerticalLayout { - alignment: center; - - Avatar { - image: root.avatar_icon; - background: root.avatar_background; - } - } - } - - changed has_focus => { - if self.has_focus { - popup.show(); - } - } - - clicked => { - popup.show(); - } - } - } - - popup := PopupWindow { - x: 0; - y: 0; - width: root.width; - height: root.height + clamp(root.item_height * root.items.length, 3 * root.item_height, 6 * root.item_height); - close_policy: close_on_click_outside; - forward-focus: popup_input; - - background_layer := Rectangle { - x: 0; - y: 0; - width: 100%; - height: root.height; - background: MaterialPalette.surface_container_high; - border_radius: MaterialStyleMetrics.border_radius_28; - clip: true; - - VerticalLayout { - HorizontalLayout { - padding: MaterialStyleMetrics.padding_4; - spacing: MaterialStyleMetrics.spacing_4; - - IconButton { - icon: Icons.arrow_back; - - clicked => { - popup.close(); - } - } - - popup_input := SearchTextInput { - placeholder_text: root.placeholder_text; - text: root.text; - - accepted => { - root.accepted(self.text); - } - - edited => { - root.text = self.text; - root.edited(self.text); - } - - key_pressed(event) => { - root.key_pressed(event) - } - - key_released(event) => { - root.key_released(event) - } - - changed text => { - root.text = text; - } - } - - IconButton { - icon: Icons.close; - - clicked => { - root.text = ""; - popup.close(); - } - } - } - - HorizontalDivider {} - - if root.items.length == 0 : MaterialText { - text: root.empty_text; - color: root.color; - horizontal_alignment: center; - style: MaterialTypography.label_small; - } - - if root.items.length == 0 : Rectangle {} - - if root.items.length > 0 : ListView { - horizontal_stretch: 1; - - for item[index] in root.items : SearchTile { - text: item.text; - supporting_text: item.supporting_text; - avatar_icon: item.avatar_icon; - avatar_text: item.avatar_text; - avatar_background: item.avatar_background; - avatar_foreground: item.avatar_foreground; - action_button_icon: item.action_button_icon; - - clicked => { - if (!root.item-activated(index)) { - root.text = self.text; - } - popup.close(); - } - - action_button_clicked => { - root.action_button_clicked(index); - } - } - } - } - - animate height { - duration: MaterialAnimations.standard_accelerate_duration; - easing: MaterialAnimations.standard_easing; - } - } - - Timer { - interval: 50ms; - - triggered => { - background_layer.height = popup.height; - self.running = false; - } - } - } -} diff --git a/ui/ui/components/segmented_button.slint b/ui/ui/components/segmented_button.slint deleted file mode 100644 index 21acf94..0000000 --- a/ui/ui/components/segmented_button.slint +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { BaseButton } from "base_button.slint"; -import { Icons } from "../icons/icons.slint"; -import { MaterialAnimations } from "../styling/material_animations.slint"; - -export struct SegmentedItem { - icon: image, - text: string -} - -component SegmentedItemTemplate { - in property icon; - in property text; - in property index; - in property selected; - in property last; - - callback clicked <=> base.clicked; - - accessible-role: list-item; - accessible-label: root.text; - accessible-item-selectable: true; - accessible-item-selected: root.selected; - accessible-item-index: root.index; - - if root.selected : Rectangle { - background: MaterialPalette.secondary_container; - } - - base := BaseButton { - min_layout_width: 0; - min_layout_height: 0; - color: MaterialPalette.on_surface; - button_vertical_padding: MaterialStyleMetrics.padding_10; - button_horizontal_padding: MaterialStyleMetrics.padding_24; - spacing: MaterialStyleMetrics.spacing_8; - icon: root.icon; - text: root.text; - - - animate width { duration: MaterialAnimations.standard_accelerate_duration; easing: MaterialAnimations.standard_easing; } - } - - if !root.last : Rectangle { - x: parent.width - self.width; - width: 1px; - height: 100%; - background: MaterialPalette.outline; - } - - states [ - selected when root.selected : { - base.icon: Icons.check; - } - ] -} - -export component SegmentedButton { - in property <[SegmentedItem]> items; - in_out property current_index; - - callback index_changed(index: int); - - min_width: max(2 * MaterialStyleMetrics.size_40, layout.min_width); - min_height: max(MaterialStyleMetrics.size_40, layout.min_height); - - accessible-role: list; - accessible-item-count: root.items.length; - - Rectangle { - clip: true; - border_width: 1px; - border_radius: self.height / 2; - border_color: MaterialPalette.outline; - - layout := HorizontalLayout { - for item[index] in root.items : SegmentedItemTemplate { - icon: item.icon; - text: item.text; - index: index; - last: index == root.items.length - 1; - selected: index == root.current_index; - - clicked => { - root.select(index); - } - } - } - } - - function select(index: int) { - if index < 0 || index >= root.items.length { - return; - } - - root.current_index = index; - root.index_changed(index); - } -} diff --git a/ui/ui/components/slider.slint b/ui/ui/components/slider.slint deleted file mode 100644 index 6e93f50..0000000 --- a/ui/ui/components/slider.slint +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT -// -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { StateLayer } from "./state_layer.slint"; - -export component Slider { - in property enabled; - in_out property value; - in property minimum; - in property stop_count: 0; - in property maximum: 100; - - callback released(value: float); - callback value_changed(value: float); - - property steps: root.stop_count > 0 ? (root.maximum - root.minimum) / root.stop_count : 1; - - min_height: 20px; - accessible-role: slider; - accessible-enabled: root.enabled; - accessible-value: root.value; - accessible-value-minimum: root.minimum; - accessible-value-maximum: root.maximum; - accessible-value-step: min(steps, (root.maximum - root.minimum) / 100); - accessible-action-set-value(v) => { - if v.is-float() { - root.set_value(v.to-float()); - } - } - accessible-action-increment => { root.increment_value(); } - accessible-action-decrement => { root.decrement_value(); } - forward-focus: focus_scope; - - focus_scope := FocusScope { - touch_area := TouchArea { - track := Rectangle { - x: 0; - height: MaterialStyleMetrics.size_16; - background: MaterialPalette.surface_container_highest; - border_radius: self.height / 2; - - Rectangle { - x: 0; - width: state_layer.x - MaterialStyleMetrics.size_6; - border-top-left-radius: track.border_radius; - border-bottom-left-radius: track.border_radius; - border-top-right-radius: MaterialStyleMetrics.size_1; - border-bottom-right-radius: MaterialStyleMetrics.size_1; - background: thumb.background; - } - - if root.stop_count > 1: Rectangle { - width: 100%; - height: 100%; - - for i in (root.maximum - root.minimum) / root.steps - 1: Rectangle { - x: root.value_to_length(track.width, (i + 1) * root.steps) - self.width / 2; - height: MaterialStyleMetrics.size_4; - width: self.height; - border_radius: self.height / 2; - background: MaterialPalette.outline; - } - } - } - - state_layer := Rectangle { - x: root.value_to_length(track.width, root.value); - width: 0; - height: track.height + (MaterialStyleMetrics.size_14 * 2); - StateLayer { - height: track.height + (MaterialStyleMetrics.size_16 * 2); - width: thumb.width + (MaterialStyleMetrics.size_2 * 2); - border_radius: self.width / 2; - pressed: touch_area.pressed; - has_hover: touch_area.has_hover; - has_focus: focus_scope.has_focus; - enabled: root.enabled; - background: thumb.background; - display_background: thumb_area.pressed || thumb_area.has_hover || focus_scope.has_focus; - - thumb_area := TouchArea { - height: track.height + (MaterialStyleMetrics.size_14 * 2); - width: self.height; - thumb := Rectangle { - width: MaterialStyleMetrics.size_4; - border_radius: self.width / 2; - background: MaterialPalette.primary; - } - - moved => { - root.set_value(root.length_to_value(state_layer.x + (self.mouse_x - self.pressed_x), track.width)); - } - - pointer_event(event) => { - if event.kind == PointerEventKind.up { - root.released(root.value); - } - } - } - } - } - - pointer_event(event) => { - if event.kind == PointerEventKind.down && event.button == PointerEventButton.left { - root.set_value(root.length_to_value(self.mouse_x, track.width)); - } - if event.kind == PointerEventKind.up { - root.released(root.value); - } - } - } - - key_pressed(event) => { - if event.text == Key.LeftArrow { - root.decrement_value(); - return accept; - } - if event.text == Key.RightArrow { - root.increment_value(); - return accept; - } - reject - } - } - - changed value => { - root.value_changed(root.value); - } - - pure function value_to_length(width: length, value: float) -> length { - clamp(width * (value - root.minimum) / (root.maximum - root.minimum), 0, width) - } - - pure function length_to_value(x: length, width: length) -> float { - x * (root.maximum - root.minimum) / width - } - - function set_value(value: float) { - if root.steps > 1 { - root.value = clamp(round(value / root.steps) * root.steps, root.minimum, root.maximum) - } else { - root.value = clamp(value, root.minimum, root.maximum) - } - } - - function increment_value() { - root.set_value(root.value + root.steps); - } - - function decrement_value() { - root.set_value(root.value - root.steps); - } -} diff --git a/ui/ui/components/snack_bar.slint b/ui/ui/components/snack_bar.slint deleted file mode 100644 index 962c742..0000000 --- a/ui/ui/components/snack_bar.slint +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { Elevation } from "elevation.slint"; -import { MaterialText } from "./material_text.slint"; -import { TextButton } from "./text_button.slint"; -import { IconButton } from "./icon_button.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { Icons } from "../icons/icons.slint"; -import { MaterialAnimations } from "../styling/material_animations.slint"; - -export component SnackBar inherits PopupWindow { - in property text; - in property action_text; - in property has_close_button; - - close_policy: no_auto_close; - - callback action_clicked(); - - snack_bar := Elevation { - x: (parent.width - self.width) / 2; - y: parent.height - self.height; - width: MaterialStyleMetrics.size_344; - level: 3; - - background_layer := Rectangle { - background: MaterialPalette.inverse_surface; - border_radius: MaterialStyleMetrics.border_radius_4; - - HorizontalLayout { - padding_left: MaterialStyleMetrics.padding_16; - padding_right: self.padding_left; - padding_top: MaterialStyleMetrics.padding_10; - padding_bottom: self.padding_top; - spacing: MaterialStyleMetrics.spacing_4; - - MaterialText { - text: root.text; - vertical_alignment: center; - horizontal_alignment: left; - wrap: word_wrap; - style: MaterialTypography.body_medium; - color: MaterialPalette.inverse_on_surface; - } - - if root.action_text != "" : TextButton { - text: root.action_text; - inverse: true; - - clicked => { - root.raise_action(); - } - } - - if root.has_close_button : IconButton { - icon: Icons.close; - inverse: true; - - clicked => { - root.close(); - } - } - } - } - - animate y, height { - duration: MaterialAnimations.standard_accelerate_duration; - easing: MaterialAnimations.standard_easing; - } - } - - Timer { - interval: 50ms; - - triggered => { - snack_bar.y = root.height - snack_bar.height - MaterialStyleMetrics.padding_30; - self.running = false; - } - } - - function raise_action() { - root.action_clicked(); - root.close(); - } -} diff --git a/ui/ui/components/state_layer.slint b/ui/ui/components/state_layer.slint deleted file mode 100644 index ee50f74..0000000 --- a/ui/ui/components/state_layer.slint +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialAnimations } from "../styling/material_animations.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { ToolTip } from "./tooltip.slint"; -import { MaterialWindowAdapter } from "./material_window.slint"; - -export component Ripple { - in property border_radius; - in property pressed_x; - in property pressed_y; - in property color; - in property clip_ripple: true; - - Rectangle { - width: 100%; - height: 100%; - border_radius: root.border_radius; - clip: root.clip_ripple; - - ripple := Rectangle { - x: root.pressed_x - self.width / 2; - y: root.pressed_y - self.height / 2; - height: self.width; - border_radius: self.width / 2; - opacity: MaterialPalette.state_layer_opacity_press; - background: root.color; - - init => { - self.width = root.width * 2 * 1.4142; - } - - animate width { duration: MaterialAnimations.ripple_duration; easing: MaterialAnimations.ripple_easing; } - } - } -} - -export component StateLayer { - in property background; - in property border_radius; - in property display_background: true; - in property enabled; - in property pressed; - in property has_focus; - in property has_hover; - - property state_layer_opacity; - - Rectangle { - width: 100%; - height: 100%; - opacity: root.state_layer_opacity; - background: root.background; - border_radius: root.border_radius; - } - - Rectangle { - width: 100%; - height: 100%; - clip: true; - border_radius: root.border_radius; - - @children - } - - states [ - disabled when !root.enabled && root.display_background : { - root.state_layer_opacity: MaterialPalette.state_layer_opacity_focus; - } - focus when root.enabled && root.has_focus : { - root.state_layer_opacity: MaterialPalette.state_layer_opacity_focus; - } - pressed when root.enabled && root.pressed : { - root.state_layer_opacity: MaterialPalette.state_layer_opacity_press; - } - hover when root.enabled && root.has_hover && !MaterialWindowAdapter.disable_hover : { - root.state_layer_opacity: MaterialPalette.state_layer_opacity_hover; - } - ] - - animate state_layer_opacity { duration: MaterialAnimations.opacity_duration; easing: MaterialAnimations.opacity_easing; } -} - -export component FocusTouchArea inherits TouchArea { - out property has_focus <=> focus_scope.has_focus; - out property enter_pressed; - in property tooltip; - in property tooltip_offset; - - callback key_pressed(KeyEvent) -> EventResult; - - forward_focus: focus_scope; - - focus_scope := FocusScope { - x: 0; - width: 0px; - enabled: root.enabled; - - key_pressed(event) => { - if !root.enabled { - return reject; - } - - if (event.text == " " || event.text == "\n") && !root.enter_pressed { - root.enter_pressed = true; - root.clicked(); - return accept; - } - - root.key_pressed(event) - } - - key_released(event) => { - if !root.enabled { - return reject; - } - - if (event.text == " " || event.text == "\n") && root.enter_pressed { - root.enter_pressed = false; - return accept; - } - - reject - } - } - - @children - - - if root.tooltip != "" && root.has_hover && !MaterialWindowAdapter.disable_hover && !root.pressed : ToolTip { - y: root.tooltip_offset; - text: root.tooltip; - } -} - -export component StateLayerArea inherits FocusTouchArea { - in property color; - in property border_radius; - in property transparent_background; - in property display_background: true; - in property clip_ripple: true; - - property popup_open; - property has_h: root.mouse-x > 0 && root.mouse-x < root.width && root.mouse-y > 0 && root.mouse-y < root.height; - - if !root.enabled || (root.enabled && (root.has_focus || root.pressed || root.enter_pressed || root.has_hover)) : StateLayer { - width: 100%; - height: 100%; - background: root.transparent_background ? transparent : root.color; - border_radius: root.border_radius; - enabled: root.enabled; - pressed: root.pressed || root.enter_pressed; - has_focus: root.has_focus; - has_hover: root.has_hover; - display_background: root.display_background; - } - - // ripple - if root.enabled && (root.pressed || root.enter_pressed) : Ripple { - pressed_x: root.pressed_x; - pressed_y: root.pressed_y; - width: 100%; - height: 100%; - border_radius: root.border_radius; - color: root.color; - clip_ripple: root.clip_ripple; - } - - @children -} diff --git a/ui/ui/components/switch.slint b/ui/ui/components/switch.slint deleted file mode 100644 index d92078a..0000000 --- a/ui/ui/components/switch.slint +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { MaterialAnimations } from "../styling/material_animations.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { FocusTouchArea, StateLayer, Ripple } from "./state_layer.slint"; -import { Icon } from "./icon.slint"; - -export component Switch { - in_out property checked; - in property enabled: true; - in property tooltip <=> touch_area.tooltip; - in property on_icon; - in property off_icon; - - property has_icon: (root.checked && root.on_icon.width > 0 && root.on_icon.height > 0) || (root.off_icon.width > 0 && root.off_icon.height > 0); - property foreground: root.checked ? MaterialPalette.on_primary_container : MaterialPalette.surface_container_highest; - - callback checked_state_changed(checked: bool); - - min_width: MaterialStyleMetrics.size_52; - min_height: MaterialStyleMetrics.size_32; - - accessible-enabled: root.enabled; - accessible-checkable: true; - accessible-checked <=> root.checked; - accessible-role: switch; - accessible-action-default => { if(root.enabled) {touch_area.clicked();} } - forward_focus: touch_area; - - touch_area := FocusTouchArea { - enabled: root.enabled; - - background_layer := Rectangle { - background: root.checked ? MaterialPalette.primary : MaterialPalette.surface_container_highest; - border_radius: self.height / 2; - border_width: root.checked ? 0 : 2px; - border_color: MaterialPalette.outline; - - state_layer := StateLayer { - x: indicator.x + (indicator.width - self.width) / 2; - width: self.height; - height: root.height + MaterialStyleMetrics.size_8; - border_radius: self.height / 2; - has_hover: touch_area.has_hover; - has_focus: touch_area.has_focus; - pressed: touch_area.pressed; - enabled: root.enabled; - - // ripple - if root.enabled && (touch_area.pressed || touch_area.enter_pressed) : Ripple { - pressed_x: touch_area.pressed_x; - pressed_y: touch_area.pressed_y; - width: 100%; - height: 100%; - border_radius: state_layer.border_radius; - color: state_layer.background; - clip_ripple: true; - } - } - - indicator := Rectangle { - property indicator-padding: root.checked || root.has_icon ? MaterialStyleMetrics.padding_4 : MaterialStyleMetrics.padding_8; - - property default_height: background_layer.height - 2 * self.indicator-padding; - - x: self.indicator-padding - (self.height - self.default_height) / 2; - width: self.height; - height: self.default_height; - border_radius: self.height / 2; - background: root.checked ? MaterialPalette.primary_container : MaterialPalette.outline; - - if root.has_icon : Icon { - source: root.checked ? root.on_icon : root.off_icon; - colorize: root.foreground; - - animate colorize { - duration: MaterialAnimations.opacity_duration; - easing: MaterialAnimations.opacity_easing; - } - } - - animate height { - duration: MaterialAnimations.emphasized_accelerate_duration; - easing: MaterialAnimations.emphasized_easing; - } - - animate background { - duration: MaterialAnimations.opacity_duration; - easing: MaterialAnimations.opacity_easing; - } - - states [ - checked when root.checked : { - x: background_layer.width - self.indicator-padding - self.width; - - in { - animate x { - duration: MaterialAnimations.emphasized_accelerate_duration; - easing: MaterialAnimations.emphasized_easing; - } - } - - out { - animate x { - duration: MaterialAnimations.emphasized_accelerate_duration; - easing: MaterialAnimations.emphasized_easing; - } - } - } - ] - } - - animate background, border_color { - duration: MaterialAnimations.opacity_duration; - easing: MaterialAnimations.opacity_easing; - } - } - - clicked => { - root.toggle(); - } - } - - function toggle() { - root.checked = !root.checked; - } - - changed checked => { - root.checked_state_changed(root.checked); - } - - states [ - disabled when !root.enabled : { - state_layer.display_background: false; - indicator.background: root.checked ? MaterialPalette.surface : MaterialPalette.outline.with_alpha(MaterialPalette.disable_opacity); - background_layer.background: (root.checked ? MaterialPalette.on_surface : MaterialPalette.surface_variant).with_alpha(MaterialPalette.state_layer_opacity_disabled); - background_layer.border_color: MaterialPalette.on_surface.with_alpha(MaterialPalette.state_layer_opacity_disabled); - foreground: root.checked ? MaterialPalette.on_surface.with_alpha(MaterialPalette.disable_opacity) : MaterialPalette.surface_container_highest; - } - pressed when touch_area.pressed: { - state_layer.background: indicator.background; - indicator.height: background_layer.height - 2px; - indicator.background: root.checked ? MaterialPalette.primary_container : MaterialPalette.on_surface_variant; - } - hover when touch_area.has_hover: { - state_layer.background: indicator.background; - } - ] -} diff --git a/ui/ui/components/tab_bar.slint b/ui/ui/components/tab_bar.slint deleted file mode 100644 index 0be204b..0000000 --- a/ui/ui/components/tab_bar.slint +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { NavigationItem } from "../items/navigation_item.slint"; -import { BaseNavigationItemTemplate, BaseNavigation } from "./base_navigation.slint"; -import { StateLayerArea } from "./state_layer.slint"; -import { MaterialText } from "./material_text.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialAnimations } from "../styling/material_animations.slint"; -import { Icon } from "./icon.slint"; - -component TabItemTemplate inherits BaseNavigationItemTemplate { - out property label_width: label.width; - - property has_selected_icon: root.selected_icon.width > 0 || root.selected_icon.height > 0; - - StateLayerArea { - color: MaterialPalette.primary; - - VerticalLayout { - padding_top: MaterialStyleMetrics.padding_10; - padding_bottom: MaterialStyleMetrics.padding_8; - spacing: MaterialStyleMetrics.spacing_2; - - if root.icon.width > 0 && root.icon.height > 0 : HorizontalLayout { - alignment: center; - - Icon { - source: root.icon; - colorize: label.color; - - states [ - selected when root.selected && root.has_selected_icon : { - source: root.selected_icon; - } - ] - } - } - - HorizontalLayout { - alignment: center; - - label := MaterialText { - text: root.text; - style: MaterialTypography.title_small; - color: MaterialPalette.on_surface_variant; - } - } - } - - clicked => { - root.clicked(); - } - } - - states [ - selected when root.selected : { - label.color: MaterialPalette.primary; - } - ] -} - -component SecondaryTabItemTemplate inherits BaseNavigationItemTemplate { - property has_selected_icon: root.selected_icon.width > 0 || root.selected_icon.height > 0; - - StateLayerArea { - color: label.color; - - HorizontalLayout { - alignment: center; - spacing: MaterialStyleMetrics.spacing_8; - - if root.icon.width > 0 && root.icon.height > 0 : VerticalLayout { - alignment: center; - - Icon { - source: root.icon; - colorize: label.color; - - states [ - selected when root.selected && root.has_selected_icon : { - source: root.selected_icon; - } - ] - } - } - - - label := MaterialText { - text: root.text; - style: MaterialTypography.title_small; - color: MaterialPalette.on_surface_variant; - vertical_alignment: center; - } - } - - clicked => { - root.clicked(); - } - } - - states [ - selected when root.selected : { - label.color: MaterialPalette.on_surface; - } - ] -} - -export component TabBar inherits BaseNavigation { - property indicator_width; - property item_width: root.width / root.items.length; - property current_x: root.item_width * self.current_index; - - min_height: max(MaterialStyleMetrics.size_49, layout.min_height); - - Rectangle { - background: MaterialPalette.surface; - - layout := HorizontalLayout { - for item[index] in root.items : tab_item := TabItemTemplate { - icon: item.icon; - selected_icon: item.selected_icon; - text: item.text; - index: index; - selected: index == root.current_index; - show_badge: item.show_badge; - badge: item.badge; - - clicked => { - root.select(index); - root.set_indicator_width(index, self.label_width); - } - - Timer { - interval: 50ms; - - triggered => { - root.set_indicator_width(index, tab_item.label_width); - self.running = false; - } - } - } - } - - indicator := Rectangle { - x: root.current_x + (root.item_width - self.width) / 2; - y: parent.height - self.height; - width: root.indicator_width; - height: MaterialStyleMetrics.size_3; - border_top_left_radius: self.height / 2; - border_top_right_radius: self.border_top_left_radius; - background: MaterialPalette.primary; - - animate x, width { - duration: MaterialAnimations.standard_accelerate_duration; - easing: MaterialAnimations.standard_easing; - } - } - } - - function set_indicator_width(index: int, width: length) { - if index != root.current_index { - return; - } - - root.indicator_width = width; - } -} - -export component SecondaryTabBar inherits BaseNavigation { - property item_width: root.width / root.items.length; - property current_x: root.item_width * self.current_index; - - min_height: max(MaterialStyleMetrics.size_49, layout.min_height); - - Rectangle { - background: MaterialPalette.surface; - - layout := HorizontalLayout { - for item[index] in root.items : SecondaryTabItemTemplate { - icon: item.icon; - selected_icon: item.selected_icon; - text: item.text; - index: index; - selected: index == root.current_index; - show_badge: item.show_badge; - badge: item.badge; - - clicked => { - root.select(index); - } - } - } - - indicator := Rectangle { - x: root.current_x; - y: parent.height - self.height; - width: root.item_width; - height: MaterialStyleMetrics.size_2; - background: MaterialPalette.primary; - - animate x { - duration: MaterialAnimations.standard_accelerate_duration; - easing: MaterialAnimations.standard_easing; - } - } - } -} diff --git a/ui/ui/components/text_button.slint b/ui/ui/components/text_button.slint deleted file mode 100644 index e04e513..0000000 --- a/ui/ui/components/text_button.slint +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { BaseButton } from "./base_button.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; - -export component TextButton { - in property icon <=> base.icon; - in property text <=> base.text; - in property enabled <=> base.enabled; - in property tooltip <=> base.tooltip; - in property inverse; - - callback clicked <=> base.clicked; - - accessible-role: button; - accessible-enabled: root.enabled; - accessible-label: root.text == "" ? root.tooltip : root.text; - accessible-action-default => { base.clicked(); } - - base := BaseButton { - border_radius: root.height / 2; - color: root.inverse ? MaterialPalette.inverse_primary : MaterialPalette.primary; - } - - states [ - disabled when !root.enabled : { - base.color: MaterialPalette.on_surface; - base.transparent_background: true; - } - ] -} diff --git a/ui/ui/components/text_field.slint b/ui/ui/components/text_field.slint deleted file mode 100644 index 231820d..0000000 --- a/ui/ui/components/text_field.slint +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { MaterialAnimations } from "../styling/material_animations.slint"; -import { MaterialText } from "./material_text.slint"; -import { IconButton } from "./icon_button.slint"; -import { Icon } from "./icon.slint"; - -export component TextField { - in property label: placeholder_text; - // Deprecated: this property was unintentionally made public. Use `label` instead. - in property placeholder_text; - in property supporting_text; - in property has_error; - in property leading_icon; - in property trailing_icon; - in property enabled: true; - in property read_only <=> input.read_only; - out property has_focus: input.has_focus; - in_out property text <=> input.text; - in property input-type <=> input.input-type; - - callback leading_icon_clicked(); - callback trailing_icon_clicked(); - callback accepted(text: string); - callback edited(text: string); - callback key_pressed(event: KeyEvent) -> EventResult; - callback key_released(event: KeyEvent) -> EventResult; - - property has_leading: root.leading_icon.width > 0 && root.leading_icon.height > 0; - property has_trailing: root.trailing_icon.width > 0 && root.trailing_icon.height > 0; - property computed_x; - property highlight: root.enabled && root.has_error ? MaterialPalette.error : root.has_focus ? MaterialPalette.primary : MaterialPalette.on_surface_variant; - - forward_focus: input; - accessible-role: text-input; - accessible-enabled: root.enabled; - accessible-value <=> text; - accessible-placeholder-text: placeholder_text; - accessible-action-set-value(v) => { text = v; edited(v); } - - layout := VerticalLayout { - spacing: MaterialStyleMetrics.spacing_4; - - background_layer := Rectangle { - border_top_left_radius: MaterialStyleMetrics.border_radius_4; - border_top_right_radius: MaterialStyleMetrics.border_radius_4; - background: MaterialPalette.surface_container_highest; - min_height: max(MaterialStyleMetrics.size_56, inner_layout.min_height); - - inner_layout := HorizontalLayout { - padding_left: root.has_leading ? MaterialStyleMetrics.padding_4 : MaterialStyleMetrics.padding_16; - padding_right: root.has_trailing ? MaterialStyleMetrics.padding_4 : MaterialStyleMetrics.padding_16; - padding_top: MaterialStyleMetrics.padding_4; - padding_bottom: self.padding_top; - spacing: MaterialStyleMetrics.spacing_2; - - if root.has_leading : IconButton { - icon: root.leading_icon; - enabled: root.enabled; - - clicked => { - root.leading_icon_clicked(); - } - } - - HorizontalLayout { - padding_top: MaterialStyleMetrics.padding_4; - padding_bottom: self.padding_top; - - Rectangle { - horizontal_stretch: 1; - clip: true; - min_height: input.min_height; - - input := TextInput { - x: min(0px, max(parent.width - self.width - self.text_cursor_width, root.computed_x)); - width: max(parent.width - self.text-cursor-width, self.preferred-width); - font_size: MaterialTypography.body_large.font_size; - font_weight: MaterialTypography.body_large.font_weight; - text_cursor_width: MaterialStyleMetrics.size_3; - vertical_alignment: bottom; - single_line: true; - color: MaterialPalette.on_surface; - selection_background_color: MaterialPalette.primary.with_alpha(0.5); - selection_foreground_color: self.color; - enabled: root.enabled; - // Disable TextInput's built-in accessibility support as the component takes care of that. - accessible-role: none; - - cursor_position_changed(cursor_position) => { - if cursor_position.x + root.computed_x < 0 { - root.computed_x = - cursor_position.x; - } else if cursor-position.x + root.computed_x > parent.width - self.text-cursor-width { - root.computed_x = parent.width - cursor_position.x - self.text-cursor-width; - } - } - - accepted => { - root.accepted(self.text); - } - - edited => { - root.edited(self.text); - } - - key_pressed(event) => { - root.key_pressed(event) - } - - key_released(event) => { - root.key_released(event) - } - } - - text_label := MaterialText { - y: (parent.height - self.height) / 2; - width: 100%; - text: root.label; - color: root.highlight; - style: MaterialTypography.body_large; - - states [ - on_top when root.text != "" || input.has_focus : { - y: 0; - style: MaterialTypography.body_small; - } - ] - - animate font_size, y { - duration: MaterialAnimations.standard_fast_duration; - easing: MaterialAnimations.standard_easing; - } - } - } - } - - if root.trailing_icon.width > 0 && root.trailing_icon.height > 0 : IconButton { - icon: root.trailing_icon; - enabled: root.enabled; - has_error: root.has_error; - - clicked => { - root.trailing_icon_clicked(); - } - } - } - - active_indicator := Rectangle { - y: parent.height - self.height; - height: MaterialStyleMetrics.size_1; - background: root.highlight; - - animate height { - duration: MaterialAnimations.standard_fast_duration; - easing: MaterialAnimations.standard_easing; - } - } - } - - if root.supporting_text != "" : HorizontalLayout { - padding_left: MaterialStyleMetrics.padding_16; - padding_right: self.padding_left; - - supporting_text := MaterialText { - text: root.supporting_text; - style: MaterialTypography.body_small; - color: root.highlight; - opacity: root.enabled ? 1 : MaterialPalette.disable_opacity; - } - } - } - - public function set_selection_offsets(start: int, end: int) { - input.set_selection_offsets(start, end); - } - - public function select_all() { - input.select_all(); - } - - public function clear_selection() { - input.clear_selection(); - } - - public function cut() { - input.cut(); - } - - public function copy() { - input.copy(); - } - - public function paste() { - input.paste(); - } - - states [ - disabled when !root.enabled : { - background_layer.background: MaterialPalette.surface_container_highest.with_alpha(MaterialPalette.disable_opacity); - text_label.opacity: MaterialPalette.disable_opacity; - input.opacity: MaterialPalette.disable_opacity; - } - focused when input.has_focus : { - active_indicator.height: MaterialStyleMetrics.size_3; - } - ] -} diff --git a/ui/ui/components/time_picker.slint b/ui/ui/components/time_picker.slint deleted file mode 100644 index 5109e62..0000000 --- a/ui/ui/components/time_picker.slint +++ /dev/null @@ -1,674 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { BaseDialog } from "./dialog.slint"; -import { Icons } from "../icons/icons.slint"; -import { MaterialText } from "./material_text.slint"; -import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; -import { MaterialTypography } from "../styling/material_typography.slint"; -import { MaterialPalette } from "../styling/material_palette.slint"; -import { StateLayerArea } from "state_layer.slint"; - -component TimeSelector inherits Rectangle { - in property selected; - in property value; - - callback clicked <=> touch_area.clicked; - - width: max(MaterialStyleMetrics.size_48, text_label.min_width); - height: max(MaterialStyleMetrics.size_48, text_label.min_height); - border_radius: max(root.width, root.height) / 2; - vertical_stretch: 0; - horizontal_stretch: 0; - - touch_area := TouchArea { - text_label := MaterialText { - text: root.value; - vertical_alignment: center; - horizontal_alignment: center; - style: MaterialTypography.body_large; - color: MaterialPalette.on_surface; - } - } - - states [ - selected when root.selected: { - text_label.color: MaterialPalette.on_primary; - } - ] -} - -component Clock { - in property <[int]> model; - in property two_columns; - in property total; - in_out property current_index; - in property current_value; - - callback current_index_changed(index: int); - - property radius: max(root.width, root.height) / 2; - property picker_ditameter: MaterialStyleMetrics.size_48; - property center: root.radius - root.picker_ditameter / 2; - property outer_padding: 2px; - property inner_padding: 32px; - property radius_outer: root.center - root.outer_padding; - property radius_inner: root.center - root.inner_padding; - property half_total: root.total / 2; - property rotation: 0.25turn; - property current_x: get_index_x(root.current_value); - property current_y: get_index_y(root.current_value); - - min_width: MaterialStyleMetrics.size_256; - min_height: self.min_width; - vertical_stretch: 0; - horizontal_stretch: 0; - - background_layer := Rectangle { - border_radius: max(self.width, self.height) / 2; - background: MaterialPalette.surface_container_highest; - - if root.current_index >= 0 || root.current_index < root.model.length: Path { - stroke: MaterialPalette.primary; - stroke_width: 2px; - viewbox_width: self.width / 1px; - viewbox_height: self.height / 1px; - - MoveTo { - x: root.width / 2px; - y: root.height / 2px; - } - - LineTo { - x: (root.current_x + root.picker_ditameter / 2) / 1px; - y: (root.current_y + root.picker_ditameter / 2) / 1px; - } - } - - Rectangle { - width: MaterialStyleMetrics.size_8; - height: self.width; - background: MaterialPalette.primary; - border_radius: self.width / 2; - } - - if root.current_index < root.model.length: Rectangle { - x: root.current_x; - y: root.current_y; - width: root.picker_ditameter; - height: root.picker_ditameter; - border_radius: root.picker_ditameter / 2; - background: MaterialPalette.primary; - - if root.current_index < 0: Rectangle { - width: MaterialStyleMetrics.size_4; - height: self.width; - border_radius: self.width / 2; - background: MaterialPalette.on_primary; - } - } - - for val[index] in root.model: TimeSelector { - x: get_index_x(val); - y: get_index_y(val); - width: root.picker_ditameter; - height: root.picker_ditameter; - value: val; - selected: index == root.current_index; - accessible_role: button; - accessible_label: @tr("{} Hours or minutes of {}", val, root.total); - accessible_action_default => { - self.clicked(); - } - - clicked => { - root.set_current_index(index); - } - } - } - - - pure function value_to_angle(value: int) -> angle { - if root.two_columns { - if value >= root.half_total { - return clamp((value - root.half_total) / root.half_total * 1turn, 0, 0.999999turn) - root.rotation; - } - return clamp(value / root.half_total * 1turn, 0, 0.99999turn) - root.rotation; - } - - clamp(value / root.total * 1turn, 0, 0.99999turn) - root.rotation - } - - pure function get_index_x(value: int) -> length { - if root.two_columns && value >= root.half_total { - return root.center + (root.radius_inner / 1px * cos(root.value_to_angle(value))) * 1px; - } - - root.center + (root.radius_outer / 1px * cos(root.value_to_angle(value))) * 1px - } - - pure function get_index_y(value: int) -> length { - // this is only for 24 mode - if root.total == 24 && value == 0 { - return root.center + (root.radius_inner / 1px * sin(root.value_to_angle(value))) * 1px; - } - if root.total == 24 && value == 12 { - return root.center + (root.radius_outer / 1px * sin(root.value_to_angle(value))) * 1px; - } - if root.two_columns && value >= root.half_total { - return root.center + (root.radius_inner / 1px * sin(root.value_to_angle(value))) * 1px; - } - - root.center + (root.radius_outer / 1px * sin(root.value_to_angle(value))) * 1px - } - - function set_current_index(index: int) { - root.current_index_changed(index); - } -} - -component TimePickerInput { - in property read_only <=> text_input.read_only; - in property checked; - in_out property text <=> text_input.text; - - callback clicked; - callback edited(value: int); - - width: MaterialStyleMetrics.size_90; - min_height: max(MaterialStyleMetrics.size_80, layout.min_height); - vertical_stretch: 0; - horizontal_stretch: 0; - - forward_focus: text_input; - - background_layer := Rectangle { - border_radius: MaterialStyleMetrics.border_radius_8; - background: MaterialPalette.surface_container_highest; - clip: true; - - layout := HorizontalLayout { - padding: MaterialStyleMetrics.padding_8; - - text_input := TextInput { - vertical_alignment: center; - horizontal_alignment: center; - color: MaterialPalette.on_surface; - font_size: MaterialTypography.display_large.font_size; - font_weight: MaterialTypography.display_large.font_weight; - input_type: number; - visible: !root.read_only; - - edited => { - root.edited(self.text.to_float()); - } - } - } - - if root.read_only : StateLayerArea { - border_radius: background_layer.border_radius; - color: MaterialPalette.on_surface; - - HorizontalLayout { - padding: MaterialStyleMetrics.padding_8; - - MaterialText { - text: root.text.to_float() >= 10 ? root.text : "0" + root.text; - style: MaterialTypography.display_large; - color: MaterialPalette.on_surface; - horizontal_alignment: center; - vertical_alignment: center; - } - } - - clicked => { - root.clicked(); - } - } - } - - states [ - has_focus when text_input.has_focus && !root.read_only: { - background_layer.background: MaterialPalette.primary_container; - background_layer.border_width: 2px; - background_layer.border_color: MaterialPalette.primary; - text_input.color: MaterialPalette.on_primary_container; - } - checked when root.checked: { - background_layer.background: MaterialPalette.primary_container; - text_input.color: MaterialPalette.on_primary_container; - } - ] -} - -component PeriodSelectorItem { - in property text <=> label.text; - in property checked; - - callback clicked <=> state_layer.clicked; - - background_layer := Rectangle { - state_layer := StateLayerArea { - color: label.color; - - label := MaterialText { - color: MaterialPalette.on_surface_variant; - horizontal_alignment: center; - } - } - } - - states [ - checked when root.checked: { - background_layer.background: MaterialPalette.tertiary_container; - label.color: MaterialPalette.on_tertiary_container; - } - ] -} - - -component PeriodSelector { - in property am_selected; - in property horizontal; - - callback update_period(am_selected: bool); - - min_width: max(MaterialStyleMetrics.size_52, layout.min_width); - min_height: max(root.horizontal ? MaterialStyleMetrics.size_38 : MaterialStyleMetrics.size_80, layout.min_height); - accessible_label: "AM or PM"; - accessible_role: checkbox; - accessible_checked: root.am_selected; - - Rectangle { - border_radius: border.border_radius; - clip: true; - - layout := VerticalLayout { - if root.horizontal : HorizontalLayout { - PeriodSelectorItem { - text: "AM"; - checked: root.am_selected; - - clicked => { - if root.am_selected { - return; - } - root.update_period(true); - } - } - - Rectangle { - width: 1px; - background: border.border_color; - vertical_stretch: 0; - } - - PeriodSelectorItem { - text: "PM"; - checked: !root.am_selected; - - clicked => { - if !root.am_selected { - return; - } - root.update_period(false); - } - } - } - - if !root.horizontal : VerticalLayout { - PeriodSelectorItem { - text: "AM"; - checked: root.am_selected; - - clicked => { - if root.am_selected { - return; - } - root.update_period(true); - } - } - - Rectangle { - height: 1px; - background: border.border_color; - vertical_stretch: 0; - } - - PeriodSelectorItem { - text: "PM"; - checked: !root.am_selected; - - clicked => { - if !root.am_selected { - return; - } - root.update_period(false); - } - } - } - } - } - - border := Rectangle { - border_radius: MaterialStyleMetrics.border_radius_8; - border_width: 1px; - border_color: MaterialPalette.outline; - } -} - -export struct Time { - hour: int, - minute: int, - second: int -} - -export component TimePickerPopup inherits PopupWindow { - in property use_24_hour_format: true; - in property title: @tr("Select time"); - in property cancel_text: @tr("Cancel"); - in property ok_text: @tr("Ok"); - in property