Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use of a markup language to design the ui #370

Open
oscar0urselli opened this issue Dec 9, 2022 · 9 comments
Open

Use of a markup language to design the ui #370

oscar0urselli opened this issue Dec 9, 2022 · 9 comments
Labels
enhancement New feature or request

Comments

@oscar0urselli
Copy link

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.

@oscar0urselli oscar0urselli added the enhancement New feature or request label Dec 9, 2022
@cobyj33
Copy link
Contributor

cobyj33 commented Dec 21, 2022

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
Pygame GUI XML Main Menu Example

However, there's some problems that I could see arising from creating a markup language though (somewhat ranting)

  • It makes assumptions about how pygame_gui works, and it somewhat boxes the project in, such as how almost all elements have pre-defined rects and anchors, which may not be true if new layouts are added for containers.
  • There'd need to be some sort of spec for how the markup works, or some version control for the markup itself while the library updates, so that updates don't "break" old markup files, like HTML5 as opposed to just "HTML".
  • Markup in nature also comes with it's own set of problems since it's static in nature and the state of a video game changes a lot. There'd need to be some way to easily change the state of the GUI with code, some sort of Document Object Model to help edit the GUI directly after loading it and bind events to elements.
  • There'd need to be a way to create custom components and use them in other XML files. For example, if I had a custom inventory slot that contained a button, image, and a few labels, I should be able to use an element rather than copying the same markdown many times. To add on, I should be able to add as many of these elements as I'd want in code, and they should automatically change with whatever is in that inventory slot in the game. something similar to a React Component should be really helpful.
  • We'd need to decide on which "markup" language would really be best at making concise, clean GUI's that will be easier to construct than code(XML, JSON, YAML, etc...). I think XML is probably the way to go, just for the familiarity with most people who have worked with the web, but something like YAML could have less verbosity

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.

@oscar0urselli
Copy link
Author

Your XML implementation is almost the same as mine, except fot the theme part.

About the problems you mentionated:

  • I do agree about the needs of a standard on how the markup should works, otherwise it will be just pure caos.
  • For sure we have to implement some sort of DOM in order to simplify the the things. At the same time I don't think it's necessary to implement immediately every function HTML has.
  • The creation of custom components could be implemented through the use of a basic template engine. This way you would write the block of code once and then use it everywhere.
  • I think we should stick with XML. It has an HTML-like feeling and as you said most people know it.

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.

@cobyj33
Copy link
Contributor

cobyj33 commented Dec 22, 2022

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.

@oscar0urselli
Copy link
Author

We can use Telegram or even better Discord, if you are ok with that, I will send you an email.

@MyreMylar
Copy link
Owner

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.Rects.

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.

@cobyj33
Copy link
Contributor

cobyj33 commented Dec 23, 2022

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

pygameguibuilder

It's just a dream though for now, just a pitch

@cobyj33
Copy link
Contributor

cobyj33 commented Dec 23, 2022

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

@dylanxyz
Copy link

dylanxyz commented Jun 7, 2024

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.

@xXLarryTFVWXx
Copy link

xXLarryTFVWXx commented Jun 25, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants