1 /**
2   This module provides a RoundRect type.
3 
4   Copyright: Chris Jones
5   License: Boost Software License, Version 1.0
6   Authors: Chris Jones
7 
8   General features...
9 
10   Custom radius on all four corners
11   asPath can be used to get a PathIterator to trace the outline
12   offset,scale,inset,outset,intersection and combine (IE union)
13   operator overloads and free functions
14 */
15 
16 module dg2d.roundrect;
17 
18 import dg2d.scalar;
19 import dg2d.point;
20 import dg2d.rect;
21 import dg2d.misc;
22 import dg2d.path;
23 
24 import std.algorithm: among;
25 
26 /** Rounded rectangle. */
27 
28 struct RoundRect
29 {
30     Scalar x0 = 0;
31     Scalar y0 = 0;
32     Scalar x1 = 0;
33     Scalar y1 = 0;
34     float[4] xRad = 0;
35     float[4] yRad = 0;
36 
37     /** Construct a RoundRect with square corners. */    
38 
39     this(Scalar x0, Scalar y0, Scalar x1, Scalar y1)
40     {
41         this.x0 = x0;
42         this.y0 = y0;
43         this.x1 = x1;
44         this.y1 = y1;
45     }
46 
47     /** Construct a RoundRect with circular corners. */    
48 
49     this(Scalar x0, Scalar y0, Scalar x1, Scalar y1, Scalar rad)
50     {
51         this.x0 = x0;
52         this.y0 = y0;
53         this.x1 = x1;
54         this.y1 = y1;
55         this.xRad = rad;
56         this.yRad = rad;
57     }
58 
59     /** Construct a RoundRect with eliptical corners. */    
60 
61     this(Scalar x0, Scalar y0, Scalar x1, Scalar y1, Scalar xRad, Scalar yRad)
62     {
63         this.x0 = x0;
64         this.y0 = y0;
65         this.x1 = x1;
66         this.y1 = y1;
67         this.xRad = xRad;
68         this.yRad = yRad;
69     }
70 
71     /** Construct a RoundRect specifying each corner seperately. */    
72 
73     this(Scalar x0, Scalar y0, Scalar x1, Scalar y1, float[4] xRad, float[4] yRad)
74     {
75         this.x0 = x0;
76         this.y0 = y0;
77         this.x1 = x1;
78         this.y1 = y1;
79         this.xRad = xRad;
80         this.yRad = yRad;
81     }
82 
83     /** returns the width */
84 
85     Scalar width()
86     {
87         return x1-x0;
88     }
89 
90     /** returns the height */
91     
92     Scalar height()
93     {
94         return y1-y0;
95     }
96 
97     /** returns the area */
98 
99     Scalar area()
100     {
101         return width*height;
102     }
103 
104     /** returns the center of the rect */
105 
106     Point center()
107     {
108         return Point((x0+x1)/2,(y0+y1)/2);
109     }
110 
111     /** Returns true if (x0 >= x1) or (y0 >= y1) */
112 
113     bool isEmpty()
114     {
115         return ((x0 >= x1) || (y0 >= y1));
116     }
117 
118     /* operator overload for add and subtract */
119  
120     RoundRect opBinary(string op)(Point rhs)
121         if (op.among!("+", "-"))
122     {
123         mixin("return Rect!T(x0 "~op~" rhs.x, y0 "~op~"rhs.y, x1 "
124           ~op~" rhs.x, y1 "~op~"rhs.y);");
125     }
126 
127     /* operator overload for multiply */
128 
129     RoundRect opBinary(string op)(Point rhs)
130         if (op == "*")
131     {
132         return RoundRect(
133             x0*rhs.x, y0*rhs.y, x1*rhs.x, y1*rhs.y,
134             [xRad[0]*rhs.x, xRad[1]*rhs.x, xRad[2]*rhs.x, xRad[3]*rhs.x],
135             [yRad[0]*rhs.y, yRad[1]*rhs.y, yRad[2]*rhs.y, yRad[3]*rhs.y]
136             );
137     }
138 
139     /* operator overload for add and subtract */
140  
141     RoundRect opBinary(string op)(Scalar[2] rhs)
142         if (op.among!("+", "-"))
143     {
144         mixin("return Rect!T(x0 "~op~" rhs[0], y0 "~op~"rhs[1], x1 "
145           ~op~" rhs[0], y1 "~op~"rhs[1]);");
146     }
147 
148     /* operator overload for multiply */
149 
150     RoundRect opBinary(string op)(Scalar[2] rhs)
151         if (op == "*")
152     {
153         return RoundRect(
154             x0*rhs[0], y0*rhs[1], x1*rhs[0], y1*rhs[1],
155             [xRad[0]*rhs[0], xRad[1]*rhs[0], xRad[2]*rhs[0], xRad[3]*rhs[0]],
156             [yRad[0]*rhs[1], yRad[1]*rhs[1], yRad[2]*rhs[1], yRad[3]*rhs[1]]
157             );
158     }
159 
160     /* operator overload for multiply */
161 
162     RoundRect opBinary(string op)(Scalar rhs)
163         if (op == "*")
164     {
165         return RoundRect(
166             x0*rhs, y0*rhs, x1*rhs, y1*rhs,
167             [xRad[0]*rhs, xRad[1]*rhs, xRad[2]*rhs, xRad[3]*rhs],
168             [yRad[0]*rhs, yRad[1]*rhs, yRad[2]*rhs, yRad[3]*rhs]
169             );
170     }
171 
172     /*
173       The corners are aproximated with a bezier curve. Look up CBezInset
174       in scalar.d for more info.
175     */
176 
177     auto asPath()
178     {
179         enum PathCmd[17] cmdlut = [
180             PathCmd.move,PathCmd.line,PathCmd.cubic,PathCmd.cubic,PathCmd.cubic,
181             PathCmd.line,PathCmd.cubic,PathCmd.cubic,PathCmd.cubic,
182             PathCmd.line,PathCmd.cubic,PathCmd.cubic,PathCmd.cubic,
183             PathCmd.line,PathCmd.cubic,PathCmd.cubic,PathCmd.cubic];
184 
185         struct RectAsPath
186         {
187         public:
188             Point opIndex(size_t idx)
189             {
190                 assert(idx < 17);
191                 switch(idx)
192                 {
193                     case 0: return Point(m_rect.x0+m_rect.xRad[0],m_rect.y0);
194                     case 1: return Point(m_rect.x1-m_rect.xRad[1],m_rect.y0);
195                     case 2: return Point(m_rect.x1-m_rect.xRad[1]*CBezInset,m_rect.y0);
196                     case 3: return Point(m_rect.x1,m_rect.y0+m_rect.yRad[1]*CBezInset);
197                     case 4: return Point(m_rect.x1,m_rect.y0+m_rect.yRad[1]);
198                     case 5: return Point(m_rect.x1,m_rect.y1-m_rect.yRad[2]);
199                     case 6: return Point(m_rect.x1,m_rect.y1-m_rect.yRad[2]*CBezInset);
200                     case 7: return Point(m_rect.x1-m_rect.xRad[2]*CBezInset,m_rect.y1);
201                     case 8: return Point(m_rect.x1-m_rect.xRad[2],m_rect.y1);
202                     case 9: return Point(m_rect.x0+m_rect.xRad[3],m_rect.y1);
203                     case 10: return Point(m_rect.x0+m_rect.xRad[3]*CBezInset,m_rect.y1);
204                     case 11: return Point(m_rect.x0,m_rect.y1-m_rect.yRad[3]*CBezInset);
205                     case 12: return Point(m_rect.x0,m_rect.y1-m_rect.yRad[3]);
206                     case 13: return Point(m_rect.x0,m_rect.y0+m_rect.yRad[0]);
207                     case 14: return Point(m_rect.x0,m_rect.y0+m_rect.yRad[0]*CBezInset);
208                     case 15: return Point(m_rect.x0+m_rect.xRad[0]*CBezInset,m_rect.y0);
209                     case 16: return Point(m_rect.x0+m_rect.xRad[0],m_rect.y0);
210                     default: assert(0);
211                 }
212             }
213             PathCmd cmd(size_t idx)
214             {
215                 assert(idx < 17);
216                 return cmdlut[idx];
217             }
218             size_t length() { return 17; }
219             void* source() { return &this; }
220             bool inPlace() { return true; }
221         private:
222             RoundRect* m_rect;
223         }
224 
225         return RectAsPath(&this);
226     }
227 }
228 
229 /**
230   Returns rect offset by x,y
231 */
232 
233 RoundRect offset(RoundRect rect, Scalar x, Scalar y)
234 {
235     return RoundRect(rect.x0+x,rect.y0+y,rect.x1+x,rect.y1+y);
236 }
237 
238 /**
239   Returns rect offset point
240 */
241 
242 RoundRect offset(RoundRect rect, Point point)
243 {
244     return RoundRect(rect.x0+point.x,rect.y0+point.y,rect.x1+point.x,rect.y1+point.y);
245 }
246 
247 /**
248   Returns rect scaled by scale_x,scale_y
249 */
250 
251 RoundRect scale(RoundRect rect, Scalar scale_x, Scalar scale_y)
252 {
253     return rect * [scale_x,scale_y];
254 }
255 
256 /**
257   Returns rect inset by delta, if you "adjustCorners" the corners will
258   be adjusted to maintain equal distance from the source rect. (unless
259   they get too small)
260 */
261 
262 RoundRect inset(bool adjustCorners = true)(RoundRect rect, Scalar delta)
263 {
264     RoundRect tmp = RoundRect(rect.x0 + delta, rect.y0 + delta,
265         rect.x1 - delta, rect.y1 - delta);
266 
267     static if (adjustCorners) 
268     {
269         static foreach (i; 0..4)
270         {
271             tmp.xRad[i] = max(0,rect.xRad[i]-delta);
272             tmp.yRad[i] = max(0,rect.yRad[i]-delta);
273         }
274     }
275     else
276     {
277         tmp.xRad = rect.xRad;
278         tmp.yRad = rect.yRad;
279     }
280     return tmp;
281 }
282 
283 /**
284   Returns rect outset by delta, if you "adjustCorners" the corners will
285   be adjusted to maintain equal distance from the source rect.
286 */
287 
288 RoundRect outset(bool adjustCorners = true)(RoundRect rect, Scalar delta)
289 {
290     RoundRect tmp = RoundRect(rect.x0 - delta, rect.y0 - delta,
291         rect.x1 + delta, rect.y1 + delta);
292 
293     static if (adjustCorners) 
294     {
295         static foreach (i; 0..4)
296         {
297             tmp.xRad[i] = max(0,rect.xRad[i]+delta);
298             tmp.yRad[i] = max(0,rect.yRad[i]+delta);
299         }
300     }
301     else
302     {
303         tmp.xRad = rect.xRad;
304         tmp.yRad = rect.yRad;
305     }
306     return tmp;
307 }
308 
309 /**
310   returns the intersection of a and b, returns a plain rect as not sure what to do with
311   the corners.
312 */
313 
314 Rect intersect(RoundRect a, RoundRect b)
315 {
316     return Rect(
317         max(a.x0, b.x0), max(a.y0, b.y0),
318         min(a.x1, b.x1), min(a.y1, b.y1)
319         );
320 }
321 
322 /**
323   returns the union of a and b, returns a plain rect as not sure what to do with
324   the corners.
325 */
326 
327 Rect combine(RoundRect a, RoundRect b)
328 {
329     return Rect(
330         min(a.x0, b.x0), min(a.y0, b.y0),
331         max(a.x1, b.x1), max(a.y1, b.y1)
332         );
333 }
334