View Single Post
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:	1098
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:	877
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:	596
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