GMUI Framework for Godot Engine

repository·master·Indexed 41 days ago

https://github.com/godothub/gmui

An MVVM (Model-View-ViewModel) UI framework for Godot 4.x that enables building complex interfaces using a declarative XML-like syntax. It features reactive state management, bidirectional data binding via g-model, conditional rendering with g-if/g-else, and dynamic list rendering with g-for. GMUI supports componentization with slots, page navigation via jump_to, and conditional compilation for different platforms.

Tokens
8.6K
Snippets
29
Records
34
Agent score
50%

What's inside GMUI

  1. How GMUI Pages and Components Work

    master

    GMUI uses .gmui files to define UI. These files are compiled into standard Godot .tscn scenes located in addons/gmui/dist/scenes/pages/{page_name}.tscn.

    • Pages: Created in a pages folder. They can be set as the main scene or instantiated within other scenes.
    • Components: Reusable UI pieces. You can import them using the @import('ComponentName', 'path/to/file.gmui') syntax within a <Script> tag.
    • Logic: Logic is written inside a <Script> tag at the bottom of the .gmui file using GDScript.
  2. Use Slots and Templates for Content Projection

    master

    GMUI supports a slot/template system similar to Vue's slots for injecting content into child components.

    Basic Slots

    In the child scene, define a <Slot /> tag. In the parent scene, wrap the content you want to inject inside a <Template> tag.

    Child XML:

    <Node2D name="SubScene2">
    	<Slot></Slot>
    </Node2D>

    Parent XML:

    <Scene name="SubScene2" scene_xml_path="res://layouts/sub_scene2.xml">
    	<Template>
    		<Node2D name="node"></Node2D>
    	</Template>
    </Scene>

    Named Slots

    To handle multiple injection points, use the name attribute on the <Slot> and the slot attribute on the <Template>.

    Child XML (Defining multiple slots):

    <Node2D name="SubScene1">
    	<Slot name="slot1"></Slot>
    	<Slot name="slot2"></Slot>
    </Node2D>

    Parent XML (Targeting specific slots):

    <Scene name="SubScene1" scene_xml_path="res://layouts/sub_scene1.xml">
    	<Template slot="slot1">
    		<Node name="node1"></Node>
    	</Template>
    	<Template slot="slot2">
    		<Node name="node2"></Node>
    	</Template>
    </Scene>
    <!-- Parent XML Example with Named Slots -->
    <Scene name="SubScene1" scene_xml_path="res://layouts/sub_scene1.xml">
    	<Template slot="slot1">
    		<Node name="node1"></Node>
    	</Template>
    	<Template slot="slot2">
    		<Node name="node2"></Node>
    	</Template>
    </Scene>
  3. Use Bidirectional Data Binding with `g-model`

    master

    GMUI supports bidirectional data binding using the g-model attribute. To implement logic, add a <Script> tag at the bottom of your .gmui file. Use the reactive() function to initialize your data state.

    To access or modify reactive data in your script, use data.rget('key') to get a value and data.rset('key', value) to set one.

    <Row align="center">
        <Column align="center">
            <Row>
                <Label text="Username"></Label>
                <LineEdit placeholder_text="Enter username" g-model="username"></LineEdit>
            </Row>
            <Row>
                <Button text="Login" ref="loginBtn"></Button>
            </Row>
        </Column>
    </Row>
    
    <Script>
    @onready var data = await reactive({'username': 'name'})
    func _mounted():
        gmui.refs['loginBtn'].rnode.pressed.connect(
            func():
            print('username:', data.rget('username'))
        )
    </Script>
  4. How bidirectional data binding works in GMUI

    master

    GMUI uses a reactive data model to synchronize UI elements with logic.

    1. Reactive Data: Initialize data using the await reactive({...}) function within a <Script> tag. This returns a reactive object.
    2. Binding: Use the g-model attribute on components (like LineEdit) to create a two-way binding with a key in your reactive data.
    3. Accessing Data: Use .rget('key') to read values and .rset('key', value) to update them. This ensures the UI stays in sync with the underlying state.
    4. Lifecycle Hooks: Use _mounted() for setup logic and _updated() to react to data changes.
    <LineEdit g-model="username"></LineEdit>
    
    <Script>
    @onready var data = await reactive({'username': 'name'})
    
    func _updated():
        print('username changed to:', data.rget('username'))
    </Script>
  5. How bidirectional data binding works with g-model

    master

    GMUI uses the g-model directive to create bidirectional data binding between UI components and reactive data.

    To implement logic, add a <Script> tag at the bottom of your .gmui file. Use the reactive() function to initialize your data state. You can access and modify this data using rget() and rset() methods on the reactive object.

    Key Concepts:

    • reactive({'key': 'value'}): Initializes a reactive data object (must be awaited).
    • g-model="key": Binds a component property to a reactive key.
    • rget('key'): Retrieves the value of a reactive property.
    • rset('key', 'value'): Sets the value of a reactive property.
    <Row align="center">
        <Column align="center">
            <Row>
                <Label text="Username"></Label>
                <LineEdit placeholder_text="Enter username" g-model="username"></LineEdit>
            </Row>
            <Row>
                <Button text="Login" ref="loginBtn"></Button>
            </Row>
        </Column>
    </Row>
    
    <Script>
    @onready var data = await reactive({'username': 'name'})
    func _mounted():
        gmui.refs['loginBtn'].rnode.pressed.connect(
            func():
            print('username:', data.rget('username'))
        )
    </Script>
  6. How slots work in GMUI components

    master

    Slots allow you to pass content into components, similar to Vue.js.

    Default Slots

    Define a <Slot /> tag in your component. When using the component, any children wrapped inside the component tags will replace the slot.

    Named Slots

    For multiple injection points, use named slots with <Template #name="value"> in the consumer and <Slot name="name" /> in the component.

    Slot Props (Passing Data to Slots)

    You can pass data from the component back to the slot content by declaring a variable in the slot declaration and receiving it in the template.

    Component Definition:

    <Slot text="my text"></Slot>

    Component Usage:

    <Component #default="props">
        <Label :text="props.text"></Label>
    </Component>
    <!-- Component Usage with Named Slots -->
    <Component>
        <Template #slot1="NULL">
            <Label text="my text1"></Label>
        </Template>
        <Template #slot2="NULL">
            <Label text="my text2"></Label>
        </Template>
    </Component>
  7. Use slots in custom components

    master

    Slots allow you to inject content into components. GMUI supports three types of slots:

    1. Default Slot: Use the <Slot /> tag in the component definition. Content placed inside the component tag in the parent will replace it.
    2. Named Slots: Use <Slot name="slot_name" /> in the component and <Template #slot_name="props"> in the parent to target specific locations.
    3. Slot Parameters (Scoped Slots): Pass data from the component to the slot content by declaring variables in the <Slot /> tag and receiving them via the # syntax in the <Template> tag.
    <!-- 1. Default Slot Usage -->
    <!-- Component Definition -->
    <Slot></Slot>
    
    <!-- Parent Usage -->
    <Component>
        <Label text="my text"></Label>
    </Component>
    
    <!-- 2. Named Slots Usage -->
    <!-- Component Definition -->
    <Slot name="slot1"></Slot>
    
    <!-- Parent Usage -->
    <Component>
        <Template #slot1="NULL">
            <Label text="my text1"></Label>
        </Template>
    </Component>
    
    <!-- 3. Slot Parameters (Scoped Slots) -->
    <!-- Component Definition -->
    <Slot text="my text"></Slot>
    
    <!-- Parent Usage -->
    <Component #default="props">
        <Label :text="props.text"></Label>
    </Component>
  8. Use Slots in Components

    master

    Slots allow you to pass content into components, similar to web frameworks.

    Default Slot

    Define a <Slot></Slot> inside your component. When using the component, any content placed between the component tags will be injected into that slot.

    Named Slots

    For multiple injection points, use named slots:

    1. In the Component: Use <Slot name="slotName"></Slot>.
    2. In the Consumer: Use <Template #slotName="NULL">...</Template> to specify which content goes into which named slot.

    Slot Parameter Passing

    You can pass data from the parent into the slot by declaring a variable in the component to capture the slot's properties.

    Component Definition:

    <Row>
        <Component #default="props">
            <Label :text="props.text"></Label>
        </Component>
    </Row>

    Usage:

    <Row>
        <Slot text="my text"></Slot>
    </Row>
  9. Install and set up GMUI

    master

    To use GMUI in your Godot 4.x project, follow these steps:

    1. Install the plugin: Use the Godot Asset Store or manually download and import the plugin package into your project.
    2. Enable the plugin: Open Project Settings in Godot and check the box to enable the GMUI plugin.
    3. Set the main scene: If prompted for the main scene, select addons/gmui/dist/scenes/pages/index.tscn (or the corresponding scene file in that directory).
    # No specific CLI command, follow Godot Editor steps
  10. Install and Setup GMUI

    master

    To use GMUI in your Godot 4.x project:

    1. Install Plugin: Search for GMUI in the Godot Asset Library, click download and import, or manually import the gmui.zip package.
    2. Enable Plugin: Go to Project Settings and enable the GMUI plugin.
    3. Set Main Scene: If prompted for a main scene, select addons/gmui/dist/scenes/pages/index.tscn or an equivalent scene file in your project.
    4. Configure Project: You can manage project metadata (name, version, icon, entry file, and screen resolution) via the gmui.json file in your project root. These settings will automatically override Godot's project settings.
    {
      "name": "My Project",
      "version": "1.0.0",
      "icon": "res://icon.png",
      "gmui_index": "res://pages/index.gmui",
      "screen": {
        "width": 1280,
        "height": 720
      }
    }
  11. Quick Start with GMUI

    master

    To use GMUI in a Godot 4.x project, follow these structural requirements:

    1. Folder Structure: New scenes must be created in a scenes folder at the root directory.
    2. Node Types: Use GMUI-provided nodes such as GNode, GNode2D, GNode3D, or GControl.
    3. Layout Files: Create XML files in a layouts folder to define your UI structure.

    Note: Due to a known bug, except for GNode, other nodes will not automatically mount scripts; you must attach them manually.

    Example XML Layout (res://layouts/main_scene.xml):

    <?xml version="1.0" encoding="UTF-8"?>
    <Node2D name="MainScene">
    	<Node2D name="node1"></Node2D>
    </Node2D>
    <?xml version="1.0" encoding="UTF-8"?>
    <Node2D name="MainScene">
    	<Node2D name="node1"></Node2D>
    </Node2D>
  12. Implement One-Way Data Binding

    master

    GMUI uses a reactive data pattern similar to Vue. To implement one-way data binding:

    1. Inherit GMUI Scripts: The root node of your scene must inherit from a GMUI base script (e.g., res://addons/gmui/scripts/common/g_node_2d.gd).
    2. Define Reactive Data: Use the vm.define_reactive() method inside the ready function to convert a dictionary into a reactive object. The vm variable is an instance provided by the parent script that manages scene data.
    3. Bind in XML: Use the g-bind:<property> directive in your XML layout to link a node property to a reactive data key.
    4. Lifecycle Methods:
      • _mounted(): Executes after ready and after the component has finished rendering.
      • _updated(): Executes whenever the reactive data is changed.

    Example Implementation:

    GDScript:

    extends "res://addons/gmui/scripts/common/g_node_2d.gd"
    
    @onready var data = vm.define_reactive({'visible': false, 'text': 'text'})
    	
    func _mounted():
    	await get_tree().create_timer(5).timeout
    	data.rset('visible', true)
    	print('mounted')
    
    func _updated():
    	print('updated')

    XML Layout:

    <?xml version="1.0" encoding="UTF-8"?>
    <Node2D name="MainScene">
    	<Node2D name="node1" g-bind:visible="visible"></Node2D>
    </Node2D>
    extends "res://addons/gmui/scripts/common/g_node_2d.gd"
    
    @onready var data = vm.define_reactive({'visible': false, 'text': 'text'})
    	
    func _mounted():
    	await get_tree().create_timer(5).timeout
    	data.rset('visible', true)
    	print('mounted')
    
    func _updated():
    	print('updated')