Skip to content

Commit f4b804d

Browse files
committed
Add font select to text element (#28)
1 parent b62b50f commit f4b804d

File tree

8 files changed

+160
-23
lines changed

8 files changed

+160
-23
lines changed

src/elements/icon.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ impl Icon {
118118
let color = with_alpha(colors::WHITE, alpha);
119119
let shadow_color = with_alpha(colors::BLACK, alpha);
120120

121-
TextDecoration::Shadow.render(ui, text, text_pos, font_size, shadow_color);
122-
draw_text_bg(ui, text, text_pos, font_size, color);
121+
TextDecoration::Shadow.render(ui, text, text_pos, font_scale, shadow_color);
122+
draw_text_bg(ui, text, text_pos, font_scale, color);
123123
}
124124
}
125125

@@ -147,10 +147,10 @@ impl Icon {
147147
ui,
148148
&text,
149149
text_pos,
150-
font_size,
150+
font_scale,
151151
shadow_color,
152152
);
153-
draw_text_bg(ui, &text, text_pos, font_size, color);
153+
draw_text_bg(ui, &text, text_pos, font_scale, color);
154154
}
155155
}
156156
}

src/elements/icon_source.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ impl IconSource {
7878
pub fn render_select(&mut self, ui: &Ui) {
7979
enum_combo(ui, "Icon", self, ComboBoxFlags::empty());
8080

81+
// we assume this stays in place, otherwise we consider the file dialog invalidated
82+
let id = self as *mut _ as usize;
83+
8184
match self {
8285
Self::Unknown | Self::Empty => {}
8386
Self::File(path) => {
@@ -87,9 +90,6 @@ impl IconSource {
8790
.read_only(true)
8891
.build();
8992

90-
// we assume this stays in place, otherwise we consider the file dialog invalidated
91-
let id = path.as_os_str().as_encoded_bytes().as_ptr() as usize;
92-
9393
static FILE: Lockbox<usize, PathBuf> = Lockbox::new();
9494

9595
ui.same_line();

src/elements/text.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use crate::{
44
component_wise::ComponentWise,
55
context::{Context, ContextUpdate},
66
render_util::{
7-
draw_text_bg, helper, input_float_with_format, input_text_multi_with_menu, Rect,
7+
draw_text_bg, font_select, helper, input_float_with_format, input_text_multi_with_menu,
8+
Font, Rect,
89
},
910
traits::{Render, RenderOptions},
1011
tree::TreeLeaf,
@@ -20,11 +21,17 @@ pub struct Text {
2021
pub progress: ProgressTrigger,
2122

2223
pub text: String,
24+
25+
#[serde(rename = "font")]
26+
pub font_name: Option<String>,
2327
pub size: f32,
2428
pub align: AlignHorizontal,
2529
pub color: [f32; 4],
2630
pub decoration: TextDecoration,
2731

32+
#[serde(skip)]
33+
loaded_font: Option<Font>,
34+
2835
#[serde(skip)]
2936
text_memo: Option<String>,
3037
}
@@ -85,6 +92,16 @@ impl Text {
8592
let offset = self.align.text_offset(ui, text, self.size);
8693
pos.add(offset)
8794
}
95+
96+
pub fn load(&mut self) {
97+
if let Some(name) = &self.font_name {
98+
if let Some(font_id) = Font::try_from_name(name) {
99+
self.loaded_font = Some(font_id);
100+
} else {
101+
log::warn!("Failed to find font \"{name}\"");
102+
}
103+
}
104+
}
88105
}
89106

90107
impl TreeLeaf for Text {}
@@ -94,16 +111,16 @@ impl Render for Text {
94111
self.update_text(ctx, state);
95112

96113
if let Some(text) = &self.text_memo {
114+
let _font = self.loaded_font.map(|font| font.push());
97115
let font_scale = self.size;
98-
let font_size = font_scale * ui.current_font_size();
99116
let pos = self.calc_pos(ui, state.pos, text);
100117
let [r, g, b, a] = self.color;
101118
let alpha = a * ui.clone_style().alpha;
102119
let color = [r, g, b, alpha];
103120

104121
self.decoration
105-
.render(ui, text, pos, font_size, [0.0, 0.0, 0.0, alpha]);
106-
draw_text_bg(ui, text, pos, font_size, color);
122+
.render(ui, text, pos, font_scale, [0.0, 0.0, 0.0, alpha]);
123+
draw_text_bg(ui, text, pos, font_scale, color);
107124
}
108125
}
109126
}
@@ -160,6 +177,10 @@ impl RenderOptions for Text {
160177
self.size = size / 100.0;
161178
}
162179

180+
if font_select(ui, "Font", &mut self.loaded_font) {
181+
self.font_name = self.loaded_font.map(|font| font.name_owned());
182+
}
183+
163184
self.align.render_combo(ui);
164185

165186
ColorEdit::new("Color", &mut self.color)
@@ -177,8 +198,10 @@ impl Default for Text {
177198
progress: ProgressTrigger::default(),
178199
align: AlignHorizontal::Center,
179200
size: 1.0,
201+
font_name: None,
180202
color: [1.0, 1.0, 1.0, 1.0],
181203
decoration: TextDecoration::default(),
204+
loaded_font: None,
182205
text_memo: None,
183206
}
184207
}

src/elements/text_decoration.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,36 +40,36 @@ pub enum TextDecoration {
4040
}
4141

4242
impl TextDecoration {
43-
fn render_at(ui: &Ui, pos: [f32; 2], text: &str, font_size: f32, color: [f32; 4]) {
44-
draw_text_bg(ui, text, pos, font_size, color)
43+
fn render_at(ui: &Ui, pos: [f32; 2], text: &str, font_scale: f32, color: [f32; 4]) {
44+
draw_text_bg(ui, text, pos, font_scale, color)
4545
}
4646

4747
pub fn render(
4848
&self,
4949
ui: &Ui,
5050
text: impl AsRef<str>,
5151
pos: [f32; 2],
52-
font_size: f32,
52+
font_scale: f32,
5353
color: [f32; 4],
5454
) {
5555
// FIXME: shadow renders behind transparent text
5656
let text = text.as_ref();
5757
match self {
5858
Self::None => {}
59-
Self::Shadow => Self::render_at(ui, pos.add([1.0, 1.0]), text, font_size, color),
59+
Self::Shadow => Self::render_at(ui, pos.add([1.0, 1.0]), text, font_scale, color),
6060
Self::ShadowDouble => {
6161
for offset in [[0.0, 1.0], [1.0, 0.0]] {
62-
Self::render_at(ui, pos.add(offset), text, font_size, color)
62+
Self::render_at(ui, pos.add(offset), text, font_scale, color)
6363
}
6464
}
6565
Self::Outline => {
6666
for offset in [[-1.0, -1.0], [1.0, 1.0]] {
67-
Self::render_at(ui, pos.add(offset), text, font_size, color)
67+
Self::render_at(ui, pos.add(offset), text, font_scale, color)
6868
}
6969
}
7070
Self::OutlineDouble => {
7171
for offset in [[-1.0, -1.0], [-1.0, 1.0], [1.0, -1.0], [1.0, 1.0]] {
72-
Self::render_at(ui, pos.add(offset), text, font_size, color)
72+
Self::render_at(ui, pos.add(offset), text, font_scale, color)
7373
}
7474
}
7575
}

src/render_util/font.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use nexus::imgui::{sys, ComboBoxFlags, Selectable, SelectableFlags, Ui};
2+
use std::{
3+
borrow::Cow,
4+
ffi::{CStr, CString},
5+
ptr::NonNull,
6+
slice,
7+
};
8+
9+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
10+
#[repr(transparent)]
11+
pub struct Font(pub NonNull<sys::ImFont>);
12+
13+
impl Font {
14+
pub unsafe fn get_all() -> impl Iterator<Item = Self> {
15+
let io = sys::igGetIO();
16+
let atlas = (*io).Fonts;
17+
let data = (*atlas).Fonts.Data;
18+
let len = (*atlas).Fonts.Size;
19+
20+
slice::from_raw_parts(data, len as usize)
21+
.into_iter()
22+
.copied()
23+
.filter_map(NonNull::new)
24+
.map(Self)
25+
}
26+
27+
pub fn try_from_name(name: impl AsRef<str>) -> Option<Self> {
28+
let name = CString::new(name.as_ref()).ok()?;
29+
for font in unsafe { Self::get_all() } {
30+
if unsafe { font.name_raw() } == name.as_c_str() {
31+
return Some(font);
32+
}
33+
}
34+
None
35+
}
36+
37+
pub unsafe fn name_raw<'a>(&self) -> &'a CStr {
38+
unsafe {
39+
let config = (*self.as_ptr()).ConfigData;
40+
let name = (*config).Name.as_ptr();
41+
CStr::from_ptr(name)
42+
}
43+
}
44+
45+
pub fn name_owned(&self) -> String {
46+
unsafe { self.name_raw() }.to_string_lossy().into_owned()
47+
}
48+
49+
pub fn as_ptr(&self) -> *mut sys::ImFont {
50+
self.0.as_ptr()
51+
}
52+
53+
pub fn push(&self) -> FontToken {
54+
unsafe { sys::igPushFont(self.as_ptr()) }
55+
FontToken
56+
}
57+
}
58+
59+
pub struct FontToken;
60+
61+
impl Drop for FontToken {
62+
fn drop(&mut self) {
63+
unsafe { sys::igPopFont() }
64+
}
65+
}
66+
67+
unsafe impl Send for Font {}
68+
69+
pub fn font_select(ui: &Ui, label: impl AsRef<str>, current: &mut Option<Font>) -> bool {
70+
const DEFAULT: &str = "Default";
71+
72+
let mut changed = false;
73+
let preview = match *current {
74+
Some(font) => {
75+
let name = unsafe { font.name_raw() };
76+
name.to_string_lossy()
77+
}
78+
None => Cow::Borrowed(DEFAULT),
79+
};
80+
81+
if let Some(_token) = ui.begin_combo_with_flags(label, preview, ComboBoxFlags::HEIGHT_LARGE) {
82+
if Selectable::new(DEFAULT).build(ui) {
83+
*current = None;
84+
changed = true;
85+
}
86+
87+
for font in unsafe { Font::get_all() } {
88+
let _font = font.push();
89+
let is_selected = Some(font) == *current;
90+
if unsafe {
91+
sys::igSelectable_Bool(
92+
font.name_raw().as_ptr(),
93+
is_selected,
94+
SelectableFlags::empty().bits() as i32,
95+
[0.0, 0.0].into(),
96+
)
97+
} {
98+
*current = Some(font);
99+
changed = true;
100+
}
101+
if is_selected {
102+
ui.set_item_default_focus();
103+
}
104+
}
105+
}
106+
107+
changed
108+
}

src/render_util/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod button;
22
mod combo;
3+
mod font;
34
mod helper;
45
mod input;
56
mod input_text;
@@ -10,8 +11,8 @@ mod text;
1011
mod tree;
1112

1213
pub use self::{
13-
button::*, combo::*, helper::*, input::*, input_text::*, popup::*, spinner::*, style::*,
14-
text::*, tree::*,
14+
button::*, combo::*, font::*, helper::*, input::*, input_text::*, popup::*, spinner::*,
15+
style::*, text::*, tree::*,
1516
};
1617

1718
use nexus::imgui::{sys, Ui};

src/render_util/text.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pub fn draw_text_bg(
55
ui: &Ui,
66
text: impl AsRef<str>,
77
pos: [f32; 2],
8-
font_size: f32,
8+
font_scale: f32,
99
color: impl Into<ImColor32>,
1010
) {
1111
let range = text.as_ref().as_bytes().as_ptr_range();
@@ -14,11 +14,12 @@ pub fn draw_text_bg(
1414
unsafe {
1515
let bg = sys::igGetBackgroundDrawList();
1616
let font = sys::igGetFont();
17+
let font_size = sys::igGetFontSize();
1718

1819
sys::ImDrawList_AddText_FontPtr(
1920
bg,
2021
font,
21-
font_size,
22+
font_scale * font_size,
2223
pos.into(),
2324
color.into().into(),
2425
range.start.cast(),

src/tree/load.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::VisitMut;
22
use crate::{
3-
elements::{IconElement, IconList},
3+
elements::{IconElement, IconList, Text},
44
trigger::FilterTrigger,
55
};
66

@@ -18,6 +18,10 @@ impl VisitMut for Loader {
1818
el.icon.load()
1919
}
2020

21+
fn visit_text(&mut self, text: &mut Text) {
22+
text.load();
23+
}
24+
2125
fn visit_filter_trigger(&mut self, trigger: &mut FilterTrigger) {
2226
trigger.load();
2327
}

0 commit comments

Comments
 (0)