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