Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
  • 10 commits
  • 4 files changed
  • 0 commit comments
  • 1 contributor
Commits on Dec 13, 2012
@fabd fabd Added Scaleform Extension method signature for Mouse.getPosition() (C…
…LIK/Mouse.as).
09fe386
@fabd fabd Added __* rule to exclude temporary files. baadb10
@fabd fabd Adds a simple debugging method that logs messages to top of screen.
*  GlobalFunc is a singleton, creates a mini console at top of screen.
*  GlobalFunc.getInstance().Deebug(msg) prints msg to debug pane.
*  Scrolls after 4 lines or so.
8097a31
@fabd fabd Fixes width of the Deebug() console (Shared.GlobalFunc). 67177f8
@fabd fabd Working Better MessageBoxControls first release.
*  Enabled the XBox Controller style controls, which lets you select the
   option with the WASD controls or the cursor keys. It lets you pick the
   selected option with the activate key (E), and the Return key.
   This means for message boxes with just a "Ok" button, you can simply
   press E or Enter to dismiss them, since the first button is already
   focused.
*  Fixed width of clickable area for the buttons to adapt with the text size.
*  Fixed vertical clickable area to extend also under the text and not just above.
*  The TAB key cycles through "Exit" type buttons, where the label matches
   exactly "Return", "Exit", "Cancel", "Back" or "No".
*  Added a subtle highlight to the focused button @done

There are no changes to the FLA required in this version, compared
to the decompiled sources from Mardoxx.
9b01358
@fabd fabd Misc clean up, removed some test code and abandoned hotkey feature. edf3472
@fabd fabd Fixes setFocus() not always working on the first button (v1.1).
*  setFocus() doesn't seem to work at all in some instances, such as
   Convenient Horses > changing Multitap Delay, or Shooting Stars
   Delay options. Another reported instance is Disenchant confirmation
   message with the single "Ok" button. In those cases, the setFocus()
   call appears to run and setupButtons() function ends properly,
   but the focus event is never called.
*   The setFocus() is wonky on vanilla Skyrim as well. In the same
   scenarios, vanilla Skyrim would display the SelectionIndicator when
   using the controller, however pressing Right, would leave it on
   the first button, thus showing two SelectionIndicators.
*   Couldn't figure out why setFocus() does not trigger. For the time
   being a working solution is to use setInterval(), this lets setupButtons()
   return, Scaleform does some of its trickery, and then the timed
   interval calls setFocus(). 25ms didn't work for the Disenchant
   message, 50ms works.
bb922f6
Commits on Dec 15, 2012
@fabd fabd Added "Done" to the button labels recognized by TAB feature.
*  "Done" is used in "Forgotten Mastery" mod.
7940491
@fabd fabd Added custom menu loader code for compatibility with upcoming mods.
*  New method parses the message box message for special command
   which tells MessageBox to hide itself and load a custom SWF on top
   of it, by using Flash's loadMovie() functionality.
b752a80
@fabd fabd If SKSE enabled, the Escape key focuses AND selects the first exit bu…
…tton.

*  If SKSE is enabled, the Escape key will focus the first exit button starting
   from the left, and select it. Thus Escape allows to quickly go to the parent
   set of options, or to close the dialog. Recognizes the same set of "exit"
   buttons as the Tab key (Return, Exit, Back, Cancel, Done and No).
*  Fix a minor bug with lastTabIndex using the non-sequential id
   found in the button name., instead of the index in the MessageButtons
   array. This didn't cause an error because it was clamped (modulo).
51bf75d
View
6 .gitignore
@@ -1,4 +1,8 @@
/src/.flashProjectProperties
/src/common/skse.as
-/src/common/skyui
+/src/common/skyui
+
+# wip, random tests or notes that I don't want to version control
+__*
+
View
14 src/CLIK/Mouse.as
@@ -3,6 +3,10 @@
// Mouse object
//****************************************************************************
+// See:
+// Scaleform AS2 Extensions Reference.pdf
+// http://gameware.autodesk.com/documents/gfx_4.0_as2_extensions.pdf
+
intrinsic class Mouse
{
static function addListener(listener:Object):Void;
@@ -13,4 +17,12 @@ intrinsic class Mouse
// scaleform extensions
// static function getTopMostEntity(obj1:Object,obj2:Number,obj3:Boolean):Object;
static function getTopMostEntity():Object;
-}
+
+ /**
+ * This method returns coordinates of the corresponding mouse cursor, in _root
+ * coordinate space. The returned value is an instance of flash.geom.Point.
+ *
+ * Scaleform version: 2.2
+ */
+ static function getPosition(mouseIndex:Number):flash.geom.Point;
+}
View
72 src/common/Shared/GlobalFunc.as
@@ -3,10 +3,80 @@ class Shared.GlobalFunc
static var RegisteredTextFields: Object = new Object();
static var RegisteredMovieClips: Object = new Object();
- function GlobalFunc()
+ private var debugWindow:MovieClip = null;
+ private var debugBg:MovieClip = null;
+ private var debugTxt:TextField = null;
+
+ private var DEBUGLOG_HEIGHT:Number = 200;
+
+ private static var inst:GlobalFunc = null;
+
+ public function GlobalFunc()
+ {
+ if (debugWindow != null) {
+ throw new Error("Woopsie doo.");
+ }
+ CreateDebugLog();
+ }
+
+ /**
+ * Dynamically create a mini console for debugging.
+ *
+ */
+ public static function getInstance(): GlobalFunc
+ {
+ if (inst == null) {
+ inst = new GlobalFunc();
+ }
+ return inst;
+ }
+
+ private function CreateDebugLog(): Void
{
+ debugWindow = _root.createEmptyMovieClip("debugWindow", _root.getNextHighestDepth());
+ debugBg = debugWindow.createEmptyMovieClip("debugBg", debugWindow.getNextHighestDepth());
+
+ debugBg.beginFill(0x000000);
+ debugBg.moveTo(0, 0);
+ debugBg.lineTo(Stage.width, 0);
+ debugBg.lineTo(Stage.width, DEBUGLOG_HEIGHT);
+ debugBg.lineTo(0, DEBUGLOG_HEIGHT);
+ debugBg.lineTo(0, 0);
+ debugBg.endFill();
+ debugBg._alpha = 50;
+
+ debugTxt = debugWindow.createTextField("debugTxt", debugWindow.getNextHighestDepth(), 10, 10, Stage.width - 20, DEBUGLOG_HEIGHT-20);
+ debugTxt.embedFonts = true;
+ debugTxt.multiline = true;
+ debugTxt.wordWrap = false;
+
+ debugWindow._x = 0;
+ debugWindow._y = 0;
+
+ var format:TextFormat = new TextFormat("$ConsoleFont", 14, 0xCCCCCC);
+ //format.color = 0xFFFF00;
+ debugTxt.setNewTextFormat(format);
}
+ // Log debug text
+ public function Deebug(aText: String, ret:Boolean): Void
+ {
+ if (debugTxt.text == "") {
+ ret = false;
+ }
+
+ // newline by default
+ if (ret !== false) {
+ ret = true;
+ }
+
+ debugTxt.text += (ret ? "\r" : "") + aText;
+
+ if (debugTxt.textHeight > debugTxt._height) {
+ debugTxt.scroll = debugTxt.textHeight - debugTxt._height;
+ }
+ }
+
static function Lerp(aTargetMin: Number, aTargetMax: Number, aSourceMin: Number, aSourceMax: Number, aSource: Number, abClamp: Boolean): Number
{
var normVal: Number = aTargetMin + (aSource - aSourceMin) / (aSourceMax - aSourceMin) * (aTargetMax - aTargetMin);
View
227 src/messagebox/MessageBox.as
@@ -1,5 +1,9 @@
import gfx.io.GameDelegate;
import gfx.controls.Button;
+import gfx.ui.NavigationCode;
+import gfx.ui.InputDetails;
+import Shared.GlobalFunc;
+import skse;
class MessageBox extends MovieClip
{
@@ -7,7 +11,15 @@ class MessageBox extends MovieClip
static var HEIGHT_MARGIN: Number = 30;
static var MESSAGE_TO_BUTTON_SPACER: Number = 10;
static var SELECTION_INDICATOR_WIDTH: Number = 25;
-
+
+ //fabd: subtle button text highlight with mouse and keyboard focus
+ static var SELECTION_ROLLOVER_ALPHA: Number = 100;
+ static var SELECTION_ROLLOUT_ALPHA: Number = 80;
+
+ //DirectX Scan Codes returned by SKSE
+ static var SKSE_KEY_ESC: Number = 0x01;
+ static var SKSE_KEY_TAB: Number = 0x0F;
+
/* Stage Elements */
var MessageText: TextField;
@@ -20,8 +32,20 @@ class MessageBox extends MovieClip
var Message: TextField;
var MessageButtons: Array;
-
-
+
+ // expired6978's menu loader
+ var proxyMenu: MovieClip;
+
+ // Better MessageBox Controls
+ var MessageBtnLabels: Array;
+ var lastTabIndex: Number = -1;
+ var setFocusIntervalId: Number = null;
+ var exitLabels: Array = [
+ // Button labels recognized by the TAB feature
+ 'Return', 'Back', 'Exit', 'Cancel', 'No',
+ 'Done' // used by "Forgotten Mastery"
+ ];
+
function MessageBox()
{
super();
@@ -34,19 +58,26 @@ class MessageBox extends MovieClip
GameDelegate.addCallBack("setMessageText", this, "SetMessage");
GameDelegate.addCallBack("setButtons", this, "setupButtons");
}
-
+
function setupButtons(): Void
{
if (undefined != ButtonContainer) {
ButtonContainer.removeMovieClip();
ButtonContainer = undefined;
}
- MessageButtons.length = 0; // This truncates the array to 0
- var controllerOrConsole: Boolean = arguments[0];
-
+
+ MessageButtons = [];
+
+ //fabd: no longer used, see below
+ //var controllerOrConsole: Boolean = arguments[0];
+
+ //GlobalFunc.getInstance().Deebug("setupButtons() " + arguments.slice(1));
+
if (arguments.length > 1) {
ButtonContainer = createEmptyMovieClip("Buttons", getNextHighestDepth());
var buttonXOffset: Number = 0;
+
+ MessageBtnLabels = [];
for (var i: Number = 1; i < arguments.length; i++) {
if (arguments[i] == " ")
@@ -58,28 +89,65 @@ class MessageBox extends MovieClip
buttonText.verticalAlign = "center";
buttonText.verticalAutoSize = "center";
buttonText.html = true;
+
+ //fabd: a wee bit darker to emphasize the highlight
+ buttonText._alpha = MessageBox.SELECTION_ROLLOUT_ALPHA;
+
buttonText.SetText(arguments[i], true);
+
+ //fabd: resize the HitArea MC so that it fits the button text.
+ // The hit area width includes the selection indicator (helps with short labels like 'Ok').
+ button.HitArea._width = buttonText._width + MessageBox.SELECTION_INDICATOR_WIDTH;
+ button.HitArea._height = buttonText._height + /*MessageBox.MESSAGE_TO_BUTTON_SPACER*/ 9 * 2; // 9 most closely matches vanilla
+ button.HitArea._x = buttonText._x - MessageBox.SELECTION_INDICATOR_WIDTH / 2;
+ button.HitArea._y = buttonText._y - /*MessageBox.MESSAGE_TO_BUTTON_SPACER*/ 9;
+
button.SelectionIndicatorHolder.SelectionIndicator._width = buttonText._width + MessageBox.SELECTION_INDICATOR_WIDTH;
button.SelectionIndicatorHolder.SelectionIndicator._y = buttonText._y + buttonText._height / 2;
+
button._x = buttonXOffset + button._width / 2;
buttonXOffset = buttonXOffset + (button._width + MessageBox.SELECTION_INDICATOR_WIDTH);
+
MessageButtons.push(button);
+ MessageBtnLabels.push(arguments[i]);
}
InitButtons();
ResetDimensions();
-
- if (controllerOrConsole) {
- Selection.setFocus(MessageButtons[0]);
- }
+
+ //fabd: always enable the gamepad style navigation
+ //if (controllerOrConsole) {
+ // Selection.setFocus(MessageButtons[0]);
+ //}
+
+ // reset tab index for TAB feature
+ lastTabIndex = -1;
+
+ //fabd: temporary solution until I figure why setFocus() doesn't always work
+ setFocusIntervalId = setInterval(this, "focusItDammitWhatsWrongWithYou", 50);
}
}
+ //fabd: note for this workaround fix:
+ // 10ms NO! doesn't work
+ // 25ms NO! works in most cases but not at Enchanting Table "Ok" confirmation
+ // 50ms OK! in all reported cases.
+ function focusItDammitWhatsWrongWithYou(): Void
+ {
+ if (setFocusIntervalId !== null) {
+ clearInterval(setFocusIntervalId);
+ setFocusIntervalId = null;
+ }
+ Selection.setFocus(MessageButtons[0]);
+ }
+
function InitButtons(): Void
{
for (var i: Number = 0; i < MessageButtons.length; i++) {
MessageButtons[i].handlePress = function () {};
- MessageButtons[i].addEventListener("press", ClickCallback);
- MessageButtons[i].addEventListener("focusIn", FocusCallback);
+ MessageButtons[i].addEventListener("press", this, "ClickCallback");
+ MessageButtons[i].addEventListener("focusIn", this, "FocusCallback");
+ MessageButtons[i].addEventListener("rollOver", this, "RollOverCallback");
+ MessageButtons[i].addEventListener("rollOut", this, "RollOverCallback");
MessageButtons[i].ButtonText.noTranslate = true;
}
}
@@ -96,6 +164,39 @@ class MessageBox extends MovieClip
Message.SetText(aText);
}
ResetDimensions();
+
+ // check for custom menu (expired6978)
+ ProcessMessage(aText);
+ }
+
+ /**
+ * Menu loader code by expired6978, added for compatibility with upcoming mods.
+ * Example:
+ * Debug.MessageBox("$$loadMovie=bla.swf$$")
+ */
+ function ProcessMessage(aText)
+ {
+ if(aText.slice(0, 2) == "$$" && aText.slice(aText.length-2, aText.length) == "$$")
+ {
+ var command: String = aText.slice(2, aText.length-2);
+ var key = command.slice(0, command.indexOf("="));
+ if (key == undefined)
+ return;
+
+ var val = command.slice(command.indexOf("=") + 1);
+ if (val == undefined)
+ return;
+
+ if(key.toLowerCase() == "loadmovie")
+ {
+ var MessageContainer: MovieClip = _root.MessageMenu;
+ MessageContainer._visible = false;
+ MessageContainer.enabled = false;
+
+ proxyMenu = _root.createEmptyMovieClip(val, _root.getNextHighestDepth());
+ proxyMenu.loadMovie(val + ".swf");
+ }
+ }
}
function ResetDimensions(): Void
@@ -134,36 +235,126 @@ class MessageBox extends MovieClip
Divider._y = ButtonContainer._y - ButtonContainer._height / 2 - MessageBox.MESSAGE_TO_BUTTON_SPACER / 2;
}
+ // Returns the digit from the button name
+ function getButtonId(button: Button): Number
+ {
+ return Number(button._name.substr(-1))
+ }
+
function ClickCallback(aEvent: Object): Void
{
- GameDelegate.call("buttonPress", [Number(aEvent.target._name.substr(-1))]);
+ GameDelegate.call("buttonPress", [getButtonId(aEvent.target)]);
}
function FocusCallback(aEvent: Object): Void
{
GameDelegate.call("PlaySound", ["UIMenuFocus"]);
+
+ //GlobalFunc.getInstance().Deebug("FocusCallback() " + (aEvent.target ? aEvent.target._name : 'woops'));
+
+ //fabd: move the highlight with the keyboard focus
+ for (var i:Number = 0; i < MessageButtons.length; i++) {
+ var isFocused:Boolean = MessageButtons[i] === aEvent.target;
+ MessageButtons[i].ButtonText._alpha = isFocused ? MessageBox.SELECTION_ROLLOVER_ALPHA : MessageBox.SELECTION_ROLLOUT_ALPHA;
+
+ if (MessageButtons[i] === aEvent.target) {
+ // cycle from here if pressing TAB
+ lastTabIndex = i;
+ }
+ }
+ }
+
+ //fabd: adds subtle highlight, and mouseover sets focus to avoid seeing two SelectionIndicator at once
+ function RollOverCallback(aEvent: Object): Void
+ {
+ //GlobalFunc.getInstance().Deebug("RollOverCallback() type = " + aEvent.type + " thisname " + this._name);
+ var b:Button = Button(aEvent.target);
+ b.ButtonText._alpha = aEvent.type == "rollOver" ? MessageBox.SELECTION_ROLLOVER_ALPHA : MessageBox.SELECTION_ROLLOUT_ALPHA;
+
+ if (aEvent.type === "rollOver") {
+ Selection.setFocus(b);
+ }
}
function onKeyDown(): Void
{
- if (Key.getCode() == 89 && MessageButtons[0].ButtonText.text == "Yes") {
+ var iKeyCode: Number = Key.getCode();
+
+ //GlobalFunc.getInstance().Deebug("Pressed key code " + iKeyCode + " Ascii " + Key.getAscii() + " char " + String.fromCharCode(iKeyCode));
+
+ if (iKeyCode == 89 && MessageButtons[0].ButtonText.text == "Yes") {
GameDelegate.call("buttonPress", [0]);
return;
}
- if (Key.getCode() == 78 && MessageButtons[1].ButtonText.text == "No") {
+ if (iKeyCode == 78 && MessageButtons[1].ButtonText.text == "No") {
GameDelegate.call("buttonPress", [1]);
return;
}
- if (Key.getCode() == 65 && MessageButtons[2].ButtonText.text == "Yes to All") {
+ if (iKeyCode == 65 && MessageButtons[2].ButtonText.text == "Yes to All") {
GameDelegate.call("buttonPress", [2]);
}
}
+ //fabd: cycle to the next "Exit" like button if present
+ function findNextExitButton(iStartFrom: Number): Number
+ {
+ var b: Number, i:Number, j: Number;
+
+ if (iStartFrom === undefined) {
+ iStartFrom = -1;
+ }
+
+ for (i = 1; i <= MessageButtons.length; i++) {
+ // cycle between buttons, so wraparound
+ b = (iStartFrom + i) % MessageButtons.length;
+
+ if (b === iStartFrom)
+ continue;
+
+ for (j = 0; j < exitLabels.length; j++) {
+ if (exitLabels[j] === MessageBtnLabels[b]) {
+ Selection.setFocus(MessageButtons[b]);
+ return b;
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ function handleInput(details: InputDetails, pathToFocus: Array): Boolean
+ {
+ //GlobalFunc.getInstance().Deebug("handleInput() for " + details.code + " v " + details.value + " idx " + details.controllerIdx);
+
+ if (GlobalFunc.IsKeyPressed(details)) {
+
+ var skseKeyDown: Number = skse ? skse.GetLastKeycode(true) : 0;
+ //GlobalFunc.getInstance().Deebug("handleInput() SKSE Key: " + skseKeyDown);
+
+ // the ESC key finds and selects the first exit button (needs SKSE to distinguish Tab from Escape)
+ if (skseKeyDown === MessageBox.SKSE_KEY_ESC) {
+ lastTabIndex = findNextExitButton();
+ if (lastTabIndex !== -1) {
+ var btnId: Number = getButtonId(MessageButtons[lastTabIndex]);
+ GameDelegate.call("buttonPress", [btnId]);
+ return true;
+ }
+ }
+
+ // the TAB key cycles through exit buttons, eg. between "Return" and "Exit"
+ if (details.navEquivalent == NavigationCode.TAB) {
+ findNextExitButton(lastTabIndex);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
function SetPlatform(aiPlatform: Number, abPS3Switch: Boolean): Void
{
if (aiPlatform != 0 && MessageButtons.length > 0) {
Selection.setFocus(MessageButtons[0]);
}
}
-
}

No commit comments for this range

Something went wrong with that request. Please try again.