1use crate::layout::{GridLayout, PluginLayout, compute_section_offsets,
6 GRID_GAP, GRID_PADDING, GRID_HEADER_H};
7use crate::widgets::WidgetType;
8
9#[derive(Clone, Debug)]
11pub struct WidgetRegion {
12 pub param_id: u32,
13 pub widget_type: WidgetType,
14 pub x: f32,
15 pub y: f32,
16 pub w: f32,
17 pub h: f32,
18 pub cx: f32,
20 pub cy: f32,
21 pub radius: f32,
22 pub normalized_value: f32,
23}
24
25pub type KnobRegion = WidgetRegion;
27
28pub struct InteractionState {
30 pub knob_regions: Vec<WidgetRegion>,
31 pub dragging: Option<DragState>,
32 pub hover_idx: Option<usize>,
34}
35
36pub struct DragState {
37 pub region_idx: usize,
38 pub param_id: u32,
39 pub start_value: f64,
40 pub start_y: f32,
41 pub widget_type: WidgetType,
42 pub region_x: f32,
43 pub region_y: f32,
44 pub region_w: f32,
45 pub region_h: f32,
46}
47
48impl Default for InteractionState {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54impl InteractionState {
55 pub fn new() -> Self {
56 Self {
57 knob_regions: Vec::new(),
58 dragging: None,
59 hover_idx: None,
60 }
61 }
62
63 pub fn build_regions(&mut self, layout: &PluginLayout) {
65 self.knob_regions.clear();
66
67 let knob_size = layout.knob_size;
68 let mut y = 35.0f32;
69
70 for row in &layout.rows {
71 if row.label.is_some() {
72 y += 18.0;
73 }
74
75 let total_cols: u32 = row.knobs.iter().map(|k| k.span.max(1)).sum();
76 let total_w = total_cols as f32 * (knob_size + 10.0) - 10.0;
77 let start_x = (layout.width as f32 - total_w) / 2.0;
78
79 let mut col = 0u32;
80 for knob_def in row.knobs.iter() {
81 let span = knob_def.span.max(1);
82 let x = start_x + col as f32 * (knob_size + 10.0);
83 let widget_w = span as f32 * (knob_size + 10.0) - 10.0;
84 let cx = x + widget_w / 2.0;
85 let cy = y + knob_size / 2.0 - 8.0;
86 let radius = knob_size / 2.0 - 6.0;
87
88 self.knob_regions.push(WidgetRegion {
89 param_id: knob_def.param_id,
90 widget_type: WidgetType::Knob,
91 x,
92 y,
93 w: widget_w,
94 h: knob_size,
95 cx,
96 cy,
97 radius,
98 normalized_value: 0.0,
99 });
100 col += span;
101 }
102
103 y += knob_size + 30.0;
104 }
105 }
106
107 pub fn hit_test(&self, mx: f32, my: f32) -> Option<usize> {
109 for (idx, region) in self.knob_regions.iter().enumerate() {
110 match region.widget_type {
111 WidgetType::Knob => {
112 let dx = mx - region.cx;
113 let dy = my - region.cy;
114 if dx * dx + dy * dy <= region.radius * region.radius {
115 return Some(idx);
116 }
117 }
118 WidgetType::Meter => continue,
119 WidgetType::Slider | WidgetType::Toggle | WidgetType::Selector | WidgetType::XYPad => {
120 if mx >= region.x && mx <= region.x + region.w
121 && my >= region.y && my <= region.y + region.h
122 {
123 return Some(idx);
124 }
125 }
126 }
127 }
128 None
129 }
130
131 pub fn widget_type_at(&self, idx: usize) -> Option<WidgetType> {
133 self.knob_regions.get(idx).map(|r| r.widget_type)
134 }
135
136 pub fn region_at(&self, idx: usize) -> Option<&WidgetRegion> {
138 self.knob_regions.get(idx)
139 }
140
141 pub fn begin_drag(&mut self, idx: usize, current_normalized: f64, mouse_y: f32) {
143 let region = match self.knob_regions.get(idx) {
144 Some(r) => r,
145 None => return,
146 };
147 let param_id = region.param_id;
148 let wtype = region.widget_type;
149 self.dragging = Some(DragState {
150 region_idx: idx,
151 param_id,
152 start_value: current_normalized,
153 start_y: mouse_y,
154 widget_type: wtype,
155 region_x: region.x,
156 region_y: region.y,
157 region_w: region.w,
158 region_h: region.h,
159 });
160 }
161
162 pub fn update_drag(&self, mouse_y: f32) -> Option<(u32, f64)> {
164 let drag = self.dragging.as_ref()?;
165 let dy = drag.start_y - mouse_y;
166 let delta = dy as f64 / 200.0;
167 let new_value = (drag.start_value + delta).clamp(0.0, 1.0);
168 Some((drag.param_id, new_value))
169 }
170
171 pub fn update_slider_drag(&self, mouse_x: f32) -> Option<(u32, f64)> {
173 let drag = self.dragging.as_ref()?;
174 let margin = 6.0;
175 let rel = (mouse_x - drag.region_x - margin) / (drag.region_w - margin * 2.0);
176 let new_value = (rel as f64).clamp(0.0, 1.0);
177 Some((drag.param_id, new_value))
178 }
179
180 pub fn end_drag(&mut self) {
182 self.dragging = None;
183 }
184
185 pub fn build_regions_grid(&mut self, layout: &GridLayout) {
187 self.knob_regions.clear();
188
189 let section_offsets = compute_section_offsets(layout);
190
191 for gw in &layout.widgets {
192 let x = GRID_PADDING + gw.col as f32 * (layout.cell_size + GRID_GAP);
193 let y = GRID_HEADER_H + GRID_PADDING
194 + gw.row as f32 * (layout.cell_size + GRID_GAP)
195 + section_offsets[gw.row as usize];
196 let w = gw.col_span as f32 * (layout.cell_size + GRID_GAP) - GRID_GAP;
197 let h = gw.row_span as f32 * (layout.cell_size + GRID_GAP) - GRID_GAP;
198 let cx = x + w / 2.0;
199 let cy = y + h / 2.0 - 8.0;
200 let radius = w.min(h) / 2.0 - 6.0;
201
202 self.knob_regions.push(WidgetRegion {
203 param_id: gw.param_id,
204 widget_type: WidgetType::Knob, x, y, w, h,
206 cx, cy, radius,
207 normalized_value: 0.0,
208 });
209 }
210 }
211}