Open oscar0urselli opened 1 year ago
I've also somewhat been experimenting with this because it's a great idea. keeping track of all of the constructors, managers, rects, containers, and anchors somewhat feels overcomplicated once there are more than a few elements. Something like HTML would be perfect.
I've somewhat tried to implement it myself, and as of now it does work pretty well for what I've tried The data would look something like this
<?xml version="1.0"?>
<!DOCTYPE pygamegui>
<pygamegui>
<head>
<themes>
<theme>data/styles.json</theme>
<theme>data/myGlobalTheme.json</theme>
<theme>
<package>data.themes</package>
<resource>mainMenuTheme.json</resource>
</theme>
</themes>
</head>
<body resolution="800 600">
<panel rect="0 0 800 600">
<panel rect="0 0 600 400" id="title_rect">
<label rect="0 0 -1 -1" anchors="center: center">Game Title</label>
</panel>
<panel rect="0 0 600 100" anchors="top: top, top: #title_rect">
<button rect="0 0 100 100" id="play_button" class="menu_button" anchors="right: right, right_target: #options_button">Play</button>
<button rect="0 0 100 100" anchors="center: center" id="options_button" class="menu_button">Options</button>
<button rect="0 0 100 100" anchors="left: left, left_target: #options_button" id="quit_button" class="menu_button">Quit</button>
</panel>
</panel>
<window rect="0 0 200 200" anchors="center: center">
<image src="data/myimage.png" rect="0 0 100 100" anchors="center: center" />
</window>
</body>
</pygamegui>
I'd say that it works pretty well and isn't too verbose, but the structure is not final at all
This isn't the exact output of the example above, but it's very similar and was rendered with my markup implementation
However, there's some problems that I could see arising from creating a markup language though (somewhat ranting)
Everything has its problems though, and markup is definitely doable and would make life 100x easier, so I agree with you. Would like to know what you think about the direction I'm thinking of and if you agree or disagree with any of it.
Your XML implementation is almost the same as mine, except fot the theme part.
About the problems you mentionated:
This is the Python code I use in the project I'm working on:
import pygame
import pygame_gui
import os
import xml.etree.ElementTree as xmlET
class XMLStructure:
def __init__(self, xml_path: str, screen, manager) -> None:
self.xml_path = xml_path
self.screen = screen
self.manager = manager
# Allowed gui elements
self.allowed_elements = [
'image', 'button', 'textbox', 'textentryline', 'horizontalslider', 'dropdownmenu', 'panel'
]
# This dictionary contains all the gui elements
self.elements = {}
# Parse the XML file
self.parse()
def parse(self):
tree = xmlET.parse(self.xml_path)
root = tree.getroot()
self.expand(root)
return self.elements
def expand(self, parent):
"""
For every element contained in a tag, create an object and store it in the dictionary
"""
for child in parent:
if child.tag in self.allowed_elements:
self.elements[child.attrib['name']] = self.add_ui_element(child.tag, child.attrib, parent)
self.expand(child)
def add_ui_element(self, tag: str, attrib: dict, parent):
"""
Read the tag name and create the associated gui elements with all the attributes
"""
# In order to use the screen size in the pos and size attributes, without hardcoding it in the files,
# use 'WIDTH' and 'HEIGHT' as placeholders.
# Ex: pos="(WIDTH - 900, 0)"
# If the WIDTH of the screen is 1920 then the resulting tuple will be (1020, 0)
pos = eval(attrib['pos'].replace('WIDTH', str(self.screen[0])).replace('HEIGHT', str(self.screen[1])))
size = eval(attrib['size'].replace('WIDTH', str(self.screen[0])).replace('HEIGHT', str(self.screen[1])))
rect = pygame.Rect(pos, size)
container = None
object_id = None
visible = 1
# Set the container of the current element as the parent tag
if parent.tag in self.allowed_elements:
container = self.elements[parent.attrib['name']]
if 'class' in attrib and 'id' in attrib:
object_id = pygame_gui.core.ObjectID(class_id = '@' + attrib['class'], object_id = '#' + attrib['id'])
if 'visible' in attrib:
visible = int(attrib['visible'])
element = None
if tag == 'image':
element = pygame_gui.elements.UIImage(
relative_rect = rect,
image_surface = pygame.image.load(os.path.join(os.getcwd(), attrib['src'])),
manager = self.manager,
container = container,
object_id = object_id
)
elif tag == 'button':
element = pygame_gui.elements.UIButton(
relative_rect = rect,
text = attrib['text'],
tool_tip_text = attrib['tool_tip_text'],
manager = self.manager,
container = container,
object_id = object_id
)
if len(attrib['tool_tip_text']) == 0:
element.tool_tip_text = None
elif tag == 'textbox':
element = pygame_gui.elements.UITextBox(
relative_rect = rect,
html_text = attrib['html_text'],
manager = self.manager,
container = container,
object_id = object_id
)
elif tag == 'textentryline':
element = pygame_gui.elements.UITextEntryLine(
relative_rect = rect,
manager = self.manager,
object_id = object_id,
container = container
)
if 'white_list' in attrib:
element.set_allowed_characters(attrib['white_list'])
elif tag == 'horizontalslider':
element = pygame_gui.elements.UIHorizontalSlider(
relative_rect = rect,
start_value = float(attrib['start_value']),
value_range = eval(attrib['value_range']),
manager = self.manager,
container = container,
click_increment = float(attrib['click_increment']),
object_id = object_id
)
elif tag == 'dropdownmenu':
element = pygame_gui.elements.UIDropDownMenu(
relative_rect = rect,
options_list = attrib['options_list'].split(';'),
starting_option = attrib['starting_option'],
manager = self.manager,
object_id = object_id,
container = container,
visible = visible
)
elif tag == 'panel':
element = pygame_gui.elements.UIPanel(
relative_rect = rect,
starting_layer_height = int(attrib['starting_layer_height']),
manager = self.manager,
container = container,
object_id = object_id,
visible = visible
)
return element
It's not pretty but it works. Keep in mind I wrote this code just to get the things work in my project. I plan to rewrite this code and add more functionalities.
It's dope to see that we're both on about the same path. Like, literally all of my tag names are the same as yours. I wonder if there's some sort of way to communicate about this library beyond just the issues section because that would be really helpful to just be able to sort things out.
We can use Telegram or even better Discord, if you are ok with that, I will send you an email.
Hello!
I do not have anything against markup languages (the library already uses json for theming after all), nor them being being used for layout. What you are discussing here sounds a lot like XAML to me, at least that is the version of this concept I am most familiar with.
I'm not as convinced of a layout markup's usefulness in an interpreted programming language like python right now, as it is pretty quick in most of my applications so far to make a quick change to the layout in the code and then re-run the program without any lengthy compile times. Then again most of my usage has been pretty simple layouts, and if it does get more complicated then I tend to build a UIElement in the library to make it less complicated.
Though perhaps a markup language could be a half-way house to a visual layout editor program? Something which would undoubtably be useful.
I will also say that I am always trying to reduce the amount of boilerplate. I recently reduced a simple Button creation to this:
hello_button = UIButton((350, 280), 'Hello')
As a test (this works right now in 0.6.6), and am looking to extend these particular changes across the whole project where possible so that you don't always have to pass around UIManagers
and pygame.Rect
s.
So, I would say to your somewhat infectious markup enthusiasm - make sure you are definitely solving for the right problem. If markup is mostly just to eliminate boilerplate stuff when creating elements - lets try and cut straight to eliminating that first. If it more for dynamic refreshing, or a stepping stone towards a visual GUI layout editor then I'm definitely more into it.
It's funny because a builder was actually what I secretly had in mind, but that's definitely its own project in itself since its such a huge undertaking. I figured that it would need its own XML and I wanted it to be written in pygame_gui itself so that everything is guaranteed to be the same from program to project (it's also one of the reasons I proposed more layouts in #382 to be honest)
Here's like a really alpha prototype view that I just threw together in Inkscape
It's just a dream though for now, just a pitch
We can use Telegram or even better Discord, if you are ok with that, I will send you an email.
Yeah discord should be just fine if you're down, cobyj33#0489
I also started working with something similar, using XML-like markup for design the ui with pygame_gui, and i came up with this:
@component('''
<Window title="Some Window" top="10" left="10" width="{width}" height="400">
<Button name="button">Click-me!</Button>
</Window>
''')
class DummyUI:
button: pygame_gui.elements.UIButton
def __init__(self, manager) -> None:
self.__build__(manager, width=340)
Essentially, a decorator @component
is used to inject a __build__
method that builds the UI based on the template markup. Any elements in the markup with the name
attribute gets assigned as an attribute in the target class.
Then, this class can be used like:
manager = pygame_gui.UIManager((800, 600))
my_ui = DummyUI(manager)
my_ui.button # points to the <Button name="button"> element
For bigger templates, embedding them inside python code could be uglier, so the @component
decorator could accept a file path for the xml file.
I want to get in on this as well!
I wrote a proof of concept HTML file and interpreter using exclusively built-in functions. It's in a repo on my account.
Is your feature request related to a problem? Please describe. I'm currently working on a project where I need to create an high number of gui elements. The problem is that this way things start to get confusing and I end up with a verbose code.
Describe the solution you'd like In order to organize better my code, I moved the declarations of the gui elements in a separated XML file. This way is almost like writing a web page in HTML. Plus, using an XML file (or something like that), it could be possible to introduce a dynamic refresh of the elements every time there is a change.
Describe alternatives you've considered I don't have other ideas, I just wanted to share this concept I implemented (in a doubius way) in my code.
Additional context I understand that it is a very specific request and so there could be no interest in adding it, but I hope you like the idea.