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_stack: Vec<(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_stack: Vec::new(),
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
877fn intersect_scissor(
878 a: (i32, i32, i32, i32),
879 b: (i32, i32, i32, i32),
880) -> (i32, i32, i32, i32) {
881 let ax2 = a.0.saturating_add(a.2);
882 let ay2 = a.1.saturating_add(a.3);
883 let bx2 = b.0.saturating_add(b.2);
884 let by2 = b.1.saturating_add(b.3);
885
886 let x1 = a.0.max(b.0);
887 let y1 = a.1.max(b.1);
888 let x2 = ax2.min(bx2);
889 let y2 = ay2.min(by2);
890
891 (x1, y1, (x2 - x1).max(0), (y2 - y1).max(0))
892}
893
894pub fn render_to_texture(width: f32, height: f32, draw: impl FnOnce()) -> Texture2D {
911 let render_target = render_target_msaa(width as u32, height as u32);
912 render_target.texture.set_filter(FilterMode::Linear);
913 let mut cam = Camera2D::from_display_rect(Rect::new(0.0, 0.0, width, height));
914 cam.render_target = Some(render_target.clone());
915 set_camera(&cam);
916
917 draw();
918
919 set_default_camera();
920 render_target.texture
921}
922
923fn rounded_rectangle_texture(cr: &CornerRadii, bb: &BoundingBox, clip: &Option<(i32, i32, i32, i32)>) -> Texture2D {
924 let render_target = render_target_msaa(bb.width as u32, bb.height as u32);
925 render_target.texture.set_filter(FilterMode::Linear);
926 let mut cam = Camera2D::from_display_rect(Rect::new(0.0, 0.0, bb.width, bb.height));
927 cam.render_target = Some(render_target.clone());
928 set_camera(&cam);
929 unsafe {
930 get_internal_gl().quad_gl.scissor(None);
931 };
932
933 draw_good_rounded_rectangle(0.0, 0.0, bb.width, bb.height, cr, WHITE);
934
935 set_default_camera();
936 unsafe {
937 get_internal_gl().quad_gl.scissor(*clip);
938 }
939 render_target.texture
940}
941
942#[cfg(feature = "tinyvg")]
945fn render_tinyvg_texture(
946 tvg_data: &[u8],
947 dest_width: f32,
948 dest_height: f32,
949 clip: &Option<(i32, i32, i32, i32)>,
950) -> Option<RenderTarget> {
951 use tinyvg::Decoder;
952 let decoder = Decoder::new(std::io::Cursor::new(tvg_data));
953 let image = match decoder.decode() {
954 Ok(img) => img,
955 Err(_) => return None,
956 };
957 render_tinyvg_image(&image, dest_width, dest_height, clip)
958}
959
960#[cfg(feature = "tinyvg")]
962fn render_tinyvg_image(
963 image: &tinyvg::format::Image,
964 dest_width: f32,
965 dest_height: f32,
966 clip: &Option<(i32, i32, i32, i32)>,
967) -> Option<RenderTarget> {
968 use tinyvg::format::{Command, Style, Segment, SegmentCommandKind, Point as TvgPoint, Color as TvgColor};
969 use kurbo::{BezPath, Point as KurboPoint, Vec2 as KurboVec2, ParamCurve, SvgArc, Arc as KurboArc, PathEl};
970 use lyon::tessellation::{FillTessellator, FillOptions, VertexBuffers, BuffersBuilder, FillVertex, FillRule};
971 use lyon::path::Path as LyonPath;
972 use lyon::math::point as lyon_point;
973
974 fn tvg_to_kurbo(p: TvgPoint) -> KurboPoint {
975 KurboPoint::new(p.x, p.y)
976 }
977
978 let tvg_width = image.header.width as f32;
979 let tvg_height = image.header.height as f32;
980 let scale_x = dest_width / tvg_width;
981 let scale_y = dest_height / tvg_height;
982
983 let render_target = render_target_msaa(dest_width as u32, dest_height as u32);
984 render_target.texture.set_filter(FilterMode::Linear);
985 let mut cam = Camera2D::from_display_rect(Rect::new(0.0, 0.0, dest_width, dest_height));
986 cam.render_target = Some(render_target.clone());
987 set_camera(&cam);
988 unsafe {
989 get_internal_gl().quad_gl.scissor(None);
990 }
991
992 let tvg_to_mq_color = |c: &TvgColor| -> Color {
993 let (r, g, b, a) = c.as_rgba();
994 Color::new(r as f32, g as f32, b as f32, a as f32)
995 };
996
997 let style_to_color = |style: &Style, color_table: &[TvgColor]| -> Color {
998 match style {
999 Style::FlatColor { color_index } => {
1000 color_table.get(*color_index).map(|c| tvg_to_mq_color(c)).unwrap_or(WHITE)
1001 }
1002 Style::LinearGradient { color_index_0, .. } |
1003 Style::RadialGradient { color_index_0, .. } => {
1004 color_table.get(*color_index_0).map(|c| tvg_to_mq_color(c)).unwrap_or(WHITE)
1005 }
1006 }
1007 };
1008
1009 let draw_filled_path_lyon = |bezpath: &BezPath, color: Color| {
1010 let mut builder = LyonPath::builder();
1011 let mut subpath_started = false;
1012
1013 for el in bezpath.iter() {
1014 match el {
1015 PathEl::MoveTo(p) => {
1016 if subpath_started {
1017 builder.end(false);
1018 }
1019 builder.begin(lyon_point((p.x * scale_x as f64) as f32, (p.y * scale_y as f64) as f32));
1020 subpath_started = true;
1021 }
1022 PathEl::LineTo(p) => {
1023 builder.line_to(lyon_point((p.x * scale_x as f64) as f32, (p.y * scale_y as f64) as f32));
1024 }
1025 PathEl::QuadTo(c, p) => {
1026 builder.quadratic_bezier_to(
1027 lyon_point((c.x * scale_x as f64) as f32, (c.y * scale_y as f64) as f32),
1028 lyon_point((p.x * scale_x as f64) as f32, (p.y * scale_y as f64) as f32),
1029 );
1030 }
1031 PathEl::CurveTo(c1, c2, p) => {
1032 builder.cubic_bezier_to(
1033 lyon_point((c1.x * scale_x as f64) as f32, (c1.y * scale_y as f64) as f32),
1034 lyon_point((c2.x * scale_x as f64) as f32, (c2.y * scale_y as f64) as f32),
1035 lyon_point((p.x * scale_x as f64) as f32, (p.y * scale_y as f64) as f32),
1036 );
1037 }
1038 PathEl::ClosePath => {
1039 builder.end(true);
1040 subpath_started = false;
1041 }
1042 }
1043 }
1044
1045 if subpath_started {
1046 builder.end(true);
1047 }
1048
1049 let lyon_path = builder.build();
1050
1051 let mut geometry: VertexBuffers<[f32; 2], u16> = VertexBuffers::new();
1052 let mut tessellator = FillTessellator::new();
1053
1054 let fill_options = FillOptions::default().with_fill_rule(FillRule::NonZero);
1055
1056 let result = tessellator.tessellate_path(
1057 &lyon_path,
1058 &fill_options,
1059 &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
1060 vertex.position().to_array()
1061 }),
1062 );
1063
1064 if result.is_err() || geometry.indices.is_empty() {
1065 return;
1066 }
1067
1068 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];
1069
1070 let vertices: Vec<Vertex> = geometry.vertices.iter().map(|pos| {
1071 Vertex {
1072 position: Vec3::new(pos[0], pos[1], 0.0),
1073 uv: Vec2::ZERO,
1074 color: color_bytes,
1075 normal: Vec4::new(0.0, 0.0, 1.0, 0.0),
1076 }
1077 }).collect();
1078
1079 let mesh = Mesh {
1080 vertices,
1081 indices: geometry.indices,
1082 texture: None,
1083 };
1084 draw_mesh(&mesh);
1085 };
1086
1087 let draw_filled_polygon_tvg = |points: &[TvgPoint], color: Color| {
1088 if points.len() < 3 {
1089 return;
1090 }
1091
1092 let mut builder = LyonPath::builder();
1093 builder.begin(lyon_point(points[0].x as f32 * scale_x, points[0].y as f32 * scale_y));
1094 for point in &points[1..] {
1095 builder.line_to(lyon_point(point.x as f32 * scale_x, point.y as f32 * scale_y));
1096 }
1097 builder.end(true);
1098 let lyon_path = builder.build();
1099
1100 let mut geometry: VertexBuffers<[f32; 2], u16> = VertexBuffers::new();
1101 let mut tessellator = FillTessellator::new();
1102
1103 let result = tessellator.tessellate_path(
1104 &lyon_path,
1105 &FillOptions::default(),
1106 &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
1107 vertex.position().to_array()
1108 }),
1109 );
1110
1111 if result.is_err() || geometry.indices.is_empty() {
1112 return;
1113 }
1114
1115 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];
1116
1117 let vertices: Vec<Vertex> = geometry.vertices.iter().map(|pos| {
1118 Vertex {
1119 position: Vec3::new(pos[0], pos[1], 0.0),
1120 uv: Vec2::ZERO,
1121 color: color_bytes,
1122 normal: Vec4::new(0.0, 0.0, 1.0, 0.0),
1123 }
1124 }).collect();
1125
1126 let mesh = Mesh {
1127 vertices,
1128 indices: geometry.indices,
1129 texture: None,
1130 };
1131 draw_mesh(&mesh);
1132 };
1133
1134 let build_bezpath = |segments: &[Segment]| -> BezPath {
1135 let mut bezier = BezPath::new();
1136 for segment in segments {
1137 let start = tvg_to_kurbo(segment.start);
1138 let mut pen = start;
1139 bezier.move_to(pen);
1140
1141 for cmd in &segment.commands {
1142 match &cmd.kind {
1143 SegmentCommandKind::Line { end } => {
1144 let end_k = tvg_to_kurbo(*end);
1145 bezier.line_to(end_k);
1146 pen = end_k;
1147 }
1148 SegmentCommandKind::HorizontalLine { x } => {
1149 let end = KurboPoint::new(*x, pen.y);
1150 bezier.line_to(end);
1151 pen = end;
1152 }
1153 SegmentCommandKind::VerticalLine { y } => {
1154 let end = KurboPoint::new(pen.x, *y);
1155 bezier.line_to(end);
1156 pen = end;
1157 }
1158 SegmentCommandKind::CubicBezier { control_0, control_1, point_1 } => {
1159 let c0 = tvg_to_kurbo(*control_0);
1160 let c1 = tvg_to_kurbo(*control_1);
1161 let p1 = tvg_to_kurbo(*point_1);
1162 bezier.curve_to(c0, c1, p1);
1163 pen = p1;
1164 }
1165 SegmentCommandKind::QuadraticBezier { control, point_1 } => {
1166 let c = tvg_to_kurbo(*control);
1167 let p1 = tvg_to_kurbo(*point_1);
1168 bezier.quad_to(c, p1);
1169 pen = p1;
1170 }
1171 SegmentCommandKind::ArcEllipse { large, sweep, radius_x, radius_y, rotation, target } => {
1172 let target_k = tvg_to_kurbo(*target);
1173 let svg_arc = SvgArc {
1174 from: pen,
1175 to: target_k,
1176 radii: KurboVec2::new(*radius_x, *radius_y),
1177 x_rotation: *rotation,
1178 large_arc: *large,
1179 sweep: *sweep,
1180 };
1181 if let Some(arc) = KurboArc::from_svg_arc(&svg_arc) {
1182 for seg in arc.append_iter(0.2) {
1183 bezier.push(seg);
1184 }
1185 }
1186 pen = target_k;
1187 }
1188 SegmentCommandKind::ClosePath => {
1189 bezier.close_path();
1190 pen = start;
1191 }
1192 }
1193 }
1194 }
1195 bezier
1196 };
1197
1198 let line_scale = (scale_x + scale_y) / 2.0;
1199
1200 for cmd in &image.commands {
1201 match cmd {
1202 Command::FillPath { fill_style, path, outline } => {
1203 let fill_color = style_to_color(fill_style, &image.color_table);
1204 let bezpath = build_bezpath(path);
1205 draw_filled_path_lyon(&bezpath, fill_color);
1206
1207 if let Some(outline_style) = outline {
1208 let line_color = style_to_color(&outline_style.line_style, &image.color_table);
1209 let line_width = outline_style.line_width as f32 * line_scale;
1210 for segment in path {
1211 let start = segment.start;
1212 let mut pen = start;
1213 for cmd in &segment.commands {
1214 match &cmd.kind {
1215 SegmentCommandKind::Line { end } => {
1216 draw_line(
1217 pen.x as f32 * scale_x, pen.y as f32 * scale_y,
1218 end.x as f32 * scale_x, end.y as f32 * scale_y,
1219 line_width, line_color
1220 );
1221 pen = *end;
1222 }
1223 SegmentCommandKind::HorizontalLine { x } => {
1224 let end = TvgPoint { x: *x, y: pen.y };
1225 draw_line(
1226 pen.x as f32 * scale_x, pen.y as f32 * scale_y,
1227 end.x as f32 * scale_x, end.y as f32 * scale_y,
1228 line_width, line_color
1229 );
1230 pen = end;
1231 }
1232 SegmentCommandKind::VerticalLine { y } => {
1233 let end = TvgPoint { x: pen.x, y: *y };
1234 draw_line(
1235 pen.x as f32 * scale_x, pen.y as f32 * scale_y,
1236 end.x as f32 * scale_x, end.y as f32 * scale_y,
1237 line_width, line_color
1238 );
1239 pen = end;
1240 }
1241 SegmentCommandKind::ClosePath => {
1242 draw_line(
1243 pen.x as f32 * scale_x, pen.y as f32 * scale_y,
1244 start.x as f32 * scale_x, start.y as f32 * scale_y,
1245 line_width, line_color
1246 );
1247 pen = start;
1248 }
1249 SegmentCommandKind::CubicBezier { control_0, control_1, point_1 } => {
1250 let c0 = tvg_to_kurbo(*control_0);
1251 let c1 = tvg_to_kurbo(*control_1);
1252 let p1 = tvg_to_kurbo(*point_1);
1253 let p0 = tvg_to_kurbo(pen);
1254 let cubic = kurbo::CubicBez::new(p0, c0, c1, p1);
1255 let steps = 16usize;
1256 let mut prev = p0;
1257 for i in 1..=steps {
1258 let t = i as f64 / steps as f64;
1259 let next = cubic.eval(t);
1260 draw_line(
1261 prev.x as f32 * scale_x, prev.y as f32 * scale_y,
1262 next.x as f32 * scale_x, next.y as f32 * scale_y,
1263 line_width, line_color
1264 );
1265 prev = next;
1266 }
1267 pen = *point_1;
1268 }
1269 SegmentCommandKind::QuadraticBezier { control, point_1 } => {
1270 let c = tvg_to_kurbo(*control);
1271 let p1 = tvg_to_kurbo(*point_1);
1272 let p0 = tvg_to_kurbo(pen);
1273 let quad = kurbo::QuadBez::new(p0, c, p1);
1274 let steps = 12usize;
1275 let mut prev = p0;
1276 for i in 1..=steps {
1277 let t = i as f64 / steps as f64;
1278 let next = quad.eval(t);
1279 draw_line(
1280 prev.x as f32 * scale_x, prev.y as f32 * scale_y,
1281 next.x as f32 * scale_x, next.y as f32 * scale_y,
1282 line_width, line_color
1283 );
1284 prev = next;
1285 }
1286 pen = *point_1;
1287 }
1288 SegmentCommandKind::ArcEllipse { large, sweep, radius_x, radius_y, rotation, target } => {
1289 let target_k = tvg_to_kurbo(*target);
1290 let p0 = tvg_to_kurbo(pen);
1291 let svg_arc = SvgArc {
1292 from: p0,
1293 to: target_k,
1294 radii: KurboVec2::new(*radius_x, *radius_y),
1295 x_rotation: *rotation,
1296 large_arc: *large,
1297 sweep: *sweep,
1298 };
1299 if let Some(arc) = KurboArc::from_svg_arc(&svg_arc) {
1300 let mut prev = p0;
1301 for seg in arc.append_iter(0.2) {
1302 match seg {
1303 PathEl::LineTo(p) | PathEl::MoveTo(p) => {
1304 draw_line(
1305 prev.x as f32 * scale_x, prev.y as f32 * scale_y,
1306 p.x as f32 * scale_x, p.y as f32 * scale_y,
1307 line_width, line_color
1308 );
1309 prev = p;
1310 }
1311 PathEl::CurveTo(c0, c1, p) => {
1312 let cubic = kurbo::CubicBez::new(prev, c0, c1, p);
1314 let steps = 8usize;
1315 let mut prev_pt = prev;
1316 for j in 1..=steps {
1317 let t = j as f64 / steps as f64;
1318 let next = cubic.eval(t);
1319 draw_line(
1320 prev_pt.x as f32 * scale_x, prev_pt.y as f32 * scale_y,
1321 next.x as f32 * scale_x, next.y as f32 * scale_y,
1322 line_width, line_color
1323 );
1324 prev_pt = next;
1325 }
1326 prev = p;
1327 }
1328 _ => {}
1329 }
1330 }
1331 }
1332 pen = *target;
1333 }
1334 }
1335 }
1336 }
1337 }
1338 }
1339 Command::FillRectangles { fill_style, rectangles, outline } => {
1340 let fill_color = style_to_color(fill_style, &image.color_table);
1341 for rect in rectangles {
1342 draw_rectangle(
1343 rect.x0 as f32 * scale_x,
1344 rect.y0 as f32 * scale_y,
1345 rect.width() as f32 * scale_x,
1346 rect.height() as f32 * scale_y,
1347 fill_color
1348 );
1349 }
1350
1351 if let Some(outline_style) = outline {
1352 let line_color = style_to_color(&outline_style.line_style, &image.color_table);
1353 let line_width = outline_style.line_width as f32 * line_scale;
1354 for rect in rectangles {
1355 draw_rectangle_lines(
1356 rect.x0 as f32 * scale_x,
1357 rect.y0 as f32 * scale_y,
1358 rect.width() as f32 * scale_x,
1359 rect.height() as f32 * scale_y,
1360 line_width, line_color
1361 );
1362 }
1363 }
1364 }
1365 Command::FillPolygon { fill_style, polygon, outline } => {
1366 let fill_color = style_to_color(fill_style, &image.color_table);
1367 draw_filled_polygon_tvg(polygon, fill_color);
1368
1369 if let Some(outline_style) = outline {
1370 let line_color = style_to_color(&outline_style.line_style, &image.color_table);
1371 let line_width = outline_style.line_width as f32 * line_scale;
1372 for i in 0..polygon.len() {
1373 let next = (i + 1) % polygon.len();
1374 draw_line(
1375 polygon[i].x as f32 * scale_x, polygon[i].y as f32 * scale_y,
1376 polygon[next].x as f32 * scale_x, polygon[next].y as f32 * scale_y,
1377 line_width, line_color
1378 );
1379 }
1380 }
1381 }
1382 Command::DrawLines { line_style, line_width, lines } => {
1383 let line_color = style_to_color(line_style, &image.color_table);
1384 for line in lines {
1385 draw_line(
1386 line.p0.x as f32 * scale_x, line.p0.y as f32 * scale_y,
1387 line.p1.x as f32 * scale_x, line.p1.y as f32 * scale_y,
1388 *line_width as f32 * line_scale, line_color
1389 );
1390 }
1391 }
1392 Command::DrawLineLoop { line_style, line_width, close_path, points } => {
1393 let line_color = style_to_color(line_style, &image.color_table);
1394 for i in 0..points.len().saturating_sub(1) {
1395 draw_line(
1396 points[i].x as f32 * scale_x, points[i].y as f32 * scale_y,
1397 points[i+1].x as f32 * scale_x, points[i+1].y as f32 * scale_y,
1398 *line_width as f32 * line_scale, line_color
1399 );
1400 }
1401 if *close_path && points.len() >= 2 {
1402 let last = points.len() - 1;
1403 draw_line(
1404 points[last].x as f32 * scale_x, points[last].y as f32 * scale_y,
1405 points[0].x as f32 * scale_x, points[0].y as f32 * scale_y,
1406 *line_width as f32 * line_scale, line_color
1407 );
1408 }
1409 }
1410 Command::DrawLinePath { line_style, line_width, path } => {
1411 let line_color = style_to_color(line_style, &image.color_table);
1412 let scaled_line_width = *line_width as f32 * line_scale;
1413 for segment in path {
1415 let start = segment.start;
1416 let mut pen = start;
1417 for cmd in &segment.commands {
1418 match &cmd.kind {
1419 SegmentCommandKind::Line { end } => {
1420 draw_line(
1421 pen.x as f32 * scale_x, pen.y as f32 * scale_y,
1422 end.x as f32 * scale_x, end.y as f32 * scale_y,
1423 scaled_line_width, line_color
1424 );
1425 pen = *end;
1426 }
1427 SegmentCommandKind::HorizontalLine { x } => {
1428 let end = TvgPoint { x: *x, y: pen.y };
1429 draw_line(
1430 pen.x as f32 * scale_x, pen.y as f32 * scale_y,
1431 end.x as f32 * scale_x, end.y as f32 * scale_y,
1432 scaled_line_width, line_color
1433 );
1434 pen = end;
1435 }
1436 SegmentCommandKind::VerticalLine { y } => {
1437 let end = TvgPoint { x: pen.x, y: *y };
1438 draw_line(
1439 pen.x as f32 * scale_x, pen.y as f32 * scale_y,
1440 end.x as f32 * scale_x, end.y as f32 * scale_y,
1441 scaled_line_width, line_color
1442 );
1443 pen = end;
1444 }
1445 SegmentCommandKind::ClosePath => {
1446 draw_line(
1447 pen.x as f32 * scale_x, pen.y as f32 * scale_y,
1448 start.x as f32 * scale_x, start.y as f32 * scale_y,
1449 scaled_line_width, line_color
1450 );
1451 pen = start;
1452 }
1453 SegmentCommandKind::CubicBezier { control_0, control_1, point_1 } => {
1455 let c0 = tvg_to_kurbo(*control_0);
1456 let c1 = tvg_to_kurbo(*control_1);
1457 let p1 = tvg_to_kurbo(*point_1);
1458 let p0 = tvg_to_kurbo(pen);
1459 let cubic = kurbo::CubicBez::new(p0, c0, c1, p1);
1460 let steps = 16usize;
1461 let mut prev = p0;
1462 for i in 1..=steps {
1463 let t = i as f64 / steps as f64;
1464 let next = cubic.eval(t);
1465 draw_line(
1466 prev.x as f32 * scale_x, prev.y as f32 * scale_y,
1467 next.x as f32 * scale_x, next.y as f32 * scale_y,
1468 scaled_line_width, line_color
1469 );
1470 prev = next;
1471 }
1472 pen = *point_1;
1473 }
1474 SegmentCommandKind::QuadraticBezier { control, point_1 } => {
1475 let c = tvg_to_kurbo(*control);
1476 let p1 = tvg_to_kurbo(*point_1);
1477 let p0 = tvg_to_kurbo(pen);
1478 let quad = kurbo::QuadBez::new(p0, c, p1);
1479 let steps = 12usize;
1480 let mut prev = p0;
1481 for i in 1..=steps {
1482 let t = i as f64 / steps as f64;
1483 let next = quad.eval(t);
1484 draw_line(
1485 prev.x as f32 * scale_x, prev.y as f32 * scale_y,
1486 next.x as f32 * scale_x, next.y as f32 * scale_y,
1487 scaled_line_width, line_color
1488 );
1489 prev = next;
1490 }
1491 pen = *point_1;
1492 }
1493 SegmentCommandKind::ArcEllipse { large, sweep, radius_x, radius_y, rotation, target } => {
1494 let target_k = tvg_to_kurbo(*target);
1495 let p0 = tvg_to_kurbo(pen);
1496 let svg_arc = SvgArc {
1497 from: p0,
1498 to: target_k,
1499 radii: KurboVec2::new(*radius_x, *radius_y),
1500 x_rotation: *rotation,
1501 large_arc: *large,
1502 sweep: *sweep,
1503 };
1504 if let Some(arc) = KurboArc::from_svg_arc(&svg_arc) {
1505 let mut prev = p0;
1506 for seg in arc.append_iter(0.2) {
1507 match seg {
1508 PathEl::LineTo(p) | PathEl::MoveTo(p) => {
1509 draw_line(
1510 prev.x as f32 * scale_x, prev.y as f32 * scale_y,
1511 p.x as f32 * scale_x, p.y as f32 * scale_y,
1512 scaled_line_width, line_color
1513 );
1514 prev = p;
1515 }
1516 PathEl::CurveTo(c0, c1, p) => {
1517 let cubic = kurbo::CubicBez::new(prev, c0, c1, p);
1519 let steps = 8usize;
1520 let mut prev_pt = prev;
1521 for j in 1..=steps {
1522 let t = j as f64 / steps as f64;
1523 let next = cubic.eval(t);
1524 draw_line(
1525 prev_pt.x as f32 * scale_x, prev_pt.y as f32 * scale_y,
1526 next.x as f32 * scale_x, next.y as f32 * scale_y,
1527 scaled_line_width, line_color
1528 );
1529 prev_pt = next;
1530 }
1531 prev = p;
1532 }
1533 _ => {}
1534 }
1535 }
1536 }
1537 pen = *target;
1538 }
1539 }
1540 }
1541 }
1542 }
1543 }
1544 }
1545
1546 set_default_camera();
1547 unsafe {
1548 get_internal_gl().quad_gl.scissor(*clip);
1549 }
1550
1551 Some(render_target)
1552}
1553
1554fn resize(texture: &Texture2D, height: f32, width: f32, clip: &Option<(i32, i32, i32, i32)>) -> Texture2D {
1555 let render_target = render_target_msaa(width as u32, height as u32);
1556 render_target.texture.set_filter(FilterMode::Linear);
1557 let mut cam = Camera2D::from_display_rect(Rect::new(0.0, 0.0, width, height));
1558 cam.render_target = Some(render_target.clone());
1559 set_camera(&cam);
1560 unsafe {
1561 get_internal_gl().quad_gl.scissor(None);
1562 };
1563 draw_texture_ex(
1564 texture,
1565 0.0,
1566 0.0,
1567 WHITE,
1568 DrawTextureParams {
1569 dest_size: Some(Vec2::new(width, height)),
1570 flip_y: true,
1571 ..Default::default()
1572 },
1573 );
1574 set_default_camera();
1575 unsafe {
1576 get_internal_gl().quad_gl.scissor(*clip);
1577 }
1578 render_target.texture
1579}
1580
1581pub async fn render<CustomElementData: Clone + Default + std::fmt::Debug>(
1583 commands: Vec<RenderCommand<CustomElementData>>,
1584 handle_custom_command: impl Fn(&RenderCommand<CustomElementData>),
1585) {
1586 let mut state = RenderState::new();
1587 for command in commands {
1588 let current_clip = state.clip_stack.last().copied();
1589 match &command.config {
1590 RenderCommandConfig::Image(image) => {
1591 let bb = command.bounding_box;
1592 let cr = &image.corner_radii;
1593 let mut tint = ply_to_macroquad_color(&image.background_color);
1594 if tint == Color::new(0.0, 0.0, 0.0, 0.0) {
1595 tint = Color::new(1.0, 1.0, 1.0, 1.0);
1596 }
1597
1598 match &image.data {
1599 ImageSource::Texture(tex) => {
1600 let has_corner_radii = cr.top_left > 0.0 || cr.top_right > 0.0 || cr.bottom_left > 0.0 || cr.bottom_right > 0.0;
1602 if !has_corner_radii {
1603 draw_texture_ex(
1604 tex,
1605 bb.x,
1606 bb.y,
1607 tint,
1608 DrawTextureParams {
1609 dest_size: Some(Vec2::new(bb.width, bb.height)),
1610 ..Default::default()
1611 },
1612 );
1613 } else {
1614 let mut manager = TEXTURE_MANAGER.lock().unwrap();
1615 let key = format!(
1617 "tex-proc:{:?}:{}:{}:{}:{}:{}:{}:{:?}",
1618 tex.raw_miniquad_id(),
1619 bb.width, bb.height,
1620 cr.top_left, cr.top_right, cr.bottom_left, cr.bottom_right,
1621 current_clip
1622 );
1623 let texture = manager.get_or_create(key, || {
1624 let mut resized_image: Image = resize(tex, bb.height, bb.width, ¤t_clip).get_texture_data();
1625 let rounded_rect: Image = rounded_rectangle_texture(cr, &bb, ¤t_clip).get_texture_data();
1626 for i in 0..resized_image.bytes.len()/4 {
1627 let this_alpha = resized_image.bytes[i * 4 + 3] as f32 / 255.0;
1628 let mask_alpha = rounded_rect.bytes[i * 4 + 3] as f32 / 255.0;
1629 resized_image.bytes[i * 4 + 3] = (this_alpha * mask_alpha * 255.0) as u8;
1630 }
1631 Texture2D::from_image(&resized_image)
1632 });
1633 draw_texture_ex(
1634 texture,
1635 bb.x,
1636 bb.y,
1637 tint,
1638 DrawTextureParams {
1639 dest_size: Some(Vec2::new(bb.width, bb.height)),
1640 ..Default::default()
1641 },
1642 );
1643 }
1644 }
1645 #[cfg(feature = "tinyvg")]
1646 ImageSource::TinyVg(tvg_image) => {
1647 let has_corner_radii = cr.top_left > 0.0 || cr.top_right > 0.0 || cr.bottom_left > 0.0 || cr.bottom_right > 0.0;
1649 if let Some(tvg_rt) = render_tinyvg_image(tvg_image, bb.width, bb.height, ¤t_clip) {
1650 let final_texture = if has_corner_radii {
1651 let mut tvg_img: Image = tvg_rt.texture.get_texture_data();
1652 let rounded_rect: Image = rounded_rectangle_texture(cr, &bb, ¤t_clip).get_texture_data();
1653 for i in 0..tvg_img.bytes.len()/4 {
1654 let this_alpha = tvg_img.bytes[i * 4 + 3] as f32 / 255.0;
1655 let mask_alpha = rounded_rect.bytes[i * 4 + 3] as f32 / 255.0;
1656 tvg_img.bytes[i * 4 + 3] = (this_alpha * mask_alpha * 255.0) as u8;
1657 }
1658 Texture2D::from_image(&tvg_img)
1659 } else {
1660 tvg_rt.texture.clone()
1661 };
1662 draw_texture_ex(
1663 &final_texture,
1664 bb.x,
1665 bb.y,
1666 tint,
1667 DrawTextureParams {
1668 dest_size: Some(Vec2::new(bb.width, bb.height)),
1669 flip_y: true,
1670 ..Default::default()
1671 },
1672 );
1673 }
1674 }
1675 ImageSource::Asset(ga) => {
1676 let mut manager = TEXTURE_MANAGER.lock().unwrap();
1678
1679 #[cfg(feature = "tinyvg")]
1680 let is_tvg = ga.get_name().to_lowercase().ends_with(".tvg");
1681 #[cfg(not(feature = "tinyvg"))]
1682 let is_tvg = false;
1683
1684 #[cfg(feature = "tinyvg")]
1685 if is_tvg {
1686 let key = format!(
1687 "tvg:{}:{}:{}:{}:{}:{}:{}:{:?}",
1688 ga.get_name(),
1689 bb.width, bb.height,
1690 cr.top_left, cr.top_right, cr.bottom_left, cr.bottom_right,
1691 current_clip
1692 );
1693 let has_corner_radii = cr.top_left > 0.0 || cr.top_right > 0.0 || cr.bottom_left > 0.0 || cr.bottom_right > 0.0;
1694 let texture = if !has_corner_radii {
1695 if let Some(cached) = manager.get(&key) {
1697 cached
1698 } else {
1699 match ga {
1700 GraphicAsset::Path(path) => {
1701 match load_file(resolve_asset_path(path)).await {
1702 Ok(tvg_bytes) => {
1703 if let Some(tvg_rt) = render_tinyvg_texture(&tvg_bytes, bb.width, bb.height, ¤t_clip) {
1704 manager.cache(key.clone(), tvg_rt)
1705 } else {
1706 warn!("Failed to load TinyVG image: {}", path);
1707 manager.cache(key.clone(), Texture2D::from_rgba8(1, 1, &[0, 0, 0, 0]))
1708 }
1709 }
1710 Err(error) => {
1711 warn!("Failed to load TinyVG file: {}. Error: {}", path, error);
1712 manager.cache(key.clone(), Texture2D::from_rgba8(1, 1, &[0, 0, 0, 0]))
1713 }
1714 }
1715 }
1716 GraphicAsset::Bytes { file_name, data: tvg_bytes } => {
1717 if let Some(tvg_rt) = render_tinyvg_texture(tvg_bytes, bb.width, bb.height, ¤t_clip) {
1718 manager.cache(key.clone(), tvg_rt)
1719 } else {
1720 warn!("Failed to load TinyVG image: {}", file_name);
1721 manager.cache(key.clone(), Texture2D::from_rgba8(1, 1, &[0, 0, 0, 0]))
1722 }
1723 }
1724 }
1725 }
1726 } else {
1727 let zerocr_key = format!(
1728 "tvg:{}:{}:{}:{}:{}:{}:{}:{:?}",
1729 ga.get_name(),
1730 bb.width, bb.height,
1731 0.0, 0.0, 0.0, 0.0,
1732 current_clip
1733 );
1734 let base_texture = if let Some(cached) = manager.get(&zerocr_key) {
1735 cached
1736 } else {
1737 match ga {
1738 GraphicAsset::Path(path) => {
1739 match load_file(resolve_asset_path(path)).await {
1740 Ok(tvg_bytes) => {
1741 if let Some(tvg_rt) = render_tinyvg_texture(&tvg_bytes, bb.width, bb.height, ¤t_clip) {
1742 manager.cache(zerocr_key.clone(), tvg_rt)
1743 } else {
1744 warn!("Failed to load TinyVG image: {}", path);
1745 manager.cache(zerocr_key.clone(), Texture2D::from_rgba8(1, 1, &[0, 0, 0, 0]))
1746 }
1747 }
1748 Err(error) => {
1749 warn!("Failed to load TinyVG file: {}. Error: {}", path, error);
1750 manager.cache(zerocr_key.clone(), Texture2D::from_rgba8(1, 1, &[0, 0, 0, 0]))
1751 }
1752 }
1753 }
1754 GraphicAsset::Bytes { file_name, data: tvg_bytes } => {
1755 if let Some(tvg_rt) = render_tinyvg_texture(tvg_bytes, bb.width, bb.height, ¤t_clip) {
1756 manager.cache(zerocr_key.clone(), tvg_rt)
1757 } else {
1758 warn!("Failed to load TinyVG image: {}", file_name);
1759 manager.cache(zerocr_key.clone(), Texture2D::from_rgba8(1, 1, &[0, 0, 0, 0]))
1760 }
1761 }
1762 }
1763 }.clone();
1764 manager.get_or_create(key, || {
1765 let mut tvg_image: Image = base_texture.get_texture_data();
1766 let rounded_rect: Image = rounded_rectangle_texture(cr, &bb, ¤t_clip).get_texture_data();
1767 for i in 0..tvg_image.bytes.len()/4 {
1768 let this_alpha = tvg_image.bytes[i * 4 + 3] as f32 / 255.0;
1769 let mask_alpha = rounded_rect.bytes[i * 4 + 3] as f32 / 255.0;
1770 tvg_image.bytes[i * 4 + 3] = (this_alpha * mask_alpha * 255.0) as u8;
1771 }
1772 Texture2D::from_image(&tvg_image)
1773 })
1774 };
1775 draw_texture_ex(
1776 texture,
1777 bb.x,
1778 bb.y,
1779 tint,
1780 DrawTextureParams {
1781 dest_size: Some(Vec2::new(bb.width, bb.height)),
1782 flip_y: true,
1783 ..Default::default()
1784 },
1785 );
1786 continue;
1787 }
1788
1789 if !is_tvg && cr.top_left == 0.0 && cr.top_right == 0.0 && cr.bottom_left == 0.0 && cr.bottom_right == 0.0 {
1790 let texture = match ga {
1791 GraphicAsset::Path(path) => manager.get_or_load(path).await,
1792 GraphicAsset::Bytes { file_name, data } => {
1793 manager.get_or_create(file_name.to_string(), || {
1794 Texture2D::from_file_with_format(data, None)
1795 })
1796 }
1797 };
1798 draw_texture_ex(
1799 texture,
1800 bb.x,
1801 bb.y,
1802 tint,
1803 DrawTextureParams {
1804 dest_size: Some(Vec2::new(bb.width, bb.height)),
1805 ..Default::default()
1806 },
1807 );
1808 } else {
1809 let source_texture = match ga {
1810 GraphicAsset::Path(path) => manager.get_or_load(path).await.clone(),
1811 GraphicAsset::Bytes { file_name, data } => {
1812 manager.get_or_create(file_name.to_string(), || {
1813 Texture2D::from_file_with_format(data, None)
1814 }).clone()
1815 }
1816 };
1817 let key = format!(
1818 "image:{}:{}:{}:{}:{}:{}:{}:{:?}",
1819 ga.get_name(),
1820 bb.width, bb.height,
1821 cr.top_left, cr.top_right, cr.bottom_left, cr.bottom_right,
1822 current_clip
1823 );
1824 let texture = manager.get_or_create(key, || {
1825 let mut resized_image: Image = resize(&source_texture, bb.height, bb.width, ¤t_clip).get_texture_data();
1826 let rounded_rect: Image = rounded_rectangle_texture(cr, &bb, ¤t_clip).get_texture_data();
1827 for i in 0..resized_image.bytes.len()/4 {
1828 let this_alpha = resized_image.bytes[i * 4 + 3] as f32 / 255.0;
1829 let mask_alpha = rounded_rect.bytes[i * 4 + 3] as f32 / 255.0;
1830 resized_image.bytes[i * 4 + 3] = (this_alpha * mask_alpha * 255.0) as u8;
1831 }
1832 Texture2D::from_image(&resized_image)
1833 });
1834 draw_texture_ex(
1835 texture,
1836 bb.x,
1837 bb.y,
1838 tint,
1839 DrawTextureParams {
1840 dest_size: Some(Vec2::new(bb.width, bb.height)),
1841 ..Default::default()
1842 },
1843 );
1844 }
1845 }
1846 }
1847 }
1848 RenderCommandConfig::Rectangle(config) => {
1849 let bb = command.bounding_box;
1850 let color = ply_to_macroquad_color(&config.color);
1851 let cr = &config.corner_radii;
1852
1853 let has_effect = !command.effects.is_empty();
1855 if has_effect {
1856 let effect = &command.effects[0];
1857 let mut mat_mgr = MATERIAL_MANAGER.lock().unwrap();
1858 let material = mat_mgr.get_or_create(effect);
1859 apply_shader_uniforms(material, effect, &bb);
1860 gl_use_material(material);
1861 }
1862
1863 if let Some(ref sr) = command.shape_rotation {
1864 use crate::math::{classify_angle, AngleType};
1865 let flip_x = sr.flip_x;
1866 let flip_y = sr.flip_y;
1867 match classify_angle(sr.rotation_radians) {
1868 AngleType::Zero => {
1869 let cr = flip_corner_radii(cr, flip_x, flip_y);
1871 draw_good_rounded_rectangle(bb.x, bb.y, bb.width, bb.height, &cr, color);
1872 }
1873 AngleType::Right90 => {
1874 let cr = rotate_corner_radii_90(&flip_corner_radii(cr, flip_x, flip_y));
1875 draw_good_rounded_rectangle(bb.x, bb.y, bb.width, bb.height, &cr, color);
1876 }
1877 AngleType::Straight180 => {
1878 let cr = rotate_corner_radii_180(&flip_corner_radii(cr, flip_x, flip_y));
1879 draw_good_rounded_rectangle(bb.x, bb.y, bb.width, bb.height, &cr, color);
1880 }
1881 AngleType::Right270 => {
1882 let cr = rotate_corner_radii_270(&flip_corner_radii(cr, flip_x, flip_y));
1883 draw_good_rounded_rectangle(bb.x, bb.y, bb.width, bb.height, &cr, color);
1884 }
1885 AngleType::Arbitrary(theta) => {
1886 draw_good_rotated_rounded_rectangle(
1887 bb.x, bb.y, bb.width, bb.height,
1888 cr, color, theta, flip_x, flip_y,
1889 );
1890 }
1891 }
1892 } else if cr.top_left == 0.0 && cr.top_right == 0.0 && cr.bottom_left == 0.0 && cr.bottom_right == 0.0 {
1893 draw_rectangle(
1894 bb.x,
1895 bb.y,
1896 bb.width,
1897 bb.height,
1898 color
1899 );
1900 } else {
1901 draw_good_rounded_rectangle(bb.x, bb.y, bb.width, bb.height, cr, color);
1902 }
1903
1904 if has_effect {
1906 gl_use_default_material();
1907 }
1908 }
1909 #[cfg(feature = "text-styling")]
1910 RenderCommandConfig::Text(config) => {
1911 let bb = command.bounding_box;
1912 let font_size = config.font_size as f32;
1913 if let Some(asset) = config.font_asset {
1915 FontManager::ensure(asset).await;
1916 }
1917 let mut fm = FONT_MANAGER.lock().unwrap();
1919 let baseline_y = bb.y + fm.metrics(config.font_size, config.font_asset).baseline_offset;
1920 let font = if let Some(asset) = config.font_asset {
1921 fm.get(asset)
1922 } else {
1923 fm.get_default()
1924 };
1925 let default_color = ply_to_macroquad_color(&config.color);
1926
1927 let has_effect = !command.effects.is_empty();
1929 if has_effect {
1930 let effect = &command.effects[0];
1931 let mut mat_mgr = MATERIAL_MANAGER.lock().unwrap();
1932 let material = mat_mgr.get_or_create(effect);
1933 apply_shader_uniforms(material, effect, &bb);
1934 gl_use_material(material);
1935 }
1936
1937 let normal_render = || {
1938 let x_scale = compute_letter_spacing_x_scale(
1939 bb.width,
1940 count_visible_chars(&config.text),
1941 config.letter_spacing,
1942 );
1943 draw_text_ex(
1944 &config.text,
1945 bb.x,
1946 baseline_y,
1947 TextParams {
1948 font_size: config.font_size as u16,
1949 font,
1950 font_scale: 1.0,
1951 font_scale_aspect: x_scale,
1952 rotation: 0.0,
1953 color: default_color
1954 }
1955 );
1956 };
1957
1958 let mut in_style_def = false;
1959 let mut escaped = false;
1960 let mut failed = false;
1961
1962 let mut text_buffer = String::new();
1963 let mut style_buffer = String::new();
1964
1965 let line = config.text.to_string();
1966 let mut segments: Vec<StyledSegment> = Vec::new();
1967
1968 for c in line.chars() {
1969 if escaped {
1970 if in_style_def {
1971 style_buffer.push(c);
1972 } else {
1973 text_buffer.push(c);
1974 }
1975 escaped = false;
1976 continue;
1977 }
1978
1979 match c {
1980 '\\' => {
1981 escaped = true;
1982 }
1983 '{' => {
1984 if in_style_def {
1985 style_buffer.push(c);
1986 } else {
1987 if !text_buffer.is_empty() {
1988 segments.push(StyledSegment {
1989 text: text_buffer.clone(),
1990 styles: state.style_stack.clone(),
1991 });
1992 text_buffer.clear();
1993 }
1994 in_style_def = true;
1995 }
1996 }
1997 '|' => {
1998 if in_style_def {
1999 state.style_stack.push(style_buffer.clone());
2000 style_buffer.clear();
2001 in_style_def = false;
2002 } else {
2003 text_buffer.push(c);
2004 }
2005 }
2006 '}' => {
2007 if in_style_def {
2008 style_buffer.push(c);
2009 } else {
2010 if !text_buffer.is_empty() {
2011 segments.push(StyledSegment {
2012 text: text_buffer.clone(),
2013 styles: state.style_stack.clone(),
2014 });
2015 text_buffer.clear();
2016 }
2017
2018 if state.style_stack.pop().is_none() {
2019 failed = true;
2020 break;
2021 }
2022 }
2023 }
2024 _ => {
2025 if in_style_def {
2026 style_buffer.push(c);
2027 } else {
2028 text_buffer.push(c);
2029 }
2030 }
2031 }
2032 }
2033 if !(failed || in_style_def) {
2034 if !text_buffer.is_empty() {
2035 segments.push(StyledSegment {
2036 text: text_buffer.clone(),
2037 styles: state.style_stack.clone(),
2038 });
2039 }
2040
2041 let time = get_time();
2042
2043 let cursor_x = std::cell::Cell::new(bb.x);
2044 let cursor_y = baseline_y;
2045 let mut pending_renders = Vec::new();
2046
2047 let x_scale = compute_letter_spacing_x_scale(
2048 bb.width,
2049 count_visible_chars(&config.text),
2050 config.letter_spacing,
2051 );
2052 {
2053 let mut tracker = ANIMATION_TRACKER.lock().unwrap();
2054 let ts_default = crate::color::Color::rgba(
2055 config.color.r,
2056 config.color.g,
2057 config.color.b,
2058 config.color.a,
2059 );
2060 render_styled_text(
2061 &segments,
2062 time,
2063 font_size,
2064 ts_default,
2065 &mut *tracker,
2066 &mut state.total_char_index,
2067 |text, tr, style_color| {
2068 let text_string = text.to_string();
2069 let text_width = measure_text(&text_string, font, config.font_size as u16, 1.0).width;
2070
2071 let color = Color::new(style_color.r / 255.0, style_color.g / 255.0, style_color.b / 255.0, style_color.a / 255.0);
2072 let x = cursor_x.get();
2073
2074 pending_renders.push((x, text_string, tr, color));
2075
2076 cursor_x.set(x + text_width*x_scale);
2077 },
2078 |text, tr, style_color| {
2079 let text_string = text.to_string();
2080 let color = Color::new(style_color.r / 255.0, style_color.g / 255.0, style_color.b / 255.0, style_color.a / 255.0);
2081 let x = cursor_x.get();
2082
2083 draw_text_ex(
2084 &text_string,
2085 x + tr.x*x_scale,
2086 cursor_y + tr.y,
2087 TextParams {
2088 font_size: config.font_size as u16,
2089 font,
2090 font_scale: tr.scale_y.max(0.01),
2091 font_scale_aspect: if tr.scale_y > 0.01 { tr.scale_x / tr.scale_y * x_scale } else { x_scale },
2092 rotation: tr.rotation.to_radians(),
2093 color
2094 }
2095 );
2096 }
2097 );
2098 }
2099 for (x, text_string, tr, color) in pending_renders {
2100 draw_text_ex(
2101 &text_string,
2102 x + tr.x*x_scale,
2103 cursor_y + tr.y,
2104 TextParams {
2105 font_size: config.font_size as u16,
2106 font,
2107 font_scale: tr.scale_y.max(0.01),
2108 font_scale_aspect: if tr.scale_y > 0.01 { tr.scale_x / tr.scale_y * x_scale } else { x_scale },
2109 rotation: tr.rotation.to_radians(),
2110 color
2111 }
2112 );
2113 }
2114 } else {
2115 if in_style_def {
2116 warn!("Style definition didn't end! Here is what we tried to render: {}", config.text);
2117 } else if failed {
2118 warn!("Encountered }} without opened style! Make sure to escape curly braces with \\. Here is what we tried to render: {}", config.text);
2119 }
2120 normal_render();
2121 }
2122
2123 if has_effect {
2125 gl_use_default_material();
2126 }
2127 }
2128 #[cfg(not(feature = "text-styling"))]
2129 RenderCommandConfig::Text(config) => {
2130 let bb = command.bounding_box;
2131 let color = ply_to_macroquad_color(&config.color);
2132 if let Some(asset) = config.font_asset {
2134 FontManager::ensure(asset).await;
2135 }
2136 let mut fm = FONT_MANAGER.lock().unwrap();
2138 let baseline_y = bb.y + fm.metrics(config.font_size, config.font_asset).baseline_offset;
2139 let font = if let Some(asset) = config.font_asset {
2140 fm.get(asset)
2141 } else {
2142 fm.get_default()
2143 };
2144
2145 let has_effect = !command.effects.is_empty();
2147 if has_effect {
2148 let effect = &command.effects[0];
2149 let mut mat_mgr = MATERIAL_MANAGER.lock().unwrap();
2150 let material = mat_mgr.get_or_create(effect);
2151 apply_shader_uniforms(material, effect, &bb);
2152 gl_use_material(material);
2153 }
2154
2155 let x_scale = compute_letter_spacing_x_scale(
2156 bb.width,
2157 config.text.chars().count(),
2158 config.letter_spacing,
2159 );
2160 draw_text_ex(
2161 &config.text,
2162 bb.x,
2163 baseline_y,
2164 TextParams {
2165 font_size: config.font_size as u16,
2166 font,
2167 font_scale: 1.0,
2168 font_scale_aspect: x_scale,
2169 rotation: 0.0,
2170 color
2171 }
2172 );
2173
2174 if has_effect {
2176 gl_use_default_material();
2177 }
2178 }
2179 RenderCommandConfig::Border(config) => {
2180 let bb = command.bounding_box;
2181 let bw = &config.width;
2182 let cr = &config.corner_radii;
2183 let color = ply_to_macroquad_color(&config.color);
2184 let s = match config.position {
2185 BorderPosition::Outside => 1.,
2186 BorderPosition::Middle => 0.5,
2187 BorderPosition::Inside => 0.0,
2188 };
2189
2190 let get_sides = |corner: f32| {
2191 (std::f32::consts::PI * corner / (2.0 * PIXELS_PER_POINT)).max(5.0) as usize
2192 };
2193 let v = |x: f32, y: f32| Vertex::new(x, y, 0., 0., 0., color);
2194
2195 let top = bw.top as f32;
2196 let left = bw.left as f32;
2197 let bottom = bw.bottom as f32;
2198 let right = bw.right as f32;
2199 let tl_r = cr.top_left;
2200 let tr_r = cr.top_right;
2201 let bl_r = cr.bottom_left;
2202 let br_r = cr.bottom_right;
2203
2204 let ox1 = bb.x - left * s;
2205 let ox2 = bb.x + bb.width + right * s;
2206 let oy1 = bb.y - top * s;
2207 let oy2 = bb.y + bb.height + bottom * s;
2208 let ix1 = bb.x + left * (1.0 - s);
2209 let ix2 = bb.x + bb.width - right * (1.0 - s);
2210 let iy1 = bb.y + top * (1.0 - s);
2211 let iy2 = bb.y + bb.height - bottom * (1.0 - s);
2212
2213 let o_tl_rx = tl_r + left * s;
2214 let o_tl_ry = tl_r + top * s;
2215 let o_tr_rx = tr_r + right * s;
2216 let o_tr_ry = tr_r + top * s;
2217 let o_bl_rx = bl_r + left * s;
2218 let o_bl_ry = bl_r + bottom * s;
2219 let o_br_rx = br_r + right * s;
2220 let o_br_ry = br_r + bottom * s;
2221 let i_tl_rx = (tl_r - left * (1.0 - s)).max(0.0);
2222 let i_tl_ry = (tl_r - top * (1.0 - s)).max(0.0);
2223 let i_tr_rx = (tr_r - right * (1.0 - s)).max(0.0);
2224 let i_tr_ry = (tr_r - top * (1.0 - s)).max(0.0);
2225 let i_bl_rx = (bl_r - left * (1.0 - s)).max(0.0);
2226 let i_bl_ry = (bl_r - bottom * (1.0 - s)).max(0.0);
2227 let i_br_rx = (br_r - right * (1.0 - s)).max(0.0);
2228 let i_br_ry = (br_r - bottom * (1.0 - s)).max(0.0);
2229
2230 let tl_sides = get_sides(o_tl_rx.max(o_tl_ry).max(i_tl_rx).max(i_tl_ry));
2231 let tr_sides = get_sides(o_tr_rx.max(o_tr_ry).max(i_tr_rx).max(i_tr_ry));
2232 let bl_sides = get_sides(o_bl_rx.max(o_bl_ry).max(i_bl_rx).max(i_bl_ry));
2233 let br_sides = get_sides(o_br_rx.max(o_br_ry).max(i_br_rx).max(i_br_ry));
2234 let side_count = tl_sides + tr_sides + bl_sides + br_sides;
2235
2236 let mut vertices = Vec::<Vertex>::with_capacity(16 + side_count * 4);
2237 let mut indices = Vec::<u16>::with_capacity(24 + side_count * 6);
2238
2239 vertices.extend([
2241 v(ox1 + o_tl_rx, oy1),
2243 v(ox2 - o_tr_rx, oy1),
2244 v(ix1 + i_tl_rx, iy1),
2245 v(ix2 - i_tr_rx, iy1),
2246 v(ox1 + o_bl_rx, oy2),
2248 v(ox2 - o_br_rx, oy2),
2249 v(ix1 + i_bl_rx, iy2),
2250 v(ix2 - i_br_rx, iy2),
2251 v(ox1, oy1 + o_tl_ry),
2253 v(ox1, oy2 - o_bl_ry),
2254 v(ix1, iy1 + i_tl_ry),
2255 v(ix1, iy2 - i_bl_ry),
2256 v(ox2, oy1 + o_tr_ry),
2258 v(ox2, oy2 - o_br_ry),
2259 v(ix2, iy1 + i_tr_ry),
2260 v(ix2, iy2 - i_br_ry),
2261 ]);
2262 for l in [0, 4, 8, 12] {
2263 indices.extend([
2264 l, l + 1, l + 2,
2265 l + 1, l + 3, l + 2
2266 ]);
2267 }
2268
2269 let corners = [
2270 (
2271 tl_sides,
2272 PI,
2273 ox1 + o_tl_rx,
2274 oy1 + o_tl_ry,
2275 ix1 + i_tl_rx,
2276 iy1 + i_tl_ry,
2277 o_tl_rx,
2278 o_tl_ry,
2279 i_tl_rx,
2280 i_tl_ry,
2281 ),
2282 (
2283 tr_sides,
2284 PI * 1.5,
2285 ox2 - o_tr_rx,
2286 oy1 + o_tr_ry,
2287 ix2 - i_tr_rx,
2288 iy1 + i_tr_ry,
2289 o_tr_rx,
2290 o_tr_ry,
2291 i_tr_rx,
2292 i_tr_ry,
2293 ),
2294 (
2295 bl_sides,
2296 PI * 0.5,
2297 ox1 + o_bl_rx,
2298 oy2 - o_bl_ry,
2299 ix1 + i_bl_rx,
2300 iy2 - i_bl_ry,
2301 o_bl_rx,
2302 o_bl_ry,
2303 i_bl_rx,
2304 i_bl_ry,
2305 ),
2306 (
2307 br_sides,
2308 0.,
2309 ox2 - o_br_rx,
2310 oy2 - o_br_ry,
2311 ix2 - i_br_rx,
2312 iy2 - i_br_ry,
2313 o_br_rx,
2314 o_br_ry,
2315 i_br_rx,
2316 i_br_ry,
2317 ),
2318 ];
2319
2320 for (sides, start, ocx, ocy, icx, icy, o_rx, o_ry, i_rx, i_ry) in corners {
2321 let step = (PI / 2.) / (sides as f32);
2322
2323 for i in 0..sides {
2324 let i = i as f32;
2325 let a1 = start + i * step;
2326 let a2 = a1 + step;
2327 let l = vertices.len() as u16;
2328
2329 vertices.extend([
2331 v(ocx + a1.cos() * o_rx, ocy + a1.sin() * o_ry),
2332 v(ocx + a2.cos() * o_rx, ocy + a2.sin() * o_ry),
2333 v(icx + a1.cos() * i_rx, icy + a1.sin() * i_ry),
2334 v(icx + a2.cos() * i_rx, icy + a2.sin() * i_ry),
2335 ]);
2336 indices.extend([
2337 l, l + 1, l + 2,
2338 l + 1, l + 3, l + 2
2339 ]);
2340 }
2341 }
2342
2343 draw_mesh(&Mesh { vertices, indices, texture: None });
2344 }
2345 RenderCommandConfig::ScissorStart() => {
2346 let bb = command.bounding_box;
2347 let dpi = miniquad::window::dpi_scale();
2352 let next_clip = (
2353 (bb.x * dpi) as i32,
2354 (bb.y * dpi) as i32,
2355 (bb.width * dpi) as i32,
2356 (bb.height * dpi) as i32,
2357 );
2358
2359 let effective_clip = if let Some(parent_clip) = state.clip_stack.last().copied() {
2360 intersect_scissor(parent_clip, next_clip)
2361 } else {
2362 next_clip
2363 };
2364
2365 state.clip_stack.push(effective_clip);
2366 unsafe {
2367 get_internal_gl().quad_gl.scissor(state.clip_stack.last().copied());
2368 }
2369 }
2370 RenderCommandConfig::ScissorEnd() => {
2371 state.clip_stack.pop();
2372 unsafe {
2373 get_internal_gl().quad_gl.scissor(state.clip_stack.last().copied());
2374 }
2375 }
2376 RenderCommandConfig::Custom(_) => {
2377 handle_custom_command(&command);
2378 }
2379 RenderCommandConfig::GroupBegin { ref shader, ref visual_rotation } => {
2380 let bb = command.bounding_box;
2381 let rt = render_target_msaa(bb.width as u32, bb.height as u32);
2382 rt.texture.set_filter(FilterMode::Linear);
2383 let cam = Camera2D {
2384 render_target: Some(rt.clone()),
2385 ..Camera2D::from_display_rect(Rect::new(
2386 bb.x, bb.y, bb.width, bb.height,
2387 ))
2388 };
2389 set_camera(&cam);
2390 clear_background(Color::new(0.0, 0.0, 0.0, 0.0));
2391 state.rt_stack.push((rt, shader.clone(), *visual_rotation, bb));
2392 }
2393 RenderCommandConfig::GroupEnd => {
2394 if let Some((rt, shader_config, visual_rotation, bb)) = state.rt_stack.pop() {
2395 if let Some((prev_rt, _, _, prev_bb)) = state.rt_stack.last() {
2397 let cam = Camera2D {
2398 render_target: Some(prev_rt.clone()),
2399 ..Camera2D::from_display_rect(Rect::new(
2400 prev_bb.x, prev_bb.y, prev_bb.width, prev_bb.height,
2401 ))
2402 };
2403 set_camera(&cam);
2404 } else {
2405 set_default_camera();
2406 }
2407
2408 if let Some(ref config) = shader_config {
2410 let mut mat_mgr = MATERIAL_MANAGER.lock().unwrap();
2411 let material = mat_mgr.get_or_create(config);
2412 apply_shader_uniforms(material, config, &bb);
2413 gl_use_material(material);
2414 }
2415
2416 let (rotation, flip_x, flip_y, pivot) = match &visual_rotation {
2418 Some(rot) => {
2419 let pivot_screen = Vec2::new(
2420 bb.x + rot.pivot_x * bb.width,
2421 bb.y + rot.pivot_y * bb.height,
2422 );
2423 (rot.rotation_radians, rot.flip_x, !rot.flip_y, Some(pivot_screen))
2425 }
2426 None => (0.0, false, true, None),
2427 };
2428
2429 draw_texture_ex(
2430 &rt.texture,
2431 bb.x,
2432 bb.y,
2433 WHITE,
2434 DrawTextureParams {
2435 dest_size: Some(Vec2::new(bb.width, bb.height)),
2436 rotation,
2437 flip_x,
2438 flip_y,
2439 pivot,
2440 ..Default::default()
2441 },
2442 );
2443
2444 if shader_config.is_some() {
2445 gl_use_default_material();
2446 }
2447 }
2448 }
2449 RenderCommandConfig::None() => {}
2450 }
2451 }
2452 TEXTURE_MANAGER.lock().unwrap().clean();
2453 MATERIAL_MANAGER.lock().unwrap().clean();
2454 FONT_MANAGER.lock().unwrap().clean();
2455}
2456
2457pub fn create_measure_text_function(
2458) -> impl Fn(&str, &crate::TextConfig) -> crate::Dimensions + 'static {
2459 move |text: &str, config: &crate::TextConfig| {
2460 #[cfg(feature = "text-styling")]
2461 let cleaned_text = {
2462 let mut result = String::new();
2464 let mut in_style_def = false;
2465 let mut escaped = false;
2466 for c in text.chars() {
2467 if escaped {
2468 result.push(c);
2469 escaped = false;
2470 continue;
2471 }
2472 match c {
2473 '\\' => {
2474 escaped = true;
2475 }
2476 '{' => {
2477 in_style_def = true;
2478 }
2479 '|' => {
2480 if in_style_def {
2481 in_style_def = false;
2482 } else {
2483 result.push(c);
2484 }
2485 }
2486 '}' => {
2487 }
2489 _ => {
2490 if !in_style_def {
2491 result.push(c);
2492 }
2493 }
2494 }
2495 }
2496 if in_style_def {
2497 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);
2498 }
2499 result
2500 };
2501 #[cfg(not(feature = "text-styling"))]
2502 let cleaned_text = text.to_string();
2503 let mut fm = FONT_MANAGER.lock().unwrap();
2504 let font = if let Some(asset) = config.font_asset {
2506 fm.get(asset)
2507 } else {
2508 fm.get_default()
2509 };
2510 let measured = macroquad::text::measure_text(
2511 &cleaned_text,
2512 font,
2513 config.font_size,
2514 1.0,
2515 );
2516 let metrics = fm.metrics(config.font_size as u16, config.font_asset);
2517 let added_space = (cleaned_text.chars().count().max(1) - 1) as f32 * config.letter_spacing as f32;
2518 crate::Dimensions::new(measured.width + added_space, metrics.height)
2519 }
2520}
2521
2522#[cfg(feature = "text-styling")]
2525fn count_visible_chars(text: &str) -> usize {
2526 let mut count = 0;
2527 let mut in_style_def = false;
2528 let mut escaped = false;
2529 for c in text.chars() {
2530 if escaped { count += 1; escaped = false; continue; }
2531 match c {
2532 '\\' => { escaped = true; }
2533 '{' => { in_style_def = true; }
2534 '|' => { if in_style_def { in_style_def = false; } else { count += 1; } }
2535 '}' => { }
2536 _ => { if !in_style_def { count += 1; } }
2537 }
2538 }
2539 count
2540}
2541
2542fn compute_letter_spacing_x_scale(bb_width: f32, visible_char_count: usize, letter_spacing: u16) -> f32 {
2549 if letter_spacing == 0 || visible_char_count <= 1 {
2550 return 1.0;
2551 }
2552 let total_spacing = (visible_char_count as f32 - 1.0) * letter_spacing as f32;
2553 let raw_width = bb_width - total_spacing;
2554 if raw_width > 0.0 {
2555 bb_width / raw_width
2556 } else {
2557 1.0
2558 }
2559}