FAQ  •  Register  •  Login

Development: Creating a custom Hotkeys/Broadcasting Toggle

Moderator: MiRai

<<

lax

User avatar

Site Admin

Posts: 7134

Joined: Tue Nov 17, 2009 9:32 pm

Post Sun Jun 16, 2019 8:55 pm

Development: Creating a custom Hotkeys/Broadcasting Toggle

UPDATE: Here's the video walkthrough following this guide: https://youtu.be/EKHcvqlsrTE

This guide is going to go a little bit further into Module development. Module development will typically be done by developers, so if you don't consider yourself a developer or are not interested in any sort of code, you will not want to follow this guide (much like most WoW players will not want to follow guides on developing WoW Addons).

However, this guide is still very much entry-level and will not go into any sort of depth with LavishScript. Just enough to get by for this fairly simple tutorial!

As with all written guides, this looks very long. I'll do a video to go along with it. But a lot of the writing is important too, e.g. for copying and pasting, or proof-reading your code! :)

So first of all let's take a little look at what we're building. Here's a little spread of 4 states for our toggle button on the left, alongside the basic toggles from Core to its right (using the old ISBoxer 1 toggle images). The idea is a custom replacement for the original toggles.
Image
Don't mind the MS Paint job, I just whipped it up for this demo. And don't mind the title bars either -- we'll be able to turn those off later on like we do with ISBoxer 1.

The button is dark (more transparent) and with no icon in the middle if Hotkeys and Broadcasting are both off. It's lit up (less transparent) if Hotkeys are on, and has an icon if Broadcasting is on. You can left click on it to toggle Hotkeys, and right click to toggle Broadcasting. Simple enough.

So let's get busy. First, make sure you know how to create a Module and open it in VS Code -- if you're new to that, see Video: Creating an ISBoxer 2 Module. I've named my new Module MiRai Toggles because I asked him how he might like it to work, and this was an idea he described, and so I am literally writing this guide for MiRai. And everyone else, too! And because it matters later, I'll mention that the Module Creator Window automatically filled in the abbreviation MRT, and I left it at that. Part of what we do later on refers to "ISB2_MRT" and such and that's where the "MRT" comes from. (Disclaimer: MiRai did not provide the shoddy MS Paint job) If you choose another name and/or abbreviation, put your abbreviation in place of MRT as appropriate.

Most of this guide is going to be about creating in-game GUI using LavishGUI 2 (LGUI2), so we're going to do almost all of our work in the MRT.lgui2Package.json file. Open that file now!

Here's what is in the file by default:
Code:
{
  "$schema": "http://www.lavishsoft.com/schema/lgui2Package.json"
}


This file is in JSON format. The { } on the outside means it describes one object (the object that will be read by LGUI2). On the inside, a list of named properties. In this case, a special property called "$schema" specifies a schema, which tells an editor what to expect in the file. Here, the lgui2Package.json schema indicates that this is a LGUI2 Package file, and VS Code will use this information to guide us along while editing the file.

Let's start adding to the file. We're going to add a LGUI2 Element. At the end of the "$schema" line, place a comma and press enter, then make a double quote that's ".
Image

VS Code pops up some suggestions based on the schema as we type. Start typing "elements" and place a colon after
Image
... and VS Code recognizes that this should be an array and use [ ] rather than { }.

Arrays do not have named properties, they just have values separated by commas. The values can be objects, arrays, strings, numbers, etc. (This is just a crash course, you can look up the JSON format for details.) We're going to define one main LGUI2 element: a window to contain our button. So our array is just going to have one object in it. Inside the [ ], put a { }, and then start creating a "type" property in the innermost object. This is where we will specify that it is a window. And again, VS Code recognizes that we're working with an array of elements, and provides suggestions as we go

Image

Here's what we've got so far.
Code:
{
  "$schema": "http://www.lavishsoft.com/schema/lgui2Package.json",
  "elements": [
    {
      "type":"window"
    }
  ]
}


At this point, we can add more properties to our window by adding a comma at the end of the "type" line and providing more property names. VS Code knows a lot about them, but you will also want to have the LGUI2 documentation handy to refer to as needed. This page describes the basic properties available to all LGUI2 elements: https://www.lavishsoft.com/wiki/index.php/LGUI2:Element; it also has an example definition of a window. A window has more specific window-only properties, as described here: https://www.lavishsoft.com/wiki/index.php/LGUI2:window. A window is also a "Content Container" (https://www.lavishsoft.com/wiki/index.php/LGUI2:Content_Container) which essentially means that it can contain one piece of "content", along with a handful of other common pieces of a Content Container element.

We can use Metadata (briefly described on the Element wiki page) to mark our window as being an ISBoxer 2 window, so when we want to do things like "turn off the title bars on all ISBoxer 2 windows" (not covered in THIS guide) it will be included. LGUI2 recognizes properties that start with _ as metadata, with the actual name of the metadata starting after the underscore. So "_isboxer2" means metadata named isboxer2. Add an "_isboxer2" property set to true, as follows -- and do note that true must be lower case. And while we're doing our homework, let's give the window a "name" we can use to refer to it.
Code:
    {
      "name": "MRT.toggles",
      "type":"window",
      "_isboxer2": true
    }


We can already test our window in-game by loading our Module and launching a window. Let's quickly add a piece of content so we have something in our window. We can use a text string as content, and LGUI2 will automatically turn it into a textblock element.
Code:
{
      "name": "MRT.toggles",
      "type":"window",
      "_isboxer2": true,
      "content":"Hello World!"
    }


And here it is. Yay!
Image

Now, we actually want to use an image as our content. LGUI2 provides an imagebox element for us to use, which is designed to display and size to an image. Start by replacing our "Hello World!" string with an empty object { } and give it a "type":"imagebox". The important bit of an imagebox element is going to be the "imageBrush". This is a Brush object which is used to "paint" something, so to speak. This typically involves a color and/or an image file. If an image file is specified, the color is blended with the image. It is also helpful to note that the default color is solid black, so you will typically want to specify solid white when using an image file for it to appear normal. Let's have a look at how that appears with an image file called Base.png (placed in our Module folder):
Code:
      "content":{
        "type":"imagebox",
        "imageBrush": {
          "color":[1.0,1.0,1.0],
          "imageFile": "Base.png"
        }
      }


And here it is loaded in game:
Image

Now we get to talk about adding behaviors. We want it to fade out a bit if Hotkeys are disabled, fade in if Hotkeys are enabled, and we want to switch in an alternate image (Repeater On.png, also in our Module folder next to the other files) if Broadcasting is enabled. LGUI2 will handle most of these things with Styles. A Style is basically a list of properties to change together, and each element can have as many different Styles as it needs. So for our needs, we're going to want 4 styles -- one for Hotkeys on, one for Hotkeys off, one for Broadcasting on, and one for Broadcasting off. The Hotkeys styles will alter the "opacity" property (part of any Element), and the Broadcasting styles will alter the "imageBrush" property. Styles go in an object named "styles", and will be activated based on their name. So let's have a look at our Styles:

Code:
      "content": {
        "type": "imagebox",
        "imageBrush": {
          "color": [1.0,1.0,1.0],
          "imageFile": "Base.png"
        },
        "opacity":0.5,
        "styles": {
          "hotkeysOn":{
            "opacity":1.0
          },
          "hotkeysOff":{
            "opacity":0.5
          },
          "broadcastOn": {
            "imageBrush": {
              "color": [1.0,1.0,1.0],
              "imageFile": "Repeater On.png"
            }
          },
          "broadcastOff": {
            "imageBrush": {
              "color": [1.0,1.0,1.0],
              "imageFile": "Base.png"
            }
          }
        }
      }


I've also set opacity to 0.5 for the imagebox, so that its state is basically starting with the "hotkeysOff" and "broadcastOff" styles already applied.

Now we need to be able to activate these Styles when Hotkeys are turned on and off, or Broadcast is turned on and off. LGUI2 provides a Triggers system which allows the GUI to react to LavishScript conditions. We're not going to go much into detail with LavishScript in this tutorial, this only requires dipping our toes in a little bit. In the future these will be listed in a reference on the ISBoxer 2 wiki, but here I'm going to provide you with exactly what to drop in for these triggers, for any ISBoxer 2 Master Hotkey / Broadcasting toggle. For Hotkeys, the condition is "${ISBoxer2.MasterHotkeyToggleState}" and for Broadcasting the condition is "${ISBoxer2.MasterBroadcastingToggleState}".

Similar to how Styles are set up, we have a "triggers" object with named trigger definitions inside. The name of each trigger is not particularly important here, but should be unique for each trigger. Each trigger will specify its condition, along with an Event Handler called "matched" to do when the trigger is matched, and optionally an "unmatched" Event Handler as well. We'll go further with Event Handlers later, but in this case we just want our element to apply styles to itself. That's done by specifying "type":"style" and providing a "styleName".

Here's how that plays out:
Code:
"triggers": {
          "broadcast": {
            "condition": "${ISBoxer2.MasterBroadcastingToggleState}",
            "matched": {
              "type": "style",
              "styleName": "broadcastOn"
            },
            "unmatched": {
              "type": "style",
              "styleName": "broadcastOff"
            }
          },
          "hotkeys":{
            "condition": "${ISBoxer2.MasterHotkeyToggleState}",
            "matched": {
              "type": "style",
              "styleName": "hotkeysOn"
            },
            "unmatched": {
              "type": "style",
              "styleName": "hotkeysOff"
            }
          }
        }


And at that point, we have a working indicator system, which we can test alongside the Core Module's toggle buttons. Clicking the buttons from the Core toggles also changes our GUI, thanks to the triggers and styles!

Image

Finally, we can make our GUI respond to clicks. The idea is for left click to toggle Hotkeys, and right click to toggle Broadcasting. To make this happen, we're going to need an Event Handler with custom handling when a click happens on our imagebox. LGUI2 provides an event called "onMouseButtonMove" on all elements, which fires any time a mouse button is pressed or released on that element. We can add an Event Handler called "onMouseButtonMove", inside an "eventHandlers" object. This time, we're going to need to add a method in LavishScript, and have our handler use that method. So let's go ahead and fill in the Event Handler part first.

This time, the "type" for the Event Handler is "method", which requires an "object" and a "method". The object name comes from our Module abbreviation, in this case it will be ISB2_MRT. The method name we are making up, but it needs to follow pretty common rules for naming something in code (starts with alpha or underscore, can contain alphanumeric and underscores, no whitespace, etc). So I'm making up "OnToggleClick".
Code:
"eventHandlers": {
          "onMouseButtonMove": {
            "type": "method",
            "method": "OnToggleClick",
            "object": "ISB2_MRT"
          }
        }


Now when we press or release a mouse button on our image, it will try to fire off OnToggleClick in our LavishScript controller. Next we're going to build that!

I'm not going to get in-depth in LavishScript in this guide. For now, we're covering just enough to get by. There will be plenty of other new tutorials and new documentation to cover that later on! :)

Open the MRT.controller.iss file. It's going to have an "objectdef" in it, short for object definition. This objectdef has 3 methods in it by default -- empty Initialize() and Shutdown() methods, and a Task_Test(). The Task_Test() method is a skeleton for creating a Task, it does nothing but produce some debug output to help show what is going on. You can remove that method entirely, or leave it, it doesn't really matter. We're going to add our own method section with the name OnToggleClick()

Typically, LavishScript methods will take parameters as part of the method definition, inside the (). LGUI2 event handlers use an alternate method to provide direct access to various objects it has available in-context. It does this by providing an object called Context. The Context will include a JSON object called "Args" with details about the event, including the name of the control that was pressed or released, and of course the position of the control.

So let's start by peeking at that, we would like to know what is happening. Start with this code for your OnToggleClick method
Code:
   method OnToggleClick()
   {
      echo OnToggleClick ${Context(type)} ${Context.Source(type)} ${Context.Args}
      
   }

Image

This will produce output in the in-game Inner Space console each time the event executes. We can use that output to understand what is going on when we click, and how we can figure out when to toggle hotkeys or broadcasting.

Here's the output from left and then right clicking:
Image
So there's a few different ways the control is specified in this object. "controlID" appears to be 1 for left and 2 for right, but just next to that we see "controlName" as "Mouse1" and "Mouse2". That's what we're going to want to use. If we look hard enough we also see "position":true and "position":false. So when we press the position is true, and when we release the position is false.

For our desired behavior, we want to toggle Hotkeys when it's Mouse1 and only when we release the button. And we want to toggle Broadcasting when it's Mouse2 and only when we release the button.

We're going to use an "if" statement to ignore the button presses (and take action on the release), and a "switch" statement to do different things depending on the button.

Let's start with the "if", since that is basically an "early exit condition" meaning we can stop doing anything at all if the position is true. We want to access "position" from Context.Args, and "if" is going to test if our condition is non-zero. For true and false, true is non-zero and false is 0. So this works nicely into "if". This can be placed directly under the "echo" line.

Code:
      if ${Context.Args[position]}
      {
         ; pressed
         return
      }


So here we access "position" from Context.Args, and we "return" from the method if it is non-zero (true).

Now we want to make a "switch", and this will be using "controlName" from Context.Args. A switch will have multiple "case" lines, which LavishScript will match against the value from the "switch" line. So if controlName is Mouse1, we want to toggle Hotkeys, and if controlName is Mouse2 we want to toggle Broadcasting. Each "case" will then need a "break" line, so that execution leaves the switch instead of continuing on to do the case entries below it.

Here's what the final method looks like:
Code:
   method OnToggleClick()
   {
      echo OnToggleClick ${Context(type)} ${Context.Source(type)} ${Context.Args}
      if ${Context.Args[position]}
      {
         ; pressed
         return
      }

      ; released
      switch ${Context.Args[controlName]}
      {
         case Mouse1
            ISBoxer2:ToggleMasterHotkeyToggle
            break
         case Mouse2
            ISBoxer2:ToggleMasterBroadcastingToggle
            break
      }
   }


As with similar snippets earlier, "ISBoxer2:ToggleMasterHotkeyToggle" and "ISBoxer2:ToggleMasterBroadcastingToggle" will in the future be described in the ISBoxer 2 wiki, but I am providing this information here for you now.

That completes the LavishScript controller file.

For reference, here is the completed MRT.lgui2Package.json file:
Code:
{
  "$schema": "http://www.lavishsoft.com/schema/lgui2Package.json",
  "elements": [
    {
      "name": "MRT.toggles",
      "_isboxer2":true,
      "type": "window",
      "content": {
        "type": "imagebox",
        "imageBrush": {
          "color": [1.0,1.0,1.0],
          "imageFile": "Base.png"
        },
        "opacity":0.5,
        "styles": {
          "hotkeysOn":{
            "opacity":1.0
          },
          "hotkeysOff":{
            "opacity":0.5
          },
          "broadcastOn": {
            "imageBrush": {
              "color": [1.0,1.0,1.0],
              "imageFile": "Repeater On.png"
            }
          },
          "broadcastOff": {
            "imageBrush": {
              "color": [1.0,1.0,1.0],
              "imageFile": "Base.png"
            }
          }
        },
        "eventHandlers": {
          "onMouseButtonMove": {
            "type": "method",
            "method": "OnToggleClick",
            "object": "ISB2_MRT"
          }
        },
        "triggers": {
          "broadcast": {
            "condition": "${ISBoxer2.MasterBroadcastingToggleState}",
            "matched": {
              "type": "style",
              "styleName": "broadcastOn"
            },
            "unmatched": {
              "type": "style",
              "styleName": "broadcastOff"
            }
          },
          "hotkeys":{
            "condition": "${ISBoxer2.MasterHotkeyToggleState}",
            "matched": {
              "type": "style",
              "styleName": "hotkeysOn"
            },
            "unmatched": {
              "type": "style",
              "styleName": "hotkeysOff"
            }
          }
        }
      }
    }
  ]
}


And that's it. Give it a whirl:
Image

Right clicking toggles Broadcast and changes images, left clicking toggles Hotkeys and changes opacity. The window element can be adjusted with transparency for "backgroundBrush" and 0 for "borderThickness" to finish its appearance, and the title bar can be toggled on and off at will by ISBoxer 2 (not covered by this guide).
<<

MiRai

User avatar

Vibrant Videographer

Posts: 2901

Joined: Fri Nov 20, 2009 3:30 pm

Post Sun Jun 16, 2019 9:06 pm

Re: Development: Creating a custom Hotkeys/Broadcasting Toggle

lax wrote:and so I am literally writing this guide for MiRai.
This excites me.

lax wrote:(Disclaimer: MiRai did not provide the shoddy MS Paint job)
I am glad this was cleared up.

Return to Alpha Discussion

Who is online

Users browsing this forum: No registered users and 1 guest