1use crate::settings_ui::{SettingsUI, UpdateCheckResult};
7use anyhow::{Context, Result};
8use par_term_config::Config;
9
10pub use crate::settings_ui::SettingsWindowAction;
13use std::sync::Arc;
14use wgpu::SurfaceError;
15use winit::event::WindowEvent;
16use winit::event_loop::ActiveEventLoop;
17use winit::keyboard::{Key, NamedKey};
18use winit::window::{Window, WindowId};
19
20pub struct SettingsWindow {
22 window: Arc<Window>,
24 window_id: WindowId,
26 surface: wgpu::Surface<'static>,
28 device: Arc<wgpu::Device>,
30 queue: Arc<wgpu::Queue>,
32 surface_config: wgpu::SurfaceConfiguration,
34 egui_ctx: egui::Context,
36 egui_state: egui_winit::State,
38 egui_renderer: egui_wgpu::Renderer,
40 pub settings_ui: SettingsUI,
42 ready: bool,
44 should_close: bool,
46 pending_paste: Option<String>,
48 pending_events: Vec<egui::Event>,
50}
51
52impl SettingsWindow {
53 pub async fn new(
55 event_loop: &ActiveEventLoop,
56 config: Config,
57 supported_vsync_modes: Vec<crate::config::VsyncMode>,
58 ) -> Result<Self> {
59 let window_attrs = Window::default_attributes()
61 .with_title("Settings")
62 .with_inner_size(winit::dpi::LogicalSize::new(700, 800))
63 .with_min_inner_size(winit::dpi::LogicalSize::new(500, 400))
64 .with_resizable(true);
65
66 let window = Arc::new(event_loop.create_window(window_attrs)?);
67 let window_id = window.id();
68 let size = window.inner_size();
69
70 #[cfg(target_os = "windows")]
73 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
74 backends: wgpu::Backends::DX12,
75 ..Default::default()
76 });
77 #[cfg(target_os = "macos")]
78 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
79 backends: wgpu::Backends::all(),
80 ..Default::default()
81 });
82 #[cfg(target_os = "linux")]
83 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
84 backends: wgpu::Backends::VULKAN | wgpu::Backends::GL,
85 ..Default::default()
86 });
87
88 let surface = instance.create_surface(window.clone())?;
90
91 let adapter = instance
93 .request_adapter(&wgpu::RequestAdapterOptions {
94 power_preference: wgpu::PowerPreference::LowPower,
95 compatible_surface: Some(&surface),
96 force_fallback_adapter: false,
97 })
98 .await
99 .context("Failed to find suitable GPU adapter")?;
100
101 let (device, queue) = adapter
103 .request_device(&wgpu::DeviceDescriptor::default())
104 .await?;
105
106 let device = Arc::new(device);
107 let queue = Arc::new(queue);
108
109 let surface_caps = surface.get_capabilities(&adapter);
111 let surface_format = surface_caps
112 .formats
113 .iter()
114 .find(|f| f.is_srgb())
115 .copied()
116 .unwrap_or(surface_caps.formats[0]);
117
118 let alpha_mode = if surface_caps
120 .alpha_modes
121 .contains(&wgpu::CompositeAlphaMode::PreMultiplied)
122 {
123 wgpu::CompositeAlphaMode::PreMultiplied
124 } else if surface_caps
125 .alpha_modes
126 .contains(&wgpu::CompositeAlphaMode::PostMultiplied)
127 {
128 wgpu::CompositeAlphaMode::PostMultiplied
129 } else if surface_caps
130 .alpha_modes
131 .contains(&wgpu::CompositeAlphaMode::Auto)
132 {
133 wgpu::CompositeAlphaMode::Auto
134 } else {
135 surface_caps.alpha_modes[0]
136 };
137
138 let surface_config = wgpu::SurfaceConfiguration {
139 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
140 format: surface_format,
141 width: size.width.max(1),
142 height: size.height.max(1),
143 present_mode: wgpu::PresentMode::AutoVsync,
144 alpha_mode,
145 view_formats: vec![],
146 desired_maximum_frame_latency: 2,
147 };
148 surface.configure(&device, &surface_config);
149
150 let scale_factor = window.scale_factor() as f32;
152 let egui_ctx = egui::Context::default();
153 let egui_state = egui_winit::State::new(
154 egui_ctx.clone(),
155 egui::ViewportId::ROOT,
156 &window,
157 Some(scale_factor),
158 None,
159 None,
160 );
161
162 let egui_renderer = egui_wgpu::Renderer::new(
164 &device,
165 surface_format,
166 egui_wgpu::RendererOptions {
167 msaa_samples: 1,
168 depth_stencil_format: None,
169 dithering: false,
170 predictable_texture_filtering: false,
171 },
172 );
173
174 let mut settings_ui = SettingsUI::new(config);
176 settings_ui.visible = true; settings_ui.update_supported_vsync_modes(supported_vsync_modes);
178
179 Ok(Self {
180 window,
181 window_id,
182 surface,
183 device,
184 queue,
185 surface_config,
186 egui_ctx,
187 egui_state,
188 egui_renderer,
189 settings_ui,
190 ready: true,
191 should_close: false,
192 pending_paste: None,
193 pending_events: Vec::new(),
194 })
195 }
196
197 pub fn window_id(&self) -> WindowId {
199 self.window_id
200 }
201
202 pub fn should_close(&self) -> bool {
204 self.should_close
205 }
206
207 pub fn inject_paste(&mut self, text: String) {
212 self.pending_paste = Some(text);
213 self.window.request_redraw();
214 }
215
216 pub fn inject_event(&mut self, event: egui::Event) {
221 self.pending_events.push(event);
222 self.window.request_redraw();
223 }
224
225 pub fn update_config(&mut self, config: Config) {
227 self.settings_ui.update_config(config);
228 }
229
230 pub fn force_update_config(&mut self, config: Config) {
234 self.settings_ui.force_update_config(config);
235 }
236
237 pub fn set_shader_error(&mut self, error: Option<String>) {
239 self.settings_ui.set_shader_error(error);
240 }
241
242 pub fn set_cursor_shader_error(&mut self, error: Option<String>) {
244 self.settings_ui.set_cursor_shader_error(error);
245 }
246
247 pub fn clear_shader_error(&mut self) {
249 self.settings_ui.clear_shader_error();
250 }
251
252 pub fn clear_cursor_shader_error(&mut self) {
254 self.settings_ui.clear_cursor_shader_error();
255 }
256
257 pub fn sync_shader_states(&mut self, custom_shader_enabled: bool, cursor_shader_enabled: bool) {
260 self.settings_ui.config.custom_shader_enabled = custom_shader_enabled;
261 self.settings_ui.config.cursor_shader_enabled = cursor_shader_enabled;
262 }
263
264 pub fn handle_window_event(&mut self, event: WindowEvent) -> SettingsWindowAction {
266 let event_response = self.egui_state.on_window_event(&self.window, &event);
268
269 match event {
270 WindowEvent::CloseRequested => {
271 self.should_close = true;
272 return SettingsWindowAction::Close;
273 }
274
275 WindowEvent::Resized(new_size) => {
276 if new_size.width > 0 && new_size.height > 0 {
277 self.surface_config.width = new_size.width;
278 self.surface_config.height = new_size.height;
279 self.surface.configure(&self.device, &self.surface_config);
280 self.window.request_redraw();
281 }
282 }
283
284 WindowEvent::KeyboardInput { event, .. } => {
285 if !event_response.consumed
287 && event.state.is_pressed()
288 && matches!(event.logical_key, Key::Named(NamedKey::Escape))
289 {
290 if !self.settings_ui.shader_editor_visible
292 && !self.settings_ui.cursor_shader_editor_visible
293 {
294 self.should_close = true;
295 return SettingsWindowAction::Close;
296 }
297 }
298 }
299
300 WindowEvent::RedrawRequested => {
301 return self.render();
302 }
303
304 _ => {}
305 }
306
307 if event_response.repaint {
309 self.window.request_redraw();
310 }
311
312 SettingsWindowAction::None
313 }
314
315 fn render(&mut self) -> SettingsWindowAction {
317 if !self.ready {
318 return SettingsWindowAction::None;
319 }
320
321 let output = match self.surface.get_current_texture() {
323 Ok(output) => output,
324 Err(SurfaceError::Lost | SurfaceError::Outdated) => {
325 self.surface.configure(&self.device, &self.surface_config);
326 return SettingsWindowAction::None;
327 }
328 Err(SurfaceError::Timeout) => {
329 log::warn!("Settings window surface timeout");
330 return SettingsWindowAction::None;
331 }
332 Err(e) => {
333 log::error!("Settings window surface error: {:?}", e);
334 return SettingsWindowAction::None;
335 }
336 };
337
338 let view = output
339 .texture
340 .create_view(&wgpu::TextureViewDescriptor::default());
341
342 let mut config_to_save = None;
344 let mut config_for_live = None;
345 let mut shader_apply = None;
346 let mut cursor_shader_apply = None;
347
348 let mut raw_input = self.egui_state.take_egui_input(&self.window);
350
351 if let Some(text) = self.pending_paste.take() {
353 raw_input.events.push(egui::Event::Paste(text));
354 }
355 raw_input.events.append(&mut self.pending_events);
356
357 let egui_output = self.egui_ctx.run(raw_input, |ctx| {
358 let (save, live, shader, cursor_shader) = self.settings_ui.show_as_panel(ctx);
360 config_to_save = save;
361 config_for_live = live;
362 shader_apply = shader;
363 cursor_shader_apply = cursor_shader;
364 });
365
366 for cmd in &egui_output.platform_output.commands {
370 match cmd {
371 egui::OutputCommand::CopyText(text) => {
372 if let Ok(mut clipboard) = arboard::Clipboard::new()
373 && let Err(e) = clipboard.set_text(text)
374 {
375 log::warn!("Settings window: failed to copy to clipboard: {}", e);
376 }
377 }
378 egui::OutputCommand::CopyImage(_) => {}
379 _ => {}
380 }
381 }
382 self.egui_state
383 .handle_platform_output(&self.window, egui_output.platform_output.clone());
384
385 let paint_jobs = self
387 .egui_ctx
388 .tessellate(egui_output.shapes, self.egui_ctx.pixels_per_point());
389
390 for (id, delta) in &egui_output.textures_delta.set {
392 self.egui_renderer
393 .update_texture(&self.device, &self.queue, *id, delta);
394 }
395
396 let mut encoder = self
398 .device
399 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
400 label: Some("Settings Window Encoder"),
401 });
402
403 let screen_descriptor = egui_wgpu::ScreenDescriptor {
405 size_in_pixels: [self.surface_config.width, self.surface_config.height],
406 pixels_per_point: self.window.scale_factor() as f32,
407 };
408
409 self.egui_renderer.update_buffers(
411 &self.device,
412 &self.queue,
413 &mut encoder,
414 &paint_jobs,
415 &screen_descriptor,
416 );
417
418 {
420 let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
421 label: Some("Settings Window Render Pass"),
422 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
423 view: &view,
424 resolve_target: None,
425 ops: wgpu::Operations {
426 load: wgpu::LoadOp::Clear(wgpu::Color {
427 r: 0.094,
428 g: 0.094,
429 b: 0.094,
430 a: 1.0,
431 }),
432 store: wgpu::StoreOp::Store,
433 },
434 depth_slice: None,
435 })],
436 depth_stencil_attachment: None,
437 timestamp_writes: None,
438 occlusion_query_set: None,
439 });
440
441 let mut render_pass = render_pass.forget_lifetime();
443
444 self.egui_renderer
445 .render(&mut render_pass, &paint_jobs, &screen_descriptor);
446 } self.queue.submit(std::iter::once(encoder.finish()));
450 output.present();
451
452 for id in &egui_output.textures_delta.free {
454 self.egui_renderer.free_texture(id);
455 }
456
457 if self.settings_ui.take_test_notification_request() {
459 return SettingsWindowAction::TestNotification;
460 }
461
462 if let Some(profiles) = self.settings_ui.take_profile_save_request() {
464 self.window.request_redraw();
465 return SettingsWindowAction::SaveProfiles(profiles);
466 }
467
468 if let Some(id) = self.settings_ui.take_profile_open_request() {
470 self.window.request_redraw();
471 return SettingsWindowAction::OpenProfile(id);
472 }
473
474 if self.settings_ui.open_log_requested {
476 self.settings_ui.open_log_requested = false;
477 return SettingsWindowAction::OpenLogFile;
478 }
479
480 if self.settings_ui.check_now_requested {
482 self.settings_ui.check_now_requested = false;
483 self.window.request_redraw();
484 return SettingsWindowAction::ForceUpdateCheck;
485 }
486
487 if self.settings_ui.update_install_requested {
489 self.settings_ui.update_install_requested = false;
490 if let Some(UpdateCheckResult::UpdateAvailable(ref info)) =
492 self.settings_ui.last_update_result
493 {
494 let version = info
495 .version
496 .strip_prefix('v')
497 .unwrap_or(&info.version)
498 .to_string();
499 self.settings_ui
500 .start_self_update_with(version.clone(), |v| {
501 crate::self_updater::perform_update(v).map(|r| {
502 crate::settings_ui::UpdateResult {
503 old_version: r.old_version,
504 new_version: r.new_version,
505 install_path: r.install_path.display().to_string(),
506 needs_restart: r.needs_restart,
507 }
508 })
509 });
510 self.window.request_redraw();
511 return SettingsWindowAction::InstallUpdate(version);
512 }
513 }
514
515 self.settings_ui.poll_update_install_status();
517
518 if let Some((index, start)) = self.settings_ui.pending_coprocess_actions.pop() {
520 log::info!(
521 "Settings window: popped coprocess action index={} start={}",
522 index,
523 start
524 );
525 self.window.request_redraw();
527 return if start {
528 SettingsWindowAction::StartCoprocess(index)
529 } else {
530 SettingsWindowAction::StopCoprocess(index)
531 };
532 }
533
534 if let Some((index, start)) = self.settings_ui.pending_script_actions.pop() {
536 crate::debug_info!(
537 "SCRIPT",
538 "Settings window: popped script action index={} start={}",
539 index,
540 start
541 );
542 self.window.request_redraw();
543 return if start {
544 SettingsWindowAction::StartScript(index)
545 } else {
546 SettingsWindowAction::StopScript(index)
547 };
548 }
549
550 if let Some(action) = self.settings_ui.pending_arrangement_actions.pop() {
552 self.window.request_redraw();
553 return action;
554 }
555
556 if self.settings_ui.identify_panes_requested {
558 self.settings_ui.identify_panes_requested = false;
559 self.window.request_redraw();
560 return SettingsWindowAction::IdentifyPanes;
561 }
562
563 if let Some(action) = self.settings_ui.shell_integration_action.take() {
565 self.window.request_redraw();
566 return match action {
567 crate::settings_ui::integrations_tab::ShellIntegrationAction::Install => {
568 SettingsWindowAction::InstallShellIntegration
569 }
570 crate::settings_ui::integrations_tab::ShellIntegrationAction::Uninstall => {
571 SettingsWindowAction::UninstallShellIntegration
572 }
573 };
574 }
575
576 if let Some(config) = config_to_save {
578 return SettingsWindowAction::SaveConfig(config);
579 }
580 if let Some(shader_result) = shader_apply {
581 return SettingsWindowAction::ApplyShader(shader_result);
582 }
583 if let Some(cursor_shader_result) = cursor_shader_apply {
584 return SettingsWindowAction::ApplyCursorShader(cursor_shader_result);
585 }
586 if let Some(config) = config_for_live {
587 return SettingsWindowAction::ApplyConfig(config);
588 }
589
590 SettingsWindowAction::None
591 }
592
593 pub fn request_redraw(&self) {
595 self.window.request_redraw();
596 }
597
598 pub fn focus(&self) {
600 self.window.focus_window();
601 self.window.request_redraw();
602 }
603
604 pub fn is_focused(&self) -> bool {
606 self.window.has_focus()
607 }
608}