1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
// SPDX-License-Identifier: MIT OR Apache-2.0
use core::{ffi::c_char, ops::Deref, ptr::null_mut};
use oxivgl_sys::*;
use super::{
WidgetError,
obj::{AsLvHandle, Obj},
};
/// Static button matrix map.
///
/// LVGL expects a null-terminated array of C string pointers. Use the
/// [`btnmatrix_map!`](crate::btnmatrix_map) macro to create safely.
///
/// LVGL stores the raw pointer (`lv_buttonmatrix_set_map`); the map MUST
/// be `'static` per spec-memory-lifetime §1/§3.
#[repr(transparent)]
pub struct ButtonmatrixMap(pub [*const c_char]);
impl core::fmt::Debug for ButtonmatrixMap {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ButtonmatrixMap").finish_non_exhaustive()
}
}
// SAFETY: The contained pointers reference 'static C string literals
// (enforced by the `btnmatrix_map!` macro).
unsafe impl Sync for ButtonmatrixMap {}
/// Create a `&'static` [`ButtonmatrixMap`] from C string literals.
///
/// Use `"\n"` entries to create row breaks. The macro appends a null
/// terminator automatically.
///
/// ```no_run
/// use oxivgl::btnmatrix_map;
/// use oxivgl::widgets::ButtonmatrixMap;
///
/// static MAP: &ButtonmatrixMap = btnmatrix_map!(
/// c"1", c"2", c"3", c"\n",
/// c"4", c"5", c"6"
/// );
/// ```
#[macro_export]
macro_rules! btnmatrix_map {
($($label:expr),+ $(,)?) => {
// SAFETY: ButtonmatrixMap is repr(transparent) over [*const c_char].
// All pointers come from c"…" literals which are 'static.
// The array is a const-promoted 'static temporary.
unsafe {
&*(&[$($label.as_ptr()),+, ::core::ptr::null()]
as *const [*const ::core::ffi::c_char]
as *const $crate::widgets::ButtonmatrixMap)
}
};
}
/// Button control flags for buttonmatrix buttons.
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ButtonmatrixCtrl(pub lv_buttonmatrix_ctrl_t);
impl ButtonmatrixCtrl {
/// No flags.
pub const NONE: Self = Self(lv_buttonmatrix_ctrl_t_LV_BUTTONMATRIX_CTRL_NONE);
/// Hidden button.
pub const HIDDEN: Self = Self(lv_buttonmatrix_ctrl_t_LV_BUTTONMATRIX_CTRL_HIDDEN);
/// No repeat on long press.
pub const NO_REPEAT: Self = Self(lv_buttonmatrix_ctrl_t_LV_BUTTONMATRIX_CTRL_NO_REPEAT);
/// Disabled (greyed out).
pub const DISABLED: Self = Self(lv_buttonmatrix_ctrl_t_LV_BUTTONMATRIX_CTRL_DISABLED);
/// Can be toggled (checked/unchecked).
pub const CHECKABLE: Self = Self(lv_buttonmatrix_ctrl_t_LV_BUTTONMATRIX_CTRL_CHECKABLE);
/// Currently checked.
pub const CHECKED: Self = Self(lv_buttonmatrix_ctrl_t_LV_BUTTONMATRIX_CTRL_CHECKED);
/// Send click event on release (vs press).
pub const CLICK_TRIG: Self = Self(lv_buttonmatrix_ctrl_t_LV_BUTTONMATRIX_CTRL_CLICK_TRIG);
/// Show popover on press.
pub const POPOVER: Self = Self(lv_buttonmatrix_ctrl_t_LV_BUTTONMATRIX_CTRL_POPOVER);
/// Enable recolor syntax in button text.
pub const RECOLOR: Self = Self(lv_buttonmatrix_ctrl_t_LV_BUTTONMATRIX_CTRL_RECOLOR);
/// Custom flag 1.
pub const CUSTOM_1: Self = Self(lv_buttonmatrix_ctrl_t_LV_BUTTONMATRIX_CTRL_CUSTOM_1);
/// Custom flag 2.
pub const CUSTOM_2: Self = Self(lv_buttonmatrix_ctrl_t_LV_BUTTONMATRIX_CTRL_CUSTOM_2);
}
impl core::ops::BitOr for ButtonmatrixCtrl {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
/// LVGL button matrix widget.
///
/// Requires `LV_USE_BUTTONMATRIX = 1` in `lv_conf.h`.
///
/// # Examples
///
/// ```no_run
/// use oxivgl::btnmatrix_map;
/// use oxivgl::widgets::{Align, Buttonmatrix, ButtonmatrixMap, Screen};
///
/// static MAP: &ButtonmatrixMap = btnmatrix_map!(c"A", c"B", c"C");
///
/// let screen = Screen::active().unwrap();
/// let btnm = Buttonmatrix::new(&screen).unwrap();
/// btnm.set_map(MAP);
/// btnm.align(Align::Center, 0, 0);
/// ```
#[derive(Debug)]
pub struct Buttonmatrix<'p> {
obj: Obj<'p>,
}
impl<'p> AsLvHandle for Buttonmatrix<'p> {
fn lv_handle(&self) -> *mut lv_obj_t {
self.obj.lv_handle()
}
}
impl<'p> Deref for Buttonmatrix<'p> {
type Target = Obj<'p>;
fn deref(&self) -> &Obj<'p> {
&self.obj
}
}
impl<'p> Buttonmatrix<'p> {
/// Create a new button matrix widget.
pub fn new(parent: &impl AsLvHandle) -> Result<Self, WidgetError> {
let parent_ptr = parent.lv_handle();
assert_ne!(parent_ptr, null_mut(), "Parent widget cannot be null");
// SAFETY: parent_ptr non-null (asserted above); lv_init() called via
// LvglDriver.
let handle = unsafe { lv_buttonmatrix_create(parent_ptr) };
if handle.is_null() {
Err(WidgetError::LvglNullPointer)
} else {
Ok(Buttonmatrix { obj: Obj::from_raw(handle) })
}
}
/// Set the button map.
///
/// LVGL stores the raw pointer; the map MUST be `'static`
/// (spec-memory-lifetime §1/§3). Use
/// [`btnmatrix_map!`](crate::btnmatrix_map).
pub fn set_map(&self, map: &'static ButtonmatrixMap) -> &Self {
assert_ne!(self.obj.handle(), null_mut(), "Buttonmatrix handle cannot be null");
// SAFETY: handle non-null; map is 'static and null-terminated.
// LVGL stores the pointer; 'static satisfies lifetime (spec §1/§3).
unsafe { lv_buttonmatrix_set_map(self.obj.handle(), map.0.as_ptr() as *const *const c_char) };
self
}
/// Get the index of the currently selected (last pressed) button.
pub fn get_selected_button(&self) -> u32 {
assert_ne!(self.obj.handle(), null_mut(), "Buttonmatrix handle cannot be null");
// SAFETY: handle non-null.
unsafe { lv_buttonmatrix_get_selected_button(self.obj.handle()) }
}
/// Set the relative width of a button.
pub fn set_button_width(&self, btn_id: u32, width: u32) -> &Self {
assert_ne!(self.obj.handle(), null_mut(), "Buttonmatrix handle cannot be null");
// SAFETY: handle non-null (asserted above).
unsafe { lv_buttonmatrix_set_button_width(self.obj.handle(), btn_id, width) };
self
}
/// Set control flags on a button.
pub fn set_button_ctrl(&self, btn_id: u32, ctrl: ButtonmatrixCtrl) -> &Self {
assert_ne!(self.obj.handle(), null_mut(), "Buttonmatrix handle cannot be null");
// SAFETY: handle non-null (asserted above).
unsafe { lv_buttonmatrix_set_button_ctrl(self.obj.handle(), btn_id, ctrl.0) };
self
}
/// Clear control flags on a button.
pub fn clear_button_ctrl(&self, btn_id: u32, ctrl: ButtonmatrixCtrl) -> &Self {
assert_ne!(self.obj.handle(), null_mut(), "Buttonmatrix handle cannot be null");
// SAFETY: handle non-null (asserted above).
unsafe { lv_buttonmatrix_clear_button_ctrl(self.obj.handle(), btn_id, ctrl.0) };
self
}
/// Set control flags on all buttons.
pub fn set_button_ctrl_all(&self, ctrl: ButtonmatrixCtrl) -> &Self {
assert_ne!(self.obj.handle(), null_mut(), "Buttonmatrix handle cannot be null");
// SAFETY: handle non-null (asserted above).
unsafe { lv_buttonmatrix_set_button_ctrl_all(self.obj.handle(), ctrl.0) };
self
}
/// Clear control flags from all buttons.
pub fn clear_button_ctrl_all(&self, ctrl: ButtonmatrixCtrl) -> &Self {
assert_ne!(self.obj.handle(), null_mut(), "Buttonmatrix handle cannot be null");
// SAFETY: handle non-null (asserted above).
unsafe { lv_buttonmatrix_clear_button_ctrl_all(self.obj.handle(), ctrl.0) };
self
}
/// Enable one-checked mode (only one button can be checked at a time).
pub fn set_one_checked(&self, en: bool) -> &Self {
assert_ne!(self.obj.handle(), null_mut(), "Buttonmatrix handle cannot be null");
// SAFETY: handle non-null (asserted above).
unsafe { lv_buttonmatrix_set_one_checked(self.obj.handle(), en) };
self
}
/// Check if a button has a specific control flag.
pub fn has_button_ctrl(&self, btn_id: u32, ctrl: ButtonmatrixCtrl) -> bool {
assert_ne!(self.obj.handle(), null_mut(), "Buttonmatrix handle cannot be null");
// SAFETY: handle non-null (asserted above).
unsafe { lv_buttonmatrix_has_button_ctrl(self.obj.handle(), btn_id, ctrl.0) }
}
/// Get the text of a button by index. Returns `None` if the index is
/// invalid or the text is not valid UTF-8.
pub fn get_button_text(&self, btn_id: u32) -> Option<&str> {
assert_ne!(self.obj.handle(), null_mut(), "Buttonmatrix handle cannot be null");
// SAFETY: handle non-null; LVGL returns NULL for invalid btn_id.
let ptr = unsafe { lv_buttonmatrix_get_button_text(self.obj.handle(), btn_id) };
if ptr.is_null() {
return None;
}
// SAFETY: ptr is a valid C string from the button map.
let cstr = unsafe { core::ffi::CStr::from_ptr(ptr) };
cstr.to_str().ok()
}
}