Hi wxRuby Gurus, I''m trying to write a sample program which allows me to drag an image (by left_mouse_button down) across the canvas. I got it to work ... well, sort of. The problem is I can drag it only by clicking on the left top corner of the image and nowhere else. Besides, the dragging is not very smooth. How can I make it better? I am using wxRuby 0.6.0 on Win XP (Home) and Ruby installed using the latest one-click Ruby installer. Code follows, image file (sk.png) attached. TIA, -- shanko ################# require ''wxruby'' class MyFrame < Wx::Frame def initialize(title) super(nil, -1, title,Wx::DEFAULT_POSITION, Wx::Size.new(250,250)) image = Wx::Image.new("sk.png") @bitmap = Wx::Bitmap.new(image) @left_dn = false evt_paint { on_paint } evt_motion {|event| on_motion(event) } evt_left_down {|event| @left_dn = true } evt_left_up {|event| @left_dn = false } end def move_region(nx,ny) ox,oy,w,h = @reg.get_box @reg.clear @reg = Wx::Region.new(nx,ny,nx+w,ny+h) dc = Wx::ClientDC.new(self) # Erase from the existing location # wb = Wx::WHITE_BRUSH dc.set_brush(wb) dc.set_pen(Wx::WHITE_PEN) dc.draw_rectangle(ox,oy,ox+w,oy+h) # Draw at the new location # dc.draw_bitmap(@bitmap,nx,ny,false) dc.free end def on_motion(event) x = event.get_x y = event.get_y move_region(x,y) if (@left_dn and (@reg.contains(x,y) == 0)) end def on_paint paint{|dc| dc.clear dc.draw_bitmap(@bitmap, 0, 0, false) } w,h = @bitmap.get_width, @bitmap.get_height @reg = Wx::Region.new(0,0,w,h) end end class ImagesApp < Wx::App def on_init frame = MyFrame.new("Simple Image Demo") frame.show end end Wx::init_all_image_handlers a = ImagesApp.new a.main_loop() __END__ _______________________________________________ wxruby-users mailing list wxruby-users@rubyforge.org http://rubyforge.org/mailman/listinfo/wxruby-users
Shashank Date wrote:> I''m trying to write a sample program which allows me to drag an image > (by left_mouse_button down) across the canvas.I''m not in a position where I can even run your code right now, but I can try to provide a few clues.> I got it to work ... well, sort of. The problem is I can drag it only by > clicking on the left top corner of the image and nowhere else.The test in on_motion looks odd. It seems that if the endpoint of the drag event is still within the border of the image, the drag is ignored. That means only drag events that start within the image but end outside will actually cause dragging. You might want to set @left_dn true ONLY if the down event started within the image. Then in on_motion if left_dn is true, do a move. At that point you might rename the variable to @dragging. Many programs that do this also capture the mouse. My mind is too fuzzy right now to remember if that is necessary for your purposes.> Besides, the dragging is not very smooth. How can I make it better?Drawing directly to the screen is always very slow. You will almost certainly want to use "double buffering", known in some graphics toolkits as a "canvas". That''s where you write to an off-screen bitmap, and then just "blit" (transfer) the bitmap to the screen. I don''t think wx has a built-in canvas object, and I don''t remember any wxRuby examples using this, so you may have to do it yourself. I think you can find an example in the Space Monkeys game I wrote a while back. The code hasn''t been updated, so it probably won''t work with a current version of wxRuby. But you can at least see the idea. The tarball can be downloaded from rubyforge: http://rubyforge.org/projects/monkeys/ Thinking from memory, create a Bitmap large enough for your work. When you want to update what appears on the screen (such as during a mouse drag), you create a MemoryDC, select the bitmap into the DC, and draw your own image into the DC. The paint handler simply blits from the memory DC into the paint DC. It would be nice if wxRuby had a Canvas class that did this for you. Kevin> > I am using wxRuby 0.6.0 on Win XP (Home) and Ruby installed using the > latest one-click Ruby installer. Code follows, image file (sk.png) > attached. > > TIA, > -- shanko > > ################# > require ''wxruby'' > > class MyFrame < Wx::Frame > > def initialize(title) > super(nil, -1, title,Wx::DEFAULT_POSITION, Wx::Size.new(250,250)) > > image = Wx::Image.new("sk.png") > @bitmap = Wx::Bitmap.new(image) > @left_dn = false evt_paint { on_paint } > evt_motion {|event| on_motion(event) } > evt_left_down {|event| @left_dn = true } > evt_left_up {|event| @left_dn = false } > end > > def move_region(nx,ny) > > ox,oy,w,h = @reg.get_box > @reg.clear > @reg = Wx::Region.new(nx,ny,nx+w,ny+h) > dc = Wx::ClientDC.new(self) > # Erase from the existing location > # > wb = Wx::WHITE_BRUSH > dc.set_brush(wb) dc.set_pen(Wx::WHITE_PEN) > dc.draw_rectangle(ox,oy,ox+w,oy+h) # Draw at the new > location # > dc.draw_bitmap(@bitmap,nx,ny,false) dc.free end > > def on_motion(event) > > x = event.get_x > y = event.get_y > move_region(x,y) if (@left_dn and (@reg.contains(x,y) == 0)) > > end > > def on_paint > paint{|dc| > dc.clear > dc.draw_bitmap(@bitmap, 0, 0, false) > } > w,h = @bitmap.get_width, @bitmap.get_height > @reg = Wx::Region.new(0,0,w,h) > end > end > > class ImagesApp < Wx::App > > def on_init > frame = MyFrame.new("Simple Image Demo") > frame.show > end > > end > > Wx::init_all_image_handlers > a = ImagesApp.new > a.main_loop() > > __END__ > > > ------------------------------------------------------------------------ > > > ------------------------------------------------------------------------ > > _______________________________________________ > wxruby-users mailing list > wxruby-users@rubyforge.org > http://rubyforge.org/mailman/listinfo/wxruby-users
Kevin Smith wrote:> The test in on_motion looks odd. It seems that if the endpoint of the > drag event is still within the border of the image, the drag is > ignored. That means only drag events that start within the image but > end outside will actually cause dragging.That''s it ! I don''t know how that condition came to be (although I wrote it myself a while back :-)). So I rewrote it like this: # ..... def on_motion(event) x = event.get_x y = event.get_y w,h = @bitmap1.get_width, @bitmap1.get_height move_region(x-(w/2),y-(h/2)) if @left_dn end And now it is working quite like what I wanted.> You might want to set @left_dn true ONLY if the down event started > within the image. Then in on_motion if left_dn is true, do a move. At > that point you might rename the variable to @dragging.Why rename the variable?> Many programs that do this also capture the mouse. My mind is too > fuzzy right now to remember if that is necessary for your purposes.I do not know how to "capture the mouse".> Drawing directly to the screen is always very slow. You will almost > certainly want to use "double buffering", known in some graphics > toolkits as a "canvas". That''s where you write to an off-screen > bitmap, and then just "blit" (transfer) the bitmap to the screen. I > don''t think wx has a built-in canvas object, and I don''t remember any > wxRuby examples using this, so you may have to do it yourself.I will take a look at it.> I think you can find an example in the Space Monkeys game I wrote a > while back. The code hasn''t been updated, so it probably won''t work > with a current version of wxRuby. But you can at least see the idea. > The tarball can be downloaded from rubyforge: > http://rubyforge.org/projects/monkeys/ > > Thinking from memory, create a Bitmap large enough for your work. When > you want to update what appears on the screen (such as during a mouse > drag), you create a MemoryDC, select the bitmap into the DC, and draw > your own image into the DC. > > The paint handler simply blits from the memory DC into the paint DC.Hmm ... I need to educate myself in such matters. Thanks for all the hints.> It would be nice if wxRuby had a Canvas class that did this for you.Yes, indeed !> KevinThanks Kevin. -- shanko
Shashank Date wrote:>> You might want to set @left_dn true ONLY if the down event started >> within the image. Then in on_motion if left_dn is true, do a move. At >> that point you might rename the variable to @dragging. > > > Why rename the variable?After reading your email again, I think I understood what you meant by renaming the variable ... just so that it would be concerned with "dragging" and not merely a left_down. Right? -- shanko
Shashank Date wrote:> >>> You might want to set @left_dn true ONLY if the down event started >>> within the image. Then in on_motion if left_dn is true, do a move. At >>> that point you might rename the variable to @dragging. >> >> Why rename the variable? > > After reading your email again, I think I understood what you meant by > renaming the variable ... just so that it would be concerned with > "dragging" and not merely a left_down. Right?Exactly. It would no longer merely indicate that the left button is down. It would now indicate that a drag is in progress. > I do not know how to "capture the mouse". In wxWindows, it is called wxWindow::CaptureMouse (most/all gui toolkits have something similar). Basically, it directs all future mouse input to your handler, even if the mouse goes outside your window until you say otherwise. You probably do want/need to do it. When the left button is released to end the drag operation, you would call ReleaseMouse. I haven''t checked, but I suspect wxRuby exposes Window#capture_mouse and release_mouse. Cheers, Kevin
Kevin Smith wrote:> In wxWindows, it is called wxWindow::CaptureMouse (most/all gui > toolkits have something similar). Basically, it directs all future > mouse input to your handler, even if the mouse goes outside your > window until you say otherwise. You probably do want/need to do it. > When the left button is released to end the drag operation, you would > call ReleaseMouse.I haven''t yet figured out how this will be needed ... see my (failed?) attempt in the code below.> I haven''t checked, but I suspect wxRuby exposes Window#capture_mouse > and release_mouse.yes, there is. And now I am working on another problem: that of overlapping images. You see, I simulate the move of the image by erasing it from the current location and drawing it at the new location. But if the current location happens to overlap with another image (even partially), then that second image part gets erased permanently. Run the code below (images attached) to see what I mean. Again, I am doing this with no understanding of MemoryDC, so please forgive and correct me if I ''m heading in the wrong direction.> Cheers, > > KevinThanks, -- shanko ############################################## require ''wxruby'' class MyFrame < Wx::Frame def initialize(title) super(nil, -1, title,Wx::DEFAULT_POSITION, Wx::Size.new(250,250)) image1 = Wx::Image.new("sk.png") @bitmap1 = Wx::Bitmap.new(image1) image2 = Wx::Image.new("sq.png") @bitmap2 = Wx::Bitmap.new(image2) @left_dn = false evt_paint { on_paint } evt_motion {|event| on_motion(event) } evt_left_down {|event| self.capture_mouse; @left_dn = true } evt_left_up {|event| self.release_mouse; @left_dn = false } end def move_region(nx,ny) ox,oy,w,h = @reg.get_box @reg.clear @reg = Wx::Region.new(nx,ny,nx+w,ny+h) dc = Wx::ClientDC.new(self) # Erase from the existing location # wb = Wx::WHITE_BRUSH dc.set_brush(wb) dc.set_pen(Wx::WHITE_PEN) dc.draw_rectangle(ox,oy,ox+w,oy+h) # Draw at the new location # dc.draw_bitmap(@bitmap1,nx,ny,false) dc.free end def on_motion(event) x = event.get_x y = event.get_y w,h = @bitmap1.get_width, @bitmap1.get_height move_region(x-(w/2),y-(h/2)) if @left_dn end def on_paint paint{|dc| dc.clear dc.draw_bitmap(@bitmap1, 0, 0, false) dc.draw_bitmap(@bitmap2, @bitmap1.get_width+5, 0, false) } @reg = Wx::Region.new(0,0,@bitmap1.get_width, @bitmap1.get_height) end end class ImagesApp < Wx::App def on_init frame = MyFrame.new("Simple Image Demo") frame.show end end Wx::init_all_image_handlers a = ImagesApp.new a.main_loop() __END__ -------------- next part -------------- A non-text attachment was scrubbed... Name: sq.png Type: image/png Size: 1040 bytes Desc: not available Url : http://rubyforge.org/pipermail/wxruby-users/attachments/20050123/2c347497/sq.png -------------- next part -------------- A non-text attachment was scrubbed... Name: sk.png Type: image/png Size: 961 bytes Desc: not available Url : http://rubyforge.org/pipermail/wxruby-users/attachments/20050123/2c347497/sk.png
>> And now I am working on another problem: that of overlapping images. You > see, I simulate the move of the image by erasing it from the current > location and drawing it at the new location. But if the current location > happens to overlap with another image (even partially), then that second > image part gets erased permanently. > Run the code below (images attached) to see what I mean. Again, I am > doing this with no understanding of > MemoryDC, so please forgive and correct me if I ''m heading in the wrong > direction. If you do things with a client DC, then you will need to redraw everything affected. What you need is wxDragImage. I''ll try to expose that. Nick Shashank Date wrote:> Kevin Smith wrote: > >> In wxWindows, it is called wxWindow::CaptureMouse (most/all gui >> toolkits have something similar). Basically, it directs all future >> mouse input to your handler, even if the mouse goes outside your >> window until you say otherwise. You probably do want/need to do it. >> When the left button is released to end the drag operation, you would >> call ReleaseMouse. > > > I haven''t yet figured out how this will be needed ... see my (failed?) > attempt in the code below. > >> I haven''t checked, but I suspect wxRuby exposes Window#capture_mouse >> and release_mouse. > > > yes, there is. > > And now I am working on another problem: that of overlapping images. You > see, I simulate the move of the image by erasing it from the current > location and drawing it at the new location. But if the current location > happens to overlap with another image (even partially), then that second > image part gets erased permanently. > Run the code below (images attached) to see what I mean. Again, I am > doing this with no understanding of > MemoryDC, so please forgive and correct me if I ''m heading in the wrong > direction. > >> Cheers, >> >> Kevin > > > Thanks, > -- shanko > > ############################################## > require ''wxruby'' > > class MyFrame < Wx::Frame > > def initialize(title) > > super(nil, -1, title,Wx::DEFAULT_POSITION, Wx::Size.new(250,250)) > > image1 = Wx::Image.new("sk.png") > @bitmap1 = Wx::Bitmap.new(image1) > image2 = Wx::Image.new("sq.png") > @bitmap2 = Wx::Bitmap.new(image2) > @left_dn = false > evt_paint { on_paint } > evt_motion {|event| on_motion(event) } > evt_left_down {|event| self.capture_mouse; @left_dn = true } > evt_left_up {|event| self.release_mouse; @left_dn = false } > end > > def move_region(nx,ny) > > ox,oy,w,h = @reg.get_box > @reg.clear > @reg = Wx::Region.new(nx,ny,nx+w,ny+h) > dc = Wx::ClientDC.new(self) > # Erase from the existing location > # > wb = Wx::WHITE_BRUSH > dc.set_brush(wb) > dc.set_pen(Wx::WHITE_PEN) > dc.draw_rectangle(ox,oy,ox+w,oy+h) > # Draw at the new location > # > dc.draw_bitmap(@bitmap1,nx,ny,false) > dc.free end > > def on_motion(event) > > x = event.get_x > y = event.get_y > w,h = @bitmap1.get_width, @bitmap1.get_height > move_region(x-(w/2),y-(h/2)) if @left_dn > > end > > def on_paint > paint{|dc| > dc.clear > dc.draw_bitmap(@bitmap1, 0, 0, false) > dc.draw_bitmap(@bitmap2, @bitmap1.get_width+5, 0, false) > } > @reg = Wx::Region.new(0,0,@bitmap1.get_width, @bitmap1.get_height) > end > > end > > class ImagesApp < Wx::App > > def on_init > frame = MyFrame.new("Simple Image Demo") > frame.show > end > > end > > Wx::init_all_image_handlers > a = ImagesApp.new > a.main_loop() > > __END__ > > > ------------------------------------------------------------------------ > > > ------------------------------------------------------------------------ > > > ------------------------------------------------------------------------ > > _______________________________________________ > wxruby-users mailing list > wxruby-users@rubyforge.org > http://rubyforge.org/mailman/listinfo/wxruby-users
Shashank Date wrote:> Kevin Smith wrote: > >> In wxWindows, it is called wxWindow::CaptureMouse (most/all gui > > I haven''t yet figured out how this will be needed ... see my (failed?) > attempt in the code below.Since I can''t run the code from here, I''m not sure how it''s failing. Without the capture/release, if you try to drag an image where the mouse cursor ends up outside the window area, it should stop moving until the mouse cursor returns to within your window. With capture/release, the image should keep moving no matter where your mouse cursor goes.> And now I am working on another problem: that of overlapping images.Nick mentioned wxDragImage, which I''m not familiar with, but certainly sounds promising. If I had to implement something without that, I would change the way the cards are drawn onto the background. I would have a Card class that tracks the card value (image) and location. Then, I would have a build_display method that would start with an empty bitmap and memory dc, would draw the background, then would draw each of the cards, in order (this allows some cards to be on top of others). Moving a card would actually blank and redraw the whole display (in memory, not on-screen). If that were too slow for some reason, then I would look for optimizations. One example of an optimization would be that when someone starts to drag a card, build an in-memory image of everything EXCEPT that card. That way, you can update the during-drag display by just copying that snapshot image and then drawing the moving card on top of it. But, again, I wouldn''t do that unless it was necessary. Cheers, Kevin
Hi, Nick wrote:> If you do things with a client DC, then you will need to redraw > everything affected. What you need is wxDragImage. I''ll try to expose > that.That will be great ! Thanks,> Nick-- shanko
Hi Kevin, Kevin Smith wrote:> Since I can''t run the code from here, I''m not sure how it''s failing. > Without the capture/release, if you try to drag an image where the > mouse cursor ends up outside the window area, it should stop moving > until the mouse cursor returns to within your window. With > capture/release, the image should keep moving no matter where your > mouse cursor goes.Oh, yes ! Now I see. In that case, my code is not failing.... :-)> Nick mentioned wxDragImage, which I''m not familiar with, but certainly > sounds promising.Yup... looking forward to it.> If I had to implement something without that, I would change the way > the cards are drawn onto the background. I would have a Card class > that tracks the card value (image) and location. Then, I would have a > build_display method that would start with an empty bitmap and memory > dc, would draw the background, then would draw each of the cards, in > order (this allows some cards to be on top of others). > > Moving a card would actually blank and redraw the whole display (in > memory, not on-screen). If that were too slow for some reason, then I > would look for optimizations. > > One example of an optimization would be that when someone starts to > drag a card, build an in-memory image of everything EXCEPT that card. > That way, you can update the during-drag display by just copying that > snapshot image and then drawing the moving card on top of it. But, > again, I wouldn''t do that unless it was necessary.Excellent tips. While I am waiting for wxDragImage, I will try and implement them just to see how it works.> Cheers,Thanks again.> Kevin-- shanko