Re: rounded rectangles problem



muppet wrote:


On Aug 11, 2005, at 7:50 PM, James Muir wrote:

Here is the latest code. I managed to get the rectangle to display since my last posting. I had forgotten to set the 'outline-color' and so nothing was displayed. I am still not able to set the 'fill- color'.


see below.

here is the driver code i am using:

use RoundedRect1;
use Gnome2::Canvas;
use Gtk2 -init;

my $window = Gtk2::Window->new;
$window->set_default_size (400, 400);
$window->signal_connect (destroy => sub {Gtk2->main_quit});

my $canvas = Gnome2::Canvas->new;
$window->add ($canvas);

Gnome2::Canvas::Item->new ($canvas->root,
RoundedRect1::,
fill_color => 'red',
outline_color => 'blue',
x1 => 10, y1 => 10,
x2 => 100, y2 => 100,
width_pixels => 5,
);

$window->show_all;
Gtk2->main;


Note that i'm setting the colors and widths and things in the driver code, not in the RoundedRect1 implementation itself.


#!/usr/bin/perl -w
#
# RoundedRect1: add a rounded rectangle.
#
# This version uses inheritance to create the rect.
#
# TODO: Check size of corner radius.
# Fill rect with color.
# Capture events inside rect.
# ---------------------------------------------------------------------- ---
package RoundedRect1;

use strict;
use Gnome2::Canvas;

use Glib ':constants';
use Glib::Object::Subclass
Gnome2::Canvas::Shape::,
properties => [


'radius' should be a double from [0.0, DBL_MAX(clipped in drawing)], not an int, because the coordinate space is continuous and may be scaled. (see the comment below)

Glib::ParamSpec->int ('radius',
'corner_radius',
'Radius of corners',
10,
100,
10,
G_PARAM_READWRITE),
Glib::ParamSpec->int ('x1',
'x1',
'Upper left X coord',
0,
32767,


In general, these bounds are far too restrictive. The canvas's coordinate space is virtually unbounded, and can have any units you like, and may be arbitrarily scaled, so the coords should actually be unbounded doubles.

use POSIX qw(DBL_MAX);
...
Glib::ParamSpec->double ('x1', 'X1', 'Upper left X coordinate',
-DBL_MAX, DBL_MAX, 0.0,
G_PARAM_READWRITE);

0,
G_PARAM_READWRITE),
Glib::ParamSpec->int ('y1',
'y1',
'Upper left Y coord',
0,
32767,
0,
G_PARAM_READWRITE),
Glib::ParamSpec->int ('x2',
'x2',
'Lower right X coord',
0,
32767,
0,
G_PARAM_READWRITE),
Glib::ParamSpec->int ('y2',
'y2',
'Lower right Y coord',
0,
32767,
0,
G_PARAM_READWRITE),
]
;


Since this INIT_INSTANCE is a no-op, you can leave it out and save the rather expensive no-op.

sub INIT_INSTANCE
{
my $self = @_;

print "INIT_INSTANCE:\n";
}



This GET_PROPERTY is a perl implementation of exactly the behavior of the built-in fallback GET_PROPERTY added in 1.060, so you can leave it out and save a slight performance penalty.

sub GET_PROPERTY
{
my ($self, $pspec) = @_;

print "GET_PROPERTY: " . $pspec->get_name . " value: " .
$self->{$pspec->get_name} . "\n";

return ($self->{$pspec->get_name} || $pspec->get_default_value);
}



On the other hand, since you're doing non-default stuff in SET_PROPERTY, you *do* need this override.

sub SET_PROPERTY
{
my ($self, $pspec, $newval) = @_;

print "SET_PROPERTY: " . $pspec->get_name . " newval: $newval\n";

$self->{$pspec->get_name} = $newval;


This check is clever -- "don't rebuild the pathdef if we don't have all the info yet" -- but is a little too strict. You've set a default value for radius, but this check makes it impossible for that to be used (unless you duplicate the default value in your INIT_INSTANCE, which i don't recommend).

If you just drop the (defined $self->{'radius'}) clause, we can do another fix below that makes it work much better.

if ((defined $self->{'radius'}) &&
(defined $self->{x1}) && (defined $self->{y1}) &&
(defined $self->{x2}) && (defined $self->{y2}))
{
$self->set_path_def(_roundedRect($self->{'radius'},
$self->{x1}, $self->{y1},
$self->{x2}, $self->{y2}));


Here, we're digging straight into the object's copies of the data. Nice and efficient, but sidesteps all of the default-handling that would be nice to use. If, instead, you use Glib::Object::get() (yes, even inside your own object implementation), then the default value machinery can take effect, and you don't have to set 'radius' explicitly before anything happens.

Note that ->get() will return a list of values if given a list of keys...

$self->set_path_def (_roundedRect ($self->get (qw(radius x1 y1 x2 y2))));

(Looking into this a little more, it turns out that there's a vfunc (that is, a virtual method with no associated signal) called "update" that is used by the C implementations to do the actual drawing of a canvas item. The bindings currently do not offer the ability to implement that vfunc (for a variety of reasons, including "nobody ever pointed out that it was missing"). At some point i will play with that and find out if it makes this object easier to implement.)


Now strike everything from here to the end of the block. Choosing the colors should be left to the user of this object.

$self->set('outline-color'=>'black'); # default.

# $self->set('fill-color'=>'white');

my $color = Gtk2::Gdk::Color->new(255,255,255);

$self->set('fill-color-gdk'=>$color);
}
}



And the answer you've been dying for is just around the bend...

# _roundedRect: return the rounded rectangle path.
# --------------------------------------------------------
sub _roundedRect
{
my ($r, $x1, $y1, $x2, $y2) = @_;

# Make sure (x1,y1) is upper left.

if (($x1 < $x2) && ($y1 > $y2))
{
my $t1 = $y1; $y1 = $y2; $y2 = $t1;
}

if (($y1 < $y2) && ($x1 > $x2))
{
my $t1 = $x1; $x1 = $x2; $x2 = $t1;
}

print "x1: $x1 y1: $y1 x2: $x2 y2: $y2\n";

# Get the points for the path.

my @p = ();

push @p, _bezier('UPPER_LEFT', $r, $x1, $y1);
push @p, _bezier('UPPER_RIGHT', $r, $x2, $y1);
push @p, _bezier('LOWER_RIGHT', $r, $x2, $y2);
push @p, _bezier('LOWER_LEFT', $r, $x1, $y2);

# Build the rounded rectangle path. Problem?

my $pathdef = Gnome2::Canvas::PathDef->new();

$pathdef->moveto ($p[0], $p[1]);
$pathdef->curveto ($p[2], $p[3], $p[4], $p[5], $p[6], $p[7]);
$pathdef->lineto ($p[8], $p[9]);
$pathdef->curveto ($p[10], $p[11], $p[12], $p[13], $p[14], $p[15]);
$pathdef->lineto ($p[16], $p[17]);
$pathdef->curveto ($p[18], $p[19], $p[20], $p[21], $p[22], $p[23]);
$pathdef->lineto ($p[24], $p[25]);
$pathdef->curveto ($p[26], $p[27], $p[28], $p[29], $p[30], $p[31]);
$pathdef->lineto ($p[0], $p[1]);


You can't fill an open path. Close the path to allow fill-color to work.

$pathdef->closepath_current;


return $pathdef;
}


# _bezier: return corner bezier points.
# --------------------------------------------------------
sub _bezier
{
my $corner = shift(@_);
my $r = shift(@_);
my $x = shift(@_);
my $y = shift(@_);

if ($corner eq 'UPPER_LEFT')
{
return ($x,$y+$r, $x,$y+($r/2), $x+($r/2),$y, $x+$r, $y);
}

if ($corner eq 'UPPER_RIGHT')
{
return ($x-$r,$y, $x-($r/2),$y, $x,$y+($r/2), $x, $y+$r);
}

if ($corner eq 'LOWER_RIGHT')
{
return ($x,$y-$r, $x,$y-($r/2), $x-($r/2),$y, $x-$r, $y);
}

if ($corner eq 'LOWER_LEFT')
{
return ($x+$r,$y, $x+($r/2),$y, $x,$y-($r/2), $x, $y-$r);
}

return ();
}


1;


Wow! Thanks for the very instructive critique of the code. :-)
I shall revise my copy accordingly.
-James




[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]