Quantcast
Scorpio.UI - a new ui & skin system - WoWInterface
Thread Tools Display Modes
03-07-20, 08:18 AM   #1
kurapica.igas
A Flamescale Wyrmkin
Join Date: Aug 2011
Posts: 128
Scorpio.UI - a new ui & skin system

After spending two years on web project with Openresty, finally I got time and the new ideas for the Scorpio lib's UI part. This is not an officially introduction, so I'm sorry I can't explain all the details. It's only a preview of my new design.

i. As the first example, I'll show the popup dialog using the Scorpio lib:

Lua Code:
  1. -- The addon is load on demand
  2. LoadAddOn("Scorpio.Widget")  
  3.  
  4. -- from this line, I can use features from Scorpio
  5. Scorpio "Test" ""  
  6.  
  7. -- Declare an async function that will be running in a coroutine
  8. __Async__()  
  9. function TestPopup()
  10.     print("Hello " .. Input("Please input your name"))
  11. end
  12.  
  13. TestPopup()

Works like:

Click image for larger version

Name:	ScorpioInput.jpg
Views:	248
Size:	173.1 KB
ID:	9420

So we can use the input result directly, the system also provide Confirm and Alert APIs like this. Since the function processed in a coroutine, the Input API can yield the coroutine, and resume it when the user finished his input, this is a trick I used many years in my IGAS lib.


ii. The new idea for the Scorpio UI is come from the HTML+CSS. The HTML only provide the elements and their relationship, we can give different css file to show a very different view with the same HTML.

We can change an element's style based on its type, its class, or the custom settings of itself. Also we can change the element's style from the element's parent(the parent type, class or the parent itself) and the parent's parent, and so on.

With this design pattern, we can split the functionality and the view styles.

Take the Input Popup dialog as an example, its template is a class defined like:

Lua Code:
  1. class "InputDialog" (function(_ENV) -- don't mind the _ENV, it's for Lua 5.2
  2.     -- inherit another template class
  3.     inherit "Dialog"  
  4.  
  5.     -- Bind the child element's script event to the input dialog's new event
  6.     -- so press the confirm button of press enter in the inputbox will trigger
  7.     -- the input dialog's OnConfirm event
  8.     __Bubbling__{ ConfirmButton = "OnClick", InputBox = "OnEnterPressed" }
  9.     event "OnConfirm"
  10.  
  11.     -- The close button is defined in the Dialog template class
  12.     __Bubbling__{ CancelButton  = "OnClick", InputBox = "OnEscapePressed", CloseButton = "OnClick" }
  13.     event "OnCancel"
  14.  
  15.     -- __ctor is the constructor of the class, normall used to bind the
  16.     -- default event handlers for the elements, but here no use, we
  17.     -- need define it so __Template__ have something to bind
  18.     --
  19.     -- The __Template__ is used to declare the child elements with
  20.     -- their types. The key is the child name, the value is the template
  21.     -- class.
  22.     --
  23.     __Template__{
  24.         -- The fontstring used to display the message
  25.         Message = FontString,    
  26.  
  27.         -- The inputbox used to get the user's input
  28.         InputBox = InputBox,
  29.  
  30.         -- The confirm button
  31.         ConfirmButton = UIPanelButton,
  32.  
  33.         -- The cancel button
  34.         CancelButton = UIPanelButton,
  35.     }
  36.     function __ctor(self) end
  37. end)

There is no texture, font and location settings in the template, so the template only provide the functionality.

Now we can declare the default skin for the template :

Lua Code:
  1. Style.UpdateSkin("Default", {
  2.     [InputDialog] = {
  3.         Size = Size(360, 130),
  4.         Resizable = false,
  5.         FrameStrata = "FULLSCREEN_DIALOG",
  6.         Location = { Anchor("CENTER") },
  7.  
  8.         -- Childs
  9.         Message = {
  10.             Location = { Anchor("TOP", 0, -16) },
  11.             Width = 290,
  12.             DrawLayer = "ARTWORK",
  13.             FontObject = GameFontHighlight,
  14.         },
  15.         InputBox = {
  16.             Location = { Anchor("TOP", 0, -50) },
  17.             Size = Size(240, 32),
  18.             AutoFocus = true,
  19.         },
  20.         ConfirmButton = {
  21.             Text = "Okay",
  22.             Location = { Anchor("BOTTOMRIGHT", -4, 16, nil, "BOTTOM") },
  23.             Size = Size(90, 20),
  24.         },
  25.         CancelButton = {
  26.             Text = "Cancel",
  27.             Location = { Anchor("BOTTOMLEFT", 4, 16, nil, "BOTTOM") },
  28.             Size = Size(90, 20),
  29.         }
  30.     },
  31. })

The skin settings is a little like the xml, we have plenty properties(case ignored) to be set, we don't need set all the properties, like the ConfirmButtons' normal texture, since it's already done in the default skin of the UIPanelButton.

Now, we'll see how to give a new skin for the InputDialog:

Lua Code:
  1. Scorpio "Test" ""
  2.  
  3. -- Skin must be register first, case ignored
  4. Style.RegisterSkin("Custom", {
  5.      [InputDialog] = {
  6.         -- inherit the default skin so we don't need to override all
  7.         inherit = "Default",    
  8.        
  9.         -- Change the backdrop settings
  10.         Backdrop = {
  11.             edgeFile = [[Interface\Tooltips\UI-Tooltip-Border]],
  12.             tile = true, tileSize = 16, edgeSize = 16,
  13.             insets = { left = 5, right = 5, top = 5, bottom = 5 }
  14.         },
  15.         BackdropBorderColor = { r = 0.6, g = 0.6, b = 0.6 },
  16.     }
  17. })
  18.  
  19. -- active the skin for the InputDialog
  20. Style.ActiveSkin("Custom", InputDialog)

When active the custom skin, all InputDialog objects will receive the skin updating:

Click image for larger version

Name:	RegisterSkin.jpg
Views:	166
Size:	125.4 KB
ID:	9421

The updating process is processed in the Scorpio's task schedule system, so it won't freeze the game even if you have thousand ui elements to be updated.

It's a little comple for the class definition, but if we don't need bind those events, we can create a class template very simple:

Lua Code:
  1. -- declare the base template class within the __Template__
  2. -- then declare the elements in the table
  3. __Template__(EditBox)
  4. class "InputBox" {
  5.     LeftBG = Texture,
  6.     RightBG = Texture,
  7.     MiddleBG = Texture,
  8. }
  9.  
  10. -- The skin part
  11. -- the same of the xml that define the InputBoxTemplate
  12. Style.UpdateSkin("Default", {
  13.     [InputBox] = {
  14.         FontObject = ChatFontNormal,
  15.         LeftBG = {
  16.             Atlas = {
  17.                 atlas = [[common-search-border-left]],
  18.                 useAtlasSize = false,
  19.             },
  20.             Location = {
  21.                 Anchor("TOPLEFT", -5, 0),
  22.                 Anchor("BOTTOMLEFT", -5, 0),
  23.             },
  24.             Width = 8,
  25.         },
  26.         RightBG = {
  27.             Atlas = {
  28.                 atlas = [[common-search-border-right]],
  29.                 useAtlasSize = false,
  30.             },
  31.             Location = {
  32.                 Anchor("TOPRIGHT", 0, 0),
  33.                 Anchor("BOTTOMRIGHT", 0, 0),
  34.             },
  35.             Width = 8,
  36.         },
  37.         MiddleBG = {
  38.             Atlas = {
  39.                 atlas = [[common-search-border-middle]],
  40.                 useAtlasSize = false,
  41.             },
  42.             Location = {
  43.                 Anchor("TOPLEFT", 0, 0, "LeftBG", "TOPRIGHT"),
  44.                 Anchor("BOTTOMRIGHT", 0, 0, "RightBG", "BOTTOMLEFT"),
  45.             }
  46.         },
  47.     },
  48. })


iii. You can find all the skin properties in Scorpio.UI.Property.lua, besided the same properties from the UI.xsd like alpha, altas, size. We also can define other useful properties like Fadeout, it works like

Lua Code:
  1. Scorpio "Test" ""
  2.  
  3. local dialog = Dialog("Test")
  4.  
  5. Style[dialog] = {
  6.     -- The dialog header settings
  7.     Header = {
  8.         Text  = "Test Fadeout",
  9.     },
  10.    
  11.     -- the fade out settings
  12.     Fadeout = {
  13.         duration    = 2,  -- the fade out duration
  14.         delay = 1,         -- the delay when move mouse away from the dialog
  15.         stop  = 0.2,       -- the final alpha
  16.     }
  17. }

Click image for larger version

Name:	fadeout.jpg
Views:	138
Size:	89.7 KB
ID:	9422

We can use the Style[obj] to change the object's custom styles. It's not a skin for the class, so only affect the object.

Since there would be too many template classes and properties, a developer tool will be provided like how we inspect the css styles in the Chrome.

For now, it's still under development, the code won't be released to curseforge until most useful template classes are added. So this is only a preview.

I still have many plans for the UI part, the CSS Box Model to replace the anchors, the reactive system to drive the ui refreshing and etc.
  Reply With Quote
03-07-20, 04:02 PM   #2
Xruptor
A Flamescale Wyrmkin
 
Xruptor's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2005
Posts: 121
This is actually a really cool idea. Obviously a lot of work and love has gone into this and I can see this being quite easy to register and apply skins.
__________________
Click HERE for the ultimate idiot test.

if (sizeof(sadness) > sizeof(happiness)) { initDepression(); }
  Reply With Quote
03-08-20, 12:16 AM   #3
kurapica.igas
A Flamescale Wyrmkin
Join Date: Aug 2011
Posts: 128
Not like the IGAS, the frames generate from the template class like Dialog, UIPanelButton is the same generated from the CreateFrame, there is no wrapper on them.

So there is no different to use them, you can use button:SetScript("OnClick", function() end), or a better form: button.OnClick = function(self) end.

Also the Style system can be used on the other ui elements that not generated by Scorpio:

Click image for larger version

Name:	fademenu.jpg
Views:	136
Size:	54.8 KB
ID:	9423

Although there is no template class for them, so no skin, only custom settings.
  Reply With Quote
04-19-20, 05:19 AM   #4
kurapica.igas
A Flamescale Wyrmkin
Join Date: Aug 2011
Posts: 128
v035 released, the basic ui, template and several common widget features has been released. You can find an introduction at:

https://github.com/kurapica/Scorpio/.../004.widget.md

The documents are still on the working, so I only show some simple APIs may interests you.

I can't say it's not a big project. There are too much features to build the whole system. I can't hide them like what I do in the non-ui part of the Scorpio lib.
  Reply With Quote
05-05-20, 07:29 AM   #5
kurapica.igas
A Flamescale Wyrmkin
Join Date: Aug 2011
Posts: 128
Scorpio v36 released, the core ui & style system is finished, and common widgets are released in the Scorpio.Widget lib.

You can find documenets in github.com/kurapica/Scorpio, but I'm afraid the whole documents will be a hard job than the library itself. So for now, you will only find more examples about the usages.

I need more time to create a guidance for beginners and experts, or with an in-game IDE for the guide.

I'll update this thread with my progress if anybody has interest.
  Reply With Quote
08-05-20, 12:03 AM   #6
kurapica.igas
A Flamescale Wyrmkin
Join Date: Aug 2011
Posts: 128
A new system is added to apply dynamic values to the ui styles:

Click image for larger version

Name:	reactive.jpg
Views:	21
Size:	54.8 KB
ID:	9464

The text of the label will be changed based on the player's health.


The real code of the Wow.UnitHealth("player") is

Lua Code:
  1. FromEvent("UNIT_HEALTH"):FirstMatch(unit):Map(_G.UnitHealth)

The FromEvent can bind any system events as a data source, the FirstMatch is a filter to check the first event argument, and then we use the Map to convert the unit to its health.

All those methods will return the same value based on the parameter, so the FromEvent("UNIT_HEALTH") will return the same object for any calls.

This is be done with the observable pattern, you may check the PLoop/022.reactive.md or http://reactivex.io/ for more details. This is a little hard for common authors, so you may leave the details to me.

Like the Wow.UnitHealth, I'll try to provide enough APIs for most case.
  Reply With Quote

WoWInterface » Developer Discussions » Dev Tools » Scorpio.UI - a new ui & skin system

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off