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
//! Inline field rendering for each trigger action variant.
use par_term_config::automation::{
SplitPaneCommand, TriggerActionConfig, TriggerSplitDirection, TriggerSplitTarget,
};
use par_term_config::color_u8_to_f32;
/// Show inline fields for a trigger action (for editing within the action row).
pub(super) fn show_action_fields(ui: &mut egui::Ui, action: &mut TriggerActionConfig) {
match action {
TriggerActionConfig::Highlight {
fg,
bg,
duration_ms,
} => {
// Background color picker
if let Some(bg_color) = bg {
let mut color = egui::Color32::from_rgb(bg_color[0], bg_color[1], bg_color[2]);
if egui::color_picker::color_edit_button_srgba(
ui,
&mut color,
egui::color_picker::Alpha::Opaque,
)
.changed()
{
*bg_color = [color.r(), color.g(), color.b()];
}
}
// Foreground color picker
if let Some(fg_color) = fg {
let mut color = egui::Color32::from_rgb(fg_color[0], fg_color[1], fg_color[2]);
ui.label("fg:");
if egui::color_picker::color_edit_button_srgba(
ui,
&mut color,
egui::color_picker::Alpha::Opaque,
)
.changed()
{
*fg_color = [color.r(), color.g(), color.b()];
}
}
ui.label("ms:");
ui.add(
egui::DragValue::new(duration_ms)
.range(100..=60000)
.speed(100.0),
);
}
TriggerActionConfig::Notify { title, message } => {
ui.label("title:");
ui.add(egui::TextEdit::singleline(title).desired_width(80.0));
ui.label("msg:");
ui.add(egui::TextEdit::singleline(message).desired_width(100.0));
}
TriggerActionConfig::MarkLine { label, color } => {
ui.label("label:");
let mut label_text = label.clone().unwrap_or_default();
if ui
.add(egui::TextEdit::singleline(&mut label_text).desired_width(80.0))
.changed()
{
*label = if label_text.is_empty() {
None
} else {
Some(label_text)
};
}
ui.label("color:");
// Ensure color is always set (backfill for configs created before
// the color field was added)
let c = color.get_or_insert([0, 180, 255]);
let mut color_f = color_u8_to_f32(*c);
if ui.color_edit_button_rgb(&mut color_f).changed() {
*c = [
(color_f[0] * 255.0) as u8,
(color_f[1] * 255.0) as u8,
(color_f[2] * 255.0) as u8,
];
}
}
TriggerActionConfig::SetVariable { name, value } => {
ui.label("name:");
ui.add(egui::TextEdit::singleline(name).desired_width(80.0));
ui.label("=");
ui.add(egui::TextEdit::singleline(value).desired_width(80.0));
}
TriggerActionConfig::RunCommand { command, args } => {
ui.label("cmd:");
ui.add(egui::TextEdit::singleline(command).desired_width(100.0));
ui.label("args:");
let mut args_str = args.join(" ");
if ui
.add(egui::TextEdit::singleline(&mut args_str).desired_width(80.0))
.changed()
{
*args = args_str.split_whitespace().map(|s| s.to_string()).collect();
}
}
TriggerActionConfig::PlaySound { sound_id, volume } => {
ui.label("sound:");
ui.add(egui::TextEdit::singleline(sound_id).desired_width(80.0));
if ui.button("Browse...").clicked() {
let sounds_dir = dirs::config_dir()
.map(|d| d.join("par-term").join("sounds"))
.unwrap_or_default();
if let Some(path) = rfd::FileDialog::new()
.set_title("Select sound file")
.set_directory(&sounds_dir)
.add_filter("Audio", &["wav", "mp3", "ogg", "flac", "aac", "m4a"])
.pick_file()
{
// If the file is inside the sounds directory, store just the filename;
// otherwise store the full path so play_sound_file can find it.
*sound_id = path
.strip_prefix(&sounds_dir)
.map(|p| p.display().to_string())
.unwrap_or_else(|_| path.display().to_string());
}
}
ui.label("vol:");
ui.add(egui::DragValue::new(volume).range(0..=100).speed(1.0));
}
TriggerActionConfig::SendText { text, delay_ms } => {
ui.label("text:");
ui.add(egui::TextEdit::singleline(text).desired_width(100.0));
ui.label("delay:");
ui.add(egui::DragValue::new(delay_ms).range(0..=10000).speed(10.0));
}
TriggerActionConfig::SplitPane {
direction,
command,
focus_new_pane,
target,
split_percent,
} => {
ui.vertical(|ui| {
// Direction row
ui.horizontal(|ui| {
ui.label("Direction:");
egui::ComboBox::from_id_salt("split_pane_direction")
.selected_text(match direction {
TriggerSplitDirection::Horizontal => "Horizontal (new pane below)",
TriggerSplitDirection::Vertical => "Vertical (new pane to the right)",
})
.show_ui(ui, |ui| {
ui.selectable_value(
direction,
TriggerSplitDirection::Horizontal,
"Horizontal (new pane below)",
);
ui.selectable_value(
direction,
TriggerSplitDirection::Vertical,
"Vertical (new pane to the right)",
);
});
});
// Target row
ui.horizontal(|ui| {
ui.label("Target:");
egui::ComboBox::from_id_salt("split_pane_target")
.selected_text(match target {
TriggerSplitTarget::Active => "Active Pane",
TriggerSplitTarget::Source => "Source Pane (when available)",
})
.show_ui(ui, |ui| {
ui.selectable_value(target, TriggerSplitTarget::Active, "Active Pane");
ui.selectable_value(
target,
TriggerSplitTarget::Source,
"Source Pane (when available)",
);
});
});
// Focus new pane checkbox
ui.checkbox(focus_new_pane, "Focus new pane after splitting");
// Split percent
ui.horizontal(|ui| {
ui.label("Split %:");
let mut pct = *split_percent as u32;
if ui
.add(egui::DragValue::new(&mut pct).range(10..=90).suffix("%"))
.on_hover_text("Existing pane size after split (new pane gets the rest). Default: 66%.")
.changed()
{
*split_percent = pct as u8;
}
});
// Command type selector
// Determine current command type index: 0=None, 1=SendText, 2=InitialCommand
let current_cmd_type: usize = match command {
None => 0,
Some(SplitPaneCommand::SendText { .. }) => 1,
Some(SplitPaneCommand::InitialCommand { .. }) => 2,
};
let mut new_cmd_type = current_cmd_type;
ui.horizontal(|ui| {
ui.label("Command:");
egui::ComboBox::from_id_salt("split_pane_cmd_type")
.selected_text(match current_cmd_type {
1 => "Send Text",
2 => "Initial Command",
_ => "None",
})
.show_ui(ui, |ui| {
ui.selectable_value(&mut new_cmd_type, 0, "None");
ui.selectable_value(&mut new_cmd_type, 1, "Send Text");
ui.selectable_value(&mut new_cmd_type, 2, "Initial Command");
});
});
// Apply command type change if user switched
if new_cmd_type != current_cmd_type {
*command = match new_cmd_type {
0 => None,
1 => Some(SplitPaneCommand::SendText {
text: String::new(),
delay_ms: 200,
}),
2 => Some(SplitPaneCommand::InitialCommand {
command: String::new(),
args: Vec::new(),
}),
_ => None,
};
}
// Sub-fields for the selected command type
match command {
Some(SplitPaneCommand::SendText { text, delay_ms }) => {
ui.horizontal(|ui| {
ui.label("Text to send:");
ui.text_edit_singleline(text);
});
ui.horizontal(|ui| {
ui.label("Delay (ms):");
ui.add(egui::DragValue::new(delay_ms).range(0..=5000).speed(10.0));
});
}
Some(SplitPaneCommand::InitialCommand {
command: cmd_str,
args,
}) => {
ui.horizontal(|ui| {
ui.label("Command:");
ui.text_edit_singleline(cmd_str);
});
ui.horizontal(|ui| {
ui.label("Arguments (space-separated):");
let mut args_str = args.join(" ");
if ui.text_edit_singleline(&mut args_str).changed() {
*args = args_str.split_whitespace().map(String::from).collect();
}
});
}
None => {}
}
});
}
}
}