DRAG

The Framework

Joshua Blank; April 10, 2024

Table of Contents

UI Framework Setup

The central classes for handling windows are the DisplayManager and the Window class. The former contains a list of all active windows spawned by the program and starts a thread in which they run upon initialization.

Basically, in any new project we start by extending the DisplayManager class with a class I always called DisplayInstance, this child class has all the global variables and controls all the windows spawned by the program.

#pragma once
#include "../../Core/Timer/Timer.h"
#include "../../Core/DynamicArray/DynamicArray.h"

#include "../ThreadManager/ThreadManager.h"
#include "../Window/Window.h"

class DisplayManager : ThreadManager<DisplayManager> {
  friend ThreadManager<DisplayManager>;

public:
  DisplayManager();
  bool Loading();
  
  Window *SpawnWindow(WindowArgs winArgs = WindowArgs());

  bool NoWindowOpen();
  void Close();

private:
  void ThreadMain(); //loop function

  void ErrorHandling(WindowInitialisationErrorCodes code);

  static LRESULT CALLBACK MessageRouter
  (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

  core::Timer _timer;
  core::DynamicArray<Window *> _windowList;
  bool _adding;
  bool _close;
};

core::DynamicArray is my own linked list implementation and could be replaced by any list class, e.g. std::vector. ThreadManager is a std::thread wrapper class:

#pragma once
#include <thread>

template <typename T>
class ThreadManager {

protected:
  ThreadManager() {}
  void Start();
  void Stop();

private:
  std::thread _mainThread;
};

template<class T>
inline void ThreadManager<T>::Start() {
  _mainThread = std::thread(&T::ThreadMain, static_cast<T*>(this));
}

template<class T>
inline void ThreadManager<T>::Stop() {
  if (_mainThread.joinable())
  _mainThread.join();
}

When we call the function DisplayManager::Start the function DisplayManager::MainThread is called in a separate thread. To create a window we call the DisplayManager::SpawnWindow function, which adds a window to _windowList. The MainThread function will then spawn the window in its next loop:

void DisplayManager::ThreadMain() {
  // run at 60fps
  _timer = core::Timer(60);

  MSG msg;
  msg.message = WM_NULL;

  while (!_close) {
  for (int i = _windowList.size() - 1; i > -1 && !_adding; i--) {

    // initialize new windows
    if(!_windowList[i]->_isInitialised) {
    ErrorHandling(_windowList[i]->initialise(MessageRouter));
    continue;
    }

    // remove closed windows for the loop
    if (!IsWindow(_windowList[i]->_winHandle)) {
    _windowList.erase(_windowList.begin() + i);
    break;
    }

    // draw the windows
    if (_timer.tick() 
    && _windowList[i]->_args->WindowSize != core::Size(0,0))
    _windowList[i]->draw();

    // handle closing windows
    if (_windowList[i]->_isClosing)
    PostMessage(_windowList[i]->_winHandle, WM_CLOSE, 0, 0);

    // event handling
#ifdef USAGE_TYPE_GAME
    if (PeekMessage(&msg, _windowList[i]->_winHandle, 0, 0, PM_REMOVE))
    DispatchMessage(&msg);
#else
    if (GetMessage(&msg, _windowList[i]->_winHandle, 0, 0))
    DispatchMessage(&msg);
#endif
  }
  }

  while (!_windowList.empty()) {
  for (int i = _windowList.size() - 1; i > -1 && !_adding; i--) {

    PostMessage(_windowList[i]->_winHandle, WM_CLOSE, 0, 0);
    if (GetMessage(&msg, _windowList[i]->_winHandle, 0, 0))
    DispatchMessage(&msg);

    if (!IsWindow(_windowList[i]->_winHandle)) {
    _windowList.erase(_windowList.begin() + i);
    break;
    }
  }
  }
}