mujoco_rs/wrappers/
mj_interface.rs

1//! MuJoCo user interface
2//! This IS NOT YET TESTED.
3use super::mj_rendering::MjrContext;
4use crate::mujoco_c::*;
5
6/******************************** */
7//           Constants
8/******************************** */
9const MAX_UI_STORAGE_SIZE: usize = 256;  // Rust-specific constant for the location where the UI will write data.
10
11
12/******************************** */
13//           Enums
14/******************************** */
15pub type MjtItem = mjtItem;
16
17
18/***********************************************************************************************************************
19** MjuiState
20***********************************************************************************************************************/
21pub type MjuiState = mjuiState;
22
23impl Default for MjuiState{
24    fn default() -> Self {
25        unsafe { std::mem::zeroed() }
26    }
27}
28
29/***********************************************************************************************************************
30** MjuiThemeSpacing
31***********************************************************************************************************************/
32pub type MjuiThemeSpacing = mjuiThemeSpacing;
33
34impl Default for MjuiThemeSpacing{
35    fn default() -> Self {
36        unsafe { std::mem::zeroed() }
37    }
38}
39
40
41/***********************************************************************************************************************
42** MjuiThemeColor
43***********************************************************************************************************************/
44pub type MjuiThemeColor = mjuiThemeColor;
45
46impl Default for MjuiThemeColor{
47    fn default() -> Self {
48        unsafe { std::mem::zeroed() }
49    }
50}
51
52
53/***********************************************************************************************************************
54** MjuiItem
55***********************************************************************************************************************/
56// pub enum MjuiItemModifier {
57//     None = 0,
58//     Control,
59//     Shift,
60//     Alt
61// }
62
63// impl MjuiItemModifier {
64//     fn from_ffi(ffi: i32) -> Self {
65//         match ffi {
66//             0 => Self::None,
67//             1 => Self::Control,
68//             2 => Self::Shift,
69//             4 => Self::Alt,
70//             _ => unreachable!()
71//         }
72//     }
73// }
74
75#[derive(Clone)]
76pub enum MjuiItemState {
77    Disable = 0,
78    Enable,
79}
80
81// /// Possible data types of the Edit widget.
82// pub enum MjuiItemEditType {
83//     String (String),
84//     Integer (i32),
85//     Double (f64),
86// }
87
88// /// Type of MuJoCo's UI widgets.
89// pub enum MjuiItemType { 
90//     Check { modifier: MjuiItemModifier, data: bool },
91//     Radio { data: bool },
92//     RadioLine { data: bool },
93//     Select,
94//     Slider { range: (f64, f64), divisions: f64, data: f64 },
95//     Edit { range: (f64, f64), data: MjuiItemEditType },
96//     Button,
97//     Other
98// }
99
100// /// MuJoCo UI widget
101// pub struct MjuiItem {
102//     pub name: String,
103//     pub state: MjuiItemState,
104//     pub sectionid: u32,
105//     pub itemid: u32,
106//     pub userid: u32,
107//     pub type_prop: MjuiItemType
108// }
109
110// impl MjuiItem {
111//     pub(crate) fn from_ffi(ffi: &mjuiItem) -> Self {
112//         /* Convert name */
113//         let name_len = ffi.name.iter().position(|&x| x == 0).unwrap_or(ffi.name.len());
114//         let u8_name = ffi.name.map(|x| x as u8);
115//         let name = String::from_utf8_lossy(&u8_name[..name_len]).to_string();
116
117//         /* Convert state */
118//         let state = match ffi.state {
119//             0 => MjuiItemState::Disable,
120//             _ => MjuiItemState::Enable
121//         };
122
123//         use MjuiItemType::*;
124//         use mjtItem::*;
125        
126//         let ffi_type_enum = ffi.type_.try_into().unwrap();
127//         let type_prop = match ffi_type_enum {
128//             mjITEM_CHECKBYTE | mjITEM_CHECKINT => {
129//                 let single =  unsafe { ffi.__bindgen_anon_1.single.as_ref() };
130//                 let data = if ffi.type_ == mjITEM_CHECKBYTE as i32 {
131//                     unsafe { *std::mem::transmute::<_, *mut i32>(ffi.pdata).as_ref().unwrap() == 1 }
132//                 }
133//                 else {
134//                     unsafe { *std::mem::transmute::<_, *mut mjtNum>(ffi.pdata).as_ref().unwrap() == 1.0 }
135//                 };
136
137//                 Check { modifier: MjuiItemModifier::from_ffi(single.modifier), data}
138//             }
139
140//             mjITEM_BUTTON => Button,
141
142//             mjITEM_RADIO => Radio { data: unsafe { *std::mem::transmute::<_, *mut i32>(ffi.pdata).as_ref().unwrap() == 1 } },        
143
144//             mjITEM_RADIOLINE => RadioLine { data: unsafe { *std::mem::transmute::<_, *mut i32>(ffi.pdata).as_ref().unwrap() == 1 } },    
145
146//             mjITEM_SLIDERINT | mjITEM_SLIDERNUM => {
147//                 let data = if ffi.type_ == mjITEM_SLIDERINT as i32 {
148//                     unsafe { *std::mem::transmute::<_, *mut i32>(ffi.pdata).as_ref().unwrap() as f64 }
149//                 }
150//                 else {
151//                     unsafe { *std::mem::transmute::<_, *mut f64>(ffi.pdata).as_ref().unwrap() }
152//                 };
153
154//                 let slider = unsafe { ffi.__bindgen_anon_1.slider.as_ref() };
155
156//                 Slider { range: slider.range.into(), divisions: slider.divisions, data }
157//             }
158
159//             mjITEM_EDITINT => {  // | mjITEM_EDITNUM | mjITEM_EDITFLOAT | mjITEM_EDITTXT
160//                 let edit = unsafe { ffi.__bindgen_anon_1.slider.as_ref() };
161//                 let data = unsafe { *std::mem::transmute::<_, *mut i32>(ffi.pdata).as_ref().unwrap() };
162//                 Edit { range: edit.range.into(), data: MjuiItemEditType::Integer(data)}
163//             }
164
165//             mjITEM_EDITNUM => {  // | mjITEM_EDITNUM | mjITEM_EDITFLOAT | mjITEM_EDITTXT
166//                 let edit = unsafe { ffi.__bindgen_anon_1.slider.as_ref() };
167//                 let data = unsafe { *std::mem::transmute::<_, *mut mjtNum>(ffi.pdata).as_ref().unwrap() };
168//                 Edit { range: edit.range.into(), data: MjuiItemEditType::Double(data)}
169//             }
170
171//             mjITEM_EDITFLOAT => {  // | mjITEM_EDITNUM | mjITEM_EDITFLOAT | mjITEM_EDITTXT
172//                 let edit = unsafe { ffi.__bindgen_anon_1.slider.as_ref() };
173//                 let data = unsafe { *std::mem::transmute::<_, *mut f32>(ffi.pdata).as_ref().unwrap() };
174//                 Edit { range: edit.range.into(), data: MjuiItemEditType::Double(data as f64)}
175//             }
176
177//             mjITEM_EDITTXT => {  // | mjITEM_EDITNUM | mjITEM_EDITFLOAT | mjITEM_EDITTXT
178//                 let edit = unsafe { ffi.__bindgen_anon_1.slider.as_ref() };
179//                 let data_char = unsafe { std::slice::from_raw_parts(ffi.pdata as *const u8, mjMAXUINAME as usize) };
180//                 let data_len = data_char.iter().position(|&x| x == 0).unwrap_or(mjMAXUINAME as usize);
181//                 let data = String::from_utf8_lossy(&data_char[..data_len]).to_string();
182
183//                 Edit { range: edit.range.into(), data: MjuiItemEditType::String(data)}
184//             }
185
186//             _ => Other
187//         };
188
189//         Self {
190//             name, state, sectionid: ffi.sectionid as u32, itemid: ffi.itemid as u32,
191//             userid: ffi.userid as u32, type_prop
192//         }
193//     }
194// }
195
196
197/***********************************************************************************************************************
198** MjuiSection
199***********************************************************************************************************************/
200pub type MjuiSection = mjuiSection;
201
202impl MjuiSection {
203    /// Returns the ``name`` attribute in Rust's ``String``.
204    pub fn name(&self) -> String {
205        let len = self.name.iter().position(|&x| x == 0).unwrap_or(mjMAXUINAME as usize);
206        let u8_arr: [u8; mjMAXUINAME as usize] = self.name.map(|x| x as u8);
207        String::from_utf8_lossy(&u8_arr[..len]).to_string()
208    }
209
210    /// Sets the ``name`` attribute from ``name``, which is Rust's ``String``.
211    pub fn set_name(&mut self, name: String) {
212        let by = name.as_bytes();
213        assert!(by.len() < mjMAXUINAME as usize);
214        unsafe { self.name[..by.len()].copy_from_slice(std::slice::from_raw_parts(by.as_ptr() as *const i8, by.len())); }
215        self.name[by.len()] = 0;  // C string zero to mark the end of the string.
216    }
217
218    // pub fn items(&self) -> Box<[MjuiItem]> {
219    //     let b: Box<[MjuiItem]> = self.item[..self.nitem as usize].iter().map(|&x| MjuiItem::from_ffi(x)).collect();
220    //     b
221    // }
222
223    pub fn items(&mut self) -> &mut [mjuiItem] {
224        &mut self.item[..self.nitem as usize]
225    }
226}
227
228
229/***********************************************************************************************************************
230** MjuiDef
231***********************************************************************************************************************/
232/// Widget specific configuration for the MjuiDef
233pub enum MjuiDefType { 
234    Check   { modifier: String },
235    Radio   { values: String },
236    Select  { values: String },
237    Slider  { range: (f64, f64) },
238    Edit    { range: (f64, f64), n_dimensions: usize, type_: MjuiDefDataType }, 
239    Button  { modifier: String },
240    End     // Ending element
241}
242
243
244pub enum MjuiDefDataType {
245    String,
246    Integer,
247    Double,
248}
249
250
251pub struct MjuiDef {
252    pub name: String,
253    pub state: MjuiItemState,
254    pub type_: MjuiDefType,
255
256    // Internal data for writing values to, since Rust's borrow checker
257    // doesn't allow MuJoCo's style of keeping a global pointer to some arbitrary data
258    // while it gets modified by the UI.
259    pub storage: [u8; MAX_UI_STORAGE_SIZE]
260}
261
262impl MjuiDef {
263    /// Creates mjuiDef for usage in MuJoCo's UI functions.
264    pub(crate) fn build_ffi_mut(&mut self) -> mjuiDef {
265        let mut ffi = mjuiDef::default();
266        let by = self.name.as_bytes();
267        unsafe { ffi.name[..by.len()].copy_from_slice(std::slice::from_raw_parts(by.as_ptr() as *const i8, by.len())); }
268
269        use MjuiDefType::*;
270        let (type_, other) = match &self.type_ {
271            Check { modifier } => {
272                (mjtItem::mjITEM_CHECKBYTE, modifier)
273            },
274
275            Radio {values } => {
276                (mjtItem::mjITEM_RADIO, values)
277            },
278
279            Select { values } => {
280                (mjtItem::mjITEM_SELECT, values)
281            },
282
283            Edit { range, n_dimensions, type_ } => {
284                let (t, r) = match type_ {
285                    MjuiDefDataType::String => (MjtItem::mjITEM_EDITTXT, (0.0, 0.0)),
286                    MjuiDefDataType::Integer => (MjtItem::mjITEM_EDITINT, *range),
287                    MjuiDefDataType::Double => (MjtItem::mjITEM_EDITNUM, *range),
288                };
289                (t, &format!("{n_dimensions} {} {}", r.0, r.1))
290            },
291
292            Button { modifier } => {
293                (mjtItem::mjITEM_BUTTON, modifier)
294            },
295
296            Slider { range } => {
297                (mjtItem::mjITEM_SLIDERNUM, &format!("{} {}", range.0, range.1))
298            },
299            End => {
300                (mjtItem::mjITEM_END, &"".to_string())
301            }
302        };
303
304        let other_bytes = other.as_bytes();
305        ffi.type_ = type_ as i32;
306        ffi.state = self.state.clone() as i32;
307        ffi.pdata = self.storage.as_mut_ptr() as *mut std::ffi::c_void;
308        unsafe { ffi.other[..other_bytes.len()].copy_from_slice(std::slice::from_raw_parts(other_bytes.as_ptr() as *const i8, other_bytes.len())); }
309
310        ffi
311    }
312}
313
314
315impl Default for mjuiDef{
316    fn default() -> Self {
317        unsafe { std::mem::zeroed() }
318    }
319}
320
321
322/***********************************************************************************************************************
323** MjUI
324***********************************************************************************************************************/
325impl Default for mjUI{
326    fn default() -> Self {
327        unsafe { std::mem::zeroed() }
328    }
329}
330
331pub struct MjUI {
332    ui: mjUI
333}
334
335impl MjUI {
336    pub fn new() -> Self {
337        Self{ ui: mjUI::default() }
338    }
339
340    pub fn add(&mut self, items: &mut [MjuiDef]) {
341        let last_item = items.last();
342        if let Some(x) = last_item {
343            if let MjuiDefType::End = x.type_ {}
344            else {
345                panic!("last item in the items parameter must be MjuiDefType::End");
346            }
347        }
348
349        let items: Box<[mjuiDef]> = items.iter_mut().map(|hdef| hdef.build_ffi_mut()).collect();
350        unsafe { mjui_add(self.ffi_mut(), items.as_ptr()); }
351    }
352
353    pub fn add_to_section(&mut self, section_id: u32, items: &mut [MjuiDef]) {
354        let items: Box<[mjuiDef]> = items.iter_mut().map(|hdef| hdef.build_ffi_mut()).collect();
355        unsafe { mjui_addToSection(self.ffi_mut(), section_id as i32, items.as_ptr()); }
356    }
357
358    pub fn resize(&mut self, context: &MjrContext) {
359        unsafe { mjui_resize(self.ffi_mut(), context.ffi()); }
360    }
361
362    /// Main update method. Must be called when the UI state changes or the user data changes.
363    pub fn update(&self, section: Option<i32>, item: Option<i32>, state: &MjuiState, context: &MjrContext) {
364        unsafe {
365            mjui_update(
366                section.unwrap_or(-1), item.unwrap_or(-1),
367                self.ffi(),
368                state, context.ffi()
369            );
370        }
371    }
372
373    // The event processing method. Creates a reference to mjuiItem.
374    pub fn event(&mut self, state: &mut MjuiState, context: &MjrContext) -> &mut mjuiItem {
375        let ffi_item = unsafe { mjui_event(self.ffi_mut(), state, context.ffi()) };
376        unsafe {  ffi_item.as_mut().unwrap() }
377    }
378
379    pub fn render(&mut self, state: &MjuiState, context: &MjrContext) {
380        unsafe { mjui_render(self.ffi_mut(), state, context.ffi()); }
381    }
382
383    pub fn ffi(&self) -> &mjUI {
384        &self.ui
385    }
386
387    pub unsafe fn ffi_mut(&mut self) -> &mut mjUI {
388        &mut self.ui
389    }
390}