#!/usr/bin/perl
# This simple demo demonstrates how cairo may be used to draw
# old-fashioned widgets with bevels that depend on lines exactly
# 1-pixel wide.
#
# This demo is really only intended to demonstrate how someone might
# emulate antique graphics, and this style is really not recommended
# for future code. Some notes:
#
# 1) We're not going for pixel-perfect emulation of crusty graphics
# here. Notice that the checkmark is rendered nicely by cairo
# without jaggies.
#
# 2) The use of opaque highlight/lowlight colors here is particularly
# passe. A much more interesting approach would blend translucent
# colors over an arbitrary background.
#
# 3) This widget style is optimized for device-pixels. As such, it
# won't scale up very well, (except for integer scale
# factors). I'd be more interested to see future widget schemes
# that look good at all scales.
#
# One way to get better-looking graphics at all scales might be to
# introduce some device-pixel snapping into cairo for
# horizontal/vertical path components. Then, a lot of the 0.5
# adjustments could disappear from code like this, and then this code
# could become more scalable.
use strict;
use warnings;
use Cairo;
use constant
{
WIDTH => 100,
HEIGHT => 70,
M_PI => 4 * atan2(1, 1),
};
my $BG_COLOR = [ 0xd4, 0xd0, 0xc8 ];
my $HI_COLOR_1 = [ 0xff, 0xff, 0xff ];
my $HI_COLOR_2 = [ 0xd4, 0xd0, 0xc8 ];
my $LO_COLOR_1 = [ 0x80, 0x80, 0x80 ];
my $LO_COLOR_2 = [ 0x40, 0x40, 0x40 ];
my $BLACK = [ 0, 0, 0 ];
sub set_hex_color
{
my ($cr, $color) = @_;
$cr->set_source_rgb (
$color->[0] / 255.0,
$color->[1] / 255.0,
$color->[2] / 255.0);
}
sub bevel_box
{
my ($cr, $x, $y, $width, $height) = @_;
$cr->save;
$cr->set_line_width (1.0);
$cr->set_line_cap ('square');
# Fill and highlight
set_hex_color ($cr, $HI_COLOR_1);
$cr->rectangle ($x, $y, $width, $height);
$cr->fill;
# 2nd hightlight
set_hex_color ($cr, $HI_COLOR_2);
$cr->move_to ($x + 1.5, $y + $height - 1.5);
$cr->rel_line_to ($width - 3, 0);
$cr->rel_line_to (0, - ($height - 3));
$cr->stroke;
# 1st lowlight
set_hex_color ($cr, $LO_COLOR_1);
$cr->move_to ($x + 0.5, $y + $height - 1.5);
$cr->rel_line_to (0, - ($height - 2));
$cr->rel_line_to ($width - 2, 0);
$cr->stroke;
# 2nd lowlight
set_hex_color ($cr, $LO_COLOR_2);
$cr->move_to ($x + 1.5, $y + $height - 2.5);
$cr->rel_line_to (0, - ($height - 4));
$cr->rel_line_to ($width - 4, 0);
$cr->stroke;
$cr->restore;
}
sub bevel_circle
{
my ($cr, $x, $y, $width) = @_;
my $radius = ($width - 1)/2.0 - 0.5;
$cr->save;
$cr->set_line_width (1);
# Fill and highlight
set_hex_color ($cr, $HI_COLOR_1);
$cr->arc ($x+$radius+1.5, $y+$radius+1.5, $radius, 0, 2*M_PI);
$cr->fill;
# 2nd highlight
set_hex_color ($cr, $HI_COLOR_2);
$cr->arc ($x+$radius+0.5, $y+$radius+0.5, $radius, 0, 2*M_PI);
$cr->stroke;
# 1st lowlight
set_hex_color ($cr, $LO_COLOR_1);
$cr->arc ($x+$radius+0.5, $y+$radius+0.5, $radius, 3*M_PI/4, 7*M_PI/4);
$cr->stroke;
# 2nd lowlight
set_hex_color ($cr, $LO_COLOR_2);
$cr->arc ($x+$radius+1.5, $y+$radius+1.5, $radius, 3*M_PI/4, 7*M_PI/4);
$cr->stroke;
$cr->restore;
}
# Slightly smaller than specified to match interior size of bevel_box
sub flat_box
{
my ($cr, $x, $y, $width, $height) = @_;
$cr->save;
# Fill background
set_hex_color ($cr, $HI_COLOR_1);
$cr->rectangle ($x+1, $y+1, $width-2, $height-2);
$cr->fill;
# Stroke outline
$cr->set_line_width (1.0);
set_hex_color ($cr, $BLACK);
$cr->rectangle ($x+1.5, $y+1.5, $width-3, $height-3);
$cr->stroke;
$cr->restore;
}
sub flat_circle
{
my ($cr, $x, $y, $width) = @_;
my $radius = ($width - 1) / 2.0;
$cr->save;
# Fill background
set_hex_color ($cr, $HI_COLOR_1);
$cr->arc ($x+$radius+0.5, $y+$radius+0.5, $radius-1, 0, 2*M_PI);
$cr->fill;
# Stroke outline
$cr->set_line_width (1.0);
set_hex_color ($cr, $BLACK);
$cr->arc ($x+$radius+0.5, $y+$radius+0.5, $radius-1, 0, 2*M_PI);
$cr->stroke;
$cr->restore;
}
sub groovy_box
{
my ($cr, $x, $y, $width, $height) = @_;
$cr->save;
# Highlight
set_hex_color ($cr, $HI_COLOR_1);
$cr->set_line_width (2);
$cr->rectangle ($x+1, $y+1, $width-2, $height-2);
$cr->stroke;
# Lowlight
set_hex_color ($cr, $LO_COLOR_1);
$cr->set_line_width (1);
$cr->rectangle ($x+0.5, $y+0.5, $width-2, $height-2);
$cr->stroke;
$cr->restore;
}
use constant
{
CHECK_BOX_SIZE => 13,
};
sub check_box
{
my ($cr, $x, $y, $checked) = @_;
$cr->save;
bevel_box ($cr, $x, $y, CHECK_BOX_SIZE, CHECK_BOX_SIZE);
if ($checked) {
set_hex_color ($cr, $BLACK);
$cr->move_to ($x+3, $y+5);
$cr->rel_line_to (2.5, 2);
$cr->rel_line_to (4.5, -4);
$cr->rel_line_to (0, 3);
$cr->rel_line_to (-4.5, 4);
$cr->rel_line_to (-2.5, -2);
$cr->close_path;
$cr->fill;
}
$cr->restore;
}
use constant
{
RADIO_SIZE => CHECK_BOX_SIZE,
};
sub radio_button
{
my ($cr, $x, $y, $checked) = @_;
$cr->save;
bevel_circle ($cr, $x, $y, RADIO_SIZE);
if ($checked) {
set_hex_color ($cr, $BLACK);
$cr->arc (
$x + (RADIO_SIZE-1) / 2.0 + 0.5,
$y + (RADIO_SIZE-1) / 2.0 + 0.5,
(RADIO_SIZE-1) / 2.0 - 3.5,
0, 2 * M_PI);
$cr->fill;
}
$cr->restore;
}
sub draw_bevels
{
my ($cr, $width, $height) = @_;
my $check_room = ($width - 20) / 3;
my $check_pad = ($check_room - CHECK_BOX_SIZE) / 2;
groovy_box ($cr, 5, 5, $width - 10, $height - 10);
check_box ($cr, 10+$check_pad, 10+$check_pad, 0);
check_box ($cr, $check_room+10+$check_pad, 10+$check_pad, 1);
flat_box ($cr, 2 * $check_room+10+$check_pad, 10+$check_pad,
CHECK_BOX_SIZE, CHECK_BOX_SIZE);
radio_button ($cr, 10+$check_pad, $check_room+10+$check_pad, 0);
radio_button ($cr, $check_room+10+$check_pad, $check_room+10+$check_pad, 1);
flat_circle ($cr, 2 * $check_room+10+$check_pad, $check_room+10+$check_pad, CHECK_BOX_SIZE);
}
{
my $surface = Cairo::ImageSurface->create ('argb32', WIDTH, HEIGHT);
my $cr = Cairo::Context->create ($surface);
$cr->rectangle (0, 0, WIDTH, HEIGHT);
set_hex_color ($cr, $BG_COLOR);
$cr->fill;
draw_bevels ($cr, WIDTH, HEIGHT);
$surface->write_to_png ('bevels.png');
}