Day 19 – Interactive Desktop Apps

I am a big fan of roleplaying games like Dungeons and Dragons. Most of these games have screens to help you hide what you’re doing when running the game and give you some of the charts used in the game to reduce looking stuff up in the books.

My game collection is extensive though and I’d much rather use my laptop to not only hide behind and track information but I could also automate dice rolls and chart usage. Whilst I could cobble some stuff together with text editors and command line magic I’d much rather have some snazzy Desktops Apps that I can show off to people.

Enter GTK::Simple, a wrapper around the gtk3 UI library used by the Linux Gnome desktop but also available on Windows and Mac. This library gives you a simple to use interface, via the power of NativeCall, to let you create simple desktop applications.

Dice Rolling

For historic reasons roleplaying games tend to use a selection of dice mostly based on platonic solids. The standard set of dice are 4, 6, 8, 10, 12, 20 and by combining the roll of 2 10 sided dice 100. Dice can be rolled in multiples and the standard notation for writing this is “xdy” where “x” is the number of dice to roll and “y” this size of the dice to roll. A single dice roll skips the 1 at the start for example “roll a d6” means roll a six sided dice.

Interestingly it’s simple enough to make a “d” operator in Perl6 :

sub infix:<d> ( UInt $count, UInt $size ) { (1..$size).roll($count) }
sub prefix:<d> ( UInt $size ) { 1 d $size }

Note that you need to space out your numbers and the “d” operator or the compiler gets confused.

What’d I’d like though is a dice roller app which gives a selection of options to roll the standard dice sets. For now I’ll not look at rolling different dice together, which some games use, or modifying rolls, which a lot of games use. I would like to see each dice rolled as this can be important depending on the system. I’d also like the total if possible.

Simple GTK::Simple

The basic usage of GTK::Simple is easy enough. Create an app, add content, put in some event handlers and off you go.

To start we have creating our app this is done as follows :

#!/usr/bin/env perl6

# Get the GTK::Simple libraries
use GTK::Simple;
use GTK::Simple::App;

# Create the main app
my $app = GTK::Simple::App.new( title => "Dice Roller" );

# Start the app running
$app.run;

But…. that’s not very interesting :

empty

Grid Layout

To layout widgets within the app we have various options but one that advised to be used is the grid. A grid lays out widgets starting at 0,0 in the top left and extending out as required.

It’s at this point in my attempts to build an app I hit a bit of a wall. The grid option is good and I used it in the final example below but when I was trying things out it wasn’t working as I expected. Still I was able to get a simple grid so show it working but there seems to be some more learning required. Anyway here’s a basic grid :

#!/usr/bin/env perl6
# Get the GTK::Simple libraries
use GTK::Simple;
use GTK::Simple::App;

# Create the main app
my $app = GTK::Simple::App.new( title => "Grid" );

$app.set-content(
    GTK::Simple::Grid.new(
        # Grid key is [x,y,height,width]
        [0,0,1,1] => GTK::Simple::Button.new( label => "a" ),
        # A Button is a simple push button with a label
        [0,1,1,1] => GTK::Simple::Button.new( label => "b" ),
        [0,2,1,1] => GTK::Simple::Button.new( label => "c" ),
        [1,0,1,3] => GTK::Simple::Button.new( label => "d" ),
    )
);

$app.border-width = 10;
# Start the app
$app.run;

Which produces :

grid

Interactivity

This is neat but the buttons don’t do anything yet. For that we need event handlers. GUI applications need to be event-driven reacting to user actions luckily Perl6 comes with functionality to handle events in the form of Supplies. Each button has a supply called clicked which was can attach a tap handler to.

The event handler can do all sorts of things including manipulating other UI objects. For example :

#!/usr/bin/env perl6

# Get the GTK::Simple libraries
use GTK::Simple;
use GTK::Simple::App;

# Create the main app
my $app = GTK::Simple::App.new( title => "Grid" );

$app.set-content(
    GTK::Simple::Grid.new(
        # As we want to refer to our buttons later we assign them
        # to variables
        [0,0,1,1] => my $b1 = GTK::Simple::Button.new( label => "Push Me" ),
        [1,1,1,1] => my $b2 = GTK::Simple::Button.new( label => "---" ),
        [2,2,1,1] => my $b3 = GTK::Simple::Button.new( label => "---" ),
        [3,3,1,1] => my $b4 = GTK::Simple::Button.new( label => "---" ),
    )
);

# The sensitive flag controls whether you can click on the button
$b2.sensitive = False;
$b3.sensitive = False;
$b4.sensitive = False;

# In the 
$b1.clicked.tap( { 
    # $_ is the clicked button. Turn it off
    .sensitive = False; 
    # Change the label on the next button
    $b2.label = "Now Me!"; 
    # Make it clickable
    $b2.sensitive = True 
} );

# Leaving on one line to cut down on space
$b2.clicked.tap( { .sensitive = False; $b3.label = "Me Next"; $b3.sensitive = True } );
$b3.clicked.tap( { .sensitive = False; $b4.label = "Me! Me!"; $b4.sensitive = True } );
# App.exit closes the app.
$b4.clicked.tap( { $app.exit } );

$app.border-width = 10;
# Start the app
$app.run;

Which makes this :

buttons

Putting it all together

With that and another widget, the Label which gives us some text, we can put together the dice rolling app :

#!/usr/bin/env perl6

# Get the GTK::Simple libraries
use GTK::Simple;
use GTK::Simple::App;

# Define our `d` operator
sub infix:<d> ( UInt $count, UInt $size ) { (1..$size).roll($count) }

# Create the main app
my $app = GTK::Simple::App.new( title => "Dice Roller" );

# Output Box : Define here so the buttons can access it.
my $output = GTK::Simple::Label.new( text => 'Roll : ');

# Ouput box updater.
sub roll( $label, $count, $size ) {
    my @roll = $count d $size;
    $label.text = "Roll : {@roll.join(" + ")} = {@roll.sum}"; 
}

# Create a grid and put the output box at the bottom filling the width
my @grid = ( [0,6,7,1] => $output );

# Track our depth in the grid
my $y = 0;

# Loop through counts
for (1..6) -> $count {

    # Track our postion along the grid
    my $x = 0;

    # Loop through standard dice sizes
    for (4,6,8,10,12,20,100) -> $size {

	# Standard labelling 
	my $label = $count > 1 ?? "{$count}d{$size}" !! "d{$size}";

	# Create our button
	my $button = GTK::Simple::Button.new(label => $label);

	# Buttons get a supply which emit when they are clicked
	# Assign our roll function with the current count and size to it
	# Note we do it in a block so it's not called right now but when
	# the button is clicked
	$button.clicked.tap(
	    { roll( $output, $count, $size ) }
	);

	# Put the button in the valid place in the grid taking up one space
	@grid.push( [$x,$y,1,1] => $button );

	$x++;
    }
    $y++
}

# Create a grid object and assign it to the app.
$app.set-content(
    GTK::Simple::Grid.new( |@grid )
);

$app.border-width = 10;

# Start the app running
$app.run;

Which looks like (here having rolled 3d6) :

dice-roller

Final thoughts

Considering I’d not touched GTK::Simple before this morning I’m quite happy with my final result. There are lots of other game tools I think I could build. Plus I’m probably going to get involved in working on the module itself to try to bring more of the GTK functionality into it as well add some documentation.

Still it’s very easy to make a desktop application to scratch a particular itch in Perl6 with GTK::Simple and not a huge amount of code.

One thought on “Day 19 – Interactive Desktop Apps

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.