Home > Net >  Dynamic Change to properties of a Kivy Popup
Dynamic Change to properties of a Kivy Popup

Time:09-07

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'
  • Related