Skip to content

Commit 050994f

Browse files
committed
Improving drawing of talking bubbles.
1 parent 82572d1 commit 050994f

File tree

1 file changed

+253
-30
lines changed

1 file changed

+253
-30
lines changed

src/canvas_items/CanvasItemBubble.vala

Lines changed: 253 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,6 @@ public class CanvasItemBubble : CanvasItem {
7676
_sel_cursors[6] = new Cursor.from_name( "s-resize", null );
7777
_sel_cursors[7] = new Cursor.from_name( "w-resize", null );
7878
_sel_cursors[8] = new Cursor.from_name( "crosshair", null ); // Talking point
79-
_sel_cursors[9] = new Cursor.from_name( "ew-resize", null ); // Base 0
80-
_sel_cursors[10] = new Cursor.from_name( "ew-resize", null ); // Base 1
8179
}
8280

8381
/* Create the points */
@@ -91,8 +89,6 @@ public class CanvasItemBubble : CanvasItem {
9189
points.append_val( new CanvasPoint( CanvasPointType.RESIZER2 ) ); // bottom
9290
points.append_val( new CanvasPoint( CanvasPointType.RESIZER3 ) ); // left
9391
points.append_val( new CanvasPoint( CanvasPointType.CONTROL ) ); // Talk point
94-
points.append_val( new CanvasPoint( CanvasPointType.CONTROL ) ); // Where talk point attaches to bubble
95-
points.append_val( new CanvasPoint( CanvasPointType.CONTROL ) ); // Where talk point attaches to bubble
9692
}
9793

9894
public override void copy( CanvasItem item ) {
@@ -129,22 +125,6 @@ public class CanvasItemBubble : CanvasItem {
129125
points.index( 8 ).copy_coords( bbox.mid_x(), (bbox.y2() + 70) );
130126
}
131127

132-
if( !_base_moved ) {
133-
points.index( 9 ).copy_coords( (bbox.mid_x() + 20), bbox.y2() );
134-
points.index( 10 ).copy_coords( (bbox.mid_x() + 50), bbox.y2() );
135-
} else {
136-
points.index( 9 ).copy_coords( points.index( 9 ).x, bbox.y2() );
137-
points.index( 10 ).copy_coords( points.index( 10 ).x, bbox.y2() );
138-
}
139-
140-
if( points.index( 10 ).x > bbox.x2() ) {
141-
points.index( 10 ).x = bbox.x2();
142-
}
143-
144-
if( points.index( 9 ).x < bbox.x1() ) {
145-
points.index( 9 ).x = bbox.x1();
146-
}
147-
148128
}
149129

150130
/* Adjusts the bounding box */
@@ -164,8 +144,6 @@ public class CanvasItemBubble : CanvasItem {
164144
case 6 : box.height += diffy; break;
165145
case 7 : box.x += diffx; box.width -= diffx; break;
166146
case 8 : points.index( 8 ).x += diffx; points.index( 8 ).y += diffy; _point_moved = true; break;
167-
case 9 : points.index( 9 ).x += diffx; _base_moved = true; break;
168-
case 10 : points.index( 10 ).x += diffx; _base_moved = true; break;
169147
}
170148

171149
if( (box.width >= 60) && (box.height >= 1) ) {
@@ -179,19 +157,264 @@ public class CanvasItemBubble : CanvasItem {
179157
return( _sel_cursors[index] );
180158
}
181159

160+
/* Helper function that finds the two tangential points on a circle to a given point */
161+
private bool find_tangents( CanvasPoint center, double radius, CanvasPoint external, CanvasPoint pt1, CanvasPoint pt2 ) {
162+
163+
var dx = center.x - external.x;
164+
var dy = center.y - external.y;
165+
var dsq = (dx * dx) + (dy * dy);
166+
var rsq = radius * radius;
167+
if( dsq < rsq ) {
168+
return( false );
169+
}
170+
var l = Math.sqrt( dsq - rsq );
171+
172+
return( find_circle_circle_intersections( center, radius, external, l, pt1, pt2 ) );
173+
174+
}
175+
176+
/* Helper function that finds the two points where two circles intersect */
177+
private bool find_circle_circle_intersections( CanvasPoint c0, double r0, CanvasPoint c1, double r1, CanvasPoint pt1, CanvasPoint pt2 ) {
178+
179+
var dx = c0.x - c1.x;
180+
var dy = c0.y - c1.y;
181+
var dist = Math.sqrt( (dx * dx) + (dy * dy) );
182+
if( (dist > (r0 + r1)) || (dist < Math.fabs( r0 - r1 )) || ((dist == 0) && (r0 == r1)) ) {
183+
return( false );
184+
}
185+
var a = ((r0 * r0) - (r1 * r1) + (dist * dist)) / (2 * dist);
186+
var h = Math.sqrt( (r0 * r0) - (a * a) );
187+
var cx2 = c0.x + a * (c1.x - c0.x) / dist;
188+
var cy2 = c0.y + a * (c1.y - c0.y) / dist;
189+
190+
pt1.copy_coords( (cx2 + (h * (c1.y - c0.y) / dist)), (cy2 - (h * (c1.x - c0.x) / dist)) );
191+
pt2.copy_coords( (cx2 - (h * (c1.y - c0.y) / dist)), (cy2 + (h * (c1.x - c0.x) / dist)) );
192+
193+
return( true );
194+
195+
}
196+
197+
//-------------------------------------------------------------
198+
// Returns the X, Y point where two lines intersect where p0 and p1
199+
// are the points for the first line and p2, p3 are the points for the second line
200+
private bool get_line_intersection( CanvasPoint p0, CanvasPoint p1, CanvasPoint p2, CanvasPoint p3, out CanvasPoint? pi ) {
201+
202+
var s1 = new CanvasPoint.from_coords( (p1.x - p0.x), (p1.y - p0.y) );
203+
var s2 = new CanvasPoint.from_coords( (p3.x - p2.x), (p3.y - p2.y) );
204+
205+
var s = (-s1.y * (p0.x - p2.x) + s1.x * (p0.y - p2.y)) / (-s2.x * s1.y + s1.x * s2.y);
206+
var t = ( s2.x * (p0.y - p2.y) - s2.y * (p0.x - p2.x)) / (-s2.x * s1.y + s1.x * s2.y);
207+
208+
if( (s >= 0) && (s <= 1) && (t >= 0) && (t <= 1) ) {
209+
pi = new CanvasPoint.from_coords( (p0.x + (t * s1.x)), (p0.y + (t * s1.y)) );
210+
return( true );
211+
}
212+
213+
pi = null;
214+
return( false ); // No collision
215+
216+
}
217+
218+
//-------------------------------------------------------------
219+
// Draw the bubble starting at the top.
220+
private bool draw_bubble_top( Context ctx, CanvasPoint? pa, CanvasPoint? pb ) {
221+
222+
var pa_n = (pa != null) && (pa.y == bbox.y1());
223+
var pb_n = (pb != null) && (pb.y == bbox.y1());
224+
225+
if( !pa_n && !pb_n ) return( false );
226+
227+
var deg = Math.PI / 180.0;
228+
var pa_e = (pa.x == bbox.x2());
229+
var pa_w = (pa.x == bbox.x1());
230+
var pb_e = (pb.x == bbox.x2());
231+
var pb_w = (pb.x == bbox.x1());
232+
233+
CanvasPoint p1, p2;
234+
bool ul, ur;
235+
236+
if( pa_w || pb_e || (pa.x < pb.x) ) {
237+
p1 = pa; p2 = pb; ul = pa_w; ur = pb_e;
238+
} else {
239+
p1 = pb; p2 = pa; ul = pb_w; ur = pa_e;
240+
}
241+
242+
if( ul ) {
243+
ctx.move_to( p1.x, p1.y );
244+
ctx.line_to( points.index( 8 ).x, points.index( 8 ).y );
245+
ctx.line_to( p2.x, p2.y );
246+
} else {
247+
var r = ((p1.x - bbox.x1()) < _radius) ? (p1.x - bbox.x1()) : _radius;
248+
ctx.arc( (bbox.x1() + r), (bbox.y1() + r), r, (180 * deg), (270 * deg) );
249+
ctx.line_to( p1.x, p1.y );
250+
ctx.line_to( points.index( 8 ).x, points.index( 8 ).y );
251+
ctx.line_to( p2.x, p2.y );
252+
}
253+
if( !ur ) {
254+
var r = ((bbox.x2() - p2.x) < _radius) ? (bbox.x2() - p2.x) : _radius;
255+
ctx.arc( (bbox.x2() - r), (bbox.y1() + r), r, (-90 * deg), (0 * deg) );
256+
}
257+
258+
ctx.arc( (bbox.x2() - _radius), (bbox.y2() - _radius), _radius, (0 * deg), (90 * deg) );
259+
ctx.arc( (bbox.x1() + _radius), (bbox.y2() - _radius), _radius, (90 * deg), (180 * deg) );
260+
261+
return( true );
262+
263+
}
264+
265+
//-------------------------------------------------------------
266+
// Draw the bubble starting at the bottom.
267+
private bool draw_bubble_bottom( Context ctx, CanvasPoint? pa, CanvasPoint? pb ) {
268+
269+
var pa_s = (pa != null) && (pa.y == bbox.y2());
270+
var pb_s = (pb != null) && (pb.y == bbox.y2());
271+
272+
if( !pa_s && !pb_s ) return( false );
273+
274+
var deg = Math.PI / 180.0;
275+
var pa_e = (pa.x == bbox.x2());
276+
var pa_w = (pa.x == bbox.x1());
277+
var pb_e = (pb.x == bbox.x2());
278+
var pb_w = (pb.x == bbox.x1());
279+
280+
CanvasPoint p1, p2;
281+
bool ll, lr;
282+
283+
if( pa_e || pb_w || (pa.x > pb.x) ) {
284+
p1 = pa; p2 = pb; ll = pb_w; lr = pa_e;
285+
} else {
286+
p1 = pb; p2 = pa; ll = pa_w; lr = pb_e;
287+
}
288+
289+
if( lr ) {
290+
ctx.move_to( p1.x, p1.y );
291+
ctx.line_to( points.index( 8 ).x, points.index( 8 ).y );
292+
ctx.line_to( p2.x, p2.y );
293+
} else {
294+
var r = ((bbox.x2() - p1.x) < _radius) ? (bbox.x2() - p1.x) : _radius;
295+
ctx.arc( (bbox.x2() - r), (bbox.y2() - r), r, (0 * deg), (90 * deg) );
296+
ctx.line_to( p1.x, p1.y );
297+
ctx.line_to( points.index( 8 ).x, points.index( 8 ).y );
298+
ctx.line_to( p2.x, p2.y );
299+
}
300+
if( !ll ) {
301+
var r = ((p2.x - bbox.x1()) < _radius) ? (p2.x - bbox.x1()) : _radius;
302+
ctx.arc( (bbox.x1() + r), (bbox.y2() - r), r, (90 * deg), (180 * deg) );
303+
}
304+
305+
ctx.arc( (bbox.x1() + _radius), (bbox.y1() + _radius), _radius, (180 * deg), (270 * deg) );
306+
ctx.arc( (bbox.x2() - _radius), (bbox.y1() + _radius), _radius, (-90 * deg), (0 * deg) );
307+
308+
return( true );
309+
310+
}
311+
312+
313+
//-------------------------------------------------------------
314+
// Draw the bubble starting on the right
315+
private bool draw_bubble_right( Context ctx, CanvasPoint? pa, CanvasPoint? pb ) {
316+
317+
var pa_e = (pa != null) && (pa.x == bbox.x2());
318+
var pb_e = (pb != null) && (pb.x == bbox.x2());
319+
320+
if( !pa_e && !pb_e ) return( false );
321+
322+
var deg = Math.PI / 180.0;
323+
CanvasPoint p1, p2;
324+
325+
if( pa.y < pb.y ) {
326+
p1 = pa; p2 = pb;
327+
} else {
328+
p1 = pb; p2 = pa;
329+
}
330+
331+
var r1 = ((p1.y - bbox.y1()) < _radius) ? (p1.y - bbox.y1()) : _radius;
332+
var r2 = ((bbox.y2() - p2.y) < _radius) ? (bbox.y2() - p2.y) : _radius;
333+
ctx.arc( (bbox.x2() - r1), (bbox.y1() + r1), r1, (-90 * deg), (0 * deg) );
334+
ctx.line_to( p1.x, p1.y );
335+
ctx.line_to( points.index( 8 ).x, points.index( 8 ).y );
336+
ctx.line_to( p2.x, p2.y );
337+
ctx.arc( (bbox.x2() - r2), (bbox.y2() - r2), r2, (0 * deg), (90 * deg) );
338+
ctx.arc( (bbox.x1() + _radius), (bbox.y2() - _radius), _radius, (90 * deg), (180 * deg) );
339+
ctx.arc( (bbox.x1() + _radius), (bbox.y1() + _radius), _radius, (180 * deg), (270 * deg) );
340+
341+
return( true );
342+
343+
}
344+
345+
//-------------------------------------------------------------
346+
// Draw the bubble starting on the left
347+
private bool draw_bubble_left( Context ctx, CanvasPoint? pa, CanvasPoint? pb ) {
348+
349+
var pa_w = (pa != null) && (pa.x == bbox.x1());
350+
var pb_w = (pb != null) && (pb.x == bbox.x1());
351+
352+
if( !pa_w && !pb_w ) return( false );
353+
354+
var deg = Math.PI / 180.0;
355+
CanvasPoint p1, p2;
356+
357+
if( pa.y > pb.y ) {
358+
p1 = pa; p2 = pb;
359+
} else {
360+
p1 = pb; p2 = pa;
361+
}
362+
363+
var r1 = ((bbox.y2() - p1.y) < _radius) ? (bbox.y2() - p1.y) : _radius;
364+
var r2 = (p2.y - (bbox.y1()) < _radius) ? (p2.y - bbox.y1()) : _radius;
365+
ctx.arc( (bbox.x1() + r1), (bbox.y2() - r1), r1, (90 * deg), (180 * deg) );
366+
ctx.line_to( p1.x, p1.y );
367+
ctx.line_to( points.index( 8 ).x, points.index( 8 ).y );
368+
ctx.line_to( p2.x, p2.y );
369+
ctx.arc( (bbox.x1() + r2), (bbox.y1() + r2), r2, (180 * deg), (270 * deg) );
370+
ctx.arc( (bbox.x2() - _radius), (bbox.y1() + _radius), _radius, (-90 * deg), (0 * deg) );
371+
ctx.arc( (bbox.x2() - _radius), (bbox.y2() - _radius), _radius, (0 * deg), (90 * deg) );
372+
373+
return( true );
374+
375+
}
376+
377+
//-------------------------------------------------------------
378+
// Draw a talking bubble.
182379
private void draw_bubble( Context ctx ) {
183380

381+
var center = new CanvasPoint.from_coords( bbox.mid_x(), bbox.mid_y() );
184382
var deg = Math.PI / 180.0;
185-
var radius = _radius;
383+
var rad = (bbox.width < bbox.height) ? (bbox.width / 5) : (bbox.height / 5);
384+
var pt1 = new CanvasPoint();
385+
var pt2 = new CanvasPoint();
386+
var pa = new CanvasPoint(); // Intersecting point
387+
var pb = new CanvasPoint(); // Intersecting point
388+
389+
var rul = new CanvasPoint.from_coords( bbox.x1(), bbox.y1() );
390+
var rur = new CanvasPoint.from_coords( bbox.x2(), bbox.y1() );
391+
var rll = new CanvasPoint.from_coords( bbox.x1(), bbox.y2() );
392+
var rlr = new CanvasPoint.from_coords( bbox.x2(), bbox.y2() );
393+
394+
if( !find_tangents( center, rad, points.index( 8 ), pt1, pt2 ) ) return;
395+
396+
if( get_line_intersection( pt1, points.index( 8 ), rul, rur, out pa ) ||
397+
get_line_intersection( pt1, points.index( 8 ), rur, rlr, out pa ) ||
398+
get_line_intersection( pt1, points.index( 8 ), rlr, rll, out pa ) ||
399+
get_line_intersection( pt1, points.index( 8 ), rll, rul, out pa ) ) {
400+
if( get_line_intersection( pt2, points.index( 8 ), rul, rur, out pb ) ||
401+
get_line_intersection( pt2, points.index( 8 ), rur, rlr, out pb ) ||
402+
get_line_intersection( pt2, points.index( 8 ), rlr, rll, out pb ) ||
403+
get_line_intersection( pt2, points.index( 8 ), rll, rul, out pb ) ) {
404+
ctx.new_sub_path();
405+
if( draw_bubble_top( ctx, pa, pb ) || draw_bubble_bottom( ctx, pa, pb ) ||
406+
draw_bubble_left( ctx, pa, pb ) || draw_bubble_right( ctx, pa, pb ) ) {
407+
ctx.close_path();
408+
return;
409+
}
410+
}
411+
}
186412

187413
ctx.new_sub_path();
188-
ctx.arc( (bbox.x + bbox.width - radius), (bbox.y + radius), radius, (-90 * deg), (0 * deg) );
189-
ctx.arc( (bbox.x + bbox.width - radius), (bbox.y + bbox.height - radius), radius, (0 * deg), (90 * deg) );
190-
ctx.line_to( points.index( 10 ).x, points.index( 10 ).y );
191-
ctx.line_to( points.index( 8 ).x, points.index( 8 ).y );
192-
ctx.line_to( points.index( 9 ).x, points.index( 9 ).y );
193-
ctx.arc( (bbox.x + radius), (bbox.y + bbox.height - radius), radius, (90 * deg), (180 * deg) );
194-
ctx.arc( (bbox.x + radius), (bbox.y + radius), radius, (180 * deg), (270 * deg) );
414+
ctx.arc( (bbox.x1() + _radius), (bbox.y1() + _radius), _radius, (180 * deg), (270 * deg) );
415+
ctx.arc( (bbox.x2() - _radius), (bbox.y1() + _radius), _radius, (-90 * deg), (0 * deg) );
416+
ctx.arc( (bbox.x2() - _radius), (bbox.y2() - _radius), _radius, (0 * deg), (90 * deg) );
417+
ctx.arc( (bbox.x1() + _radius), (bbox.y2() - _radius), _radius, (90 * deg), (180 * deg) );
195418
ctx.close_path();
196419

197420
}

0 commit comments

Comments
 (0)