bones_framework/render/
ui.rs1use std::sync::Arc;
4
5use crate::prelude::*;
6
7pub use ::egui;
8use serde::Deserialize;
9
10pub mod widgets;
11
12pub fn ui_plugin(_session: &mut SessionBuilder) {
14 }
16
17#[derive(HasSchema, Clone, Debug, Default, Deref, DerefMut)]
19pub struct EguiCtx(pub egui::Context);
20
21#[derive(HasSchema, Clone, Deref, DerefMut)]
27#[schema(no_default)]
28pub struct EguiInputHook(pub Arc<dyn Fn(&mut Game, &mut egui::RawInput) + Sync + Send + 'static>);
29
30impl EguiInputHook {
31 pub fn new<F: Fn(&mut Game, &mut egui::RawInput) + Sync + Send + 'static>(hook: F) -> Self {
33 Self(Arc::new(hook))
34 }
35}
36
37#[derive(HasSchema, Clone, Debug, Default, Deref, DerefMut)]
39pub struct EguiTextures(pub HashMap<Handle<Image>, egui::TextureId>);
40
41impl EguiTextures {
42 #[track_caller]
44 pub fn get(&self, handle: Handle<Image>) -> egui::TextureId {
45 *self.0.get(&handle).unwrap()
46 }
47}
48
49#[derive(HasSchema, Clone)]
51#[schema(no_default)]
52#[type_data(asset_loader(["ttf", "otf"], FontLoader))]
53pub struct Font {
54 pub family_name: Arc<str>,
56 pub data: egui::FontData,
58 pub monospace: bool,
60}
61
62#[derive(HasSchema, Debug, serde::Deserialize, Clone)]
65#[derive_type_data(SchemaDeserialize)]
66pub struct FontMeta {
67 #[serde(deserialize_with = "deserialize_arc_str")]
69 pub family: Arc<str>,
70 pub size: f32,
72 pub color: Color,
74}
75
76impl Default for FontMeta {
77 fn default() -> Self {
78 Self {
79 family: "".into(),
80 size: Default::default(),
81 color: Default::default(),
82 }
83 }
84}
85
86impl From<FontMeta> for egui::FontSelection {
87 fn from(val: FontMeta) -> Self {
88 egui::FontSelection::FontId(val.id())
89 }
90}
91
92impl FontMeta {
93 pub fn id(&self) -> egui::FontId {
95 egui::FontId::new(self.size, egui::FontFamily::Name(self.family.clone()))
96 }
97
98 pub fn rich(&self, t: impl Into<String>) -> egui::RichText {
100 egui::RichText::new(t).color(self.color).font(self.id())
101 }
102
103 pub fn with_color(&self, color: Color) -> Self {
105 Self {
106 family: self.family.clone(),
107 size: self.size,
108 color,
109 }
110 }
111}
112
113fn deserialize_arc_str<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Arc<str>, D::Error> {
114 String::deserialize(d).map(|x| x.into())
115}
116
117pub struct FontLoader;
119impl AssetLoader for FontLoader {
120 fn load(&self, _ctx: AssetLoadCtx, bytes: &[u8]) -> BoxedFuture<anyhow::Result<SchemaBox>> {
121 let bytes = bytes.to_vec();
122 Box::pin(async move {
123 let (family_name, monospace) = {
124 let face = ttf_parser::Face::parse(&bytes, 0)?;
125 (
126 face.names()
127 .into_iter()
128 .filter(|x| x.name_id == ttf_parser::name_id::FAMILY)
129 .find_map(|x| x.to_string())
130 .ok_or_else(|| {
131 anyhow::format_err!("Could not read font family from font file")
132 })?
133 .into(),
134 face.is_monospaced(),
135 )
136 };
137 let data = egui::FontData::from_owned(bytes.to_vec());
138
139 Ok(SchemaBox::new(Font {
140 family_name,
141 data,
142 monospace,
143 }))
144 })
145 }
146}
147
148#[derive(HasSchema, Clone, Debug)]
150#[repr(C)]
151pub struct EguiSettings {
152 pub scale: f64,
154}
155
156impl Default for EguiSettings {
157 fn default() -> Self {
158 Self { scale: 1.0 }
159 }
160}
161
162pub trait EguiContextExt {
164 fn clear_focus(self);
166
167 fn get_state<T: Clone + Default + Sync + Send + 'static>(self) -> T;
174
175 fn set_state<T: Clone + Default + Sync + Send + 'static>(self, value: T);
179}
180
181impl EguiContextExt for &egui::Context {
182 fn clear_focus(self) {
183 self.memory_mut(|r| r.request_focus(egui::Id::null()));
184 }
185 fn get_state<T: Clone + Default + Sync + Send + 'static>(self) -> T {
186 self.data_mut(|data| data.get_temp_mut_or_default::<T>(egui::Id::null()).clone())
187 }
188 fn set_state<T: Clone + Default + Sync + Send + 'static>(self, value: T) {
189 self.data_mut(|data| *data.get_temp_mut_or_default::<T>(egui::Id::null()) = value);
190 }
191}
192
193pub trait EguiResponseExt {
195 fn focus_by_default(self, ui: &mut egui::Ui) -> egui::Response;
197}
198
199impl EguiResponseExt for egui::Response {
200 fn focus_by_default(self, ui: &mut egui::Ui) -> egui::Response {
201 if ui.ctx().memory(|r| r.focus().is_none()) {
202 ui.ctx().memory_mut(|r| r.request_focus(self.id));
203
204 self
205 } else {
206 self
207 }
208 }
209}