#This example creates a scrolling bitmap within another window. This example uses
#a hooked event to paint to the window directly, rather than using a Graphic
#Control.

use Win32::GUI;
use strict;

#create a new class which stops the WM_ERASEBKGND message from erasing the background
#this stops the flicker of the window on resize.
my $WC = new Win32::GUI::Class(
    -name => "NoFlicker", 
    -style => 0,
);

#Create the window and child controls.
my $mainwin = new Win32::GUI::Window (
    -pos         => [100, 100],
    -size        => [330, 235],
    -name        => "Window",
    -text        => "Bitmap Scroll demo",
    -pushstyle   => WS_CLIPCHILDREN,
    -class       => $WC,
    #NEM Events for this window
    -onResize    => \&MainResize,
    -onTerminate => sub {return -1;}
);

$mainwin->AddButton (
    -name        => 'Open',
    -pos         => [205, 20],
    -size        => [110, 20],
    -text        => 'Open Bitmap',
    -onClick     => \&FindAndOpenBitmap,
);

#Define global variables
my $memdc;
my $bitmap;        #will hold the bitmap

#Create a child window with a scroll bars. 
my $ChildWin = new Win32::GUI::Window (
    -parent      => $mainwin,
    -name        => "ChildWin",
    -pos         => [0, 0],
    -size        => [200, 200],
    -popstyle    => WS_CAPTION | WS_SIZEBOX,
    -pushstyle   => WS_CHILD | WS_CLIPCHILDREN,
    -pushexstyle => WS_EX_CLIENTEDGE,
    -class       => $WC,
    -hscroll     => 1,
    -vscroll     => 1,
    -onScroll    => \&Scroll,
    -onResize    => sub {&Resize($bitmap,@_)},
    -onPaint     => sub {&Paint($memdc,@_)},
);

#Create a memory DC compatible with the child window DC
$memdc=$ChildWin->GetDC->CreateCompatibleDC();

#show both windows and enter the Dialog phase.
$mainwin->Show();
$ChildWin->Show();
Win32::GUI::Dialog();

sub Paint {
  #Paint event handler, called when ever the window needs to be redrawn/painted
  #get the window that needs to be repainted, in this case it is the child window
  my $mdc=shift;
  my $win=shift;
  my $dc=shift;
  #Perform a bit block transfer of the memory DC into the window DC
  #The cordinates are based upon the position of the scroll bars
  $dc->BitBlt($win->GetClientRect,$mdc,$win->ScrollPos(0),$win->ScrollPos(1));
  $dc->Validate();
  return 0;
}

sub AdjScroll
{ my $bmap = shift;
  my $cwin = shift;
  # Set the scroll bar page of each scroll bar.
  # This has the effect of increasing/decreasing the size of the bar within
  # the scroll bar as the window is resized.
  if ( $bmap )
  { my ( $width, $height ) = $bmap->Info();
    my ( $cwid, $chei ) = ( $cwin->GetClientRect )[ 2 .. 3 ];
    my ( $sx, $sy ) = ( $cwin->ScrollPos( 0 ), $cwin->ScrollPos( 1 ));
    while ( 1 )
    { # the ScrollPage calls might trigger a Resize event, so this code might
      # run reentrantly.  To be sure we don't use old data later and overwrite
      # newer data, we reobtain the ClientRect after each operation that might
      # change it.
      $cwin->ScrollPage( 0, $cwid );
      my ( $cwidA, $cheiA ) = ( $cwin->GetClientRect )[ 2 .. 3 ];
      $cwin->ScrollPage( 1, $cheiA );
      my ( $cwid2, $chei2 ) = ( $cwin->GetClientRect )[ 2 .. 3 ];
      last if $cwid == $cwid2  &&  $chei == $chei2;
      ( $cwid, $chei ) = ( $cwid2, $chei2 );
    }
    if ( $sx != $cwin->ScrollPos( 0 )  ||  $sy != $cwin->ScrollPos( 1 ))
    { $cwin->InvalidateRect( 0 );
    }
  }
}

sub MainResize {
  my $win=shift;
  my ($width, $height) = ($win->GetClientRect)[2..3];
  $win->Open->Left($width-120);
  $win->ChildWin->Resize($width-150,$height);
  return 1;
}

sub Resize
{ # Resize handler, get the window
  AdjScroll( $_[ 0 ], $_[ 1 ] );
  return 1;
}

my ( %state );

sub OpenBitmap
{ my ( $bmap, $mdc, $cwin ) = @_;
  if ( $bmap )
  { # if we have a valid bitmap, get the dimensions
    my ( $width, $height ) = $bmap->Info();
    # select the bitmap into the memory DC so it can be manipulated later.
    $mdc->SelectObject( $bmap );
    # set the scroll bars to 0, unless same size as before
    if ( ! exists $state{ $cwin }
      ||  $state{ $cwin }[ 0 ] != $width
      ||  $state{ $cwin }[ 1 ] != $height )
    { $state{ $cwin } = [ $width, $height ];
      $cwin->ScrollRange( 0, 0, $width - 1 );
      $cwin->ScrollRange( 1, 0, $height - 1 );
      $cwin->ScrollPos( 0, 0 );
      $cwin->ScrollPos( 1, 0 );
    }
    AdjScroll( $bmap, $cwin );
    # invalidate the child window so windows triggers the paint event
    $cwin->InvalidateRect( 1 );
  }
  return 1;
}

sub FindAndOpenBitmap {
  #Function to load in the bitmap
  my $file = Win32::GUI::GetOpenFileName(
                   -owner => $mainwin,
                   -hidereadonly => 0,
                   -title  => "Open an bitmap file",
                   -filter => ['Bitmaps' => '*.bmp',
                               'All files'    => '*.*',
                              ],                
                   );
  $bitmap=new Win32::GUI::Bitmap($file);   
  if ($bitmap) {
    OpenBitmap($bitmap,$memdc,$ChildWin);
  }
  return 1;
}

sub Scroll {
  # Scroll event handler. We have to explicitly "move" the scroll bars.
  # Once they have been moved, we repaint the window.
  my( $win, $scrollbar, $operation, $position ) = @_;
  if ( $operation != SB_ENDSCROLL  &&  $operation != SB_THUMBPOSITION )
  { $win->Scroll( $scrollbar, $operation, -1, 1 );
    $win->InvalidateRect( 0 );
  }
  return 1;
}
