FreeTUIT

Codeless GUI Programming

Eric Wilhelm
http://scratchcomputing.com/
1 / 90

What is FreeTUIT?
2 / 90

Codeless GUI Programming
3 / 90

Declarative Widget Layout
4 / 90

Separating

layout (buttons, toolbars, menus)

from

(events, interaction) logic
5 / 90

GUI

Graphical User Interface

Specifically: "Desktop GUI"

Specifically: wxWidgets (for now)
6 / 90

But, who am I?
7 / 90

And, why am I here?
8 / 90

<shameless>
"somebody you can write checks to
and they fix bugs"
</shameless>
9 / 90

And, I write code

http://vectorsection.org
10 / 90

Lots of Perl

http://search.cpan.org/~ewilhelm/

ApacheLog-Parser
CAD-Calc
CAD-Drawing
CAD-Format-STL
Class-Accessor-Classy
Date-Piece
Devel-NoGlobalSig
Devel-TraceDeps
11 / 90

And...


Device-SerialPins
File-Fu
Getopt-Base
Getopt-AsDocumented
Graph-ChainBuilder
HTTP-Server-Simple-Er
lambda
Linux-USBKeyboard
List-oo
Math-Geometry-Planar-Offset
Math-Round-Var
Math-Vec
Module-Finder
Shebangml
Time-Mock
Text-Slidez
12 / 90

And...

dotReader
bin-wxcat
wxPerl-Constructors
wxPerl-Styles
13 / 90

Plenty of code to write.
14 / 90

Always wanting
something more
concise.
15 / 90

GUI programming

Tends toward the verbose and overly-explicit specification of all things big and small (such as the buttons and scrollbars, the actions and events (like the mouse-clicks and keystrokes, the focus and unfocus, the clicking and unclicking.)) The API provides a great degree of flexibility, very little guidance, and tends toward condescendingly reminding you that you're leveraging an enormous volume of advanced algorithms for memory management and widget rendering painstakingly crafted by untold masses of developers.

As if you weren't
just drawing pixels on the screen.
16 / 90

So...
17 / 90

You end up with
lots of code.
18 / 90

Very wide and unwieldy lines of code.
19 / 90

Piles of knobs.
20 / 90

You
hate it.
21 / 90

It
hates you back.
22 / 90

Some Constants
To Consider

  • SLOC/developer/day
  • bugs/KSLOC
(aka "no silver bullet" — Brooks)
23 / 90

Thus:

Codeless GUI programming.
24 / 90

Actually just:

less-code GUI programming.
25 / 90

But first...
Web 1.999 progress report.
(Where's my flying car?)
26 / 90

Types of Software

  • Command-Line
  • Web Applications
  • Desktop Applications
27 / 90

Endpoints

  • Humans
  • Information
  • Robots
28 / 90

Web Applications

Humans <-> Humans
Information -> Humans
29 / 90

CLI Applications

Robots <-> Information
or
Information <-> Information
30 / 90

GUI Applications

Humans <-> Information
or
Humans <-> Robots
or
Humans <-> Humans
31 / 90

Dealing with

  • Humans
  • Information
  • Robots
  • Complexity
32 / 90

Taming Complexity
33 / 90

(don't try to tame robots)
34 / 90

(or humans)
35 / 90

Small Pieces
Loosely Joined

vs

Discoverability
Interactive Error Checking
Rich Presentation
36 / 90

A       B       C
----    ----    ----
foo     bar     baz
foot    bart    bazt
vs
37 / 90

Formatting Tables
in ASCII
(calculate widths, print)
vs
A Table Widget
(rows, columns, interactive)
38 / 90

Richer Widgets
39 / 90

More Affordances
40 / 90

Better Affordances
41 / 90

But there's a catch...
42 / 90

Rich is expensive
43 / 90

Complicated
44 / 90

e.g.
Hello World
45 / 90

CLI application

echo world | \
  perl -e 'print shift, " ", <>' \
          hello
46 / 90

highly flexible
perl -e '
  print shift(@ARGV), " ",
    readline(STDIN)'
  
47 / 90

hello
48 / 90

echo world |
49 / 90

Web Application

<!DOCTYPE is a lie anyway ...>
<html>
  <head>
    <title>webapp</title>
  </head>
  <body><p>hello world</p></body>
</html>
(not so flexible)
50 / 90

GUI application


51 / 90

#!/usr/bin/perl

package MyFrame; use warnings; use strict; use Wx qw(:everything); use base qw(Wx::Frame);

sub new {
  my( $self, $parent, $id, $title, $pos, $size, $style, $name ) = @_;
  $parent = undef                unless defined $parent;
  $id     = -1                   unless defined $id;
  $title  = "Hello World"        unless defined $title;
  $pos    = wxDefaultPosition    unless defined $pos;
  $size   = wxDefaultSize        unless defined $size;
  $name   = ""                   unless defined $name;
  $style = wxDEFAULT_FRAME_STYLE unless defined $style;

  $self = $self->SUPER::new( $parent, $id, $title, $pos, $size, $style, $name );

  my $text = Wx::TextCtrl->new(
    $self, -1, "hello world", wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE
  );

  my $sizer = Wx::BoxSizer->new(Wx::wxVERTICAL);
  $sizer->Add($text, 1, wxEXPAND, 0); $self->SetSizer($sizer); $self->Layout;

  return($self);
}

package MyApp; use base 'Wx::App';
sub OnInit { my $self = shift; my $frame = MyFrame->new; $frame->Show(1); }

package main; my $app = MyApp->new; $app->MainLoop;

# vim:ts=2:sw=2:et:sta
52 / 90

So that's a bit tedious
53 / 90

I tried to tighten it.

#!/usr/bin/perl

package MyFrame; use warnings; use strict; use Wx qw(:everything);
use wxPerl::Constructors; use base qw(wxPerl::Frame);
use wxPerl::Styles qw(style wxVal);

sub new {
  my $class = shift;
  my ($parent, $title, %opts) = @_;
  $title ||= 'Hello World';

  my $self = $class->SUPER::new($parent, $title, %opts);

  my $text = wxPerl::TextCtrl->new($self, "hello world",  style(te => 'multiline'));

  my $sizer = Wx::BoxSizer->new(wxVal('VERTICAL'));
  $sizer->Add($text, 1, wxVal('EXPAND')); $self->SetSizer($sizer); $self->Layout;

  return($self);
}

package MyApp; use base 'Wx::App';
sub OnInit { my $self = shift; my $frame = MyFrame->new; $frame->Show(1); }

package main; my $app = MyApp->new; $app->MainLoop;

# vim:ts=2:sw=2:et:sta
54 / 90

But this is still just
"hello world".
55 / 90

Doesn't actually do anything
56 / 90

Well, you can use the text widget
(but no load/save)
57 / 90

With FreeTUIT

#! /usr/bin/env freetuit

app{
  frame[title="Hello World" size=400x250]{
    row{
      textarea[s:stretch=1 s:expand=1]{
        hello world
      }
    }
  }#frame;
}#app;
58 / 90

Shorter and Cleaner


Less code. Same results.
59 / 90

Other ways

  • Glade (GTK)
  • designer (Qt)
  • wxGlade
  • xrced
60 / 90

wxGlade
61 / 90

xrced
62 / 90

Generate

  • UI
  • XRC
  • code
63 / 90

But generated code is...
64 / 90

generated
#!/usr/bin/perl -w -- 
# generated by wxGlade 0.4.1 on Sun Oct  5 17:16:22 2008
# To get wxPerl visit http://wxPerl.sourceforge.net/

use Wx 0.15 qw[:allclasses];
use strict;

package MyFrame;

use Wx qw[:everything];
use base qw(Wx::Frame);
use strict;

sub new {
	my( $self, $parent, $id, $title, $pos, $size, $style, $name ) = @_;
	$parent = undef              unless defined $parent;
	$id     = -1                 unless defined $id;
	$title  = "blah"             unless defined $title;
	$pos    = wxDefaultPosition  unless defined $pos;
	$size   = wxDefaultSize      unless defined $size;
	$name   = ""                 unless defined $name;

# begin wxGlade: MyFrame::new

	$style = wxDEFAULT_FRAME_STYLE 
		unless defined $style;

	$self = $self->SUPER::new( $parent, $id, $title, $pos, $size, $style, $name );
	$self->{object_2} = Wx::TextCtrl->new($self, -1, "", wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE);
	$self->{object_3} = Wx::TextCtrl->new($self, -1, "", wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE);
	$self->{object_5} = Wx::StaticText->new($self, -1, "blah", wxDefaultPosition, wxDefaultSize, );
	$self->{object_6} = Wx::ComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_DROPDOWN);
	$self->{object_8} = Wx::Button->new($self, -1, "Button1");
	$self->{object_9} = Wx::Button->new($self, -1, "Button2");

	$self->__set_properties();
	$self->__do_layout();

# end wxGlade
	return $self;

}


sub __set_properties {
	my $self = shift;

# begin wxGlade: MyFrame::__set_properties

	$self->{object_6}->SetSelection(-1);

# end wxGlade
}
# ... continued






sub __do_layout {
	my $self = shift;

# begin wxGlade: MyFrame::__do_layout

	$self->{object_1} = Wx::BoxSizer->new(wxVERTICAL);
	$self->{object_4} = Wx::FlexGridSizer->new(2, 2, 0, 0);
	$self->{object_7} = Wx::BoxSizer->new(wxHORIZONTAL);
	$self->{object_1}->Add($self->{object_2}, 1, wxEXPAND, 0);
	$self->{object_1}->Add($self->{object_3}, 1, wxEXPAND, 0);
	$self->{object_4}->Add($self->{object_5}, 2, wxEXPAND, 0);
	$self->{object_4}->Add($self->{object_6}, 0, wxEXPAND, 0);
	$self->{object_4}->Add(165, 0, 1, wxEXPAND, 0);
	$self->{object_7}->Add($self->{object_8}, 1, 0, 0);
	$self->{object_7}->Add($self->{object_9}, 1, 0, 0);
	$self->{object_4}->Add($self->{object_7}, 0, wxEXPAND, 0);
	$self->{object_4}->AddGrowableCol(0);
	$self->{object_1}->Add($self->{object_4}, 0, wxEXPAND, 0);

	# XXX it doesn't pack correctly because wxglade is incorrect
	#$self->SetAutoLayout(1);
	$self->SetSizer($self->{object_1});
	#$self->{object_1}->Fit($self);
	$self->{object_1}->SetSizeHints($self);
	$self->Layout();

# end wxGlade
}

package MyApp;
use base 'Wx::App';
sub OnInit {
  my $self = shift;
  my $frame = MyFrame->new;
    $frame->Show(1);
}

package main;

my $app = MyApp->new;
$app->MainLoop;

1;
65 / 90

UI/XRC XML is...
66 / 90

XML
67 / 90

And not pretty XML

<?xml version="1.0" encoding="utf-8"?>
<resource>
  <object class="wxFrame" name="FRAME1">
    <title></title>
    <object class="wxBoxSizer">
      <orient>wxVERTICAL</orient>
      <object class="sizeritem">
        <object class="wxTextCtrl">
          <style>wxTE_MULTILINE</style>
        </object>
        <option>1</option>
        <flag>wxEXPAND</flag>
      </object>
68 / 90

And still

<?xml version="1.0"?>
<resource>
  ...
      <object class="wxMenuItem" name="menu_quit">
        <label>E$xit\tAlt-X</label>
      </object>
you have to code it in
...
  $self->{xrc} = Wx::XmlResource->new();
  $self->xrc->InitAllHandlers();
  $self->xrc->Load('xrc/resource.xrc');
...
  EVT_MENU( $frame, Wx::XmlResource::GetXRCID('menu_quit'),
            sub { $frame->Close } );
69 / 90

XRC doesn't do

events
EVT_MENU(
  $frame,
  Wx::XmlResource::GetXRCID('menu_quit'),
  sub { $frame->Close }
);
70 / 90

Trouble

dealing with sizers
71 / 90

Every application has
that one widget
72 / 90

73 / 90

The rest is boilerplate.
74 / 90

FreeTUIT

  • normalized API
  • declarative layout
  • declarative events
  • efficient markup
  • flexible runtime
75 / 90

Normalized API

my $top = 'FreeTUIT::Wx';
my $app = $top->create_child(
  app => %options
);
my $frame = $app->create_child(
  frame => title => "frame"
);
76 / 90

Declarative Layout
app{
frame[title=whatever]{
  column{
    textarea[:text1 s:expand=1 s:stretch=1]{}
    grid[stretch_columns="0:1" s:expand=1]{
      gridrow{
        label[s:align="left,center" s:expand=1]{Hi!}
        combobox[s:expand=1]{option{...}}
      }
      gridrow{
        ...
        row[s:expand=1]{ button{Ok} button{Cancel} }
      }
    }#grid; }#column;
}#frame; }#app;
77 / 90

Declarative Events
menubar{
  menu[:_File]{
    action[:_Open]
    action[:_Fail]
  }
  ...
}
Automatically calls:
$frame->menu_file_open($evt);
78 / 90

Example: Simple Text Editor
79 / 90

#!/usr/bin/env freetuit

app{
  frame[@FreeTUIT::Example::SimpleEditor title="SimpleDit" size=600x400]{
    column{
      textarea[:text0 s:expand=1 s:stretch=1]{}
    }
    menubar{
      menu[:_File]{
        action[:_New/Ctrl-N]
        action[:_Open/Ctrl-O]
        action[:_Save/Ctrl-S]
        action[:_Quit/Ctrl-W]
      }
      menu[:_Help]{
        action[:_About]
      }
    }#menubar;
    toolbar{
      tool[:New  =menu_file_new  icon=:file-new  tip="New File"]
      tool[:Open =menu_file_open icon=:file-open tip="Open File"]
      tool[:Save =menu_file_save icon=:file-save tip="Save File"]
    }#toolbar;
  }#frame;
}#app;

# vim:ts=2:sw=2:et:sta:ft=hbml
80 / 90

package FreeTUIT::Example::SimpleEditor;

use warnings;
use strict;
use Carp;

use base 'FreeTUIT::Wx::frame';

use File::Fu;

=head1 NAME

FreeTUIT::Example::SimpleEditor - edit and save ascii text

=cut

# we override finish() as an OnInit - probably shouldn't be this way
my $title;
sub finish {
  my $self = shift;

  $self->SUPER::finish(@_);

  $title = $self->GetTitle;
  $self->set_filename('');
  $self->text0->SetFocus;
} ######################################################################

# accessor and policy for managing the current File::Fu::File
sub filename {shift->{filename}}
sub set_filename {
  my $self = shift;
  my ($file) = @_;

  $self->{filename} = $file;

  my $name = $file ? $file->file : '*unnamed*';
  $self->SetTitle("$title - $name");
} ######################################################################

=head1 Menu Events

=head2 menu_file_new

  $frame->menu_file_new($evt);

=cut

sub menu_file_new {
  my $self = shift;

  # TODO prompt about saving
  $self->text0->SetValue('');
  $self->set_filename(undef);
} ######################################################################

=head2 menu_file_open

  $frame->menu_file_open($evt);

=cut

sub menu_file_open {
  my $self = shift;

  my $dialog = Wx::FileDialog->new($self,
    "Choose a file",
    "",
    "",
    "*.*",
    Wx::wxOPEN()|Wx::wxFILE_MUST_EXIST(),
  );
  my $ok = $dialog->ShowModal;
  return() unless($ok == Wx::wxID_OK());

  my $file = File::Fu->file($dialog->GetPath);
  my $text = $file->read;
  $self->text0->SetValue($text);
  $self->set_filename($file);
} ######################################################################

=head2 menu_file_save

  $frame->menu_file_save($evt);

=cut

sub menu_file_save {
  my $self = shift;

  unless($self->filename) {
    my $dialog = Wx::FileDialog->new($self,
      "Enter filename",
      '',
      '',
      '*.*',
      Wx::wxSAVE()|Wx::wxOVERWRITE_PROMPT()
    );
    my $ok = $dialog->ShowModal; # XXX a focus nit?
    return() unless($ok == Wx::wxID_OK());
    $self->set_filename(File::Fu->file($dialog->GetPath));
  }

  $self->filename->write($self->text0->GetValue);
} ######################################################################

=head2 menu_file_quit

  $frame->menu_file_quit($evt);

=cut

sub menu_file_quit {
  my $self = shift;
  $self->Close;
} ######################################################################

=head2 menu_help_about

  $frame->menu_help_about($evt);

=cut

sub menu_help_about {
  my $self = shift;
  my $dialog = Wx::MessageDialog->new($self,
    "This is $title - a silly little demo for FreeTUIT\n\n" .
    "If you are using this for serious editing, have fun!"
  );
  $dialog->ShowModal;
} ######################################################################

1;
81 / 90

Layout
#!/usr/bin/env freetuit

app{
  frame[
    class=FreeTUIT::Example::SimpleEditor
    title="SimpleDit"
    size=600x400
  ]{
    column{
      textarea[:text0 s:expand=1 s:stretch=1]{}
    }
    ...
82 / 90

Menubar
...
  menubar{
    menu[:_File]{
      action[:_New/Ctrl-N]
      action[:_Open/Ctrl-O]
    ...
    menu[:_Help]{
      action[:_About]
    }
83 / 90

Toolbar

toolbar{
  tool[:New  =menu_file_new  icon=:file-new  tip="New File"]
  tool[:Open =menu_file_open icon=:file-open tip="Open File"]
  tool[:Save =menu_file_save icon=:file-save tip="Save File"]
}#toolbar;
shortcuts:
tool[
  name=New
  id=menu_file_new
  icon=:file-new
  tip="New File"
  ]
84 / 90

Backend

package FreeTUIT::Example::SimpleEditor;
...
use base 'FreeTUIT::Wx::frame';

sub menu_file_new {
  my $self = shift;

  # TODO prompt about saving
  $self->text0->SetValue('');
  $self->set_filename(undef);
}
...
85 / 90

Just Connect Each event
sub menu_file_open {
  my $self = shift;

  my $dialog = Wx::FileDialog->new($self,
    "Choose a file", "", "", "*.*",
    Wx::wxOPEN()|Wx::wxFILE_MUST_EXIST(),
  );
  my $ok = $dialog->ShowModal;
  return() unless($ok == Wx::wxID_OK());

  my $file = File::Fu->file($dialog->GetPath);
  my $text = $file->read;
  $self->text0->SetValue($text);
  $self->set_filename($file);
}
86 / 90

Lots to do still

  • more widgets
  • runtime API
  • hotkey manager
  • dialogs
http://scratchcomputing.com/svn/FreeTUIT/trunk
87 / 90

Other Thoughts

  • other toolkits?
  • other languages?
88 / 90

Questions?

Thank You
89 / 90

http://scratchcomputing.com/
90 / 90