如何使用Ruby Gtk实现图片缩放与拖拽选框功能?
Hey there! Great news—all the features you're asking for (zoom, drag scrolling, and drag selection) are absolutely achievable with Ruby-GTK. The trick is to use a custom Gtk::DrawingArea where you can handle low-level drawing and mouse events yourself.
Below is a complete, working example that implements everything you described, plus some extra quality-of-life features. I'll break down how each part works so you can adapt it to your existing code.
Ruby-GTK Image Viewer with Zoom, Drag Scrolling, and Selection
Full Working Example
require 'gtk3' class ImageViewer < Gtk::DrawingArea def initialize(image_path) super() @image = Gdk::Pixbuf.new(file: image_path) @scale = 1.0 @offset_x = 0.0 @offset_y = 0.0 @dragging = false @drag_start_x = 0 @drag_start_y = 0 @selection_start = nil @selection_end = nil @selecting = false # Enable required mouse events set_events(Gdk::EventMask::BUTTON_PRESS_MASK | Gdk::EventMask::BUTTON_RELEASE_MASK | Gdk::EventMask::POINTER_MOTION_MASK | Gdk::EventMask::SCROLL_MASK) # Connect event handlers signal_connect('button-press-event') { |_, event| on_button_press(event) } signal_connect('button-release-event') { |_, event| on_button_release(event) } signal_connect('motion-notify-event') { |_, event| on_motion(event) } signal_connect('scroll-event') { |_, event| on_scroll(event) } signal_connect('draw') { |_, cr| on_draw(cr) } end # Handle mouse button presses def on_button_press(event) if event.button == 1 # Left click: start selection @selecting = true @selection_start = [event.x, event.y] @selection_end = nil elsif event.button == 2 # Middle click: start panning @dragging = true @drag_start_x = event.x @drag_start_y = event.y end true # Indicate we handled the event end # Handle mouse button releases def on_button_release(event) if event.button == 1 && @selecting @selecting = false # Convert selection coordinates to original image space if @selection_end x1, y1 = @selection_start x2, y2 = @selection_end img_x1 = (x1 - @offset_x)/@scale img_y1 = (y1 - @offset_y)/@scale img_x2 = (x2 - @offset_x)/@scale img_y2 = (y2 - @offset_y)/@scale puts "Selected area: (#{img_x1.round}, #{img_y1.round}) to (#{img_x2.round}, #{img_y2.round})" end elsif event.button == 2 && @dragging @dragging = false end queue_draw # Refresh the view true end # Handle mouse motion def on_motion(event) if @dragging # Update pan offset based on mouse movement delta_x = event.x - @drag_start_x delta_y = event.y - @drag_start_y @offset_x += delta_x @offset_y += delta_y @drag_start_x = event.x @drag_start_y = event.y queue_draw elsif @selecting @selection_end = [event.x, event.y] queue_draw end true end # Handle scroll wheel for zoom def on_scroll(event) # Zoom in/out around the mouse cursor zoom_factor = event.direction == Gdk::ScrollDirection::UP ? 1.1 : 0.9 mouse_x = event.x mouse_y = event.y # Calculate image position under mouse before zoom img_x = (mouse_x - @offset_x) / @scale img_y = (mouse_y - @offset_y) / @scale # Update scale @scale *= zoom_factor # Adjust offset to keep mouse over the same image point @offset_x = mouse_x - (img_x * @scale) @offset_y = mouse_y - (img_y * @scale) queue_draw true end # Custom drawing logic def on_draw(cr) # Clear background cr.set_source_rgb(1, 1, 1) cr.paint # Draw scaled image at current offset cr.save cr.translate(@offset_x, @offset_y) cr.scale(@scale, @scale) Gdk::Cairo.set_source_pixbuf(cr, @image, 0, 0) cr.paint cr.restore # Draw selection rectangle if active if @selecting && @selection_end x1, y1 = @selection_start x2, y2 = @selection_end # Semi-transparent blue fill cr.set_source_rgba(0, 0, 1, 0.3) cr.rectangle([x1, x2].min, [y1, y2].min, (x2 - x1).abs, (y2 - y1).abs) cr.fill # Solid blue border cr.set_source_rgb(0, 0, 1) cr.rectangle([x1, x2].min, [y1, y2].min, (x2 - x1).abs, (y2 - y1).abs) cr.stroke end true end # Reset view to centered, 100% scale def reset_view @scale = 1.0 @offset_x = (allocated_width - @image.width)/2.0 @offset_y = (allocated_height - @image.height)/2.0 queue_draw end end # Main application setup if __FILE__ == $0 Gtk.init window = Gtk::Window.new(title: 'Custom Image Viewer') window.set_default_size(800, 600) window.signal_connect('destroy') { Gtk.main_quit } # Replace with your image path viewer = ImageViewer.new('path/to/your/image.png') # Add reset button reset_btn = Gtk::Button.new(label: 'Reset View') reset_btn.signal_connect('clicked') { viewer.reset_view } # Layout setup vbox = Gtk::Box.new(:vertical, 5) vbox.pack_start(viewer, expand: true, fill: true, padding: 0) vbox.pack_start(reset_btn, expand: false, fill: true, padding: 5) window.add(vbox) window.show_all # Reset view when window is resized window.signal_connect('configure-event') { viewer.reset_view unless viewer.instance_variable_get(:@dragging) } Gtk.main end
Key Features Breakdown
1. Zoom Functionality
- Uses the scroll wheel to zoom in/out around the mouse cursor (so the point under your mouse stays in place)
- Adjusts the
@scalevariable and recalculates the offset to maintain the correct position - Handled in the
on_scrollmethod
2. Drag Scrolling
- Middle-click and drag to pan the image across the viewport
- Tracks mouse movement delta during drag to update the image's offset
- Handled in the mouse press, motion, and release events for the middle button
3. Drag Selection
- Left-click and drag to draw a semi-transparent blue selection rectangle
- Converts on-screen coordinates to the original image's coordinate system (accounting for scale and offset) when you release the mouse
- You can extend this to perform actions like cropping on the selected area
4. Quality-of-Life Extras
- Reset button to center the image and return to 100% scale
- Automatic view reset when the window is resized (unless you're actively dragging)
Tips for Extending
- Add Zoom Buttons: Add "+" and "-" buttons that call a method to adjust
@scaleand refresh the view - Image Refresh: Integrate your existing refresh logic by updating the
@imagevariable and callingqueue_draw - Crop Functionality: Use the selected coordinates to create a new
Gdk::Pixbuffrom the original image - Smooth Zoom: Add animation for zoom transitions using a timer to incrementally adjust the scale
Notes
- Make sure to replace
'path/to/your/image.png'with the actual path to your image file - This example uses GTK3, so you'll need the
ruby-gtk3gem installed (gem install gtk3) - All event handlers return
trueto indicate we've handled the event (prevents default GTK behavior)
Content of the question来源于Stack Exchange, 提问作者ArafatK
相关产品推荐
相关产品推荐

