- Joined
- Mar 18, 2012
- Messages
- 1,716
UIWindow
Struct UIWindow is the parent struct you base your windows in an UIScreen on.
JASS:
library UIWindow uses UIButton, UIBorder, UIBasic, UIScreen/* v1.0
*****************************************************************************
*
* Each custom UI is build of one UIScreen having a unlimited amount of UIWindows.
* Struct UIWindow is the parent struct you base your windows in an UIScreen on.
*
* Realistic example:
* struct Inventory extends UIScreen
* struct Backpack extends UIWindow.
* struct Equipmenet extends UIWindow ...
*
****************************************************************************
*/
globals
//* Required room for data storage:
//* ===============================
private constant hashtable TABLE = InitHashtable()
private constant hashtable TRACKER = InitHashtable()
endglobals
//* Parent struct of all custom windows.
struct UIWindow
//* Stub methods:
//* =============
public stub method onHover takes UIScreen whichScreen, UIButton hovered returns nothing
endmethod
public stub method onClick takes UIScreen whichScreen, UIButton clicked returns nothing
endmethod
public stub method onShow takes UIScreen whichScreen, boolean flag returns nothing
endmethod
public stub method onShowPage takes UIScreen whichScreen, boolean flag returns nothing
endmethod
//* Readonly members:
//* =================
readonly UIScreen screen
readonly UIBorder border
readonly Table cells
readonly real lastClick
readonly real originX
readonly real originY
readonly real width
readonly real height
//* I honestly don't know how this method performs on Battle.net
//* In single player games it works flawless. ( latency? )
method isDoubleClick takes nothing returns boolean
return (TimerGetElapsed(UI_STAMP) - lastClick < UI_MOUSE_DOUBLE_CLICK)
endmethod
//* Get the amount of buttons in a window.
readonly static constant integer TABLE_SIZE = -1
method getButtonCount takes nothing returns integer
return cells[TABLE_SIZE]
endmethod
//* User-friendly type-casting.
method getButton takes integer index returns UIButton
debug call ThrowWarning(not cells.has(index), "UIWindow", "getButton", "index", this, "No UIButton saved on this index [" + I2S(index) + "]!")
return cells[index]
endmethod
method operator enabled takes nothing returns boolean
return screen.isWindowOfTypeOpen(getType())
endmethod
//* User-friendly getters.
method operator user takes nothing returns player
return screen.user
endmethod
method operator userId takes nothing returns integer
return screen.userId
endmethod
//* Get window dimensions:
//* ======================
//! textmacro UI_WINDOW_SIZE_OPERATOR takes NAME, SUFFIX, EXTRA
public method operator $NAME$$SUFFIX$ takes nothing returns real
return origin$SUFFIX$ $EXTRA$
endmethod
//! endtextmacro
//! runtextmacro optional UI_WINDOW_SIZE_OPERATOR("max", "X", "+ width")
//! runtextmacro optional UI_WINDOW_SIZE_OPERATOR("max", "Y", "+ height")
//! runtextmacro optional UI_WINDOW_SIZE_OPERATOR("min", "X", "")
//! runtextmacro optional UI_WINDOW_SIZE_OPERATOR("min", "Y", "")
//! runtextmacro optional UI_WINDOW_SIZE_OPERATOR("center", "X", "+ width*.5")
//! runtextmacro optional UI_WINDOW_SIZE_OPERATOR("center", "Y", "+ height*.5")
//* Multiple pages:
//* ===============
//* The page API is still in its infancy.
//* I will work on it when I have time.
readonly integer pages
readonly integer page
readonly integer pageSize
private boolean circle//* When nextPage() hits the maximum, should we go back to page 1? ( same for prevPage() )
debug private boolean invalid
//* Optional getters:
//* =================
//* Optional getters:
//* =================
static if LIBRARY_UIBoard then
method operator board takes nothing returns UIBoard
return screen.userId
endmethod
endif
static if LIBRARY_UISound then
method operator audio takes nothing returns UISound
return screen.userId
endmethod
endif
static if LIBRARY_UITextbox and LIBRARY_Font then
method operator textbox takes nothing returns UITextbox
return screen.userId
endmethod
endif
//* Window Dummy:
//* =============
//* The dummy inherits the window coordinates. Just pass in a offset.
method createDummy takes player p, integer dummyId, real offsetX, real offsetY, real offsetZ, real face, real scale returns unit
local boolean prev = ToogleUnitIndexer(false)
set bj_lastCreatedUnit = CreateUnit(p, dummyId, originX + offsetX, originY + offsetY, 1)
call ToogleUnitIndexer(prev)
call SetUnitFacing(bj_lastCreatedUnit, face + 1.)
call SetUnitFacing(bj_lastCreatedUnit, face)
call SetUnitScale(bj_lastCreatedUnit, scale*screen.scale, 0., 0.)
call UnitAddAbility(bj_lastCreatedUnit, 'Aloc')
call UnitAddAbility(bj_lastCreatedUnit, 'Amrf')
call UnitRemoveAbility(bj_lastCreatedUnit, 'Amrf')
call SetUnitFlyHeight(bj_lastCreatedUnit, offsetZ, 0.)
call SetUnitX(bj_lastCreatedUnit, originX + offsetX)
call SetUnitY(bj_lastCreatedUnit, originY + offsetY)
if (GetLocalClient() != user) then
call SetUnitVertexColor(bj_lastCreatedUnit, 0, 0, 0, 0)// More safe then hide/show unit for a local client.
endif
call PauseUnit(bj_lastCreatedUnit, true)
return bj_lastCreatedUnit
endmethod
//* Pages API:
//* ==========
//* Set(), Next(), Prev() ... all return a boolean for success.
private method showPage takes boolean flag returns nothing
local UIButton b
local integer i = pageSize*(page - 1)
local integer size = i + pageSize
loop
exitwhen (i == size)
set b = getButton(i)
if (b.enabled != flag) then
set b.enabled = flag
call b.show(flag)
endif
set i = i + 1
endloop
call onShowPage(screen, flag)
endmethod
method setPage takes integer index returns boolean
if not (enabled) or (index > pages) or (index == page) or (index < 1) then
return false
endif
call showPage(false)
set page = index
call showPage(true)
return true
endmethod
method prevPage takes nothing returns boolean
local integer prev = page - 1
if (pages == 1) or not (enabled) then
return false
elseif (prev < 1) then
if not (circle) then
return false
endif
set prev = pages
endif
call showPage(false)
set page = prev
call showPage(true)
return true
endmethod
method nextPage takes nothing returns boolean
local integer next = page + 1
if (pages == 1) or not (enabled) then
return false
elseif (next > pages) then
if not (circle) then
return false
endif
set next = 1
endif
call showPage(false)
set page = next
call showPage(true)
return true
endmethod
//* Pages are copies of the first page. API may change in the future.
//* They also can inherit Button data if you want.
private static thistype var = 0
private static boolean trans = false
private static method newPage takes nothing returns nothing
local thistype this = thistype.var
local boolean import = thistype.trans
local integer index = 0
local integer size = cells[thistype.TABLE_SIZE]
local UIButton copy = 0
set pages = pages + 1
loop
exitwhen index == pageSize
set copy = getButton(index)
if (copy.defaultId == 0) then
set cells[size] = UIButton.createImg(copy.typeId, user, copy.defaultStr, copy.track, copy.scale, false)
else
set cells[size] = UIButton.createDest(copy.typeId, user, copy.defaultId, copy.track, copy.scale, false)
endif
if (import) then
set UIButton(cells[size]).data = copy.data
endif
set size = size +1
set index = index + 1
endloop
set cells[thistype.TABLE_SIZE] = size
endmethod
//* Pages API:
//* ==========
//* Add() generates x copies of the first page.
method addPages takes integer amount, boolean import, boolean circulate returns nothing
debug call ThrowError((amount <= 0), "UIWindow", "addPages", "amount", this, "Inavlid argument ( amount <= 0 )!")
debug call ThrowError((amount >= 10), "UIWindow", "addPages", "amount", this, "The maximum number of allowed pages is 9!")
debug call ThrowError((invalid), "UIWindow", "addPages", "invalid", this, "Additional Pages Can Only Be Set Once!")
debug set invalid = true
set pageSize = cells[thistype.TABLE_SIZE]
set circle = circulate
loop
exitwhen 0 >= amount
set thistype.var = this
set thistype.trans = import
call ExecuteFunc(thistype.newPage.name)
set amount = amount - 1
endloop
endmethod
//* Show a window:
//* ==============
method show takes boolean flag returns nothing
if (this == 0) then
return
endif
if (flag) and (GetPlayerUI(screen.user) == screen) then
if (screen.openWindowOfType(getType())) then
set page = 1
else
return
endif
elseif not (flag) then
if not (screen.closeWindowOfType(getType())) then
return
endif
else
return
endif
if (GetLocalClient() == screen.user) then
call border.show(flag)
endif
call onShow(screen, flag)
call showPage(flag)
endmethod
//**
//* Track search & save:
//* ====================
//*
//* Trackables are poorly supported in Warcraft III. In fact we can't
//* remove handles of type "trackable" once they are no longer used.
//* Therefore I came up with an algorithm to search out of "all ever created Tracks in UIWindows"
//* these which match 100% to the one we wish to create and use that Track instance instead.
//*
//* library TileDefinition creates unique integers per tile in your map.
//* Tracks are saved in stacks on the tile id in an hashtable.
//* With an 64x64 track model the loop will exit after (1 - 3) * players loops.
//* For bigger model it will be 1 * players, for smallers the loopup time will increase.
//*
//* Due to an hashtable lookup during both trackable events ( hover & click ),
//* a Track instance can evaluate any UIWindow onHover/onClick stub method without
//* running into code collision with other UIs which use the same Track instance.
//* Credits to IcemanBo:
//* ====================
static if not LIBRARY_TileDefinition then
private static integer WorldTiles_X = 0
private static integer WorldTiles_Y = 0
private static method setVars takes nothing returns nothing
set WorldTiles_X = R2I(WorldBounds.maxX - WorldBounds.minX) / 128 + 1
set WorldTiles_Y = R2I(WorldBounds.maxY - WorldBounds.minY) / 128 + 1
endmethod
private static method GetTileId takes real x, real y returns integer
local integer xI = R2I(x - WorldBounds.minX + 64) / 128
local integer yI = R2I(y - WorldBounds.minY + 64) / 128
if ((xI < 0) or (xI >= .WorldTiles_X) or (yI < 0) or (yI >= .WorldTiles_Y)) then
return -1
endif
return (yI * .WorldTiles_X + xI)
endmethod
endif
private method saveTrack takes Track track returns Track
local integer id = GetTileId(track.x, track.y)
local integer size = LoadInteger(TRACKER, id, 0) + 1
call SaveInteger(TRACKER, id, size, track)
call SaveInteger(TRACKER, id, -size, userId)
call SaveInteger(TRACKER, id, 0, size)
return track
endmethod
private method searchTrack takes string model, real x, real y, real z, real face returns Track
local integer id = GetTileId(x, y)
local integer size = LoadInteger(TRACKER, id, 0)
local Track t
loop
exitwhen 0 == size
if (LoadInteger(TRACKER, id, -size) == userId) then
set t = LoadInteger(TRACKER, id, size)
if (t.x == x) and (t.y == y) and (t.z == z) and (t.facing == face) and (t.model == model) then
return t
endif
endif
set size = size - 1
endloop
return saveTrack(Track.createForPlayer(model, x, y, z, face, user))
endmethod
//* UIButton API:
//* =============
//* Adds an UIButton to your custom UI. The function uses a search & save algorithm for Tracks
//* ( read above ) to optimize the trackable handle count in your map.
method addDestButton takes string model, real x, real y, real z, real face, integer objectId, real scale returns UIButton
local integer typeId = getType()
local integer size = getButtonCount()
local Track track = searchTrack(model, originX + x, originY + y, z, face)
debug call ThrowError(invalid, "UIWindow", "addButton", "invalid", this, "Can't add further UIButtons, once method addPages is called!")
set cells[size] = UIButton.createDest(typeId, user, objectId, track, scale*screen.scale, false)
set cells[TABLE_SIZE]= size + 1
set pageSize = size + 1
call SaveInteger(TABLE, screen, -track, this)
call SaveInteger(TABLE, screen, track, size)
return cells[size]
endmethod
method addImageButton takes string model, real x, real y, real z, real face, string file, real scale returns UIButton
local integer typeId = getType()
local integer size = getButtonCount()
local Track track = searchTrack(model, originX + x, originY + y, z, face)
debug call ThrowError(invalid, "UIWindow", "addButton", "invalid", this, "Can't add further UIButtons, once method addPages is called!")
set cells[size] = UIButton.createImg(typeId, user, file, track, scale*screen.scale, false)
set cells[TABLE_SIZE]= size + 1
set pageSize = size + 1
call SaveInteger(TABLE, screen, -track, this)
call SaveInteger(TABLE, screen, track, size)
return cells[size]
endmethod
//* Wrapper methods to ease the usage of UIBorder. ( adds originX / originY to the desired coordinates )
method frame takes real borderScale returns nothing
call border.construct(originX + width*.5, originY + height*.5, width, height, borderScale*screen.scale)
endmethod
method frameEx takes real extraX, real extraY, real borderScale returns nothing
call border.construct(originX + width*.5, originY + height*.5, width + extraX, height + extraY, borderScale*screen.scale)
endmethod
method addBorder takes real x, real y, real sizeX,real sizeY, real borderScale returns nothing
call border.construct(originX + x + sizeX*.5, originY + y + sizeY*.5, sizeX, sizeY, borderScale*screen.scale)
endmethod
method addBorderImage takes string file, real x, real y, real sizeX, real sizeY, integer imageType returns image
return border.addImage(file, sizeX, sizeY, originX + x, originY + y, imageType)
endmethod
method addScreenImage takes string file, real x, real y, real sizeX, real sizeY, integer imageType returns image
return border.addImage(file, sizeX, sizeY, screen.originX + x, screen.originY + y, imageType)
endmethod
//* Creates a new UIWindow for the player allocated in the UIScreen argument!
static method create takes UIScreen whichScreen, real x, real y, real sx, real sy returns thistype
debug local boolean error1 = (x + sx > whichScreen.width)
debug local boolean error2 = (y + sy > whichScreen.height)
debug local boolean error3 = (x < 0.)
debug local boolean error4 = (y < 0.)
local thistype this = thistype.allocate()
set cells = Table.create()
set originX = whichScreen.originX + x
set originY = whichScreen.originY + y
set width = sx
set height = sy
set screen = whichScreen
set border = UIBorder.create()
set pages = 1
set page = 1
set pageSize = 0
debug set invalid = false
debug call ThrowError(error1, "UIWindow", "create", "error1", this, "X coordinates of the window are out of the screen bounds [" + R2S(x + sx) + "]!")
debug call ThrowError(error2, "UIWindow", "create", "error2", this, "Y coordinates of the window are out of the screen bounds [" + R2S(y + sy) + "]!")
debug call ThrowError(error3, "UIWindow", "create", "error3", this, "X coordinates of the window are out of the screen bounds [" + R2S(x) + "]!")
debug call ThrowError(error4, "UIWindow", "create", "error4", this, "Y coordinates of the window are out of the screen bounds [" + R2S(y) + "]!")
return this
endmethod
//* Trackable events:
//* =================
private static method onAnyHover takes nothing returns boolean
local UIScreen UI = GetPlayerUI(Track.tracker)
local thistype this = LoadInteger(TABLE, UI, -Track.instance)
local integer index = LoadInteger(TABLE, UI, Track.instance) + (pageSize*(page - 1))
local UIButton hovered = cells[index]
if (this != 0) and (hovered.enabled) then
call onHover(UI, hovered)
endif
return false
endmethod
private static method onAnyClick takes nothing returns boolean
local UIScreen UI = GetPlayerUI(Track.tracker)
local thistype this = LoadInteger(TABLE, UI, -Track.instance)
local integer index = LoadInteger(TABLE, UI, Track.instance) + (pageSize*(page - 1))
local UIButton clicked = cells[index]
if (0 != this) and (clicked.enabled) then
call onClick(UI, clicked)
set lastClick = TimerGetElapsed(UI_STAMP)
endif
return false
endmethod
//* Trackables remain! As clean as possible.
method destroy takes nothing returns nothing
local integer index = 0
local integer size = getButtonCount()
call deallocate()
loop
exitwhen index == size
call getButton(index).destroy()
set index = index + 1
endloop
call cells.destroy()
call border.destroy()
endmethod
//* Executed from UIScreen:
//* =======================
//* via function.execute()
static method recursiveShow takes thistype this, boolean flag returns nothing
call show(flag)
endmethod
static method recursiveDestroy takes thistype this returns nothing
call destroy()
endmethod
private static method init takes nothing returns nothing
call Track.registerAnyClick(function thistype.onAnyClick)
call Track.registerAnyHover(function thistype.onAnyHover)
static if not LIBRARY_TileDefinition then
call thistype.setVars()
endif
endmethod
implement UIInit
//* Only available in debug mode. Highlights the borders of your screen.
//* So you can check ingame, that you settings are correct.
debug public method highlight takes nothing returns nothing
debug local string flash = "MFPB"
debug call AddLightning(flash, false, originX, originY, originX, originY + height)
debug call AddLightning(flash, false, originX, originY + height, originX + width, originY + height)
debug call AddLightning(flash, false, originX + width, originY, originX, originY)
debug call AddLightning(flash, false, originX + width, originY + height, originX + width, originY)
debug endmethod
endstruct
endlibrary
Changelog:
v1.1 - UIBorders are now also the same way relative to originX/originY as UIWindows are.
Last edited: