• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

CustomWindow v1.1.1.2

JASS:
/*************************************************
 *  CustomWindow v1.1.1.2 by Maker
 * 
 *  Allows the creation of custom windows.
 *
 *  Requires Table by Bribe and TimerUtils by Vexorian
 *
 * Importing instructions:
 * 1.   Import all images with the name beginning with Border.
 *      There are 8 of them. Also copy background.TGA.
 * 2.   Make sure the image paths are correct in the globals block in this library.
 * 3.   Copy CustomTextWindow, Table and TimerUtilsEx into your map.
 *
 *
 * API
 *
 * struct CustowWindow
 *
 * o method destroy takes nothing returns nothing
 *      Destroys a custom window
 * o method setHeight takes real height returns nothing
 *      Sets the height of a custom window
 * o method setTransparency takes integer alpha returns nothing
 *      Sets the transparency of a window
 * o method show takes boolean show returns nothing
 *      Shows/hides a custom window
 * o static method create takes real scale, real sizeX, real sizeY, real posX, real posY, integer imageType returns thistype
 *      Creates a custom window
 *
 * struct CustomTextWindow
 *
 * o method destroy takes nothing returns nothing
 *      Destroys a custom text window
 * o method show takes boolean show returns nothing
 *      Shows/hides a custom text window
 * o method setTransparency takes integer alpha returns nothing
 *      Sets the transparency of a custom text window
 * o method fadeOut real time, real delay, boolean destroy returns nothing
 *      Fades out a custom text window. Time is the fade time, delay the time before fading.
 *      If destroy is true, the window will be destroyed when it has faded out.
 * o method setHeight real height returns nothing
 *      Sets the height of a custom window
 * o static method create takes string title, string text, real posX, real posY, real textSize, real borderScale, integer maxWidth, integer imageType returns thistype
 *      Creates a custom text window
 *
 *
 *  Try to avoid large text sizes, over about 28, since
 *  the game engine will add a line break if text tag width
 *  would become too large.
 *
 ************************************************/

library CustomWindow requires Table, TimerUtils

    globals
        private constant string     BACKGROUND  = "war3mapImported\\background.TGA"
        private constant string     BOTTOM_L    = "war3mapImported\\BorderDownLeftNE.TGA"
        private constant string     TOP_L       = "war3mapImported\\BorderUpLeftNE.TGA"
        private constant string     BOTTOM_R    = "war3mapImported\\BorderDownRightNE.TGA"
        private constant string     TOP_R       = "war3mapImported\\BorderUpRightNE.TGA"
        private constant string     LEFT        = "war3mapImported\\BorderLeftNE.TGA"
        private constant string     TOP         = "war3mapImported\\BorderUpNE.TGA"
        private constant string     RIGHT       = "war3mapImported\\BorderRightNE.TGA"
        private constant string     BOTTOM      = "war3mapImported\\BorderDownNE.TGA"
        
        private constant integer    TRANS       = 200       // Transparency of the background, 0 = fully transparent, 255 = not transparent
        private constant integer    MAXCHARS    = 38        // Maximum number of characters in a row
        private constant real       CHARHEIGHT  = 4         // Height of a letter in pixels
        private constant real       CHARWIDTH   = 1.5       // Width of a letter in pixels
        private constant real       BORDERWIDTH = 28        // Width of borders, visible portion of default size
        private constant real       LOOPTIME    = 0.03125
        
        /* Use CHARHEIGHT and CHARWIDTH to adjust the window's wrapping around the text */
    endglobals
    
    // Not to be changed
    globals
        private             real        X
        private             real        Y
        private constant    real        DEFSIZE     = 64    // Size of visible portion of an image
        private constant    real        RATIO       = (DEFSIZE+2)/DEFSIZE
        private             location    LOC         = Location(0,0)
    endglobals

    struct CustomWindow extends array
        private integer images
        private static Table array imgs
        private static integer ic = 0
        private static integer ir = 0
        private thistype rn
        
        method destroy takes nothing returns nothing
            local integer i = this.images
            loop
                exitwhen i == 0
                call DestroyImage(imgs[this].image[i])
                set i = i - 1
            endloop
            call imgs[this].destroy()
            set .rn = ir
            set ir = this
        endmethod
        
        method setHeight takes real height returns nothing
            local integer i = .images
            loop
                exitwhen i == 0
                call SetImageConstantHeight(imgs[this].image[i], true, height)
                set i = i - 1
            endloop
        endmethod
        
        method show takes boolean show returns nothing
            local integer i = .images
            loop
                exitwhen i == 0
                call SetImageRenderAlways(imgs[this].image[i], show)
                call ShowImage(imgs[this].image[i], show)
                set i = i - 1
            endloop
        endmethod
        
        method setTransparency takes integer alpha returns nothing
            local integer i = .images
            loop
                exitwhen i == 0
                if i != 1 then
                    call SetImageColor(imgs[this].image[i], 255, 255, 255, alpha)
                elseif alpha < TRANS then
                    call SetImageColor(imgs[this].image[i], 255, 255, 255, alpha)
                else
                    call SetImageColor(imgs[this].image[i], 255, 255, 255, TRANS)
                endif
                set i = i - 1
            endloop
        endmethod
    
        private static method addRecycle takes nothing returns thistype
            local thistype this
            if ir == 0 then
                set ic = ic + 1
                set this = ic
            else
                set this = ir
                set ir = .rn
            endif
            return this
        endmethod
        
        // posX and posY are the coordinates of the center of the image
        static method create takes real borderScale, real sizeX, real sizeY, real posX, real posY, integer imageType returns thistype
            local thistype this     = thistype.addRecycle()
            local integer   i       = 0             // Image counter
            local integer   j                       // For loops
            local integer   nx      = 0             // How many x pieces
            local integer   ny      = 0             // How many y pieces
            local real      sx      = 0             // X size
            local real      sy      = 0             // Y size
            local real      cx      = 0             // X coordinate of lower left corner
            local real      cy      = 0             // Y coordinate of lower left corner
            local real      bwidth                  // Border width
            local real      sxa
            local real      sya
                   
            set X = DEFSIZE*borderScale                               // Visible image size of one image
            set Y = DEFSIZE*borderScale
            set bwidth = BORDERWIDTH * borderScale                    // Visible border width
            set imgs[this] = Table.create()
            
            if sizeX < 2*X then
                set sizeX = 2*X
            endif
            set sx = X * RATIO                                  // X size of one block, corners
            set nx = R2I((sizeX-2*X) / X)                       // How many top/bottom blocks, excluding corner blocks          
            set sxa = (sizeX-2*X) / (nx * X) * sx               // X size of one block, top and bottom
            set cx = posX - sizeX/2 + X - borderScale                 

            if sizeY < 2*Y then
                set sizeY = 2*Y
            endif
            set sy = Y * RATIO
            set ny = R2I((sizeY-2*Y) / Y)
            set sya = (sizeY-2*X) / (ny * X) * sy
            set cy = posY - sizeY/2 + Y - borderScale
            
            set i = i + 1   // Background
            set imgs[this].image[i] = CreateImage(BACKGROUND, sizeX-bwidth, sizeY-bwidth, 0, posX - (sizeX-bwidth)/2, posY - (sizeY-bwidth)/2, 0, 0, 0, 0, imageType)
            call SetImageColor(imgs[this].image[i], 255, 255, 255, TRANS)
            
            set posX = posX - borderScale
            set posY = posY - borderScale

            set i = i + 1   // Down left corner
            set imgs[this].image[i] = CreateImage(BOTTOM_L, sx, sy, 0, posX - sizeX/2, posY - sizeY/2, 0, 0, 0, 0, imageType)
            
            set i = i + 1   // Up left corner
            set imgs[this].image[i] = CreateImage(TOP_L,    sx, sy, 0, posX - sizeX/2, posY + sizeY/2 - sy/RATIO, 0, 0, 0, 0, imageType)
            
            set i = i + 1   // Up right corner
            set imgs[this].image[i] = CreateImage(TOP_R,    sx, sy, 0, posX + sizeX/2 - X, posY + sizeY/2 - sy/RATIO, 0, 0, 0, 0, imageType)
            
            set i = i + 1   // Down right corner
            set imgs[this].image[i] = CreateImage(BOTTOM_R, sx, sy, 0, posX + sizeX/2 - X, posY - sizeY/2, 0, 0, 0, 0, imageType)

            set j = 0
            loop            // Up and down blocks
                set i = i + 1
                set imgs[this].image[i] = CreateImage(TOP, sxa, sy, 0, cx, posY + sizeY/2 - Y, 0, 0, 0, 0, imageType)
                set i = i + 1
                set imgs[this].image[i] = CreateImage(BOTTOM, sxa, sy, 0, cx, posY - sizeY/2, 0, 0, 0, 0, imageType)
                set j = j + 1
                exitwhen j == nx
                set cx = cx + sxa/RATIO
            endloop
            
            set j = 0
            loop            // Left and right blocks
                set i = i + 1
                set imgs[this].image[i] = CreateImage(LEFT, sx, sya, 0, posX - sizeX/2, cy, 0, 0, 0, 0, imageType)
                set i = i + 1
                set imgs[this].image[i] = CreateImage(RIGHT, sx, sya, 0, posX + sizeX/2 - X, cy, 0, 0, 0, 0, imageType)
                set j = j + 1
                exitwhen j == ny
                set cy = cy + sya/RATIO
            endloop
            
            set this.images = i
            
            return this
        endmethod
        
    endstruct
    
    struct CustomTextWindow extends array
        private real ttPosX
        private real ttPosY
        private real height
        private real ttSize
        private integer visibility
        private integer fadeRate
        private integer fadeOutRate
        private timer t
        private timer tOut
        private string title
        private string text
        texttag tt
        private boolean destroys
        private boolean destroysOut
        CustomWindow cw
        private thistype rn
        private static integer ic = 0
        private static integer ir = 0
        
        method destroy takes nothing returns nothing
            if this.tt != null then
                call DestroyTextTag(this.tt)
                set this.tt = null
            endif
            set this.title = null
            set this.text = null
            set this.destroys = false
            if this.t != null then
                call ReleaseTimer(this.t)
                set this.t = null
            endif
            if this.tOut != null then
                call ReleaseTimer(this.tOut)
                set this.tOut = null
            endif
            call this.cw.destroy()
            set this.rn = ir
            set ir = this
        endmethod
        
        method setTransparency takes integer alpha returns nothing
            set this.visibility = alpha
            if this.tt == null and alpha > 0 and alpha < 255 then
                call this.show(true)
                call SetTextTagPermanent(this.tt, false)
                call SetTextTagColor(this.tt, 255, 255, 255, alpha)
            elseif alpha > 0 then
                call SetTextTagColor(this.tt, 255, 255, 255, alpha)
            elseif this.tt != null and alpha <= 0 then
                call DestroyTextTag(this.tt)
                set this.tt = null
            endif
            call this.cw.setTransparency(alpha)
        endmethod
        
        method show takes boolean show returns nothing
            if show then
                if this.tt == null then
                    call MoveLocation(LOC, .ttPosX, .ttPosY)
                    set this.tt = CreateTextTag()
                    call SetTextTagText(this.tt, this.text, this.ttSize/1000)
                    call SetTextTagPos(this.tt, this.ttPosX, this.ttPosY, .height - GetLocationZ(LOC))
                    call SetTextTagColor(this.tt, 255, 255, 255, 255)
                    call SetTextTagVisibility(this.tt, true)
                endif
            else
                if this.t != null then
                    call PauseTimer(this.t)
                endif
                call DestroyTextTag(this.tt)
                set this.tt = null
            endif
            call this.cw.show(show)
        endmethod
        
        private static method fade takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            set this.visibility = this.visibility - this.fadeRate
            if this.visibility > 0 and this.visibility < 255 then
                call this.setTransparency(this.visibility)
            else
                call PauseTimer(this.t)
                if this.destroys then
                    call this.destroy()
                else
                    if this.fadeRate < 0 then
                        call this.setTransparency(255)
                    elseif this.fadeRate > 0 then
                        call this.setTransparency(0)
                    endif
                    if this.t != null then
                        call ReleaseTimer(this.t)
                        set this.t = null
                    endif
                endif
            endif
        endmethod
        
        private static method fadeDelay takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            set this.fadeRate = this.fadeOutRate
            set this.destroys = this.destroysOut
            call SetTextTagPermanent(this.tt, false)
            if this.tOut != null then
                call ReleaseTimer(this.tOut)
                set this.tOut = null
            endif
            if this.t == null then
                set this.t = NewTimerEx(this)
            endif
            call TimerStart(this.t, LOOPTIME, true, function thistype.fade)
        endmethod
        
        method fadeIn takes real time returns nothing
            if .t == null then
                set .t = NewTimerEx(this)
            endif
            if time == 0 then
                set time = 0.01
            endif
            set .destroys = false
            set .fadeRate = -(R2I(255*LOOPTIME/time) + 1)
            call SetTextTagPermanent(.tt, false)
            call TimerStart(.t, LOOPTIME, true, function thistype.fade)
        endmethod
        
        method fadeOut takes real time, real delay, boolean destroys returns nothing
            if time == 0 then
                set time = 0.01
            endif
            set .fadeOutRate = R2I(255*LOOPTIME/time) + 1
            set .destroysOut = destroys
            call SetTextTagPermanent(.tt, false)
            if delay != 0 then
                if .tOut == null then
                    set .tOut = NewTimerEx(this)
                endif
                call TimerStart(.tOut, delay, false, function thistype.fadeDelay)
            else
                if .tOut != null then
                    call PauseTimer(.tOut)
                endif
                if .t == null then
                    set .t = NewTimerEx(this)
                endif
                set this.fadeRate = this.fadeOutRate
                set this.destroys = this.destroysOut
                call TimerStart(.t, LOOPTIME, true, function thistype.fade)
            endif
        endmethod
        
        method setHeight takes real height returns nothing
            call MoveLocation(LOC, .ttPosX, .ttPosY)
            call SetTextTagPos(.tt, .ttPosX, .ttPosY, height - GetLocationZ(LOC))
            call this.cw.setHeight(height)
            set .height = height
        endmethod
    
        private static method addRecycle takes nothing returns thistype
            local thistype this
            if 0==ir then
                set ic=ic+1
                set this=ic
            else
                set this=ir
                set ir=.rn
            endif
            return this
        endmethod
    
        // posX and pos Y are the coordinates of bottom left corner
        static method create takes string title, string text, real posX, real posY, real textSize, real borderScale, integer maxWidth, integer imageType returns thistype
            local thistype this = thistype.addRecycle()
            local integer i
            local integer rows = 1
            local integer wordlength = 0
            local string word = null
            local string newStr = null
            local string char
            local integer chars = 0
            local integer charsMax = 0
            local real width
            local real sizeX
            local real sizeY
            
            if maxWidth > MAXCHARS or maxWidth == 0 then
                set maxWidth = MAXCHARS
            endif
            
            if text != null then
                set rows = rows + 1
                set width = 0
                set i = 0
                loop
                    set char = SubString(text, i, i+1)
                    if char == " " or char == "" then
                        if chars < maxWidth then
                            set newStr = newStr + " " + word
                            if char == " " then
                                set chars = chars + 1
                            endif
                            if chars > charsMax then
                                set charsMax = chars
                            endif
                        else
                            if chars - wordlength > charsMax then
                                set charsMax = chars - wordlength
                            endif
                            set newStr = newStr + "\n" + " " + word
                            set chars = 1 + wordlength
                            set rows = rows + 1
                        endif
                        set word = null
                        set wordlength = 0
                    else
                        set chars = chars + 1
                        set wordlength = wordlength + 1
                        set word = word + char
                    endif
                    exitwhen char == ""
                    set i = i + 1
                endloop
            else
                set charsMax = StringLength(title) + 2
            endif
            
            call MoveLocation(LOC, posX, posY)
            set this.height = GetLocationZ(LOC)
            
            set sizeX = charsMax * CHARWIDTH * textSize/5 + 2*DEFSIZE*borderScale
            set sizeY = rows * CHARHEIGHT * textSize/5 + 2*DEFSIZE*borderScale
            
            set this.ttSize = textSize
            set this.ttPosX = posX
            set this.ttPosY = posY
            set this.title = title
            set this.text = "|cffffcc00" + this.title + "|r\n" + newStr
            set this.cw = CustomWindow.create(borderScale, sizeX, sizeY, posX+sizeX/2-DEFSIZE*borderScale, posY+sizeY/2-DEFSIZE*borderScale, imageType)
            
            set char = null
            set newStr = null
            
            return this
        endmethod
    endstruct
        
endlibrary



The system is useful in creating tooltips for buttons in inventory systems, creating windows for full screen invetories and hero pic systems.

You could create all windows with this and hide the images easily so the system doesn't take map space.

The system creates the border from blocks, avoiding overly stretched images. For example with image size 1, a window 320 pixels wide would horizontally consist of two corner pieces and 3 top border pieces.

The default size of a block is 64. You can use scale parameter in create method to change the value.

CustomWindow creates a window without text, CustomTextWindow adds text.

The system goes through the given text and breaks it down to words. It keeps track of the character count per line, and adds line breaks when needed. It then calculates the amount of blocks in width and height needed to wrap the text.

Floating texts suffer from automated line breaks if they become too wide, so avoid large text sizes.



v0.9.0.0 Uploaded 22.11.2011
v0.9.0.1 Uploaded 23.11.2011
-Changed text casing
-Changed image path variable names
-Replaced TimerUtilsEx library with TimerUtils
-Changed static method destroyCW/destroyCT to method destroy
-Changed add method to create
-CustomTextWindow struct extends array now
v0.9.0.2 Uploaded 29.11.2011
-Reduced the imported image size from 96x96 to 66x66. File size from 36 kb to 17 kb.
-set nx = R2I((sizeX-2*X) / X) + 1 -> set nx = R2I(0.5 + (sizeX-2*X) / X). Results in tighter wraps in some cases.
v0.9.0.3 Uploaded 13.12.2011
-Changed static methods that manipulate spesific windows to non static methods
v0.9.1.0 Uploaded 19.01.2012
-Prevented floating texts from being faded out by the game engine
-If a custom text window is not destroyed after fading out, it doesn't release and null the timer of the struct. Pauses it instead
v1.0.0.0
-Floating text now fades instead of disappering immediately, just like the borders
-Rather than storing the floating text, the system stores the text as a string when the text is not visible
v1.0.1.0 Uploaded 08.01.2013
-Now the windows can fade in
-Renamed CTW.fade to CTW.setTransparency
v1.0.1.1 Uploaded 09.01.2013
-Optimized code
-Removed a bug which caused the text to appear and disappear after the default
floating text removal time. Text tag are set to destroy when set to invisible now
v1.1.1.1 Uploaded 10.01.2013
-Now supports exact window sizes instead of rounding. This means you can align windows next to
each other more easily and the texts are wrapped more aesthetically and consistently
-v1.1.1.2 Uploaded 18.01.2013
-Added GetLocationZ when creating floating texts. Images and texts are now at the same height regardless of terrain height


Keywords:
custom, window, image, maker, tooltip, text
Contents

CustomWindow (Map)

Reviews
24th Nov 2011 Bribe: Approved. Highly recommended with potential for Director's Cut as it gets more refined.
Level 37
Joined
Mar 6, 2006
Messages
9,240
v1.1.1.1 Uploaded 10.01.2013
-Now supports exact window sizes instead of rounding. This means you can align windows next to
each other more easily and the texts are wrapped more aesthetically and consistently

This was a very nice functional update if I may say so. You couldn't place windows next to each other before without leaving a space between then or the windows overlapping. Now you can.
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
-v1.1.1.2
-Added GetLocationZ when creating floating texts. Images and texts are now at the same height regardless of terrain height

When images are created, they take terrain height into consideration, floating texts do not. Previously if you create a text window on a spot that had terrain height something else than 0, the floating text was at a wrong height causing it to not fit the window as intended.

Now it should be easier to make text fit the window.
 
Level 6
Joined
Jan 4, 2014
Messages
227
Gonna be useful in my map, Thanks :)

EDIT : is there a way to keep the windows following the screen while the camera moves ?

i.e adding new things to the UI :D
 
Last edited:
Top