excessive / DOMy

A DOM-like GUI framework for the *awesome* LÖVE framework
Other
32 stars 2 forks source link

Widget API #23

Closed karai17 closed 9 years ago

karai17 commented 9 years ago

A Widget is a predefined GUI object that can be loaded into any GUI instance. A simple example would be a Window Frame that includes a close button, a collapse button, a title area, and a content area. The Window Frame would have predefined styles and scripts. Once a widget is loaded, you can load it like any other element.

My basic idea for the Widget API is as follows:

  1. Define a Widget Directory for the GUI instance (default would be %DOMINATRIX%/widgets)
  2. Each Widget should be in its own folder with the Widget Name (widgets/window_frame, widgets/minimap)
  3. Each Widget should have at minimum a markup.lua file. Loadable files include markup.lua, styles.lua, and scripts.lua.
  4. Widgets can be loaded into the DOM via markup or scripts and will be rendered into regular elements, retaining no "special" data indicating they are from a Widget.
  5. setting the ID of a Widget Element will set the id of the first element in the Widget.
  6. A Widget should have a single container such as a block or inline element with its contents fully encapsulated within.
-- main.lua
local dom = require "libs.DOMinatrix"
local gui = dom.new()
gui:add_widget_directory("assets/widgets/")
gui:include_markup("assets/gui/game_screen.lua")
-- game_screen.lua
return {
    { "window_frame", id="character_window" },
}
-- window_frame/markup.lua
return {
    { "inline", class="window_frame",
        { "text", class="window_frame_title" },
        { "button", class="window_frame_collapse" },
        { "button", class="window_frame_close" },
        { "block", class="window_frame_content" },
    }
}
karai17 commented 9 years ago

I think it would be nice to have the ability to override a widget's individual pieces. This would allow the user to define the contents of a widget within markup instead of needing to do so programmatically after the fact. I am not quite sure how I would go about this, though.

pablomayobre commented 9 years ago

Something like jekyll does with Liquid? Templating?

karai17 commented 9 years ago

I don't understand what you mean.

pablomayobre commented 9 years ago

Jekyll the static page generator used by Github pages uses Liquid, a templating language, in order to generate the pages.

Basically you write layouts, which are .html files (but could be anything else) saved in the _layouts folder, for example lets call this layout mylayout.html:

<div style="width:50px; margin:auto; color:#F00">
    {{ content }}
</div>

Here {{ content }} is a Liquid variable that will be replaced by the actual content (There are plenty of variables but {{ content }} is the most important)

Then you define your pages, and at the top you specify the layout to use, for example this file, called mypage.html:

---
layout:mylayout
---
This will be my content and will be red and centered (possibly), and have a width of 50px because why not?

This will be turned into a final file called mypage.html with the following content

<div style="width:50px; margin:auto; color:#F00">
This will be my content and will be red and centered (possibly), and have a width of 50px because why not?
</div>

Layouts can also use other layouts.

Multiple pages can use the same layout.

They can be any type of file (another common example is generating RSS feeds file which are .xml)

Also it is common to use a _config.yml file which holds data you can then use as variables, so it is pretty extensive.

Liquid also provides syntax for conditionals and loops, for example you can write an if statement like this: {% if something %}Write this{% endif %}

If you are interested in templating markup language check Liquid and Soquid (@LPGHatguy attempt at templating language in Lua)

karai17 commented 9 years ago

I have implemented it a little differently. What I do (I just finished implementing this about 3 minutes ago) is allow you to override default settings of the widget by specifying an attribute in the widget declaration. This attribute checks against the widget's elements to see if they have an appropriate class value. If so, it will insert the attribute's data into that element. This also allows you to insert a group of elements into an element by breaking apart the root table of the group. Finally, if you assign an id to the widget, it will assign that id to the container of the widget.

-- group of elements
local content = {
    { "image" },
    { "image" },
    { "image" },
    { "image" },
}

--widget
{ "window_frame", title="Some Window", content=content, id="some_window" }

-- widget markup
{ "inline", class="window_frame",
    { "text", class="window_frame_title" },
    { "block", class="window_frame_content" },
}

-- processed widget markup
{ "inline", class="window_frame", id="some_window",
    { "text", "Some Window", class="window_frame_title" },
    { "block", class="window_frame_content",
        { "image" },
        { "image" },
        { "image" },
        { "image" },
    },
}
pablomayobre commented 9 years ago

I see, it looks fine for me! How would this look in different file though? What will be in each one? How should I call this?

karai17 commented 9 years ago

Each widget is in a self-contained folder within a group directory. The default is %DOMY%/widgets, so in the above example's case you would have %DOMY%/widgets/window_frame. Inside that folder is a markup.lua file (and styles.lua and scripts.lua files). that markup file contains the aforementioned widget markup. The single tag listed widget is the markup you would write in your normal markup file to define your UI. DOMy knows if it is an element or a widget so it will process it automatically and replace the widget tag with the processed elements.

karai17 commented 9 years ago

Widget styles now work. I will see if I can get scripts working today.

Bobbyjoness commented 9 years ago

What will scripts do?

karai17 commented 9 years ago

Scripting the ui is always useful, but I need to figure out how to apply scripts to widgets consistantly. On Apr 2, 2015 8:11 PM, "Bobbyjoness" notifications@github.com wrote:

What will scripts do?

— Reply to this email directly or view it on GitHub https://github.com/excessive/DOMinatrix/issues/23#issuecomment-89073647.

karai17 commented 9 years ago

I've added a function to the GUI instance that allows you to register functions. This allows you to define functions in a scripts file and save anything that may need to be run more than once (such as creating a new element and applying all the callbacks it needs).

This finalizes the widget system and API!

local function create_button()
    local button = gui:new_element("button")
    button.on_clicked = function()
        print("Clicked!")
    end
end

gui:register_function("create_button", create_button)

gui:create_button()
pablomayobre commented 9 years ago

I think the registration should be under the widget name or something, otherwise if I have two widgets that define a function called create_button the later one would override the first one.

So if for example my widget mywidg registers create_button I can find the function under gui.mywidg:create_button

If you do this you would need something like gui:this_widget to get the current widget registered functions (since the widget name may be changed by the user) and something like gui:widgets would be nice to have too, so you can find other widgets.

karai17 commented 9 years ago

The registration isn't specifically for widgets, it is a general purpose functionality that widgets can take advantage of. The idea here is that creating a new widget (or any new element, really) programmatically might require you to assign data such as callbacks or classes or other such things that you have pre-written in your scripts file that worked for the initial loading of elements.

Creating a new element won't run your scripts file again, and running the whole thing again might cause unwanted occurrences anyway, so this is a bridge where you can write your UI-specific scripts in a separate file and then register reusable code to invoke in your game code as needed.

s-ol commented 9 years ago

I would vouch against storing widgets in a folder, and rather expose an API that creates a widget from a markup/style/script triple. You can then still have widgets in the same structure you have outlined above, just add an init.lua and let the Lua require work it's magic:

init.lua:

local modname = (...):gsub('%.init$', '')

local markdown = require(modname .. '.markdown')
local styles       = require(modname .. '.styles')
local scripts      = require(modname .. '.scripts')

-- optionally process or modify the widget here
-- for example generate two different versions, process localization etc.

return gui:registerWidget('this_widget_name', markdown, styles, scripts)

That way you could also dynamically generate widgets.

karai17 commented 9 years ago

While I'm not completely against your idea, I don't really see much benefit from it, either.

s-ol commented 9 years ago

It would for example allow widget-editors, loading widgets from other sources (internet) and I am sure there are other cases I can't think of at the top of my head.

I just generally find it weird that FS is mixed in with GUI, especially when in lua there is no general way of handling FS.

karai17 commented 9 years ago

But you can already do that! You can download widgets and place the folder into some directory and register that directory to DOMy. On Apr 4, 2015 5:53 PM, "Sol Bekic" notifications@github.com wrote:

It would for example allow widget-editors, loading widgets from other sources (internet) and I am sure there are other cases I can't think of at the top of my head.

— Reply to this email directly or view it on GitHub https://github.com/excessive/DOMy/issues/23#issuecomment-89663902.