use crate::SettingsUI;
use crate::section::collapsing_section;
use par_term_config::{CursorShaderConfig, CursorShaderMetadata};
use std::collections::HashSet;
use super::shader_settings::show_reset_button;
pub(super) fn show_cursor_shader_metadata_and_settings(
ui: &mut egui::Ui,
settings: &mut SettingsUI,
changes_this_frame: &mut bool,
collapsed: &mut HashSet<String>,
) {
let shader_name = settings.temp_cursor_shader.clone();
let metadata = settings
.cursor_shader_metadata_cache
.get(&shader_name)
.cloned();
ui.add_space(4.0);
let header_text = if let Some(ref meta) = metadata {
if let Some(ref name) = meta.name {
format!("Cursor Shader Settings: {}", name)
} else {
format!("Cursor Shader Settings: {}", shader_name)
}
} else {
format!("Cursor Shader Settings: {}", shader_name)
};
collapsing_section(
ui,
&header_text,
"cursor_shader_settings",
true,
collapsed,
|ui| {
if let Some(ref meta) = metadata {
show_cursor_shader_metadata_info(ui, meta);
ui.add_space(4.0);
ui.separator();
}
ui.add_space(4.0);
ui.label("Per-shader overrides (takes precedence over global settings):");
ui.add_space(4.0);
show_per_cursor_shader_settings(
ui,
settings,
&shader_name,
&metadata,
changes_this_frame,
);
},
);
}
fn show_cursor_shader_metadata_info(ui: &mut egui::Ui, metadata: &CursorShaderMetadata) {
egui::Grid::new("cursor_shader_metadata_grid")
.num_columns(2)
.spacing([10.0, 4.0])
.show(ui, |ui| {
if let Some(ref name) = metadata.name {
ui.label("Name:");
ui.label(name);
ui.end_row();
}
if let Some(ref author) = metadata.author {
ui.label("Author:");
ui.label(author);
ui.end_row();
}
if let Some(ref version) = metadata.version {
ui.label("Version:");
ui.label(version);
ui.end_row();
}
if let Some(ref description) = metadata.description {
ui.label("Description:");
ui.label(description);
ui.end_row();
}
});
}
fn show_per_cursor_shader_settings(
ui: &mut egui::Ui,
settings: &mut SettingsUI,
shader_name: &str,
metadata: &Option<CursorShaderMetadata>,
changes_this_frame: &mut bool,
) {
let has_override = settings
.config
.cursor_shader_configs
.contains_key(shader_name);
let meta_defaults = metadata.as_ref().map(|m| m.defaults.clone());
let current_override = settings
.config
.cursor_shader_configs
.get(shader_name)
.cloned();
{
let effective_value = current_override
.as_ref()
.and_then(|o| o.base.animation_speed)
.or_else(|| meta_defaults.as_ref().and_then(|m| m.base.animation_speed))
.unwrap_or(settings.config.shader.cursor_shader_animation_speed);
let has_override_val = current_override
.as_ref()
.and_then(|o| o.base.animation_speed)
.is_some();
let mut value = effective_value;
ui.horizontal(|ui| {
ui.label("Animation speed:");
let response = ui.add(egui::Slider::new(&mut value, 0.0..=5.0));
if response.changed() {
let override_entry = settings
.config
.get_or_create_cursor_shader_override(shader_name);
override_entry.base.animation_speed = Some(value);
settings.has_changes = true;
*changes_this_frame = true;
}
if show_reset_button(ui, has_override_val)
&& let Some(override_entry) =
settings.config.cursor_shader_configs.get_mut(shader_name)
{
override_entry.base.animation_speed = None;
settings.has_changes = true;
*changes_this_frame = true;
}
});
}
{
let effective_value = current_override
.as_ref()
.and_then(|o| o.hides_cursor)
.or_else(|| meta_defaults.as_ref().and_then(|m| m.hides_cursor))
.unwrap_or(settings.config.shader.cursor_shader_hides_cursor);
let has_override_val = current_override
.as_ref()
.and_then(|o| o.hides_cursor)
.is_some();
let mut value = effective_value;
ui.horizontal(|ui| {
if ui
.checkbox(&mut value, "Hide default cursor")
.on_hover_text(
"When enabled, the normal cursor is not drawn, allowing the shader to fully replace cursor rendering",
)
.changed()
{
let override_entry = settings
.config
.get_or_create_cursor_shader_override(shader_name);
override_entry.hides_cursor = Some(value);
settings.has_changes = true;
*changes_this_frame = true;
}
if show_reset_button(ui, has_override_val)
&& let Some(override_entry) =
settings.config.cursor_shader_configs.get_mut(shader_name)
{
override_entry.hides_cursor = None;
settings.has_changes = true;
*changes_this_frame = true;
}
});
}
{
let effective_value = current_override
.as_ref()
.and_then(|o| o.disable_in_alt_screen)
.or_else(|| meta_defaults.as_ref().and_then(|m| m.disable_in_alt_screen))
.unwrap_or(settings.config.shader.cursor_shader_disable_in_alt_screen);
let has_override_val = current_override
.as_ref()
.and_then(|o| o.disable_in_alt_screen)
.is_some();
let mut value = effective_value;
ui.horizontal(|ui| {
if ui
.checkbox(&mut value, "Disable in alt screen")
.on_hover_text(
"When enabled, the cursor shader is paused in alt-screen apps like vim, less, and htop",
)
.changed()
{
let override_entry = settings
.config
.get_or_create_cursor_shader_override(shader_name);
override_entry.disable_in_alt_screen = Some(value);
settings.has_changes = true;
*changes_this_frame = true;
}
if show_reset_button(ui, has_override_val)
&& let Some(override_entry) =
settings.config.cursor_shader_configs.get_mut(shader_name)
{
override_entry.disable_in_alt_screen = None;
settings.has_changes = true;
*changes_this_frame = true;
}
});
}
if has_override {
ui.add_space(8.0);
if ui
.button("Reset All Overrides")
.on_hover_text("Remove all per-shader overrides and use defaults")
.clicked()
{
settings.config.remove_cursor_shader_override(shader_name);
settings.has_changes = true;
*changes_this_frame = true;
}
}
ui.add_space(8.0);
ui.separator();
ui.add_space(4.0);
ui.horizontal(|ui| {
if ui
.button("💾 Save Defaults to Shader")
.on_hover_text(
"Write the current effective settings as defaults in the shader file's metadata block.\n\
This will update or create the /*! par-term shader metadata ... */ block.",
)
.clicked()
{
save_cursor_settings_to_shader_metadata(settings, shader_name, metadata);
}
});
}
fn save_cursor_settings_to_shader_metadata(
settings: &mut SettingsUI,
shader_name: &str,
existing_metadata: &Option<CursorShaderMetadata>,
) {
let shader_path = par_term_config::Config::shader_path(shader_name);
if !shader_path.exists() {
log::error!(
"Cannot save metadata: cursor shader file not found: {}",
shader_path.display()
);
settings.cursor_shader_editor_error = Some(format!(
"Cannot save metadata: shader file not found:\n{}",
shader_path.display()
));
return;
}
let new_metadata =
build_cursor_metadata_from_settings(settings, shader_name, existing_metadata);
match par_term_config::update_cursor_shader_metadata_file(&shader_path, &new_metadata) {
Ok(()) => {
log::info!("Saved metadata to cursor shader: {}", shader_path.display());
settings
.cursor_shader_metadata_cache
.invalidate(shader_name);
settings.cursor_shader_editor_error = None;
}
Err(e) => {
log::error!("Failed to save metadata to cursor shader: {}", e);
settings.cursor_shader_editor_error = Some(format!("Failed to save metadata:\n{}", e));
}
}
}
fn build_cursor_metadata_from_settings(
settings: &SettingsUI,
shader_name: &str,
existing_metadata: &Option<CursorShaderMetadata>,
) -> CursorShaderMetadata {
let mut metadata = existing_metadata
.clone()
.unwrap_or_else(|| CursorShaderMetadata {
name: Some(shader_name.trim_end_matches(".glsl").to_string()),
author: None,
description: None,
version: Some("1.0.0".to_string()),
defaults: CursorShaderConfig::default(),
});
let current_override = settings.config.cursor_shader_configs.get(shader_name);
let meta_defaults = existing_metadata.as_ref().map(|m| &m.defaults);
let mut new_defaults = meta_defaults.cloned().unwrap_or_default();
let effective_speed = current_override
.and_then(|o| o.base.animation_speed)
.or_else(|| meta_defaults.and_then(|m| m.base.animation_speed))
.unwrap_or(settings.config.shader.cursor_shader_animation_speed);
if (effective_speed - 1.0).abs() > 0.001 {
new_defaults.base.animation_speed = Some(effective_speed);
} else {
new_defaults.base.animation_speed = None;
}
let effective_hides_cursor = current_override
.and_then(|o| o.hides_cursor)
.or_else(|| meta_defaults.and_then(|m| m.hides_cursor))
.unwrap_or(settings.config.shader.cursor_shader_hides_cursor);
if effective_hides_cursor {
new_defaults.hides_cursor = Some(true);
} else {
new_defaults.hides_cursor = None;
}
let effective_disable_in_alt_screen = current_override
.and_then(|o| o.disable_in_alt_screen)
.or_else(|| meta_defaults.and_then(|m| m.disable_in_alt_screen))
.unwrap_or(settings.config.shader.cursor_shader_disable_in_alt_screen);
if !effective_disable_in_alt_screen {
new_defaults.disable_in_alt_screen = Some(false);
} else {
new_defaults.disable_in_alt_screen = None;
}
metadata.defaults = new_defaults;
metadata
}