MWUI interface (resolve #6594)

Includes a mechanism for overriding templates, which supports dynamic changes to templates at any point during runtime.

Some templates include "legacy" MyGUI skins, and should be replaced when #6654 (closed) is implemented.

Some points which are still not clear to me:

  1. The template push/pop overloading methods assume all the passed templates are different tables, and that scripts only change the templates they've added themselves. The problem is that most of the time people will only slightly modify existing templates, and will want to make a copy of them, which is not trivial to do (even in the example below deepCopy doesn't copy ui.contents correctly. We should either provide a standard deepCopy function, or come up with an easier way to do this.
  2. Currently the UI package doesn't expose the resources, maybe it should. It's not clear to me if it's useful, as they can't be overridden this way anyway.
  3. push and pop are a bit clunky, as pop implies it removes the top of the override stack, while it actually removes the template passed to it as an argument. I guess I should rename them to addTemplateOveride and removeTemplateOverride, but maybe anyone has other suggestions on how to handle the override list for each template key.
  4. updateAll implementation is a bit iffy.
  5. Maybe this should be a package, rather than an interface.

Example of a script which allows to dynamically change the font size and borders on all UI elements.

local ui = require('openmw.ui')
local util = require('openmw.util')
local async = require('openmw.async')
local interfaces = require('openmw.interfaces')

local v2 = util.vector2

local mwUi = interfaces.MWUI

local textEditBoxOverride = ui.deepLayoutCopy(mwUi.templates.textEditBox)
mwUi:override('textEditBox', textEditBoxOverride)
local bordersOverride
local bordersToggle = true
local textProps = textEditBoxOverride.props

ui.create {
  template = mwUi.templates.box,
  type = ui.TYPE.Widget,
  layer = 'Windows',
  props = {
    size = v2(700, 700),
    relativePosition = v2(0.5, 0.5),
    anchor = v2(0.5, 0.5),
  },
  content = ui.content {
    {
      type = ui.TYPE.TextEdit,
      template = mwUi.templates.textEditBox,
      props = {
        relativePosition = v2(0, 0),
        anchor = v2(0, 0),
        relativeSize = v2(1, 0.5),
        text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas eu arcu odio. Maecenas luctus pellentesque ullamcorper. Duis ut laoreet erat. Mauris eros lacus, ullamcorper et interdum at, auctor ac ipsum. Nunc ac ante suscipit, ornare est sed, lacinia nibh. Sed at tempor tellus. Nam egestas vestibulum tortor sit amet finibus.",
        wordWrap = true,
        multiline = true,
      },
      events = {
        mousePress = async:callback(function(e)
          if e.button ~= 0 then
            ui.updateAll()
            return
          end
          ui.showMessage(("Toggled borders %s"):format(bordersToggle and 'yes' or 'no'))
          if bordersToggle then
            bordersOverride = mwUi:temporaryOverride('borders', {})
          else
            if bordersOverride then
              bordersOverride()
            end
          end
          bordersToggle = not bordersToggle
          ui.updateAll()
        end),
      },
    },
    {
      type = ui.TYPE.Widget,
      template = mwUi.templates.borders,
      props = {
        relativePosition = v2(0.25, 0.75),
        anchor = v2(0.5, 0.5),
        size = v2(100, 60),
      },
      content = ui.content {
        {
          type = ui.TYPE.Text,
          template = mwUi.templates.textNormal,
          props = {
            text = "Smaller",
            relativePosition = v2(0.5, 0.5),
            anchor = v2(0.5, 0.5),
          },
          events = {
            mouseClick = async:callback(function()
              textProps.textSize = textProps.textSize - 1
              mwUi:override('textEditBox', textEditBoxOverride)
              ui.showMessage(("Text size %i"):format(textProps.textSize))
              ui.updateAll()
            end),
          },
        }
      },
    },
    {
      type = ui.TYPE.Widget,
      template = mwUi.templates.borders,
      props = {
        relativePosition = v2(0.5, 0.75),
        anchor = v2(0.5, 0.5),
        size = v2(100, 60),
      },
      content = ui.content {
        {
          type = ui.TYPE.Text,
          template = mwUi.templates.textNormal,
          props = {
            text = "Bigger",
            relativePosition = v2(0.5, 0.5),
            anchor = v2(0.5, 0.5),
          },
          events = {
            mouseClick = async:callback(function()
              textProps.textSize = textProps.textSize + 1
              mwUi:override('textEditBox', textEditBoxOverride)
              ui.showMessage(("Text size %i"):format(textProps.textSize))
              ui.updateAll()
            end),
          },
        }
      },
    },
    {
      type = ui.TYPE.Widget,
      template = mwUi.templates.borders,
      props = {
        relativePosition = v2(0.75, 0.75),
        anchor = v2(0.5, 0.5),
        size = v2(150, 60),
      },
      content = ui.content {
        {
          type = ui.TYPE.Text,
          template = mwUi.templates.textNormal,
          props = {
            text = "Toggle Borders",
            relativePosition = v2(0.5, 0.5),
            anchor = v2(0.5, 0.5),
          },
          events = {
            mouseClick = async:callback(function()
              ui.showMessage(("Toggled borders %s"):format(bordersToggle and 'yes' or 'no'))
              if bordersToggle then
                bordersOverride = mwUi:temporaryOverride('borders', {})
              else
                if bordersOverride then
                  bordersOverride()
                end
              end
              bordersToggle = not bordersToggle
              ui.updateAll()
            end),
          },
        }
      },
    },
  }
}
Edited by uramer

Merge request reports

Loading