diff --git a/Cargo.lock b/Cargo.lock index ed70731..686fe61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -439,6 +439,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -532,6 +538,12 @@ dependencies = [ "syn 2.0.108", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.0" @@ -674,6 +686,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colorchoice" version = "1.0.4" @@ -1225,6 +1243,21 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -1501,6 +1534,16 @@ dependencies = [ "wasip2", ] +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.32.3" @@ -1727,6 +1770,7 @@ dependencies = [ "iced_renderer", "iced_widget", "iced_winit", + "image", "thiserror 1.0.69", ] @@ -1790,6 +1834,8 @@ dependencies = [ "half", "iced_core", "iced_futures", + "image", + "kamadak-exif", "log", "once_cell", "raw-window-handle", @@ -1997,6 +2043,24 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-traits", + "png", + "qoi", + "tiff", +] + [[package]] name = "indexmap" version = "2.12.0" @@ -2089,6 +2153,15 @@ dependencies = [ "libc", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" +dependencies = [ + "rayon", +] + [[package]] name = "js-sys" version = "0.3.82" @@ -2099,6 +2172,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kamadak-exif" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4fc70d0ab7e5b6bafa30216a6b48705ea964cdfc29c050f2412295eba58077" +dependencies = [ + "mutate_once", +] + [[package]] name = "khronos-egl" version = "6.0.0" @@ -2126,6 +2208,12 @@ dependencies = [ "smallvec", ] +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "libc" version = "0.2.177" @@ -2304,6 +2392,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "mutate_once" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af" + [[package]] name = "naga" version = "0.19.2" @@ -3155,6 +3249,15 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3" +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quick-xml" version = "0.37.5" @@ -3895,6 +3998,17 @@ dependencies = [ "syn 2.0.108", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -4436,6 +4550,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "weezl" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" + [[package]] name = "wgpu" version = "0.19.4" @@ -5437,6 +5557,15 @@ dependencies = [ "syn 2.0.108", ] +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + [[package]] name = "zvariant" version = "4.2.0" diff --git a/config-frontend/Cargo.toml b/config-frontend/Cargo.toml index 101e8b2..aeb0b59 100644 --- a/config-frontend/Cargo.toml +++ b/config-frontend/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" rust-version.workspace = true [dependencies] -iced = { version = "0.13.1", features = ["tokio"] } +iced = { version = "0.13.1", features = ["tokio", "image"] } messaging = { path = "../messaging" } protobuf = "3.7.2" rfd = "0.15.4" diff --git a/config-frontend/src/main.rs b/config-frontend/src/main.rs index b12ade5..dc62519 100644 --- a/config-frontend/src/main.rs +++ b/config-frontend/src/main.rs @@ -13,6 +13,7 @@ use iced::task::Task; use iced::{Element, Subscription}; use messaging::client_wrapper::{ClientCommands, ClientWrapper}; use messaging::protos::key_config::command_action::Command; +use messaging::protos::server_config::ServerConfig; use std::cmp::PartialEq; use std::time::Duration; @@ -29,16 +30,26 @@ enum View { struct LaunchpadConfigApp { view: View, socket_client: Option, + /// Used to determine if we need to try and connect to server again connecting_to_backend: bool, brightness: u8, current_input_sequence: Vec, current_command_input_value: String, + current_server_config: Option, } impl LaunchpadConfigApp { fn get_client(&mut self) -> Option<&mut ClientWrapper> { self.socket_client.as_mut() } + + fn request_server_config(&mut self) -> Option { + if let Some(client) = self.get_client() { + client.request_server_config().ok(); + return client.check_for_server_config().ok(); + } + None + } } fn view(application_state: &'_ LaunchpadConfigApp) -> Element<'_, Messages> { @@ -50,6 +61,10 @@ fn view(application_state: &'_ LaunchpadConfigApp) -> Element<'_, Messages> { application_state.current_input_sequence.to_owned(), mode.to_owned(), application_state.current_command_input_value.to_owned(), + application_state + .current_server_config + .to_owned() + .unwrap_or_default(), ), _ => todo!(), } @@ -81,6 +96,7 @@ fn update(application_state: &mut LaunchpadConfigApp, message: Messages) -> Task application_state.connecting_to_backend = false; application_state.view = View::Configure(ConfigurableZones::None, ExtraConfigMode::Default); + return Task::done(Messages::RequestBackendConfig); } Messages::SetBrightness(new_brightness) => { @@ -100,6 +116,7 @@ fn update(application_state: &mut LaunchpadConfigApp, message: Messages) -> Task if let Some(client) = application_state.get_client() { client.clear_display_zone_image(display_zone).ok(); } + return Task::done(Messages::RequestBackendConfig); } Messages::SetBootLogo => { @@ -122,10 +139,12 @@ fn update(application_state: &mut LaunchpadConfigApp, message: Messages) -> Task .set_display_zone_image(display_zone, absolute_path) .ok(); } + return Task::done(Messages::RequestBackendConfig); } Messages::OpenConfigurationPanel(zone) => { application_state.view = View::Configure(zone, ExtraConfigMode::Default); + return Task::done(Messages::RequestBackendConfig); } Messages::OpenInputMappingConfigurationPanel(zone, mode) => match mode { @@ -193,12 +212,12 @@ fn update(application_state: &mut LaunchpadConfigApp, message: Messages) -> Task Messages::SetKeyConfig(input_id, sequence) => { let mut builder = messaging::proto_builders::KeyConfigActionBuilder::new(); sequence.iter().for_each(|action| match action { - common::KeyConfigOptions::Key(key_action) => { + KeyConfigOptions::Key(key_action) => { builder.add_prebuilt_key_action( ProtoKeyActionWrapper::from(key_action.to_owned()).key_action(), ); } - common::KeyConfigOptions::Command(command_action) => { + KeyConfigOptions::Command(command_action) => { builder.add_command_action(command_action.to_owned()) } }); @@ -211,6 +230,16 @@ fn update(application_state: &mut LaunchpadConfigApp, message: Messages) -> Task return Task::done(Messages::ResetInputBuffer); } + Messages::RequestBackendConfig => { + return Task::done(Messages::BackendConfigUpdated( + application_state.request_server_config(), + )); + } + Messages::BackendConfigUpdated(config) => { + if let Some(new_config) = config { + application_state.current_server_config = Some(new_config); + } + } } Task::none() } diff --git a/config-frontend/src/messages.rs b/config-frontend/src/messages.rs index 0bf65b9..0ae49cd 100644 --- a/config-frontend/src/messages.rs +++ b/config-frontend/src/messages.rs @@ -3,6 +3,7 @@ use iced::keyboard::{Key, Modifiers}; use messaging::protos::display_zones::DisplayZone; use messaging::protos::inputs::InputId; use messaging::protos::key_config::command_action::Command; +use messaging::protos::server_config::ServerConfig; #[derive(Debug, Clone)] pub enum Messages { @@ -15,6 +16,8 @@ pub enum Messages { InitialiseBackend, BackendInitialised, + RequestBackendConfig, + BackendConfigUpdated(Option), OpenConfigurationPanel(ConfigurableZones), OpenInputMappingConfigurationPanel(ConfigurableZones, ExtraConfigMode), diff --git a/config-frontend/src/views/config.rs b/config-frontend/src/views/config.rs index 467d1f9..32ec6d3 100644 --- a/config-frontend/src/views/config.rs +++ b/config-frontend/src/views/config.rs @@ -1,5 +1,6 @@ mod buttons; mod knobs; +mod saved_config_display; mod touchscreen; use crate::common::{ @@ -21,6 +22,7 @@ use iced::{Length, widget}; use messaging::protos::inputs::InputId; use messaging::protos::key_config::FreeformCommand; use messaging::protos::key_config::command_action::Command; +use messaging::protos::server_config::ServerConfig; pub struct Config; @@ -107,6 +109,7 @@ impl<'a> Config { current_key_sequence: Vec, selected_mode: ExtraConfigMode, current_command_input: String, + current_backend_config: ServerConfig, ) -> iced::Element<'_, Messages> { let base = widget::container( column![ @@ -145,7 +148,11 @@ impl<'a> Config { selected_mode, current_command_input, ), - _ => button_config_settings(selected_config_zone), + _ => button_config_settings( + selected_config_zone, + current_backend_config.display_images, + current_backend_config.key_configs, + ), }, ConfigurableZones::Touchscreen1(ref selected_input) | ConfigurableZones::Touchscreen2(ref selected_input) @@ -157,7 +164,11 @@ impl<'a> Config { selected_mode, current_command_input, ), - _ => touchscreen_zone_config_settings(selected_config_zone), + _ => touchscreen_zone_config_settings( + selected_config_zone, + current_backend_config.display_images, + current_backend_config.key_configs, + ), }, ConfigurableZones::TouchscreenExtra(ref selected_input) => match selected_input { @@ -169,7 +180,7 @@ impl<'a> Config { current_command_input, ) } - _ => touchscreen_swipe_settings(), + _ => touchscreen_swipe_settings(current_backend_config.key_configs), }, ConfigurableZones::Knob1(ref selected_input) | ConfigurableZones::Knob2(ref selected_input) @@ -183,7 +194,10 @@ impl<'a> Config { current_command_input, ) } - _ => knob_config_settings(selected_config_zone), + _ => knob_config_settings( + selected_config_zone, + current_backend_config.key_configs, + ), }, _ => column![], }, diff --git a/config-frontend/src/views/config/buttons.rs b/config-frontend/src/views/config/buttons.rs index eae2bd2..a891d53 100644 --- a/config-frontend/src/views/config/buttons.rs +++ b/config-frontend/src/views/config/buttons.rs @@ -1,9 +1,13 @@ use crate::common::{ButtonInput, ConfigurableZones, ExtraConfigMode}; use crate::messages::Messages; use crate::views::config::BUTTON_COUNT; +use crate::views::config::saved_config_display::{current_image, current_key_config}; use iced::widget; use iced::widget::row; use messaging::protos::display_zones::DisplayZone; +use messaging::protos::inputs::InputId; +use messaging::protos::key_config::KeyConfig; +use messaging::protos::server_config::DisplayImage; pub fn button_grid_first_row<'a>() -> widget::Row<'a, Messages> { (0..BUTTON_COUNT / 2).fold(row![], |row, i| { @@ -39,7 +43,11 @@ pub fn button_grid_second_row<'a>() -> widget::Row<'a, Messages> { row.push(button) }) } -pub fn button_config_settings<'a>(button: ConfigurableZones) -> widget::Column<'a, Messages> { +pub fn button_config_settings<'a>( + button: ConfigurableZones, + image_config: Vec, + key_config: Vec, +) -> widget::Column<'a, Messages> { let button_mapping = match button { ConfigurableZones::Button1(_) => ( "Button 1", @@ -115,12 +123,12 @@ pub fn button_config_settings<'a>(button: ConfigurableZones) -> widget::Column<' let pressed_action_button = widget::button("On pressed").on_press(Messages::OpenInputMappingConfigurationPanel( - button_mapping.2, + button_mapping.2.clone(), ExtraConfigMode::KeyRecording, )); let released_action_button = widget::button("On released").on_press(Messages::OpenInputMappingConfigurationPanel( - button_mapping.3, + button_mapping.3.clone(), ExtraConfigMode::KeyRecording, )); @@ -130,8 +138,11 @@ pub fn button_config_settings<'a>(button: ConfigurableZones) -> widget::Column<' iced::widget::column![ title, button, + current_key_config(&key_config, InputId::from(button_mapping.2)), pressed_action_button, + current_key_config(&key_config, InputId::from(button_mapping.3)), released_action_button, + current_image(&image_config, button_mapping.1), clear_image_button ] } diff --git a/config-frontend/src/views/config/knobs.rs b/config-frontend/src/views/config/knobs.rs index 3b5cc8b..51ee29a 100644 --- a/config-frontend/src/views/config/knobs.rs +++ b/config-frontend/src/views/config/knobs.rs @@ -1,8 +1,11 @@ use crate::common::{ConfigurableZones, ExtraConfigMode, KnobInput}; use crate::messages::Messages; use crate::views::config::KNOB_COUNT; +use crate::views::config::saved_config_display::current_key_config; use iced::widget; use iced::widget::row; +use messaging::protos::inputs::InputId; +use messaging::protos::key_config::KeyConfig; pub fn knob_row<'a>() -> widget::Row<'a, Messages> { (0..KNOB_COUNT).fold(row![], |row, i| { @@ -20,8 +23,11 @@ pub fn knob_row<'a>() -> widget::Row<'a, Messages> { ) }) } -pub fn knob_config_settings<'a>(zone: ConfigurableZones) -> widget::Column<'a, Messages> { - let touchscreen_zone_mapping = match zone { +pub fn knob_config_settings<'a>( + zone: ConfigurableZones, + key_config: Vec, +) -> widget::Column<'a, Messages> { + let knob_mapping = match zone { ConfigurableZones::Knob1(_) => ( "Knob 1", ConfigurableZones::Knob1(KnobInput::Clockwise), @@ -56,24 +62,27 @@ pub fn knob_config_settings<'a>(zone: ConfigurableZones) -> widget::Column<'a, M let knob_clockwise_config = widget::button("Clockwise").on_press(Messages::OpenInputMappingConfigurationPanel( - touchscreen_zone_mapping.1, + knob_mapping.1.clone(), ExtraConfigMode::KeyRecording, )); let knob_counter_clockwise_config = widget::button("Counter Clockwise").on_press(Messages::OpenInputMappingConfigurationPanel( - touchscreen_zone_mapping.2, + knob_mapping.2.clone(), ExtraConfigMode::KeyRecording, )); let knob_pressed_config = widget::button("Pressed").on_press(Messages::OpenInputMappingConfigurationPanel( - touchscreen_zone_mapping.3, + knob_mapping.3.clone(), ExtraConfigMode::KeyRecording, )); iced::widget::column![ - widget::text!("{} config", touchscreen_zone_mapping.0), + widget::text!("{} config", knob_mapping.0), + current_key_config(&key_config, InputId::from(knob_mapping.1)), knob_clockwise_config, + current_key_config(&key_config, InputId::from(knob_mapping.2)), knob_counter_clockwise_config, + current_key_config(&key_config, InputId::from(knob_mapping.3)), knob_pressed_config ] } diff --git a/config-frontend/src/views/config/saved_config_display.rs b/config-frontend/src/views/config/saved_config_display.rs new file mode 100644 index 0000000..e0d4a3f --- /dev/null +++ b/config-frontend/src/views/config/saved_config_display.rs @@ -0,0 +1,50 @@ +use crate::messages::Messages; +use iced::widget; +use iced::widget::column; +use messaging::protos::display_zones::DisplayZone; +use messaging::protos::inputs::InputId; +use messaging::protos::key_config::KeyConfig; +use messaging::protos::server_config::DisplayImage; + +pub fn current_image<'a>( + images: &[DisplayImage], + display_zone: DisplayZone, +) -> iced::Element<'a, Messages> { + let current_image = images.iter().find(|image| { + image + .display_zone + .enum_value_or(DisplayZone::DISPLAY_ZONE_UNSPECIFIED) + == display_zone + }); + let display_text = widget::text!( + "{}", + current_image.map_or("Not specified", |image| { image.path.as_str() }) + ); + + if let Some(display_image) = current_image { + let image = widget::image(display_image.path.as_str()); + return column![display_text, image].into(); + } + + column![display_text].into() +} + +pub fn current_key_config<'a>( + key_configs: &[KeyConfig], + input_action: InputId, +) -> iced::Element<'a, Messages> { + let current_key_config = key_configs.iter().find(|key_config| { + key_config + .input_id + .enum_value_or(InputId::INPUT_ACTION_UNSPECIFIED) + == input_action + }); + + widget::text!( + "{}", + current_key_config.map_or(String::from("Not specified"), |key_config| { + format!("{:?}", key_config.actions) + }) + ) + .into() +} diff --git a/config-frontend/src/views/config/touchscreen.rs b/config-frontend/src/views/config/touchscreen.rs index f96ea4d..5d559fd 100644 --- a/config-frontend/src/views/config/touchscreen.rs +++ b/config-frontend/src/views/config/touchscreen.rs @@ -1,9 +1,13 @@ use crate::common::{ConfigurableZones, ExtraConfigMode, TouchscreenInput, TouchscreenZoneInput}; use crate::messages::Messages; use crate::views::config::TOUCHSCREEN_ZONES; +use crate::views::config::saved_config_display::{current_image, current_key_config}; use iced::widget; use iced::widget::row; use messaging::protos::display_zones::DisplayZone; +use messaging::protos::inputs::InputId; +use messaging::protos::key_config::KeyConfig; +use messaging::protos::server_config::DisplayImage; pub fn touchscreen_zones_row<'a>() -> widget::Row<'a, Messages> { (0..TOUCHSCREEN_ZONES).fold(row![], |row, i| { @@ -36,6 +40,8 @@ pub fn touchscreen_zones_row<'a>() -> widget::Row<'a, Messages> { pub fn touchscreen_zone_config_settings<'a>( zone: ConfigurableZones, + image_config: Vec, + key_config: Vec, ) -> widget::Column<'a, Messages> { let touchscreen_zone_mapping = match zone { ConfigurableZones::Touchscreen1(_) => ( @@ -70,7 +76,7 @@ pub fn touchscreen_zone_config_settings<'a>( let key_mapping_config_button = widget::button("On pressed").on_press(Messages::OpenInputMappingConfigurationPanel( - touchscreen_zone_mapping.2, + touchscreen_zone_mapping.2.clone(), ExtraConfigMode::KeyRecording, )); @@ -80,7 +86,9 @@ pub fn touchscreen_zone_config_settings<'a>( iced::widget::column![ widget::text!("{} config", touchscreen_zone_mapping.0), display_zone_config, + current_key_config(&key_config, InputId::from(touchscreen_zone_mapping.2)), key_mapping_config_button, + current_image(&image_config, touchscreen_zone_mapping.1), clear_image_button ] } @@ -92,7 +100,7 @@ pub fn touchscreen_extra<'a>() -> widget::Row<'a, Messages> { row![button] } -pub fn touchscreen_swipe_settings<'a>() -> widget::Column<'a, Messages> { +pub fn touchscreen_swipe_settings<'a>(key_config: Vec) -> widget::Column<'a, Messages> { let left_swipe_config_button = widget::button("On left swipe").on_press(Messages::OpenInputMappingConfigurationPanel( ConfigurableZones::TouchscreenExtra(TouchscreenInput::SwipeLeft), @@ -105,5 +113,10 @@ pub fn touchscreen_swipe_settings<'a>() -> widget::Column<'a, Messages> { ExtraConfigMode::KeyRecording, )); - iced::widget::column![row![left_swipe_config_button, right_swipe_config_button]] + iced::widget::column![row![ + current_key_config(&key_config, InputId::TOUCHSCREEN_SWIPED_LEFT), + left_swipe_config_button, + current_key_config(&key_config, InputId::TOUCHSCREEN_SWIPED_RIGHT), + right_swipe_config_button + ]] } diff --git a/firmware-api/src/display_zones.rs b/firmware-api/src/display_zones.rs index a1e6887..e5553f1 100644 --- a/firmware-api/src/display_zones.rs +++ b/firmware-api/src/display_zones.rs @@ -59,12 +59,12 @@ impl TryFrom for DisplayZones { 12 => DisplayZones::Button2, 13 => DisplayZones::Button3, 14 => DisplayZones::Button4, - 15 => DisplayZones::Button5, - 16 => DisplayZones::Button6, - 17 => DisplayZones::Button7, - 18 => DisplayZones::Button8, - 19 => DisplayZones::Button9, - 20 => DisplayZones::Button10, + 5 => DisplayZones::Button5, + 6 => DisplayZones::Button6, + 7 => DisplayZones::Button7, + 8 => DisplayZones::Button8, + 9 => DisplayZones::Button9, + 10 => DisplayZones::Button10, 1 => DisplayZones::Touchscreen1, 2 => DisplayZones::Touchscreen2,