1use std::cell::Cell;
14
15use blockworx_doc::block_model::Asset;
16use blockworx_geom::{Align2, Angle, Pos2, Rect, Vec2, WorldPx};
17
18use crate::{
19 Font, Renderer,
20 palette::{PaletteStroke, Swatch},
21 text::Layout,
22};
23#[derive(Default)]
25pub struct Bounds(Cell<Option<Rect>>);
26
27impl Bounds {
28 pub fn get(&self) -> Option<Rect> {
30 self.0.get()
31 }
32
33 pub fn point(&self, p: Pos2) {
34 self.rect(Rect::from_min_max(p, p));
35 }
36
37 pub fn rect(&self, r: Rect) {
38 self.0.set(Some(match self.0.get() {
39 Some(b) => b.union(r),
40 None => r,
41 }));
42 }
43
44 pub fn stroked(&self, r: Rect, stroke: &PaletteStroke) {
47 self.rect(r.expand(stroke.width.get() * 0.5));
48 }
49
50 pub fn path(&self, points: &[Pos2]) {
52 for p in points {
53 self.point(*p);
54 }
55 }
56
57 pub fn disc(&self, center: Pos2, radius: WorldPx, stroke: &PaletteStroke) {
58 let pad = radius.get() + stroke.width.get() * 0.5;
59 self.rect(Rect::from_center_size(center, Vec2::splat(2.0 * pad)));
60 }
61}
62
63pub struct Extent<'r, R: Renderer> {
68 inner: &'r R,
69 bounds: Bounds,
70}
71
72impl<'r, R: Renderer> Extent<'r, R> {
73 pub fn measuring_through(inner: &'r R) -> Self {
74 Self {
75 inner,
76 bounds: Bounds::default(),
77 }
78 }
79
80 pub fn finish(self) -> Option<Rect> {
82 self.bounds.get()
83 }
84}
85
86impl<R: Renderer> Renderer for Extent<'_, R> {
87 fn rect(
88 &self,
89 rect: Rect,
90 _rounding: WorldPx,
91 _fill: impl Into<Swatch>,
92 stroke: impl Into<PaletteStroke>,
93 ) {
94 self.bounds.stroked(rect, &stroke.into());
95 }
96
97 fn line_segment(&self, points: [Pos2; 2], _stroke: impl Into<PaletteStroke>) {
98 self.bounds.path(&points);
99 }
100
101 fn line(&self, points: Vec<Pos2>, _stroke: impl Into<PaletteStroke>) {
102 self.bounds.path(&points);
103 }
104
105 fn circle(
106 &self,
107 center: Pos2,
108 radius: WorldPx,
109 _fill: impl Into<Swatch>,
110 stroke: impl Into<PaletteStroke>,
111 ) {
112 self.bounds.disc(center, radius, &stroke.into());
113 }
114
115 fn add_convex_polygon(
116 &self,
117 points: Vec<Pos2>,
118 _fill: impl Into<Swatch>,
119 _stroke: impl Into<PaletteStroke>,
120 ) {
121 self.bounds.path(&points);
122 }
123
124 fn text(
125 &self,
126 pos: Pos2,
127 anchor: Align2,
128 text: impl ToString,
129 font: &Font,
130 _color: impl Into<Swatch>,
131 ) -> Rect {
132 self.text_wrapped(
133 pos,
134 anchor,
135 text,
136 font,
137 Swatch::TRANSPARENT,
138 WorldPx::UNBOUNDED,
139 )
140 }
141
142 fn text_wrapped(
143 &self,
144 pos: Pos2,
145 anchor: Align2,
146 text: impl ToString,
147 font: &Font,
148 _color: impl Into<Swatch>,
149 max_width: WorldPx,
150 ) -> Rect {
151 let size = self.inner.text_size_wrapped(text, font, max_width);
152 let rect = anchor.anchor_size(pos, size);
153 if size != Vec2::ZERO {
154 self.bounds.rect(rect);
155 }
156 rect
157 }
158
159 fn rotated_text(
160 &self,
161 pos: Pos2,
162 anchor: Align2,
163 text: impl ToString,
164 font: &Font,
165 _color: impl Into<Swatch>,
166 _angle: Angle,
167 ) {
168 let size = self.inner.text_size(text, font);
169 if size != Vec2::ZERO {
170 self.bounds.point(pos);
174 self.bounds.rect(anchor.anchor_size(pos, size));
175 }
176 }
177
178 fn text_size(&self, text: impl ToString, font: &Font) -> Vec2 {
179 self.inner.text_size(text, font)
180 }
181
182 fn text_size_wrapped(&self, text: impl ToString, font: &Font, max_width: WorldPx) -> Vec2 {
183 self.inner.text_size_wrapped(text, font, max_width)
184 }
185
186 fn text_layout(&self, text: &str, font: &Font, max_width: WorldPx) -> Layout {
187 self.inner.text_layout(text, font, max_width)
188 }
189
190 fn draw_image(&self, rect: Rect, _image: &Asset) {
191 self.bounds.rect(rect);
192 }
193
194 fn image_intrinsic_size(&self, image: &Asset) -> Option<Vec2> {
195 self.inner.image_intrinsic_size(image)
196 }
197}