So I want to display a label if someone tries to click play and there is no save file made yet. Then I want it to fade out. The while loop works, reducing the value of alpha to 0. And it displays the label as long as I don't have the self.remove_widget(no_save) added in but then it just stays as a solid label. Any help would be appreciated. Or is there an easier way to do this?
class StartMenu(Screen):
def check_save(self):
global save_state
if save_state == None:
color = (0,1,0,1)
while color[3] > 0:
no_save = Label(text='No save file found. Please press New Game', color=color)
self.add_widget(no_save)
color = color [:3] + (color[3] - (.1),)
time.sleep(.1)
self.remove_widget(no_save)
Rather than doing the fade out yourself, why not use the built in Animation functionality? Try something like this. I would also suggest moving save_state from the global realm to your class, and instead of creating and destroying the label every run, I would create at initialization and simply hide or show it as it becomes necessary.
class StartMenu(Screen):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.save_state = None
no_save = Label('No save file found. Please press new game.', hidden=True)
self.add_widget(no_save)
def check_save(self):
if not self.save_state:
self.no_save.hidden = False
def hide_label(w): w.hidden = True
Animation(opacity=0, duration=1, on_complete=hide_label).start(self.no_save)
Quick shoutout to zeeMonkeys for pointing out the Animation solution in the comments before I did.
Related
I'm currently trying to create program that allows a user to view pictures of a shirt the user selects combined with different pants.
The program contains a directory filled with pictures of shirts, aswell as one of pants.
The user selects a shirt from a DropDown-menu, and the program then generates, saves, and displays the all possible combinations created using the shirt.
To accomplish this I'm using Tkinter & PIL (Pillow).
Here's my problem: When the user edits the DropDown-menu, and selects another shirt, I want the program to generate new images using THAT shirt, and replace the old images currently displayed.
I've read answers to similar questions, and some suggest a setter and getter, to detect and call a function when a variables' value is changed. I'm not quite sure I understand how it works, and definetly not how to implement it into my code. The function I want to call is inside a nested loop. Does that make any difference in this context?
Here is the code. The generateImage()function is a placeholder for the many rows of code that acutally generate the images.
To detect when the selected option from the DropDown-menu is changed, I use variable.trace.
shirtcolors = ["blue", "red", "green"]
def ChosenImage(*args):
chosen_image = variable.get()
print("value is: " + chosen_image)
selectedimage = ""
if chosen_image == "blue":
selectedimage = "C:/User/Desktop/OutfitGenerator/shirts/IMG_0840.jpg"
elif chosen_image == "red":
selectedimage = "C:/User/Desktop/OutfitGenerator/shirts/IMG_0850.jpg"
elif chosen_image == "green":
selectedimage = "C:/User/Desktop/OutfitGenerator/shirts/IMG_0860.jpg"
return selectedimage
DropdownList = tk.OptionMenu(frame, variable, *shirtcolors)
DropdownList.pack()
variable.trace("w", callback=ChosenImage)
shirtslist = [ChosenImage()]
pantsdirectory= "C:/User/Desktop/OutfitGenerator/pants"
pantslist = [os.path.abspath(os.path.join(pantsdirecotry, h)) for h in os.listdir(pantsdirecotry)]
for i in shirtslist:
for j in pantslist:
def generateImage(file1, file2):
My problem is that i can't figure out how to make the program run the code below the variable.trace line again. When a callback is sent to ChosenImage I want it to also continue the rest of the code, now using the new selectedimage value. However the continuation of the code cannot happen until the callback has reached the ChosenImage function, and it has changed its value.
My problem is that i can't figure out how to make the program run the
code below the variable.trace line again. When a callback is sent to
ChosenImage I want it to also continue the rest of the code, now using
the new selectedimage value.
This is the wrong way of thinking. Your callback is the thing that should generate the combinations/images - your callback shouldn't return anything, it should only have side-effects:
import tkinter as tk
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("Window")
self.geometry("256x64")
self.resizable(width=False, height=False)
self.shirt_colors = ["blue", "red", "green"]
self.pants_colors = ["brown", "black"]
self.drop_down_var = tk.StringVar(self)
self.menu = tk.OptionMenu(self, self.drop_down_var, *self.shirt_colors)
self.menu.pack()
self.drop_down_var.trace("w", callback=self.on_change)
def on_change(self, *args):
# Everytime the selection changes, generate combinations
shirt_color = self.drop_down_var.get()
for pant_color in self.pants_colors:
print("{} shirt with {} pants".format(shirt_color, pant_color))
def main():
Application().mainloop()
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
I have plotted a circle glyph. When I select some points they are marked as selected. But I lose the selection when I change the tool I am using or when I click on some other place of the figure. Which is the best way to keep the selection on the circle glyph?. I want to deselect all the points when the ResetTool is pressed. I am working with the master (0.12.14+25.g675aacf72) branch of bokeh where the Selection class already exists.
def update_selection(self, attr, old, new):
''' I do this to avoid deselection '''
if new.indices == []:
self.source.selected.indices = self.old_selection
else:
self.old_selection = list(new.indices)
source.on_change(
'selected',
update_selection
)
I use this to call a callback when the Reset tool is pressed:
def deselect_points(self, event):
''' I do this to deselect point on Reset event
But when the indices are updated the update_selection method is called
'''
self.source.selected.indices = []
plot.on_event(Reset, deselect_points)
So is there a way to keep the selection and only deselect point on Reset event?
Update 03/14/2018
This is happening only when the Tap Tool is enabled. So I have written an issue on GitHub to check if this is the expected behaviour
Finally I made a workaround like this:
# [...]
self.reset_selection = False
# [...]
def _reset_lines(self, event):
# hide the glyphs which mark the selected points
self.lr.visible = False
self.lr_circle_selected.visible = False
self.lr.data_source.data = self.empty_source.data
self.lr_circle_selected.view = self.empty_view
self.reset_selection = True
def update_selection(self, attr, old, new):
if new.indices == []:
if not self.reset_selection:
new.indices = list(self.selection) # keep the selection
else:
self.reset_selection = False
self.selection = []
else:
self.selection = list(new.indices)
self.plot.on_event(Reset, self._reset_lines)
So I am going to use this in the mean time I donĀ“t find anything better
Update
It seems that the issue is already closed. So I hope this is solved already
I'm using urwid, which is a Python "framework" for designing terminal user interfaces in ncurses. There's one thing though that I'm not able to do in urwid that was easy in curses - make the cursor invisible. As it is now, the cursor is visible when selecting buttons, and it just looks plain ugly. Is there a way to disable it?
I agree that the flashing cursor on an urwid.Button looks a bit lame, so I've come up with a solution to hide it. In urwid, the Button class is just a subclass of WidgetWrap containing a SelectableIcon and two Text widgets (the enclosing "<" and ">"). It's the SelectableIcon class that sets the cursor position to the first character of the label, by default. By subclassing SelectableIcon, modifying the cursor position and then wrapping it into an urwid.WidgetWrap subclass you can create your own custom button that can do all the tricks a built-in Button, or even more.
Here' what it looks like in my project.
import urwid
class ButtonLabel(urwid.SelectableIcon):
def __init__(self, text):
"""
Here's the trick:
we move the cursor out to the right of the label/text, so it doesn't show
"""
curs_pos = len(text) + 1
urwid.SelectableIcon.__init__(self, text, cursor_position=curs_pos)
Next, you can wrap a ButtonLabel object along with any other objects into a WidgetWrap subclass that will be your custom button class.
class FixedButton(urwid.WidgetWrap):
_selectable = True
signals = ["click"]
def __init__(self, label):
self.label = ButtonLabel(label)
# you could combine the ButtonLabel object with other widgets here
display_widget = self.label
urwid.WidgetWrap.__init__(self, urwid.AttrMap(display_widget, None, focus_map="button_reversed"))
def keypress(self, size, key):
"""
catch all the keys you want to handle here
and emit the click signal along with any data
"""
pass
def set_label(self, new_label):
# we can set the label at run time, if necessary
self.label.set_text(str(new_label))
def mouse_event(self, size, event, button, col, row, focus):
"""
handle any mouse events here
and emit the click signal along with any data
"""
pass
In this code, there is actually not much combination of widgets in the FixedButton WidgetWrap subclass, but you could add a "[" and "]" to the edges of the button, wrap it into a LineBox, etc. If all this is superfluous, you can just move the event handling functions into the ButtonLabel class, and make it emit a signal when it gets clicked.
To make the button reversed when the user moves on it, wrap it into AttrMap and set the focus_map to some palette entry ("button_reversed", in my case).
Building upon Drunken Master's answer, I've cleaned up the solution as much as possible.
The urwid.SelectableIcon is basically an urwid.Text field with this ugly blinking cursor.
So instead of overriding the urwid.SelectableIcon and packing it into an urwid.WidgetWrap, let's take an urwid.Text directly and make it selectable and react to button/mouse activation (inspired from urwid's simple menu tutorial):
import urwid
choices = u'Chapman Cleese Gilliam Idle Jones Palin'.split()
class ListEntry(urwid.Text):
_selectable = True
signals = ["click"]
def keypress(self, size, key):
"""
Send 'click' signal on 'activate' command.
"""
if self._command_map[key] != urwid.ACTIVATE:
return key
self._emit('click')
def mouse_event(self, size, event, button, x, y, focus):
"""
Send 'click' signal on button 1 press.
"""
if button != 1 or not urwid.util.is_mouse_press(event):
return False
self._emit('click')
return True
def menu(title, choices):
body = [urwid.Text(title), urwid.Divider()]
for c in choices:
button = ListEntry(c)
urwid.connect_signal(button, 'click', item_chosen, c)
body.append(urwid.AttrMap(button, None, focus_map='reversed'))
return urwid.ListBox(urwid.SimpleFocusListWalker(body))
def item_chosen(button, choice):
response = urwid.Text([u'You chose ', choice, u'\n'])
done = ListEntry(u'Ok')
urwid.connect_signal(done, 'click', exit_program)
main.original_widget = urwid.Filler(urwid.Pile([response,
urwid.AttrMap(done, None, focus_map='reversed')]))
def exit_program(button):
raise urwid.ExitMainLoop()
main = urwid.Padding(menu(u'Pythons', choices), left=2, right=2)
top = urwid.Overlay(main, urwid.SolidFill(u'\N{MEDIUM SHADE}'),
align='center', width=('relative', 60),
valign='middle', height=('relative', 60),
min_width=20, min_height=9)
urwid.MainLoop(top, palette=[('reversed', 'standout', '')]).run()
Works like a charm:
urwid uses the curs_set function, but does not expose it as a class method anywhere. Someone could modify urwid to allow using this method; otherwise there's no reliable method of doing this.
You might report it as an issue.
Along the lines of Drunken Master's answer, but with "minimally invasive surgery":
class ButtonLabel(urwid.SelectableIcon):
'''
use Drunken Master's trick to move the cursor out of view
'''
def set_text(self, label):
'''
set_text is invoked by Button.set_label
'''
self.__super.set_text(label)
self._cursor_position = len(label) + 1
class MyButton(urwid.Button):
'''
- override __init__ to use our ButtonLabel instead of urwid.SelectableIcon
- make button_left and button_right plain strings and variable width -
any string, including an empty string, can be set and displayed
- otherwise, we leave Button behaviour unchanged
'''
button_left = "["
button_right = "]"
def __init__(self, label, on_press=None, user_data=None):
self._label = ButtonLabel("")
cols = urwid.Columns([
('fixed', len(self.button_left), urwid.Text(self.button_left)),
self._label,
('fixed', len(self.button_right), urwid.Text(self.button_right))],
dividechars=1)
super(urwid.Button, self).__init__(cols)
if on_press:
urwid.connect_signal(self, 'click', on_press, user_data)
self.set_label(label)
Here, we only modify the button's appearance but otherwise leave its behaviour unchanged.
I am trying to figure out how to change a rectangle's color continuously, with a second between each change. Right now I have this simple function which makes a window with a square above a button, that changes the color of the square after every button click:
def junk():
def random_color():
red = int(random.random()*256)
green = int(random.random()*256)
blue = int(random.random()*256)
return '#' + ('{:0>#02X}'*3).format(red,green,blue)
def change_color():
c.itemconfig(r, fill=random_color())
x = Tkinter.Tk()
c = Tkinter.Canvas(master=x)
c['width'] = 400; c['height'] = 400
r = c.create_rectangle(0,0,400,400)
b = Tkinter.Button(master=x, command=change_color)
b['text'] = 'change color'
c.pack(); b.pack(); x.mainloop()
What I want is to be able to click once, and then have the colors change automatically. I know I want to use a CheckButton instead of a Button for this, so that one click will start the loop, and and the next click will stop it.
Also, this is not how I am structuring my "real" code, this is how I am testing from the IDLE shell. Defining the helper functions inside the junk function makes it easy to get at all the relevant code at once, without having the bloat of a full class. So please don't give me comments on style, this is quick and dirty on purpose.
TL;DR I'm not sure how to get a continuous loop running to change the color, while being able to start and stop the loop with a button click.
I figured it out. Before I show my solution, I want to correct a mistaken statement I made above: I don't to use a Checkbutton to make this work. I can make a normal button into a toggle button by changing the 'relief' option of the button. Here is my solution:
def junk():
def color_poll():
global alarm
c.itemconfig(r, fill=random_color())
if keep_going:
alarm = c.after(1000, color_poll)
def change_color():
global keep_going, alarm
if not keep_going:
keep_going = True
b['text']='STOP';b['fg']='red';b['relief']=Tkinter.SUNKEN
color_poll()
else:
keep_going = False; c.after_cancel(alarm); alarm = None
b['text']='GO';b['fg']='green';b['relief']=Tkinter.RAISED
x = Tkinter.Tk()
c = Tkinter.Canvas(master=x)
c['width'] = 400; c['height'] = 400
r = c.create_rectangle(0,0,400,400)
global keep_going, alarm
keep_going = False; alarm = None
b = Tkinter.Button(master=x, command=change_color)
b['text'] = 'GO';b['fg']='green';b['font']='Arial 16';b['relief']=Tkinter.RAISED
c.pack(); b.pack(); x.mainloop()
I'm using the same random_color function, but I moved it out because it out of the junk function because it didn't need to be there.
I guess my question is pretty much summed up in the title.
I am using an update call (similar to the one in the Pong tutorial). Within this call I update the points of a line. Though I can check that the points are indeed being updated, the actual line drawing is not.
I'll put some of the code up here:
class GraphInterface(Widget):
node = ObjectProperty(None)
def update(self, dt):
for widget in self.children:
if isinstance(widget, GraphEdge) and widget.collide_widget(self):
widget.check_connection()
class GraphEdge(Widget):
r = NumericProperty(1.0)
#determines if edge has an attached node
connected_point_0 = Property(False)
connected_point_1 = Property(False)
#provides details of the attached node
connected_node_0 = Widget()
connected_node_1 = Widget()
def __init__(self, **kwargs):
super(GraphEdge, self).__init__(**kwargs)
with self.canvas:
Color(self.r, 1, 1, 1)
self.line = Line(points=[100, 200, 200, 200], width = 2.0, close = True)
def snap_to_node(self, node):
if self.collide_widget(node):
if (self.connected_point_1 is False):
print "collision"
self.connected_point_1 = True
self.connected_node_1 = node
del self.line.points[-2:]
self.line.points[-2:]+=node.center
self.size = [math.sqrt(((self.line.points[0]-self.line.points[2])**2 + (self.line.points[1]-self.line.points[3])**2))]*2
self.center = ((self.line.points[0]+self.line.points[2])/2,(self.line.points[1]+self.line.points[3])/2)
return True
pass
The idea is to check for collisions initially, and once a collision has been made, I attach the line to this node widget. The points are then update as I move the node around. However right now although the points are updated, the drawing of the line is not.
If you need anymore code or information please ask.
del self.line.points[-2:]
self.line.points[-2:]+=node.center
These lines bypass operations that set the property, so the VertexInstruction doesn't know anything has changed and doesn't redraw itself.
They're a bit strange anyway, it would be simpler to just write:
self.line.points = self.line.points[:-2] + node.center
This would also update the instruction graphics, because you set the property directly rather than only modifying the existing list.