Недавно я попробовал Kivy для личного проекта, который хотел реализовать: приложения, позволяющего создать персонажа DnD с некоторыми деталями. Этот персонаж будет храниться в файле JSON и загружаться на главном экране. Проблема в том, что при использовании флажков для выбора пола это вообще не работает, и я не могу найти проблему.
Вот файл main.py:
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.graphics import Color, Line
from kivy.uix.button import Button
from kivy.properties import ObjectProperty, ListProperty
import random
import json
import os
class MainScreen(Screen):
def get_random_color(self):
# Returns a random RGBA color
return [random.random() for _ in range(3)] + [0.8]
def add_character_button(self, character_data):
# Create a button for each saved character with a random color and only the name
button = Button(
text=character_data["name"],
size_hint_y=None,
height=50,
background_normal='',
background_color=character_data['color']
)
button.custom_color = button.background_color.copy()
button.bind(pos=self.update_border, size=self.update_border)
# Add a border to the button
with button.canvas.before:
Color(0, 0, 0, 1) # Set black color for border
self.border_line = Line(rectangle=(button.x, button.y, button.width, button.height), width=1)
button.bind(on_release=lambda btn: self.show_character_details(character_data))
self.ids.character_list.add_widget(button)
def update_border(self, button, _):
# Update border position and size when button size or position changes
self.border_line.rectangle = (button.x, button.y, button.width, button.height)
def load_user_characters(self):
# Load characters from a JSON file and add them to the screen
if os.path.exists('characters.json'):
with open('characters.json', 'r') as f:
characters = json.load(f)
for character in characters:
self.add_character_button(character)
def show_character_details(self, character_data):
character_details_screen = self.manager.get_screen('character_details')
character_details_screen.set_character_data(character_data) # Pass character data
self.manager.current = 'character_details'
class SecondScreen(Screen):
character_name = ObjectProperty(None)
character_age = ObjectProperty(None)
character_power1 = ObjectProperty(None)
character_power2 = ObjectProperty(None)
def save_character(self):
main_screen = self.manager.get_screen('main')
name = self.ids.character_name.text
age = self.ids.character_age.text
gender = 'Man' if self.ids.man_checkbox.active else 'Woman' if self.ids.woman_checkbox.active else 'Other'
power1 = self.ids.character_power1.text
power2 = self.ids.character_power2.text
color = main_screen.get_random_color()
character_data = {
'name': name,
'age': age,
'gender': gender,
'power1': power1,
'power2': power2,
'color': color
}
# Save character to the list
self.save_characters_to_file(character_data)
# Add button to the main screen dynamically
main_screen.add_character_button(character_data)
# Clear the inputs after saving
self.ids.character_name.text = ''
self.ids.character_age.text = ''
self.ids.character_power1.text = ''
self.ids.character_power2.text = ''
self.ids.man_checkbox.active = False
self.ids.woman_checkbox.active = False
# Switch back to the main screen
self.manager.current = 'main'
def save_characters_to_file(self, new_character):
characters = []
# Load existing characters from file if it exists
if os.path.exists('characters.json'):
with open('characters.json', 'r') as f:
characters = json.load(f)
# Add the new character
characters.append(new_character)
# Save updated character list to file
with open('characters.json', 'w') as f:
json.dump(characters, f)
selected_gender = None
def set_gender(self, checkbox, value):
if value == True: # If this checkbox is active
# Uncheck the others
if checkbox == self.ids.man_checkbox:
self.ids.woman_checkbox.active = False
elif checkbox == self.ids.woman_checkbox:
self.ids.man_checkbox.active = False
class CharacterDetailsScreen(Screen):
character_color = ListProperty([1, 1, 1, 1]) #default
character_data = None #default
def on_enter(self):
if self.character_data: # Check if character_data is set
self.ids.character_name.text = self.character_data['name']
self.ids.character_age.text = self.character_data['age']
self.ids.character_gender.text = self.character_data['gender']
self.ids.character_power1.text = self.character_data['power1']
self.ids.character_power2.text = self.character_data['power2']
self.character_color = self.character_data['color']
def set_character_data(self, character):
self.character_data = character # Store character data
class RosterApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(MainScreen(name='main'))
sm.add_widget(SecondScreen(name='second'))
sm.add_widget(CharacterDetailsScreen(name='character_details'))
# Load characters into the main screen when the app starts
sm.get_screen('main').load_user_characters()
return sm
if __name__ == '__main__':
RosterApp().run()
:
FloatLayout:
orientation: 'vertical'
# Light yellow background
FloatLayout:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: 'img.png'
# Round button in the bottom right corner
Button:
text: ""
size_hint: None, None
size: 50, 50
pos_hint: {'right': 0.95, 'bottom': 0.05}
background_normal: 'plus.webp'
background_color: 0.737, 0.635, 0.514, 1 # light almost-off golden
border: (50, 50, 50, 50) # Simulates a round border
canvas.before:
Color:
rgba: 0.086, 0.086, 0.086, 0.75 # Same dark gray color
Ellipse: # Draw an ellipse to make the button round
pos: self.pos
size: self.size
on_release:
app.root.current = 'second'
# dark gray top bar with app name
FloatLayout:
size_hint_y: None
height: 50
pos_hint: {'top': 1}
canvas.before:
Color:
rgba: 0.086, 0.086, 0.086, 0.75 # dark gray color
Rectangle:
pos: self.pos
size: self.size
Label:
text: "DnD Roster Panel"
color: 1, 1, 1, 1 # white text
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
ScrollView:
size_hint: (1, 0.8)
pos_hint: {'top': 0.9}
BoxLayout:
id: character_list
orientation: 'vertical'
size_hint_y: None
height: self.minimum_height # Adjusts height to fit content
spacing: 10
padding: 10
:
FloatLayout:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: 'img.png'
FloatLayout:
orientation: 'vertical'
FloatLayout:
size_hint_y: None
height: 50
pos_hint: {'top': 1}
canvas.before:
Color:
rgba: 0.086, 0.086, 0.086, 0.75 # dark gray color
Rectangle:
pos: self.pos
size: self.size
Label:
text: "DnD Roster Panel"
color: 1, 1, 1, 1 # white text
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
ScrollView:
size_hint: 1, 0.85 # Make it take up the remaining space
pos_hint: {'top': 0.85}
do_scroll_x: False # Disable horizontal scrolling
BoxLayout:
orientation: 'vertical'
size_hint_y: None
height: self.minimum_height
padding: [20, 20, 20, 20] # Add some padding for spacing
spacing: 20 # Add some space between elements
Label:
text: 'Name'
color: 1, 1, 1, 1
halign: "center"
TextInput:
id: character_name
hint_text: ""
multiline: False
size_hint_y: None
height: 40
Label:
text: 'Age'
color: 1, 1, 1, 1
halign: "center"
TextInput:
id: character_age
hint_text: ""
multiline: False
input_filter: 'int'
size_hint_y: None
height: 40
Label:
text: 'Select Gender'
color: 1, 1, 1, 1
halign: "center"
GridLayout:
cols: 2
spacing: 40
padding: 20
size_hint_y: None
height: self.minimum_height
Label:
text: "Man"
CheckBox:
id: man_checkbox
on_active: root.set_gender(self, self.active) # Uncheck female if male is checked
Label:
text: "Woman"
CheckBox:
id: woman_checkbox
on_active: root.set_gender(self, self.active) # Uncheck male if female is checked
Label:
text: 'First Power'
color: 1, 1, 1, 1
halign: "center"
TextInput:
id: character_power1
hint_text: ""
multiline: False
size_hint_y: None
height: 40
Label:
text: 'Second Power'
color: 1, 1, 1, 1
halign: "center"
TextInput:
id: character_power2
hint_text: ""
multiline: False
size_hint_y: None
height: 40
Button:
text: "Save Character"
size_hint_y: None
height: 50
on_release:
root.save_character() # Call save_character method
:
FloatLayout:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: 'img.png'
FloatLayout:
orientation: 'vertical'
FloatLayout:
size_hint_y: None
height: 50
pos_hint: {'top': 1}
canvas.before:
Color:
rgba: self.parent.parent.character_color
Rectangle:
pos: self.pos
size: self.size
Label:
text: 'DnD Roster Panel'
color: 1, 1, 1, 1 # white text
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
Label:
id: character_name
text: 'Name'
size_hint_y: None
pos_hint: {'center_x': 0.5, 'top': 0.85}
height: 20
font_size: 30
GridLayout:
cols: 2
spacing: 10
padding: 20
size_hint_y: None
height: self.minimum_height # Adjusts height to fit content
# Age
Label:
text: "Age: "
size_hint_y: None
height: 50
canvas.before:
Color:
rgba: 1, 1, 1, 1 # Black border color
Line:
rectangle: (self.x, self.y, self.width, self.height)
Label:
id: character_age
text: "Age:"
size_hint_y: None
height: 50
canvas.before:
Color:
rgba: 1, 1, 1, 1 # Black border color
Line:
rectangle: (self.x, self.y, self.width, self.height)
# Gender
Label:
text: "Gender: "
size_hint_y: None
height: 50
canvas.before:
Color:
rgba: 1, 1, 1, 1 # Black border color
Line:
rectangle: (self.x, self.y, self.width, self.height)
Label:
id: character_gender
text: "Gender:"
size_hint_y: None
height: 50
canvas.before:
Color:
rgba: 1, 1, 1, 1 # Black border color
Line:
rectangle: (self.x, self.y, self.width, self.height)
# First Power
Label:
text: "First Power:"
size_hint_y: None
height: 50
canvas.before:
Color:
rgba: 1, 1, 1, 1 # Black border color
Line:
rectangle: (self.x, self.y, self.width, self.height)
Label:
id: character_power1
text: "First Power:"
size_hint_y: None
height: 50
canvas.before:
Color:
rgba: 1, 1, 1, 1 # Black border color
Line:
rectangle: (self.x, self.y, self.width, self.height)
# Second Power
Label:
text: "Second Power:"
size_hint_y: None
height: 50
canvas.before:
Color:
rgba: 1, 1, 1, 1 # Black border color
Line:
rectangle: (self.x, self.y, self.width, self.height)
Label:
id: character_power2
text: "Second Power:"
size_hint_y: None
height: 50
canvas.before:
Color:
rgba: 1, 1, 1, 1 # Black border color
Line:
rectangle: (self.x, self.y, self.width, self.height)
Button:
text: ""
size_hint: None, None
size: 50, 50
pos_hint: {'left': 0.95, 'bottom': 0.05}
background_normal: 'back.png'
background_color: 0.737, 0.635, 0.514, 1 # light almost-off golden
border: (50, 50, 50, 50) # Simulates a round border
canvas.before:
Color:
rgba: 0.086, 0.086, 0.086, 0.75 # Same dark gray color
Ellipse: # Draw an ellipse to make the button round
pos: self.pos
size: self.size
on_release:
app.root.current = 'main'
Мне бы хотелось, чтобы приложение позволяло вам устанавливать и снимать флажки, но ограничивало выбор только одним полем (только один пол).
Сам код тоже может быть очень запутанным, но на данный момент он работает нормально XD
Недавно я попробовал Kivy для личного проекта, который хотел реализовать: приложения, позволяющего создать персонажа DnD с некоторыми деталями. Этот персонаж будет храниться в файле JSON и загружаться на главном экране. Проблема в том, что при использовании флажков для выбора пола это вообще не работает, и я не могу найти проблему. Вот файл main.py: [code]from kivy.app import App from kivy.uix.screenmanager import ScreenManager, Screen from kivy.graphics import Color, Line from kivy.uix.button import Button from kivy.properties import ObjectProperty, ListProperty import random import json import os
class MainScreen(Screen): def get_random_color(self): # Returns a random RGBA color return [random.random() for _ in range(3)] + [0.8]
def add_character_button(self, character_data): # Create a button for each saved character with a random color and only the name button = Button( text=character_data["name"], size_hint_y=None, height=50, background_normal='', background_color=character_data['color'] ) button.custom_color = button.background_color.copy() button.bind(pos=self.update_border, size=self.update_border)
# Add a border to the button with button.canvas.before: Color(0, 0, 0, 1) # Set black color for border self.border_line = Line(rectangle=(button.x, button.y, button.width, button.height), width=1)
def update_border(self, button, _): # Update border position and size when button size or position changes self.border_line.rectangle = (button.x, button.y, button.width, button.height)
def load_user_characters(self): # Load characters from a JSON file and add them to the screen if os.path.exists('characters.json'): with open('characters.json', 'r') as f: characters = json.load(f) for character in characters: self.add_character_button(character)
def show_character_details(self, character_data): character_details_screen = self.manager.get_screen('character_details') character_details_screen.set_character_data(character_data) # Pass character data self.manager.current = 'character_details'
# Load existing characters from file if it exists if os.path.exists('characters.json'): with open('characters.json', 'r') as f: characters = json.load(f)
# Add the new character characters.append(new_character)
# Save updated character list to file with open('characters.json', 'w') as f: json.dump(characters, f)
selected_gender = None def set_gender(self, checkbox, value): if value == True: # If this checkbox is active # Uncheck the others if checkbox == self.ids.man_checkbox: self.ids.woman_checkbox.active = False elif checkbox == self.ids.woman_checkbox: self.ids.man_checkbox.active = False
class CharacterDetailsScreen(Screen): character_color = ListProperty([1, 1, 1, 1]) #default character_data = None #default def on_enter(self): if self.character_data: # Check if character_data is set self.ids.character_name.text = self.character_data['name'] self.ids.character_age.text = self.character_data['age'] self.ids.character_gender.text = self.character_data['gender'] self.ids.character_power1.text = self.character_data['power1'] self.ids.character_power2.text = self.character_data['power2'] self.character_color = self.character_data['color']
def set_character_data(self, character): self.character_data = character # Store character data
class RosterApp(App): def build(self): sm = ScreenManager() sm.add_widget(MainScreen(name='main')) sm.add_widget(SecondScreen(name='second')) sm.add_widget(CharacterDetailsScreen(name='character_details'))
# Load characters into the main screen when the app starts sm.get_screen('main').load_user_characters()
return sm
if __name__ == '__main__': RosterApp().run()
[/code] И мой файл Kivy: [code]: FloatLayout: orientation: 'vertical'
ScrollView: size_hint: 1, 0.85 # Make it take up the remaining space pos_hint: {'top': 0.85} do_scroll_x: False # Disable horizontal scrolling
BoxLayout: orientation: 'vertical' size_hint_y: None height: self.minimum_height padding: [20, 20, 20, 20] # Add some padding for spacing spacing: 20 # Add some space between elements
Button: text: "" size_hint: None, None size: 50, 50 pos_hint: {'left': 0.95, 'bottom': 0.05} background_normal: 'back.png' background_color: 0.737, 0.635, 0.514, 1 # light almost-off golden border: (50, 50, 50, 50) # Simulates a round border canvas.before: Color: rgba: 0.086, 0.086, 0.086, 0.75 # Same dark gray color Ellipse: # Draw an ellipse to make the button round pos: self.pos size: self.size on_release: app.root.current = 'main' [/code] Мне бы хотелось, чтобы приложение позволяло вам устанавливать и снимать флажки, но ограничивало выбор только одним полем (только один пол). Сам код тоже может быть очень запутанным, но на данный момент он работает нормально XD