1use macroquad::prelude::*;
2use macroquad::miniquad::{BlendState, BlendFactor, BlendValue, Equation};
3use crate::{math::BoundingBox, render_commands::{CornerRadii, RenderCommand, RenderCommandConfig}, shaders::{ShaderConfig, ShaderUniformValue}};
4
5#[cfg(feature = "text-styling")]
6use crate::text_styling::{render_styled_text, StyledSegment};
7#[cfg(feature = "text-styling")]
8use rustc_hash::FxHashMap;
9
10const PIXELS_PER_POINT: f32 = 2.0;
11
12fn resolve_asset_path(path: &str) -> &str {
15 #[cfg(target_os = "android")]
16 if let Some(stripped) = path.strip_prefix("assets/") {
17 return stripped;
18 }
19 path
20}
21
22#[cfg(feature = "text-styling")]
23static ANIMATION_TRACKER: std::sync::LazyLock<std::sync::Mutex<FxHashMap<String, (usize, f64)>>> = std::sync::LazyLock::new(|| std::sync::Mutex::new(FxHashMap::default()));
24
25#[derive(Debug)]
27pub enum GraphicAsset {
28 Path(&'static str), Bytes{file_name: &'static str, data: &'static [u8]}, }
31impl GraphicAsset {
32 pub fn get_name(&self) -> &str {
33 match self {
34 GraphicAsset::Path(path) => path,
35 GraphicAsset::Bytes { file_name, .. } => file_name,
36 }
37 }
38}
39
40#[derive(Debug, Clone)]
43pub enum ImageSource {
44 Asset(&'static GraphicAsset),
46 Texture(Texture2D),
48 #[cfg(feature = "tinyvg")]
50 TinyVg(tinyvg::format::Image),
51}
52
53impl ImageSource {
54 pub fn get_name(&self) -> &str {
56 match self {
57 ImageSource::Asset(ga) => ga.get_name(),
58 ImageSource::Texture(_) => "[Texture2D]",
59 #[cfg(feature = "tinyvg")]
60 ImageSource::TinyVg(_) => "[TinyVG procedural]",
61 }
62 }
63}
64
65impl From<&'static GraphicAsset> for ImageSource {
66 fn from(asset: &'static GraphicAsset) -> Self {
67 ImageSource::Asset(asset)
68 }
69}
70
71impl From<Texture2D> for ImageSource {
72 fn from(tex: Texture2D) -> Self {
73 ImageSource::Texture(tex)
74 }
75}
76
77#[cfg(feature = "tinyvg")]
78impl From<tinyvg::format::Image> for ImageSource {
79 fn from(img: tinyvg::format::Image) -> Self {
80 ImageSource::TinyVg(img)
81 }
82}
83
84#[derive(Debug)]
86pub enum FontAsset {
87 Path(&'static str),
89 Bytes {
91 file_name: &'static str,
92 data: &'static [u8],
93 },
94}
95
96impl FontAsset {
97 pub fn key(&self) -> &'static str {
99 match self {
100 FontAsset::Path(path) => path,
101 FontAsset::Bytes { file_name, .. } => file_name,
102 }
103 }
104}
105
106pub static FONT_MANAGER: std::sync::LazyLock<std::sync::Mutex<FontManager>> =
108 std::sync::LazyLock::new(|| std::sync::Mutex::new(FontManager::new()));
109
110pub struct FontManager {
112 fonts: rustc_hash::FxHashMap<&'static str, FontData>,
113 default_font: Option<DefaultFont>,
114 pub max_frames_not_used: usize,
115}
116struct DefaultFont {
117 key: &'static str,
118 font: Font,
119}
120struct FontData {
121 pub frames_not_used: usize,
122 pub font: Font,
123}
124impl FontManager {
125 pub fn new() -> Self {
126 Self {
127 fonts: rustc_hash::FxHashMap::default(),
128 default_font: None,
129 max_frames_not_used: 60,
130 }
131 }
132
133 pub fn get(&mut self, asset: &'static FontAsset) -> Option<&Font> {
135 let key = asset.key();
136 if let Some(data) = self.fonts.get_mut(key) {
137 return Some(&data.font);
138 }
139 self.default_font.as_ref()
141 .filter(|d| d.key == key)
142 .map(|d| &d.font)
143 }
144
145 pub fn get_default(&self) -> Option<&Font> {
148 self.default_font.as_ref().map(|d| &d.font)
149 }
150
151 pub async fn load_default(asset: &'static FontAsset) {
153 let font = match asset {
154 FontAsset::Bytes { data, .. } => {
155 macroquad::text::load_ttf_font_from_bytes(data)
156 .expect("Failed to load font from bytes")
157 }
158 FontAsset::Path(path) => {
159 let resolved = resolve_asset_path(path);
160 macroquad::text::load_ttf_font(resolved).await
161 .unwrap_or_else(|e| panic!("Failed to load font '{}': {:?}", path, e))
162 }
163 };
164 let mut fm = FONT_MANAGER.lock().unwrap();
165 fm.default_font = Some(DefaultFont { key: asset.key(), font });
166 }
167
168 pub async fn ensure(asset: &'static FontAsset) {
170 {
172 let mut fm = FONT_MANAGER.lock().unwrap();
173 if fm.default_font.as_ref().map(|d| d.key) == Some(asset.key()) {
175 return;
176 }
177 if let Some(data) = fm.fonts.get_mut(asset.key()) {
179 data.frames_not_used = 0;
180 return;
181 }
182 }
183
184 let font = match asset {
186 FontAsset::Bytes { data, .. } => {
187 macroquad::text::load_ttf_font_from_bytes(data)
188 .expect("Failed to load font from bytes")
189 }
190 FontAsset::Path(path) => {
191 let resolved = resolve_asset_path(path);
192 macroquad::text::load_ttf_font(resolved).await
193 .unwrap_or_else(|e| panic!("Failed to load font '{}': {:?}", path, e))
194 }
195 };
196
197 let mut fm = FONT_MANAGER.lock().unwrap();
199 let key = asset.key();
200 fm.fonts.entry(key).or_insert(FontData { frames_not_used: 0, font });
201 }
202
203 pub fn clean(&mut self) {
204 self.fonts.retain(|_, data| data.frames_not_used <= self.max_frames_not_used);
205 for (_, data) in self.fonts.iter_mut() {
206 data.frames_not_used += 1;
207 }
208 }
209
210 pub fn size(&self) -> usize {
212 self.fonts.len()
213 }
214}
215
216pub static TEXTURE_MANAGER: std::sync::LazyLock<std::sync::Mutex<TextureManager>> = std::sync::LazyLock::new(|| std::sync::Mutex::new(TextureManager::new()));
218
219pub struct TextureManager {
223 textures: rustc_hash::FxHashMap<String, CacheEntry>,
224 pub max_frames_not_used: usize,
225}
226struct CacheEntry {
227 frames_not_used: usize,
228 owner: TextureOwner,
229}
230enum TextureOwner {
231 Standalone(Texture2D),
232 RenderTarget(RenderTarget),
233}
234
235impl TextureOwner {
236 pub fn texture(&self) -> &Texture2D {
237 match self {
238 TextureOwner::Standalone(tex) => tex,
239 TextureOwner::RenderTarget(rt) => &rt.texture,
240 }
241 }
242}
243
244impl From<Texture2D> for TextureOwner {
245 fn from(tex: Texture2D) -> Self {
246 TextureOwner::Standalone(tex)
247 }
248}
249
250impl From<RenderTarget> for TextureOwner {
251 fn from(rt: RenderTarget) -> Self {
252 TextureOwner::RenderTarget(rt)
253 }
254}
255
256impl TextureManager {
257 pub fn new() -> Self {
258 Self {
259 textures: rustc_hash::FxHashMap::default(),
260 max_frames_not_used: 1,
261 }
262 }
263
264 pub fn get(&mut self, path: &str) -> Option<&Texture2D> {
266 if let Some(entry) = self.textures.get_mut(path) {
267 entry.frames_not_used = 0;
268 Some(entry.owner.texture())
269 } else {
270 None
271 }
272 }
273
274 pub async fn get_or_load(&mut self, path: &'static str) -> &Texture2D {
276 if !self.textures.contains_key(path) {
277 let texture = load_texture(resolve_asset_path(path)).await.unwrap();
278 self.textures.insert(path.to_owned(), CacheEntry { frames_not_used: 0, owner: texture.into() });
279 }
280 let entry = self.textures.get_mut(path).unwrap();
281 entry.frames_not_used = 0;
282 entry.owner.texture()
283 }
284
285 pub fn get_or_create<F>(&mut self, key: String, create_fn: F) -> &Texture2D
287 where F: FnOnce() -> Texture2D
288 {
289 if !self.textures.contains_key(&key) {
290 let texture = create_fn();
291 self.textures.insert(key.clone(), CacheEntry { frames_not_used: 0, owner: texture.into() });
292 }
293 let entry = self.textures.get_mut(&key).unwrap();
294 entry.frames_not_used = 0;
295 entry.owner.texture()
296 }
297
298 pub async fn get_or_create_async<F, Fut>(&mut self, key: String, create_fn: F) -> &Texture2D
299 where F: FnOnce() -> Fut,
300 Fut: std::future::Future<Output = Texture2D>
301 {
302 if !self.textures.contains_key(&key) {
303 let texture = create_fn().await;
304 self.textures.insert(key.clone(), CacheEntry { frames_not_used: 0, owner: texture.into() });
305 }
306 let entry = self.textures.get_mut(&key).unwrap();
307 entry.frames_not_used = 0;
308 entry.owner.texture()
309 }
310
311 #[allow(private_bounds)]
313 pub fn cache(&mut self, key: String, value: impl Into<TextureOwner>) -> &Texture2D {
314 self.textures.insert(key.clone(), CacheEntry { frames_not_used: 0, owner: value.into() });
315 self.textures.get(&key).unwrap().owner.texture()
316 }
317
318 pub fn clean(&mut self) {
319 self.textures.retain(|_, entry| entry.frames_not_used <= self.max_frames_not_used);
320 for (_, entry) in self.textures.iter_mut() {
321 entry.frames_not_used += 1;
322 }
323 }
324
325 pub fn size(&self) -> usize {
326 self.textures.len()
327 }
328}
329
330const DEFAULT_VERTEX_SHADER: &str = "#version 100
332attribute vec3 position;
333attribute vec2 texcoord;
334attribute vec4 color0;
335varying lowp vec2 uv;
336varying lowp vec4 color;
337uniform mat4 Model;
338uniform mat4 Projection;
339void main() {
340 gl_Position = Projection * Model * vec4(position, 1);
341 color = color0 / 255.0;
342 uv = texcoord;
343}
344";
345
346pub const DEFAULT_FRAGMENT_SHADER: &str = "#version 100
348precision lowp float;
349varying vec2 uv;
350varying vec4 color;
351uniform sampler2D Texture;
352void main() {
353 gl_FragColor = color;
354}
355";
356
357pub static MATERIAL_MANAGER: std::sync::LazyLock<std::sync::Mutex<MaterialManager>> =
359 std::sync::LazyLock::new(|| std::sync::Mutex::new(MaterialManager::new()));
360
361pub struct MaterialManager {
370 materials: rustc_hash::FxHashMap<std::borrow::Cow<'static, str>, MaterialData>,
371 shader_storage: rustc_hash::FxHashMap<String, String>,
373 pub max_frames_not_used: usize,
375}
376
377struct MaterialData {
378 pub frames_not_used: usize,
379 pub material: Material,
380}
381
382impl MaterialManager {
383 pub fn new() -> Self {
384 Self {
385 materials: rustc_hash::FxHashMap::default(),
386 shader_storage: rustc_hash::FxHashMap::default(),
387 max_frames_not_used: 60, }
389 }
390
391 pub fn get_or_create(&mut self, config: &ShaderConfig) -> &Material {
394 let key: &str = &config.fragment;
395 if !self.materials.contains_key(key) {
396 let mut uniform_decls: Vec<UniformDesc> = vec![
398 UniformDesc::new("u_resolution", UniformType::Float2),
400 UniformDesc::new("u_position", UniformType::Float2),
401 ];
402 for u in &config.uniforms {
403 let utype = match &u.value {
404 ShaderUniformValue::Float(_) => UniformType::Float1,
405 ShaderUniformValue::Vec2(_) => UniformType::Float2,
406 ShaderUniformValue::Vec3(_) => UniformType::Float3,
407 ShaderUniformValue::Vec4(_) => UniformType::Float4,
408 ShaderUniformValue::Int(_) => UniformType::Int1,
409 ShaderUniformValue::Mat4(_) => UniformType::Mat4,
410 };
411 uniform_decls.push(UniformDesc::new(&u.name, utype));
412 }
413
414 let blend_pipeline_params = PipelineParams {
415 color_blend: Some(BlendState::new(
416 Equation::Add,
417 BlendFactor::Value(BlendValue::SourceAlpha),
418 BlendFactor::OneMinusValue(BlendValue::SourceAlpha),
419 )),
420 alpha_blend: Some(BlendState::new(
421 Equation::Add,
422 BlendFactor::Value(BlendValue::SourceAlpha),
423 BlendFactor::OneMinusValue(BlendValue::SourceAlpha),
424 )),
425 ..Default::default()
426 };
427
428 let material = load_material(
429 ShaderSource::Glsl {
430 vertex: DEFAULT_VERTEX_SHADER,
431 fragment: &config.fragment,
432 },
433 MaterialParams {
434 pipeline_params: blend_pipeline_params,
435 uniforms: uniform_decls,
436 ..Default::default()
437 },
438 )
439 .unwrap_or_else(|e| {
440 eprintln!("Failed to compile shader material: {:?}", e);
441 load_material(
443 ShaderSource::Glsl {
444 vertex: DEFAULT_VERTEX_SHADER,
445 fragment: DEFAULT_FRAGMENT_SHADER,
446 },
447 MaterialParams::default(),
448 )
449 .unwrap()
450 });
451
452 self.materials.insert(config.fragment.clone(), MaterialData {
453 frames_not_used: 0,
454 material,
455 });
456 }
457
458 let entry = self.materials.get_mut(key).unwrap();
459 entry.frames_not_used = 0;
460 &entry.material
461 }
462
463 pub fn clean(&mut self) {
465 self.materials.retain(|_, data| data.frames_not_used <= self.max_frames_not_used);
466 for (_, data) in self.materials.iter_mut() {
467 data.frames_not_used += 1;
468 }
469 }
470
471 pub fn set_source(&mut self, name: &str, fragment: &str) {
477 if let Some(old_source) = self.shader_storage.get(name) {
478 if old_source == fragment {
479 return; }
481 self.materials.remove(old_source.as_str());
483 }
484 self.shader_storage.insert(name.to_string(), fragment.to_string());
485 }
486
487 pub fn get_source(&self, name: &str) -> Option<&str> {
489 self.shader_storage.get(name).map(String::as_str)
490 }
491}
492
493pub fn set_shader_source(name: &str, fragment: &str) {
511 MATERIAL_MANAGER.lock().unwrap().set_source(name, fragment);
512}
513
514fn apply_shader_uniforms(material: &Material, config: &ShaderConfig, bb: &BoundingBox) {
516 material.set_uniform("u_resolution", (bb.width, bb.height));
518 material.set_uniform("u_position", (bb.x, bb.y));
519
520 for u in &config.uniforms {
522 match &u.value {
523 ShaderUniformValue::Float(v) => material.set_uniform(&u.name, *v),
524 ShaderUniformValue::Vec2(v) => material.set_uniform(&u.name, *v),
525 ShaderUniformValue::Vec3(v) => material.set_uniform(&u.name, *v),
526 ShaderUniformValue::Vec4(v) => material.set_uniform(&u.name, *v),
527 ShaderUniformValue::Int(v) => material.set_uniform(&u.name, *v),
528 ShaderUniformValue::Mat4(v) => material.set_uniform(&u.name, *v),
529 }
530 }
531}
532
533fn ply_to_macroquad_color(ply_color: &crate::color::Color) -> Color {
534 Color {
535 r: ply_color.r / 255.0,
536 g: ply_color.g / 255.0,
537 b: ply_color.b / 255.0,
538 a: ply_color.a / 255.0,
539 }
540}
541
542fn draw_good_rounded_rectangle(x: f32, y: f32, w: f32, h: f32, cr: &CornerRadii, color: Color) {
545 use std::f32::consts::{FRAC_PI_2, PI};
546
547 if cr.top_left == 0.0 && cr.top_right == 0.0 && cr.bottom_left == 0.0 && cr.bottom_right == 0.0 {
548 draw_rectangle(x, y, w, h, color);
549 return;
550 }
551
552 let est_verts = [cr.top_left, cr.top_right, cr.bottom_left, cr.bottom_right]
555 .iter()
556 .map(|&r| if r <= 0.0 { 1 } else { ((FRAC_PI_2 * r) / PIXELS_PER_POINT).max(6.0) as usize + 1 })
557 .sum::<usize>();
558 let mut outline: Vec<Vec2> = Vec::with_capacity(est_verts);
559
560 let add_arc = |outline: &mut Vec<Vec2>, cx: f32, cy: f32, radius: f32, start_angle: f32, end_angle: f32| {
561 if radius <= 0.0 {
562 outline.push(Vec2::new(cx, cy));
563 return;
564 }
565 let sides = ((FRAC_PI_2 * radius) / PIXELS_PER_POINT).max(6.0) as usize;
566 let step = (end_angle - start_angle) / sides as f32;
568 let step_cos = step.cos();
569 let step_sin = step.sin();
570 let mut dx = start_angle.cos() * radius;
571 let mut dy = start_angle.sin() * radius;
572 for _ in 0..=sides {
573 outline.push(Vec2::new(cx + dx, cy + dy));
574 let new_dx = dx * step_cos - dy * step_sin;
575 let new_dy = dx * step_sin + dy * step_cos;
576 dx = new_dx;
577 dy = new_dy;
578 }
579 };
580
581 add_arc(&mut outline, x + cr.top_left, y + cr.top_left, cr.top_left,
583 PI, 3.0 * FRAC_PI_2);
584 add_arc(&mut outline, x + w - cr.top_right, y + cr.top_right, cr.top_right,
586 3.0 * FRAC_PI_2, 2.0 * PI);
587 add_arc(&mut outline, x + w - cr.bottom_right, y + h - cr.bottom_right, cr.bottom_right,
589 0.0, FRAC_PI_2);
590 add_arc(&mut outline, x + cr.bottom_left, y + h - cr.bottom_left, cr.bottom_left,
592 FRAC_PI_2, PI);
593
594 let n = outline.len();
595 if n < 3 { return; }
596
597 let color_bytes = [
598 (color.r * 255.0) as u8,
599 (color.g * 255.0) as u8,
600 (color.b * 255.0) as u8,
601 (color.a * 255.0) as u8,
602 ];
603
604 let cx = x + w / 2.0;
605 let cy = y + h / 2.0;
606
607 let mut vertices = Vec::with_capacity(n + 1);
608 vertices.push(Vertex {
610 position: Vec3::new(cx, cy, 0.0),
611 uv: Vec2::new(0.5, 0.5),
612 color: color_bytes,
613 normal: Vec4::new(0.0, 0.0, 1.0, 0.0),
614 });
615 for p in &outline {
617 vertices.push(Vertex {
618 position: Vec3::new(p.x, p.y, 0.0),
619 uv: Vec2::new((p.x - x) / w, (p.y - y) / h),
620 color: color_bytes,
621 normal: Vec4::new(0.0, 0.0, 1.0, 0.0),
622 });
623 }
624
625 let mut indices = Vec::with_capacity(n * 3);
626 for i in 0..n {
627 indices.push(0u16); indices.push((i + 1) as u16);
629 indices.push(((i + 1) % n + 1) as u16);
630 }
631
632 let mesh = Mesh {
633 vertices,
634 indices,
635 texture: None,
636 };
637 draw_mesh(&mesh);
638}
639
640fn draw_good_rotated_rounded_rectangle(
645 x: f32,
646 y: f32,
647 w: f32,
648 h: f32,
649 cr: &CornerRadii,
650 color: Color,
651 rotation_radians: f32,
652 flip_x: bool,
653 flip_y: bool,
654) {
655 use std::f32::consts::{FRAC_PI_2, PI};
656
657 let cx = x + w / 2.0;
658 let cy = y + h / 2.0;
659
660 let cos_r = rotation_radians.cos();
661 let sin_r = rotation_radians.sin();
662
663 let rotate_point = |px: f32, py: f32| -> Vec2 {
665 let mut dx = px - cx;
667 let mut dy = py - cy;
668 if flip_x { dx = -dx; }
669 if flip_y { dy = -dy; }
670 let rx = dx * cos_r - dy * sin_r;
671 let ry = dx * sin_r + dy * cos_r;
672 Vec2::new(cx + rx, cy + ry)
673 };
674
675 let est_verts = if cr.top_left == 0.0 && cr.top_right == 0.0 && cr.bottom_left == 0.0 && cr.bottom_right == 0.0 {
678 4
679 } else {
680 [cr.top_left, cr.top_right, cr.bottom_left, cr.bottom_right]
681 .iter()
682 .map(|&r| if r <= 0.0 { 1 } else { ((FRAC_PI_2 * r) / PIXELS_PER_POINT).max(6.0) as usize + 1 })
683 .sum::<usize>()
684 };
685 let mut outline: Vec<Vec2> = Vec::with_capacity(est_verts);
686
687 let add_arc = |outline: &mut Vec<Vec2>, arc_cx: f32, arc_cy: f32, radius: f32, start_angle: f32, end_angle: f32| {
688 if radius <= 0.0 {
689 outline.push(rotate_point(arc_cx, arc_cy));
690 return;
691 }
692 let sides = ((FRAC_PI_2 * radius) / PIXELS_PER_POINT).max(6.0) as usize;
693 let step = (end_angle - start_angle) / sides as f32;
695 let step_cos = step.cos();
696 let step_sin = step.sin();
697 let mut dx = start_angle.cos() * radius;
698 let mut dy = start_angle.sin() * radius;
699 for _ in 0..=sides {
700 outline.push(rotate_point(arc_cx + dx, arc_cy + dy));
701 let new_dx = dx * step_cos - dy * step_sin;
702 let new_dy = dx * step_sin + dy * step_cos;
703 dx = new_dx;
704 dy = new_dy;
705 }
706 };
707
708 if cr.top_left == 0.0 && cr.top_right == 0.0 && cr.bottom_left == 0.0 && cr.bottom_right == 0.0 {
709 outline.push(rotate_point(x, y));
711 outline.push(rotate_point(x + w, y));
712 outline.push(rotate_point(x + w, y + h));
713 outline.push(rotate_point(x, y + h));
714 } else {
715 add_arc(&mut outline, x + cr.top_left, y + cr.top_left, cr.top_left,
716 PI, 3.0 * FRAC_PI_2);
717 add_arc(&mut outline, x + w - cr.top_right, y + cr.top_right, cr.top_right,
718 3.0 * FRAC_PI_2, 2.0 * PI);
719 add_arc(&mut outline, x + w - cr.bottom_right, y + h - cr.bottom_right, cr.bottom_right,
720 0.0, FRAC_PI_2);
721 add_arc(&mut outline, x + cr.bottom_left, y + h - cr.bottom_left, cr.bottom_left,
722 FRAC_PI_2, PI);
723 }
724
725 let n = outline.len();
726 if n < 3 { return; }
727
728 let color_bytes = [
729 (color.r * 255.0) as u8,
730 (color.g * 255.0) as u8,
731 (color.b * 255.0) as u8,
732 (color.a * 255.0) as u8,
733 ];
734
735 let center_rot = Vec2::new(cx, cy);
736
737 let mut vertices = Vec::with_capacity(n + 1);
738 vertices.push(Vertex {
739 position: Vec3::new(center_rot.x, center_rot.y, 0.0),
740 uv: Vec2::new(0.5, 0.5),
741 color: color_bytes,
742 normal: Vec4::new(0.0, 0.0, 1.0, 0.0),
743 });
744 for p in &outline {
745 vertices.push(Vertex {
746 position: Vec3::new(p.x, p.y, 0.0),
747 uv: Vec2::new((p.x - x) / w, (p.y - y) / h),
748 color: color_bytes,
749 normal: Vec4::new(0.0, 0.0, 1.0, 0.0),
750 });
751 }
752
753 let mut indices = Vec::with_capacity(n * 3);
754 for i in 0..n {
755 indices.push(0u16);
756 indices.push((i + 1) as u16);
757 indices.push(((i + 1) % n + 1) as u16);
758 }
759
760 draw_mesh(&Mesh { vertices, indices, texture: None });
761}
762
763fn rotate_corner_radii_90(cr: &CornerRadii) -> CornerRadii {
765 CornerRadii {
766 top_left: cr.bottom_left,
767 top_right: cr.top_left,
768 bottom_right: cr.top_right,
769 bottom_left: cr.bottom_right,
770 }
771}
772
773fn rotate_corner_radii_180(cr: &CornerRadii) -> CornerRadii {
775 CornerRadii {
776 top_left: cr.bottom_right,
777 top_right: cr.bottom_left,
778 bottom_right: cr.top_left,
779 bottom_left: cr.top_right,
780 }
781}
782
783fn rotate_corner_radii_270(cr: &CornerRadii) -> CornerRadii {
785 CornerRadii {
786 top_left: cr.top_right,
787 top_right: cr.bottom_right,
788 bottom_right: cr.bottom_left,
789 bottom_left: cr.top_left,
790 }
791}
792
793fn flip_corner_radii(cr: &CornerRadii, flip_x: bool, flip_y: bool) -> CornerRadii {
795 let mut result = cr.clone();
796 if flip_x {
797 std::mem::swap(&mut result.top_left, &mut result.top_right);
798 std::mem::swap(&mut result.bottom_left, &mut result.bottom_right);
799 }
800 if flip_y {
801 std::mem::swap(&mut result.top_left, &mut result.bottom_left);
802 std::mem::swap(&mut result.top_right, &mut result.bottom_right);
803 }
804 result
805}
806
807struct RenderState {
808 clip: Option<(i32, i32, i32, i32)>,
809 rt_stack: Vec<(RenderTarget, Option<crate::shaders::ShaderConfig>, Option<crate::engine::VisualRotationConfig>, BoundingBox)>,
811 #[cfg(feature = "text-styling")]
812 style_stack: Vec<String>,
813 #[cfg(feature = "text-styling")]
814 total_char_index: usize,
815}
816
817impl RenderState {
818 fn new() -> Self {
819 Self {
820 clip: None,
821 rt_stack: Vec::new(),
822 #[cfg(feature = "text-styling")]
823 style_stack: Vec::new(),
824 #[cfg(feature = "text-styling")]
825 total_char_index: 0,
826 }
827 }
828}
829
830pub fn render_to_texture(width: f32, height: f32, draw: impl FnOnce()) -> Texture2D {
847 let render_target = render_target_msaa(width as u32, height as u32);
848 render_target.texture.set_filter(FilterMode::Linear);
849 let mut cam = Camera2D::from_display_rect(Rect::new(0.0, 0.0, width, height));
850 cam.render_target = Some(render_target.clone());
851 set_camera(&cam);
852
853 draw();
854
855 set_default_camera();
856 render_target.texture
857}
858
859fn rounded_rectangle_texture(cr: &CornerRadii, bb: &BoundingBox, clip: &Option<(i32, i32, i32, i32)>) -> Texture2D {
860 let render_target = render_target_msaa(bb.width as u32, bb.height as u32);
861 render_target.texture.set_filter(FilterMode::Linear);
862 let mut cam = Camera2D::from_display_rect(Rect::new(0.0, 0.0, bb.width, bb.height));
863 cam.render_target = Some(render_target.clone());
864 set_camera(&cam);
865 unsafe {
866 get_internal_gl().quad_gl.scissor(None);
867 };
868
869 draw_good_rounded_rectangle(0.0, 0.0, bb.width, bb.height, cr, WHITE);
870
871 set_default_camera();
872 unsafe {
873 get_internal_gl().quad_gl.scissor(*clip);
874 }
875 render_target.texture
876}
877
878#[cfg(feature = "tinyvg")]
881fn render_tinyvg_texture(
882 tvg_data: &[u8],
883 dest_width: f32,
884 dest_height: f32,
885 clip: &Option<(i32, i32, i32, i32)>,
886) -> Option<RenderTarget> {
887 use tinyvg::Decoder;
888 let decoder = Decoder::new(std::io::Cursor::new(tvg_data));
889 let image = match decoder.decode() {
890 Ok(img) => img,
891 Err(_) => return None,
892 };
893 render_tinyvg_image(&image, dest_width, dest_height, clip)
894}
895
896#[cfg(feature = "tinyvg")]
898fn render_tinyvg_image(
899 image: &tinyvg::format::Image,
900 dest_width: f32,
901 dest_height: f32,
902 clip: &Option<(i32, i32, i32, i32)>,
903) -> Option<RenderTarget> {
904 use tinyvg::format::{Command, Style, Segment, SegmentCommandKind, Point as TvgPoint, Color as TvgColor};
905 use kurbo::{BezPath, Point as KurboPoint, Vec2 as KurboVec2, ParamCurve, SvgArc, Arc as KurboArc, PathEl};
906 use lyon::tessellation::{FillTessellator, FillOptions, VertexBuffers, BuffersBuilder, FillVertex, FillRule};
907 use lyon::path::Path as LyonPath;
908 use lyon::math::point as lyon_point;
909
910 fn tvg_to_kurbo(p: TvgPoint) -> KurboPoint {
911 KurboPoint::new(p.x, p.y)
912 }
913
914 let tvg_width = image.header.width as f32;
915 let tvg_height = image.header.height as f32;
916 let scale_x = dest_width / tvg_width;
917 let scale_y = dest_height / tvg_height;
918
919 let render_target = render_target_msaa(dest_width as u32, dest_height as u32);
920 render_target.texture.set_filter(FilterMode::Linear);
921 let mut cam = Camera2D::from_display_rect(Rect::new(0.0, 0.0, dest_width, dest_height));
922 cam.render_target = Some(render_target.clone());
923 set_camera(&cam);
924 unsafe {
925 get_internal_gl().quad_gl.scissor(None);
926 }
927
928 let tvg_to_mq_color = |c: &TvgColor| -> Color {
929 let (r, g, b, a) = c.as_rgba();
930 Color::new(r as f32, g as f32, b as f32, a as f32)
931 };
932
933 let style_to_color = |style: &Style, color_table: &[TvgColor]| -> Color {
934 match style {
935 Style::FlatColor { color_index } => {
936 color_table.get(*color_index).map(|c| tvg_to_mq_color(c)).unwrap_or(WHITE)
937 }
938 Style::LinearGradient { color_index_0, .. } |
939 Style::RadialGradient { color_index_0, .. } => {
940 color_table.get(*color_index_0).map(|c| tvg_to_mq_color(c)).unwrap_or(WHITE)
941 }
942 }
943 };
944
945 let draw_filled_path_lyon = |bezpath: &BezPath, color: Color| {
946 let mut builder = LyonPath::builder();
947 let mut subpath_started = false;
948
949 for el in bezpath.iter() {
950 match el {
951 PathEl::MoveTo(p) => {
952 if subpath_started {
953 builder.end(false);
954 }
955 builder.begin(lyon_point((p.x * scale_x as f64) as f32, (p.y * scale_y as f64) as f32));
956 subpath_started = true;
957 }
958 PathEl::LineTo(p) => {
959 builder.line_to(lyon_point((p.x * scale_x as f64) as f32, (p.y * scale_y as f64) as f32));
960 }
961 PathEl::QuadTo(c, p) => {
962 builder.quadratic_bezier_to(
963 lyon_point((c.x * scale_x as f64) as f32, (c.y * scale_y as f64) as f32),
964 lyon_point((p.x * scale_x as f64) as f32, (p.y * scale_y as f64) as f32),
965 );
966 }
967 PathEl::CurveTo(c1, c2, p) => {
968 builder.cubic_bezier_to(
969 lyon_point((c1.x * scale_x as f64) as f32, (c1.y * scale_y as f64) as f32),
970 lyon_point((c2.x * scale_x as f64) as f32, (c2.y * scale_y as f64) as f32),
971 lyon_point((p.x * scale_x as f64) as f32, (p.y * scale_y as f64) as f32),
972 );
973 }
974 PathEl::ClosePath => {
975 builder.end(true);
976 subpath_started = false;
977 }
978 }
979 }
980
981 if subpath_started {
982 builder.end(true);
983 }
984
985 let lyon_path = builder.build();
986
987 let mut geometry: VertexBuffers<[f32; 2], u16> = VertexBuffers::new();
988 let mut tessellator = FillTessellator::new();
989
990 let fill_options = FillOptions::default().with_fill_rule(FillRule::NonZero);
991
992 let result = tessellator.tessellate_path(
993 &lyon_path,
994 &fill_options,
995 &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
996 vertex.position().to_array()
997 }),
998 );
999
1000 if result.is_err() || geometry.indices.is_empty() {
1001 return;
1002 }
1003
1004 let color_bytes = [(color.r * 255.0) as u8, (color.g * 255.0) as u8, (color.b * 255.0) as u8, (color.a * 255.0) as u8];
1005
1006 let vertices: Vec<Vertex> = geometry.vertices.iter().map(|pos| {
1007 Vertex {
1008 position: Vec3::new(pos[0], pos[1], 0.0),
1009 uv: Vec2::ZERO,
1010 color: color_bytes,
1011 normal: Vec4::new(0.0, 0.0, 1.0, 0.0),
1012 }
1013 }).collect();
1014
1015 let mesh = Mesh {
1016 vertices,
1017 indices: geometry.indices,
1018 texture: None,
1019 };
1020 draw_mesh(&mesh);
1021 };
1022
1023 let draw_filled_polygon_tvg = |points: &[TvgPoint], color: Color| {
1024 if points.len() < 3 {
1025 return;
1026 }
1027
1028 let mut builder = LyonPath::builder();
1029 builder.begin(lyon_point(points[0].x as f32 * scale_x, points[0].y as f32 * scale_y));
1030 for point in &points[1..] {
1031 builder.line_to(lyon_point(point.x as f32 * scale_x, point.y as f32 * scale_y));
1032 }
1033 builder.end(true);
1034 let lyon_path = builder.build();
1035
1036 let mut geometry: VertexBuffers<[f32; 2], u16> = VertexBuffers::new();
1037 let mut tessellator = FillTessellator::new();
1038
1039 let result = tessellator.tessellate_path(
1040 &lyon_path,
1041 &FillOptions::default(),
1042 &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
1043 vertex.position().to_array()
1044 }),
1045 );
1046
1047 if result.is_err() || geometry.indices.is_empty() {
1048 return;
1049 }
1050
1051 let color_bytes = [(color.r * 255.0) as u8, (color.g * 255.0) as u8, (color.b * 255.0) as u8, (color.a * 255.0) as u8];
1052
1053 let vertices: Vec<Vertex> = geometry.vertices.iter().map(|pos| {
1054 Vertex {
1055 position: Vec3::new(pos[0], pos[1], 0.0),
1056 uv: Vec2::ZERO,
1057 color: color_bytes,
1058 normal: Vec4::new(0.0, 0.0, 1.0, 0.0),
1059 }
1060 }).collect();
1061
1062 let mesh = Mesh {
1063 vertices,
1064 indices: geometry.indices,
1065 texture: None,
1066 };
1067 draw_mesh(&mesh);
1068 };
1069
1070 let build_bezpath = |segments: &[Segment]| -> BezPath {
1071 let mut bezier = BezPath::new();
1072 for segment in segments {
1073 let start = tvg_to_kurbo(segment.start);
1074 let mut pen = start;
1075 bezier.move_to(pen);
1076
1077 for cmd in &segment.commands {
1078 match &cmd.kind {
1079 SegmentCommandKind::Line { end } => {
1080 let end_k = tvg_to_kurbo(*end);
1081 bezier.line_to(end_k);
1082 pen = end_k;
1083 }
1084 SegmentCommandKind::HorizontalLine { x } => {
1085 let end = KurboPoint::new(*x, pen.y);
1086 bezier.line_to(end);
1087 pen = end;
1088 }
1089 SegmentCommandKind::VerticalLine { y } => {
1090 let end = KurboPoint::new(pen.x, *y);
1091 bezier.line_to(end);
1092 pen = end;
1093 }
1094 SegmentCommandKind::CubicBezier { control_0, control_1, point_1 } => {
1095 let c0 = tvg_to_kurbo(*control_0);
1096 let c1 = tvg_to_kurbo(*control_1);
1097 let p1 = tvg_to_kurbo(*point_1);
1098 bezier.curve_to(c0, c1, p1);
1099 pen = p1;
1100 }
1101 SegmentCommandKind::QuadraticBezier { control, point_1 } => {
1102 let c = tvg_to_kurbo(*control);
1103 let p1 = tvg_to_kurbo(*point_1);
1104 bezier.quad_to(c, p1);
1105 pen = p1;
1106 }
1107 SegmentCommandKind::ArcEllipse { large, sweep, radius_x, radius_y, rotation, target } => {
1108 let target_k = tvg_to_kurbo(*target);
1109 let svg_arc = SvgArc {
1110 from: pen,
1111 to: target_k,
1112 radii: KurboVec2::new(*radius_x, *radius_y),
1113 x_rotation: *rotation,
1114 large_arc: *large,
1115 sweep: *sweep,
1116 };
1117 if let Some(arc) = KurboArc::from_svg_arc(&svg_arc) {
1118 for seg in arc.append_iter(0.2) {
1119 bezier.push(seg);
1120 }
1121 }
1122 pen = target_k;
1123 }
1124 SegmentCommandKind::ClosePath => {
1125 bezier.close_path();
1126 pen = start;
1127 }
1128 }
1129 }
1130 }
1131 bezier
1132 };
1133
1134 let line_scale = (scale_x + scale_y) / 2.0;
1135
1136 for cmd in &image.commands {
1137 match cmd {
1138 Command::FillPath { fill_style, path, outline } => {
1139 let fill_color = style_to_color(fill_style, &image.color_table);
1140 let bezpath = build_bezpath(path);
1141 draw_filled_path_lyon(&bezpath, fill_color);
1142
1143 if let Some(outline_style) = outline {
1144 let line_color = style_to_color(&outline_style.line_style, &image.color_table);
1145 let line_width = outline_style.line_width as f32 * line_scale;
1146 for segment in path {
1147 let start = segment.start;
1148 let mut pen = start;
1149 for cmd in &segment.commands {
1150 match &cmd.kind {
1151 SegmentCommandKind::Line { end } => {
1152 draw_line(
1153 pen.x as f32 * scale_x, pen.y as f32 * scale_y,
1154 end.x as f32 * scale_x, end.y as f32 * scale_y,
1155 line_width, line_color
1156 );
1157 pen = *end;
1158 }
1159 SegmentCommandKind::HorizontalLine { x } => {
1160 let end = TvgPoint { x: *x, y: pen.y };
1161 draw_line(
1162 pen.x as f32 * scale_x, pen.y as f32 * scale_y,
1163 end.x as f32 * scale_x, end.y as f32 * scale_y,
1164 line_width, line_color
1165 );
1166 pen = end;
1167 }
1168 SegmentCommandKind::VerticalLine { y } => {
1169 let end = TvgPoint { x: pen.x, y: *y };
1170 draw_line(
1171 pen.x as f32 * scale_x, pen.y as f32 * scale_y,
1172 end.x as f32 * scale_x, end.y as f32 * scale_y,
1173 line_width, line_color
1174 );
1175 pen = end;
1176 }
1177 SegmentCommandKind::ClosePath => {
1178 draw_line(
1179 pen.x as f32 * scale_x, pen.y as f32 * scale_y,
1180 start.x as f32 * scale_x, start.y as f32 * scale_y,
1181 line_width, line_color
1182 );
1183 pen = start;
1184 }
1185 SegmentCommandKind::CubicBezier { control_0, control_1, point_1 } => {
1186 let c0 = tvg_to_kurbo(*control_0);
1187 let c1 = tvg_to_kurbo(*control_1);
1188 let p1 = tvg_to_kurbo(*point_1);
1189 let p0 = tvg_to_kurbo(pen);
1190 let cubic = kurbo::CubicBez::new(p0, c0, c1, p1);
1191 let steps = 16usize;
1192 let mut prev = p0;
1193 for i in 1..=steps {
1194 let t = i as f64 / steps as f64;
1195 let next = cubic.eval(t);
1196 draw_line(
1197 prev.x as f32 * scale_x, prev.y as f32 * scale_y,
1198 next.x as f32 * scale_x, next.y as f32 * scale_y,
1199 line_width, line_color
1200 );
1201 prev = next;
1202 }
1203 pen = *point_1;
1204 }
1205 SegmentCommandKind::QuadraticBezier { control, point_1 } => {
1206 let c = tvg_to_kurbo(*control);
1207 let p1 = tvg_to_kurbo(*point_1);
1208 let p0 = tvg_to_kurbo(pen);
1209 let quad = kurbo::QuadBez::new(p0, c, p1);
1210 let steps = 12usize;
1211 let mut prev = p0;
1212 for i in 1..=steps {
1213 let t = i as f64 / steps as f64;
1214 let next = quad.eval(t);
1215 draw_line(
1216 prev.x as f32 * scale_x, prev.y as f32 * scale_y,
1217 next.x as f32 * scale_x, next.y as f32 * scale_y,
1218 line_width, line_color
1219 );
1220 prev = next;
1221 }
1222 pen = *point_1;
1223 }
1224 SegmentCommandKind::ArcEllipse { large, sweep, radius_x, radius_y, rotation, target } => {
1225 let target_k = tvg_to_kurbo(*target);
1226 let p0 = tvg_to_kurbo(pen);
1227 let svg_arc = SvgArc {
1228 from: p0,
1229 to: target_k,
1230 radii: KurboVec2::new(*radius_x, *radius_y),
1231 x_rotation: *rotation,
1232 large_arc: *large,
1233 sweep: *sweep,
1234 };
1235 if let Some(arc) = KurboArc::from_svg_arc(&svg_arc) {
1236 let mut prev = p0;
1237 for seg in arc.append_iter(0.2) {
1238 match seg {
1239 PathEl::LineTo(p) | PathEl::MoveTo(p) => {
1240 draw_line(
1241 prev.x as f32 * scale_x, prev.y as f32 * scale_y,
1242 p.x as f32 * scale_x, p.y as f32 * scale_y,
1243 line_width, line_color
1244 );
1245 prev = p;
1246 }
1247 PathEl::CurveTo(c0, c1, p) => {
1248 let cubic = kurbo::CubicBez::new(prev, c0, c1, p);
1250 let steps = 8usize;
1251 let mut prev_pt = prev;
1252 for j in 1..=steps {
1253 let t = j as f64 / steps as f64;
1254 let next = cubic.eval(t);
1255 draw_line(
1256 prev_pt.x as f32 * scale_x, prev_pt.y as f32 * scale_y,
1257 next.x as f32 * scale_x, next.y as f32 * scale_y,
1258 line_width, line_color
1259 );
1260 prev_pt = next;
1261 }
1262 prev = p;
1263 }
1264 _ => {}
1265 }
1266 }
1267 }
1268 pen = *target;
1269 }
1270 }
1271 }
1272 }
1273 }
1274 }
1275 Command::FillRectangles { fill_style, rectangles, outline } => {
1276 let fill_color = style_to_color(fill_style, &image.color_table);
1277 for rect in rectangles {
1278 draw_rectangle(
1279 rect.x0 as f32 * scale_x,
1280 rect.y0 as f32 * scale_y,
1281 rect.width() as f32 * scale_x,
1282 rect.height() as f32 * scale_y,
1283 fill_color
1284 );
1285 }
1286
1287 if let Some(outline_style) = outline {
1288 let line_color = style_to_color(&outline_style.line_style, &image.color_table);
1289 let line_width = outline_style.line_width as f32 * line_scale;
1290 for rect in rectangles {
1291 draw_rectangle_lines(
1292 rect.x0 as f32 * scale_x,
1293 rect.y0 as f32 * scale_y,
1294 rect.width() as f32 * scale_x,
1295 rect.height() as f32 * scale_y,
1296 line_width, line_color
1297 );
1298 }
1299 }
1300 }
1301 Command::FillPolygon { fill_style, polygon, outline } => {
1302 let fill_color = style_to_color(fill_style, &image.color_table);
1303 draw_filled_polygon_tvg(polygon, fill_color);
1304
1305 if let Some(outline_style) = outline {
1306 let line_color = style_to_color(&outline_style.line_style, &image.color_table);
1307 let line_width = outline_style.line_width as f32 * line_scale;
1308 for i in 0..polygon.len() {
1309 let next = (i + 1) % polygon.len();
1310 draw_line(
1311 polygon[i].x as f32 * scale_x, polygon[i].y as f32 * scale_y,
1312 polygon[next].x as f32 * scale_x, polygon[next].y as f32 * scale_y,
1313 line_width, line_color
1314 );
1315 }
1316 }
1317 }
1318 Command::DrawLines { line_style, line_width, lines } => {
1319 let line_color = style_to_color(line_style, &image.color_table);
1320 for line in lines {
1321 draw_line(
1322 line.p0.x as f32 * scale_x, line.p0.y as f32 * scale_y,
1323 line.p1.x as f32 * scale_x, line.p1.y as f32 * scale_y,
1324 *line_width as f32 * line_scale, line_color
1325 );
1326 }
1327 }
1328 Command::DrawLineLoop { line_style, line_width, close_path, points } => {
1329 let line_color = style_to_color(line_style, &image.color_table);
1330 for i in 0..points.len().saturating_sub(1) {
1331 draw_line(
1332 points[i].x as f32 * scale_x, points[i].y as f32 * scale_y,
1333 points[i+1].x as f32 * scale_x, points[i+1].y as f32 * scale_y,
1334 *line_width as f32 * line_scale, line_color
1335 );
1336 }
1337 if *close_path && points.len() >= 2 {
1338 let last = points.len() - 1;
1339 draw_line(
1340 points[last].x as f32 * scale_x, points[last].y as f32 * scale_y,
1341 points[0].x as f32 * scale_x, points[0].y as f32 * scale_y,
1342 *line_width as f32 * line_scale, line_color
1343 );
1344 }
1345 }
1346 Command::DrawLinePath { line_style, line_width, path } => {
1347 let line_color = style_to_color(line_style, &image.color_table);
1348 let scaled_line_width = *line_width as f32 * line_scale;
1349 for segment in path {
1351 let start = segment.start;
1352 let mut pen = start;
1353 for cmd in &segment.commands {
1354 match &cmd.kind {
1355 SegmentCommandKind::Line { end } => {
1356 draw_line(
1357 pen.x as f32 * scale_x, pen.y as f32 * scale_y,
1358 end.x as f32 * scale_x, end.y as f32 * scale_y,
1359 scaled_line_width, line_color
1360 );
1361 pen = *end;
1362 }
1363 SegmentCommandKind::HorizontalLine { x } => {
1364 let end = TvgPoint { x: *x, y: pen.y };
1365 draw_line(
1366 pen.x as f32 * scale_x, pen.y as f32 * scale_y,
1367 end.x as f32 * scale_x, end.y as f32 * scale_y,
1368 scaled_line_width, line_color
1369 );
1370 pen = end;
1371 }
1372 SegmentCommandKind::VerticalLine { y } => {
1373 let end = TvgPoint { x: pen.x, y: *y };
1374 draw_line(
1375 pen.x as f32 * scale_x, pen.y as f32 * scale_y,
1376 end.x as f32 * scale_x, end.y as f32 * scale_y,
1377 scaled_line_width, line_color
1378 );
1379 pen = end;
1380 }
1381 SegmentCommandKind::ClosePath => {
1382 draw_line(
1383 pen.x as f32 * scale_x, pen.y as f32 * scale_y,
1384 start.x as f32 * scale_x, start.y as f32 * scale_y,
1385 scaled_line_width, line_color
1386 );
1387 pen = start;
1388 }
1389 SegmentCommandKind::CubicBezier { control_0, control_1, point_1 } => {
1391 let c0 = tvg_to_kurbo(*control_0);
1392 let c1 = tvg_to_kurbo(*control_1);
1393 let p1 = tvg_to_kurbo(*point_1);
1394 let p0 = tvg_to_kurbo(pen);
1395 let cubic = kurbo::CubicBez::new(p0, c0, c1, p1);
1396 let steps = 16usize;
1397 let mut prev = p0;
1398 for i in 1..=steps {
1399 let t = i as f64 / steps as f64;
1400 let next = cubic.eval(t);
1401 draw_line(
1402 prev.x as f32 * scale_x, prev.y as f32 * scale_y,
1403 next.x as f32 * scale_x, next.y as f32 * scale_y,
1404 scaled_line_width, line_color
1405 );
1406 prev = next;
1407 }
1408 pen = *point_1;
1409 }
1410 SegmentCommandKind::QuadraticBezier { control, point_1 } => {
1411 let c = tvg_to_kurbo(*control);
1412 let p1 = tvg_to_kurbo(*point_1);
1413 let p0 = tvg_to_kurbo(pen);
1414 let quad = kurbo::QuadBez::new(p0, c, p1);
1415 let steps = 12usize;
1416 let mut prev = p0;
1417 for i in 1..=steps {
1418 let t = i as f64 / steps as f64;
1419 let next = quad.eval(t);
1420 draw_line(
1421 prev.x as f32 * scale_x, prev.y as f32 * scale_y,
1422 next.x as f32 * scale_x, next.y as f32 * scale_y,
1423 scaled_line_width, line_color
1424 );
1425 prev = next;
1426 }
1427 pen = *point_1;
1428 }
1429 SegmentCommandKind::ArcEllipse { large, sweep, radius_x, radius_y, rotation, target } => {
1430 let target_k = tvg_to_kurbo(*target);
1431 let p0 = tvg_to_kurbo(pen);
1432 let svg_arc = SvgArc {
1433 from: p0,
1434 to: target_k,
1435 radii: KurboVec2::new(*radius_x, *radius_y),
1436 x_rotation: *rotation,
1437 large_arc: *large,
1438 sweep: *sweep,
1439 };
1440 if let Some(arc) = KurboArc::from_svg_arc(&svg_arc) {
1441 let mut prev = p0;
1442 for seg in arc.append_iter(0.2) {
1443 match seg {
1444 PathEl::LineTo(p) | PathEl::MoveTo(p) => {
1445 draw_line(
1446 prev.x as f32 * scale_x, prev.y as f32 * scale_y,
1447 p.x as f32 * scale_x, p.y as f32 * scale_y,
1448 scaled_line_width, line_color
1449 );
1450 prev = p;
1451 }
1452 PathEl::CurveTo(c0, c1, p) => {
1453 let cubic = kurbo::CubicBez::new(prev, c0, c1, p);
1455 let steps = 8usize;
1456 let mut prev_pt = prev;
1457 for j in 1..=steps {
1458 let t = j as f64 / steps as f64;
1459 let next = cubic.eval(t);
1460 draw_line(
1461 prev_pt.x as f32 * scale_x, prev_pt.y as f32 * scale_y,
1462 next.x as f32 * scale_x, next.y as f32 * scale_y,
1463 scaled_line_width, line_color
1464 );
1465 prev_pt = next;
1466 }
1467 prev = p;
1468 }
1469 _ => {}
1470 }
1471 }
1472 }
1473 pen = *target;
1474 }
1475 }
1476 }
1477 }
1478 }
1479 }
1480 }
1481
1482 set_default_camera();
1483 unsafe {
1484 get_internal_gl().quad_gl.scissor(*clip);
1485 }
1486
1487 Some(render_target)
1488}
1489
1490fn resize(texture: &Texture2D, height: f32, width: f32, clip: &Option<(i32, i32, i32, i32)>) -> Texture2D {
1491 let render_target = render_target_msaa(width as u32, height as u32);
1492 render_target.texture.set_filter(FilterMode::Linear);
1493 let mut cam = Camera2D::from_display_rect(Rect::new(0.0, 0.0, width, height));
1494 cam.render_target = Some(render_target.clone());
1495 set_camera(&cam);
1496 unsafe {
1497 get_internal_gl().quad_gl.scissor(None);
1498 };
1499 draw_texture_ex(
1500 texture,
1501 0.0,
1502 0.0,
1503 WHITE,
1504 DrawTextureParams {
1505 dest_size: Some(Vec2::new(width, height)),
1506 flip_y: true,
1507 ..Default::default()
1508 },
1509 );
1510 set_default_camera();
1511 unsafe {
1512 get_internal_gl().quad_gl.scissor(*clip);
1513 }
1514 render_target.texture
1515}
1516
1517pub async fn render<CustomElementData: Clone + Default + std::fmt::Debug>(
1519 commands: Vec<RenderCommand<CustomElementData>>,
1520 handle_custom_command: impl Fn(&RenderCommand<CustomElementData>),
1521) {
1522 let mut state = RenderState::new();
1523 for command in commands {
1524 match &command.config {
1525 RenderCommandConfig::Image(image) => {
1526 let bb = command.bounding_box;
1527 let cr = &image.corner_radii;
1528 let mut tint = ply_to_macroquad_color(&image.background_color);
1529 if tint == Color::new(0.0, 0.0, 0.0, 0.0) {
1530 tint = Color::new(1.0, 1.0, 1.0, 1.0);
1531 }
1532
1533 match &image.data {
1534 ImageSource::Texture(tex) => {
1535 let has_corner_radii = cr.top_left > 0.0 || cr.top_right > 0.0 || cr.bottom_left > 0.0 || cr.bottom_right > 0.0;
1537 if !has_corner_radii {
1538 draw_texture_ex(
1539 tex,
1540 bb.x,
1541 bb.y,
1542 tint,
1543 DrawTextureParams {
1544 dest_size: Some(Vec2::new(bb.width, bb.height)),
1545 ..Default::default()
1546 },
1547 );
1548 } else {
1549 let mut manager = TEXTURE_MANAGER.lock().unwrap();
1550 let key = format!(
1552 "tex-proc:{:?}:{}:{}:{}:{}:{}:{}:{:?}",
1553 tex.raw_miniquad_id(),
1554 bb.width, bb.height,
1555 cr.top_left, cr.top_right, cr.bottom_left, cr.bottom_right,
1556 state.clip
1557 );
1558 let texture = manager.get_or_create(key, || {
1559 let mut resized_image: Image = resize(tex, bb.height, bb.width, &state.clip).get_texture_data();
1560 let rounded_rect: Image = rounded_rectangle_texture(cr, &bb, &state.clip).get_texture_data();
1561 for i in 0..resized_image.bytes.len()/4 {
1562 let this_alpha = resized_image.bytes[i * 4 + 3] as f32 / 255.0;
1563 let mask_alpha = rounded_rect.bytes[i * 4 + 3] as f32 / 255.0;
1564 resized_image.bytes[i * 4 + 3] = (this_alpha * mask_alpha * 255.0) as u8;
1565 }
1566 Texture2D::from_image(&resized_image)
1567 });
1568 draw_texture_ex(
1569 texture,
1570 bb.x,
1571 bb.y,
1572 tint,
1573 DrawTextureParams {
1574 dest_size: Some(Vec2::new(bb.width, bb.height)),
1575 ..Default::default()
1576 },
1577 );
1578 }
1579 }
1580 #[cfg(feature = "tinyvg")]
1581 ImageSource::TinyVg(tvg_image) => {
1582 let has_corner_radii = cr.top_left > 0.0 || cr.top_right > 0.0 || cr.bottom_left > 0.0 || cr.bottom_right > 0.0;
1584 if let Some(tvg_rt) = render_tinyvg_image(tvg_image, bb.width, bb.height, &state.clip) {
1585 let final_texture = if has_corner_radii {
1586 let mut tvg_img: Image = tvg_rt.texture.get_texture_data();
1587 let rounded_rect: Image = rounded_rectangle_texture(cr, &bb, &state.clip).get_texture_data();
1588 for i in 0..tvg_img.bytes.len()/4 {
1589 let this_alpha = tvg_img.bytes[i * 4 + 3] as f32 / 255.0;
1590 let mask_alpha = rounded_rect.bytes[i * 4 + 3] as f32 / 255.0;
1591 tvg_img.bytes[i * 4 + 3] = (this_alpha * mask_alpha * 255.0) as u8;
1592 }
1593 Texture2D::from_image(&tvg_img)
1594 } else {
1595 tvg_rt.texture.clone()
1596 };
1597 draw_texture_ex(
1598 &final_texture,
1599 bb.x,
1600 bb.y,
1601 tint,
1602 DrawTextureParams {
1603 dest_size: Some(Vec2::new(bb.width, bb.height)),
1604 flip_y: true,
1605 ..Default::default()
1606 },
1607 );
1608 }
1609 }
1610 ImageSource::Asset(ga) => {
1611 let mut manager = TEXTURE_MANAGER.lock().unwrap();
1613
1614 #[cfg(feature = "tinyvg")]
1615 let is_tvg = ga.get_name().to_lowercase().ends_with(".tvg");
1616 #[cfg(not(feature = "tinyvg"))]
1617 let is_tvg = false;
1618
1619 #[cfg(feature = "tinyvg")]
1620 if is_tvg {
1621 let key = format!(
1622 "tvg:{}:{}:{}:{}:{}:{}:{}:{:?}",
1623 ga.get_name(),
1624 bb.width, bb.height,
1625 cr.top_left, cr.top_right, cr.bottom_left, cr.bottom_right,
1626 state.clip
1627 );
1628 let has_corner_radii = cr.top_left > 0.0 || cr.top_right > 0.0 || cr.bottom_left > 0.0 || cr.bottom_right > 0.0;
1629 let texture = if !has_corner_radii {
1630 if let Some(cached) = manager.get(&key) {
1632 cached
1633 } else {
1634 match ga {
1635 GraphicAsset::Path(path) => {
1636 match load_file(resolve_asset_path(path)).await {
1637 Ok(tvg_bytes) => {
1638 if let Some(tvg_rt) = render_tinyvg_texture(&tvg_bytes, bb.width, bb.height, &state.clip) {
1639 manager.cache(key.clone(), tvg_rt)
1640 } else {
1641 warn!("Failed to load TinyVG image: {}", path);
1642 manager.cache(key.clone(), Texture2D::from_rgba8(1, 1, &[0, 0, 0, 0]))
1643 }
1644 }
1645 Err(error) => {
1646 warn!("Failed to load TinyVG file: {}. Error: {}", path, error);
1647 manager.cache(key.clone(), Texture2D::from_rgba8(1, 1, &[0, 0, 0, 0]))
1648 }
1649 }
1650 }
1651 GraphicAsset::Bytes { file_name, data: tvg_bytes } => {
1652 if let Some(tvg_rt) = render_tinyvg_texture(tvg_bytes, bb.width, bb.height, &state.clip) {
1653 manager.cache(key.clone(), tvg_rt)
1654 } else {
1655 warn!("Failed to load TinyVG image: {}", file_name);
1656 manager.cache(key.clone(), Texture2D::from_rgba8(1, 1, &[0, 0, 0, 0]))
1657 }
1658 }
1659 }
1660 }
1661 } else {
1662 let zerocr_key = format!(
1663 "tvg:{}:{}:{}:{}:{}:{}:{}:{:?}",
1664 ga.get_name(),
1665 bb.width, bb.height,
1666 0.0, 0.0, 0.0, 0.0,
1667 state.clip
1668 );
1669 let base_texture = if let Some(cached) = manager.get(&zerocr_key) {
1670 cached
1671 } else {
1672 match ga {
1673 GraphicAsset::Path(path) => {
1674 match load_file(resolve_asset_path(path)).await {
1675 Ok(tvg_bytes) => {
1676 if let Some(tvg_rt) = render_tinyvg_texture(&tvg_bytes, bb.width, bb.height, &state.clip) {
1677 manager.cache(zerocr_key.clone(), tvg_rt)
1678 } else {
1679 warn!("Failed to load TinyVG image: {}", path);
1680 manager.cache(zerocr_key.clone(), Texture2D::from_rgba8(1, 1, &[0, 0, 0, 0]))
1681 }
1682 }
1683 Err(error) => {
1684 warn!("Failed to load TinyVG file: {}. Error: {}", path, error);
1685 manager.cache(zerocr_key.clone(), Texture2D::from_rgba8(1, 1, &[0, 0, 0, 0]))
1686 }
1687 }
1688 }
1689 GraphicAsset::Bytes { file_name, data: tvg_bytes } => {
1690 if let Some(tvg_rt) = render_tinyvg_texture(tvg_bytes, bb.width, bb.height, &state.clip) {
1691 manager.cache(zerocr_key.clone(), tvg_rt)
1692 } else {
1693 warn!("Failed to load TinyVG image: {}", file_name);
1694 manager.cache(zerocr_key.clone(), Texture2D::from_rgba8(1, 1, &[0, 0, 0, 0]))
1695 }
1696 }
1697 }
1698 }.clone();
1699 manager.get_or_create(key, || {
1700 let mut tvg_image: Image = base_texture.get_texture_data();
1701 let rounded_rect: Image = rounded_rectangle_texture(cr, &bb, &state.clip).get_texture_data();
1702 for i in 0..tvg_image.bytes.len()/4 {
1703 let this_alpha = tvg_image.bytes[i * 4 + 3] as f32 / 255.0;
1704 let mask_alpha = rounded_rect.bytes[i * 4 + 3] as f32 / 255.0;
1705 tvg_image.bytes[i * 4 + 3] = (this_alpha * mask_alpha * 255.0) as u8;
1706 }
1707 Texture2D::from_image(&tvg_image)
1708 })
1709 };
1710 draw_texture_ex(
1711 texture,
1712 bb.x,
1713 bb.y,
1714 tint,
1715 DrawTextureParams {
1716 dest_size: Some(Vec2::new(bb.width, bb.height)),
1717 flip_y: true,
1718 ..Default::default()
1719 },
1720 );
1721 continue;
1722 }
1723
1724 if !is_tvg && cr.top_left == 0.0 && cr.top_right == 0.0 && cr.bottom_left == 0.0 && cr.bottom_right == 0.0 {
1725 let texture = match ga {
1726 GraphicAsset::Path(path) => manager.get_or_load(path).await,
1727 GraphicAsset::Bytes { file_name, data } => {
1728 manager.get_or_create(file_name.to_string(), || {
1729 Texture2D::from_file_with_format(data, None)
1730 })
1731 }
1732 };
1733 draw_texture_ex(
1734 texture,
1735 bb.x,
1736 bb.y,
1737 tint,
1738 DrawTextureParams {
1739 dest_size: Some(Vec2::new(bb.width, bb.height)),
1740 ..Default::default()
1741 },
1742 );
1743 } else {
1744 let source_texture = match ga {
1745 GraphicAsset::Path(path) => manager.get_or_load(path).await.clone(),
1746 GraphicAsset::Bytes { file_name, data } => {
1747 manager.get_or_create(file_name.to_string(), || {
1748 Texture2D::from_file_with_format(data, None)
1749 }).clone()
1750 }
1751 };
1752 let key = format!(
1753 "image:{}:{}:{}:{}:{}:{}:{}:{:?}",
1754 ga.get_name(),
1755 bb.width, bb.height,
1756 cr.top_left, cr.top_right, cr.bottom_left, cr.bottom_right,
1757 state.clip
1758 );
1759 let texture = manager.get_or_create(key, || {
1760 let mut resized_image: Image = resize(&source_texture, bb.height, bb.width, &state.clip).get_texture_data();
1761 let rounded_rect: Image = rounded_rectangle_texture(cr, &bb, &state.clip).get_texture_data();
1762 for i in 0..resized_image.bytes.len()/4 {
1763 let this_alpha = resized_image.bytes[i * 4 + 3] as f32 / 255.0;
1764 let mask_alpha = rounded_rect.bytes[i * 4 + 3] as f32 / 255.0;
1765 resized_image.bytes[i * 4 + 3] = (this_alpha * mask_alpha * 255.0) as u8;
1766 }
1767 Texture2D::from_image(&resized_image)
1768 });
1769 draw_texture_ex(
1770 texture,
1771 bb.x,
1772 bb.y,
1773 tint,
1774 DrawTextureParams {
1775 dest_size: Some(Vec2::new(bb.width, bb.height)),
1776 ..Default::default()
1777 },
1778 );
1779 }
1780 }
1781 }
1782 }
1783 RenderCommandConfig::Rectangle(config) => {
1784 let bb = command.bounding_box;
1785 let color = ply_to_macroquad_color(&config.color);
1786 let cr = &config.corner_radii;
1787
1788 let has_effect = !command.effects.is_empty();
1790 if has_effect {
1791 let effect = &command.effects[0];
1792 let mut mat_mgr = MATERIAL_MANAGER.lock().unwrap();
1793 let material = mat_mgr.get_or_create(effect);
1794 apply_shader_uniforms(material, effect, &bb);
1795 gl_use_material(material);
1796 }
1797
1798 if let Some(ref sr) = command.shape_rotation {
1799 use crate::math::{classify_angle, AngleType};
1800 let flip_x = sr.flip_x;
1801 let flip_y = sr.flip_y;
1802 match classify_angle(sr.rotation_radians) {
1803 AngleType::Zero => {
1804 let cr = flip_corner_radii(cr, flip_x, flip_y);
1806 draw_good_rounded_rectangle(bb.x, bb.y, bb.width, bb.height, &cr, color);
1807 }
1808 AngleType::Right90 => {
1809 let cr = rotate_corner_radii_90(&flip_corner_radii(cr, flip_x, flip_y));
1810 draw_good_rounded_rectangle(bb.x, bb.y, bb.width, bb.height, &cr, color);
1811 }
1812 AngleType::Straight180 => {
1813 let cr = rotate_corner_radii_180(&flip_corner_radii(cr, flip_x, flip_y));
1814 draw_good_rounded_rectangle(bb.x, bb.y, bb.width, bb.height, &cr, color);
1815 }
1816 AngleType::Right270 => {
1817 let cr = rotate_corner_radii_270(&flip_corner_radii(cr, flip_x, flip_y));
1818 draw_good_rounded_rectangle(bb.x, bb.y, bb.width, bb.height, &cr, color);
1819 }
1820 AngleType::Arbitrary(theta) => {
1821 draw_good_rotated_rounded_rectangle(
1822 bb.x, bb.y, bb.width, bb.height,
1823 cr, color, theta, flip_x, flip_y,
1824 );
1825 }
1826 }
1827 } else if cr.top_left == 0.0 && cr.top_right == 0.0 && cr.bottom_left == 0.0 && cr.bottom_right == 0.0 {
1828 draw_rectangle(
1829 bb.x,
1830 bb.y,
1831 bb.width,
1832 bb.height,
1833 color
1834 );
1835 } else {
1836 draw_good_rounded_rectangle(bb.x, bb.y, bb.width, bb.height, cr, color);
1837 }
1838
1839 if has_effect {
1841 gl_use_default_material();
1842 }
1843 }
1844 #[cfg(feature = "text-styling")]
1845 RenderCommandConfig::Text(config) => {
1846 let bb = command.bounding_box;
1847 let font_size = config.font_size as f32;
1848 if let Some(asset) = config.font_asset {
1850 FontManager::ensure(asset).await;
1851 }
1852 let mut fm = FONT_MANAGER.lock().unwrap();
1854 let font = if let Some(asset) = config.font_asset {
1855 fm.get(asset)
1856 } else {
1857 fm.get_default()
1858 };
1859 let default_color = ply_to_macroquad_color(&config.color);
1860
1861 let has_effect = !command.effects.is_empty();
1863 if has_effect {
1864 let effect = &command.effects[0];
1865 let mut mat_mgr = MATERIAL_MANAGER.lock().unwrap();
1866 let material = mat_mgr.get_or_create(effect);
1867 apply_shader_uniforms(material, effect, &bb);
1868 gl_use_material(material);
1869 }
1870
1871 let normal_render = || {
1872 let x_scale = compute_letter_spacing_x_scale(
1873 bb.width,
1874 count_visible_chars(&config.text),
1875 config.letter_spacing,
1876 );
1877 draw_text_ex(
1878 &config.text,
1879 bb.x,
1880 bb.y + bb.height,
1881 TextParams {
1882 font_size: config.font_size as u16,
1883 font,
1884 font_scale: 1.0,
1885 font_scale_aspect: x_scale,
1886 rotation: 0.0,
1887 color: default_color
1888 }
1889 );
1890 };
1891
1892 let mut in_style_def = false;
1893 let mut escaped = false;
1894 let mut failed = false;
1895
1896 let mut text_buffer = String::new();
1897 let mut style_buffer = String::new();
1898
1899 let line = config.text.to_string();
1900 let mut segments: Vec<StyledSegment> = Vec::new();
1901
1902 for c in line.chars() {
1903 if escaped {
1904 if in_style_def {
1905 style_buffer.push(c);
1906 } else {
1907 text_buffer.push(c);
1908 }
1909 escaped = false;
1910 continue;
1911 }
1912
1913 match c {
1914 '\\' => {
1915 escaped = true;
1916 }
1917 '{' => {
1918 if in_style_def {
1919 style_buffer.push(c);
1920 } else {
1921 if !text_buffer.is_empty() {
1922 segments.push(StyledSegment {
1923 text: text_buffer.clone(),
1924 styles: state.style_stack.clone(),
1925 });
1926 text_buffer.clear();
1927 }
1928 in_style_def = true;
1929 }
1930 }
1931 '|' => {
1932 if in_style_def {
1933 state.style_stack.push(style_buffer.clone());
1934 style_buffer.clear();
1935 in_style_def = false;
1936 } else {
1937 text_buffer.push(c);
1938 }
1939 }
1940 '}' => {
1941 if in_style_def {
1942 style_buffer.push(c);
1943 } else {
1944 if !text_buffer.is_empty() {
1945 segments.push(StyledSegment {
1946 text: text_buffer.clone(),
1947 styles: state.style_stack.clone(),
1948 });
1949 text_buffer.clear();
1950 }
1951
1952 if state.style_stack.pop().is_none() {
1953 failed = true;
1954 break;
1955 }
1956 }
1957 }
1958 _ => {
1959 if in_style_def {
1960 style_buffer.push(c);
1961 } else {
1962 text_buffer.push(c);
1963 }
1964 }
1965 }
1966 }
1967 if !(failed || in_style_def) {
1968 if !text_buffer.is_empty() {
1969 segments.push(StyledSegment {
1970 text: text_buffer.clone(),
1971 styles: state.style_stack.clone(),
1972 });
1973 }
1974
1975 let time = get_time();
1976
1977 let cursor_x = std::cell::Cell::new(bb.x);
1978 let cursor_y = bb.y + bb.height;
1979 let mut pending_renders = Vec::new();
1980
1981 let x_scale = compute_letter_spacing_x_scale(
1982 bb.width,
1983 count_visible_chars(&config.text),
1984 config.letter_spacing,
1985 );
1986 {
1987 let mut tracker = ANIMATION_TRACKER.lock().unwrap();
1988 let ts_default = crate::color::Color::rgba(
1989 config.color.r,
1990 config.color.g,
1991 config.color.b,
1992 config.color.a,
1993 );
1994 render_styled_text(
1995 &segments,
1996 time,
1997 font_size,
1998 ts_default,
1999 &mut *tracker,
2000 &mut state.total_char_index,
2001 |text, tr, style_color| {
2002 let text_string = text.to_string();
2003 let text_width = measure_text(&text_string, font, config.font_size as u16, 1.0).width;
2004
2005 let color = Color::new(style_color.r / 255.0, style_color.g / 255.0, style_color.b / 255.0, style_color.a / 255.0);
2006 let x = cursor_x.get();
2007
2008 pending_renders.push((x, text_string, tr, color));
2009
2010 cursor_x.set(x + text_width*x_scale);
2011 },
2012 |text, tr, style_color| {
2013 let text_string = text.to_string();
2014 let color = Color::new(style_color.r / 255.0, style_color.g / 255.0, style_color.b / 255.0, style_color.a / 255.0);
2015 let x = cursor_x.get();
2016
2017 draw_text_ex(
2018 &text_string,
2019 x + tr.x*x_scale,
2020 cursor_y + tr.y,
2021 TextParams {
2022 font_size: config.font_size as u16,
2023 font,
2024 font_scale: tr.scale_y.max(0.01),
2025 font_scale_aspect: if tr.scale_y > 0.01 { tr.scale_x / tr.scale_y * x_scale } else { x_scale },
2026 rotation: tr.rotation.to_radians(),
2027 color
2028 }
2029 );
2030 }
2031 );
2032 }
2033 for (x, text_string, tr, color) in pending_renders {
2034 draw_text_ex(
2035 &text_string,
2036 x + tr.x*x_scale,
2037 cursor_y + tr.y,
2038 TextParams {
2039 font_size: config.font_size as u16,
2040 font,
2041 font_scale: tr.scale_y.max(0.01),
2042 font_scale_aspect: if tr.scale_y > 0.01 { tr.scale_x / tr.scale_y * x_scale } else { x_scale },
2043 rotation: tr.rotation.to_radians(),
2044 color
2045 }
2046 );
2047 }
2048 } else {
2049 if in_style_def {
2050 warn!("Style definition didn't end! Here is what we tried to render: {}", config.text);
2051 } else if failed {
2052 warn!("Encountered }} without opened style! Make sure to escape curly braces with \\. Here is what we tried to render: {}", config.text);
2053 }
2054 normal_render();
2055 }
2056
2057 if has_effect {
2059 gl_use_default_material();
2060 }
2061 }
2062 #[cfg(not(feature = "text-styling"))]
2063 RenderCommandConfig::Text(config) => {
2064 let bb = command.bounding_box;
2065 let color = ply_to_macroquad_color(&config.color);
2066 if let Some(asset) = config.font_asset {
2068 FontManager::ensure(asset).await;
2069 }
2070 let mut fm = FONT_MANAGER.lock().unwrap();
2072 let font = if let Some(asset) = config.font_asset {
2073 fm.get(asset)
2074 } else {
2075 fm.get_default()
2076 };
2077
2078 let has_effect = !command.effects.is_empty();
2080 if has_effect {
2081 let effect = &command.effects[0];
2082 let mut mat_mgr = MATERIAL_MANAGER.lock().unwrap();
2083 let material = mat_mgr.get_or_create(effect);
2084 apply_shader_uniforms(material, effect, &bb);
2085 gl_use_material(material);
2086 }
2087
2088 let x_scale = compute_letter_spacing_x_scale(
2089 bb.width,
2090 config.text.chars().count(),
2091 config.letter_spacing,
2092 );
2093 draw_text_ex(
2094 &config.text,
2095 bb.x,
2096 bb.y + bb.height,
2097 TextParams {
2098 font_size: config.font_size as u16,
2099 font,
2100 font_scale: 1.0,
2101 font_scale_aspect: x_scale,
2102 rotation: 0.0,
2103 color
2104 }
2105 );
2106
2107 if has_effect {
2109 gl_use_default_material();
2110 }
2111 }
2112 RenderCommandConfig::Border(config) => {
2113 let bb = command.bounding_box;
2114 let bw = &config.width;
2115 let cr = &config.corner_radii;
2116 let color = ply_to_macroquad_color(&config.color);
2117 if cr.top_left == 0.0 && cr.top_right == 0.0 && cr.bottom_left == 0.0 && cr.bottom_right == 0.0 {
2118 if bw.left == bw.right && bw.left == bw.top && bw.left == bw.bottom {
2119 let border_width = bw.left as f32;
2120 draw_rectangle_lines(
2121 bb.x - border_width / 2.0,
2122 bb.y - border_width / 2.0,
2123 bb.width + border_width,
2124 bb.height + border_width,
2125 border_width,
2126 color
2127 );
2128 } else {
2129 draw_line(
2131 bb.x,
2132 bb.y - bw.top as f32 / 2.0,
2133 bb.x + bb.width,
2134 bb.y - bw.top as f32 / 2.0,
2135 bw.top as f32,
2136 color
2137 );
2138 draw_line(
2140 bb.x - bw.left as f32 / 2.0,
2141 bb.y,
2142 bb.x - bw.left as f32 / 2.0,
2143 bb.y + bb.height,
2144 bw.left as f32,
2145 color
2146 );
2147 draw_line(
2149 bb.x,
2150 bb.y + bb.height + bw.bottom as f32 / 2.0,
2151 bb.x + bb.width,
2152 bb.y + bb.height + bw.bottom as f32 / 2.0,
2153 bw.bottom as f32,
2154 color
2155 );
2156 draw_line(
2158 bb.x + bb.width + bw.right as f32 / 2.0,
2159 bb.y,
2160 bb.x + bb.width + bw.right as f32 / 2.0,
2161 bb.y + bb.height,
2162 bw.right as f32,
2163 color
2164 );
2165 }
2166 } else {
2167 draw_line(
2170 bb.x + cr.top_left,
2171 bb.y - bw.top as f32 / 2.0,
2172 bb.x + bb.width - cr.top_right,
2173 bb.y - bw.top as f32 / 2.0,
2174 bw.top as f32,
2175 color
2176 );
2177 draw_line(
2179 bb.x - bw.left as f32 / 2.0,
2180 bb.y + cr.top_left,
2181 bb.x - bw.left as f32 / 2.0,
2182 bb.y + bb.height - cr.bottom_left,
2183 bw.left as f32,
2184 color
2185 );
2186 draw_line(
2188 bb.x + cr.bottom_left,
2189 bb.y + bb.height + bw.bottom as f32 / 2.0,
2190 bb.x + bb.width - cr.bottom_right,
2191 bb.y + bb.height + bw.bottom as f32 / 2.0,
2192 bw.bottom as f32,
2193 color
2194 );
2195 draw_line(
2197 bb.x + bb.width + bw.right as f32 / 2.0,
2198 bb.y + cr.top_right,
2199 bb.x + bb.width + bw.right as f32 / 2.0,
2200 bb.y + bb.height - cr.bottom_right,
2201 bw.right as f32,
2202 color
2203 );
2204
2205 if cr.top_left > 0.0 {
2208 let width = bw.left.max(bw.top) as f32;
2209 let points = ((std::f32::consts::PI * (cr.top_left + width)) / 2.0 / PIXELS_PER_POINT).max(5.0);
2210 draw_arc(
2211 bb.x + cr.top_left,
2212 bb.y + cr.top_left,
2213 points as u8,
2214 cr.top_left,
2215 180.0,
2216 bw.left as f32,
2217 90.0,
2218 color
2219 );
2220 }
2221 if cr.top_right > 0.0 {
2223 let width = bw.top.max(bw.right) as f32;
2224 let points = ((std::f32::consts::PI * (cr.top_right + width)) / 2.0 / PIXELS_PER_POINT).max(5.0);
2225 draw_arc(
2226 bb.x + bb.width - cr.top_right,
2227 bb.y + cr.top_right,
2228 points as u8,
2229 cr.top_right,
2230 270.0,
2231 bw.top as f32,
2232 90.0,
2233 color
2234 );
2235 }
2236 if cr.bottom_left > 0.0 {
2238 let width = bw.left.max(bw.bottom) as f32;
2239 let points = ((std::f32::consts::PI * (cr.bottom_left + width)) / 2.0 / PIXELS_PER_POINT).max(5.0);
2240 draw_arc(
2241 bb.x + cr.bottom_left,
2242 bb.y + bb.height - cr.bottom_left,
2243 points as u8,
2244 cr.bottom_left,
2245 90.0,
2246 bw.bottom as f32,
2247 90.0,
2248 color
2249 );
2250 }
2251 if cr.bottom_right > 0.0 {
2253 let width = bw.bottom.max(bw.right) as f32;
2254 let points = ((std::f32::consts::PI * (cr.bottom_right + width)) / 2.0 / PIXELS_PER_POINT).max(5.0);
2255 draw_arc(
2256 bb.x + bb.width - cr.bottom_right,
2257 bb.y + bb.height - cr.bottom_right,
2258 points as u8,
2259 cr.bottom_right,
2260 0.0,
2261 bw.right as f32,
2262 90.0,
2263 color
2264 );
2265 }
2266 }
2267 }
2268 RenderCommandConfig::ScissorStart() => {
2269 let bb = command.bounding_box;
2270 let dpi = miniquad::window::dpi_scale();
2275 state.clip = Some((
2276 (bb.x * dpi) as i32,
2277 (bb.y * dpi) as i32,
2278 (bb.width * dpi) as i32,
2279 (bb.height * dpi) as i32,
2280 ));
2281 unsafe {
2282 get_internal_gl().quad_gl.scissor(state.clip);
2283 }
2284 }
2285 RenderCommandConfig::ScissorEnd() => {
2286 state.clip = None;
2287 unsafe {
2288 get_internal_gl().quad_gl.scissor(None);
2289 }
2290 }
2291 RenderCommandConfig::Custom(_) => {
2292 handle_custom_command(&command);
2293 }
2294 RenderCommandConfig::GroupBegin { ref shader, ref visual_rotation } => {
2295 let bb = command.bounding_box;
2296 let rt = render_target_msaa(bb.width as u32, bb.height as u32);
2297 rt.texture.set_filter(FilterMode::Linear);
2298 let cam = Camera2D {
2299 render_target: Some(rt.clone()),
2300 ..Camera2D::from_display_rect(Rect::new(
2301 bb.x, bb.y, bb.width, bb.height,
2302 ))
2303 };
2304 set_camera(&cam);
2305 clear_background(Color::new(0.0, 0.0, 0.0, 0.0));
2306 state.rt_stack.push((rt, shader.clone(), *visual_rotation, bb));
2307 }
2308 RenderCommandConfig::GroupEnd => {
2309 if let Some((rt, shader_config, visual_rotation, bb)) = state.rt_stack.pop() {
2310 if let Some((prev_rt, _, _, prev_bb)) = state.rt_stack.last() {
2312 let cam = Camera2D {
2313 render_target: Some(prev_rt.clone()),
2314 ..Camera2D::from_display_rect(Rect::new(
2315 prev_bb.x, prev_bb.y, prev_bb.width, prev_bb.height,
2316 ))
2317 };
2318 set_camera(&cam);
2319 } else {
2320 set_default_camera();
2321 }
2322
2323 if let Some(ref config) = shader_config {
2325 let mut mat_mgr = MATERIAL_MANAGER.lock().unwrap();
2326 let material = mat_mgr.get_or_create(config);
2327 apply_shader_uniforms(material, config, &bb);
2328 gl_use_material(material);
2329 }
2330
2331 let (rotation, flip_x, flip_y, pivot) = match &visual_rotation {
2333 Some(rot) => {
2334 let pivot_screen = Vec2::new(
2335 bb.x + rot.pivot_x * bb.width,
2336 bb.y + rot.pivot_y * bb.height,
2337 );
2338 (rot.rotation_radians, rot.flip_x, !rot.flip_y, Some(pivot_screen))
2340 }
2341 None => (0.0, false, true, None),
2342 };
2343
2344 draw_texture_ex(
2345 &rt.texture,
2346 bb.x,
2347 bb.y,
2348 WHITE,
2349 DrawTextureParams {
2350 dest_size: Some(Vec2::new(bb.width, bb.height)),
2351 rotation,
2352 flip_x,
2353 flip_y,
2354 pivot,
2355 ..Default::default()
2356 },
2357 );
2358
2359 if shader_config.is_some() {
2360 gl_use_default_material();
2361 }
2362 }
2363 }
2364 RenderCommandConfig::None() => {}
2365 }
2366 }
2367 TEXTURE_MANAGER.lock().unwrap().clean();
2368 MATERIAL_MANAGER.lock().unwrap().clean();
2369 FONT_MANAGER.lock().unwrap().clean();
2370}
2371
2372pub fn create_measure_text_function(
2373) -> impl Fn(&str, &crate::TextConfig) -> crate::Dimensions + 'static {
2374 move |text: &str, config: &crate::TextConfig| {
2375 #[cfg(feature = "text-styling")]
2376 let cleaned_text = {
2377 let mut result = String::new();
2379 let mut in_style_def = false;
2380 let mut escaped = false;
2381 for c in text.chars() {
2382 if escaped {
2383 result.push(c);
2384 escaped = false;
2385 continue;
2386 }
2387 match c {
2388 '\\' => {
2389 escaped = true;
2390 }
2391 '{' => {
2392 in_style_def = true;
2393 }
2394 '|' => {
2395 if in_style_def {
2396 in_style_def = false;
2397 } else {
2398 result.push(c);
2399 }
2400 }
2401 '}' => {
2402 }
2404 _ => {
2405 if !in_style_def {
2406 result.push(c);
2407 }
2408 }
2409 }
2410 }
2411 if in_style_def {
2412 warn!("Ended inside a style definition while cleaning text for measurement! Make sure to escape curly braces with \\. Here is what we tried to measure: {}", text);
2413 }
2414 result
2415 };
2416 #[cfg(not(feature = "text-styling"))]
2417 let cleaned_text = text.to_string();
2418 let mut fm = FONT_MANAGER.lock().unwrap();
2419 let font = if let Some(asset) = config.font_asset {
2421 fm.get(asset)
2422 } else {
2423 fm.get_default()
2424 };
2425 let measured = macroquad::text::measure_text(
2426 &cleaned_text,
2427 font,
2428 config.font_size,
2429 1.0,
2430 );
2431 let added_space = (cleaned_text.chars().count().max(1) - 1) as f32 * config.letter_spacing as f32;
2432 crate::Dimensions::new(measured.width + added_space, measured.height)
2433 }
2434}
2435
2436#[cfg(feature = "text-styling")]
2439fn count_visible_chars(text: &str) -> usize {
2440 let mut count = 0;
2441 let mut in_style_def = false;
2442 let mut escaped = false;
2443 for c in text.chars() {
2444 if escaped { count += 1; escaped = false; continue; }
2445 match c {
2446 '\\' => { escaped = true; }
2447 '{' => { in_style_def = true; }
2448 '|' => { if in_style_def { in_style_def = false; } else { count += 1; } }
2449 '}' => { }
2450 _ => { if !in_style_def { count += 1; } }
2451 }
2452 }
2453 count
2454}
2455
2456fn compute_letter_spacing_x_scale(bb_width: f32, visible_char_count: usize, letter_spacing: u16) -> f32 {
2463 if letter_spacing == 0 || visible_char_count <= 1 {
2464 return 1.0;
2465 }
2466 let total_spacing = (visible_char_count as f32 - 1.0) * letter_spacing as f32;
2467 let raw_width = bb_width - total_spacing;
2468 if raw_width > 0.0 {
2469 bb_width / raw_width
2470 } else {
2471 1.0
2472 }
2473}