windjammer_ui/components/generated/
propertyeditor.rs

1#![allow(clippy::all)]
2#![allow(noop_method_call)]
3use super::traits::Renderable;
4
5#[derive(Clone, Debug, PartialEq)]
6pub enum PropertyType {
7    Number { min: f32, max: f32, step: f32 },
8    Integer { min: i32, max: i32 },
9    Boolean,
10    Text,
11    Color,
12    Dropdown { options: Vec<String> },
13}
14
15#[derive(Debug, Clone, PartialEq)]
16pub struct Property {
17    pub name: String,
18    pub value: String,
19    pub property_type: PropertyType,
20    pub unit: String,
21    pub tooltip: String,
22    pub on_change: String,
23}
24
25impl Property {
26    #[inline]
27    pub fn number(name: String, value: f32, min: f32, max: f32) -> Property {
28        Property {
29            name,
30            value: format!("{:.3}", value),
31            property_type: PropertyType::Number {
32                min,
33                max,
34                step: 0.1,
35            },
36            unit: "".to_string(),
37            tooltip: "".to_string(),
38            on_change: "".to_string(),
39        }
40    }
41    #[inline]
42    pub fn integer(name: String, value: i32, min: i32, max: i32) -> Property {
43        Property {
44            name,
45            value: format!("{}", value),
46            property_type: PropertyType::Integer { min, max },
47            unit: "".to_string(),
48            tooltip: "".to_string(),
49            on_change: "".to_string(),
50        }
51    }
52    #[inline]
53    pub fn boolean(name: String, value: bool) -> Property {
54        Property {
55            name,
56            value: {
57                if value {
58                    "true".to_string()
59                } else {
60                    "false".to_string()
61                }
62            },
63            property_type: PropertyType::Boolean,
64            unit: "".to_string(),
65            tooltip: "".to_string(),
66            on_change: "".to_string(),
67        }
68    }
69    #[inline]
70    pub fn text(name: String, value: String) -> Property {
71        Property {
72            name,
73            value,
74            property_type: PropertyType::Text,
75            unit: "".to_string(),
76            tooltip: "".to_string(),
77            on_change: "".to_string(),
78        }
79    }
80    #[inline]
81    pub fn color(name: String, value: String) -> Property {
82        Property {
83            name,
84            value,
85            property_type: PropertyType::Color,
86            unit: "".to_string(),
87            tooltip: "".to_string(),
88            on_change: "".to_string(),
89        }
90    }
91    #[inline]
92    pub fn unit(mut self, unit: String) -> Property {
93        self.unit = unit;
94        self
95    }
96    #[inline]
97    pub fn tooltip(mut self, tooltip: String) -> Property {
98        self.tooltip = tooltip;
99        self
100    }
101    #[inline]
102    pub fn on_change(mut self, handler: String) -> Property {
103        self.on_change = handler;
104        self
105    }
106}
107
108impl Renderable for Property {
109    #[inline]
110    fn render(self) -> String {
111        let tooltip_attr = {
112            if self.tooltip != "" {
113                format!(" title='{}'", self.tooltip)
114            } else {
115                "".to_string()
116            }
117        };
118        let unit_html = {
119            if self.unit != "" {
120                format!("<span class='prop-unit'>{}</span>", self.unit)
121            } else {
122                "".to_string()
123            }
124        };
125        let input_html = match self.property_type.clone() {
126            PropertyType::Number {
127                min: mn,
128                max: mx,
129                step: st,
130            } => {
131                format!(
132                    "
133                    <div class='prop-number'>
134                        <input type='number' class='prop-input' 
135                               value='{}' min='{}' max='{}' step='{}'
136                               onchange='{}(this.value)'/>
137                        {}
138                    </div>
139                ",
140                    self.value, mn, mx, st, self.on_change, unit_html
141                )
142            }
143            PropertyType::Integer { min: mn, max: mx } => {
144                format!(
145                    "
146                    <div class='prop-number'>
147                        <input type='number' class='prop-input' 
148                               value='{}' min='{}' max='{}' step='1'
149                               onchange='{}(this.value)'/>
150                        {}
151                    </div>
152                ",
153                    self.value, mn, mx, self.on_change, unit_html
154                )
155            }
156            PropertyType::Boolean => {
157                let checked = {
158                    if self.value == "true" {
159                        "checked".to_string()
160                    } else {
161                        "".to_string()
162                    }
163                };
164                format!(
165                    "
166                    <label class='prop-toggle'>
167                        <input type='checkbox' {} onchange='{}(this.checked)'/>
168                        <span class='toggle-slider'></span>
169                    </label>
170                ",
171                    checked, self.on_change
172                )
173            }
174            PropertyType::Text => {
175                format!(
176                    "
177                    <input type='text' class='prop-input prop-text' 
178                           value='{}' onchange='{}(this.value)'/>
179                ",
180                    self.value, self.on_change
181                )
182            }
183            PropertyType::Color => {
184                format!(
185                    "
186                    <div class='prop-color'>
187                        <input type='color' class='color-swatch' 
188                               value='{}' onchange='{}(this.value)'/>
189                        <input type='text' class='color-hex' 
190                               value='{}' onchange='{}(this.value)'/>
191                    </div>
192                ",
193                    self.value, self.on_change, self.value, self.on_change
194                )
195            }
196            PropertyType::Dropdown { options: opts } => {
197                let mut options_html = "".to_string();
198                for o in opts {
199                    let selected = {
200                        if o.as_str() == self.value.as_str() {
201                            "selected".to_string()
202                        } else {
203                            "".to_string()
204                        }
205                    };
206                    options_html +=
207                        format!("<option value='{}' {}>{}</option>", o, selected, o).as_str();
208                }
209                format!(
210                    "
211                    <select class='prop-select' onchange='{}(this.value)'>
212                        {}
213                    </select>
214                ",
215                    self.on_change, options_html
216                )
217            }
218        };
219        format!(
220            "
221            <div class='prop-row'{}>
222                <label class='prop-label'>{}</label>
223                <div class='prop-value'>
224                    {}
225                </div>
226            </div>
227        ",
228            tooltip_attr, self.name, input_html
229        )
230    }
231}
232
233#[derive(Debug, Clone, PartialEq)]
234pub struct Vec3Editor {
235    pub label: String,
236    pub x: f32,
237    pub y: f32,
238    pub z: f32,
239    pub on_change: String,
240}
241
242impl Vec3Editor {
243    #[inline]
244    pub fn new(label: String, x: f32, y: f32, z: f32) -> Vec3Editor {
245        Vec3Editor {
246            label,
247            x,
248            y,
249            z,
250            on_change: "".to_string(),
251        }
252    }
253    #[inline]
254    pub fn on_change(mut self, handler: String) -> Vec3Editor {
255        self.on_change = handler;
256        self
257    }
258}
259
260impl Renderable for Vec3Editor {
261    #[inline]
262    fn render(self) -> String {
263        format!(
264            "
265            <div class='vec3-editor'>
266                <label class='prop-label'>{}</label>
267                <div class='vec3-inputs'>
268                    <div class='vec3-axis'>
269                        <span class='axis-label x'>X</span>
270                        <input type='number' step='0.1' value='{:.3}' 
271                               onchange='{}(\"x\", this.value)'/>
272                    </div>
273                    <div class='vec3-axis'>
274                        <span class='axis-label y'>Y</span>
275                        <input type='number' step='0.1' value='{:.3}' 
276                               onchange='{}(\"y\", this.value)'/>
277                    </div>
278                    <div class='vec3-axis'>
279                        <span class='axis-label z'>Z</span>
280                        <input type='number' step='0.1' value='{:.3}' 
281                               onchange='{}(\"z\", this.value)'/>
282                    </div>
283                </div>
284            </div>
285        ",
286            self.label, self.x, self.on_change, self.y, self.on_change, self.z, self.on_change
287        )
288    }
289}
290
291#[inline]
292pub fn property_editor_styles() -> String {
293    "
294    .prop-row {
295        display: flex;
296        align-items: center;
297        padding: 6px 0;
298        border-bottom: 1px solid rgba(255,255,255,0.05);
299    }
300    
301    .prop-row:hover {
302        background: rgba(255,255,255,0.02);
303    }
304    
305    .prop-label {
306        width: 100px;
307        font-size: 12px;
308        color: #999;
309        flex-shrink: 0;
310    }
311    
312    .prop-value {
313        flex: 1;
314    }
315    
316    .prop-input {
317        width: 100%;
318        padding: 6px 10px;
319        border: 1px solid #333;
320        border-radius: 4px;
321        background: #1a1a2e;
322        color: #e0e0e0;
323        font-size: 12px;
324    }
325    
326    .prop-input:focus {
327        border-color: #e94560;
328        outline: none;
329        box-shadow: 0 0 0 2px rgba(233, 69, 96, 0.2);
330    }
331    
332    .prop-number {
333        display: flex;
334        align-items: center;
335        gap: 4px;
336    }
337    
338    .prop-unit {
339        font-size: 11px;
340        color: #666;
341    }
342    
343    /* Toggle switch */
344    .prop-toggle {
345        position: relative;
346        display: inline-block;
347        width: 44px;
348        height: 24px;
349    }
350    
351    .prop-toggle input {
352        opacity: 0;
353        width: 0;
354        height: 0;
355    }
356    
357    .toggle-slider {
358        position: absolute;
359        cursor: pointer;
360        top: 0;
361        left: 0;
362        right: 0;
363        bottom: 0;
364        background-color: #333;
365        transition: 0.3s;
366        border-radius: 24px;
367    }
368    
369    .toggle-slider:before {
370        position: absolute;
371        content: '';
372        height: 18px;
373        width: 18px;
374        left: 3px;
375        bottom: 3px;
376        background-color: white;
377        transition: 0.3s;
378        border-radius: 50%;
379    }
380    
381    .prop-toggle input:checked + .toggle-slider {
382        background-color: #e94560;
383    }
384    
385    .prop-toggle input:checked + .toggle-slider:before {
386        transform: translateX(20px);
387    }
388    
389    /* Color editor */
390    .prop-color {
391        display: flex;
392        gap: 8px;
393        align-items: center;
394    }
395    
396    .color-swatch {
397        width: 32px;
398        height: 32px;
399        border: none;
400        border-radius: 4px;
401        cursor: pointer;
402    }
403    
404    .color-hex {
405        width: 80px;
406        padding: 6px 8px;
407        border: 1px solid #333;
408        border-radius: 4px;
409        background: #1a1a2e;
410        color: #e0e0e0;
411        font-family: monospace;
412        font-size: 12px;
413    }
414    
415    /* Vec3 editor */
416    .vec3-editor {
417        display: flex;
418        align-items: center;
419        padding: 6px 0;
420    }
421    
422    .vec3-inputs {
423        display: flex;
424        gap: 4px;
425        flex: 1;
426    }
427    
428    .vec3-axis {
429        display: flex;
430        align-items: center;
431        flex: 1;
432    }
433    
434    .vec3-axis input {
435        width: 100%;
436        padding: 6px 8px;
437        border: 1px solid #333;
438        border-radius: 0 4px 4px 0;
439        background: #1a1a2e;
440        color: #e0e0e0;
441        font-size: 12px;
442    }
443    
444    .axis-label {
445        padding: 6px 8px;
446        font-size: 11px;
447        font-weight: 600;
448        border-radius: 4px 0 0 4px;
449    }
450    
451    .axis-label.x { background: #e94560; color: white; }
452    .axis-label.y { background: #4ade80; color: #1a1a2e; }
453    .axis-label.z { background: #60a5fa; color: white; }
454    
455    /* Select dropdown */
456    .prop-select {
457        width: 100%;
458        padding: 6px 10px;
459        border: 1px solid #333;
460        border-radius: 4px;
461        background: #1a1a2e;
462        color: #e0e0e0;
463        font-size: 12px;
464        cursor: pointer;
465    }
466    "
467    .to_string()
468}