Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
preset_browser.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <pulp/view/view.hpp>
11#include <functional>
12#include <string>
13#include <vector>
14#include <algorithm>
15#include <cctype>
16
17namespace pulp::view {
18
33class PresetBrowser : public View {
34public:
36 : manager_(manager)
37 {
38 set_focusable(true);
39 refresh();
40 }
41
43 std::function<void(const state::PresetInfo&)> on_preset_selected;
44
46 std::function<void(const state::PresetInfo&)> on_preset_activated;
47
49 void refresh() {
50 all_presets_ = manager_.all_presets();
51 apply_filter();
52 }
53
55 void set_filter(const std::string& text) {
56 filter_text_ = text;
57 apply_filter();
58 }
59
60 const std::string& filter_text() const { return filter_text_; }
61
65 show_mode_ = mode;
66 apply_filter();
67 }
68
70 void select_next() {
71 if (filtered_.empty()) return;
72 selected_index_ = (selected_index_ + 1) % static_cast<int>(filtered_.size());
73 notify_selection();
74 }
75
78 if (filtered_.empty()) return;
79 if (selected_index_ < 0) {
80 selected_index_ = static_cast<int>(filtered_.size()) - 1;
81 notify_selection();
82 return;
83 }
84 selected_index_ = (selected_index_ - 1 + static_cast<int>(filtered_.size()))
85 % static_cast<int>(filtered_.size());
86 notify_selection();
87 }
88
90 int selected_index() const { return selected_index_; }
91
94 if (selected_index_ >= 0 && selected_index_ < static_cast<int>(filtered_.size()))
95 return &filtered_[static_cast<size_t>(selected_index_)];
96 return nullptr;
97 }
98
100 int visible_count() const { return static_cast<int>(filtered_.size()); }
101
102 float row_height() const { return row_height_; }
103 void set_row_height(float h) { row_height_ = h; }
104
105 void paint(canvas::Canvas& canvas) override {
106 auto b = local_bounds();
107
108 // Background
109 canvas.set_fill_color(resolve_color("preset_browser_bg",
110 canvas::Color::hex(0x16213e)));
111 canvas.fill_rounded_rect(b.x, b.y, b.width, b.height, 6);
112
113 // Header: current preset name + nav arrows
114 float header_h = 32.0f;
115 canvas.set_fill_color(resolve_color("surface",
116 canvas::Color::hex(0x1a1a2e)));
117 canvas.fill_rect(b.x, b.y, b.width, header_h);
118
119 canvas.set_font("system", 13);
120 canvas.set_fill_color(resolve_color("text",
121 canvas::Color::hex(0xe0e0e0)));
122
123 std::string current = manager_.current_preset_name();
124 if (current.empty()) current = "(No preset)";
125 if (manager_.has_unsaved_changes()) current += " *";
126 canvas.fill_text(current, b.x + 30, b.y + header_h / 2 + 4);
127
128 // Nav arrows
129 canvas.set_fill_color(resolve_color("text_muted",
130 canvas::Color::hex(0x808090)));
131 canvas.fill_text("\xe2\x97\x80", b.x + 8, b.y + header_h / 2 + 4); // ◀
132 canvas.fill_text("\xe2\x96\xb6", b.x + b.width - 18, b.y + header_h / 2 + 4); // ▶
133
134 // Search filter display
135 if (!filter_text_.empty()) {
136 canvas.set_font("system", 11);
137 canvas.set_fill_color(resolve_color("accent",
138 canvas::Color::hex(0xe94560)));
139 canvas.fill_text("Filter: " + filter_text_,
140 b.x + 8, b.y + header_h + 14);
141 }
142
143 // Preset list
144 float list_y = b.y + header_h + (filter_text_.empty() ? 4.0f : 22.0f);
145 float list_h = b.y + b.height - list_y;
146
147 canvas.save();
148 canvas.clip_rect(b.x, list_y, b.width, list_h);
149
150 int visible_start = static_cast<int>(scroll_offset_ / row_height_);
151 int visible_count = static_cast<int>(list_h / row_height_) + 2;
152
153 for (int i = visible_start;
155 ++i)
156 {
157 float y = list_y + static_cast<float>(i) * row_height_ - scroll_offset_;
158 const auto& preset = filtered_[static_cast<size_t>(i)];
159
160 // Selection highlight
161 if (i == selected_index_) {
162 canvas.set_fill_color(resolve_color("list_selected_bg",
163 canvas::Color::hex(0x0f3460)));
164 canvas.fill_rect(b.x + 2, y, b.width - 4, row_height_);
165 }
166
167 // Factory/user indicator
168 canvas.set_font("system", 10);
169 if (preset.is_factory) {
170 canvas.set_fill_color(resolve_color("text_muted",
171 canvas::Color::hex(0x606070)));
172 canvas.fill_text("F", b.x + 6, y + row_height_ * 0.65f);
173 }
174
175 // Preset name
176 canvas.set_font("system", 13);
177 canvas.set_fill_color(resolve_color("text",
178 canvas::Color::hex(0xe0e0e0)));
179 canvas.fill_text(preset.name, b.x + 22, y + row_height_ * 0.65f);
180
181 // Folder path (if any)
182 if (!preset.folder.empty()) {
183 canvas.set_font("system", 10);
184 canvas.set_fill_color(resolve_color("text_muted",
185 canvas::Color::hex(0x808090)));
186 canvas.fill_text(preset.folder,
187 b.x + b.width - 80, y + row_height_ * 0.65f);
188 }
189 }
190
191 canvas.restore();
192 }
193
194 void on_mouse_event(const MouseEvent& event) override {
195 if (!event.is_down) return;
196 auto b = local_bounds();
197
198 float header_h = 32.0f;
199
200 // Click in header area — nav arrows
201 if (event.position.y < header_h) {
202 if (event.position.x < 25) {
206 } else if (event.position.x > b.width - 25) {
207 select_next();
210 }
211 return;
212 }
213
214 // Click in list area
215 float list_y = header_h + (filter_text_.empty() ? 4.0f : 22.0f);
216 if (event.position.y < list_y)
217 return;
218 float click_y = event.position.y - list_y + scroll_offset_;
219 int index = static_cast<int>(click_y / row_height_);
220
221 if (index >= 0 && index < static_cast<int>(filtered_.size())) {
222 selected_index_ = index;
223 notify_selection();
224
225 if (event.click_count == 2 && on_preset_activated) {
226 on_preset_activated(filtered_[static_cast<size_t>(index)]);
227 }
228 }
229 }
230
231 bool on_key_event(const KeyEvent& event) override {
232 if (!event.is_down) return false;
233
234 if (event.key == KeyCode::up) { select_previous(); return true; }
235 if (event.key == KeyCode::down) { select_next(); return true; }
236 if (event.key == KeyCode::enter && selected_preset()) {
238 return true;
239 }
240 return false;
241 }
242
243 bool wants_mouse_input() const override { return true; }
244
245private:
246 state::PresetManager& manager_;
247 std::vector<state::PresetInfo> all_presets_;
248 std::vector<state::PresetInfo> filtered_;
249 std::string filter_text_;
250 ShowMode show_mode_ = ShowMode::all;
251 int selected_index_ = -1;
252 float row_height_ = 24.0f;
253 float scroll_offset_ = 0.0f;
254
255 void apply_filter() {
256 filtered_.clear();
257 for (const auto& p : all_presets_) {
258 if (show_mode_ == ShowMode::factory_only && !p.is_factory) continue;
259 if (show_mode_ == ShowMode::user_only && p.is_factory) continue;
260
261 if (!filter_text_.empty()) {
262 // Case-insensitive search in name and folder
263 std::string lower_name = p.name;
264 std::string lower_folder = p.folder;
265 std::string lower_filter = filter_text_;
266 std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(),
267 [](unsigned char c) { return std::tolower(c); });
268 std::transform(lower_folder.begin(), lower_folder.end(), lower_folder.begin(),
269 [](unsigned char c) { return std::tolower(c); });
270 std::transform(lower_filter.begin(), lower_filter.end(), lower_filter.begin(),
271 [](unsigned char c) { return std::tolower(c); });
272
273 if (lower_name.find(lower_filter) == std::string::npos &&
274 lower_folder.find(lower_filter) == std::string::npos) continue;
275 }
276
277 filtered_.push_back(p);
278 }
279
280 if (selected_index_ >= static_cast<int>(filtered_.size())) {
281 selected_index_ = filtered_.empty() ? -1 : 0;
282 }
283 }
284
285 void notify_selection() {
288 }
289 }
290};
291
292} // namespace pulp::view
Definition canvas.hpp:240
Built-in preset manager for Pulp plugins.
Definition preset_manager.hpp:60
bool on_key_event(const KeyEvent &event) override
Key event with modifiers and up/down state.
Definition preset_browser.hpp:231
void select_previous()
Select previous preset in the filtered list.
Definition preset_browser.hpp:77
float row_height() const
Definition preset_browser.hpp:102
const std::string & filter_text() const
Definition preset_browser.hpp:60
const state::PresetInfo * selected_preset() const
Get the currently selected preset (nullptr if none).
Definition preset_browser.hpp:93
void set_row_height(float h)
Definition preset_browser.hpp:103
void set_filter(const std::string &text)
Set search filter text. Empty string shows all presets.
Definition preset_browser.hpp:55
void set_show_mode(ShowMode mode)
Definition preset_browser.hpp:64
int visible_count() const
Number of presets currently shown (after filtering).
Definition preset_browser.hpp:100
PresetBrowser(state::PresetManager &manager)
Definition preset_browser.hpp:35
ShowMode
Show only factory presets, only user presets, or all.
Definition preset_browser.hpp:63
@ all
Definition preset_browser.hpp:63
@ factory_only
Definition preset_browser.hpp:63
@ user_only
Definition preset_browser.hpp:63
void on_mouse_event(const MouseEvent &event) override
Mouse down with full event context.
Definition preset_browser.hpp:194
int selected_index() const
Get the currently selected preset index (-1 if none).
Definition preset_browser.hpp:90
void select_next()
Select next preset in the filtered list.
Definition preset_browser.hpp:70
bool wants_mouse_input() const override
True if this widget handles mouse down/drag through virtual methods even when it does not expose JS p...
Definition preset_browser.hpp:243
void paint(canvas::Canvas &canvas) override
Definition preset_browser.hpp:105
void refresh()
Refresh the preset list from the PresetManager.
Definition preset_browser.hpp:49
std::function< void(const state::PresetInfo &)> on_preset_selected
Called when a preset is selected (single click).
Definition preset_browser.hpp:43
std::function< void(const state::PresetInfo &)> on_preset_activated
Called when a preset is activated (double click or Enter).
Definition preset_browser.hpp:46
Color resolve_color(const std::string &name, Color fallback={}) const
@ text
Definition view.hpp:2742
void set_focusable(bool f)
Definition view.hpp:669
Rect local_bounds() const
Definition view.hpp:88
Input event types with modifier keys and pointer IDs.
Definition affine_transform.hpp:24
Hover-cursor tracking for platform window hosts.
Definition fwd.hpp:27
@ h
Definition input_events.hpp:39
@ y
Definition input_events.hpp:42
@ up
Definition input_events.hpp:53
@ c
Definition input_events.hpp:39
@ down
Definition input_events.hpp:53
@ p
Definition input_events.hpp:40
@ i
Definition input_events.hpp:40
@ b
Definition input_events.hpp:39
@ enter
Definition input_events.hpp:57
Built-in preset management — save/load/delete/rename/import.
static Color hex(uint32_t rgb)
Construct from hex 0xRRGGBB (alpha = 1.0).
Definition canvas.hpp:66
Metadata for a single preset file.
Definition preset_manager.hpp:19
Rich key event with key code, modifiers, and repeat state.
Definition input_events.hpp:193
KeyCode key
Definition input_events.hpp:194
bool is_down
True for key-down, false for key-up.
Definition input_events.hpp:196
Rich mouse/pointer event with position, modifiers, button state, pointer ID, and stylus properties.
Definition input_events.hpp:104
bool is_down
True for mouse-down / touch-began events.
Definition input_events.hpp:111
int click_count
1=single, 2=double, 3=triple click
Definition input_events.hpp:110
Point position
Position in local view coordinates.
Definition input_events.hpp:105
float x
Definition geometry.hpp:21
float y
Definition geometry.hpp:21
Additional UI components: ComboBox, Tooltip, ProgressBar, CallOutBox, TabPanel, ListBox,...