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
//! Profile edit/create view for `ProfileModalUI`.
//!
//! The header, sub-tab bar, validation message, and Done/Cancel are drawn
//! here and stay visible on every sub-tab; the fields of each sub-tab are
//! in `edit_tabs.rs`.
use super::{ModalMode, ProfileModalUI};
use crate::nerd_font::NERD_FONT_PRESETS;
use crate::shell_detection;
use par_term_config::layout_constants::{
PROFILE_ICON_PICKER_MAX_HEIGHT, PROFILE_ICON_PICKER_MIN_WIDTH,
};
use std::collections::HashSet;
impl ProfileModalUI {
/// Render the edit/create view
pub(crate) fn render_edit_view(&mut self, ui: &mut egui::Ui, collapsed: &mut HashSet<String>) {
// Check if the profile being edited is a dynamic profile
let is_dynamic_profile = self
.editing_id
.and_then(|id| self.working_profiles.iter().find(|p| p.id == id))
.is_some_and(|p| p.source.is_dynamic());
let title = match &self.mode {
ModalMode::Create => "Create Profile",
ModalMode::Edit(_) => {
if is_dynamic_profile {
"View Profile"
} else {
"Edit Profile"
}
}
_ => "Profile",
};
ui.heading(title);
// Show read-only notice for dynamic profiles
if is_dynamic_profile {
ui.add_space(4.0);
ui.horizontal(|ui| {
ui.label(egui::RichText::new("ℹ").color(egui::Color32::from_rgb(100, 180, 255)));
ui.colored_label(
egui::Color32::from_rgb(100, 180, 255),
"This profile is managed by a remote source and cannot be edited locally.",
);
});
}
ui.add_space(4.0);
self.render_edit_tab_bar(ui);
ui.separator();
ui.scope(|ui| {
// Disable all form fields for dynamic (read-only) profiles
if is_dynamic_profile {
ui.disable();
}
self.render_edit_tab(ui, collapsed);
});
// Validation error
if let Some(error) = &self.validation_error {
ui.add_space(8.0);
ui.colored_label(egui::Color32::RED, error);
}
// Help text
ui.add_space(8.0);
ui.label(
egui::RichText::new(
"Note: Inherited settings from parent profiles are used when this profile's field is empty.",
)
.small()
.color(egui::Color32::GRAY),
);
// Footer buttons
ui.add_space(8.0);
ui.separator();
ui.horizontal(|ui| {
if is_dynamic_profile {
// Dynamic profiles are read-only; only show Back button
if ui.button("Back").clicked() {
self.cancel_edit();
}
} else {
if ui
.button("Done")
.on_hover_text(
"Keep these edits and return to the list. Save at the bottom of \
the window writes them; Revert drops them.",
)
.clicked()
{
self.save_form();
}
if ui
.button("Cancel")
.on_hover_text("Drop the edits to this profile")
.clicked()
{
self.cancel_edit();
}
}
});
}
/// The icon field and its Nerd Font picker popup.
pub(super) fn render_icon_picker(&mut self, ui: &mut egui::Ui) {
ui.horizontal(|ui| {
ui.text_edit_singleline(&mut self.temp_icon);
let picker_label = if self.temp_icon.is_empty() {
"\u{ea7b}" // Nerd Font file icon
} else {
&self.temp_icon
};
let picker_btn = ui.button(picker_label);
egui::Popup::from_toggle_button_response(&picker_btn)
.close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside)
.show(|ui| {
ui.set_min_width(PROFILE_ICON_PICKER_MIN_WIDTH);
egui::ScrollArea::vertical()
.max_height(PROFILE_ICON_PICKER_MAX_HEIGHT)
.show(ui, |ui| {
for (category, icons) in NERD_FONT_PRESETS {
ui.label(egui::RichText::new(*category).small().strong());
ui.horizontal_wrapped(|ui| {
for (icon, label) in *icons {
let btn = ui.add_sized(
[28.0, 28.0],
egui::Button::new(
egui::RichText::new(*icon).size(16.0),
)
.frame(false),
);
if btn.on_hover_text(*label).clicked() {
self.temp_icon = icon.to_string();
egui::Popup::close_all(ui.ctx());
}
}
});
ui.add_space(2.0);
}
ui.add_space(4.0);
if ui.button("Clear icon").clicked() {
self.temp_icon.clear();
egui::Popup::close_all(ui.ctx());
}
});
});
});
}
/// The shell picker: inherit the global shell, or one of the detected
/// shells.
pub(super) fn render_shell_selector(&mut self, ui: &mut egui::Ui) {
ui.horizontal(|ui| {
let shells = shell_detection::detected_shells();
let selected_label = self
.temp_shell
.as_ref()
.map(|path| {
shells
.iter()
.find(|s| s.path == *path)
.map(|s| s.name.clone())
.unwrap_or_else(|| path.clone())
})
.unwrap_or_else(|| "Default (inherit global)".to_string());
egui::ComboBox::from_id_salt("shell_selector")
.selected_text(&selected_label)
.show_ui(ui, |ui| {
if ui
.selectable_label(self.temp_shell.is_none(), "Default (inherit global)")
.clicked()
{
self.temp_shell = None;
}
ui.separator();
for shell in shells {
let is_selected =
self.temp_shell.as_ref().is_some_and(|s| s == &shell.path);
if ui
.selectable_label(
is_selected,
format!("{} ({})", shell.name, shell.path),
)
.clicked()
{
self.temp_shell = Some(shell.path.clone());
}
}
});
});
}
}