Scrolling
In many applications, you'll want the canvas to be larger than what appears on
the screen. You can attach horizontal and vertical scrollbars to the canvas in
the usual way via the xview
and yview
methods.
You can specify both how large you'd like it to be on screen and its full size
(which would require scrolling to see). The width
and height
configuration
options control how much space the canvas widget requests from the geometry
manager. The scrollregion
configuration option tells Tk how large the canvas
surface is by specifying its left, top, right, and bottom coordinates, e.g.,
0 0 1000 1000
.
You should be able to modify the sketchpad program to add scrolling, given what you already know. Give it a try.
Once you've done that, scroll the canvas down just a little bit, and then try drawing. You'll see that the line you're drawing appears above where the mouse is pointing! Surprised?
What's going on is that the global bind
command doesn't know that the canvas
is scrolled (it doesn't know the details of any particular widget). So if you've
scrolled the canvas down by 50 pixels, and you click on the top left corner,
bind will report that you've clicked at (0,0). But we know that because of the
scrolling, that position should really be (0,50).
The canvasx
and canvasy
methods translate the position onscreen (which bind
reports) into the actual point on the canvas (taking into account scrolling).
Be careful if you're adding
canvasx
andcanvasy
methods directly to the event binding scripts. You need to watch the quoting and substitutions to ensure the conversions are done when the event fires. As always, it's better to place that code in a procedure separate from the event binding itself.
Here then, is our complete example. We probably don't want the palette to be scrolled away when the canvas is scrolled, but we'll leave that for another day.
// cargo run --example canvas_scrolling use std::os::raw::c_double; use tcl::*; use tk::*; use tk::canvas::*; use tk::cmd::*; fn main() -> TkResult<()> { let tk = make_tk!()?; let root = tk.root(); let canvas = root .add_canvas( "canvas" -scrollregion("0 0 1000 1000") -yscrollcommand(".v set") -xscrollcommand(".h set") )? .grid( -sticky("nwes") -column(0i32) -row(0i32) )?; root.grid_columnconfigure( 0, -weight(1) )?; root.grid_rowconfigure( 0, -weight(1) )?; let _h = root .add_ttk_scrollbar( "h" -orient("horizontal") -command(".canvas xview") )? .grid( -column(0) -row(1) -sticky("we") )?; let _v = root .add_ttk_scrollbar( "v" -orient("vertical") -command(".canvas yview") )? .grid( -column(1) -row(0) -sticky("ns") )?; Widget::bind( &canvas, event::button_press_1(), "set lastx [.canvas canvasx %x]; set lasty [.canvas canvasy %y]" )?; Widget::bind( &canvas, event::button_1().motion(), tclosure!( tk, |evt_x:c_double, evt_y:c_double| -> TkResult<()> { let x = canvas.canvasx( evt_x, None )?; let y = canvas.canvasy( evt_y, None )?; let last_x = tk.get_double("lastx")?; let last_y = tk.get_double("lasty")?; let color = tk.get("color")?; canvas.dtag( item_tag( "all" ), Some( ItemTag( "paletteSelected".to_owned() )))?; canvas.itemconfigure( item_tag( "palette" ), -outline("white") )?; canvas.addtag( "paletteSelected", SearchSpec::WithTag( item_tag( &format!( "palette{}", color.clone().get_string() )).into() ))?; canvas.itemconfigure( item_tag( "paletteSelected" ), -outline("#999999") )?; canvas.create_line( &[ (last_x,last_y), (x,y) ], -fill(color) -width(5) -tags("currentline") )?; tk.set( "lastx", x ); tk.set( "lasty", y ); Ok(()) } ))?; Widget::bind( &canvas, event::button_1().button_release(), tclosure!( tk, || ->TkResult<()> { Ok( canvas.itemconfigure( item_tag( "currentline" ), -width(1) )? ) }) )?; let id = canvas.create_rectangle( 10.0, 10.0, 30.0, 30.0, -fill("red") -tags("palette palettered") )?; canvas.bind( id, event::button_press_1(), tclosure!( tk, || { tk.set( "color", "red" ); Ok(()) }))?; let id = canvas.create_rectangle( 10.0, 35.0, 30.0, 55.0, -fill("blue") -tags("palette paletteblue") )?; canvas.bind( id, event::button_press_1(), tclosure!( tk, || { tk.set( "color", "blue" ); Ok(()) }))?; let id = canvas.create_rectangle( 10.0, 60.0, 30.0, 80.0, -fill("black") -tags("palette paletteblack paletteSelected") )?; canvas.bind( id, event::button_press_1(), tclosure!( tk, || { tk.set( "color", "black" ); Ok(()) }))?; tk.set( "color", "black" ); canvas.itemconfigure( item_tag( "palette" ), -width(5) )?; Ok( main_loop() ) }