Kivy Python Right Click
Asked Answered
A

3

5

I'm making a game of Minesweeper with Kivy using Button widgets. I want to be able to have different actions depending on whether the mouse click is a left mouse click or a right mouse click. Can anybody help me?

Below is my Cell class and the imported modules.

Note that Cell.onPressed() is the function that is called when a button is pressed.

import kivy
from kivy.config import Config

Config.set('input', 'mouse', 'mouse,disable_multitouch')

from random import randint
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.anchorlayout import AnchorLayout

width = 10
height = 10
bombs = 5
size = 60

class Cell():  

  def __init__(self):
    self.isBomb = False
    self.isVisible = False
    self.neighbors = None
    self.location = []
    self.button = Button(size = (size, size), size_hint = (None, None))
    self.button.bind(on_press = self.onPressed)

  def build(self, x, y):
    self.location = [x, y]
    self.count_neighbors()

  def onPressed(self, instance):
    #if left_click == True:    
    self.isVisible = True
    self.button.text = str(self.neighbors)
    if self.neighbors == 0:
      for i in range(-1, 2):
        for j in range(-1, 2):
          if (0 <= self.location[0] + i < width) and (0 <= self.location[1] + j < height):
            if grid[self.location[0] + i][self.location[1] + j].isVisible == False:
              grid[self.location[0] + i][self.location[1] + j].onPressed(instance)    
    #if right_click == True:
      #Toggle state


  def count_neighbors(self):
    if self.isBomb == False:
      count = 0
      for i in range(-1, 2):
        for j in range(-1, 2):
          if (0 <= self.location[0] + i < width) and (0 <= self.location[1] + j < height):
            if grid[self.location[0] + i][self.location[1] + j].isBomb == True:
              count += 1
      self.neighbors = count

class TestApp(App):

  def build(self):
    root = AnchorLayout(anchor_x = 'center', anchor_y = 'center')
    grid_root = RelativeLayout(size = (width * size, height * size), size_hint = (None, None))
    layout = []
    for i in range(height):
      layout.append(BoxLayout(orientation='horizontal', size_hint = (.8, .8), pos = (0, (height - 1) * size - i * size)))
      for j in range(width):    
        layout[i].add_widget(grid[j][i].button)    
      grid_root.add_widget(layout[i])
    root.add_widget(grid_root)
    return root


def init_grid():
  global grid
  grid = [[Cell() for x in range(width)] for y in range(height)]
  for _ in range(bombs):
    while True:
      x = randint(0, height - 1)
      y = randint(0, width - 1)
      if grid[x][y].isBomb == False:
        grid[x][y].isBomb = True
        break  
  for i in range(width):
    for j in range(height):
      grid[j][i].build(j, i)


if __name__ == '__main__':
  init_grid()
  TestApp().run()
Attorney answered 12/6, 2017 at 5:24 Comment(0)
A
8

You should bind on_touch_down instead of the on_press so you can have the touch parameter available:

...
self.button.bind(on_touch_down = self.onPressed)
...

def onPressed(self, instance, touch):
    if touch.button == 'right':
        print("right mouse clicked")
    ...
Admiral answered 12/6, 2017 at 5:53 Comment(7)
I made an edit to include the entire program. Your answer did not work for me, I either get a 'Too many arguments' error or 'touch is not defined'. What module do you have to import and how would I best implement it?Attorney
Sorry I made an editing error, it should be on_touch_down in bind instead of on_press, updating shortly. Please try second example again.Admiral
I don't understand why a button can't simply detect whether a left or right click. From what I'm reading I need to use the widget module from kivy and it was stated "Widget.on_touch_down(), Widget.on_touch_move(), Widget.on_touch_up() don’t do any sort of collisions. If you want to know if the touch is inside your widget, use Widget.collide_point()." I basically have to rethink my whole program now.Attorney
@DamonPalovaara, all you need is to update the binding line and replace onPressed function with the one above. I will clear out the first code to prevent confusion. Indeed they do collision when you bind like that.Admiral
I wasn't able to get that to work, what I ended up doing was creating a class that inherits from Widget class TouchInput(Widget): def on_touch_down(self, touch): global mouse mouse = touch.button Then I created an instance of it and added it to my root widget. So now every-time a button is clicked is just checks the status of mouse. def onPressed(self, instance): if mouse == 'left': print('Left!') if mouse == 'right': print('Right!')Attorney
Interesting, it's ok if that works for you. I still don't recommend having two different event handlers, order can be problematic in future.Admiral
As far as I tested it worked but I would like a less hacky solution. I'm new to Kivy, I only started using it to make a GUI for my Minesweeper game.Attorney
A
1

I got something working, it's a little hackish though. I created a new class that inherits from Widget and simply changes a global variable called 'mouse'

class TouchInput(Widget):

  def on_touch_down(self, touch):
    global mouse
    mouse = touch.button

I created an instance of TouchInput() and added it to my root layout

class TestApp(App):

  def build(self):
    root = AnchorLayout(anchor_x = 'center', anchor_y = 'center')
    root_input = TouchInput()
    grid_root = RelativeLayout(size = (width * size, height * size), size_hint = (None, None))
    layout = []
    for i in range(height):
      layout.append(BoxLayout(orientation='horizontal', size_hint = (.8, .8), pos = (0, (height - 1) * size - i * size)))
      for j in range(width):    
        layout[i].add_widget(grid[j][i].button)    
      grid_root.add_widget(layout[i])
    root.add_widget(grid_root)
    root.add_widget(root_input)
    return(root)

Now anytime a button is pressed it can check whether it was a right or left click.

def onPressed(self, instance):
  if mouse == 'left':
    print('Left!')
  if mouse == 'right':
    print('Right!')
Attorney answered 12/6, 2017 at 15:56 Comment(2)
Better than the accepted solution in my opinion. When I use the accepted solution it seems to call the function for all widgets above it for some reason.Vallee
@Vallee Unless the on_touch_down method returns True (indicating the touch has been handled), the touch event is propagated to other widgets. Usually just adding a return True at the bottom will fix this.Protest
G
0

I was able to tell what button is pressed by creating a new class as follow:

from kivy.uix.button import Button 
class ClickableButton(Button):
    def __init__(self,**kwargs):
        super(Button, self).__init__(**kwargs)
        self.mouse_button = None #mouse_button records what button is pressed
        self.bind(on_touch_down = self.callback_touch_down) #set up a function to be called on mouse events
    def callback_touch_down(self, instance, touch):
        self.mouse_button = touch.button #record what button is clicked on touch down on mouse events

The ClickableButton class inherits from Button so it can be used just like the normal kivy Button, like below:

from kivy.app import App
def callback(instance):
    print('The <%s> button is being pressed' % instance.mouse_button)

class MyApp(App):  
    def build(self):  
        return ClickableButton(text = "ClickableButton",on_press = callback)

if __name__ == '__main__':
    root = MyApp().run()

Now the programs knows that what specific kivy button is pressed and what mouse button was clicked. Also the Button.on_touch_down() function does not bubble up to multiple instances.

Glance answered 11/7, 2019 at 4:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.