1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
//! Render implementation for [`SettingsWindow`].
//!
//! Contains the `render()` method, which drives the egui frame, submits GPU
//! commands, and translates settings-UI results into [`SettingsWindowAction`]
//! values returned to the caller.
use super::{SettingsWindow, SettingsWindowAction};
use crate::settings_ui::UpdateCheckResult;
impl SettingsWindow {
/// Render the settings window.
///
/// Called from `handle_window_event` when a `RedrawRequested` event arrives.
/// Returns a [`SettingsWindowAction`] describing any user-driven change that
/// the caller (window manager) must act upon.
pub(super) fn render(&mut self) -> SettingsWindowAction {
if !self.ready {
return SettingsWindowAction::None;
}
// Get surface texture
let output = match self.surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(o)
| wgpu::CurrentSurfaceTexture::Suboptimal(o) => o,
wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => {
// Logged loudly on purpose: a surface stuck Outdated reconfigures
// on every redraw, which stalls the shared GPU queue — visible
// as flicker in the main window. Silence here hides that storm.
log::warn!("Settings window surface outdated/lost — reconfiguring");
self.surface.configure(&self.device, &self.surface_config);
return SettingsWindowAction::None;
}
wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
// Per-frame while occluded — retry, not a fault (unlike the
// outdated/lost storm above, which is loud on purpose).
crate::debug_log!("RENDER", "Settings window surface timeout/occluded");
return SettingsWindowAction::None;
}
wgpu::CurrentSurfaceTexture::Validation => {
log::error!("Settings window surface validation error");
return SettingsWindowAction::None;
}
};
let view = output
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
// Track settings results
let mut config_to_save = None;
let mut config_for_live = None;
let mut shader_apply = None;
let mut cursor_shader_apply = None;
// Run egui
let mut raw_input = self.egui_state.take_egui_input(&self.window);
// Inject pending events from menu accelerators (Cmd+V/C/X/A intercepted by muda)
if let Some(text) = self.pending_paste.take() {
raw_input.events.push(egui::Event::Paste(text));
}
raw_input.events.append(&mut self.pending_events);
let mut egui_output = self.egui_ctx.run_ui(raw_input, |ctx| {
// Show the settings UI as a panel (not a nested window) and capture results
let (save, live, shader, cursor_shader) = self.settings_ui.show_as_panel(ctx);
config_to_save = save;
config_for_live = live;
shader_apply = shader;
cursor_shader_apply = cursor_shader;
});
// Handle platform output (clipboard, cursor)
// Manually handle clipboard copy as a fallback for macOS menu accelerator issues.
// In egui 0.33, copy commands are in platform_output.commands as OutputCommand::CopyText.
for cmd in &egui_output.platform_output.commands {
match cmd {
egui::OutputCommand::CopyText(text) => {
if let Ok(mut clipboard) = arboard::Clipboard::new()
&& let Err(e) = clipboard.set_text(text)
{
log::warn!("Settings window: failed to copy to clipboard: {}", e);
}
}
egui::OutputCommand::CopyImage(_) => {}
_ => {}
}
}
self.egui_state
.handle_platform_output(&self.window, egui_output.platform_output.clone());
// Tessellate shapes
let paint_jobs = self
.egui_ctx
.tessellate(egui_output.shapes, self.egui_ctx.pixels_per_point());
// Upload egui textures. Since egui 0.36 each texture id carries a
// SmallVec of deltas (the font atlas can deliver several patches per
// frame), so apply them in order.
for (id, deltas) in &egui_output.textures_delta.set {
for delta in deltas {
// egui 0.34 can deliver a partial font-atlas patch before this
// renderer has allocated the font texture (emilk/egui#8228);
// egui-wgpu panics on that, so pre-allocate the full atlas first.
if delta.pos.is_some() && self.egui_renderer.texture(id).is_none() {
let full_image = self
.egui_ctx
.fonts(|f| egui::epaint::ImageData::Color(std::sync::Arc::new(f.image())));
self.egui_renderer.update_texture(
&self.device,
&self.queue,
*id,
&egui::epaint::ImageDelta {
pos: None,
image: full_image,
options: delta.options,
},
);
}
self.egui_renderer
.update_texture(&self.device, &self.queue, *id, delta);
}
}
// Create command encoder
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Settings Window Encoder"),
});
// Screen descriptor
let screen_descriptor = egui_wgpu::ScreenDescriptor {
size_in_pixels: [self.surface_config.width, self.surface_config.height],
pixels_per_point: self.window.scale_factor() as f32,
};
// Update buffers
self.egui_renderer.update_buffers(
&self.device,
&self.queue,
&mut encoder,
&paint_jobs,
&screen_descriptor,
);
// Render pass
{
let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Settings Window Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.094,
g: 0.094,
b: 0.094,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
// Convert to 'static lifetime as required by egui_renderer.render()
let mut render_pass = render_pass.forget_lifetime();
self.egui_renderer
.render(&mut render_pass, &paint_jobs, &screen_descriptor);
} // render_pass dropped here
// Submit
self.queue.submit(std::iter::once(encoder.finish()));
self.queue.present(output);
// Free textures
for id in &egui_output.textures_delta.free {
self.egui_renderer.free_texture(id);
}
// epaint 0.36 panics if a consumed delta is dropped uncleared (same
// rule as Renderer::apply_egui_texture_deltas in the main window).
egui_output.textures_delta.clear();
// A resolved close prompt closes once this frame's saves are handed
// over; the host applies the returned action before closing.
if self.settings_ui.take_close_ready() {
self.should_close = true;
}
// A save is frame-local, so it goes before the queued requests below:
// those persist until a later frame picks them up.
if let Some(config) = config_to_save {
self.window.request_redraw();
return SettingsWindowAction::SaveConfig(config);
}
// Check for test notification request
if self.settings_ui.take_test_notification_request() {
return SettingsWindowAction::TestNotification;
}
// Check for profile save request
if let Some(profiles) = self.settings_ui.take_profile_save_request() {
self.window.request_redraw();
return SettingsWindowAction::SaveProfiles(profiles);
}
// Check for Assistant prompt-library changes
if self.settings_ui.take_assistant_prompts_changed() {
self.window.request_redraw();
return SettingsWindowAction::AssistantPromptsChanged;
}
// Check for open log file request
if self.settings_ui.open_log_requested {
self.settings_ui.open_log_requested = false;
return SettingsWindowAction::OpenLogFile;
}
// Check for update check request
if self.settings_ui.check_now_requested {
self.settings_ui.check_now_requested = false;
self.window.request_redraw();
return SettingsWindowAction::ForceUpdateCheck;
}
// Check for update install request
if self.settings_ui.update_install_requested {
self.settings_ui.update_install_requested = false;
// Extract the version from the last_update_result
if let Some(UpdateCheckResult::UpdateAvailable(ref info)) =
self.settings_ui.last_update_result
{
let version = info
.version
.strip_prefix('v')
.unwrap_or(&info.version)
.to_string();
self.settings_ui
.start_self_update_with(version.clone(), |v| {
par_term_update::self_updater::perform_update(v, crate::VERSION).map(|r| {
crate::settings_ui::UpdateResult {
old_version: r.old_version,
new_version: r.new_version,
install_path: r.install_path.display().to_string(),
needs_restart: r.needs_restart,
}
})
});
self.window.request_redraw();
return SettingsWindowAction::InstallUpdate(version);
}
}
// Poll for update install completion
self.settings_ui.poll_update_install_status();
// Check for coprocess start/stop actions
if let Some((index, start)) = self.settings_ui.pending_coprocess_actions.pop() {
log::info!(
"Settings window: popped coprocess action index={} start={}",
index,
start
);
// Request another redraw to process remaining actions (if any) and config changes
self.window.request_redraw();
return if start {
SettingsWindowAction::StartCoprocess(index)
} else {
SettingsWindowAction::StopCoprocess(index)
};
}
// Check for script start/stop actions
if let Some((index, start)) = self.settings_ui.pending_script_actions.pop() {
crate::debug_info!(
"SCRIPT",
"Settings window: popped script action index={} start={}",
index,
start
);
self.window.request_redraw();
return if start {
SettingsWindowAction::StartScript(index)
} else {
SettingsWindowAction::StopScript(index)
};
}
// Check for arrangement actions
if let Some(action) = self.settings_ui.pending_arrangement_actions.pop() {
self.window.request_redraw();
return action;
}
// Check for identify panes request
if self.settings_ui.identify_panes_requested {
self.settings_ui.identify_panes_requested = false;
self.window.request_redraw();
return SettingsWindowAction::IdentifyPanes;
}
// Check for shell integration install/uninstall actions
if let Some(action) = self.settings_ui.shell_integration_action.take() {
self.window.request_redraw();
return match action {
crate::settings_ui::integrations_tab::ShellIntegrationAction::Install => {
SettingsWindowAction::InstallShellIntegration
}
crate::settings_ui::integrations_tab::ShellIntegrationAction::Uninstall => {
SettingsWindowAction::UninstallShellIntegration
}
};
}
// Determine action based on settings UI results
if let Some(shader_result) = shader_apply {
return SettingsWindowAction::ApplyShader(shader_result);
}
if let Some(cursor_shader_result) = cursor_shader_apply {
return SettingsWindowAction::ApplyCursorShader(cursor_shader_result);
}
if let Some(config) = config_for_live {
return SettingsWindowAction::ApplyConfig(config);
}
SettingsWindowAction::None
}
}