rocklee has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks,

I recently began experimenting with Tk::Zinc and POE in order to, hopefully, build a nice video player frontend (see 717982). After a smooth Linux installation, I am now up and running and have come across some "real" problems.

  1. Is there a Tk::Zinc(::Graphics) way of building multiple items with similar characteristics? (and how good is my hacked solution)
  2. Is there a reliable way to position text at center of group?
  3. Is there a way to correctly calulate delta x/y if scaling is active?
  4. Did I missing something when it comes to calculating positions of items relative to display settings?

Code snippets do not work in isolation, use the complete program. Please note that this is my first-ever POE and first-ever Zinc program, two complex technologies. It may be a bit messy (but I dare to say it demonstrates a basic multi-workspace concept cleaner than the rather cryptic testGraphics.pl example in the Zinc demos)

Read on for detailed questions, or if you want to give my multi-workspace Tk::Zinc application skeleton a go!

1. Is there a way..

I construct buttons with similar characteristics and only a few varying options. I could not find a way in Tk::Zinc(::Graphics) to do this, so I devised the following method:

sub ButtonFactory { my ($custom_options, $shape, $text) = @_; $$custom_options{-items}{shape} = $shape; $$custom_options{-items}{text} = $text; return %{ merge( \%button_skeleton, $custom_options) }; } # merge is Hash::Merge
So I can do stuff like
my %BUTTON = ButtonFactory(..); for (1..20) { $BUTTON{option} = 'value'; buildZincItem(.., .., $BUTTON); }

Am I missing something here? Is there a better way to do this? And if not, exactly how poor is the method I devised, and is there any obvious slow/bad code in it?

See subs draw_workspace_select and draw_interface

2. Centering text within button

I need to create an atomic group with a roundedrectangle and a text item. The text item should be centered on the roundedrectangle. Horizontal alignment is ok, but I cannot find a reliable way of positioning it vertically.

# $BW = Button Width # $BH = Button Height # $aspect = Aspect ratio of sceen ie 1.666 my %BUTTON = ButtonFactory( { }, { -coords => [[-(int $BW/2), 0], [(int $BW/2), $BH]], }, { -coords => [ 0, int ($BH * $aspect ) / 5.5 ], -params => { -font => $H->{bigfont}, } }, );

This absolute hack seems to work for my two monitors on my two resoutions (1920x1200 and 1280x1024) with the given button sizes. But obviously it is retarded - how can I do this reliably for any given 'container' size?

3. Delta x/y troubles

I tag some items with 'movable', which in turn has some mouse bindings that allow me to drag them around. This works perfectly, except if there is a scale translation active. In this case delta x/y does not take into account the scale factor, and the item is moved faster/slower than the mouse pointer instead of 1:1. I have tried various methods to compensate for this, but none work as expected.

To reproduce, start program, click 'add media', drag resulting item in workspace. Change scale factor (+/-) and try again. (see sub move_item_motion).

4. Did I miss something..?

I feel like I'm doing extra work maintaining information about the screen size and aspect and how to create items at the correct scale, and position them. (see sub interface_math)

Am I completely overlooking some feature set that allows me to do these things more accurately/cleaner?

Concluding remarks

I'd be very interested in all kinds of feedback regarding my POE/Zinc solution here. It is my first attempt, and I have already refactored it a number of times as I learn new stuff. Are there any obvious features of POE or Zinc that would have eased my implementation that I have yet to discover?

Looking forward to your input..

PS: Only tested on my Linux system. Requires Tk, POE, Tk::Zinc, Tk::Zinc::Graphics and Hash::Merge

Overview of bindings in below program

650 lines of code follow

#!/usr/bin/perl use strict; use warnings; use Tk; use POE; use Tk::Zinc; use Tk::Zinc::Graphics; use Hash::Merge qw{ merge }; # Only one line has been changed from default RIGHT_PRECEDENT setting, + # avoid duplicates with array parameters such as -coords=>[], overwrit +e them. Hash::Merge::specify_behavior( { SCALAR => { SCALAR => sub { $_[1] }, ARRAY => sub { [ $_[0], @{$_[1]} ] }, HASH => sub { $_[1] } }, ARRAY => { SCALAR => sub { $_[1] }, ARRAY => sub { $_[1] }, # <-- This one! HASH => sub { $_[1] } }, HASH => { SCALAR => sub { $_[1] }, ARRAY => sub { [ values %{$_[0]}, @{$_[1]} ] }, HASH => sub { Hash::Merge::_merge_hashes( $_[0], $_[1] ) } } }, "SkeletonMerger" ); my %button_skeleton = ( -itemtype => 'group', -atomic => 1, -sensitive => 1, -coords => [50, 500], -items => { 'shape' => { -itemtype => 'roundedrectangle', -params => { -closed => 1, -filled => 1, -fillcolor => 'button', -linewidth => 1, -linecolor => '#ffffff', -priority => 10, } }, 'text' => { -itemtype => 'text', -params => { -color => '#ffffff', -anchor => 'n', -alignment => 'center', -priority => 20, } } } ); POE::Session->create( inline_states => { # Initialize the application GUI, these are all called in one long c +hain _start => \&zinc_init, interface_math => \&interface_math, init_workspaces => \&init_workspaces, draw_decoration => \&draw_decoration, draw_workspace_select => \&draw_workspace_select, draw_interface => \&draw_interface, set_bindings => \&set_bindings, set_active_workspace => \&set_active_workspace, # Button postbacks from Tk buttonclick_workspace => \&buttonclick_workspace, buttonclick_add_media => \&buttonclick_add_media, buttonclick_add_anchor => \&buttonclick_add_anchor, buttonclick_quit => \&buttonclick_quit, # GUI events translate_view => \&translate_view, zoom_view => \&zoom_view, mouse_pan_view => \&mouse_pan_view, # Item is being dragged in workspace select_item => \&select_item, move_item_start => \&move_item_start, move_item_stop => \&move_item_stop, move_item_motion => \&move_item_motion, }, ); $poe_kernel -> run(); exit 0; # zinc_init: POE state # Initialize a Zinc widget on the $poe_main_window, create a # few toplevel (Zinc) groups and then enters fullscreen. sub zinc_init { my ($K, $H) = @_[ KERNEL, HEAP ]; my %gradients = ( 'menu_background'=> '=path 48 48|#e7ffe7;20 0 70|#007900;20', 'button_red' => '=axial 250|#ff0000;20|#660000;30', 'button_yellow' => '=axial 250|#aeb320;20|#cfb316;30', 'button' => '=axial 250|#aaaaaa;50|#a8a8a8;30', 'button_workspace_active' => '=axial 250|#aeb333;50|#cfb316;30', 'button_workspace' => '=axial 250|#aaaaaa;50|#a8a8a8;30', 'anchor' => '=axial 220|#d100c5;50|#a221f1;30', ); $H->{SX} = $poe_main_window -> screenwidth; $H->{SY} = $poe_main_window -> screenheight; $H->{zinc} = $poe_main_window -> Zinc( -render => 1, -width => $H->{SX}, -height => $H->{SY}, -borderwidth=> 0, -backcolor => '#222222' ) -> pack; $K->signal_ui_destroy($H->{zinc}); die "OpenGL required.\n" if ($H->{zinc}->cget(-render) < 1); &setGradients($H->{zinc}, \%gradients); $H->{_decoration_group} = $H->{zinc} -> add( 'group', 1, -atomic => 1, -sensitive => 0, -tags => ['decoration'] ); $H->{_button_group} = $H->{zinc} -> add( 'group', 1, -atomic => 0, -sensitive => 1, -priority => 10, -tags => ['ifacebutton'] ); $H->{_workspace_group} = $H->{zinc} -> add( 'group', 1, -atomic => 0, -sensitive => 1, -priority => 20, -tags => ['ifacebutton'] ); $poe_main_window -> focusForce; $poe_main_window -> FullScreen(1); $poe_main_window -> overrideredirect(0); $poe_main_window -> resizable(0,0); $K -> yield('interface_math'); } # interface_math: POE state # Calculate positions for elements of the GUI (as this varies from com +puter # to computer. This has NOT been extensively tested and probably conta +ins # a huge amount of bugs and/or unknown side-effects. sub interface_math { my ($K, $S, $H) = @_[ KERNEL, SESSION, HEAP ]; my $aspect = $H->{SX} / $H->{SY}; my $widescreen = $aspect >= 1.5 ? 1 : 0; my $menuheight = int $H->{SX}/5; my $buttonwidth = 170; my $buttonheight = 30; if ($widescreen) { $menuheight = int $menuheight * 0.8; $buttonwidth = int $buttonwidth * 1.2; $buttonheight= int $buttonheight* 1.3; } if ($H->{SX} >= 1400) { $menuheight = int $menuheight * 1.2; $buttonwidth = int $buttonwidth * 1.2; $buttonheight = int $buttonheight * 1.2; } my @fontsize = (70, 95, 120); @fontsize = (90, 110, 130) if ($H->{SX} >= 1024); @fontsize = (100, 120, 140) if ($H->{SX} >= 1280); @fontsize = (110, 150, 200) if ($H->{SX} >= 1600); my $basefont = '-adobe-arial-bold-r-normal--*-%d-*-*-*-*-*-*'; $H->{smallfont} = sprintf($basefont, $fontsize[0]); $H->{font} = sprintf($basefont, $fontsize[1]); $H->{bigfont} = sprintf($basefont, $fontsize[2]); # Store results on the heap $H->{iface} = { 'aspect' => $aspect, 'menuheight' => $menuheight, 'buttonwidth' => $buttonwidth, 'buttonheight' => $buttonheight, 'spacing' => 5, # don't go crazy 'step_size' => 10, # translation }; $K -> yield('init_workspaces'); } # init_workspaces: POE state # Initialize workspaces, that is to say, the necessary Zinc groups to +hold # the workspace area, as well as define clipping area for each workspa +ce. sub init_workspaces { my ($K, $S, $H) = @_[ KERNEL, SESSION, HEAP ]; # Shortcuts to interface calculations, at expense of clarity. my $SP = $H->{iface}->{spacing}; my $MH = $H->{iface}->{menuheight}; for (1..20) { # Add the toplevel 'page' group $H->{_ws}{$_}{page} = $H->{zinc}->add( 'group', $H->{_workspace_group}, -atomic => 0, -sensitive => 1, -tags => ['WORKSPACE', $_, 'page'] ); # Create clipping area $H->{_ws}{$_}{clip} = $H->{zinc} -> add( 'rectangle', $H->{_ws}{$_}{page}, [[ $SP, $MH+$SP ], [ $H->{SX}-$SP, $H->{SY}-$SP]], -linewidth => 1, -linecolor => '#000000', -filled => 1, -fillcolor => '#000000;2', -visible => 0, -tags => ['WORKSPACE', $_, 'clip'], ); # Add it to the page group $H->{zinc}->itemconfigure( $H->{_ws}{$_}{page}, -clip => $H->{_ws}{$_}{clip}, ); # And finally create the content group $H->{_ws}{$_}{content} = $H->{zinc}->add( 'group', $H->{_ws}{$_}{page}, -visible => 0, -tags => ['WORKSPACE', $_, 'content'], ); # TEST - populate worksace $H->{zinc}->add('text', $H->{_ws}{$_}{content}, -position => [20, $H->{iface}{menuheight}+20], -font => $H->{bigfont}, -text => "This is workspace number: $_", -color => '#ffffff', -tags => ['WORKSPACE', $_, 'item'], ); } # 1..20 $K -> yield('draw_decoration'); } # draw_decoration: POE state # Draws static interface decoration (on init) sub draw_decoration { my ($K, $S, $H) = @_[ KERNEL, SESSION, HEAP ]; my $MH = $H->{iface}->{menuheight}; &buildZincItem($H->{zinc}, $H->{_decoration_group}, -itemtype => 'roundedrectangle', -coords => [ [0, 0], [$H->{SX}, $MH] ], -trunc => 'right', -params => { -closed => 1, -filled => 1, -fillcolor => 'menu_background', -linewidth => 1, -linecolor => '#ffffff', -priority => 10, -sensitive => 0, }); $K->yield('draw_workspace_select'); } # draw_workspace_select: POE state # Draws a matrix of 4x5 buttons that select active workspace sub draw_workspace_select { my ($K, $S, $H) = @_[ KERNEL, SESSION, HEAP ]; my $SP = $H->{iface}->{spacing}; my $BH = ($H->{iface}->{menuheight} - 8*$SP) / 5; my $BW = $BH * 1.5 ; my %BUTTON = ButtonFactory( { }, { -coords => [[-(int $BW/2), 0], [(int $BW/2), $BH]], }, { -coords => [ 0, int ($BH * $H->{iface}->{aspect} ) / 5.5 ], -params => { -font => $H->{bigfont}, } }, ); my $row = 0; # Construct a matrix of 20 %BUTTONs, with changing options... for (1..20) { $BUTTON{-coords} = [ int ( $BW/2 + $SP*3 )+$row*($BW+$SP), int (($_ - 1)-($row*5))*($SP+$BH)+$SP*2 ]; $BUTTON{-items}{shape}{-params}{-fillcolor} = 'button_workspace'; $BUTTON{-items}{shape}{-params}{-tags} = ['BTNSET_WS', $_, 'shape' +]; $BUTTON{-items}{text} {-params}{-tags} = ['BTNSET_WS', $_, 'text' +]; $BUTTON{-items}{text} {-params}{-text} = $_; &buildZincItem($H->{zinc}, $H->{_button_group}, %BUTTON); $row++ if ($_ % 5 == 0); } $H->{iface}{selectorwidth} = $SP*3 + ( ($BW+$SP)*4 ); $K->yield('draw_interface'); } # draw_interface: POE state # Draws elements on the GUI sub draw_interface { my ($K, $S, $H) = @_[ KERNEL, SESSION, HEAP ]; # Shortcuts to interface calculations, at expense of clarity. my $BW = $H->{iface}->{buttonwidth}; my $SP = $H->{iface}->{spacing}; my $SW = $H->{iface}->{selectorwidth}; # Ad-hoc data structure with only the options we need. my @vertical_buttons = ( ['Add Media', 'button_yellow', 'BTN_ADD_MEDIA' ], ['Add Anchor', 'button_yellow', 'BTN_ADD_ANCHOR'], ['Test', 'button_red', 'VOID'], ['Test', 'button_red', 'VOID'], ['Test', 'button_red', 'VOID'], ['Exit', 'button_yellow', 'BTNQUIT'], ); my $BH = ($H->{iface}{menuheight} - 9*$SP) / 6; # Send our custom options to the magic sub.. my %BUTTON = ButtonFactory( { }, { -coords => [[-(int $BW/2), 0], [(int $BW/2), $BH]], }, { -coords => [ 0, int ($BH * $H->{iface}->{aspect} ) / 5.5 ], -params => { -font => $H->{bigfont}, } }, ); my $i=0; # Vertical buttons foreach (@vertical_buttons) { my ($text, $gradient, $tag) = @{$_}; $BUTTON{-coords} = [ ($SW + int $BW/2)+$SP*3, $i*($SP+$BH)+$SP*2 ] +; $BUTTON{-items}{shape}{-params}{-fillcolor} = $gradient; $BUTTON{-items}{shape}{-params}{-tags} = [$tag, $_, 'shape']; $BUTTON{-items}{text} {-params}{-tags} = [$tag, $_, 'text' ]; $BUTTON{-items}{text} {-params}{-text} = $text; &buildZincItem($H->{zinc}, $H->{_button_group}, %BUTTON); $i++; } $K->yield('set_bindings'); } sub set_bindings { my ($K, $S, $H) = @_[ KERNEL, SESSION, HEAP ]; # Interface buttons, each with its own POE state $H->{zinc}->bind('BTNQUIT', '<1>', $S->postback('buttonclick_ +quit') ); $H->{zinc}->bind('BTN_ADD_MEDIA','<1>', $S->postback('buttonclick_ad +d_media')); $H->{zinc}->bind('BTN_ADD_ANCHOR','<1>', $S->postback('buttonclick_a +dd_anchor')); $H->{zinc}->bind('BTNSET_WS', '<1>', $S->postback('buttonclick +_workspace')); # Zoom controls $poe_main_window -> bind('<plus>', $S->postback('zoom_v +iew' => 'in' ) ); $poe_main_window -> bind('<minus>', $S->postback('zoom_ +view' => 'out' ) ); $poe_main_window -> bind('<Control-Key-plus>', $S->postback('zoom_v +iew' => 'long_in' ) ); $poe_main_window -> bind('<Control-Key-minus>', $S->postback('zoom_v +iew' => 'long_out' ) ); # Pan (translation) controls $poe_main_window -> bind('<KeyPress-Up>', $S->postback('transl +ate_view' => 'up' ) ); $poe_main_window -> bind('<KeyPress-Down>', $S->postback('transl +ate_view' => 'down' ) ); $poe_main_window -> bind('<KeyPress-Left>', $S->postback('transl +ate_view' => 'left' ) ); $poe_main_window -> bind('<KeyPress-Right>', $S->postback('transl +ate_view' => 'right') ); $poe_main_window -> bind('<Control-KeyPress-Up>', $S->postback('t +ranslate_view' => 'long_up' ) ); $poe_main_window -> bind('<Control-KeyPress-Down>', $S->postback('t +ranslate_view' => 'long_down' ) ); $poe_main_window -> bind('<Control-KeyPress-Left>', $S->postback('t +ranslate_view' => 'long_left' ) ); $poe_main_window -> bind('<Control-KeyPress-Right>', $S->postback('t +ranslate_view' => 'long_right') ); # Mouse-pan (postback 'on' binds to B1-Motion, 'off' unbinds) $poe_main_window -> bind('<Key-space>', $S->postback('mouse_p +an_view' => 'on' ) ); $poe_main_window -> bind('<KeyRelease-space>', $S->postback('mouse_ +pan_view' => 'off' ) ); # Select item if Ctrl-clicked $poe_main_window -> bind('<Double-ButtonPress-1>', $S->postback('sel +ect_item') ); # Movable items $H->{zinc}->bind('movable', '<1>', $S->postback('move_item +_start' ) ); $H->{zinc}->bind('movable', '<B1-Motion>', $S->postback('move_i +tem_motion') ); $H->{zinc}->bind('movable', '<ButtonRelease-1>',$S->postback('move_i +tem_stop' ) ); # In case we lost focus, ALWAYS take it back.. $poe_main_window -> bind('<ButtonPress-1>', sub{$poe_main_window->f +ocusForce;}); $poe_main_window -> focusForce; # Activate workspace 1 $H->{zinc}->itemconfigure('(BTNSET_WS && shape && 1)', -fillcolor => + 'button_workspace_active'); $K->yield('set_active_workspace', 1); } # set_active_workspace: POE state # Lowers all workspaces and raises selection sub set_active_workspace { my ($H, $id) = @_[ HEAP, ARG0 ]; $H->{zinc}->itemconfigure('(WORKSPACE && content)', -visible => 0); $H->{zinc}->itemconfigure('(WORKSPACE && clip)', -visible => 0); $H->{zinc}->itemconfigure("(WORKSPACE && $id && clip)", -visible +=> 1); $H->{zinc}->itemconfigure("(WORKSPACE && $id && content)", -visible +=> 1); $H->{zinc}->lower("WORKSPACE"); $H->{zinc}->raise("(WORKSPACE && $id)"); $H->{_ws}{current} = $id; return; } # --------------------------------------------------------- # --- END OF INITIALIZATION CHAIN --- # --- EVERYTHING FROM HERE ON IS BASED ON GUI CALLBACKS --- # --------------------------------------------------------- # buttonclick_quit: POE postback from 'Quit' button sub buttonclick_quit { my ($K, $S, $H) = @_[ KERNEL, SESSION, HEAP ]; $poe_main_window -> destroy($H->{zinc}); return; } # buttonclick_add_media: POE postback from 'Add media' button # Should add a new 'Media' item to the workspace.. sub buttonclick_add_media { my ($H) = $_[ HEAP ]; my $current = $H->{_ws}{current}; my %BUTTON = ButtonFactory( { -coords => [ 500, 500 ], -params => { -tags => ['movable', 'WORKSPACE', $current, 'group'], } }, { -coords => [[ 0, 0 ], [100, 50]], -params => { -fillcolor => 'button' , -tags => ['movable', 'WORKSPACE', $current, 'item'], }, }, { -coords => [ 0,-5 ], -params => { -text=>'-MEDIA-',-font => $H->{bigfont}, -tags => ['selectable', 'movable', 'WORKSPACE', $current, 'it +em'], } }, ); &buildZincItem($H->{zinc},$H->{_ws}{$current}{content}, %BUTTON); } sub buttonclick_add_anchor { my ($H) = $_[ HEAP ]; my $current = $H->{_ws}{current}; my %BUTTON = ButtonFactory( { -coords => [ $H->{SX}/2, ($H->{SY}+ $H->{iface}{menuheight})/2], -params => { -tags => ['movable', 'anchor', 'WORKSPACE', $current, 'group +'], } }, { -coords => [[ 0, 0 ], [50, 50]], -params => { -fillcolor => 'anchor' , -tags => ['movable', 'anchor', 'WORKSPACE', $current ], }, }, { -coords => [ 0,-5 ], -params => { -text=>'-ANCHOR-',-font => $H->{bigfont}, -tags => ['movable', 'anchor', 'WORKSPACE', $current ], } }, ); &buildZincItem($H->{zinc},$H->{_ws}{$current}{content}, %BUTTON); } # buttonclick_workspace: POE postback from Workspace Selector # Activates corresponding button in the matrix and then raise # the workspace by yielding set_active_workspace sub buttonclick_workspace { my ($K, $H) = @_[ KERNEL, HEAP ]; my ($id) = grep (/^[0-9]+/, $H->{zinc}->itemcget('current', -tags) +); $H->{zinc}->itemconfigure('(BTNSET_WS && shape)', -fillcolor => 'but +ton_workspace'); $H->{zinc}->itemconfigure("(BTNSET_WS && shape && $id)", -fillcolor +=> 'button_workspace_active'); $K->yield('set_active_workspace', $id); } # translate_view: POE postback # Translates (pans) current workspace up/down/left/right sub translate_view { my ($H, $dir) = @_[ HEAP, ARG0 ]; $dir = shift @{$dir}; my $current = $H->{_ws}{current}; my $SZ = $H->{iface}{step_size}; my $dx = ($dir eq 'left') ? $SZ : ($dir eq 'right') ? -$SZ : 0; my $dy = ($dir eq 'up' ) ? $SZ : ($dir eq 'down' ) ? -$SZ : 0; $dx = ($dir eq 'long_left') ? $SZ*10 : ($dir eq 'long_right') ? - +($SZ*10) : $dx; $dy = ($dir eq 'long_up' ) ? $SZ*10 : ($dir eq 'long_down' ) ? - +($SZ*10) : $dy; $H->{zinc}->translate($H->{_ws}{$current}{content}, $dx, $dy); } # zoom_view: POE postback # Zooms current workspace in/long_in or out/long_out # FIXME: Breaks dx/dy on movable items sub zoom_view { my ($K, $H, $dir, $long) = @_[ KERNEL, HEAP, ARG0, ARG1 ]; $dir = shift @{$dir}; $long = shift @{$long}; my $current = $H->{_ws}{current}; my ($scale) = $H->{zinc}->tget($H->{_ws}{$current}{content}, 'scale' +); my $factor = ($dir eq 'long_in') ? 1.5 : ($dir eq 'long_out') ? 0.5 +: undef; $factor = ($dir eq 'in') ? 1.1 : ($dir eq 'out') ? 0.9 +: $factor; print "Zoom $dir - $scale - $factor\n"; $H->{zinc}->scale($H->{_ws}{$current}{content}, $factor, $factor); } # mouse_pan_view: POE postback, posted by Key-Space # Toggles whether B1-Motion pans current workspace (bound to key-space +) sub mouse_pan_view { my ($K, $H, $tog ) = @_[ KERNEL, HEAP, ARG0 ]; $tog = shift @{$tog}; # Toggle ON - bind anon sub to B1-Motion if ($tog eq 'on' and not $H->{_panning}) { $H->{_panning} = $poe_main_window->pointerxy; $poe_main_window->bind('<B1-Motion>', sub { my $current = $H->{_ws}->{current}; my $ev = $H->{zinc}->XEvent; $H->{zinc}->translate( $H->{_ws}{$current}{content}, ( $ev->x - $H->{_panning}[0] ), ( $ev->y - $H->{_panning}[1] ), ); $H->{_panning} = [$ev->x, $ev->y]; } ); # Space was released, turn panning off } elsif ($tog eq 'off' and defined($H->{_panning})) { $poe_main_window->bind('<B1-Motion>', undef); delete $H->{_panning}; } } # move_item_start: POE postback # Is bound to all items with 'movable' tag, star sub move_item_start { my ($H) = $_[HEAP]; # Do not allow moving item if Panning is in progress return if defined($H->{_panning}); my $current = $H->{_ws}{current}; my $ev = $H->{zinc}->XEvent; $H->{_ws}{$current}{_dragging} = [$ev->x, $ev->y]; } # move_item_motion: POE postback # <B1-Motion> on a movable object, handles repositioning # BUG: dx/dy breaks if workspace is zoomed!! sub move_item_motion { my ($H) = $_[HEAP]; my $current = $H->{_ws}{current}; return unless defined($H->{_ws}{$current}{_dragging}); my $ev = $H->{zinc}->XEvent; my ($dx, $dy) = @{ $H->{_ws}{$current}{_dragging} }; my $group = $H->{zinc}->group('current'); if (grep(/^anchor$/, $H->{zinc}->itemcget('current', -tags)) ) { $group = $H->{zinc}->group($group); } $H->{zinc}->translate($group, ($ev->x - ($dx)), ($ev->y - ($dy)), ); $H->{_ws}{$current}{_dragging} = [$ev->x, $ev->y]; } # move_item_stop: POE postback # B1-Motion on movable item is finished. sub move_item_stop { my ($H) = $_[HEAP]; my $current = $H->{_ws}{current}; delete $H->{_ws}{$current}{_dragging}; return; } sub select_item { my ($H) = $_[HEAP]; my $current = $H->{_ws}{current}; my $ev = $H->{zinc}->XEvent; #Stub } # Takes three (hash) arguments, the latter two are merged directly int +o the first, # forming an incomplete Tk::Zinc::Graphics tree. It is then merged wit +h # %button_skeleton, providing a complete widget tree with custom optio +ns. sub ButtonFactory { my ($custom_options, $shape, $text) = @_; $$custom_options{-items}{shape} = $shape; $$custom_options{-items}{text} = $text; return %{ merge( \%button_skeleton, $custom_options) }; }

Replies are listed 'Best First'.
Re: Total Newbies TkZinc questions
by zentara (Cardinal) on Oct 21, 2008 at 17:42 UTC
    Very nice looking GUI. I'm not much of an expert on POE, and usually just rely on Tk's event-loop.....but you may have your reasons.

    To your questions:

    1. Is there a Tk::Zinc(::Graphics) way of building multiple items with similar characteristics? (and how good is my hacked solution)

    Yes, look at the clone() method. Look in the zinc-demos, under User Contributed Demos, for TripleRotatingWheel. I make one wheel object, then clone it, then translate it into position. Like: my $arc2 = $zinc->clone($arc1);

    2.Is there a reliable way to position text at center of group?

    Here is a snippet to show you how. I also throw in a useful trick for reversing the y-axis direction, so coords are more like standard cartesian.

    3. Is there a way to correctly calulate delta x/y if scaling is active?

    Your dragging works fine by itself, and your scaling works fine by itself. Maybe just make them mutually exclusive, and pop a warning saying scaling is disabled during dragging? How often is someone going to want to do that anyways? I saw a similar problem in the Gnome2::Canvas and Goo::Canvas where I needed to do some clever multiplications/divisions to account for the current scale when saving to pdf. All I can say is I spent a few hours experimenting before I got it right. :-)

    4. Did I missing something when it comes to calculating positions of items relative to display settings?

    If you are referring to the drag/scale problem, what I think you will need to do is have 2 separate code blocks for calculating dx/dy. One if scaling is active, and 1 for simple dragging. Since your scaling factor can constantly be changing while the darg is occurring, I think it will mean alot of recalculations for each pixel of drag. Think about it....how can Zinc know what your dx/dy is, if the scale is changing while you drag. At best, it will be jerky( visually not codewise :-) ). I have 2 suggestions. One is to pop a warning, saying scaling is disabled during a drag. Or two, get rid of the +/- button press for scaling, and use a SpinBox instead, to set the scale. That way, the mouse can only be in 1 place at a time...either setting the scale-SpinBox or dragging an item.


    I'm not really a human, but I play one on earth Remember How Lucky You Are
      "Very nice looking GUI. [..]..but you may have your reasons."

      I appreciate that comment from a Chancellor, that must be like a Warlock or something ;-) And no, I really don't have my reasons, except it seemed cool, and this initial experiment confirms so.

      Thank you very much for the text positioning snippet, I wonder how many digits away I was ;-)

      Can't believe I didn't notice/realize what the clone() function was for *blush*. But there is a different feature of the ButtonFactory() approach - it allows me to isolate a number of arbitrary-depth group/item definitions (i.e. Tk::Zinc::Graphics).

      So, does spawning the initial item from a ButtonFactory() and then cloning make any sense? What is the Warlock approach to isolating item templates? - Subclassing, or?

      "[..] pop a warning saying scaling is disabled during dragging? How often is someone going to want to do that anyways?"

      Considering I will most likely be the only user of this program ever, it is clearly not that much of an issue. Nevertheless, I want to get it right. I can easily imagine doing different tasks on different scale levels, say, zooming out and selecting some items to drag them to the other other side of the workspace, then zoom back in over that area to fine-tune alignment etc. If the program was usable, I would do this all the time.

      In mostly any visual GUI sense that I can think of, retaining 1:1 relation between the user's input and visual translation makes the most sense. I expect this to be possible by somehow transforming the coordinates between the two systems, the question is how!;-) (this "feature" seems to be present in the Zinc demos that have both scale and drag, by the way)

      "[..] All I can say is I spent a few hours experimenting before I got it right. :-)"

      In that case, I think I'll leave it for the next Big Rewrite ;-)

      "If you are referring to the drag/scale problem, .."
      No, actually I was not. Let me clarify. In the interface_math sub, I perform some calculations based on totally random factors;
      • Menu area's height is set to 1/5 of screen height, which would easily break if the user has a nonstandard resolution.
      • Four lines defining (x,y,z) font sizes for different resolutions, taken from thin air.
      • if (widescreen) and if (display_x_size > 1400).

      The point is, this code can break under a huge number of circumstances. My question is if there exists a framework or standardised method for making these interface related decisions?

      "[..] if the scale is changing while you drag [..]"

      I think you may have missed the point here; The problem is not while you zoom, it's that, once you have zoomed, to any level but the startup factor 1, the dx/dy is borked for the application lifetime. It gets worse the further you are from scale 1, so hit + or - a few times and then move items to see the bug at work.

      Apologies for the comment on code clarity in the demos, I was referring to the testGraphics.pl demonstrating tabBox in particular..No spells, please! Will update the main post.

      Thanks for your valuable input!

        ".. if the scale is changing while you drag .."

        I think you may have missed the point here; The problem is not while you zoom, it's that, once you have zoomed, to any level but the startup factor 1, the dx/dy is borked for the application lifetime. It gets worse the further you are from scale 1, so hit + or - a few times and then move items to see the bug at work.

        I don't see any bug on my system, I'm using PDL-2.4.3 on linux. I can scale up, and scale down repeatedly, and the mouse drag works just fine. The only problem I see is the draggable item may go off screen during a scale procedure, and then dragging is prevented. If that is the bug you are referring to, make a Scrolled Zinc window in your workspaces. If it isn't your bug, are you on Win32 ? Or what version platform are you on?

        Also remember scaling is done by default with respect to the (0,0) point of the group, so you either need to put your draggable item in it's own group and center it, or specify the center-of-zoom when scaling.


        I'm not really a human, but I play one on earth Remember How Lucky You Are

      Answering my own questions, indeed it took a few hours to figure out ;-) The dragging issue is solved by the zinc->transform() command, like magic! Here is some pseudocode in case someone else ponders this, I suppose it's obvious to the gurus.. :-\

      # -- PSEUDOCODE, DOES NOT WORK -- my ($dx, $dy); sub mobile_start { my $ev = $zinc->XEvent; ($dx, $dy) = $zinc->transform(1, $scaled_group, [$ev->x, $ev->y]); } sub mobile_move { my $ev = $zinc->XEvent; my ($tr_evx, $tr_evy) = $zinc->transform(1, $scaled_group, [$ev->x, +$ev->y]); $zinc->translate($some_group, $tr_evx-$dx, $tr_evy-$dy); ($dx, $dy) = ($tr_evx, $tr_evy); }
        Since "I" didn't see your bug before your transform addition, I suspect I will see it after you modify the code. :-) Maybe update your original code, and let me try again. There may be some hidden bug related to versions and platforms, but if you will be the only user, it really dosn't matter.......do whatever works for you.

        I'm not really a human, but I play one on earth Remember How Lucky You Are