Game Development Community

TX2D 3.0.0.0 GUIBitmapButton resource

by Jason Cahill · 07/14/2009 (11:06 pm) · 3 comments

Description:
This is a new GUI control for a bitmap-based button with text. GUIButton is strictly a rectangle/border with text. As with all GUI controls, there is a Style class and the actual GUIBitmapButton. The Style is meant to be shared across multiple buttons in the same UI. I put the image loading code into the Style, so it is easily shared across a set of buttons.

How to build:
Copy and paste this source code into a new file TorqueCoreGUIGUIBitmapButton.cs. Rebuild your engine.

Things to know:
Basically, you three things: A GUIBitmapButtonStyle, a GUIBitmapButton control, and a bitmap image that conforms to the following rule: the image is assumed to contain 4 equal heighted chunks: normal, selected, pushed, and not active. So, if you have a button image for your normal state that is 200x100, you need to supply an image that is 200x400 with pixels (0,0)-(199,99) for normal, (0,100)-(199,199) for selected, (0,200)-(199,299) for pushed, and (0,300)-(199,399) for not active. You can have an optional border and text with text colors. Here is a sample usage of the control:

<GUIBitmapButtonStyle name="MyBitmapButtonStyle">
      <FontType>Arial22</FontType>
      <HasBorder>true</HasBorder>
      <IsOpaque>true</IsOpaque>
      <Active>false</Active>
      <Focusable>true</Focusable>
      <BorderColors>
        <ColorBase><X>255</X><Y>255</Y><Z>255</Z><W>255</W></ColorBase>
        <ColorHL><X>0</X><Y>255</Y><Z>0</Z><W>255</W></ColorHL>
        <ColorSEL><X>128</X><Y>128</Y><Z>128</Z><W>255</W></ColorSEL>
        <ColorNA><X>0</X><Y>0</Y><Z>255</Z><W>255</W></ColorNA>
      </BorderColors>
      <FillColors>
        <Color><X>0</X><Y>0</Y><Z>255</Z><W>255</W></Color>
      </FillColors>
      <TextColors>
        <ColorBase><X>255</X><Y>255</Y><Z>255</Z><W>255</W></ColorBase>
        <ColorHL><X>254</X><Y>194</Y><Z>43</Z><W>255</W></ColorHL>
        <ColorSEL><X>0</X><Y>0</Y><Z>0</Z><W>255</W></ColorSEL>
        <ColorNA><X>0</X><Y>0</Y><Z>0</Z><W>255</W></ColorNA>
      </TextColors>
      <Alignment>JustifyCenter</Alignment>
      <SizeToText>true</SizeToText>
      <Bitmap>dataimagesbutton</Bitmap>
    </GUIBitmapButtonStyle>

    <GUIBitmapButton name="button1">
      <Style nameRef="MyBitmapButtonStyle"/>
      <ButtonText>Two</ButtonText>
      <Active>true</Active>
      <Position>
        <X>503</X>
        <Y>317</Y>
      </Position>
      <Size>
        <X>277</X>
        <Y>94</Y>
      </Size>
      <HorizSizing>Left</HorizSizing>
      <VertSizing>Top</VertSizing>
    </GUIBitmapButton>

Here's the full source code for the control. I grant free use of this code to anyone for any purpose, indie or commercial. I grant GarageGames full, royalty free use of this source code for inclusion in a future release of Torque X.

using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;

using GarageGames.Torque.Core;
using GarageGames.Torque.GFX;
using GarageGames.Torque.Sim;
using GarageGames.Torque.MathUtil;
using GarageGames.Torque.Platform;
using GarageGames.Torque.Util;
using System.Xml.Serialization;

namespace GarageGames.Torque.GUI
{
    /// <summary>
    /// The style properties for the GUIBitmapButton control.
    /// </summary>
    public class GUIBitmapButtonStyle : GUIStyle
    {
        //======================================================
        #region Public properties, operators, constants, and enums

        /// <summary>
        /// Whether the GUIBitmapButton control should resize itself to the same
        /// dimensions of the specified bitmap.
        /// </summary>
        public bool SizeToBitmap
        {
            get { return _sizeToBitmap; }
            set { _sizeToBitmap = value; }
        }

        /// <summary>
        /// The font resource used when rendering the text. The font resource
        /// contains glyph information and is used by the FontRenderer. The
        /// font resource is created when the FontType property is specified.
        /// </summary>
        public Resource<SpriteFont> Font
        {
            get
            {
                if (_font.IsInvalid)
                    _font = ResourceManager.Instance.LoadFont(_fontType);

                return _font;
            }
        }

        /// <summary>
        /// The font to use for rendering the text. The default engine fonts are
        /// specified in the project TorqueEngineData. 
        /// <example>style.FontType = "Arial16";</example>
        /// </summary>
        public string FontType
        {
            get { return _fontType; }
            set
            {
                _fontType = value;

                if (_fontType != String.Empty)
                    _font = ResourceManager.Instance.LoadFont(_fontType);
            }
        }

        /// <summary>
        /// The color of the text when rendered.
        /// </summary>
        public ColorCollection TextColor
        {
            get { return _textColor; }
        }

        /// <summary>
        /// Defines the horizontal text justification.
        /// </summary>
        public TextAlignment Alignment
        {
            get { return _textAlignment; }
            set { _textAlignment = value; }
        }

        /// <summary>
        /// The image file to render to the screen.
        /// </summary>
        public string Bitmap
        {
            get { return _bitmapName; }
            set
            {
                _bitmapName = value;
                if (_bitmapName != String.Empty)
                {
                    if (_material != null)
                        _material.Dispose();

                    _material = new Materials.SimpleMaterial();
                    _material.TextureFilename = _bitmapName;
                    _material.IsTranslucent = true;
                    _material.IsColorBlended = true;

                    // rdbnote: the render material should really handle this for me
                    Resource<Texture> res = ResourceManager.Instance.LoadTexture(_material.TextureFilename);
                    Texture2D texture = (Texture2D)res.Instance;

                    _bitmapSize = new Vector2(texture.Width, texture.Height);

                    // invalidate the temp. resource
                    res.Invalidate();
                }
                else
                {
                    if (_material != null)
                        _material.Dispose();
                    _material = null;
                }
            }
        }

        public Materials.SimpleMaterial Material
        {
            get { return _material; }
        }

        public Vector2 BitmapSize
        {
            get { return _bitmapSize; }
        }

        #endregion

        //======================================================
        #region Public methods

        public override void OnLoaded()
        {
            base.OnLoaded();

            for (int i = 0; i < _textColorAsVector4.Count; i++)
            {
                TextColor[(CustomColor)i] = new Color(_textColorAsVector4[i] / 255.0f);
            }
        }

        #endregion

        //======================================================
        #region Private, protected, internal fields

        string _fontType;
        Resource<SpriteFont> _font;

        // This is an image made up of four evenly sized chunks: normal, selected, pushed, and not active
        string _bitmapName = String.Empty;
        Materials.SimpleMaterial _material = null;
        Vector2 _bitmapSize;

        ColorCollection _textColor = new ColorCollection();
        TextAlignment _textAlignment = TextAlignment.JustifyLeft;

        bool _sizeToBitmap = true;

        [XmlElement(ElementName = "TextColors")]
        [TorqueXmlDeserializeInclude]
        protected List<Vector4> _textColorAsVector4 = new List<Vector4>();

        #endregion
    }

    /// <summary>
    /// Renders a skinnable button.
    /// </summary>
    public class GUIBitmapButton : GUIControl
    {
        //======================================================
        #region Constructors

        public GUIBitmapButton()
        {
            int gamepadId = InputManager.Instance.FindDevice("gamepad0");
            int keyboardId = InputManager.Instance.FindDevice("keyboard0");
            InputMap.BindCommand(gamepadId, (int)XGamePadDevice.GamePadObjects.A, SetButtonPushed, SetButtonSelected);
            InputMap.BindCommand(keyboardId, (int)Keys.Enter, SetButtonPushed, SetButtonSelected);
        }

        #endregion

        //======================================================
        #region Public properties, operators, constants, and enums


        /// <summary>
        /// Defines the various states of a button.
        /// </summary>
        public enum ButtonState
        {
            Normal = 0, // regular look
            Selected,   // has focus
            Pushed,     // receiving input

            NumStates
        }

        /// <summary>
        /// The string to render on the button.
        /// </summary>
        public string ButtonText
        {
            get { return _buttonText; }
            set { _buttonText = value; }
        }

        /// <summary>
        /// This delegate is called when the button is selectd.
        /// </summary>
        public OnButtonSelected OnSelectedDelegate
        {
            get { return _selectedDelegate; }
            set { _selectedDelegate = value; }
        }

        #endregion

        //======================================================
        #region Public methods

        public override void OnRender(Vector2 offset, RectangleF updateRect)
        {
            Profiler.Instance.StartBlock("GUIBitmapButton.OnRender");

            bool highlight = _buttonState == ButtonState.Selected;
            bool depressed = _buttonState == ButtonState.Pushed;

            // clear bitmap modulation
            DrawUtil.ClearBitmapModulation();

            RectangleF ctrlRect = new RectangleF(offset, _bounds.Extent);
            int imageIndex;
            CustomColor colorIndex;

            if (!Active)
            {
                imageIndex = 3;
                colorIndex = CustomColor.ColorNA;
            }
            else
            {
                if (depressed)
                {
                    imageIndex = 2;
                    colorIndex = CustomColor.ColorSEL;
                }
                else if (highlight)
                {
                    imageIndex = 1;
                    colorIndex = CustomColor.ColorHL;
                }
                else
                {
                    imageIndex = 0;
                    colorIndex = CustomColor.ColorBase;
                }
            }

            if (_style.Material != null)
            {
                RectangleF srcRegion = new RectangleF();
                RectangleF dstRegion = new RectangleF();

                srcRegion.X = 0;
                srcRegion.Y = imageIndex * (_style.BitmapSize.Y / 4);
                srcRegion.Width = _style.BitmapSize.X;
                srcRegion.Height = (_style.BitmapSize.Y / 4);

                dstRegion.X = offset.X;
                dstRegion.Y = offset.Y;
                dstRegion.Width = _bounds.Extent.X;
                dstRegion.Height = _bounds.Extent.Y;

                DrawUtil.BitmapStretchSR(_style.Material, dstRegion, srcRegion, BitmapFlip.None);
            }

            //// Fill in if appropriate
            //if (Style.IsOpaque)
            //    DrawUtil.RectFill(ctrlRect, Style.FillColor[colorIndex]);

            // Draw the border if appopriate
            if (Style.HasBorder)
                DrawUtil.Rect(ctrlRect, Style.BorderColor[colorIndex]);

            // render justified text
            Vector2 textPos = new Vector2(0.0f, 0.0f);

            if (_buttonState == ButtonState.Pushed)
                textPos += new Vector2(1.0f, 1.0f);

            DrawUtil.JustifiedText(_style.Font, textPos, _bounds.Extent, _style.Alignment, _style.TextColor[colorIndex], _buttonText);

            Profiler.Instance.EndBlock("GUIBitmapButton.OnRender");
        }

        public override bool OnInputEvent(ref TorqueInputDevice.InputEventData data)
        {
            // an inactive button is a disabled button
            if (Active)
            {
                if (_inputMap != null)
                {
                    if (_inputMap.ProcessInput(data))
                    {
                        if (data.EventAction == TorqueInputDevice.Action.Make)
                        {
                            _buttonState = ButtonState.Pushed;
                        }
                        else if (data.EventAction == TorqueInputDevice.Action.Break)
                        {
                            if (GUICanvas.Instance.GetFocusControl() == this)
                                _buttonState = ButtonState.Selected;
                            else
                                _buttonState = ButtonState.Normal;
                        }

                        return true;
                    }
                }

                // if the button is currently pushed, eat input until it is released
                if (_buttonState == ButtonState.Pushed)
                    return false;
            }

            // if we got here, this control didn't handle the input,
            //  pass it up to the parent if we have one
            return (Parent != null ? Parent.OnInputEvent(ref data) : false);
        }

        public override void OnGainFocus(GUIControl oldFocusCtrl)
        {
            base.OnGainFocus(oldFocusCtrl);

            // only change state if the button is in normal mode
            if (_buttonState == ButtonState.Normal)
                _buttonState = ButtonState.Selected;
        }

        public override void OnLoseFocus(GUIControl newFocusCtrl)
        {
            base.OnLoseFocus(newFocusCtrl);

            // only change state if the button is selected
            if (_buttonState == ButtonState.Selected)
                _buttonState = ButtonState.Normal;
        }

        public void SetButtonPushed()
        {
            _buttonState = ButtonState.Pushed;
        }

        public void SetButtonSelected()
        {
            _buttonState = ButtonState.Selected;

            if (_selectedDelegate != null)
                _selectedDelegate();
        }

        public override void CopyTo(TorqueObject obj)
        {
            base.CopyTo(obj);

            GUIBitmapButton obj2 = (GUIBitmapButton)obj;
            obj2.ButtonText = ButtonText;
            obj2.OnSelectedDelegate = OnSelectedDelegate;
        }

        #endregion

        //======================================================
        #region Private, protected, internal methods

        protected override bool _OnNewStyle(GUIStyle style)
        {
            _style = (style as GUIBitmapButtonStyle);

            Assert.Fatal(_style != null, "GUIBitmapButton._OnNewStyle - control was assigned an invalid style!");

            if (_style == null || !base._OnNewStyle(style))
                return false;

            return true;
        }

        #endregion

        //======================================================
        #region Private, protected, internal fields

        string _buttonText = String.Empty;

        ButtonState _buttonState = ButtonState.Normal;
        GUIBitmapButtonStyle _style = null;
        OnButtonSelected _selectedDelegate;

        #endregion
    }
}

Also in TorqueCore\Core\Xml\TorqueSceneData.cs, add the following lines beneath GUISplashStyle (line 297):
_defaultTypeMap.Add("GUIBitmapButton", typeof(Torque.GUI.GUIBitmapButton));
            _defaultTypeMap.Add("GUIBitmapButtonStyle", typeof(Torque.GUI.GUIBitmapButtonStyle));

#1
07/17/2009 (10:59 am)
Nice! Much needed info! Added to Community Links.

Brian
#2
08/05/2009 (11:25 am)
Quote:Also in TorqueCore\Core\Xml\TorqueSceneData.cs, add the following lines beneath GUISplashStyle (line 297):
_defaultTypeMap.Add("GUIBitmapButton", typeof(Torque.GUI.GUIBitmapButton));  
             _defaultTypeMap.Add("GUIBitmapButtonStyle", typeof(Torque.GUI.GUIBitmapButtonStyle));

Can you explain how to work this if you don't have the source code?
#3
03/14/2010 (7:48 am)
Yeah, it seems that if you don't, you are just at GarageGames' mercy until they decide to add it in :/