I'm building a random weather generation app for Dungeons and Dragons, more specifically Rime of the Frostmaiden. I'm fairly new to Python and even newer to Kivy. I've been trying to no avail to dynamically change the label on a Kivy Popup but can't quite figure it out. I'm currently trying to change just the label text, but can only just manage to open the popup. I've tried to cobble together various solutions to similar problems I found, but always seem to fall short. I'm a little confused about how to pass information and commands between the main app class and the popup.
In the below code the commented out print commands is where I'd like to change the popup message and display it to the user.
I'm also planning on passing the image file names for each weather effect from the weather_conditions dictionary to a separate popup at the end of the simulation.
Any help in understanding how to pass commands to a popup from the main app would be greatly appreciated. Though I'm keen to learn as much as I can, and any best practice tips, constructive criticism, etc would be welcomed.
.py file
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.uix.popup import Popup
import random
Window.size = (360, 600)
wc_info_p = ''
wc_info_s = ''
duration_p = ''
duration_s = ''
next_weather_event=''
max_duration_p = ''
max_duration_s = ''
error_message = ''
weather_conditons = {
1: {'weather': 'Clear Skies', 'dice': 2, 'sides': 4, 'event1': 2, 'event2': 5, 'event3': 9, 'img': 'CS1.png'},
2: {'weather': 'Light Cloud Coverage', 'dice': 1, 'sides': 4, 'event2': 1, 'event1': 3, 'event3': 5, 'img': 'LCC2.png'},
3: {'weather': 'Overcast', 'dice': 1, 'sides': 4, 'event1': 2, 'event2': 4, 'event3': 11, 'img': 'OC3.png'},
4: {'weather': 'Storm Clouds', 'dice': 1, 'sides': 4, 'event1': 3, 'event2': 6, 'event3': 10, 'img': 'SC4.png'},
5: {'weather': 'Light Snow Dusting', 'dice': 1, 'sides': 4, 'event1': 2, 'event2': 2, 'event3': 10, 'img': 'LS5.png'},
6: {'weather': 'Heavy Snowfall', 'dice': 1, 'sides': 4, 'event1': 3, 'event2': 7, 'event3': 11, 'img': 'HS6.png'},
7: {'weather': 'Blizzard', 'dice': 2, 'sides': 4, 'event1': 4, 'event2': 6, 'event3': 8, 'img': 'BZ7.png'},
8: {'weather': 'White Out', 'dice': 1, 'sides': 4, 'event1': 5, 'event2': 6, 'event3': 7, 'img': 'WO8.png'},
9: {'weather': 'Extreme Fog', 'dice': 1, 'sides': 4, 'event1': 1, 'event2': 5, 'event3': 12, 'img': 'EF9.png'},
10: {'weather': 'Lightning Storm', 'dice': 2, 'sides': 4, 'event1': 4, 'event2': 5, 'event3': 6, 'img': 'LS10.png'},
11: {'weather': 'Hail Storm', 'dice': 1, 'sides': 4, 'event1': 3, 'event2': 6, 'event3': 7, 'img': 'HS11.png'},
12: {'weather': 'Wind Vortex', 'dice': 1, 'sides': 4, 'event1': 1, 'event2': 1, 'event3': 1, 'img': 'WV12.png'},
}
def event_calc(dur, maxdur):
event_percentage = (float(dur) / float(maxdur))*100
if 0 < event_percentage <= 50:
event = 'event1'
elif 50 < event_percentage <= 75:
event = 'event2'
else:
event = 'event3'
return event
class MyLayout(Widget):
greeting = ObjectProperty(None)
pwe = ObjectProperty(None)
pcb_one = ObjectProperty(None)
pcb_two = ObjectProperty(None)
swe = ObjectProperty(None)
scb_one = ObjectProperty(None)
scb_two = ObjectProperty(None)
prolls = ObjectProperty(None)
proll_one = ObjectProperty(None)
proll_two = ObjectProperty(None)
srolls = ObjectProperty(None)
sroll_one = ObjectProperty(None)
sroll_two = ObjectProperty(None)
blank_one = ObjectProperty(None)
calc_one = ObjectProperty(None)
calc_two = ObjectProperty(None)
b_roll = ObjectProperty(None)
b_clear_two = ObjectProperty(None)
i_rotfm = ObjectProperty(None)
def checkbox_click(self, instance, value, id_name):
if id_name == 'pcb_one':
self.proll_one.opacity = 1
self.proll_one.disabled = False
self.proll_two.opacity = 0.5
self.proll_two.disabled = True
if id_name == 'pcb_two':
self.proll_one.opacity = 0
self.proll_one.disabled = True
self.proll_one.text = ''
self.proll_two.opacity = 0
self.proll_two.disabled = True
self.proll_two.text = ''
#Disable Secondary Roll if Primary is disabled
self.scb_two.active = True
self.sroll_one.opacity = 0
self.sroll_one.disabled = True
self.sroll_one.text = ''
self.sroll_two.opacity = 0
self.sroll_two.disabled = True
self.sroll_two.text = ''
if id_name == 'scb_one':
#Secondary Roll cannot be enabled if Primary is disabled
if self.pcb_two.active == True:
self.scb_two.active = True
self.sroll_one.opacity = 0
self.sroll_one.disabled = True
self.sroll_one.text = ''
self.sroll_two.opacity = 0
self.sroll_two.disabled = True
self.sroll_two.text = ''
else:
self.sroll_one.opacity = 0.5
self.sroll_one.disabled = True
self.sroll_two.opacity = 0.5
self.sroll_two.disabled = True
if id_name == 'scb_two':
self.sroll_one.opacity = 0
self.sroll_one.disabled = True
self.sroll_one.text = ''
self.sroll_two.opacity = 0
self.sroll_two.disabled = True
self.sroll_two.text = ''
if self.pcb_two.active == True and self.scb_two.active == True:
self.b_roll.text = 'Auto'
elif self.pcb_one.active == True:
self.b_roll.text = 'Roll'
clicks = 0
def roll(self):
global weather_conditons
global wc_info_p
global wc_info_s
global duration_p
global duration_s
global next_weather_event
global max_duration_p
global max_duration_s
global error_message
self.clicks = 1
stage = min(4, self.clicks)
if stage == 1 and self.pcb_two.active == True:
wc_info_p = weather_conditons.get(random.randint(1,12), None)
duration_p = random.randint(wc_info_p['dice'], wc_info_p['dice'] * wc_info_p['sides'])
max_duration_p = float(wc_info_p['dice'] * wc_info_p['sides'])
next_weather_event = wc_info_p[event_calc(duration_p, max_duration_p)]
wc_info_s = weather_conditons.get(next_weather_event, None)
duration_s = random.randint(wc_info_s['dice'], wc_info_s['dice'] * wc_info_s['sides'])
max_duration_s = float(wc_info_s['dice'] * wc_info_s['sides'])
self.clicks = 0
print(wc_info_p['weather'], 'for', duration_p , 'hours', wc_info_s['weather'], 'for', duration_s , 'hours')
elif stage == 1:
if self.proll_one.text == '':
#print('please enter roll 1')
self.clicks -= 1
#Opening the Popup is as far as I've managed to get.
ErrorMsg().open()
elif int(self.proll_one.text) < 1 or int(self.proll_one.text) > 12:
#print('please enter a number between 1 and 12')
self.clicks -= 1
else:
wc_info_p = weather_conditons.get(int(self.proll_one.text), None)
self.proll_two.opacity = 1
self.proll_two.disabled = False
self.proll_two.hint_text = f"Roll {wc_info_p['dice']} D{wc_info_p['sides']}"
elif stage == 2 and self.scb_two.active == True:
if self.proll_two.text == '':
#print('please enter roll 2')
self.clicks -= 1
elif int(self.proll_two.text) < 1 or int(self.proll_two.text) > float(wc_info_p['dice'] * wc_info_p['sides']):
#print('please enter a number between 1 and ', float(wc_info_p['dice'] * wc_info_p['sides']))
self.clicks -= 1
elif self.scb_two.active == True:
duration_p = int(self.proll_two.text)
max_duration_p = float(wc_info_p['dice'] * wc_info_p['sides'])
next_weather_event = wc_info_p[event_calc(duration_p, max_duration_p)]
wc_info_s = weather_conditons.get(next_weather_event, None)
duration_s = random.randint(wc_info_s['dice'], wc_info_s['dice'] * wc_info_s['sides'])
max_duration_s = float(wc_info_s['dice'] * wc_info_s['sides'])
self.clicks = 0
#print(wc_info_p['weather'], 'for', duration_p , 'hours', wc_info_s['weather'], 'for', duration_s , 'hours')
self.clear_content()
elif stage == 2:
if self.proll_two.text == '':
print('please enter roll 2')
self.clicks -= 1
else:
duration_p = int(self.proll_two.text)
max_duration_p = float(wc_info_p['dice'] * wc_info_p['sides'])
next_weather_event = wc_info_p[event_calc(duration_p, max_duration_p)]
wc_info_s = weather_conditons.get(next_weather_event, None)
if self.scb_one.active == True:
self.sroll_one.opacity = 1
self.sroll_one.disabled = False
elif stage == 3:
if self.sroll_one.disabled == False:
if self.sroll_one.text == '':
#print('please enter roll 3')
self.clicks -= 1
elif int(self.sroll_one.text) < 1 or int(self.sroll_one.text) > 12:
#print('please enter a number between 1 and 12')
self.clicks -= 1
else:
next_weather_event = wc_info_p[event_calc(int(self.sroll_one.text), 12)]
wc_info_s = weather_conditons.get(next_weather_event, None)
self.sroll_two.opacity = 1
self.sroll_two.disabled = False
self.sroll_two.hint_text = f"Roll {wc_info_s['dice']} D{wc_info_s['sides']}"
elif stage == 4:
if self.sroll_two.disabled == False:
if self.sroll_two.text == '':
#print('please enter roll 4')
self.clicks -= 1
elif int(self.sroll_two.text) < 1 or int(self.sroll_two.text) > float(wc_info_s['dice'] * wc_info_s['sides']):
#print('please enter a number between 1 and ', float(wc_info_s['dice'] * wc_info_s['sides']))
self.clicks -= 1
else:
duration_s = int(self.sroll_two.text)
print(wc_info_p['weather'], 'for', duration_p , 'hours', wc_info_s['weather'], 'for', duration_s , 'hours')
self.clear_content()
def clear_content(self):
self.pcb_one.active = True
self.scb_one.active = True
self.clicks = 0
reset_inputs = [self.proll_two, self.sroll_one, self.sroll_two]
for reset_input in reset_inputs:
reset_input.opacity = 0.5
reset_input.disabled: 'True'
reset_input.text = ""
reset_input.hint_text = ''
self.proll_one.text = ""
self.proll_one.opacity = 1
self.proll_one.disabled = False
class ErrorMsg(Popup):
pass
class Icewind(App):
def build(self):
return MyLayout()
if __name__ == "__main__":
Icewind().run()
.kv file
#:import Factory kivy.factory.Factory
<Button>
background_color: '#50047d'
<MyLayout>
greeting:greeting
pwe:pwe
pcb_one:pcb_one
pcb_two:pcb_two
swe:swe
scb_one:scb_one
scb_two:scb_two
prolls:prolls
proll_one:proll_one
proll_two:proll_two
srolls:srolls
sroll_one:sroll_one
sroll_two:sroll_two
blank_one:blank_one
calc_one:calc_one
calc_two:calc_two
b_roll:b_roll
b_clear:b_clear
i_rotfm:i_rotfm
GridLayout:
cols:1
size: root.width, root.height
spacing: 2
padding: 5
Image:
id: i_rotfm
source: 'RotFM.png'
allow_stretch: True
Label:
id: greeting
text: 'Rime of the Frostmaiden\nIcewind Dale Weather Generator'
bold: True
color: '#ffffff'
size_hint: (1, 0.25)
halign: 'center'
GridLayout:
cols:3
size: root.width, root.height
spacing_horizontal: 2
spacing_vertical: 1
Label:
id: blank_one
text: ''
bold: True
color: '#ffffff'
size_hint: (1, 0.25)
Label:
id: calc_one
text: 'Roll'
bold: True
color: '#ffffff'
size_hint: (0.6, 0.25)
Label:
id: calc_two
text: 'Random'
bold: True
color: '#ffffff'
size_hint: (0.6, 0.25)
Label:
id: pwe
text: 'Primary Weather'
bold: True
color: '#ffffff'
size_hint: (1, 0.25)
text_size: self.width, None
CheckBox:
id: pcb_one
active: True
group: 'check_one'
on_active: root.checkbox_click(self, self.active, 'pcb_one')
allow_no_selection: False
size_hint: (0.6, 0.25)
CheckBox:
id: pcb_two
on_active: root.checkbox_click(self, self.active, 'pcb_two')
group: 'check_one'
allow_no_selection: False
size_hint: (0.6, 0.25)
Label:
id: swe
text: 'Secondary Weather'
bold: True
color: '#ffffff'
size_hint: (1, 0.25)
text_size: self.width, None
CheckBox:
id: scb_one
active: True
group: 'check_two'
on_active: root.checkbox_click(self, self.active, 'scb_one')
allow_no_selection: False
size_hint: (0.6, 0.25)
CheckBox:
id: scb_two
on_active: root.checkbox_click(self, self.active, 'scb_two')
group: 'check_two'
allow_no_selection: False
size_hint: (0.6, 0.25)
Label:
id: prolls
text: 'Enter Primary Rolls'
bold: True
color: '#ffffff'
size_hint: (1, 0.25)
text_size: self.width, None
TextInput:
id: proll_one
hint_text: 'Roll a D12'
multiline: False
input_type: 'number'
halign: 'center'
padding_y: [self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), 0]
size_hint: (0.6, 0.25)
TextInput:
id: proll_two
hint_text: ''
multiline: False
input_type: 'number'
halign: 'center'
padding_y: [self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), 0]
opacity: 0.5
disabled: True
size_hint: (0.6, 0.25)
Label:
id: srolls
text: 'Enter Secondary Rolls'
bold: True
color: '#ffffff'
size_hint: (1, 0.25)
text_size: self.width, None
TextInput:
id: sroll_one
hint_text: ''
multiline: False
input_type: 'number'
halign: 'center'
padding_y: [self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), 0]
opacity: 0.5
disabled: True
size_hint: (0.6, 0.25)
TextInput:
id: sroll_two
hint_text: ''
multiline: False
input_type: 'number'
halign: 'center'
padding_y: [self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), 0]
opacity: 0.5
disabled: True
size_hint: (0.6, 0.25)
GridLayout:
cols:2
size: root.width, root.height
spacing_horizontal: 2
spacing_vertical: 1
size_hint: (1, 0.25)
Button:
id: b_roll
text: 'Roll'
bold: True
size_hint: (1, 0.25)
on_press: root.roll()
Button:
id: b_clear
text: 'Clear'
bold: True
size_hint: (1, 0.25)
on_press: root.clear_content()
<ErrorMsg@Popup>
title: ""
separator_height: 0
size_hint: (0.5, 0.2)
pos_hint: {'center':0.5, 'y':0.7}
GridLayout:
cols:1
Label:
id: error_text
text: 'label test'
text_size: self.size
valign: 'top'
CodePudding user response:
If you add a property for the Label
text in your ErrorMsg
, like this:
<ErrorMsg@Popup>
text: 'Abba' # property used in Label text below
title: ""
separator_height: 0
size_hint: (0.5, 0.2)
pos_hint: {'center':0.5, 'y':0.7}
GridLayout:
cols:1
Label:
id: error_text
text: root.text # refers to root level text property
text_size: self.size
valign: 'top'
Then you can change that Label
text by saving a reference to the ErrorMsg
instance and using that to change the text
property. So, in your roll()
method:
# Opening the Popup is as far as I've managed to get.
self.popup = ErrorMsg()
self.popup.text = 'please enter roll 1'
self.popup.open()
Then, elsewhere in the MyLayout
class you can change the text:
self.popup.text = 'please enter a number between 1 and 12'