• Which the release of FS2020 we see an explosition of activity on the forun and of course we are very happy to see this. But having all questions about FS2020 in one forum becomes a bit messy. So therefore we would like to ask you all to use the following guidelines when posting your questions:

    • Tag FS2020 specific questions with the MSFS2020 tag.
    • Questions about making 3D assets can be posted in the 3D asset design forum. Either post them in the subforum of the modelling tool you use or in the general forum if they are general.
    • Questions about aircraft design can be posted in the Aircraft design forum
    • Questions about airport design can be posted in the FS2020 airport design forum. Once airport development tools have been updated for FS2020 you can post tool speciifc questions in the subforums of those tools as well of course.
    • Questions about terrain design can be posted in the FS2020 terrain design forum.
    • Questions about SimConnect can be posted in the SimConnect forum.

    Any other question that is not specific to an aspect of development or tool can be posted in the General chat forum.

    By following these guidelines we make sure that the forums remain easy to read for everybody and also that the right people can find your post to answer it.

FSX:SE [C++/GDI+] Our arch-nemesis: TRANSPARENCY

Vitus

Resource contributor
Messages
1,480
Country
newzealand
OK, after you talked me into using GDI+, to enable wordwrap for my lengthy strings, I hit another roadblock.

My old nemesis: TRANSPARENCY!

The gauge I am working on is the dialog interface for ground crew and mechanic (bottom left):
R0Yx8Rx.jpg


Little Heinrich there is drawn using the MAKE_ICON macro and it's the text in his speech bubble that needs the word-wrapping.

So bit by bit I moved things over to GDI+ and I solved one problem: the text now wraps nicely and sticks to it's bubble.

Buuuuut on the other hand I lost the transparency around both the bubble and the portrait. The images will just show up on a black background.

For most part, I stick to the famous GDI+ sample project, in terms of structure and calling convention. I load the images in my PANEL_SERVICE_POST_INSTALL routine:
Code:
mBackground = new Gdiplus::Image(fullpath.c_str());    //fullpath is of type std::wstring

In the PANEL_SERVICE_PRE_DRAW I do the following:
Code:
mGraphics->Clear(0,0,0,0);
[...]
mLayoutRect = RectF(0,0,800,256);
mGraphics->DrawImage(mBackground,mLayoutRect);
[...]
SET_OFF_SCREEN(pelement);

I tried loading 8 bit BMPs - those were the ones I used in the old macro-code - with the background color being set to RGB(0,0,0). 16bit BMP wouldn't work either and I also tried loading the image as a PNG with a dedicated Alpha channel. No luck. While the areas that are outside of the mLayoutRect are being clear, as soon as the image gets drawn on the screen, everything behind the image is set to black.





And since you guys talked me into using GDI+, I'm hoping you feel guilty enough to help me out solving this mystery. :laughing::rotfl:

Cheers,
Vitus
 
You should have kept the icon and simply created a static image within the confines of the text bubble to be used for the GDI+ output.
 
Do you mean add the MAKE_ICON macros into the GDI+ gauge? If so, how?
 
The GDI+ gauge requires a MAKE_STATIC to be able to have something to render to. You can add a MAKE_ICON to the list just like any other gauge.
 
In this case I want to draw the speech bubble UNDER the GDI+ text though. And it looks like the sim doesn't like it when MAKE_STATIC is the last element in the list.
 
btw. why is this the better approach, rather than using GDI+ to load and draw the bitmaps?
 
Because the sim can render it's gauge components better than you can with GDI+.
The sim doesn't care if the last element is an static image... not sure why you would think that.

Why is the underlying image an icon?
 
I will add that it is possible to do as I suggest, but it really isn't possible to show you how without seeing your code and your images, etc. All I can offer is that it's possible, it will work well...
 
Check out the screenshot I posted. The elements are:
- portrait of Heinrich or Jack
- speech bubble
- string with the name
- string with the message

I use icon to be able to toggle between the different people talking.

I tried adding the old macro-code to the GDI+ part, but it'd crash the sim when I start it up. I figured that this might be the sim not liking the order.
 
Okey dokey.

C++:
#include "Global.h"
#include "DialogReplySetup.h"
#include "Dialog.h"

DialogReplyGauge::DialogReplyGauge()
    : mGaugeWidth(0)
    , mGaugeHeight(0)
    , mCanvas(0)
    , mBackground(0)
    , mPortraitRampie(0)
    , mPortraitMechanicD0(0)
    , mPortraitMechanicD1(0)
    , mPortraitMechanicD2(0)
    , mPortraitMechanicD3(0)
    , mPelements(0)
    , mMachineFont(0)
    , mHandFont(0)
    , PENWIDTH(3)
    , mcbDest(MAX_PATH * sizeof(WCHAR))
{
    mPaperBrush = new SolidBrush(Color(255, 191, 191, 191));

    mHandBrush = new SolidBrush(Color(210, 90, 60, 150));    //light blue

    mFormBrush = new SolidBrush(Color(210, 70, 67, 58));        //blackish

    mStringFormat.SetAlignment(StringAlignmentCenter);
    mStringFormat.SetLineAlignment(StringAlignmentCenter);
}

DialogReplyGauge::~DialogReplyGauge() {
    if (mPaperBrush)
        delete mPaperBrush;
    if (mHandBrush)
        delete mHandBrush;
    if (mFormBrush)
        delete mFormBrush;
    if (mBackground)
        delete mBackground;
    if (mPortraitRampie)
        delete mPortraitRampie;
    if (mPortraitMechanicD0)
        delete mPortraitMechanicD0;
    if (mPortraitMechanicD1)
        delete mPortraitMechanicD1;
    if (mPortraitMechanicD2)
        delete mPortraitMechanicD2;
    if (mPortraitMechanicD3)
        delete mPortraitMechanicD3;
    if (mMachineFont)
        delete mMachineFont;
    if (mHandFont)
        delete mHandFont;
}

void DialogReplyGauge::DoCallback(GAUGEHDR * gauge, const int serviceID) {
    using namespace Gdiplus;
    std::wstring tmp_wstr;
    int count;

    switch (serviceID) {
    case PANEL_SERVICE_POST_INITIALIZE:

        //load fonts from font folder
        tmp_wstr = path_sim.wstring() + L"\\fonts\\Om Telolet Om.ttf";
        privateFontCollection.AddFontFile(tmp_wstr.c_str());
        tmp_wstr = path_sim.wstring() + L"\\fonts\\veteran typewriter.ttf";
        privateFontCollection.AddFontFile(tmp_wstr.c_str());
        count = privateFontCollection.GetFamilyCount();
        if (count == 0)
            debug_log("Could not load custom fonts.");

        mMachineFont = new Font(L"Veteran Typewriter", 18.0f, FontStyleRegular, UnitPixel, &privateFontCollection);
        mHandFont = new Font(L"Om Telolet Om", 18.0f, FontStyleRegular, UnitPixel, &privateFontCollection);

        break;
    case PANEL_SERVICE_PRE_UPDATE:
        break;

    case PANEL_SERVICE_PRE_DRAW:
        if (mCanvas) {
            PELEMENT_STATIC_IMAGE pelement = (PELEMENT_STATIC_IMAGE)gauge->elements_list[0];

            if (pelement) {
                //mGraphics->Clear(Color(0, 0, 0, 0));

                std::wstring msg_name = L"";
                std::wstring msg = L"";

                mGraphics->SetTextRenderingHint(TextRenderingHintAntiAlias);
                mGraphics->SetSmoothingMode(SmoothingModeAntiAlias);

                ///////////////////////////////////////////////////////
                //    BACKGROUND
                ///////////////////////////////////////////////////////
                /*mLayoutRect = RectF(
                    (REAL)(0.2225 * mGaugeWidth), (REAL)(0.3671875 * mGaugeHeight),
                    (REAL)(0.7775 * mGaugeWidth), (REAL)(0.6328125 * mGaugeHeight));
                mGraphics->DrawImage(mBackground, mLayoutRect);*/

                mGraphics->FillRectangle(mPaperBrush,
                    (REAL)(0.2862500*mGaugeWidth), (REAL)(0.41796875*mGaugeHeight),
                    (REAL)(0.6850000*mGaugeWidth), (REAL)(0.50781250*mGaugeHeight));

                ///////////////////////////////////////////////////////
                //    Text
                ///////////////////////////////////////////////////////

                if (dialogManager.showMessage()) {
                    switch (dialogManager.getCurrentMessage().portrait) {
                    case PORTRAIT_RAMPIE:
                        msg_name = L"Heinrich A., Ramp Agent:";
                        break;
                    case PORTRAIT_MECHANIC:
                    case PORTRAIT_MECHANIC_S1:
                    case PORTRAIT_MECHANIC_S2:
                    case PORTRAIT_MECHANIC_S3:
                        msg_name = L"Jack N., Mechanic:";
                        break;
                    default:
                        msg_name = L"";
                    }
                    msg = s2ws(dialogManager.getCurrentMessage().message);
                }
                else {
                    msg_name = L"";
                    msg = L"";
                }

                mLayoutRect = RectF(
                    (REAL)(0.2962500 * mGaugeWidth), (REAL)(0.4296875 * mGaugeHeight),
                    (REAL)(0.6750000 * mGaugeWidth), (REAL)(0.1250000 * mGaugeHeight));
                StringCbPrintf(mpszDest, mcbDest, msg_name.c_str());
                mGraphics->DrawString(mpszDest, lstrlen(mpszDest), mMachineFont, mLayoutRect, &mStringFormat, mFormBrush);

                mLayoutRect = RectF(
                    (REAL)(0.2962500 * mGaugeWidth), (REAL)(0.5351562 * mGaugeHeight),
                    (REAL)(0.6750000 * mGaugeWidth), (REAL)(0.3906250 * mGaugeHeight));
                StringCbPrintf(mpszDest, mcbDest, msg.c_str());
                mGraphics->DrawString(mpszDest, lstrlen(mpszDest), mHandFont, mLayoutRect, &mStringFormat, mHandBrush);

                SET_OFF_SCREEN(pelement);
            }
        }
        break;

    case PANEL_SERVICE_POST_INSTALL:
        int tempX = 0;
        int tempY = 0;

        mPelements.clear();
        mCanvas = 0;

        PELEMENT_STATIC_IMAGE element = reinterpret_cast<PELEMENT_STATIC_IMAGE>(gauge->elements_list[0]);
        do {
            if (mPelements.size() == 0) {
                tempX = element->image_data.final->dim.x;
                tempY = element->image_data.final->dim.y;

                if (element->hdc) {
                    mStringFormat.SetAlignment(StringAlignmentNear);
                    mCanvas = element;

                    mGraphics.reset(new Graphics(element->hdc));
                    mGraphics->SetTextRenderingHint(TextRenderingHintAntiAlias);
                    mGraphics->SetCompositingMode(CompositingModeSourceOver);
                    mGraphics->SetInterpolationMode(InterpolationModeHighQualityBicubic);

                }
            }
            else {
                element = reinterpret_cast<PELEMENT_STATIC_IMAGE>(element->next_element[0]);
            }

            mPelements.push_back(element);
        } while (element->next_element);

        if (tempX && tempY) {
            mGaugeWidth = tempX;
            mGaugeHeight = tempY;

            mMachineFont = new Font(L"Veteran Typewriter", (REAL)(0.0198895028*mGaugeHeight), FontStyleRegular, UnitPixel, &privateFontCollection);
            mHandFont = new Font(L"Om Telolet Om", (REAL)(0.0198895028*mGaugeHeight), FontStyleRegular, UnitPixel, &privateFontCollection);
        }

        //load the bitmaps
        //path_aircraft_folder.append
        std::wstring tmp_wstr = s2ws(path_aircraft_folder.string());
        tmp_wstr.append(L"\\..\\Wing42 Lockheed Vega shared\\res\\dialog_bubble.bmp");
        mBackground = new Gdiplus::Bitmap(tmp_wstr.c_str());
        //mBackground = new Gdiplus::Image(tmp_wstr.c_str());
        //mBackground = Bitmap::FromResource((HINSTANCE)GetModuleHandle(NULL), MAKEINTRESOURCE(BMP_DIALOG_BG_BUBBLE));

        break;
    }
}


static const GUID gauge_guid_dialog_reply = { 0x7fdf9d98, 0x28ba, 0x417a,{ 0xa8, 0xf8, 0x4d, 0xb9, 0x4c, 0x0e, 0x80, 0xee } };

/////////////////////////////////////////////////////////////////////////////
// Dialog Gauge Declarations
/////////////////////////////////////////////////////////////////////////////

// Main system simulation gauge:
#define     GAUGE_NAME          "dialog_reply"
#define     GAUGEHDR_VAR_NAME   gaugehdr_dialog_reply
#define        GAUGE_H                256
#define     GAUGE_W                800

#define GAUGE_FONT_DEFAULT      "Om Telolet Om"
#define GAUGE_FONT_FORMAL        "Veteran Typewriter"
#define GAUGE_CHARSET           DEFAULT_CHARSET
#define GAUGE_WEIGHT_DEFAULT    FW_NORMAL

//    Set up gauge header
char dialog_reply_gauge_name[] = GAUGE_NAME;
extern PELEMENT_HEADER dialog_reply_element_list;
extern MOUSERECT dialog_reply_mouse_rect[];

/////////////////////////////////////////////////////////////////////////////
// Dialogs Gauge macros
/////////////////////////////////////////////////////////////////////////////

GAUGE_HEADER_FS1000(
    GAUGEHDR_VAR_NAME,
    GAUGE_W,
    dialog_reply_gauge_name,
    &dialog_reply_element_list,
    dialog_reply_mouse_rect,
    (PGAUGE_CALLBACK)DialogReplyGauge::Callback<DialogReplyGauge>,
    0L, 0L,
    gauge_guid_dialog_reply,
    0,
    0,
    0,
    0,
    0
);


FLOAT64 FSAPI reply_portrait_cb(PELEMENT_ICON pelement) {
    int i = 0;
    if (dialogManager.showMessage()) i = (int)dialogManager.getCurrentMessage().portrait;
    return i;
};

MAKE_ICON
(
    dialog_portrait,
    BMP_DIALOG_PORTRAIT_NONE,
    NULL,
    NULL,
    IMAGE_USE_ERASE | IMAGE_ERASE_ALWAYS | IMAGE_USE_BRIGHT | IMAGE_USE_TRANSPARENCY,
    0,
    10, 10,
    MODULE_VAR_NONE, reply_portrait_cb,
    ICON_SWITCH_TYPE_SET_CUR_ICON,
    6,
    0,
    0
);

MAKE_ICON
(
    dialog_bubble,
    BMP_DIALOG_BG_BUBBLE,
    &dialog_portrait.header,
    NULL,
    IMAGE_USE_ERASE | IMAGE_ERASE_ALWAYS | IMAGE_USE_BRIGHT | IMAGE_USE_TRANSPARENCY,
    0,
    178, 94,
    MODULE_VAR_NONE, NULL,
    ICON_SWITCH_TYPE_SET_CUR_ICON,
    6,
    0,
    0
);

MAKE_STATIC
(
    gaugeDialogReply_image,
    BMP_DIALOG_NONE,
    NULL,//&dialog_bubble.header,
    NULL,
    IMAGE_CREATE_DIBSECTION | IMAGE_USE_TRANSPARENCY | IMAGE_USE_ALPHA | IMAGE_USE_ERASE,// | IMAGE_ERASE_ALWAYS,
    0,
    0, 0
)

PELEMENT_HEADER        dialog_reply_element_list = &gaugeDialogReply_image.header;

MOUSE_BEGIN(dialog_reply_mouse_rect, HELP_NONE, 0, 0)
MOUSE_END

#undef GAUGE_NAME
#undef GAUGEHDR_VAR_NAME
#undef GAUGE_H
#undef GAUGE_W
#undef GAUGE_FONT_DEFAULT
#undef GAUGE_FONT_FORMAL
#undef GAUGE_CHARSET           
#undef GAUGE_WEIGHT_DEFAULT
 
I would create a base MAKE_STATIC that is all transparent (0,0,0) except for the speech bubble, it is NOT a GDI+ element. The next element would be a MAKE_ICON for your agent image. The last would be another MAKE_STATIC that is defined as being within the speech bubble area that you want text to appear. IT would be a GDI+ element.
 
I would lay it out like this image: Green is the base MAKE_STATIC, blue is the MAKE_ICON for the agent image, and tan is the area of the MAKE_STATIC for the GDI+ text output.

 
Back
Top