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
//! Profile list view for `ProfileModalUI`. Rows use the shared list-editor
//! buttons (UX.md SC4); a delete is confirmed by the row's second click.
use super::{ProfileModalAction, ProfileModalUI};
impl ProfileModalUI {
/// Render the list view
pub(crate) fn render_list_view(&mut self, ui: &mut egui::Ui) -> ProfileModalAction {
let mut action = ProfileModalAction::None;
// Header with create button
ui.horizontal(|ui| {
ui.heading("Profiles");
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("+ New Profile").clicked() {
self.start_create();
}
});
});
ui.separator();
// Profile list. No scroll area of its own: the Settings content area
// already scrolls (UX.md SC9).
ui.scope(|ui| {
if self.working_profiles.is_empty() {
ui.vertical_centered(|ui| {
ui.add_space(40.0);
ui.label(
egui::RichText::new("No profiles yet")
.italics()
.color(egui::Color32::GRAY),
);
ui.add_space(10.0);
ui.label("Click '+ New Profile' to create one");
});
} else {
let len = self.working_profiles.len();
let mut row_action = None;
for (idx, profile) in self.working_profiles.clone().iter().enumerate() {
let is_selected = self.selected_id == Some(profile.id);
// Use push_id with profile.id to ensure stable widget ID for double-click detection
ui.push_id(profile.id, |ui| {
let bg_color = if is_selected {
egui::Color32::from_rgba_unmultiplied(70, 100, 140, 150)
} else {
egui::Color32::TRANSPARENT
};
let frame = egui::Frame::NONE
.fill(bg_color)
.inner_margin(egui::Margin::symmetric(8, 4))
.corner_radius(4.0);
frame.show(ui, |ui| {
ui.horizontal(|ui| {
// Icon and name
if let Some(icon) = &profile.icon {
ui.label(icon);
}
let name_response = ui.selectable_label(is_selected, &profile.name);
if name_response.clicked() {
self.selected_id = Some(profile.id);
}
if name_response.double_clicked() {
self.start_edit(profile.id);
}
// Dynamic profile indicator
if profile.source.is_dynamic() {
ui.label(
egui::RichText::new("[dynamic]")
.color(egui::Color32::from_rgb(100, 180, 255))
.small(),
);
}
// Dynamic profiles are read-only: View, no
// delete or duplicate (UX.md SC4 row).
let is_dynamic = profile.source.is_dynamic();
ui.with_layout(
egui::Layout::right_to_left(egui::Align::Center),
|ui| {
if is_dynamic {
if ui.small_button("View").clicked() {
self.start_edit(profile.id);
}
return;
}
let key = profile.id.to_string();
if let Some(a) = crate::list_editor::row_actions(
ui,
&mut self.pending_row_delete,
crate::list_editor::Row {
index: idx,
len,
list: "profile",
key: &key,
delete_label: "Delete",
},
crate::list_editor::RowButtons::ALL,
) {
row_action = Some(a);
}
},
);
});
});
});
}
if let Some(a) = row_action {
self.apply_row_action(a);
}
}
});
// Profile edits join the Settings window's Save / Revert (UX.md SS5),
// so the list has no Save or Cancel of its own. Emptying a non-empty
// list still asks first; confirming runs the global Save.
if self.confirm_empty_save {
ui.separator();
ui.group(|ui| {
ui.label(
egui::RichText::new("⚠ Save with no profiles?")
.strong()
.color(egui::Color32::from_rgb(244, 67, 54)),
);
ui.label(format!(
"This deletes all {} saved profiles.",
self.baseline_profiles.len()
));
ui.horizontal(|ui| {
if ui.button("Delete All Profiles").clicked() {
action = self.confirm_empty_list_save();
}
if ui.button("Cancel").clicked() {
self.confirm_empty_save = false;
}
});
});
}
// The legacy modal window hides on Save; the caller reads
// working_profiles before anything clears them.
if action == ProfileModalAction::Save {
self.visible = false;
}
action
}
}