diff --git a/RustApp/.cargo/config.toml b/RustApp/.cargo/config.toml index 34effef2..2ecdb8a8 100644 --- a/RustApp/.cargo/config.toml +++ b/RustApp/.cargo/config.toml @@ -1,5 +1,5 @@ [env] -RUST_LOG = "warn,android_mic=info" +RUST_LOG = "error,android_mic=info" ANDROID_MIC_COMMIT = "undefined" # ANDROID_MIC_FORMAT = "flatpak" # RUST_LOG = "info" diff --git a/RustApp/i18n/en/android_mic.ftl b/RustApp/i18n/en/android_mic.ftl index 59b7dff2..56546399 100644 --- a/RustApp/i18n/en/android_mic.ftl +++ b/RustApp/i18n/en/android_mic.ftl @@ -47,6 +47,7 @@ reset_denoise_settings = Reset Denoise Settings title_app = App start_at_login = Start at login +start_minimized = Start minimized auto_connect = Auto connect theme = Theme amplify = Amplify diff --git a/RustApp/src/audio/denoise_rnnoise.rs b/RustApp/src/audio/denoise_rnnoise.rs index 13488476..932f2ca6 100644 --- a/RustApp/src/audio/denoise_rnnoise.rs +++ b/RustApp/src/audio/denoise_rnnoise.rs @@ -28,7 +28,7 @@ pub fn denoise_f32_stream(data: &[Vec]) -> anyhow::Result>> { // Convert f32 to i16 range let data_i16: Vec> = data .iter() - .map(|channel| channel.iter().map(|&x| (x * i16::MAX as f32)).collect()) + .map(|channel| channel.iter().map(|&x| x * i16::MAX as f32).collect()) .collect(); // Append new data into the cache diff --git a/RustApp/src/audio/process.rs b/RustApp/src/audio/process.rs index 75a195f9..a4cdb7a2 100644 --- a/RustApp/src/audio/process.rs +++ b/RustApp/src/audio/process.rs @@ -15,7 +15,10 @@ impl AudioStream { /// This function converts an audio stream from packet into producer /// apply any necessary conversions based on the audio format /// and returns mono channel f32 vector for audio wave display - pub fn process_audio_packet(&mut self, packet: AudioPacketMessage) -> anyhow::Result> { + pub fn process_audio_packet( + &mut self, + packet: AudioPacketMessage, + ) -> anyhow::Result>> { match self.audio_params.target_format.audio_format { AudioFormat::I16 => self.process_audio_packet_internal::(packet), AudioFormat::I24 => self.process_audio_packet_internal::(packet), @@ -32,7 +35,7 @@ impl AudioStream { fn process_audio_packet_internal( &mut self, packet: AudioPacketMessage, - ) -> anyhow::Result> + ) -> anyhow::Result>> where F: cpal::SizedSample + AudioBytes + std::fmt::Debug + 'static, { @@ -130,18 +133,22 @@ impl AudioStream { } } - // prepare mono channel buffer to return - let buffer_mono = if config.target_format.channel_count.to_number() == 1 { - buffer[0].clone() - } else { - // if not mono, average the channels - let mut mono_buffer: Vec = Vec::with_capacity(buffer[0].len()); - for i in 0..buffer[0].len() { - let sample: f32 = buffer.iter().map(|ch| ch[i]).sum::() - / config.target_format.channel_count.to_number() as f32; - mono_buffer.push(sample); + let buffer_mono = if self.is_window_visible { + // prepare mono channel buffer to return + if config.target_format.channel_count.to_number() == 1 { + Some(buffer[0].clone()) + } else { + // if not mono, average the channels + let mut mono_buffer: Vec = Vec::with_capacity(buffer[0].len()); + for i in 0..buffer[0].len() { + let sample: f32 = buffer.iter().map(|ch| ch[i]).sum::() + / config.target_format.channel_count.to_number() as f32; + mono_buffer.push(sample); + } + Some(mono_buffer) } - mono_buffer + } else { + None }; Ok(buffer_mono) diff --git a/RustApp/src/config.rs b/RustApp/src/config.rs index cce855be..36ce03db 100644 --- a/RustApp/src/config.rs +++ b/RustApp/src/config.rs @@ -2,6 +2,7 @@ use std::{fmt::Display, net::IpAddr}; use clap::Parser; use light_enum::Values; +use local_ip_address::local_ip; use serde::{Deserialize, Serialize}; use crate::fl; @@ -19,15 +20,20 @@ pub struct Config { // device that could be disconnected sometime. pub device_name: Option, pub start_at_login: bool, + pub start_minimized: bool, pub auto_connect: bool, pub denoise: bool, pub denoise_kind: DenoiseKind, + /// range: [-100, 0] pub speex_noise_suppress: i32, pub speex_vad_enabled: bool, + /// range: [0, 100] pub speex_vad_threshold: u32, pub speex_agc_enabled: bool, + /// range: [8000, 65535] pub speex_agc_target: u32, pub speex_dereverb_enabled: bool, + /// range: [0.0, 1.0] pub speex_dereverb_level: f32, pub theme: AppTheme, pub amplify: bool, @@ -68,13 +74,14 @@ impl Default for Config { theme: Default::default(), amplify: false, amplify_value: 2.0, - speex_noise_suppress: -30, // range: [-100, 0] + speex_noise_suppress: -30, speex_vad_enabled: false, - speex_vad_threshold: 80, // range: [0, 100] + speex_vad_threshold: 80, speex_agc_enabled: false, - speex_agc_target: 8000, // range: [8000, 65535] + speex_agc_target: 8000, speex_dereverb_enabled: false, - speex_dereverb_level: 0.5, // range: [0.0, 1.0] + speex_dereverb_level: 0.5, + start_minimized: false, } } } @@ -89,6 +96,10 @@ impl Config { self.speex_dereverb_enabled = false; self.speex_dereverb_level = 0.5; } + + pub fn ip_or_default(&self) -> Option { + self.ip.or(local_ip().ok()) + } } #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Values)] diff --git a/RustApp/src/streamer/adb_streamer.rs b/RustApp/src/streamer/adb_streamer.rs index e6167081..a89c07c7 100644 --- a/RustApp/src/streamer/adb_streamer.rs +++ b/RustApp/src/streamer/adb_streamer.rs @@ -1,7 +1,10 @@ use anyhow::Result; use tokio::process::Command; -use crate::streamer::{StreamerMsg, tcp_streamer}; +use crate::{ + config::ConnectionMode, + streamer::{StreamerMsg, tcp_streamer}, +}; use super::{ AudioStream, ConnectError, StreamerTrait, @@ -108,6 +111,7 @@ impl StreamerTrait for AdbStreamer { TcpStreamerState::Streaming { .. } => StreamerMsg::Connected { ip: None, port: None, + mode: ConnectionMode::Adb, }, } } diff --git a/RustApp/src/streamer/mod.rs b/RustApp/src/streamer/mod.rs index e3685fb2..68d217b1 100644 --- a/RustApp/src/streamer/mod.rs +++ b/RustApp/src/streamer/mod.rs @@ -36,11 +36,20 @@ const MAX_PORT: u16 = 60000; pub struct AudioStream { pub buff: Producer, pub audio_params: AudioProcessParams, + pub is_window_visible: bool, } impl AudioStream { - pub fn new(buff: Producer, audio_params: AudioProcessParams) -> Self { - Self { buff, audio_params } + pub fn new( + buff: Producer, + audio_params: AudioProcessParams, + is_window_visible: bool, + ) -> Self { + Self { + buff, + audio_params, + is_window_visible, + } } } @@ -48,6 +57,7 @@ impl Debug for AudioStream { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("AudioStream") .field("audio_params", &self.audio_params) + .field("is_window_visible", &self.is_window_visible) .finish() } } diff --git a/RustApp/src/streamer/streamer_runner.rs b/RustApp/src/streamer/streamer_runner.rs index ecfe11f9..e666f111 100644 --- a/RustApp/src/streamer/streamer_runner.rs +++ b/RustApp/src/streamer/streamer_runner.rs @@ -12,6 +12,7 @@ use std::net::IpAddr; use tokio::sync::mpsc::{self, Sender}; use crate::audio::AudioProcessParams; +use crate::config::ConnectionMode; use crate::streamer::{StreamerTrait, WriteError}; use super::{AudioStream, ConnectError, DummyStreamer, Streamer, tcp_streamer, udp_streamer}; @@ -36,10 +37,12 @@ pub enum StreamerCommand { connect_options: ConnectOption, buff: Producer, audio_params: AudioProcessParams, + is_window_visible: bool, }, ReconfigureStream { buff: Producer, audio_params: AudioProcessParams, + is_window_visible: bool, }, Stop, } @@ -51,17 +54,21 @@ impl Debug for StreamerCommand { connect_options, buff: _, audio_params, + is_window_visible, } => f .debug_struct("Connect") .field("connect_options", connect_options) .field("audio_params", audio_params) + .field("is_window_visible", is_window_visible) .finish(), Self::ReconfigureStream { buff: _, audio_params, + is_window_visible, } => f .debug_struct("ReconfigureStream") .field("audio_params", audio_params) + .field("is_window_visible", is_window_visible) .finish(), Self::Stop => write!(f, "Stop"), } @@ -82,6 +89,7 @@ pub enum StreamerMsg { Connected { ip: Option, port: Option, + mode: ConnectionMode, }, Ready(Sender), } @@ -128,8 +136,10 @@ pub fn sub() -> impl Stream { connect_options, buff, audio_params, + is_window_visible, } => { - let stream_config = AudioStream::new(buff, audio_params); + let stream_config = + AudioStream::new(buff, audio_params, is_window_visible); let new_streamer: Result = match connect_options { ConnectOption::Tcp { ip } => { @@ -167,8 +177,13 @@ pub fn sub() -> impl Stream { } } } - StreamerCommand::ReconfigureStream { buff, audio_params } => { - let stream_config = AudioStream::new(buff, audio_params); + StreamerCommand::ReconfigureStream { + buff, + audio_params, + is_window_visible, + } => { + let stream_config = + AudioStream::new(buff, audio_params, is_window_visible); streamer.reconfigure_stream(stream_config); } diff --git a/RustApp/src/streamer/tcp_streamer.rs b/RustApp/src/streamer/tcp_streamer.rs index d6d6e0a8..b8b068b4 100644 --- a/RustApp/src/streamer/tcp_streamer.rs +++ b/RustApp/src/streamer/tcp_streamer.rs @@ -5,7 +5,10 @@ use prost::Message; use tokio::net::{TcpListener, TcpStream}; use tokio_util::codec::{Framed, LengthDelimitedCodec}; -use crate::streamer::{DEFAULT_PC_PORT, MAX_PORT, StreamerMsg, WriteError}; +use crate::{ + config::ConnectionMode, + streamer::{DEFAULT_PC_PORT, MAX_PORT, StreamerMsg, WriteError}, +}; use super::{AudioPacketMessage, AudioStream, ConnectError, StreamerTrait}; @@ -76,6 +79,7 @@ impl StreamerTrait for TcpStreamer { TcpStreamerState::Streaming { .. } => StreamerMsg::Connected { ip: Some(self.ip), port: Some(self.port), + mode: ConnectionMode::Tcp, }, } } @@ -100,6 +104,7 @@ impl StreamerTrait for TcpStreamer { Ok(Some(StreamerMsg::Connected { ip: Some(self.ip), port: Some(self.port), + mode: ConnectionMode::Tcp, })) } TcpStreamerState::Streaming { @@ -107,34 +112,26 @@ impl StreamerTrait for TcpStreamer { disconnect_loop_detecter: _, } => { match framed.next().await { - Some(Ok(frame)) => { - let mut res = None; - - match AudioPacketMessage::decode(frame) { - Ok(packet) => { - let buffer_size = packet.buffer.len(); - let sample_rate = packet.sample_rate; - - if let Ok(buffer) = self.stream_config.process_audio_packet(packet) - { - // compute the audio wave from the buffer - res = Some(StreamerMsg::UpdateAudioWave { + Some(Ok(frame)) => match AudioPacketMessage::decode(frame) { + Ok(packet) => { + let buffer_size = packet.buffer.len(); + let sample_rate = packet.sample_rate; + + match self.stream_config.process_audio_packet(packet) { + Ok(Some(buffer)) => { + debug!("received {} bytes", buffer_size); + Ok(Some(StreamerMsg::UpdateAudioWave { data: AudioPacketMessage::to_wave_data( &buffer, sample_rate, ), - }); - - debug!("received {} bytes", buffer_size); - }; - } - Err(e) => { - return Err(ConnectError::WriteError(WriteError::Deserializer(e))); + })) + } + _ => Ok(None), } } - - Ok(res) - } + Err(e) => Err(ConnectError::WriteError(WriteError::Deserializer(e))), + }, Some(Err(e)) => { match e.kind() { diff --git a/RustApp/src/streamer/udp_streamer.rs b/RustApp/src/streamer/udp_streamer.rs index 945c8f58..b30a33fa 100644 --- a/RustApp/src/streamer/udp_streamer.rs +++ b/RustApp/src/streamer/udp_streamer.rs @@ -5,7 +5,10 @@ use prost::Message; use tokio::net::UdpSocket; use tokio_util::{codec::LengthDelimitedCodec, udp::UdpFramed}; -use crate::streamer::{AudioPacketMessage, DEFAULT_PC_PORT, MAX_PORT, WriteError}; +use crate::{ + config::ConnectionMode, + streamer::{AudioPacketMessage, DEFAULT_PC_PORT, MAX_PORT, WriteError}, +}; use super::{AudioPacketMessageOrdered, AudioStream, ConnectError, StreamerMsg, StreamerTrait}; @@ -62,14 +65,13 @@ impl StreamerTrait for UdpStreamer { StreamerMsg::Connected { ip: Some(self.ip), port: Some(self.port), + mode: ConnectionMode::Udp, } } async fn next(&mut self) -> Result, ConnectError> { match self.framed.next().await { Some(Ok((frame, addr))) => { - let mut res = None; - match AudioPacketMessageOrdered::decode(frame) { Ok(packet) => { if packet.sequence_number < self.tracked_sequence { @@ -85,21 +87,18 @@ impl StreamerTrait for UdpStreamer { let buffer_size = packet.buffer.len(); let sample_rate = packet.sample_rate; - if let Ok(buffer) = self.stream_config.process_audio_packet(packet) { - // compute the audio wave from the buffer - res = Some(StreamerMsg::UpdateAudioWave { - data: AudioPacketMessage::to_wave_data(&buffer, sample_rate), - }); - - debug!("From {:?}, received {} bytes", addr, buffer_size); + match self.stream_config.process_audio_packet(packet) { + Ok(Some(buffer)) => { + debug!("From {:?}, received {} bytes", addr, buffer_size); + Ok(Some(StreamerMsg::UpdateAudioWave { + data: AudioPacketMessage::to_wave_data(&buffer, sample_rate), + })) + } + _ => Ok(None), } } - Err(e) => { - return Err(ConnectError::WriteError(WriteError::Deserializer(e))); - } + Err(e) => Err(ConnectError::WriteError(WriteError::Deserializer(e))), } - - Ok(res) } Some(Err(e)) => { diff --git a/RustApp/src/streamer/usb_streamer.rs b/RustApp/src/streamer/usb_streamer.rs index ba035d9e..a88f29e7 100644 --- a/RustApp/src/streamer/usb_streamer.rs +++ b/RustApp/src/streamer/usb_streamer.rs @@ -12,7 +12,7 @@ use super::{ frame::UsbStream, }, }; -use crate::streamer::WriteError; +use crate::{config::ConnectionMode, streamer::WriteError}; use super::{AudioPacketMessage, ConnectError, StreamerMsg, StreamerTrait}; @@ -185,6 +185,7 @@ impl StreamerTrait for UsbStreamer { UsbStreamerState::Streaming => StreamerMsg::Connected { ip: None, port: None, + mode: ConnectionMode::Usb, }, } } @@ -194,27 +195,23 @@ impl StreamerTrait for UsbStreamer { Some(Ok(frame)) => { self.state = UsbStreamerState::Streaming; - let mut res = None; match AudioPacketMessage::decode(frame) { Ok(packet) => { let buffer_size = packet.buffer.len(); let sample_rate = packet.sample_rate; - if let Ok(buffer) = self.stream_config.process_audio_packet(packet) { - // compute the audio wave from the buffer - res = Some(StreamerMsg::UpdateAudioWave { - data: AudioPacketMessage::to_wave_data(&buffer, sample_rate), - }); - - debug!("received {} bytes", buffer_size); + match self.stream_config.process_audio_packet(packet) { + Ok(Some(buffer)) => { + debug!("received {} bytes", buffer_size); + Ok(Some(StreamerMsg::UpdateAudioWave { + data: AudioPacketMessage::to_wave_data(&buffer, sample_rate), + })) + } + _ => Ok(None), } } - Err(e) => { - return Err(ConnectError::WriteError(WriteError::Deserializer(e))); - } + Err(e) => Err(ConnectError::WriteError(WriteError::Deserializer(e))), } - - Ok(res) } Some(Err(e)) => { panic!("{}", e); diff --git a/RustApp/src/ui/app.rs b/RustApp/src/ui/app.rs index 47b5c256..ce88254c 100644 --- a/RustApp/src/ui/app.rs +++ b/RustApp/src/ui/app.rs @@ -7,7 +7,7 @@ use cpal::{ Device, Host, traits::{DeviceTrait, HostTrait}, }; -use local_ip_address::{list_afinet_netifas, local_ip}; +use local_ip_address::list_afinet_netifas; use notify_rust::Notification; use rtrb::RingBuffer; use tokio::sync::mpsc::Sender; @@ -160,6 +160,7 @@ impl AppState { self.send_command(StreamerCommand::ReconfigureStream { buff: producer, audio_params: AudioProcessParams::new(audio_config, config), + is_window_visible: self.main_window.is_some(), }); Task::none() @@ -202,11 +203,22 @@ impl AppState { let connect_options = match config.connection_mode { ConnectionMode::Tcp => { - let ip = config.ip.unwrap_or(local_ip().unwrap()); + let Some(ip) = config.ip_or_default() else { + let e = "no address ip found"; + + error!("failed to start audio stream: {e}"); + return self.add_log(e); + }; + ConnectOption::Tcp { ip } } ConnectionMode::Udp => { - let ip = config.ip.unwrap_or(local_ip().unwrap()); + let Some(ip) = config.ip_or_default() else { + let e = "no address ip found"; + + error!("failed to start audio stream: {e}"); + return self.add_log(e); + }; ConnectOption::Udp { ip } } #[cfg(feature = "adb")] @@ -221,6 +233,7 @@ impl AppState { connect_options, buff: producer, audio_params: AudioProcessParams::new(audio_config, config), + is_window_visible: self.main_window.is_some(), }); Task::none() @@ -238,6 +251,26 @@ impl AppState { Task::none() } + + fn open_main_window(&mut self) -> Task { + let mut commands = Vec::new(); + let settings = window::Settings { + size: Size::new(800.0, 600.0), + position: window::Position::Centered, + icon: window_icon!("icon"), + ..Default::default() + }; + + let (window_id, command) = cosmic::iced::window::open(settings); + + commands.push(command.map(|_| cosmic::action::Action::None)); + + commands.push(self.set_window_title(fl!("main_window_title"), window_id)); + + self.main_window = Some(CustomWindow { window_id }); + + Task::batch(commands) + } } pub struct Flags { @@ -308,7 +341,7 @@ impl Application for AppState { ip: *ip, }) .collect::>(); - let network_adapter = match &flags.config.data().ip { + let network_adapter = match &flags.config.data().ip_or_default() { Some(ip) => match network_adapters.iter().find(|adapter| adapter.ip == *ip) { Some(adapter) => Some(adapter.clone()), None => { @@ -331,20 +364,8 @@ impl Application for AppState { } }; - // configure window settings - let settings = window::Settings { - size: Size::new(800.0, 600.0), - position: window::Position::Centered, - icon: window_icon!("icon"), - ..Default::default() - }; - let mut commands = Vec::new(); - let (new_id, command) = cosmic::iced::window::open(settings); - - commands.push(command.map(|_| cosmic::action::Action::None)); - let mut app = Self { core, audio_stream: None, @@ -357,7 +378,7 @@ impl Application for AppState { connection_state: ConnectionState::Default, network_adapters, network_adapter, - main_window: Some(CustomWindow { window_id: new_id }), + main_window: None, settings_window: None, about_window: None, logs: Vec::new(), @@ -382,7 +403,9 @@ impl Application for AppState { info!("config path: {}", flags.config_path); info!("log path: {}", flags.log_path); - commands.push(app.set_window_title(fl!("main_window_title"), new_id)); + if !app.config.data().start_minimized { + commands.push(app.open_main_window()); + } (app, Task::batch(commands)) } @@ -447,7 +470,7 @@ impl Application for AppState { return self.add_log(format!("Listening on `{ip}:{port}`").as_str()); } } - StreamerMsg::Connected { ip, port } => { + StreamerMsg::Connected { ip, port, mode } => { if let Some(system_tray) = self.system_tray.as_mut() { system_tray.update_menu_state(false, &fl!("state_connected")); } @@ -458,15 +481,18 @@ impl Application for AppState { ip.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), port.unwrap_or_default() ); - // show notification when app is minimized - let _ = Notification::new() - .summary("AndroidMic") - .body(format!("Connected on {address}").as_str()) - .auto_icon() - .show() - .map_err(|e| { - error!("failed to show notification: {e}"); - }); + + if mode != ConnectionMode::Udp { + // show notification when app is minimized + let _ = Notification::new() + .summary("AndroidMic") + .body(format!("Connected on {address}").as_str()) + .auto_icon() + .show() + .map_err(|e| { + error!("failed to show notification: {e}"); + }); + } } self.connection_state = ConnectionState::Connected; @@ -650,6 +676,9 @@ impl Application for AppState { .update(|c| c.speex_dereverb_level = speex_dereverb_level); return self.update_audio_stream(); } + ConfigMsg::StartMinimized(start_minimized) => { + self.config.update(|s| s.start_minimized = start_minimized); + } }, AppMsg::HideWindow => { let mut effects = Vec::new(); @@ -679,7 +708,7 @@ impl Application for AppState { self.about_window = None; } - if !self.has_shown_minimize_notification { + if !self.config.data().start_minimized && !self.has_shown_minimize_notification { let _ = Notification::new() .summary("AndroidMic") .body(&fl!("minimized_to_tray")) @@ -691,6 +720,8 @@ impl Application for AppState { self.has_shown_minimize_notification = true; } + effects.push(self.update_audio_stream()); + return cosmic::iced_runtime::Task::batch(effects); } AppMsg::Menu(menu_msg) => match menu_msg { @@ -721,26 +752,9 @@ impl Application for AppState { )), ); } else { - let settings = window::Settings { - size: Size::new(800.0, 600.0), - position: window::Position::Centered, - icon: window_icon!("icon"), - ..Default::default() - }; - - let (new_id, command) = cosmic::iced::window::open(settings); - self.main_window = Some(CustomWindow { window_id: new_id }); - let set_window_title = - self.set_window_title(fl!("main_window_title"), new_id); + let command = self.open_main_window(); - return command - .map(|_| cosmic::action::Action::None) - .chain(set_window_title) - .chain(cosmic::iced_runtime::task::effect( - cosmic::iced::runtime::Action::Window(window::Action::GainFocus( - new_id, - )), - )); + return Task::batch(vec![command, self.update_audio_stream()]); } } SystemTrayMsg::Exit => { diff --git a/RustApp/src/ui/message.rs b/RustApp/src/ui/message.rs index c0b1030d..136ff038 100644 --- a/RustApp/src/ui/message.rs +++ b/RustApp/src/ui/message.rs @@ -34,6 +34,7 @@ pub enum ConfigMsg { UseRecommendedFormat, ResetDenoiseSettings, StartAtLogin(bool), + StartMinimized(bool), AutoConnect(bool), DeNoise(bool), DeNoiseKind(DenoiseKind), diff --git a/RustApp/src/ui/tray.rs b/RustApp/src/ui/tray.rs index a75f8e2b..37163008 100644 --- a/RustApp/src/ui/tray.rs +++ b/RustApp/src/ui/tray.rs @@ -77,11 +77,10 @@ impl SystemTray { })); let tray_sender = sender.clone(); - TrayIconEvent::set_event_handler(Some(move |event: TrayIconEvent| match event { - TrayIconEvent::DoubleClick { .. } => { + TrayIconEvent::set_event_handler(Some(move |event: TrayIconEvent| { + if let TrayIconEvent::DoubleClick { .. } = event { let _ = tray_sender.send(SystemTrayMsg::Show); } - _ => {} })); Ok(( diff --git a/RustApp/src/ui/view.rs b/RustApp/src/ui/view.rs index 26f55a79..6a085e28 100644 --- a/RustApp/src/ui/view.rs +++ b/RustApp/src/ui/view.rs @@ -424,7 +424,10 @@ pub fn settings_window(app: &AppState) -> Element<'_, ConfigMsg> { ) })), ) - .push(button::text(fl!("reset_denoise_settings")).on_press(ConfigMsg::ResetDenoiseSettings)) + .push( + button::text(fl!("reset_denoise_settings")) + .on_press(ConfigMsg::ResetDenoiseSettings), + ) .push( settings::section() .title(fl!("title_app")) @@ -442,6 +445,16 @@ pub fn settings_window(app: &AppState) -> Element<'_, ConfigMsg> { } else { None }) + .add( + row() + .align_y(Vertical::Center) + .push(text(fl!("start_minimized"))) + .push(horizontal_space()) + .push( + toggler(config.start_minimized) + .on_toggle(ConfigMsg::StartMinimized), + ), + ) .add( row() .align_y(Vertical::Center) @@ -460,10 +473,9 @@ pub fn settings_window(app: &AppState) -> Element<'_, ConfigMsg> { ConfigMsg::Theme, )), ) - .add( - widget::settings::item::builder(fl!("about")) - .control(button::text(fl!("about_open")).on_press(ConfigMsg::ToggleAboutWindow)), - ), + .add(widget::settings::item::builder(fl!("about")).control( + button::text(fl!("about_open")).on_press(ConfigMsg::ToggleAboutWindow), + )), ), ) .into()