Thread Tools Display Modes
03-07-20, 08:18 AM   #1
kurapica.igas
A Chromatic Dragonspawn
Join Date: Aug 2011
Posts: 152
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:	1093
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:	873
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:	594
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.

[2021-03-24]For now, the first version introduction for the ui style and the unit frame are all done:

003.ui.md
006.unitframe.md

Last edited by kurapica.igas : 03-23-21 at 09:13 PM.
  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: 133
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 Chromatic Dragonspawn
Join Date: Aug 2011
Posts: 152
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:	547
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 Chromatic Dragonspawn
Join Date: Aug 2011
Posts: 152
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 Chromatic Dragonspawn
Join Date: Aug 2011
Posts: 152
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 Chromatic Dragonspawn
Join Date: Aug 2011
Posts: 152
A new system is added to apply dynamic values to the ui styles:

Click image for larger version

Name:	reactive.jpg
Views:	433
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
10-02-20, 08:45 PM   #7
kurapica.igas
A Chromatic Dragonspawn
Join Date: Aug 2011
Posts: 152
Working on a new Code Editor, the main part is finished.

Click image for larger version

Name:	example.gif
Views:	464
Size:	1.25 MB
ID:	9484

The example code can be found at Cube/CubeEditor.lua, it's an example to show how to use the Scorpio widget Lib with the Style system.
  Reply With Quote
10-15-20, 02:10 AM   #8
kurapica.igas
A Chromatic Dragonspawn
Join Date: Aug 2011
Posts: 152
Click image for larger version

Name:	Api.png
Views:	347
Size:	558.7 KB
ID:	9498

Just finished the new Cube, it's browser is using the SimpleHtml to show the contents, the main problem is how to simply generate the contents, since we need a complex string concat to generate the html.

I used a trick from my web framework called template string, so the Lua code can be embed into the string, and used to generate the html.

For the example in the pic, the template string is:

https://github.com/kurapica/Cube/blo....lua#L316-L341

And the code that use it to generate the result is :

https://github.com/kurapica/Cube/blo....lua#L154-L160

Just like build a tiny website in the game.

The template string's doc is on PLoop/template-strings if it interesting you.
  Reply With Quote
01-13-21, 08:01 PM   #9
kurapica.igas
A Chromatic Dragonspawn
Join Date: Aug 2011
Posts: 152
The reactive system is already done, a nameplate mod released at https://www.curseforge.com/wow/addons/aim.

It's a tiny mod, almost all indicators are skin settings defined in the DefaultSkin.lua.

Take the healthbar as an example:

Lua Code:
  1. HealthBar               = {
  2.     -- like class in css, share skin settings with a table for many elements
  3.     SHARE_STATUSBAR_SKIN,  
  4.  
  5.     location = { Anchor("BOTTOMLEFT"), Anchor("BOTTOMRIGHT") },
  6.     height = BAR_HEIGHT,
  7.    
  8.     -- threat, tap, class color
  9.     statusBarColor = Wow.UnitExtendColor(true),
  10.  
  11.     -- The minmax value for the status bar
  12.     minMaxValues = Wow.UnitHealthMax(),
  13.  
  14.     -- Use smooth value instead the value, use NIL to block the default settings
  15.     value = NIL,
  16.  
  17.     -- An extended property works for slider and statusbar for smoothing effect
  18.     -- the unit's health value will be dispatched here
  19.     smoothValue = Wow.UnitHealth(),
  20.  
  21.     -- the back ground settings with a dynamic color for target
  22.     backgroundFrame = {
  23.         -- The border color turn white if the unit is player's target
  24.         backdropBorderColor = Wow.UnitIsTarget():Map(function(val) return val and Color.WHITE or  Color.BLACK end),
  25.     },
  26. },

The APIs like Wow.UnitHealth() will subscribe the UNIT_HEALTH event observable, and match the unit provided by the healthbar's parent(or parent's parent until find a UnitFrame).

The API's definition is simple:

Lua Code:
  1. function Wow.UnitHealth()
  2.     -- Use the Next for a tiny delay after the UnitHealthMax
  3.     return Wow.FromUnitEvent("UNIT_HEALTH", "UNIT_MAXHEALTH"):Next():Map(UnitHealth)
  4. end

The Wow.FromUnitEvent will be used to bind the UNIT event and the unit from the parent. The Next() will delay the data dispatch for one frame and distinct it, so it won't be send twice or more, also make sure the value will be a tiny later than the Wow.UnitHealthMax which will change the status bar's minMax.

Full document will be out in the github when another mod for Raid Panel is released.
  Reply With Quote
02-24-21, 07:19 AM   #10
kurapica.igas
A Chromatic Dragonspawn
Join Date: Aug 2011
Posts: 152
So another raid panel addon based on the Scoprio's Style system is released at https://www.curseforge.com/wow/addons/ashtoash.



Besides the skin system to make the indicators simple, with the raid panel addon:

1. we can create as many panels as we want, we have unit panels(a dead only mode included), pet panels, watch unit panels(for boss, target, nameplate1 and etc, also "tank", "tanktarget" can be used)

2. re-size and relayout during combat

3. The group member change events may occur many times in the same time, a secure delay system will make sure only do one time re-layout for them, since it's a secure mechanism, works well during combat.

I'll focus on the documents for the UI and Skin part, so if any have interesting, you can find docs on the github
  Reply With Quote
03-23-21, 09:14 PM   #11
kurapica.igas
A Chromatic Dragonspawn
Join Date: Aug 2011
Posts: 152
For now, the first version introduction for the ui style and the unit frame are all done:

003.ui.md
006.unitframe.md

With the Cube Browser, it won't be hard to use.
  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