Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 756 lines (748 sloc) 29.2 KB
#!/usr/bin/perl
#
# ds-measurer, v1.6.1, GTK+
# Copyright © 2016-2017 Dmitry Sokolov
#
# This file is part of ds-measurer.
#
# ds-measurer is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ds-measurer is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with ds-measurer. If not, see <http://www.gnu.org/licenses/>.
#
# Скрипт предназначен для измерения области экрана — ширины, высоты, координат
# начальной и конечной точек измерения, угла и длины отрезка между ними.
# Процесс происходит в графическом режиме: на отдельном прозрачном слое поверх
# текущей отрисовки экрана выводятся направляющие линии курсора и выделения
# и текущие числовые значения. Требуется включённый композитный режим вывода.
# В процессе измерения есть возможность делать скриншоты выделенной области
# в буфер обмена и/или файл на диске, сохранять в буфер обмена текущие числовые
# значения выделения, переключаться в режим выделения из центра, ограничивать
# выделение каким-либо из заданных соотношений сторон, включать вспомогательные
# направляющие (центровые, золотого сечения, диагональ), менять их цвета и др.
# Со скриптом поставляется конфигурационный файл в формате YAML, в котором
# можно настроить цвета отрисовки, сочетания клавиш для разных функций, набор
# соотношений сторон фиксации выделения, формат и маску файла скриншота и др.
# Подробнее: http://sokolov.website/programs/ds-utils/ds-measurer
#
# Зависимости: perl, libgtk2-perl, libmath-round-perl, libyaml-perl
#
# Ключей нет; запуск: ds-measurer
#
# Dmitry Sokolov <dmitry@sokolov.website>
use strict;
use warnings;
use utf8;
use open qw/:encoding(UTF-8) :std/;
use Gtk2 "-init";
use POSIX;
use Math::Round;
use YAML;
use File::Basename;
use constant PI => 4 * atan2(1, 1);
my @cpaths = ($ENV{"HOME"} . "/.config/ds-measurer.yaml", dirname(__FILE__) . "/ds-measurer.yaml");
my $config = 0;
foreach(@cpaths) {if(-s && -f) {$config = YAML::LoadFile($_); last;}}
die "No configuration file found at:\n" . join("\n", @cpaths) . "\n" if !$config;
$config->{"screenshot"}->{"file"} =~ s/^~/$ENV{"HOME"}/;
my (@m, @g, %all, $cb, $cr);
my ($last, $creating, $move, $fromcenter, $fixedratio, $arm, $sf) = (0, 0, 0, 0, 0, 1, 0);
my @pp = (3, 4, 5, 12); # Константы отступов для текста
my @primes = (2);
my %idx = (m => undef, g => undef); # Индексы активных выделений / направляющих
my %pack = (m => \@m, g => \@g); # Для обращений к массивам выделений / направляющих по переменной $type
my %calc = (m => \&calc_m, g => \&calc_g); # Для обращений к функциям расчёта по переменной $type
my %draw = (m => \&draw_m, g => \&draw_g); # Для обращений к функциям отрисовки по переменной $type
my %gstate = (show => 1, snap => $config->{"guides"}->{"snap distance"}); # Состояние направляющих
my $type = "m";
my $go;
my $fresh_selection = 1;
if($config->{"common"}->{"selection mode"} eq "two pixels") {$go = 0;}
elsif($config->{"common"}->{"selection mode"} eq "outer") {$go = -0.5;}
else {$go = 0.5;}
my $window = Gtk2::Window->new;
$window->signal_connect(destroy => sub {Gtk2->main_quit;});
$window->signal_connect(key_press_event => \&key_press);
$window->signal_connect(key_release_event => \&key_release);
$window->fullscreen;
my $da = Gtk2::DrawingArea->new;
$da->set_events([qw/exposure_mask leave-notify-mask button-press-mask button-release-mask scroll-mask pointer-motion-mask pointer-motion-hint-mask/]);
$da->signal_connect(expose_event => \&expose);
$da->signal_connect(button_press_event => \&button_press);
$da->signal_connect(motion_notify_event => \&motion);
$da->signal_connect(button_release_event => \&button_release);
$da->signal_connect(scroll_event => \&scroll);
$window->add($da);
$cb = Gtk2::Clipboard->get(Gtk2::Gdk->SELECTION_CLIPBOARD);
$window->set_colormap($window->get_screen->get_rgba_colormap);
$window->show_all;
$da->window->set_cursor(Gtk2::Gdk::Cursor::new(undef, "blank-cursor"));
supplement_primes(
$window->get_screen->get_width > $window->get_screen->get_height ?
$window->get_screen->get_width : $window->get_screen->get_height
);
my %image = &cscc("image");
my %c = &cscc();
($c{dsx}, $c{dsy}) = (0, 0);
my %w = &cscc();
$w{sline} = 0;
$w{type} = "m";
$w{marks} = $config->{"selection guides"};
$w{colors} = [&set_colors("default")];
&cursor();
Gtk2->main;
sub supplement_primes {
my $new = shift;
return if $primes[$#primes] >= $new;
@primes = ();
my @sieve = (1) x ($new * 2);
$sieve[0] = $sieve[1] = 0;
for(my $i = 2; $i ** 2 <= $new * 2; $i++) {
if($sieve[$i]) {
for(my $y = $i ** 2, my $z = 1; $y <= $new * 2; $y = $i ** 2 + $i * $z++) {
$sieve[$y] = 0;
}
}
}
for(my $i = 0; $i < scalar @sieve; $i++) {
if($sieve[$i]) {
push @primes, $i;
}
}
}
sub reduce_fraction {
my ($n, $d) = @_;
supplement_primes($n > $d ? $n : $d);
foreach my $p (@primes) {
if($n > 1 && $d > 1) {
while(!($n % $p) && !($d % $p)) {
$n = $n / $p;
$d = $d / $p;
}
}
last if $p > $n && $p > $d;
}
return ($n, $d);
}
sub cscc {
my ($t, $w, $h) = ($_[0] || "recording", $_[1] || $window->get_screen->get_width, $_[2] || $window->get_screen->get_height);
my ($surface, $context);
if($t eq "image") {$surface = Cairo::ImageSurface->create("argb32", $w, $h);}
else {$surface = Cairo::RecordingSurface->create("color-alpha", undef);}
$context = Cairo::Context->create($surface);
$context->set_line_width(1);
$context->save;
return (surface => $surface, context => $context);
}
sub freeze {
push @m, {&cscc()};
$m[$#m]{type} = "m";
$m[$#m]{id} = ++$last;
$all{$last} = \$m[$#m];
$m[$#m]{sline} = $w{sline};
$m[$#m]{marks} = {%{$w{marks}}};
for my $key (keys %{$w{marks}}) {
$m[$#m]{marks}{$key} = {%{$w{marks}{$key}}};
}
$m[$#m]{colors} = [@{$w{colors}}];
&calc_m($m[$#m], $c{x0}, $c{y0}, $c{x1}, $c{y1});
&draw_m($m[$#m]);
}
sub guide {
my $a = shift;
push @g, {&cscc()};
$g[$#g]{type} = "g";
$g[$#g]{id} = ++$last;
$all{$last} = \$g[$#g];
$g[$#g]{color} = [@{$w{colors}[2]}];
&calc_g($g[$#g], $c{xr}, $c{yr}, $a);
&draw_g($g[$#g]);
}
sub cursor {
(undef, $c{x1}, $c{y1}) = $da->window->get_pointer;
if(defined $c{x0}) {
$c{xr} = $c{x1} - $c{x0} >= 0 ? ($c{x1} - $go) : ($c{x1} + $go);
$c{yr} = $c{y1} - $c{y0} >= 0 ? ($c{y1} - $go) : ($c{y1} + $go);
} else {
$c{xr} = $c{x1} + $go;
$c{yr} = $c{y1} + $go;
}
$c{context}->set_source_rgba(@{$w{colors}[1]});
$c{context}->move_to($c{xr}, 0);
$c{context}->line_to($c{xr}, $window->get_screen->get_height);
$c{context}->move_to(0, $c{yr});
$c{context}->line_to($window->get_screen->get_width, $c{yr});
$c{context}->stroke;
}
sub clear {
my @contexts = @_ ? (@_) : (Gtk2::Gdk::Cairo::Context->create($da->window));
foreach(@contexts) {
$_->set_operator("clear");
$_->paint;
$_->set_operator("over");
$_->new_path;
}
return @contexts;
}
sub clear_in {
my ($C, $x, $y, $w, $h) = @_;
$C->rectangle($x, $y, $w, $h);
$C->set_operator("clear");
$C->fill;
$C->set_operator("over");
$C->new_path;
}
sub screenshot {
my ($S) = @_;
my ($xs, $ys, $ws, $hs) = ($$S{x0} < $$S{x1} ? $$S{x0} : $$S{x1}, $$S{y0} < $$S{y1} ? $$S{y0} : $$S{y1}, $$S{w}, $$S{h});
my $ss = Gtk2::Gdk::Pixbuf->new("rgb", 0, 8, $ws, $hs);
$ss->get_from_drawable(Gtk2::Gdk->get_default_root_window, Gtk2::Gdk::Colormap->get_system, $xs, $ys, 0, 0, $ws, $hs);
if($config->{"screenshot"}->{"target"} eq "both"
|| $config->{"screenshot"}->{"target"} eq "file") {
eval {$ss->save(strftime($config->{"screenshot"}->{"file"}, localtime), $config->{"screenshot"}->{"format"});}
or print $@;
}
if($config->{"screenshot"}->{"target"} eq "both"
|| $config->{"screenshot"}->{"target"} eq "clipboard") {
$cb->set_image($ss);
}
}
sub set_colors {
my $ct = shift;
my @colors;
my @n = ("main", "basic", "guide", "text main", "text basic", "text faded");
my @c = ("r", "g", "b", "a");
for(my $i = $#n; $i >= 0; $i--) {
for(my $y = $#c; $y >= 0; $y--) {
$colors[$i][$y] = $config->{"colors"}->{$ct}->{$n[$i]}->{$c[$y]};
}
}
return @colors;
}
sub highlight {
my ($t, $i, $widget) = @_;
if(@{$pack{$t}}) {
if(defined $i || defined $idx{$t}) {
if(defined $idx{$t}) {
${$pack{$t}}[$idx{$t}]{active} = 0;
&clear(${$pack{$t}}[$idx{$t}]{context});
if($t eq "g") {&draw_g(${$pack{$t}}[$idx{$t}], ${$pack{$t}}[$idx{$t}]{color});}
elsif($t eq "m") {&draw_m(${$pack{$t}}[$idx{$t}], ${$pack{$t}}[$idx{$t}]{marks}, ${$pack{$t}}[$idx{$t}]{colors});}
}
$idx{$t} = defined $i ? $i % @{$pack{$t}} : undef;
if(defined $idx{$t}) {
${$pack{$t}}[$idx{$t}]{active} = 1;
&clear(${$pack{$t}}[$idx{$t}]{context});
if($t eq "g") {&draw_g(${$pack{$t}}[$idx{$t}], [&set_colors("highlight")]->[2]);}
elsif($t eq "m") {&draw_m(${$pack{$t}}[$idx{$t}], ${$pack{$t}}[$idx{$t}]{marks}, [&set_colors("highlight")]);}
}
$widget->queue_draw;
}
}
}
sub maskout_delta {
my ($S, $d) = @_;
$$S{frame} = 0 if !defined $$S{frame};
$$S{frame} = nearest(0.001, $$S{frame} + $d);
$$S{frame} = 0 if $$S{frame} < 0;
$$S{frame} = 1 if $$S{frame} > 1;
}
sub key_n2c {
return Gtk2::Gdk::Keymap->get_entries_for_keyval(Gtk2::Gdk->keyval_from_name(shift))->{"keycode"};
}
sub calc_m {
my ($S, $x0, $y0, $x1, $y1, $fc, $fr) = @_;
$fc = $fromcenter if !defined $fc;
$fr = $fixedratio if !defined $fr;
$$S{x0} = $x0; $$S{y0} = $y0; $$S{x1} = $x1; $$S{y1} = $y1;
# При !$creating только перемещение с прежними абсолютными координатами
if($creating) {
# Выделение из центра
if($fc) {$$S{x0} = $x0 * 2 - $x1; $$S{y0} = $y0 * 2 - $y1;};
# Выделение с фиксированным соотношением сторон
if($fr) {
if(abs $x1 - $x0 >= abs $y1 - $y0) {
$$S{x1} = $x0 + round($arm * ($x1 - $x0 >= 0 ? (abs $y1 - $y0) : (0 - abs $y1 - $y0)));
$$S{x0} = $x0 - round($arm * ($x1 - $x0 >= 0 ? (abs $y1 - $y0) : (0 - abs $y1 - $y0))) if $fc;
} else {
$$S{y1} = $y0 + round($arm * ($y1 - $y0 >= 0 ? (abs $x1 - $x0) : (0 - abs $x1 - $x0)));
$$S{y0} = $y0 - round($arm * ($y1 - $y0 >= 0 ? (abs $x1 - $x0) : (0 - abs $x1 - $x0))) if $fc;
}
}
}
# Прилипание к направляющим
if($gstate{snap}) {
my ($tw, $th) = ($$S{x1} - $$S{x0}, $$S{y1} - $$S{y0});
$$S{snapped} = 0;
foreach my $g (@g) {
if($$g{ar} == 0) {
if((abs $c{dsy} <= $gstate{snap} || $fr || $fc)
&& abs($$S{y0} - $$g{y}) <= $gstate{snap}) {
$$S{snapped} = 1;
$$S{y0} = $$S{y1} - $$S{y0} >= 0
? floor $$g{y} - $go
: floor $$g{y} + ($fc ? 0 : $go);
if($fc) {$$S{y1} = $y0 * 2 - $$S{y0};}
if($move) {$$S{y1} = $$S{y0} + $th;}
$c{dsy} = $creating ? ($$S{y0} - $c{y0}) : ($$S{y1} - $c{y1} - $c{fdy});
$c{y0i} = floor $$g{y} if $fresh_selection;
}
if((abs $c{dsy} <= $gstate{snap} || $fr || $fc)
&& abs($$S{y1} - $$g{y}) <= $gstate{snap}) {
$$S{snapped} = 1;
$$S{y1} = $$S{y1} - $$S{y0} >= 0
? floor $$g{y} + $go
: floor $$g{y} - ($fc ? 0 : $go);
if($fc) {$$S{y0} = $y0 * 2 - $$S{y1};}
if($move) {$$S{y0} = $$S{y1} - $th;}
}
}
elsif($$g{ar} == 90) {
if((abs $c{dsx} <= $gstate{snap} || $fr || $fc)
&& abs($$S{x0} - $$g{x}) <= $gstate{snap}) {
$$S{snapped} = 1;
$$S{x0} = $$S{x1} - $$S{x0} >= 0
? floor $$g{x} - $go
: floor $$g{x} + ($fc ? 0 : $go);
if($fc) {$$S{x1} = $x0 * 2 - $$S{x0};}
if($move) {$$S{x1} = $$S{x0} + $tw;}
$c{dsx} = $creating ? ($$S{x0} - $c{x0}) : ($$S{x1} - $c{x1} - $c{fdx});
$c{x0i} = floor $$g{x} if $fresh_selection;
}
if((abs $c{dsx} <= $gstate{snap} || $fr || $fc)
&& abs($$S{x1} - $$g{x}) <= $gstate{snap}) {
$$S{snapped} = 1;
$$S{x1} = $$S{x1} - $$S{x0} >= 0
? floor $$g{x} + $go
: floor $$g{x} - ($fc ? 0 : $go);
if($fc) {$$S{x0} = $x0 * 2 - $$S{x1};}
if($move) {$$S{x0} = $$S{x1} - $tw;}
}
}
}
if(!$$S{snapped} || abs $c{dsx} > $gstate{snap}) {$c{dsx} = 0;}
if(!$$S{snapped} || abs $c{dsy} > $gstate{snap}) {$c{dsy} = 0;}
}
# При !$creating только перемещение с прежними абсолютными координатами
if($creating) {
# Расчёт ширины, высоты, длины, угла и значений для прорисовки
($$S{w}, $$S{h}) = (abs $$S{x1} - $$S{x0}, abs $$S{y1} - $$S{y0});
($$S{l}, $$S{a}) = (($$S{w} ** 2 + $$S{h} ** 2) ** 0.5, atan2($$S{h}, $$S{w}));
$$S{aratio} = [reduce_fraction($$S{w}, $$S{h})];
$$S{lr} = round($$S{l}); $$S{ar} = nearest(0.01, $$S{a} * 180 / PI); $$S{ar} =~ s/,/\./;
}
($$S{xr}, $$S{wr}) = $$S{x1} - $$S{x0} >= 0 ? ($$S{x0} + $go, $$S{x1} - $$S{x0} - ($go + $go)) : ($$S{x0} - $go, $$S{x1} - $$S{x0} + ($go + $go));
($$S{yr}, $$S{hr}) = $$S{y1} - $$S{y0} >= 0 ? ($$S{y0} + $go, $$S{y1} - $$S{y0} - ($go + $go)) : ($$S{y0} - $go, $$S{y1} - $$S{y0} + ($go + $go));
}
sub draw_m {
my $S = $_[0];
my %marks = $_[1] ? %{$_[1]} : %{$w{marks}};
my @colors = $_[2] ? @{$_[2]} : @{$w{colors}};
if($$S{frame}) {
$$S{context}->save;
$$S{context}->set_source_rgba(0, 0, 0, $$S{frame});
$$S{context}->paint;
if($$S{active}) {
for(my $i = 1; $i < 5; $i++) {
$$S{context}->set_line_width($i * 4);
$$S{context}->set_source_rgba(0, 0, 0, 0.25 * $$S{frame} / $i);
$$S{context}->rectangle($$S{x0}, $$S{y0}, $$S{x1} - $$S{x0}, $$S{y1} - $$S{y0});
$$S{context}->stroke;
}
}
&clear_in($$S{context}, $$S{x0}, $$S{y0}, $$S{x1} - $$S{x0}, $$S{y1} - $$S{y0});
&clear_in($c{context}, $$S{x0}, $$S{y0}, $$S{x1} - $$S{x0}, $$S{y1} - $$S{y0});
$$S{context}->restore;
} else {
# Координатный прямоугольник
$$S{context}->set_source_rgba(@{$colors[1]});
$$S{context}->rectangle($$S{xr}, $$S{yr}, $$S{wr}, $$S{hr});
$$S{context}->stroke;
}
if(!$$S{frame} || !$$S{onlymask}) {
# Направляющие выделения
foreach my $m (keys %marks) {
if($marks{$m}{show}) {
$$S{context}->set_source_rgba(@{$colors[1]});
foreach my $l (@{$marks{$m}{x}}) {
$$S{context}->move_to($$S{xr} + ceil($$S{wr} * $l), $$S{yr});
$$S{context}->line_to($$S{xr} + ceil($$S{wr} * $l), $$S{yr} + $$S{hr});
}
foreach my $l (@{$marks{$m}{y}}) {
$$S{context}->move_to($$S{xr}, $$S{yr} + ceil($$S{hr} * $l));
$$S{context}->line_to($$S{xr} + $$S{wr}, $$S{yr} + ceil($$S{hr} * $l));
}
$$S{context}->stroke;
}
}
# Основной отрезок
if($$S{sline}) {
$$S{context}->set_source_rgba(@{$colors[0]});
$$S{context}->move_to($$S{xr}, $$S{yr});
$$S{context}->line_to($$S{xr} + $$S{wr}, $$S{yr} + $$S{hr});
$$S{context}->stroke;
}
# Угол
if($$S{sline} && $$S{w} > 25) {
$$S{context}->set_source_rgba(@{$colors[1]});
$$S{context}->arc($$S{xr}, $$S{yr}, 25, 0, $$S{a}) if $$S{x1} - $$S{x0} >= 0 && $$S{y1} - $$S{y0} >= 0;
$$S{context}->arc($$S{xr}, $$S{yr}, 25, PI * 2 - $$S{a}, 0) if $$S{x1} - $$S{x0} >= 0 && $$S{y1} - $$S{y0} < 0;
$$S{context}->arc($$S{xr}, $$S{yr}, 25, PI, PI + $$S{a}) if $$S{x1} - $$S{x0} < 0 && $$S{y1} - $$S{y0} < 0;
$$S{context}->arc($$S{xr}, $$S{yr}, 25, PI - $$S{a}, PI) if $$S{x1} - $$S{x0} < 0 && $$S{y1} - $$S{y0} >= 0;
$$S{context}->stroke;
}
# Текст
my ($tt, $te);
$$S{context}->set_source_rgba(@{$colors[4]});
$$S{context}->select_font_face("sans", "normal", "normal");
$$S{context}->set_font_size(11);
# Текст, ширина
$tt = $$S{w}; $te = $$S{context}->text_extents($tt);
$$S{context}->move_to(
$$S{xr} + $$S{wr} / 2 - $te->{"width"} / 2,
$$S{yr} + $$S{hr} - ($$S{y1} - $$S{y0} >= 0 ? (0 - $te->{"height"} - $pp[1]) : $pp[2])
);
$$S{context}->show_text($tt);
# Текст, высота
$tt = $$S{h}; $te = $$S{context}->text_extents($tt);
$$S{context}->move_to(
$$S{xr} + $$S{wr} + ($$S{x1} - $$S{x0} >= 0 ? $pp[0] : (0 - $te->{"width"} - $pp[1])),
$$S{yr} + $$S{hr} / 2 + $te->{"height"} / 2
);
$$S{context}->show_text($tt);
# Текст, угол
if($$S{sline}) {
$tt = "$$S{ar}°"; $te = $$S{context}->text_extents($tt);
$$S{context}->move_to(
$$S{xr} + ($$S{x1} - $$S{x0} >= 0 ? $pp[0] : (0 - $te->{"width"} - $pp[1])),
$$S{yr} - ($$S{y1} - $$S{y0} >= 0 ? $pp[2] : (0 - $te->{"height"} - $pp[1]))
);
$$S{context}->show_text($tt);
}
# Текст, координаты начальные
$$S{context}->set_source_rgba(@{$colors[5]});
$tt = "$$S{x0}, $$S{y0}"; $te = $$S{context}->text_extents($tt);
$$S{context}->move_to(
$$S{xr} + ($$S{x1} - $$S{x0} >= 0 ? (0 - $te->{"width"} - $pp[1]) : $pp[0]),
$$S{yr} - ($$S{y1} - $$S{y0} >= 0 ? $pp[2] : (0 - $te->{"height"} - $pp[1]))
);
$$S{context}->show_text($tt);
# Текст, координаты текущие
$$S{context}->set_source_rgba(@{$colors[3]});
$tt = "$$S{x1}, $$S{y1}"; $te = $$S{context}->text_extents($tt);
$$S{context}->move_to(
$$S{xr} + $$S{wr} + ($$S{x1} - $$S{x0} >= 0 ? $pp[0] : (0 - $te->{"width"} - $pp[1])),
$$S{yr} + $$S{hr} - ($$S{y1} - $$S{y0} >= 0 ? (0 - $te->{"height"} - $pp[0]) : $pp[2])
);
$$S{context}->show_text($tt);
# Текст, соотношение сторон
$$S{context}->set_source_rgba(@{$colors[5]});
$tt = "$$S{aratio}[0] : $$S{aratio}[1]"; $te = $$S{context}->text_extents($tt);
$$S{context}->move_to(
$$S{xr} + $$S{wr} + ($$S{x1} - $$S{x0} >= 0 ? $pp[0] : (0 - $te->{"width"} - $pp[1])),
$$S{yr} + $$S{hr} - ($$S{y1} - $$S{y0} >= 0 ? (0 - $te->{"height"} * 2 - $pp[2] * 2) : ($pp[2] + $te->{"height"} + $pp[2]))
);
$$S{context}->show_text($tt);
# Текст, соотношение сторон, десятичная дробь
if($$S{aratio}[1]) {
my $tt_x = $$S{aratio}[0] / $$S{aratio}[1];
$tt = nearest(0.001, $tt_x);
if($$S{x1} - $$S{x0} >= 0) {$tt .= " ~" if $tt != $tt_x;}
else {$tt = "~ " . $tt if $tt != $tt_x;}
$te = $$S{context}->text_extents($tt);
$$S{context}->move_to(
$$S{xr} + $$S{wr} + ($$S{x1} - $$S{x0} >= 0 ? $pp[0] : (0 - $te->{"width"} - $pp[1])),
$$S{yr} + $$S{hr} - ($$S{y1} - $$S{y0} >= 0 ? (0 - $te->{"height"} * 3 - $pp[2] * 3) : ($pp[2] + $te->{"height"} * 2 + $pp[2] * 2))
);
$$S{context}->set_source_rgba(@{$colors[5]});
$$S{context}->show_text($tt);
}
# Текст, длина
if($$S{sline} && $$S{w} > 55 && $$S{h} > 31) {
$$S{context}->set_source_rgba(@{$colors[3]});
$tt = "$$S{lr}"; $te = $$S{context}->text_extents($tt);
$$S{context}->move_to(
$$S{xr} + $$S{wr} / 2 + ($$S{x1} - $$S{x0} >= 0 ? $pp[0] : (0 - $te->{"width"} - $pp[1])),
$$S{yr} + $$S{hr} / 2 - ($$S{y1} - $$S{y0} >= 0 ? $pp[2] : (0 - $te->{"height"} - $pp[1]))
);
$$S{context}->show_text($tt);
}
}
}
sub calc_g {
my ($S, $x, $y, $a) = @_;
$$S{x} = $x; $$S{y} = $y; $$S{a} = $a;
if($a / PI == int($a / PI)) {$$S{dx} = 1; $$S{dy} = 0;}
elsif($a / (PI / 2) == int($a / (PI / 2))) {$$S{dx} = 0; $$S{dy} = 1;}
else {
$$S{dx} = cos($a) * $window->get_screen->get_width;
$$S{dy} = sin($a) * $window->get_screen->get_height;
}
$$S{ar} = nearest(0.01, $$S{a} * 180 / PI); $$S{ar} =~ s/,/\./;
}
sub draw_g {
my $S = $_[0];
$$S{context}->set_source_rgba($_[1] ? @{$_[1]} : @{$$S{color}});
if(!$$S{dy}) {
$$S{context}->move_to(0, $$S{y});
$$S{context}->rel_line_to($window->get_screen->get_width, 0);
} elsif(!$$S{dx}) {
$$S{context}->move_to($$S{x}, 0);
$$S{context}->rel_line_to(0, $window->get_screen->get_height);
} else {
$$S{context}->move_to($$S{x} - $$S{dx}, $$S{y} - $$S{dy});
$$S{context}->rel_line_to(2 * $$S{dx}, 2 * $$S{dy});
}
$$S{context}->stroke;
}
sub expose {
&clear($image{context});
foreach(@m, @g, \%c, \%w) {
$image{context}->set_source_surface($_->{surface}, 0, 0);
$image{context}->paint;
}
($cr) = &clear();
$cr->set_source_surface($image{surface}, 0, 0);
$cr->paint;
}
sub motion {
my ($widget, $event) = @_;
my $S = !$creating && defined $idx{$type} ? ${$pack{$type}}[$idx{$type}] : \%w;
if(!$sf && (!defined($event) || $event->type eq "motion-notify" && $event->is_hint || $event->type eq "key-press" || $event->type eq "key-release")) {
&clear($c{context});
# Расчёт последнего смещения курсора
($c{dx}, $c{dy}) = ($c{x1}, $c{y1});
&cursor();
($c{dx}, $c{dy}) = ($c{x1} - $c{dx}, $c{y1} - $c{dy});
if($event->state & "mod1-mask" && ($creating || defined $idx{$type})) {&maskout_delta($S, $c{dy} * 0.002);}
if($creating || defined $idx{$type} && $move) {
if($move) {
# Новые «координаты клика» курсора при передвижении вместо выделения
if(defined $c{x0}) {
($c{x0}, $c{y0}) = ($c{x0} + $c{dx}, $c{y0} + $c{dy});
($c{x0i}, $c{y0i}) = ($c{x0i} + $c{dx}, $c{y0i} + $c{dy});
}
} elsif($creating) {
# Коррекция на пиксель при негативном выделении
$c{x0} = $c{x1} - $c{x0i} >= 0 || $fromcenter ? $c{x0i} : $c{x0i} + $go + $go;
$c{y0} = $c{y1} - $c{y0i} >= 0 || $fromcenter ? $c{y0i} : $c{y0i} + $go + $go;
}
# Расчёт выделения или перемещения
if($creating) {
&calc_m($S, $c{x0} + $c{dsx}, $c{y0} + $c{dsy}, $c{x1} + $c{dsx}, $c{y1} + $c{dsy});
} else {
if($type eq "g") {&calc_g($S, $$S{x} + $c{dx}, $$S{y} + $c{dy}, $$S{a});}
elsif($type eq "m") {
&calc_m($S,
$c{x1} + $c{fdx} + $c{dsx} - $$S{w},
$c{y1} + $c{fdy} + $c{dsy} - $$S{h},
$c{x1} + $c{fdx} + $c{dsx},
$c{y1} + $c{fdy} + $c{dsy},
0, 0);}
}
}
if($creating || defined $idx{$type} && ($move || $event->state & "mod1-mask" || $event->type eq "key-release")) {
&clear($$S{context});
if($type eq "g" && !$creating) {&draw_g($S, !$creating ? [&set_colors("highlight")]->[2] : undef);}
elsif($type eq "m" || $creating) {&draw_m($S, $$S{marks}, !$creating ? [&set_colors("highlight")] : undef);}
}
$widget->queue_draw;
$fresh_selection = 0;
}
}
sub button_press {
my ($widget, $event) = @_;
if($event->button == 1) {
$fresh_selection = 1;
$creating = 1 if !$move;
($c{x0i}, $c{y0i}) = $event->coords;
($c{x0}, $c{y0}) = ($c{x0i}, $c{y0i});
}
elsif($event->button == 3) {
$move = 1;
if(!$creating) {
$c{fdx} = ${$pack{$type}}[$idx{$type}]{x1} - $c{x1};
$c{fdy} = ${$pack{$type}}[$idx{$type}]{y1} - $c{y1};
}
}
}
sub button_release {
my ($widget, $event) = @_;
if($event->button == 1) {
&clear($c{context}, $w{context});
$creating = 0;
($c{x0}, $c{y0}, $c{x0i}, $c{y0i}) = (undef, undef, undef, undef);
&cursor();
$widget->queue_draw;
}
elsif($event->button == 3) {
$move = 0;
}
elsif($event->button == 2) {
&highlight($type, undef, $widget);
}
}
# Активировать предыдущее / следующее
sub scroll {
my ($widget, $event) = @_;
my $d = $event->direction eq "down" ? 1 : 0;
&highlight($type, defined $idx{$type} ? $idx{$type} + ($d ? 1 : -1) : ($d ? 0 : (@{$pack{$type}} - 1)), $widget);
}
sub key_press {
my ($widget, $event) = @_;
# Выделение из центра
if($event->hardware_keycode == key_n2c($config->{"shortcuts"}->{"from center"})) {
$fromcenter = 1;
&motion;
}
# Выделение с фиксированным соотношением сторон
elsif($event->hardware_keycode == key_n2c($config->{"shortcuts"}->{"fixed ratio"})) {
$fixedratio = 1;
&motion;
}
# Для направляющих
elsif($event->hardware_keycode == 66) {
$type = $event->state & "lock-mask" ? "m" : "g";
}
}
sub key_release {
my ($widget, $event) = @_;
# Отмена выделения из центра
if($event->hardware_keycode == key_n2c($config->{"shortcuts"}->{"from center"})) {
$fromcenter = 0;
&motion;
}
# Отмена выделения с фиксированным соотношением сторон
elsif($event->hardware_keycode == key_n2c($config->{"shortcuts"}->{"fixed ratio"})) {
$fixedratio = 0;
&motion;
}
# Копирование значений в буфер обмена
elsif($event->hardware_keycode == key_n2c($config->{"shortcuts"}->{"copy values"})
&& ($creating || defined $idx{g} || defined $idx{m})) {
my $S = $creating ? \%w : (defined $idx{$type} ? ${$pack{$type}}[$idx{$type}] : ($type eq "m" ? $g[$idx{g}] : $m[$idx{m}]));
if($$S{type} eq "m") {$cb->set_text("⤢ $$S{w} ⨯ $$S{h} | 𝄩 $$S{lr} | ⦟ $$S{ar}° | ⇱ ($$S{x0}, $$S{y0}) | ⇲ ($$S{x1}, $$S{y1})");}
elsif($$S{type} eq "g") {$cb->set_text("🞡 ($$S{x}, $$S{y}) | ⦟ $$S{ar}°");}
}
# Выход
elsif($event->hardware_keycode == key_n2c($config->{"shortcuts"}->{"quit"})) {
exit 0;
}
# Заморозка
elsif($event->hardware_keycode == key_n2c($config->{"shortcuts"}->{"freeze"})) {
&freeze() if $creating;
$widget->queue_draw;
}
# Горизонтальная направляющая
elsif($event->hardware_keycode == key_n2c($config->{"shortcuts"}->{"guide horizontal"})) {
&guide(0);
$widget->queue_draw;
}
# Вертикальная направляющая
elsif($event->hardware_keycode == key_n2c($config->{"shortcuts"}->{"guide vertical"})) {
&guide(PI / 2);
$widget->queue_draw;
}
# Направляющие: показывать, прилипать
elsif($event->hardware_keycode == key_n2c($config->{"shortcuts"}->{"snap to guides"})) {
if($event->state & "mod1-mask" && $event->state & "control-mask" && $event->state & "shift-mask") {
foreach my $g (@g) {
undef @g; $idx{"g"} = undef;
}
$widget->queue_draw;
} elsif($event->state & "control-mask" && $event->state & "shift-mask") {
$gstate{snap} = $gstate{snap} ? 0 : $config->{"guides"}->{"snap distance"};
} elsif($event->state & "shift-mask") {
$gstate{show} = $gstate{show} ? 0 : 1;
foreach my $g (@g) {
if($gstate{show}) {&draw_g($g);} else {&clear($g->{context});}
}
&highlight("g", $idx{"g"}, $widget) if $gstate{show};
$widget->queue_draw;
}
}
# Удаление
elsif($event->hardware_keycode == key_n2c($config->{"shortcuts"}->{"clear"})) {
if(defined $idx{$type}) {
delete($all{${splice(@{$pack{$type}}, $idx{$type}, 1)}{id}});
$idx{$type} = undef;
}
$widget->queue_draw;
}
# Удаление последнего
elsif($event->hardware_keycode == key_n2c($config->{"shortcuts"}->{"clear last"})) {
if(keys %all) {
my $x = delete($all{(sort keys %all)[-1]});
pop @{$pack{$$x->{type}}};
if(defined $idx{$$x->{type}} && $idx{$$x->{type}} > (@{$pack{$$x->{type}}} - 1)) {
$idx{$$x->{type}} = undef;
}
$widget->queue_draw;
}
}
# Удаление всех
elsif($event->hardware_keycode == key_n2c($config->{"shortcuts"}->{"clear all"})) {
undef @{$pack{$type}};
$idx{$type} = undef;
$widget->queue_draw;
}
# Скриншот
elsif($event->hardware_keycode == key_n2c($config->{"shortcuts"}->{"screenshot"})
&& ($creating || defined $idx{m})) {
$sf = 1;
&clear();
Glib::Timeout->add(100, sub {
&screenshot($creating ? \%w : $m[$idx{m}]);
$sf = 0;
$widget->queue_draw;
});
}
# Основной отрезок
elsif($event->hardware_keycode == key_n2c($config->{"shortcuts"}->{"segment line"})) {
my $S = !$creating && defined $idx{m} ? $m[$idx{m}] : \%w;
$$S{sline} = !$$S{sline};
&motion;
}
# Вспомогательные линии
elsif($event->hardware_keycode == key_n2c($config->{"shortcuts"}->{"only mask"})) {
my $S = !$creating && defined $idx{m} ? $m[$idx{m}] : \%w;
$$S{onlymask} = !$$S{onlymask};
&motion;
}
else {
# Направляющие выделения
for my $key (keys %{$config->{"selection guides"}}) {
if($event->hardware_keycode == key_n2c($key)) {
my $S = !$creating && defined $idx{m} ? $m[$idx{m}] : \%w;
$$S{marks}{$key}{show} = !$$S{marks}{$key}{show};
&motion;
last;
}
}
# Цветовые схемы
while(my ($key, $value) = each %{$config->{"colors shortcuts"}}) {
if($event->hardware_keycode == key_n2c($value)) {
my $S = defined $idx{$type} ? ${$pack{$type}}[$idx{$type}] : \%w;
$$S{color} = [&set_colors($key)]->[2] if $type eq "g";
$$S{colors} = [&set_colors($key)] if $type eq "m";
&motion;
}
}
# Соотношение сторон
while(my ($key, $value) = each %{$config->{"aspect ratio shortcuts"}}) {
if($event->hardware_keycode == key_n2c($value)) {
$arm = eval($key);
&motion;
}
}
}
}
exit 0;