par_term/
shader_install_ui.rs1use crate::shader_installer;
7use crate::ui_constants::{
8 SHADER_INSTALL_BUTTON_HEIGHT, SHADER_INSTALL_BUTTON_WIDTH, SHADER_INSTALL_DIALOG_WIDTH,
9 SHADER_INSTALL_INNER_MARGIN,
10};
11use egui::{Align2, Color32, Context, Frame, RichText, Window, epaint::Shadow};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum ShaderInstallResponse {
16 Install,
18 Never,
20 Later,
22 None,
24}
25
26pub struct ShaderInstallUI {
28 pub visible: bool,
30 pub installing: bool,
32 pub progress_message: Option<String>,
34 pub error_message: Option<String>,
36 pub success_message: Option<String>,
38}
39
40impl ShaderInstallUI {
41 pub fn new() -> Self {
43 Self {
44 visible: false,
45 installing: false,
46 progress_message: None,
47 error_message: None,
48 success_message: None,
49 }
50 }
51
52 pub fn show_dialog(&mut self) {
54 self.visible = true;
55 self.installing = false;
56 self.progress_message = None;
57 self.error_message = None;
58 self.success_message = None;
59 }
60
61 pub fn hide(&mut self) {
63 self.visible = false;
64 }
65
66 pub fn show(&mut self, ctx: &Context) -> ShaderInstallResponse {
69 if !self.visible {
70 return ShaderInstallResponse::None;
71 }
72
73 let mut response = ShaderInstallResponse::None;
74
75 let mut style = (*ctx.global_style()).clone();
77 let solid_bg = Color32::from_rgba_unmultiplied(32, 32, 32, 255);
78 style.visuals.window_fill = solid_bg;
79 style.visuals.panel_fill = solid_bg;
80 style.visuals.widgets.noninteractive.bg_fill = solid_bg;
81 ctx.set_global_style(style);
82
83 let viewport = ctx.input(|i| i.viewport_rect());
84
85 Window::new("Shader Pack Available")
86 .resizable(false)
87 .collapsible(false)
88 .default_width(SHADER_INSTALL_DIALOG_WIDTH)
89 .default_pos(viewport.center())
90 .pivot(Align2::CENTER_CENTER)
91 .frame(
92 Frame::window(&ctx.global_style())
93 .fill(solid_bg)
94 .inner_margin(SHADER_INSTALL_INNER_MARGIN)
95 .stroke(egui::Stroke::new(1.0, Color32::from_gray(80)))
96 .shadow(Shadow {
97 offset: [4, 4],
98 blur: 16,
99 spread: 4,
100 color: Color32::from_black_alpha(180),
101 }),
102 )
103 .show(ctx, |ui| {
104 ui.vertical_centered(|ui| {
105 ui.add_space(8.0);
107 ui.label(
108 RichText::new("Custom Shaders Available")
109 .size(20.0)
110 .strong(),
111 );
112 ui.add_space(16.0);
113 });
114
115 ui.label(
117 "par-term includes 49+ custom background shaders and 12 cursor \
118 effect shaders that can transform your terminal experience.",
119 );
120 ui.add_space(8.0);
121
122 ui.label("Effects include:");
123 ui.indent("effects_list", |ui| {
124 ui.label("- CRT monitors, scanlines, and retro effects");
125 ui.label("- Matrix rain, starfields, and particle systems");
126 ui.label("- Plasma, fire, and abstract visualizations");
127 ui.label("- Cursor trails, glows, and ripple effects");
128 });
129
130 ui.add_space(16.0);
131
132 if self.installing {
134 ui.horizontal(|ui| {
135 ui.spinner();
136 ui.label(
137 self.progress_message
138 .as_deref()
139 .unwrap_or("Installing shaders..."),
140 );
141 });
142 } else if let Some(error) = &self.error_message {
143 ui.colored_label(Color32::from_rgb(255, 100, 100), error);
144 ui.add_space(8.0);
145 ui.label("You can try again later using: par-term install-shaders");
146 } else if let Some(success) = &self.success_message {
147 ui.colored_label(Color32::from_rgb(100, 255, 100), success);
148 ui.add_space(8.0);
149 ui.label("Configure shaders in Settings (F12) under 'Background & Effects'.");
150 }
151
152 ui.add_space(16.0);
153
154 ui.vertical_centered(|ui| {
156 if !self.installing && self.success_message.is_none() {
158 ui.horizontal(|ui| {
159 if ui
160 .add_sized(
161 [SHADER_INSTALL_BUTTON_WIDTH, SHADER_INSTALL_BUTTON_HEIGHT],
162 egui::Button::new("Yes, Install"),
163 )
164 .clicked()
165 {
166 response = ShaderInstallResponse::Install;
167 }
168
169 ui.add_space(8.0);
170
171 if ui
172 .add_sized(
173 [SHADER_INSTALL_BUTTON_WIDTH, SHADER_INSTALL_BUTTON_HEIGHT],
174 egui::Button::new("Never"),
175 )
176 .on_hover_text("Don't ask again")
177 .clicked()
178 {
179 response = ShaderInstallResponse::Never;
180 }
181
182 ui.add_space(8.0);
183
184 if ui
185 .add_sized(
186 [SHADER_INSTALL_BUTTON_WIDTH, SHADER_INSTALL_BUTTON_HEIGHT],
187 egui::Button::new("Later"),
188 )
189 .on_hover_text("Ask again next time")
190 .clicked()
191 {
192 response = ShaderInstallResponse::Later;
193 }
194 });
195 } else if self.success_message.is_some() {
196 if ui
198 .add_sized(
199 [SHADER_INSTALL_BUTTON_WIDTH, SHADER_INSTALL_BUTTON_HEIGHT],
200 egui::Button::new("OK"),
201 )
202 .clicked()
203 {
204 self.visible = false;
205 }
206 }
207 });
208
209 ui.add_space(8.0);
210
211 if !self.installing
213 && self.success_message.is_none()
214 && self.error_message.is_none()
215 {
216 ui.vertical_centered(|ui| {
217 ui.label(
218 RichText::new(
219 "You can always install later with: par-term install-shaders",
220 )
221 .weak()
222 .small(),
223 );
224 });
225 }
226 });
227
228 response
229 }
230
231 pub fn set_installing(&mut self, message: &str) {
233 self.installing = true;
234 self.progress_message = Some(message.to_string());
235 self.error_message = None;
236 }
237
238 pub fn set_error(&mut self, error: &str) {
240 self.installing = false;
241 self.progress_message = None;
242 self.error_message = Some(error.to_string());
243 }
244
245 pub fn set_success(&mut self, message: &str) {
247 self.installing = false;
248 self.progress_message = None;
249 self.error_message = None;
250 self.success_message = Some(message.to_string());
251 }
252}
253
254impl Default for ShaderInstallUI {
255 fn default() -> Self {
256 Self::new()
257 }
258}
259
260impl crate::traits::OverlayComponent for ShaderInstallUI {
261 type Action = ShaderInstallResponse;
262
263 fn show(&mut self, ctx: &egui::Context) -> Self::Action {
264 ShaderInstallUI::show(self, ctx)
265 }
266
267 fn is_visible(&self) -> bool {
268 self.visible
269 }
270
271 fn set_visible(&mut self, visible: bool) {
272 self.visible = visible;
273 }
274}
275
276pub fn install_shaders_headless() -> Result<usize, String> {
279 shader_installer::install_shaders()
280}