Source

App::Harmonograph / lib / harmonograph.pl

Full commit
#!/usr/bin/perl

use v5.12;
use warnings;
use Wx;
use Wx::Perl::DrawMap;
use Wx::Perl::TextSlider;
use Wx::Perl::RadioGroup;
use Wx::Perl::Smart::Frame;
use Colour::Flow;
use Math::Trig;
use File::Spec;
use YAML::Tiny;

package Harmonograph;
our $VERSION = '0.92';
use base qw(Wx::App);

my $fav_file = 'favs.yml'; 
my $l18n_file = 'localisation.yaml';
my $language = 'german';

sub OnInit {
	my $app = shift;

	# load localisation texts in chosen language
	my ($v, $dir, $f) = File::Spec->splitpath(__FILE__);
	$l18n_file = File::Spec->catfile($dir, $l18n_file) if $dir;
	die "localisation file $l18n_file is missing!" unless -e $l18n_file;
	my %l18n = %{YAML::Tiny->read( $l18n_file )->[0]{$language}};
	$app->{'l18n'} = \%l18n;

	# loading the numbers of the remembered favorites
	$app->{'favorites'} = -e $fav_file ? YAML::Tiny->read( $fav_file ) : YAML::Tiny->new;
	my @remembered_pic_names = ref $app->{'favorites'}->[0] eq 'HASH' ? keys $app->{'favorites'}->[0] : ();

	# making UI
	my $frame = $app->{'frame'} = Wx::Perl::Smart::Frame->new(__PACKAGE__." $VERSION");

	my $boardsize = Wx::GetDisplaySize()->GetHeight() - 250;
	my $origin_offset = $app->{'origin_offset'} = $boardsize / 2;
	my $max_amp = $app->{'max_amp'} = $origin_offset - 10;        # max amplitude
	my $repaint = sub { $app->Repaint() };
	my %range_defaults = (# label, min, max, init
		frequency_x => ['X',               1, 1,  30], frequency_y => ['Y',               1,  1,   30],
		amplitude_x => ['X',               0, 0, 360], amplitude_y => ['Y',               0,  0,  360],
		rotation    => [$l18n{'amount'},   1, 0,  30], friction    => [$l18n{'friction'}, 0,  0,  200],
		length      => [$l18n{'length'},  12, 1, 100], density     => [$l18n{'density'},100,  1,  100],
		thickness   => [$l18n{'thickness'},1, 1,  12], zoom        => [$l18n{'zoom'},     0,-10,   10], 
		start_colour=> [$l18n{'start'},    0, 0,1500], flow_colour => [$l18n{'flow'},     0,  0,   30],
		scale_colour=> [$l18n{'scale'},    1, 1,   4],
	);
	$frame->SubscribeWidgets
		({$_         => Wx::Perl::TextSlider->new ($frame, @{$range_defaults{$_}}, $repaint)}) for keys %range_defaults;
	$frame->SubscribeWidgets({
		drawboard    => Wx::Perl::DrawMap->new($frame, $boardsize),
		fav_select   => [-ComboBox => \@remembered_pic_names, 0, -1, sub {
										$frame->SetValues( $app->{'favorites'}[0]{ $_[0]->GetValue() } );$app->Repaint()}],
		format_select=> [-ComboBox => [qw(PNG JPG TIFF BMP XPM)], 0, 70       ],
		save         => [-Button   => $l18n{'save'},    sub {$app->Save()    }],
		save_all     => [-Button   => $l18n{'all'},     sub {$app->SaveAll() }],
		remember     => [-Button   => $l18n{'remember'},sub {$app->Remember()}],
		forget       => [-Button   => $l18n{'forget'},  sub {$app->Forget()  }],
		y_invers     => [-CheckBox => $l18n{'y_inverse'}, 0, $repaint],
		rotation_dir => Wx::Perl::RadioGroup->new($frame, [@l18n{'no','left','right'}], 0, &Wx::wxHORIZONTAL, $repaint),
	});

	$frame->SetSmartLayout([
		# left part
		'<drawboard>',
		10,
		{border => 10, flags => &Wx::wxALL|&Wx::wxGROW},
		'<fav_select>',
		['<format_select>', 10,'<save>', 10, '<save_all>', \1, '<forget>', 10, '<remember>'],
	],[ # right half
		-TabbedBox =>  [ 
			$l18n{'oscillators'} => [[
				{border => 5, flags => &Wx::wxGROW|&Wx::wxALL},
				-LabeledBox => [ $l18n{'frequency'} => [qw( <frequency_x> <frequency_y> <y_invers> )]],
				-LabeledBox => [ $l18n{'start_amp'} => [qw( <amplitude_x> <amplitude_y>            )]],
				-LabeledBox => [ $l18n{'rotation'}  => [qw( <rotation_dir> <rotation>              )]],
				{border => 10},								'<friction>', 
			]],
			$l18n{'visuals'} => [[
				{border => 5, flags => &Wx::wxGROW|&Wx::wxALL},
				-LabeledBox => [ $l18n{'line'}  => [qw( <length> <density>                         )]], # <thickness>
				-LabeledBox => [ $l18n{'color'} => [qw( <start_colour> <flow_colour> <scale_colour>)]],
				{border => 10},							'<zoom>',
			]]
		],
	],);#~line
	$frame->ResetValues();
	$app->Repaint();
	$app->SetTopWindow($frame);
	1;
}


sub Remember {
	my $app = shift;
	my $frame = $app->{'frame'};
	my $cb = $frame->w('fav_select'); # combo box
	my $name = Wx::GetTextFromUser
		($app->{'l18n'}{'ask_for_a_name'}, __PACKAGE__." $VERSION", '', $frame);
	$name = Wx::GetTextFromUser
		($app->{'l18n'}{'ask_another_name'}, __PACKAGE__." $VERSION", '', $frame)
			while exists $app->{'favorites'}[0]{$name} or not $name;
	$app->{'favorites'}[0]{$name} = $app->{'frame'}->GetValues;
	$cb->SetValue($name);
	$cb->Insert($name, 0);
	$app->{'favorites'}->write( $fav_file );
}
sub Forget {
	my $app = shift;
	my $cb = $app->{'frame'}->w('fav_select');
	return if $cb->IsEmpty;
	my $name = $cb->GetValue();
	$cb->Delete( $cb->FindString($name) );
	delete $app->{'favorites'}[0]{$name};
	if ($cb->IsEmpty){
		$cb->SetValue('');
		$app->{'frame'}->ResetValues();
		$app->{'favorites'} =  YAML::Tiny->new;
	} else {
		$cb->SetSelection(0);
		$app->SetValues( $app->{'favorites'}[0]{ $cb->GetValue() } );
		delete $app->{'favorites'}[0]{$name};
	}
	$app->Repaint();
	$app->{'favorites'}->write( $fav_file );
}



sub Save {
	my $app = shift;
	my $format = lc $app->{'frame'}->w('format_select')->GetValue();
	my $file = Wx::FileSelector(
		$app->{'l18n'}{'save_under'}.' '.uc($format),            '.', 
		$app->{'frame'}->w('fav_select')->GetValue().".$format",  '',
		'(*)|*', &Wx::wxFD_SAVE, $app->{'frame'}
	);
	$app->{'frame'}->w('drawboard')->SaveAsFile($file, $format) if $file;
}
sub SaveAll {
	my $app = shift;
	my $frame = $app->{'frame'};
	my $format = lc $app->{'frame'}->w('format_select')->GetValue();
	my $dir = Wx::DirSelector( $app->{'l18n'}{'save_all_under'}.' '.uc($format), '.', 0, [-1,-1], $frame);
	return unless -d $dir;
	my $values = $frame->GetValues();
	for (keys $app->{'favorites'}[0]) {
		$frame->SetValues( $app->{'favorites'}[0]{$_} );
		$app->Repaint();
		my $file = File::Spec->catfile($dir, "$_.$format" );
		$frame->w('drawboard')->SaveAsFile($file, $format);
	}
	$frame->SetValues($values);
	$app->Repaint();
}



sub Repaint {
	my $app = shift;
	my $board = $app->{'frame'}->w('drawboard');
	my $v = $app->{'frame'}->GetValues; # values

	my $origin_offset   = $app->{'origin_offset'};
	my $max_amp         = $app->{'max_amp'} * (1.2**$v->{'zoom'});
	my $win_size_factor = $max_amp / 10;
	my $pi = 3.1415926536; 
	my $xangle = $v->{'amplitude_x'} / 360 * $pi;                               # beginning with start amplitudes
	my $yangle = $v->{'amplitude_y'} / 360 * $pi;
	my $dx = $v->{'frequency_x'};                                               # one step of the oscillator rotation 
	my $dy = $v->{'frequency_y'};
	my $max_freq = $dx > $dy ? $dx : $dy;
	my $step_size = 100 / $v->{'density'} / $max_freq / $win_size_factor;
	$dx *= 0.2 * $step_size;                                                   # one step of the oscillator rotation 
	$dy *= 0.2 * $step_size;
	my $friction_factor =  (1 - (0.00005 * $v->{'friction'})) ** $step_size;
	my $steps = $v->{'length'} * 100 * $v->{'density'};

	#$dy = $v->{'y_invers'} ? - $dy : $dy;
	#my $x = Math::Trig::asin( ($v->{'amplitude_x'} - $max_amp)/$max_amp );
	#my $y = Math::Trig::asin( ($v->{'amplitude_y'} - $max_amp)/$max_amp );
	#my $drot = $v->{'rotation'} / 2000;
	#my $rota_dir = $v->{'rotation_dir'};
	#$drot = - $drot if $rota_dir == 2;
	#my $rot = 0;# deg2rad($degrees);
	my $colour_flow = Colour::Flow->new(
		 $v->{'scale_colour'},
		($v->{'flow_colour'} ? 10 / $v->{'flow_colour'} * $v->{'density'} : 0), # change rate
		 $v->{'start_colour'},
	);
	$board->Colour( $colour_flow->get_rgb() );
	$board->NewDrawing();

	for my $cc (0 .. $steps) {
		$xangle += $dx;
		$yangle += $dy;
		$max_amp *= $friction_factor;

		#my $xs = sin($x += $dx);
		#my $ys = sin($y += $dy);
		#$rot += $drot;
		#($xs, $ys) = rotate($xs, $ys, $rot) if $rota_dir;
		$board->Colour( $colour_flow->get_rgb() ) if $colour_flow->next_step(); 

		#$app->{'dc'}->DrawPoint( ($xs*$cur_amp*$value->{'zoom'})+$midoffset,
		#                         ($ys*$cur_amp*$value->{'zoom'})+$midoffset );

		$board->Point(sin($xangle) * $max_amp + $origin_offset, 
					  sin($yangle) * $max_amp + $origin_offset);
	}
	$board->PublishDrawing();
}
sub rotate {
	my ($x, $y, $r) = @_;
	my ($sinr, $cosr) = (sin($r), cos($r));
	return ($cosr*$x - $sinr*$y, $sinr*$x + $cosr*$y);
}

package main;
Harmonograph->new->MainLoop;