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
//! Text helpers
//!
//! Convenience functions for colored text, wrapped text, disabled text and
//! label helpers.
//!
//! Quick examples:
//! ```no_run
//! # use dear_imgui_rs::*;
//! # let mut ctx = Context::create();
//! # let ui = ctx.frame();
//! ui.text("normal");
//! ui.text_colored([1.0, 0.5, 0.0, 1.0], "warning");
//! ui.text_disabled("disabled");
//! ui.text_wrapped("very long text that will wrap when needed...");
//! ```
//!
use crate::Ui;
use crate::style::StyleColor;
use crate::sys;
impl Ui {
/// Calculates the size required to render text with the current font and font size.
///
/// This is equivalent to [`Ui::calc_text_size_with_opts`] with
/// `hide_text_after_double_hash` set to `false` and wrapping disabled.
#[doc(alias = "CalcTextSize")]
pub fn calc_text_size(&self, text: impl AsRef<str>) -> [f32; 2] {
self.calc_text_size_with_opts(text, false, -1.0)
}
/// Calculates the size required to render text with explicit display options.
///
/// When `hide_text_after_double_hash` is `true`, the `##` label suffix is
/// excluded from the measurement. A positive `wrap_width` enables wrapping;
/// values at or below zero disable it.
///
/// # Panics
///
/// Panics if `wrap_width` is not finite.
#[doc(alias = "CalcTextSize")]
pub fn calc_text_size_with_opts(
&self,
text: impl AsRef<str>,
hide_text_after_double_hash: bool,
wrap_width: f32,
) -> [f32; 2] {
Self::assert_finite_f32("Ui::calc_text_size_with_opts()", "wrap_width", wrap_width);
let text = text.as_ref();
self.run_with_bound_context(|| unsafe {
self.calc_text_size_bound(text, hide_text_after_double_hash, wrap_width)
})
}
/// Measures text while the owning ImGui context is already current.
///
/// # Safety
///
/// The caller must bind this `Ui`'s live ImGui context for the duration of
/// the call.
pub(crate) unsafe fn calc_text_size_bound(
&self,
text: &str,
hide_text_after_double_hash: bool,
wrap_width: f32,
) -> [f32; 2] {
let text_range = self.scratch_txt_range(text);
let size = unsafe {
sys::igCalcTextSize(
text_range.start,
text_range.end,
hide_text_after_double_hash,
wrap_width,
)
};
[size.x, size.y]
}
/// Display colored text
///
/// This implementation uses zero-copy optimization with `igTextEx`,
/// avoiding string allocation and null-termination overhead.
///
/// # Example
/// ```no_run
/// # use dear_imgui_rs::*;
/// # let mut ctx = Context::create();
/// # let ui = ctx.frame();
/// ui.text_colored([1.0, 0.0, 0.0, 1.0], "Red text");
/// ui.text_colored([0.0, 1.0, 0.0, 1.0], "Green text");
/// ```
#[doc(alias = "TextColored")]
pub fn text_colored(&self, color: [f32; 4], text: impl AsRef<str>) {
let s = text.as_ref();
// Temporarily set the text color
let _token = self.push_style_color(StyleColor::Text, color);
// Use igTextEx with zero-copy (begin/end pointers)
self.run_with_bound_context(|| unsafe {
let begin = s.as_ptr() as *const std::os::raw::c_char;
let end = begin.add(s.len());
sys::igTextEx(begin, end, 0); // ImGuiTextFlags_None = 0
})
}
/// Display disabled (grayed out) text
///
/// This implementation uses zero-copy optimization with `igTextEx`,
/// avoiding string allocation and null-termination overhead.
///
/// # Example
/// ```no_run
/// # use dear_imgui_rs::*;
/// # let mut ctx = Context::create();
/// # let ui = ctx.frame();
/// ui.text_disabled("This option is not available");
/// ```
#[doc(alias = "TextDisabled")]
pub fn text_disabled(&self, text: impl AsRef<str>) {
let s = text.as_ref();
// Get the disabled color from the current style
let disabled_color = self.style_color(StyleColor::TextDisabled);
// Temporarily set the text color to disabled color
let _token = self.push_style_color(StyleColor::Text, disabled_color);
// Use igTextEx with zero-copy (begin/end pointers)
self.run_with_bound_context(|| unsafe {
let begin = s.as_ptr() as *const std::os::raw::c_char;
let end = begin.add(s.len());
sys::igTextEx(begin, end, 0); // ImGuiTextFlags_None = 0
})
}
/// Display text wrapped to fit the current item width
///
/// This uses `PushTextWrapPos + TextUnformatted + PopTextWrapPos` to avoid
/// calling C variadic APIs and to keep the input string unformatted.
#[doc(alias = "TextWrapped")]
pub fn text_wrapped(&self, text: impl AsRef<str>) {
let s = text.as_ref();
let _wrap = self.push_text_wrap_pos(0.0);
self.run_with_bound_context(|| unsafe {
let begin = s.as_ptr() as *const std::os::raw::c_char;
let end = begin.add(s.len());
sys::igTextUnformatted(begin, end);
})
}
/// Display a label and text on the same line
#[doc(alias = "LabelText")]
pub fn label_text(&self, label: impl AsRef<str>, text: impl AsRef<str>) {
let (label_ptr, text_ptr) = self.scratch_txt_two(label, text);
self.run_with_bound_context(|| unsafe {
// Always treat the value as unformatted user text.
const FMT: &[u8; 3] = b"%s\0";
sys::igLabelText(
label_ptr,
FMT.as_ptr() as *const std::os::raw::c_char,
text_ptr,
);
})
}
/// Render a hyperlink-style text button. Returns true when clicked.
#[doc(alias = "TextLink")]
pub fn text_link(&self, label: impl AsRef<str>) -> bool {
self.run_with_bound_context(|| unsafe { sys::igTextLink(self.scratch_txt(label)) })
}
/// Render a hyperlink-style text button, and open the given URL when clicked.
/// Returns true when clicked.
#[doc(alias = "TextLinkOpenURL")]
pub fn text_link_open_url(&self, label: impl AsRef<str>, url: impl AsRef<str>) -> bool {
let (label_ptr, url_ptr) = self.scratch_txt_two(label, url);
self.run_with_bound_context(|| unsafe { sys::igTextLinkOpenURL(label_ptr, url_ptr) })
}
}
#[cfg(test)]
mod tests {
fn setup_context() -> crate::Context {
let mut ctx = crate::Context::create();
ctx.io_mut().set_display_size([128.0, 128.0]);
ctx.io_mut().set_delta_time(1.0 / 60.0);
ctx.font_atlas()
.try_claim_legacy_renderer()
.expect("legacy renderer font atlas should be available")
.build();
ctx
}
#[test]
fn calc_text_size_supports_default_and_advanced_options() {
let mut ctx = setup_context();
let ui = ctx.frame();
let default_size = ui.calc_text_size("Column##sort_key");
let explicit_default = ui.calc_text_size_with_opts("Column##sort_key", false, -1.0);
let visible_label = ui.calc_text_size_with_opts("Column##sort_key", true, -1.0);
let trailing_hash = ui.calc_text_size_with_opts("Column#", true, -1.0);
assert_eq!(default_size, explicit_default);
assert!(visible_label[0] < default_size[0]);
assert_eq!(visible_label[1], default_size[1]);
assert_eq!(trailing_hash, ui.calc_text_size("Column#"));
let unwrapped = ui.calc_text_size("one two three four");
let wrapped = ui.calc_text_size_with_opts("one two three four", false, unwrapped[0] / 2.0);
assert!(wrapped[0] < unwrapped[0]);
assert!(wrapped[1] > unwrapped[1]);
for wrap_width in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = ui.calc_text_size_with_opts("text", false, wrap_width);
}))
.is_err()
);
}
}
}