I'm working on creating controls in pygame (button and label are the only ones done, as well as setting a tooltip for the control). I'm drawing the controls in the correct Z order but I'm trying to detect that the mouse is not over another control, so it only activates the one that's visible.
This, somewhat, works if you remove test.set_on_bottom(btnButton7), it will only trigger button7 even if the mouse is over one of the buttons below it, but if you click on one of the buttons below button7 and move the mouse over button7 it still thinks that the original button is being clicked (when it shouldn't)
Been wrapping my brain around this problem for a few days now and I just can't see to figure it out.
(Also, I just thought about it this morning but I should have turned each of the controls into a class instead of an id, so this will get reworked later)
The code is too long to post, here's the pastebin link
All of the processing of setting the states and triggering the messages is done in the process_events method
def process_events(self):
button = pygame.mouse.get_pressed()
mouse_pos = pygame.mouse.get_pos()
for control_id in reversed(self.__z_order):
state = self.__control_list[control_id]['state']
if state in ('deleted', 'disabled') or not self.__control_list[control_id]['draw']:
continue
if self.__control_list[control_id]['rect'].collidepoint(mouse_pos):
# left mouse is pressed
if button[0]:
# current state of this control is hot and left mouse is pressed on it
if state == 'hot':
for x in self.__z_order[0:control_id - 1]:
if self.__control_list[x]['state'] == 'pressed':
self.__control_list[x]['state'] = 'normal'
can_change = True
for x in self.__z_order[control_id + 1:]:
if self.__control_list[x]['state'] == 'pressed':
can_change = False
break
# change the state to pressed
if can_change:
self.__control_list[control_id]['state'] = 'pressed'
self.__control_list[control_id]['mouse_pos_lclick'] = None
self.__control_list[control_id]['mouse_pos_ldown'] = mouse_pos
if self.__control_list[control_id]['on_hover_called']:
self.__draw_tip = None
if (time.clock() - self.__control_list[control_id][
'dbl_timer'] >= self.__dbl_click_delay) and \
(time.clock() - self.__control_list[control_id]['timer'] <= self.__dbl_click_speed):
if self.__event_mode:
if self.__control_list[control_id]['on_dbl_lclick']:
self.__control_list[control_id]['on_dbl_lclick']()
else:
self.__messages.append(self.Message(control_id, PGC_LBUTTONDBLCLK))
# print('Double click', self.__control_list[control_id]['text'])
self.__control_list[control_id]['dbl_timer'] = time.clock()
self.__control_list[control_id]['timer'] = -1
break
# go through the controls from top to bottom first
for control_id in reversed(self.__z_order):
state = self.__control_list[control_id]['state']
if state in ('deleted', 'disabled') or not self.__control_list[control_id]['draw']:
continue
# check if the mouse is over this control
if self.__control_list[control_id]['rect'].collidepoint(mouse_pos):
# left mouse is not down
if not button[0]:
# state is currently pressed
if state == 'pressed':
# check if there's a timer initiated for this control
# this prevents 2 clicks + a double click message
if self.__control_list[control_id]['timer'] >= 0:
self.__control_list[control_id]['dbl_timer'] = -1
self.__control_list[control_id]['timer'] = time.clock()
# if the event mode
if self.__event_mode:
# call the function if there is one
if self.__control_list[control_id]['on_lclick']:
self.__control_list[control_id]['on_lclick']()
else:
# post the message to the messages queue
self.__messages.append(self.Message(control_id, PGC_LBUTTONUP))
# print('Click', self.__control_list[control_id]['text'])
# the timer is < 0 (should be -1), double click just happened
else:
# reset the timer to 0 so clicking can happen again
self.__control_list[control_id]['timer'] = 0
# go through all of the ids below this control
for x in self.__z_order[0:control_id - 1]:
# set all the hot controls to normal
if self.__control_list[x]['state'] == 'hot':
self.__control_list[x]['state'] = 'normal'
can_change = True
# go through all the controls on top of this control
for x in self.__z_order[control_id + 1:]:
# something else is on top of this and it's already hot, can't change this control
if self.__control_list[x]['state'] == 'hot':
can_change = False
break
if can_change:
self.__control_list[control_id]['state'] = 'hot'
self.__control_list[control_id]['mouse_pos_lclick'] = mouse_pos
self.__control_list[control_id]['mouse_pos_ldown'] = None
# state is not currently hot (but we're hovering over this control)
elif state != 'hot':
# check for any other contorls
for x in self.__z_order[0:control_id - 1]:
if self.__control_list[x]['state'] == 'hot':
self.__control_list[x]['state'] = 'normal'
can_change = True
for x in self.__z_order[control_id + 1:]:
if self.__control_list[x]['state'] == 'hot':
can_change = False
break
# change the state to hot
if can_change:
self.__control_list[control_id]['state'] = 'hot'
self.__control_list[control_id]['mouse_pos_hover'] = mouse_pos
# used to start a tooltip (needs work)
self.__control_list[control_id]['mouse_pos_rect'] = pygame.Rect(mouse_pos[0] - 7,
mouse_pos[1] - 7,
mouse_pos[0] + 7,
mouse_pos[1] + 7)
# state is currently 'hot'
else:
# timer for on_hover hasn't been initialized
if self.__control_list[control_id]['timer_on_hover'] == 0:
self.__control_list[control_id]['timer_on_hover'] = time.clock()
# mouse is in the area
if self.__control_list[control_id]['mouse_pos_rect'].collidepoint(mouse_pos):
# if the on_hover hasn't been triggered and there is a timer for the on_hover
if not self.__control_list[control_id]['on_hover_called'] and self.__control_list[control_id]['timer_on_hover']:
# if the mouse has been in the hover area for 1.5 seconds or more
if time.clock() - self.__control_list[control_id]['timer_on_hover'] >= 1.5:
# trigger the hover
self.__control_list[control_id]['on_hover_called'] = True
# on_hover is a function call, call the function
if self.__control_list[control_id]['on_hover']['type'] == 'function':
self.__control_list[control_id]['on_hover']['func'](self.__control_list[control_id]['on_hover']['args'])
# on_hover is a tip, set the self.__draw_tip variable to the tip we need
else:
self.__draw_tip = self.__control_list[control_id]['on_hover'].copy()
self.__draw_tip['rect'].x = mouse_pos[0]
self.__draw_tip['rect'].y = mouse_pos[1]
# mouse is not in the control rect and the state is not currently normal
elif state != 'normal':
# set it to normal
self.__control_list[control_id]['state'] = 'normal'
# clear the on_hover stuff
if self.__control_list[control_id]['on_hover_called']:
self.__control_list[control_id]['on_hover_called'] = False
self.__draw_tip = None
if self.__control_list[control_id]['timer_on_hover']:
self.__control_list[control_id]['timer_on_hover'] = 0
(Will select as the correct answer tomorrow when the 24hr timeout is over)
Ended up figuring it out. I was way over complicating it.
Work with the control that has been saved as needed
def process_events(self):
button = pygame.mouse.get_pressed()
mouse_pos = pygame.mouse.get_pos()
top_id = -1
for control_id in reversed(self.__z_order):
if self.__control_list[control_id]['state'] != 'disabled' and \
self.__control_list[control_id]['draw'] and \
self.__control_list[control_id]['rect'].collidepoint(mouse_pos):
top_id = control_id
break
if top_id != -1:
# go through all of the controls
for control_id in self.__z_order:
# skip the top most control and any that are disabled/deleted
if self.__control_list[control_id]['state'] != 'disabled' and \
self.__control_list[control_id]['state'] != 'normal' and \
control_id != top_id:
# set it to normal
self.__control_list[control_id]['state'] = 'normal'
# clear the on_hover stuff
if self.__control_list[control_id]['on_hover_called']:
self.__control_list[control_id]['on_hover_called'] = False
self.__draw_tip = None
if self.__control_list[control_id]['timer_on_hover']:
self.__control_list[control_id]['timer_on_hover'] = 0
else:
for control_id in self.__z_order:
if self.__control_list[control_id]['state'] != 'disabled':
# set it to normal
self.__control_list[control_id]['state'] = 'normal'
# clear the on_hover stuff
if self.__control_list[control_id]['on_hover_called']:
self.__control_list[control_id]['on_hover_called'] = False
self.__draw_tip = None
if self.__control_list[control_id]['timer_on_hover']:
self.__control_list[control_id]['timer_on_hover'] = 0
return
if button[0]:
# current state of this control is hot and left mouse is pressed on it
if self.__control_list[top_id]['state'] == 'hot':
self.__control_list[top_id]['state'] = 'pressed'
self.__control_list[top_id]['mouse_pos_lclick'] = None
self.__control_list[top_id]['mouse_pos_ldown'] = mouse_pos
if self.__control_list[top_id]['on_hover_called']:
self.__draw_tip = None
if (time.clock() - self.__control_list[top_id][
'dbl_timer'] >= self.__dbl_click_delay) and \
(time.clock() - self.__control_list[top_id]['timer'] <= self.__dbl_click_speed):
if self.__event_mode:
if self.__control_list[top_id]['on_dbl_lclick']:
self.__control_list[top_id]['on_dbl_lclick']()
else:
self.__messages.append(self.Message(top_id, PGC_LBUTTONDBLCLK))
# print('Double click', self.__control_list[top_id]['text'])
self.__control_list[top_id]['dbl_timer'] = time.clock()
self.__control_list[top_id]['timer'] = -1
elif not button[0]:
# state is currently pressed
if self.__control_list[top_id]['state'] == 'pressed':
# check if there's a timer initiated for this control
# this prevents 2 clicks + a double click message
if self.__control_list[top_id]['timer'] >= 0:
self.__control_list[top_id]['dbl_timer'] = -1
self.__control_list[top_id]['timer'] = time.clock()
# if the event mode
if self.__event_mode:
# call the function if there is one
if self.__control_list[top_id]['on_lclick']:
self.__control_list[top_id]['on_lclick']()
else:
# post the message to the messages queue
self.__messages.append(self.Message(top_id, PGC_LBUTTONUP))
# print('Click', self.__control_list[top_id]['text'])
# the timer is < 0 (should be -1), double click just happened
else:
# reset the timer to 0 so clicking can happen again
self.__control_list[top_id]['timer'] = 0
# go through all of the ids below this control
for x in self.__z_order[0:top_id - 1]:
# set all the hot controls to normal
if self.__control_list[x]['state'] == 'hot':
self.__control_list[x]['state'] = 'normal'
can_change = True
# go through all the controls on top of this control
for x in self.__z_order[top_id + 1:]:
# something else is on top of this and it's already hot, can't change this control
if self.__control_list[x]['state'] == 'hot':
can_change = False
break
if can_change:
self.__control_list[top_id]['state'] = 'hot'
self.__control_list[top_id]['mouse_pos_lclick'] = mouse_pos
self.__control_list[top_id]['mouse_pos_ldown'] = None
# state is not currently hot (but we're hovering over this control)
elif self.__control_list[top_id]['state'] != 'hot':
self.__control_list[top_id]['state'] = 'hot'
self.__control_list[top_id]['mouse_pos_hover'] = mouse_pos
# used to start a tooltip (needs work)
self.__control_list[top_id]['mouse_pos_rect'] = pygame.Rect(mouse_pos[0] - 7,
mouse_pos[1] - 7,
mouse_pos[0] + 7,
mouse_pos[1] + 7)
# state is currently 'hot'
else:
# timer for on_hover hasn't been initialized
if self.__control_list[top_id]['timer_on_hover'] == 0:
self.__control_list[top_id]['timer_on_hover'] = time.clock()
# mouse is in the area
if self.__control_list[top_id]['mouse_pos_rect'].collidepoint(mouse_pos):
# if the on_hover hasn't been triggered and there is a timer for the on_hover
if not self.__control_list[top_id]['on_hover_called'] and \
self.__control_list[top_id]['timer_on_hover']:
# if the mouse has been in the hover area for 1.5 seconds or more
if time.clock() - self.__control_list[top_id]['timer_on_hover'] >= 1.5:
# trigger the hover
self.__control_list[top_id]['on_hover_called'] = True
# on_hover is a function call, call the function
if self.__control_list[top_id]['on_hover']['type'] == 'function':
self.__control_list[top_id]['on_hover']['func'] \
(self.__control_list[top_id]['on_hover']['args'])
# on_hover is a tip, set the self.__draw_tip variable to the tip we need
else:
self.__draw_tip = self.__control_list[top_id]['on_hover'].copy()
self.__draw_tip['rect'].x = mouse_pos[0]
self.__draw_tip['rect'].y = mouse_pos[1]