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

Add QML Bindings for (almost all) liblcf data #205

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

Ghabry
Copy link
Member

@Ghabry Ghabry commented Mar 11, 2022

This expoeses liblcf data structures to QML. QWidgets are kinda obsolute, so this is a first step in direction of QML.

Without this binding using QML is just extremely painful because the entire API relies on the Meta Object Compiler.

It uses a code generator. I investigated manual alternatives for many days and a generator just works best here.

The binding is holding pointers to all the lcf data, so it is a thin, non-owning wrapper. Of course all this wrapping is not for free but that cannot be really avoided when crossing C++ <-> Javascript/QML boundaries. But this is an editor so performance is not a mayor concern compared to the Player.

With this change it is possible to write code like this in QML:

var actor = project.database.actors.get(1)
console.log(actor.name)

Also data binding becomes super easy this way. E.g. to edit the current actor name in the QML Ui you can write (just an example, not included yet):

Controls.TextField {
    Kirigami.FormData.label: "Name:"
    text: obj.name // obj is a wrapper around lcf::rpg::Actor. All fields are available!

    onTextChanged: {
        obj.name = text
    }
}

We could even provide a way to generate e.g. these Actor curves from a expression.
Want quadratic hp growth? Easy:

var hp = actor.parameter.maxhp;
for (var i = 0; i < hp.length; ++i) {
  hp = pow(i + 1, 2);
}

Content:

  • src/binding/generated contains the generated API. This is generated from binding_generator/generator.py. Yes the generator is based on the liblcf generator and also consumes the CSV files from it.
  • src/binding the files in here inherit from the generated API. This way it is possible to add more exported properties and functions for QML if necessary. Most of them will likely stay empty for a long time or forever but these files give us more flexibility.

To demonstrate that the extension API works I added as an example to Actor:

Q_INVOKABLE void sayHello() {
    std::cout << "Hello from " << m_data.name.c_str() << "\n";
}

Now all actors have the new method sayHello available in QML.


TODO I will resolve when it starts to become a problem:

  • Flags/Named Bitfields are not bound yet
  • Inheritance (only used by saved character data) is not supported
  • Adding and Removing objects (e.g. Actors) from lists is not supported yet. Due to how the data binding works this needs some C++ trickery to work. But the requirements to add this later are already available in form of ArrayAdapter.

@jetrotal
Copy link

jetrotal commented Mar 25, 2022

Hi, I have made a library for visual Assets called "ez" with my limited knowledge on QML.
Maybe you can cut off the functions and var appButtons, since they list the menus and items, and you made a better way of Binding elements that are already on the liblcf.

property string assets: pathIsLocal ? Qt.resolvedUrl(".") : "https://raw.githubusercontent.com/jetrotal/easyRPG_UI/main/";
    
background:ez.bg;

component UI_Library : Item {
        visible:false
        property real resizeFactor: 1
        property var style: {

            "sizes": {
                "text":9 * resizeFactor,
                "topBar":27 * resizeFactor,
                "logo": 16 * resizeFactor,
                "win":16 * resizeFactor,
                "icon":24 * resizeFactor,
                "checkboxSpacing":15 * resizeFactor,
                "checkboxOuter":11 * resizeFactor,
                "checkboxInner":5 * resizeFactor,

            },

            "colors": {
                "bg": "#292b2f",
                "header": "#242529",
                "textA": "#97999C",
                "textB": "#FFF",
                "highlight": "#8AFF4E",
                "separator": "#555",
                "darkBorders": "#1e1e1e",
                "shadowColor":"#222327"
            },

            "images": {
                "logo": assets + "img/ez-logo.svg",
                "bg": assets + "img/bg/tile.svg",
                "icons": {
                    "win": {
                        "url": assets + "img/icons/window/"
                    },
                    "top": {
                        "url": assets + "img/icons/top/"
                    }
                }
            }
        } // STYLES
        property var appButtons: {
            "workSpace": [{
                "obj": "appHome",
                "display": "Home",
            },/*separator*/, {
                "obj": "mapEditor",
                "display": "Map Editor",
            }, {
                "obj": "databaseEditor",
                "display": "Database Editor",
            }, {
                "obj": "eventsEditor",
                "display": "Events Editor",
            },/*separator*/, {
                "obj": "resourceManager",
                "display": "Resource Manager",
            }, {
                "obj": "gameSearch",
                "display": "Search",
            },/*separator*/, {
                "obj": "bgmTest",
                "display": "BGM test",
            },/*separator*/, {
                "obj": "gameSettings",
                "display": "Playtest Settings",
            }, {
                "obj": "gamePlay",
                "display": "Begin Playtest",
            }],
            "window": [{
                "obj": "min",
                "display": "Minimize Window",
            }, {
                "obj": "max",
                "display": "Maximize Window",
            }, {
                "obj": "close",
                "display": "Close Window",
            }],
            "topBar": [{
                "obj": "project",
                "display": "Game Project",
                "contents": [{
                    "obj":"newProject","display":"New Project", "shortcut":"Ctrl+N", "disabled":1
                }, {
                    "obj":"openProject","display":"Open Project", "shortcut":"Ctrl+O", "disabled":1
                }, {
                    "obj":"closeProject","display":"Close Project",  "shortcut":"Ctrl+X"
                },/*separator*/, {
                    "obj":"createDisk","display":"Create Game Disk"
                },/*separator*/, {
                    "obj":"quit","display":"Quit"
                }]
            }, {
                "obj": "maps",
                "display": "Maps",
                "contents": [{
                    "obj":"saveMap","display":"Save Map", "shortcut":"Ctrl+S"
                }, {
                    "obj":"revertMap","display":"Revert Map", "shortcut":"Ctrl+R"
                },/*separator*/, {
                    "obj":"lowerLayer","display":"Lower Layer", "shortcut":"F5"
                }, {
                    "obj":"upperLayer","display":"Upper Layer", "shortcut":"F6"
                }, {
                    "obj":"eventsLayer","display":"Events Layer", "shortcut":"F7"
                },/*separator*/, {
                    "obj":"defaultZoom","display":"Zoom 100%"
                }, {
                    "obj":"zoomIn","display":"Zoom In", "shortcut":"+"
                }, {
                    "obj":"zoomOut","display":"Zoom Out", "shortcut":"-"
                }]
            }, {
                "obj": "view",
                "display": "View",
                "contents": [{
                    "obj":"palette","display":"Palette","checkbox":1,"checked":1
                }, {
                    "obj":"mapTree","display":"Map Tree","checkbox":1,"checked":1
                }]
            }, {
                "obj": "tools",
                "display": "Tools",
                "contents": [{
                    "obj":"database","display":"DataBase", "shortcut":"F8"
                }, {
                    "obj":"resourceManager","display":"Resource Manager"
                }, {
                    "obj":"jukebox","display":"Jukebox"
                }, {
                    "obj":"search","display":"Search"
                },]
            }, {
                "obj": "game",
                "display": "Game",
                "contents": [{
                    "obj":"playTest","display":"PlayTest", "shortcut":"F4"
                },/*separator*/, {
                    "obj":"fullScreen","display":"Fullscreen", "checkbox":1,"checked":1
                }, {
                    "obj":"showTitle","display":"Show Title Background","checkbox":1,"checked":1
                },/*separator*/, {
                    "obj":"playSettings","display":"PlayTest Settings"
                },]
            }, {
                "obj": "help",
                "display": "Help",
                "contents": [{
                    "obj":"contents","display":"Contents", "shortcut":"F1"
                },/*separator*/, {
                    "obj":"about","display":"About"
                }, {
                    "obj":"aboutQt","display":"About Qt"
                },]
            }, {
                "obj": "debug",
                "display": "Debug",
                "contents": [{
                    "obj":"rtpPath","display":"RTP Path"
                }, {
                    "obj":"caching","display":"Enable Caching","checkbox":1,"checked":1
                },/*separator*/, {
                    "obj":"displayButtons","display":"Display Button Actions","checkbox":1,"checked":debugParams.displayButtons * debug
                }, {
                                   "obj":"displayBorders","display":"Display Invisible Borders","checkbox":1,"checked":debugParams.displayBorders * debug
                               },, {
                                   "obj":"listProperties","display":"List Properties on Console","checkbox":1,"checked":debugParams.listProperties * debug
                               },]
            }]
        }

        function listProperties(item) {
            // debug props
            var result="";
            for (var p in item) result += String(item[p]) !== "function() { [native code] }" ? (p +": " + (String(item[p]) ==="[object Object]" ?" {; "+ listProperties(item[p])+" } " : item[p]) +`;`):"";
            return(result.split(';').join(pathIsLocal ? "\n" : "<br>"))
        }


        function detectCommand(bt, type,qmlItem) {
            if (type[1] === "debug") debugFunction(bt)
            if (debugParams.displayButtons===1) footerMark.text = ("running detectCommand() - You clicked: " + JSON.stringify(bt.obj) + "  |  Type: "+JSON.stringify(type))
            else footerMark.text="";

            if (debugParams.listProperties===1) console.log(ez.listProperties(qmlItem))


            if (type === "workspace") return changeWorkspace(bt)
            if (type === "window") return resizeWindow(bt)

        }

        function debugFunction(bt){
            console.log(bt.obj +": "+debugParams[bt.obj])
            debugParams[bt.obj] = debugParams[bt.obj] === 0 ? 1 : 0
            contentBody.border.width = debugParams.displayBorders
        }

        function resizeWindow(bt) {
            if (bt.obj === "min") return app.showMinimized()
            if (bt.obj === "max") return console.log(app.visibility), app.visibility === 4 ? app.showNormal() :app.showMaximized()
            if (bt.obj === "close") return app.close()
        }

        function changeWorkspace(e) {
            currWorkspace.text = "<pre><font color='" + styles.colors.textB + "'>  " + e.display + "  </pre>"
        }

        property Item bg: Rectangle {
            anchors.fill: parent
            color: ez.style.colors.bg
            Image {
                anchors.fill:parent
                fillMode: Image.Tile
                source: ez.style.images.bg
            }
        }


    }
    UI_Library {
        id:ez
    } // ez[variableName] // ez.styles[styleVar] //

The full file is here: https://github.com/jetrotal/easyRPG_UI/blob/main/scripts/qml/index.qml
and the images folder is here: https://github.com/jetrotal/easyRPG_UI/tree/main/img

I failed to make it work as a typical QML file since I was not able to install the Editor's projec, or kirigami or any other library on my computer. But, I hope that helps on something;

lumiscosity added a commit to lumiscosity/Editor that referenced this pull request Jul 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

3 participants