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