CRM64Pro GDK v0.80.0
A free cross-platform game development kit built on top of SDL 3.0
Loading...
Searching...
No Matches
Tutorial 12: GUI

Overview

This tutorial reuses the background and cursor stored in Tutorial.cdc, then creates a modeless control panel with labels, text input, buttons, a checkbox, a slider and a progress display.

A second modal panel demonstrates how modal focus temporarily blocks interaction with the modeless panel. Widget IDs and semantic events keep the event loop independent from most widget pointers.

Final result

CRM64Pro Tutorial 12 GUI

Runtime controls

  • Mouse: Interact with panels and widgets.
  • Enter: Commit text while the input widget has focus.
  • ESC: Release text input focus.
  • D: Show or hide the debug window.
  • Grave key: Show or hide the built-in console.
  • Q: Exit the tutorial.

Prerequisites

  • CRM64Pro GDK installed and configured with a supported C++17 compiler.
  • Tutorial package downloaded and fully extracted, preserving its folder structure.
  • Run Tutorials 02 and 04 first so Tutorial.cdc contains the background and cursor resources.
  • Write access to the platform output directory for the log.

What you will learn

  • Create modeless and modal panels with GUIMgr.
  • Create and configure common typed widgets.
  • Assign separate content and title fonts.
  • Route semantic GUI events by widget ID.
  • Mirror slider values into a progress widget.
  • Use application panels beside the debug window and console.

Step by step

1. Give every interactive widget an ID

Semantic GUI events identify their source by widget ID, so one compact enumeration describes the panel controls.

enum eTutorialWidgetID
{
WID_LABEL_INTRO = 1,
WID_LABEL_INPUT,
WID_LABEL_STATUS,
WID_TEXT_INPUT,
WID_BUTTON_APPLY,
WID_BUTTON_MODAL,
WID_CHECK_NOTIFICATIONS,
WID_LABEL_SLIDER,
WID_SLIDER_VALUE,
WID_PROGRESS_VALUE,
WID_LABEL_MODAL,
WID_BUTTON_CONFIRM,
WID_BUTTON_CLOSE
};

2. Create a modeless panel

Create the panel through GUIMgr, validate it, then configure its base widget and title.

// A modeless panel remains interactive alongside other modeless panels.
mLog.msg(LL_INFO, " Create modeless panel ... ");
rState.idMainPanel = mC64.guiMgr().create("TutorialGUIMain");
Panel* pPanel = mC64.guiMgr().getPanel(rState.idMainPanel);
if(rState.idMainPanel < 0 || pPanel == nullptr)
{
logTaskFailed(mLog, rState.idMainPanel);
return false;
}
pPanel->baseWidget().setSize(620, 400);
pPanel->baseWidget().setPosition(PH_CENTER, PH_CENTER);
// The panel base widget font is used by the title, independently from its children.
pPanel->baseWidget().setFont(rState.idTitleFont);
pPanel->baseWidget().setFeatures(WF_DRAGDROP | WF_FADE, true);
pPanel->setTitle("Tutorial 12: GUI", PH_CENTER, Position(PH_TOP, 12));
pPanel->setPanelType(PT_MODELESS);

3. Create typed widgets

The panel owns the returned widgets. Validate the complete set once before configuring it.

// Typed create helpers return widgets owned by the panel. Widget IDs are
// later used to route semantic events without keeping every pointer.
WidgetLabel* pLabel = pPanel->createLabel("lblIntro", WID_LABEL_INTRO);
WidgetLabel* pInputLabel = pPanel->createLabel("lblInput", WID_LABEL_INPUT);
WidgetLabel* pStatus = pPanel->createLabel("lblStatus", WID_LABEL_STATUS);
WidgetTextEdit* pInput = pPanel->createTextEdit("txtInput", WID_TEXT_INPUT);
WidgetButton* pApply = pPanel->createButton("btnApply", WID_BUTTON_APPLY);
WidgetButton* pModal = pPanel->createButton("btnModal", WID_BUTTON_MODAL);
WidgetCheckBox* pNotifications = pPanel->createCheckBox("chkNotifications", WID_CHECK_NOTIFICATIONS);
WidgetLabel* pSliderLabel = pPanel->createLabel("lblSlider", WID_LABEL_SLIDER);
WidgetSlider* pSlider = pPanel->createSlider("sldValue", WID_SLIDER_VALUE, SA_HORIZONTAL);
WidgetProgress* pProgress = pPanel->createProgress("prgValue", WID_PROGRESS_VALUE);
if(!pLabel || !pInputLabel || !pStatus || !pInput || !pApply || !pModal ||
!pNotifications || !pSliderLabel || !pSlider || !pProgress)
{
logTaskFailed(mLog);
return false;
}

4. Configure text input

Set the font, size, initial text, alignment and focus behavior directly on the text-edit widget.

pInput->setFont(rState.idTitleFont);
pInput->setSize(420, 26);
pInput->setText("Hello from CRM64Pro GUI");
pInput->setTextAlign(WTA_CENTER);
pInput->setPosition(Position(PH_LEFT, 100), Position(PH_TOP, 103));
// Enter commits the text. ESC or pointer focus loss releases input focus.
pInput->setFeatures(WF_LOSTFOCUS, true);

5. Pair a slider with progress

Give both widgets the same range. The slider accepts input while the progress widget displays the current value.

// Slider and progress use the same range. The slider is interactive; the
// progress widget is updated from EC_WIDGET_VALUECHANGED events.
pSlider->setSize(240, 22);
pSlider->setRange(0, 100);
pSlider->setValue(rState.iSliderValue);
pSlider->setPosition(Position(PH_LEFT, 260), Position(PH_TOP, 260));
pProgress->setSize(240, 20);
pProgress->setRange(0, 100);
pProgress->setValue(rState.iSliderValue);
pProgress->setPosition(Position(PH_LEFT, 260), Position(PH_TOP, 304));

6. Add a modal panel

A modal panel uses the same widget API, but blocks modeless panels while shown. Keep it hidden until requested.

pPanel->baseWidget().setSize(300, 150);
pPanel->baseWidget().setPosition(PH_CENTER, PH_CENTER);
pPanel->baseWidget().setFont(rState.idTitleFont);
pPanel->setTitle("Modal panel", PH_CENTER, Position(PH_TOP, 12));
pPanel->setPanelType(PT_MODAL);
WidgetLabel* pLabel = pPanel->createLabel("lblModal", WID_LABEL_MODAL);
WidgetButton* pConfirm = pPanel->createButton("btnConfirm", WID_BUTTON_CONFIRM);
WidgetButton* pClose = pPanel->createButton("btnClose", WID_BUTTON_CLOSE);
if(!pLabel || !pConfirm || !pClose)
{
logTaskFailed(mLog);
return false;
}

7. Read semantic GUI events

Main::update() returns CRM64Pro widget events through ET_C64. The payload carries the widget ID and, when applicable, its new value.

else if(event.type == ET_C64 &&
(event.user.code == EC_WIDGET_ACTION || event.user.code == EC_WIDGET_VALUECOMMITTED ||
event.user.code == EC_WIDGET_TOGGLED || event.user.code == EC_WIDGET_VALUECHANGED))
{
// Semantic widget events store the widget ID in data1 and the
// new checkbox or slider value in data2 when applicable.
const Sint32 iWidgetID = static_cast<Sint32>(reinterpret_cast<intptr_t>(event.user.data1));
const Sint32 iWidgetValue = static_cast<Sint32>(reinterpret_cast<intptr_t>(event.user.data2));
state.iWidgetActions++;

8. Open the modal panel

Show the modal panel and move panel focus to it when its button event arrives.

else if(iWidgetID == WID_BUTTON_MODAL)
{
// Showing a modal panel and assigning focus blocks the modeless panel.
if(pModalPanel)
{
pModalPanel->baseWidget().show();
mC64.guiMgr().setPanelFocus(state.idModalPanel);
}
setStatus(pMainPanel, pConsole, "Modal panel opened");
}

9. Mirror live values

Use the slider event value to update both tutorial state and the read-only progress widget.

else if(iWidgetID == WID_SLIDER_VALUE)
{
// Mirror the live slider value into the read-only progress display.
state.iSliderValue = iWidgetValue;
WidgetProgress* pProgress = pMainPanel ? pMainPanel->getProgress(WID_PROGRESS_VALUE) : nullptr;
if(pProgress) pProgress->setValue(state.iSliderValue);
char szStatus[64];
snprintf(szStatus, sizeof(szStatus), "Progress value: %d", state.iSliderValue);
setStatus(pMainPanel, pConsole, szStatus);
}

10. Finish the loop and release panels

Exit before another state update after quitting. Closing a panel also releases the widgets it owns.

if(!bRunning) break;
state.fMouseX = mC64.cursorMgr().getX();
state.fMouseY = mC64.cursorMgr().getY();
state.iCurrentRenderRate = static_cast<Sint32>(mC64.timer().getCurrentRFR());
state.iCurrentLogicRate = static_cast<Sint32>(mC64.timer().getCurrentLFR());
updateLogic(state);
// Closing a panel also releases the widgets created inside that panel.
mLog.msg(LL_INFO, " Close GUI windows ... ");
mC64.guiMgr().close(state.idDebugWindow);
mC64.guiMgr().close(state.idModalPanel);
mC64.guiMgr().close(state.idMainPanel);
logTaskOk(mLog);

Complete source

The tutorial source is authoritative. The following listing is included directly from validation/source/Tutorial_12_GUI.cpp.

/* ----------------------------------------------------------------------
CRM64Pro GDK - Roberto Prieto
Copyright (C) 2013-2026 MegaStorm Systems
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
------------------------------------------------------------------------
Tutorial 12: GUI
------------------------------------------------------------------------
Overview:
This tutorial reuses the background and cursor added to Tutorial.cdc
by earlier tutorials, then creates a small GUI with panels, labels,
buttons, text input, a checkbox, a slider and progress.
Key concepts:
- Create modeless and modal panels with GUIMgr.
- Add labels, buttons, text edit, checkbox, slider and progress widgets.
- Assign normal text and larger title fonts to widgets.
- Update a progress widget from slider value events.
- Handle GUI widget events from Main::update().
- Keep the debug window and console available beside application panels.
Controls:
- Mouse: Interact with panels and widgets.
- Enter: Commit text while the input widget has focus.
- ESC: Release text input focus.
- D: Toggle debug window.
- Grave: Toggle console.
- Q: Quit.
Loop order:
- Process input and GUI widget events.
- Run one fixed logic update.
- Let Main::update() call the screen render callback.
- Let GUIMgr render panels after the screen callback.
------------------------------------------------------------------------ */
#include "CRM64Pro.h"
#include "RunParameter.h"
using namespace CRM64Pro;
#ifdef CRM64PRO_PLATFORM_WINDOWS
#define OUTPUTDIR "Win64/"
#elif defined(CRM64PRO_PLATFORM_LINUX)
#define OUTPUTDIR "Linux/"
#elif defined(CRM64PRO_PLATFORM_MACOS)
#define OUTPUTDIR "macOS/"
#endif
#define RESOURCE_BG_CDC_NAME "Tutorial_bg"
#define RESOURCE_CURSOR_CDC_NAME "Tutorial_cursor"
#define OUTPUT_CDC OUTPUTDIR"Tutorial.cdc"
// Fixed-rate timing.
// Render rate 0 lets the screen render as often as possible. Logic rate controls counters.
static const Sint32 iLogicRate = 20;
static const Sint32 iRenderRate = 0;
// Window size.
static const Sint32 iScreenW = 960;
static const Sint32 iScreenH = 540;
// Widget IDs identify the source of semantic GUI events returned by Main::update().
enum eTutorialWidgetID
{
WID_LABEL_INTRO = 1,
WID_LABEL_INPUT,
WID_LABEL_STATUS,
WID_TEXT_INPUT,
WID_BUTTON_APPLY,
WID_BUTTON_MODAL,
WID_CHECK_NOTIFICATIONS,
WID_LABEL_SLIDER,
WID_SLIDER_VALUE,
WID_PROGRESS_VALUE,
WID_LABEL_MODAL,
WID_BUTTON_CONFIRM,
WID_BUTTON_CLOSE
};
// Values changed while the tutorial runs.
struct TutorialState
{
Sint32 idBgImage = -1;
Sint32 idCursor = -1;
Sint32 idBuiltInFont = -1;
Sint32 idTitleFont = -1;
Sint32 idMainPanel = -1;
Sint32 idModalPanel = -1;
Sint32 idDebugWindow = -1;
float fMouseX = 0.0f;
float fMouseY = 0.0f;
Sint32 iCurrentRenderRate = 0;
Sint32 iCurrentLogicRate = 0;
Sint32 iLogicFrames = 0;
Sint32 iWidgetActions = 0;
Sint32 iSliderValue = 50;
Sint32 iNotifications = 0;
};
// Helper functions used by the setup log messages.
static void logTaskOk(Log& mLog)
{
mLog.msg(LL_DEBUG, "OK\n");
}
// Record a failed tutorial task.
static void logTaskFailed(Log& mLog, Sint32 iCode)
{
mLog.msg(LL_ERROR, "FAILED (Code %d)\n", iCode);
}
// Record a failed tutorial task.
static void logTaskFailed(Log& mLog)
{
mLog.msg(LL_ERROR, "FAILED\n");
}
// Keep a fixed-rate counter so the debug window can show logic updates beside GUI activity.
static void updateLogic(TutorialState& rState)
{
rState.iLogicFrames++;
}
// Draw the current frame. GUIMgr draws panels after this callback.
static void renderFrame(Image* pBgImage)
{
if(pBgImage) pBgImage->render();
}
// Screen render callback.
// Main::update() calls this when the screen is ready to render.
static Sint32 renderWrapper(Sint32, void* pObj)
{
TutorialState* pState = static_cast<TutorialState*>(pObj);
if(!pState) return 0;
Image* pBgImage = Main::instance().imageMgr().get(pState->idBgImage);
renderFrame(pBgImage);
return 0;
}
// DebugWindow is a normal GUI panel, so it can coexist with application panels.
static void toggleDebugWindow(DebugWindow* pDebug)
{
if(!pDebug) return;
if(pDebug->baseWidget().status() == GS_SHOWN) pDebug->baseWidget().hide();
else pDebug->baseWidget().show();
}
// Send short GUI feedback to both an application label and the built-in console.
static void setStatus(Panel* pMainPanel, Console* pConsole, const string& rStatus)
{
if(pMainPanel)
{
WidgetLabel* pStatus = pMainPanel->getLabel(WID_LABEL_STATUS);
if(pStatus) pStatus->setText("Status: " + rStatus);
}
if(pConsole) pConsole->print("GUI: %s\n", rStatus.c_str());
}
// Create the tutorial control panel.
static bool createMainPanel(Main& mC64, Log& mLog, TutorialState& rState)
{
// A modeless panel remains interactive alongside other modeless panels.
mLog.msg(LL_INFO, " Create modeless panel ... ");
rState.idMainPanel = mC64.guiMgr().create("TutorialGUIMain");
Panel* pPanel = mC64.guiMgr().getPanel(rState.idMainPanel);
if(rState.idMainPanel < 0 || pPanel == nullptr)
{
logTaskFailed(mLog, rState.idMainPanel);
return false;
}
pPanel->baseWidget().setSize(620, 400);
// The panel base widget font is used by the title, independently from its children.
pPanel->baseWidget().setFont(rState.idTitleFont);
pPanel->setTitle("Tutorial 12: GUI", PH_CENTER, Position(PH_TOP, 12));
// Typed create helpers return widgets owned by the panel. Widget IDs are
// later used to route semantic events without keeping every pointer.
WidgetLabel* pLabel = pPanel->createLabel("lblIntro", WID_LABEL_INTRO);
WidgetLabel* pInputLabel = pPanel->createLabel("lblInput", WID_LABEL_INPUT);
WidgetLabel* pStatus = pPanel->createLabel("lblStatus", WID_LABEL_STATUS);
WidgetTextEdit* pInput = pPanel->createTextEdit("txtInput", WID_TEXT_INPUT);
WidgetButton* pApply = pPanel->createButton("btnApply", WID_BUTTON_APPLY);
WidgetButton* pModal = pPanel->createButton("btnModal", WID_BUTTON_MODAL);
WidgetCheckBox* pNotifications = pPanel->createCheckBox("chkNotifications", WID_CHECK_NOTIFICATIONS);
WidgetLabel* pSliderLabel = pPanel->createLabel("lblSlider", WID_LABEL_SLIDER);
WidgetSlider* pSlider = pPanel->createSlider("sldValue", WID_SLIDER_VALUE, SA_HORIZONTAL);
WidgetProgress* pProgress = pPanel->createProgress("prgValue", WID_PROGRESS_VALUE);
if(!pLabel || !pInputLabel || !pStatus || !pInput || !pApply || !pModal ||
!pNotifications || !pSliderLabel || !pSlider || !pProgress)
{
logTaskFailed(mLog);
return false;
}
// Labels use WF_BGDISABLE so only their text is drawn over the panel.
pLabel->setFont(rState.idBuiltInFont);
pLabel->setText("Drag this panel while debug and console remain available.");
pLabel->setFeatures(WF_BGDISABLE, true);
pInputLabel->setFont(rState.idBuiltInFont);
pInputLabel->setText("Message:");
pInputLabel->setPosition(Position(PH_LEFT, 20), Position(PH_TOP, 108));
pInputLabel->setFeatures(WF_BGDISABLE, true);
pInput->setFont(rState.idTitleFont);
pInput->setSize(420, 26);
pInput->setText("Hello from CRM64Pro GUI");
pInput->setPosition(Position(PH_LEFT, 100), Position(PH_TOP, 103));
// Enter commits the text. ESC or pointer focus loss releases input focus.
pInput->setFeatures(WF_LOSTFOCUS, true);
// Buttons emit EC_WIDGET_ACTION when activated.
pApply->setFont(rState.idTitleFont);
pApply->setSize(130, 30);
pApply->setText("Apply message");
pApply->setPosition(Position(PH_LEFT, 100), Position(PH_TOP, 154));
pModal->setFont(rState.idTitleFont);
pModal->setSize(130, 30);
pModal->setText("Open modal");
pModal->setPosition(Position(PH_RIGHT, -100), Position(PH_TOP, 154));
// A checkbox emits EC_WIDGET_TOGGLED with its new state in the event payload.
pNotifications->setFont(rState.idTitleFont);
pNotifications->setSize(210, 24);
pNotifications->setText("Enable notifications");
pNotifications->setPosition(Position(PH_LEFT, 40), Position(PH_TOP, 214));
pSliderLabel->setFont(rState.idBuiltInFont);
pSliderLabel->setText("Slider updates progress:");
pSliderLabel->setPosition(Position(PH_LEFT, 40), Position(PH_TOP, 264));
pSliderLabel->setFeatures(WF_BGDISABLE, true);
// Slider and progress use the same range. The slider is interactive; the
// progress widget is updated from EC_WIDGET_VALUECHANGED events.
pSlider->setSize(240, 22);
pSlider->setRange(0, 100);
pSlider->setValue(rState.iSliderValue);
pSlider->setPosition(Position(PH_LEFT, 260), Position(PH_TOP, 260));
pProgress->setSize(240, 20);
pProgress->setRange(0, 100);
pProgress->setValue(rState.iSliderValue);
pProgress->setPosition(Position(PH_LEFT, 260), Position(PH_TOP, 304));
pStatus->setFont(rState.idBuiltInFont);
pStatus->setText("Status: Ready");
pStatus->setFeatures(WF_BGDISABLE, true);
// Show the panel and give it keyboard focus for normal interaction.
pPanel->baseWidget().show();
mC64.guiMgr().setPanelFocus(rState.idMainPanel);
logTaskOk(mLog);
return true;
}
// Create the tutorial modal panel.
static bool createModalPanel(Main& mC64, Log& mLog, TutorialState& rState)
{
// A modal panel blocks interaction with modeless panels while it is shown.
mLog.msg(LL_INFO, " Create modal panel ... ");
rState.idModalPanel = mC64.guiMgr().create("TutorialGUIModal");
Panel* pPanel = mC64.guiMgr().getPanel(rState.idModalPanel);
if(rState.idModalPanel < 0 || pPanel == nullptr)
{
logTaskFailed(mLog, rState.idModalPanel);
return false;
}
pPanel->baseWidget().setSize(300, 150);
pPanel->baseWidget().setFont(rState.idTitleFont);
pPanel->setTitle("Modal panel", PH_CENTER, Position(PH_TOP, 12));
WidgetLabel* pLabel = pPanel->createLabel("lblModal", WID_LABEL_MODAL);
WidgetButton* pConfirm = pPanel->createButton("btnConfirm", WID_BUTTON_CONFIRM);
WidgetButton* pClose = pPanel->createButton("btnClose", WID_BUTTON_CLOSE);
if(!pLabel || !pConfirm || !pClose)
{
logTaskFailed(mLog);
return false;
}
pLabel->setFont(rState.idBuiltInFont);
pLabel->setText("Modal panel blocks other panels.");
pLabel->setFeatures(WF_BGDISABLE, true);
pConfirm->setFont(rState.idTitleFont);
pConfirm->setSize(100, 28);
pConfirm->setText("Confirm");
pConfirm->setPosition(Position(PH_LEFT, 30), Position(PH_BOTTOM, -20));
pClose->setFont(rState.idTitleFont);
pClose->setSize(100, 28);
pClose->setText("Close");
// Keep the modal panel hidden until the modeless panel opens it.
pPanel->baseWidget().hide();
logTaskOk(mLog);
return true;
}
// Run the tutorial application and release its resources.
int main(int argc, char* argv[])
{
if(!runParameterReadRender(argc, argv, eRenderer)) return 1;
// Start CRM64Pro memory tracking before creating engine resources.
// atexit() releases memory tracking data when the program exits.
atexit(CMem::destroy);
// Main gives access to the CRM64Pro managers.
Main& mC64 = Main::instance();
Log& mLog = *mC64.logMgr().get();
TutorialState state;
// Write log output to stdout and to a file under validation/bin.
mLog.init("Tutorial_12_GUI", LL_DEBUG, LM_FILE | LM_STDOUT, OUTPUTDIR"Tutorial_12_GUI.log");
mLog.msg(LL_INFO, "\nSETUP: screen and timing\n");
// Initialize the timer before calling Main::update().
// setRate() receives render frames per second, then logic frames per second.
mLog.msg(LL_INFO, " Initialize timer ... ");
if(!mC64.timer().init())
{
logTaskFailed(mLog);
return -1;
}
logTaskOk(mLog);
mLog.msg(LL_INFO, " Set frame rates (render=%d, logic=%d) ... ", iRenderRate, iLogicRate);
if(!mC64.timer().setRate(iRenderRate, iLogicRate))
{
logTaskFailed(mLog);
return -1;
}
logTaskOk(mLog);
// Add a 1 ms idle delay inside Main::update().
// Configure and show the default screen.
Screen* pScreen = mC64.configMgr().get();
pScreen->setSize(iScreenW, iScreenH);
pScreen->setDriver(eRenderer);
pScreen->setTitle("CRM64Pro - Tutorial 12: GUI");
mLog.msg(LL_INFO, " Create screen (%dx%d) ... ", iScreenW, iScreenH);
if(pScreen->show() < 0)
{
mLog.msg(LL_CRITICAL, "FAILED\n");
return -1;
}
logTaskOk(mLog);
mLog.msg(LL_INFO, " Set render callback ... ");
if(!pScreen->setRenderCallback([&state](Sint32 iMode) { return renderWrapper(iMode, &state); }))
{
logTaskFailed(mLog);
return -1;
}
logTaskOk(mLog);
mLog.msg(LL_INFO, "\nRESOURCES: background, cursor and font\n");
// Earlier tutorials store these reusable resources in Tutorial.cdc.
mLog.msg(LL_INFO, " Load background image from CDC ... ");
state.idBgImage = mC64.imageMgr().load(OUTPUT_CDC, RESOURCE_BG_CDC_NAME);
if(state.idBgImage < 0)
{
logTaskFailed(mLog, state.idBgImage);
return -1;
}
logTaskOk(mLog);
mLog.msg(LL_INFO, " Load cursor from CDC ... ");
state.idCursor = mC64.cursorMgr().load(OUTPUT_CDC, RESOURCE_CURSOR_CDC_NAME);
Cursor* pCursor = mC64.cursorMgr().get(state.idCursor);
if(state.idCursor < 0 || pCursor == nullptr)
{
logTaskFailed(mLog, state.idCursor);
return -1;
}
logTaskOk(mLog);
mLog.msg(LL_INFO, " Select cursor ... ");
if(mC64.cursorMgr().select(state.idCursor) < 0 || !mC64.cursorMgr().show())
{
logTaskFailed(mLog);
return -1;
}
logTaskOk(mLog);
// Child widgets use a compact white font for normal panel content.
mLog.msg(LL_INFO, " Load built-in font ... ");
state.idBuiltInFont = mC64.fontMgr().getBuiltin("CourierNew10White");
if(state.idBuiltInFont < 0 || mC64.fontMgr().get(state.idBuiltInFont) == nullptr)
{
logTaskFailed(mLog, state.idBuiltInFont);
return -1;
}
logTaskOk(mLog);
// Panel base widgets use a larger black font for their title bars.
mLog.msg(LL_INFO, " Load title font ... ");
state.idTitleFont = mC64.fontMgr().getBuiltin("CourierNew10Black");
if(state.idTitleFont < 0 || mC64.fontMgr().get(state.idTitleFont) == nullptr)
{
logTaskFailed(mLog, state.idTitleFont);
return -1;
}
logTaskOk(mLog);
mLog.msg(LL_INFO, "\nGUI: application panels\n");
// Build application panels before the debug and console tools so all GUI
// surfaces can be used together during the event loop.
if(!createMainPanel(mC64, mLog, state) || !createModalPanel(mC64, mLog, state))
{
return -1;
}
mLog.msg(LL_INFO, "\nTOOLS: debug window and console\n");
// The DebugWindow is available, but this tutorial starts with it hidden.
mLog.msg(LL_INFO, " Create debug window ... ");
state.idDebugWindow = mC64.guiMgr().create("Debug", CRM64PRO_GUI_DEBUGWINDOW);
DebugWindow* pDebug = mC64.guiMgr().getDebugWindow(state.idDebugWindow);
if(!pDebug)
{
logTaskFailed(mLog, state.idDebugWindow);
return -1;
}
pDebug->addWatch("Mouse X", nullptr, &state.fMouseX);
pDebug->addWatch("Mouse Y", nullptr, &state.fMouseY);
pDebug->addWatch("Widget actions", &state.iWidgetActions);
pDebug->addWatch("Render fps", &state.iCurrentRenderRate);
pDebug->addWatch("Logic fps", &state.iCurrentLogicRate);
pDebug->addWatch("Logic frames", &state.iLogicFrames);
pDebug->addWatch("Slider value", &state.iSliderValue);
pDebug->addWatch("Notifications", &state.iNotifications);
pDebug->baseWidget().hide();
logTaskOk(mLog);
// Main creates the default Console. Log output is also sent there.
Console* pConsole = mC64.guiMgr().getConsole();
if(pConsole) pConsole->print("Tutorial 12: GUI ready. Press ` to toggle the console.\n");
mLog.msg(LL_INFO, "\nCONTROLS\n");
mLog.msg(LL_INFO, " Mouse: Interact with panels and widgets\n");
mLog.msg(LL_INFO, " [ENTER] Commit text while input has focus\n");
mLog.msg(LL_INFO, " [ESC] Release text input focus\n");
mLog.msg(LL_INFO, " [D] Toggle debug window\n");
mLog.msg(LL_INFO, " [`] Toggle console\n");
mLog.msg(LL_INFO, " [Q] Quit\n");
mLog.msg(LL_INFO, "\nRUNNING\n");
// Keep borrowed panel pointers for event handlers. GUIMgr retains ownership.
Panel* pMainPanel = mC64.guiMgr().getPanel(state.idMainPanel);
Panel* pModalPanel = mC64.guiMgr().getPanel(state.idModalPanel);
SDL_Event event;
bool bRunning = true;
while(bRunning)
{
// Main::update() processes input, runs internal GUI logic and calls the
// screen render callback when a render frame is ready.
while(mC64.update(&event))
{
if(event.type == SDL_EVENT_QUIT)
{
bRunning = false;
}
else if(event.type == SDL_EVENT_KEY_DOWN)
{
// SDL3 stores the key value in event.key.key.
mLog.msg(LL_INFO, " Key pressed: %s (%d)\n", SDL_GetKeyName(event.key.key), event.key.key);
if(event.key.key == SDLK_Q)
{
bRunning = false;
}
else if(event.key.key == SDLK_D)
{
toggleDebugWindow(pDebug);
}
}
else if(event.type == ET_C64 &&
(event.user.code == EC_WIDGET_ACTION || event.user.code == EC_WIDGET_VALUECOMMITTED ||
event.user.code == EC_WIDGET_TOGGLED || event.user.code == EC_WIDGET_VALUECHANGED))
{
// Semantic widget events store the widget ID in data1 and the
// new checkbox or slider value in data2 when applicable.
const Sint32 iWidgetID = static_cast<Sint32>(reinterpret_cast<intptr_t>(event.user.data1));
const Sint32 iWidgetValue = static_cast<Sint32>(reinterpret_cast<intptr_t>(event.user.data2));
state.iWidgetActions++;
// Buttons, committed text, checkbox toggles and slider changes
// all arrive through the same semantic GUI event channel.
if(iWidgetID == WID_BUTTON_APPLY || iWidgetID == WID_TEXT_INPUT)
{
string sInput;
WidgetTextEdit* pInput = pMainPanel ? pMainPanel->getTextEdit(WID_TEXT_INPUT) : nullptr;
if(pInput && pInput->getText(sInput))
{
setStatus(pMainPanel, pConsole, "Message applied");
mLog.msg(LL_INFO, " Message applied: %s\n", sInput.c_str());
}
}
else if(iWidgetID == WID_BUTTON_MODAL)
{
// Showing a modal panel and assigning focus blocks the modeless panel.
if(pModalPanel)
{
pModalPanel->baseWidget().show();
mC64.guiMgr().setPanelFocus(state.idModalPanel);
}
setStatus(pMainPanel, pConsole, "Modal panel opened");
}
else if(iWidgetID == WID_BUTTON_CONFIRM)
{
if(pModalPanel) pModalPanel->baseWidget().hide();
setStatus(pMainPanel, pConsole, "Modal action confirmed");
}
else if(iWidgetID == WID_BUTTON_CLOSE)
{
if(pModalPanel) pModalPanel->baseWidget().hide();
setStatus(pMainPanel, pConsole, "Modal panel closed");
}
else if(iWidgetID == WID_CHECK_NOTIFICATIONS)
{
state.iNotifications = iWidgetValue;
setStatus(pMainPanel, pConsole, state.iNotifications ? "Notifications enabled" : "Notifications disabled");
}
else if(iWidgetID == WID_SLIDER_VALUE)
{
// Mirror the live slider value into the read-only progress display.
state.iSliderValue = iWidgetValue;
WidgetProgress* pProgress = pMainPanel ? pMainPanel->getProgress(WID_PROGRESS_VALUE) : nullptr;
if(pProgress) pProgress->setValue(state.iSliderValue);
char szStatus[64];
snprintf(szStatus, sizeof(szStatus), "Progress value: %d", state.iSliderValue);
setStatus(pMainPanel, pConsole, szStatus);
}
}
}
if(!bRunning) break;
state.fMouseX = mC64.cursorMgr().getX();
state.fMouseY = mC64.cursorMgr().getY();
state.iCurrentRenderRate = static_cast<Sint32>(mC64.timer().getCurrentRFR());
state.iCurrentLogicRate = static_cast<Sint32>(mC64.timer().getCurrentLFR());
updateLogic(state);
}
// Close resources owned by this tutorial.
mC64.guiMgr().info();
mLog.msg(LL_INFO, "\nCLEANUP\n");
// Closing a panel also releases the widgets created inside that panel.
mLog.msg(LL_INFO, " Close GUI windows ... ");
mC64.guiMgr().close(state.idDebugWindow);
mC64.guiMgr().close(state.idModalPanel);
mC64.guiMgr().close(state.idMainPanel);
logTaskOk(mLog);
mLog.msg(LL_INFO, " Close fonts ... ");
mC64.fontMgr().close(0);
logTaskOk(mLog);
mLog.msg(LL_INFO, " Close cursors ... ");
mC64.cursorMgr().close(0);
logTaskOk(mLog);
mLog.msg(LL_INFO, " Close images ... ");
mC64.imageMgr().close(0);
logTaskOk(mLog);
mLog.msg(LL_INFO, " Close CDC archives ... ");
mC64.archiveMgr().close(0);
logTaskOk(mLog);
return 0;
}
Sint32 close(Sint32 idCDC) override
Close and destroy a CDC object.
Definition CoreArchive.cpp:2476
Screen * get(Sint32 idScreen=0)
Get a pointer to the screen using its handle.
Definition CoreConfig.cpp:1497
void setMTFriendly(Uint8 iDelayMS)
Set the multithread-friendly scheduler yield.
Definition CoreConfig.cpp:1183
Console Object class.
Definition CRM64Pro.h:9678
Cursor Object class.
Definition CRM64Pro.h:3734
float getY() const
Get the mouse cursor Y position.
Definition GraphicsCursor.cpp:1041
Sint32 select(Sint32 idCursor)
Select a mouse cursor.
Definition GraphicsCursor.cpp:968
bool show()
Make visible the selected mouse cursor.
Definition GraphicsCursor.cpp:1001
Sint32 load(const string &sCDCFile, const string &sName)
Load a cursor stored in a CDC file.
Definition GraphicsCursor.cpp:1115
Sint32 close(Sint32 idCursor) override
Close and destroy a cursor.
Definition GraphicsCursor.cpp:897
Cursor * get(Sint32 idCursor)
Get a pointer to the cursor using its handle.
Definition GraphicsCursor.cpp:962
float getX() const
Get the mouse cursor X position.
Definition GraphicsCursor.cpp:1035
DebugWindow Object class.
Definition CRM64Pro.h:9810
Sint32 getBuiltin(const string &sName)
Get a built-in embedded font id using its name.
Definition GraphicsFont.cpp:1436
Sint32 close(Sint32 idFont) override
Close and destroy a font.
Definition GraphicsFont.cpp:1363
Font * get(const string &sName)
Get a pointer to the font using its name.
Definition GraphicsFont.cpp:1235
Image Object class.
Definition CRM64Pro.h:3356
Sint32 render(Sint32 idRes=0, SDL_FRect *pSrc=nullptr, SDL_FRect *pDst=nullptr)
Render the image.
Definition GraphicsImage.cpp:1575
Sint32 load(const string &sCDCFile, const string &sName)
Load an image stored in a CDC file.
Definition GraphicsImage.cpp:2476
Sint32 close(Sint32 idImg) override
Close and destroy an image.
Definition GraphicsImage.cpp:2178
Log Object class.
Definition CRM64Pro.h:771
CRM64Pro GDK Main class.
Definition CRM64Pro.h:502
Panel Object class.
Definition CRM64Pro.h:9243
Screen Object class.
Definition CRM64Pro.h:1055
Sint32 show()
Show the screen.
Definition CoreConfig.cpp:362
Sint32 setDriver(eConfigRendererDriver eCRD)
Set the renderer driver.
Definition CoreConfig.cpp:454
bool setRenderCallback(RenderCallback fnCallback)
Set a callback function for rendering graphics at Render Frame Rate.
Definition CoreConfig.cpp:765
Sint32 setSize(Sint32 iWidth, Sint32 iHeight)
Set the size of the screen.
Definition CoreConfig.cpp:526
bool setTitle(const string &sName)
Set the title.
Definition CoreConfig.cpp:674
Button widget for user actions.
Definition CRM64Pro.h:8266
bool setText(const string &sText, const Position &rPosX=Position(PH_CENTER), const Position &rPosY=Position(PH_CENTER))
Set the button caption.
Definition GUIWidgetBasic.cpp:317
CheckBox widget for toggle and radio-button behavior.
Definition CRM64Pro.h:8312
bool setText(const string &sText, const Position &rPosX=Position(PH_CENTER), const Position &rPosY=Position(PH_CENTER))
Set the checkbox caption.
Definition GUIWidgetBasic.cpp:410
Label widget for static text display.
Definition CRM64Pro.h:8179
Sint32 setText(const string &sText, const Position &rPosX=Position(PH_CENTER), const Position &rPosY=Position(PH_CENTER))
Set the label text.
Definition GUIWidgetBasic.cpp:98
Progress widget for value display.
Definition CRM64Pro.h:8394
Sint32 setRange(Sint32 iMin, Sint32 iMax)
Set the progress range.
Definition GUIWidgetBasic.cpp:754
Sint32 setValue(Sint32 iValue)
Set the current progress value.
Definition GUIWidgetBasic.cpp:723
Sint32 setSize(Sint32 iWidth, Sint32 iHeight) override
Set the progress widget size.
Definition GUIWidgetBasic.cpp:741
Slider widget for interactive value selection.
Definition CRM64Pro.h:8463
Sint32 setSize(Sint32 iWidth, Sint32 iHeight) override
Set the slider widget size.
Definition GUIWidgetSlider.cpp:285
Sint32 setValue(Sint32 iValue)
Set the slider value.
Definition GUIWidgetSlider.cpp:294
Sint32 setRange(Sint32 iMin, Sint32 iMax)
Set the slider range.
Definition GUIWidgetSlider.cpp:308
TextEdit widget for editable text input.
Definition CRM64Pro.h:8556
bool getText(string &sText) const
Get the text content.
Definition GUIWidgetTextEdit.cpp:1266
bool setText(const string &sText)
Set the text content.
Definition GUIWidgetTextEdit.cpp:1240
Sint32 setFont(Sint32 idFont, eWidgetState eWS=WS_DEFAULT) override
Set the text-edit font.
Definition GUIWidgetTextEdit.cpp:1538
Sint32 setTextAlign(eWidgetTextAlign eAlign)
Set the horizontal content-text alignment.
Definition GUIWidgetTextEdit.cpp:1225
Sint32 setSize(Sint32 iWidth, Sint32 iHeight) override
Set the text edit size.
Definition GUIWidgetTextEdit.cpp:1189
void setStatsLevel(eMemStatsLevel eMSL)
Set the memory stats gathering level.
Definition CMemoryManager.cpp:618
void destroy()
Releases the internal heap and displays final stats.
Definition CMemoryManager.cpp:954
@ MSL_NORMAL
Collect allocations per module (CRM64Pro and user). Default for debug builds.
Definition CRM64Pro.h:386
eConfigRendererDriver
Screen renderer drivers.
Definition CRM64Pro.h:1013
@ CRD_SOFTWARE
Screen::setDriver(): software renderer driver. Slow but works on all platforms.
Definition CRM64Pro.h:1015
Sint32 create(const string &sName, Uint32 iType=CRM64PRO_GUI_PANEL) override
Creates a new GUI object.
Definition GUI.cpp:1051
Sint32 addWatch(const string &sName, Sint32 *iIntegerVar, float *fFloatVar=nullptr)
Add a watch.
Definition GUIDebugWindow.cpp:275
#define CRM64PRO_GUI_DEBUGWINDOW
Definition CRM64Pro.h:9195
Panel * getPanel(Sint32 idGUI)
Get a pointer to a Panel using its handle.
Definition GUI.cpp:1324
WidgetButton * createButton(const string &sName, Sint32 idWidget)
Create a Button widget and return its typed pointer.
Definition GUIPanel.cpp:1153
WidgetProgress * getProgress(Sint32 idWidget)
Get a typed Progress widget using its ID.
Definition GUIPanel.cpp:1247
WidgetLabel * getLabel(Sint32 idWidget)
Get a typed Label widget using its ID.
Definition GUIPanel.cpp:1217
Widget & baseWidget()
Get the reference to base Widget for this object.
Definition GUIDebugWindow.cpp:269
Sint32 print(const char *format,...)
Print a formatted message to the console.
Definition GUIConsole.cpp:403
Widget & baseWidget()
Get the reference to base Widget for this object.
Definition GUIPanel.cpp:878
WidgetProgress * createProgress(const string &sName, Sint32 idWidget)
Create a Progress widget and return its typed pointer.
Definition GUIPanel.cpp:1167
WidgetTextEdit * getTextEdit(Sint32 idWidget)
Get a typed TextEdit widget using its ID.
Definition GUIPanel.cpp:1259
Sint32 setPanelType(ePanelType ePT, Sint32 iTime=0)
Set the panel type.
Definition GUIPanel.cpp:1350
Sint32 setTitle(const string &sText, const Position &rPosX=Position(PH_CENTER), const Position &rPosY=Position(PH_CENTER))
Set the panel title text.
Definition GUIPanel.cpp:884
WidgetLabel * createLabel(const string &sName, Sint32 idWidget)
Create a Label widget and return its typed pointer.
Definition GUIPanel.cpp:1132
Console * getConsole(Sint32 idGUI=CRM64PRO_GUI_CONSOLE)
Get a pointer to a Console using its handle.
Definition GUI.cpp:1366
WidgetCheckBox * createCheckBox(const string &sName, Sint32 idWidget)
Create a CheckBox widget and return its typed pointer.
Definition GUIPanel.cpp:1160
bool info(Sint32 iMode=0) override
Request GUI Manager information.
Definition GUI.cpp:1005
Sint32 setPanelFocus(Sint32 idGUI)
Set the focus on the given GUI panel id.
Definition GUI.cpp:1346
DebugWindow * getDebugWindow(Sint32 idGUI)
Get a pointer to a DebugWindow using its handle.
Definition GUI.cpp:1375
Sint32 close(Sint32 idGUI) override
Close and destroy a GUI object.
Definition GUI.cpp:1168
WidgetSlider * createSlider(const string &sName, Sint32 idWidget, eSliderAxis eAxis=SA_HORIZONTAL)
Create a Slider widget and return its typed pointer.
Definition GUIPanel.cpp:1174
WidgetTextEdit * createTextEdit(const string &sName, Sint32 idWidget)
Create a TextEdit widget and return its typed pointer.
Definition GUIPanel.cpp:1189
@ PT_MODAL
Modal panel. When it gets the focus, it blocks the input for others panels on the same screen.
Definition CRM64Pro.h:9201
@ PT_MODELESS
Modeless panel. When it gets the focus, it does not block the input for others panels.
Definition CRM64Pro.h:9200
eGeneralStatus status() const
Get the widget status.
Definition GUIWidget.cpp:1772
bool hide()
Hide a widget.
Definition GUIWidget.cpp:1729
virtual Sint32 setSize(Sint32 iWidth, Sint32 iHeight)
Set the size.
Definition GUIWidget.cpp:1813
virtual Sint32 setFont(Sint32 idFont, eWidgetState eWS=WS_DEFAULT)
Set the font.
Definition GUIWidget.cpp:2063
bool setFeatures(eWidgetFeature eWF, bool bEnable)
Set widget feature flags.
Definition GUIWidget.cpp:1786
bool show()
Show a widget.
Definition GUIWidget.cpp:1671
Sint32 setPosition(const Position &rPosX=Position(PH_CENTER), const Position &rPosY=Position(PH_CENTER))
Set the position.
Definition GUIWidget.cpp:1923
@ WTA_CENTER
Center text inside the content area.
Definition CRM64Pro.h:7448
@ WF_LOSTFOCUS
Allow the widget to generate EC_WIDGET_LOSTFOCUS events.
Definition CRM64Pro.h:7464
@ WF_FADE
Fading effect at showing and hiding time.
Definition CRM64Pro.h:7456
@ WF_DRAGDROP
Allow to move the widget (drag&drop).
Definition CRM64Pro.h:7457
@ WF_BGDISABLE
Background rendering is disabled.
Definition CRM64Pro.h:7461
@ SA_HORIZONTAL
Horizontal slider axis.
Definition CRM64Pro.h:7440
Log * get(Sint32 idLog=0)
Get a pointer to the log object.
Definition CoreLog.cpp:921
bool init(const string &sAppname, eLogLevel eLL, eLogMode eLM, const string &sFilename="", const string &sAuthor="", const string &sEmail="", const string &sWeb="")
Initialize the log object.
Definition CoreLog.cpp:338
@ LM_FILE
Log output mode for the 'lm' parameter in CRM64Pro::Log::init(). Output to a file and overwrite exist...
Definition CRM64Pro.h:748
@ LM_STDOUT
Log output mode for the 'lm' parameter in CRM64Pro::Log::init(). Output to standard output (stdout).
Definition CRM64Pro.h:750
@ LL_INFO
Runtime events (startup/shutdown). Messages without timestamp.
Definition CRM64Pro.h:764
@ LL_DEBUG
Detailed information for flow control.
Definition CRM64Pro.h:765
@ LL_CRITICAL
Fatal errors, application crash, or assertions.
Definition CRM64Pro.h:761
@ LL_ERROR
Runtime errors that do not require immediate termination.
Definition CRM64Pro.h:762
GUIMgr & guiMgr()
Get reference to GUI Manager.
Definition CoreMain.cpp:808
Timer & timer()
Get reference to Timer module.
Definition CoreMain.cpp:730
Sint32 update(SDL_Event *evUser=nullptr)
Manage the GDK update cycle with a fixed virtual logic frame rate.
Definition CoreMain.cpp:451
ImageMgr & imageMgr()
Get reference to Image Manager.
Definition CoreMain.cpp:754
CursorMgr & cursorMgr()
Get reference to Cursor Manager.
Definition CoreMain.cpp:760
static void terminate()
Main explicit destructor.
Definition CoreMain.cpp:266
static Main & instance()
Access the Main singleton.
Definition CoreMain.cpp:252
LogMgr & logMgr()
Get reference to Log Manager.
Definition CoreMain.cpp:724
ArchiveMgr & archiveMgr()
Get reference to Archive Manager.
Definition CoreMain.cpp:736
FontMgr & fontMgr()
Get reference to Font Manager.
Definition CoreMain.cpp:778
ConfigMgr & configMgr()
Get reference to Config Manager.
Definition CoreMain.cpp:712
@ GS_SHOWN
General status: enabled and shown.
Definition CRM64Pro.h:300
@ PH_CENTER
Cursor::setHotSpot(): set hotspot to cursor_size/2. Sprite::setAnimHotSpot(): set hotspot to frame_si...
Definition CRM64Pro.h:336
@ PH_BOTTOM
Widget/Font: bottom with optional offset. Scene: align to map bottom/end (MapHeight - ViewportHeight)...
Definition CRM64Pro.h:338
@ PH_LEFT
Widget/Font: left with optional offset. Scene: align to map left/begin (0.0). Valid for X axis.
Definition CRM64Pro.h:339
@ PH_RIGHT
Widget/Font: right with optional offset. Scene: align to map right/end (MapWidth - ViewportWidth)....
Definition CRM64Pro.h:340
@ PH_TOP
Widget/Font: top with optional offset. Scene: align to map top/begin (0.0). Valid for Y axis.
Definition CRM64Pro.h:337
@ EC_WIDGET_ACTION
A GUI button or command-style widget triggered an action.
Definition CRM64Pro.h:284
@ EC_WIDGET_VALUECHANGED
A GUI widget numeric value changed.
Definition CRM64Pro.h:288
@ EC_WIDGET_TOGGLED
A GUI checkbox toggled.
Definition CRM64Pro.h:289
@ EC_WIDGET_VALUECOMMITTED
A GUI edit value was committed.
Definition CRM64Pro.h:290
@ ET_C64
CRM64Pro GDK internal events.
Definition CRM64Pro.h:275
bool init(eTimerState eState=TS_INIT)
Initialize the timer system.
Definition CoreTimer.cpp:543
bool setRate(Uint16 iR, Uint16 iL)
Set preferred Render and Logic Frame Rate.
Definition CoreTimer.cpp:556
CRM64Pro GDK.
Definition AudioTrack.cpp:36
Absolute coordinate or helper-relative position used by layout APIs.
Definition CRM64Pro.h:346

Navigation

Previous: Tutorial 11 - 2D lightmaps

Back to the tutorials index

Next: Tutorial 13 - XML