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