Newer
Older
Import / applications / HighwayDash / ports / Framework / Dialog.h
/*!
 @header    Dialog.h
 @brief     Various widgets
 @version   0.0.1
 @author    John Ryland
 @copyright Copyright 2017 John Ryland. All rights reserved.
*/
//  BlockyFroggy
//  Copyright © 2017 John Ryland.
//  All rights reserved.
#pragma once
#ifndef DIALOG_H
#define DIALOG_H


#include <vector>
#include <stack>
#include <functional>
#include <string>
#include <map>
#include <cmath>
#include "Animation.h"
#include "Lua.h"
#include "GLProgram.h"
#include "Math.h"


//! GameUi is the namespace for the Ui subsystem.
//! It include Widget types and a Ui stack system
//! as well as loading from a JSON description.
namespace GameUi {


class Ui;
typedef std::map<std::string, std::vector<std::string> >  ActionMap;
typedef std::map<std::string, std::string>                StringMap;

  
struct Point
{
  int  x, y;
};


struct Rectangle
{
  int  x, y, w, h;
  bool isInside(Point pt) const;
};


/*!
 @struct Color
 
 @brief The Color Struct
 
 @discussion Holds a 32bit integer color with RGBA channels.
 */
struct Color
{
  Color(uint32_t a_c) { color = a_c; }
  //! @todo remove union and make accessor functions to get and set the components - should be more straight forward
  union {
    uint32_t color;
#if __BYTE_ORDER == __LITTLE_ENDIAN
    struct { uint8_t  r, g, b, a; }; // LE
#elif __BYTE_ORDER == __BIG_ENDIAN
    struct { uint8_t  b, g, r, a; }; // BE
#else
#   error "if on _WIN32, perhaps can assume LE"
#endif
  };
};

  
struct Text
{
  Text() { length = 0; }
  Text(const char *a_text) : input(a_text), formatted(a_text) { length = input.length(); }
  Text& operator=(const char *a_text) { input = a_text; formatted = a_text; return *this; }
  void update(StringMap& variables);
  size_t size() const { return length; }
  const char* c_str() const { return formatted.c_str(); }
private:
  std::string input;
  std::string formatted;
  size_t      length;
};


struct PixelData
{
  //! @todo add some initializer and accessors
  Color*   m_data;
  size_t   m_stride;
  size_t   m_rows;
};


struct AnimationParameters
{
  std::string   m_event;
  std::string   m_parameter;
  //std::string   m_curve;
  CurveFunction m_curve;
  float         m_timeScale = 1.0;
  float         m_timeOffset = 0.0;
  bool          m_loop = false;
  int           m_t0 = 0;
  int           m_t1 = 1;
};
typedef std::vector<AnimationParameters> AnimationList;


struct TextureLoadJob
{
  std::string assetName;
  uint8_t     texIdx;
};


struct MouseState
{
  Point pos;
  bool down;
};



//! @todo Does UiVertex need to be exposed in the header?
DECLARE_ATTRIB_TYPE(vec2i, int16_t, x, y)
DECLARE_ATTRIB_TYPE(tex3i, uint8_t, u, v, texIdx)


DECLARE_VERTEX(UiVertex)
  DECLARE_ATTRIB(vec2i, position,      GL_FALSE)
  DECLARE_ATTRIB(col4i, color,         GL_TRUE)
  DECLARE_ATTRIB(tex3i, textureCoords, GL_FALSE) // Fix the font and ui control elements in to a 256x256 texture
DECLARE_VERTEX_END


/// @cond INTERNAL
static_assert(sizeof(UiVertex) == 16, "Bad size");
/// @endcond


struct UpdateState
{
  uint8_t getTextureIdForAsset(const char* assetName);
  //std::map<std::string, uint8_t> mappedTextureIds;
  std::vector<TextureLoadJob> m_loadTexturesQueue;
  StringMap m_variables; // Similar to environment variables
                // name->value pairs for substitution when adding strings,
                // eg:   m_variables["BLAH"] = "foo"; addString("${BLAH}");
                // This will result in "foo" being drawn instead of "${BLAH}" as m_variables maps it to this.
};


class DrawItemsInterface
{
public:
  virtual void addQuad(const Rectangle& r, const Rectangle& tex, Color col, uint8_t texId = 0) = 0;
  virtual void addIcon(int x, int y, char icon, Color col, int scale = 2) = 0;
  virtual void addChar(int x, int y, char ch, Color col, int scale = 2) = 0;
  virtual void addString(int x, int y, const Text& a_text, Color col, int scale = 2) = 0;
  virtual void addContrastString(int x, int y, const Text& a_text, int scale = 2) = 0;
  virtual void add9Slice(const Rectangle& r, const Rectangle& t, int radius, Color col) = 0;
};


class DrawItems : public DrawItemsInterface
{
public:
  DrawItems() { m_items.reserve(65536); }
  void addQuad(const Rectangle& r, const Rectangle& tex, Color col, uint8_t texId = 0) override;
  void addIcon(int x, int y, char icon, Color col, int scale = 2) override;
  void addChar(int x, int y, char ch, Color col, int scale = 2) override;
  void addString(int x, int y, const Text& a_text, Color col, int scale = 2) override;
  void addContrastString(int x, int y, const Text& a_text, int scale = 2) override;
  void add9Slice(const Rectangle& r, const Rectangle& t, int radius, Color col) override;
  std::vector<UiVertex> m_items;
};


class UiControl
{
public:
  virtual ~UiControl(){}

  bool handleMouse(MouseState ms);

/*
  enum MouseState {
    ButtonState = 1,
    InsideState = 2,
    PositionState = 4
  };
  virtual void onMouseStateChange()   {}
*/

  // Other possible events:
  // onHide/Close
  // onShow/Open
  // onKeyPress
  // onKeyRelease
  // onMinimize
  // onMaximize
  // onEvent // generic messaging system
  // onHover
  // onEnter
  // onLeave

  virtual void onMousePress()       { genericDispatch("onMousePress");   }
  virtual void onMouseRelease()     { genericDispatch("onMouseRelease"); }
  virtual void onMouseMove()        { genericDispatch("onMouseMove");    }
  virtual void onMouseEnter()       { genericDispatch("onMouseEnter");   }
  virtual void onMouseLeave()       { genericDispatch("onMouseLeave");   }
  virtual void onMouseClicked()     { genericDispatch("onMouseClicked"); }
 
  virtual void onUpdate(UpdateState& variables, float elapsed) {}
  virtual void onDraw(DrawItemsInterface& drawItems) {}

/*
  void setRequiresUpdate()   { m_needsUpdate = true;  }
  void clearRequiresUpdate() { m_needsUpdate = false; }
  bool requiresUpdate()      { return m_needsUpdate;  }
  const char* name()         { return m_name.c_str(); }
*/

  bool m_needsUpdate = true;
  std::string m_name = "null";
  AnimationList m_animationList;
  ActionMap m_actionMap;
  Ui* m_ui = nullptr;

protected:
  Rectangle m_geometry;
  bool m_mouseDownInside = false;
  bool m_mouseDownLast = false;
  bool m_mouseInsideLast = false;
  MouseState m_mouseState;
  void genericDispatch(const char* event);
};


//! @todo too many hard coded numbers in the UiControl subclasses

class Label : public UiControl
{
public:
  Label(int x, int y, int alignmentFlags, const char* a_text) : m_x(x), m_y(y), m_flags(alignmentFlags), m_text(a_text) {
    m_geometry = Rectangle{x,y,uint16_t(m_text.size()*10),16};
    m_name = "label";
  }
  void onUpdate(UpdateState& state, float elapsed) override {
    m_text.update(state.m_variables);
    UiControl::onUpdate(state, elapsed);
  }
  void onDraw(DrawItemsInterface& items) override {
    if (m_flags) {
      const int chWidth = 20;
      int strWidth = (int)m_text.size() * chWidth;
      items.addContrastString(m_x - strWidth, m_y, m_text);
    } else
      items.addContrastString(m_x, m_y, m_text);
    UiControl::onDraw(items);
  }
private:
  int m_x, m_y, m_flags;
  Text m_text;
};


class ButtonBase : public UiControl
{
public:
  void onMousePress()  override   { if (!m_pressed) { m_pressed = true;  m_needsUpdate = true; } UiControl::onMousePress();   }
  void onMouseRelease() override  { if (m_pressed)  { m_pressed = false; m_needsUpdate = true; } UiControl::onMouseRelease(); }
  void onMouseClicked() override  { if (m_isToggle) { m_on = !m_on;      m_needsUpdate = true; } UiControl::onMouseClicked(); }
  //std::function<void()> m_clicked;
  bool m_pressed = false;
  bool m_isToggle = false;
  bool m_on = false;
};


class PixmapEditor : public ButtonBase
{
public:
  PixmapEditor(Rectangle screenGeometry, PixelData pixmap, Rectangle pixmapRect) {
    m_geometry = screenGeometry;
    m_pixmapSlice = pixmapRect;
    m_pixmap = pixmap;
    m_name = "pixmapeditor";
  }
  void onUpdate(UpdateState& state, float elapsed) override {
    UiControl::onUpdate(state, elapsed);
  }
  void onDraw(DrawItemsInterface& items) override {
    if (!m_pixmapSlice.w || !m_pixmapSlice.h)
      return;
    int pixW = m_geometry.w / m_pixmapSlice.w;
    int pixH = m_geometry.h / m_pixmapSlice.h;
    for (int j = 0; j < m_pixmapSlice.h; j++)
      for (int i = 0; i < m_pixmapSlice.w; i++) {
        int x = m_geometry.x + pixW * i;
        int y = m_geometry.y + pixH * j;
        size_t idx = (m_pixmapSlice.y + j) * m_pixmap.m_stride + m_pixmapSlice.x + i;
        if (idx < m_pixmap.m_stride * m_pixmap.m_rows)
          items.addQuad(Rectangle{x, y, pixW, pixH}, Rectangle{179,113,30,30}, m_pixmap.m_data[idx]);
      }
    UiControl::onDraw(items);
  }
private:
  PixelData m_pixmap;
  Rectangle m_pixmapSlice;
};


class ToolButton : public ButtonBase
{
public:
  ToolButton(int x, int y, int icon) : m_x(x), m_y(y), m_icon(icon) {
    // , std::function<void()> clicked) : m_x(x), m_y(y), m_icon(icon) {
    //m_clicked = clicked;
    m_geometry = Rectangle{x,y,50,50};
    m_name = "toolbutton";
  }
  void onUpdate(UpdateState& state, float elapsed) override {
    UiControl::onUpdate(state, elapsed);
  }
  void onDraw(DrawItemsInterface& items) override {
    // Tool Button
    int state = (m_pressed) ? 31 : ((m_on) ? 61 : 1);
    items.add9Slice(Rectangle{m_x, m_y, 50, 50}, Rectangle{state, 114, 30, 30}, 10, 0xaadd9999);
    // Icon
    items.addIcon(m_x+5, m_y+6+(m_pressed?1:0), m_icon, 0xFF772222);
    //int tx1 = 1 + 20*m_icon, ty1 = 5*19;//64;
    //items.addQuad(Rectangle{m_x+4, m_y+5+(m_pressed?1:0), 40, 38}, Rectangle{tx1, ty1, 20, 19}, 0xFF772222);
    UiControl::onDraw(items);
  }
private:
  int m_x, m_y, m_icon;
};

  
static const int buttonHeight = 70;
  
class Button : public ButtonBase
{
public:
  Button(int x, int y, const char* a_text) : m_x(x), m_y(y), m_text(a_text) {
    // , std::function<void()> clicked) : m_x(x), m_y(y), m_text(a_text) {
    //m_clicked = clicked;
    m_geometry = Rectangle{x,y,250,buttonHeight};
    m_name = "button";
  }
  void onUpdate(UpdateState& state, float elapsed) override {
    m_text.update(state.m_variables);
    UiControl::onUpdate(state, elapsed);
  }
  void onDraw(DrawItemsInterface& items) override {
    Log(LL_Trace, "button update");
    int state = (m_pressed) ? 31 : ((m_on) ? 61 : 1);
    items.add9Slice(Rectangle{m_x, m_y, 250, buttonHeight}, Rectangle{state, 114, 30, 30}, 15, 0xff550011);// 0xaa550000);
    items.addContrastString(m_x+125-(int)m_text.size()*10, m_y+18+(m_pressed?1:0), m_text);
    UiControl::onDraw(items);
  }
private:
  int m_x, m_y;
  Text m_text;
};


class CheckBox : public UiControl
{
public:
  CheckBox(int x, int y, const char* a_text, std::string binding) : m_x(x), m_y(y), m_text(a_text), m_dataBinding(binding) {
    m_geometry = Rectangle{x,y,250,50};
    m_name = "checkbox";
    m_mouseState.down = false;
    m_lastMouseState.down = false;
  }
  void onUpdate(UpdateState& state, float elapsed) override {
    if (!m_mouseState.down) {
      m_down = false;
      m_on = state.m_variables[m_dataBinding] == "on";
    } else if (!m_down && !m_lastMouseState.down && m_geometry.isInside(m_mouseState.pos)) {
      m_down = true;
      m_on = !m_on;
    }
    m_lastMouseState = m_mouseState;
    state.m_variables[m_dataBinding] = (m_on) ? "on" : "off";
    m_text.update(state.m_variables);
    UiControl::onUpdate(state, elapsed);
  }
  void onDraw(DrawItemsInterface& items) override {
    char icon = 10 + ((m_on) ? 1 : 0);
    items.addIcon(m_x, m_y, icon, 0xff550011);
    items.addContrastString(m_x+45, m_y, m_text);
    UiControl::onDraw(items);
  }
private:
  MouseState m_lastMouseState;
  bool m_down = false;
  bool m_on = false;
  int m_x, m_y;
  Text m_text;
  std::string m_dataBinding;
};


class Slider : public UiControl
{
public:
  Slider(int x, int y, int height, std::string binding) : m_x(x), m_y(y), m_h(height), m_dataBinding(binding) {
    m_geometry = Rectangle{x-15,y-20,50,50};
    m_name = "slider";
    m_mouseState.down = false;
    m_lastMouseState.down = false;
    m_down = false;
  }
  void onUpdate(UpdateState& state, float elapsed) override {
    if (!m_mouseState.down)
      m_down = false;
    else if (!m_down && !m_lastMouseState.down && m_geometry.isInside(m_mouseState.pos))
      m_down = true;
    if (m_down && m_mouseState.down)
      //m_geometry.y += m_mouseState.pos.y - m_lastMouseState.pos.y; // Usual mouse kind of control
      m_geometry.y = m_mouseState.pos.y - 20;     // Perhaps more touch screen style of control
    m_lastMouseState = m_mouseState;
    m_geometry.y = Math::clamp(m_geometry.y, m_y-20, m_y+m_h-20);
    float percent = float(m_geometry.y - m_y + 20) / float(m_h);
    state.m_variables[m_dataBinding] = std::to_string(percent);
    UiControl::onUpdate(state, elapsed);
  }
  void onDraw(DrawItemsInterface& items) override {
    items.add9Slice(Rectangle{m_x, m_y, 20, m_h}, Rectangle{211, 114, 30, 30}, 10, 0xff550011);
    int state = (m_down) ? 31 : 1;
    items.add9Slice(m_geometry, Rectangle{state, 114, 30, 30}, 10, 0xff550011);
    UiControl::onDraw(items);
  }
private:
  MouseState m_lastMouseState;
  bool m_down;
  int m_x, m_y, m_h;
  std::string m_dataBinding;
};


class HSlider : public UiControl
{
public:
  HSlider(int x, int y, int width, std::string binding) : m_x(x), m_y(y), m_w(width), m_dataBinding(binding) {
    m_geometry = Rectangle{x-15,y-20,50,50};
    m_name = "hslider";
    m_mouseState.down = false;
    m_lastMouseState.down = false;
    m_down = false;
  }
  void onUpdate(UpdateState& state, float elapsed) override {
    if (!m_mouseState.down)
      m_down = false;
    else if (!m_down && !m_lastMouseState.down && m_geometry.isInside(m_mouseState.pos))
      m_down = true;
    if (m_down && m_mouseState.down)
      //m_geometry.x += m_mouseState.pos.x - m_lastMouseState.pos.x; // Usual mouse kind of control
      m_geometry.x = m_mouseState.pos.x - 20;     // Perhaps more touch screen style of control
    m_lastMouseState = m_mouseState;
    m_geometry.x = Math::clamp(m_geometry.x, m_x-20, m_x+m_w-20);
    float percent = float(m_geometry.x - m_x + 20) / float(m_w);
    state.m_variables[m_dataBinding] = std::to_string(percent);
    UiControl::onUpdate(state, elapsed);
  }
  void onDraw(DrawItemsInterface& items) override {
    items.add9Slice(Rectangle{m_x, m_y+3, m_w, 5}, Rectangle{211, 114, 30, 30}, 2, 0xff550011);
    int state = (m_down) ? 31 : 1;
    items.add9Slice(m_geometry, Rectangle{state, 114, 30, 30}, 10, 0xff550011);
    UiControl::onDraw(items);
  }
private:
  MouseState m_lastMouseState;
  bool m_down;
  int m_x, m_y, m_w;
  std::string m_dataBinding;
};



class Frame : public UiControl
{
public:
  Frame(int x, int y, int w, int h, const char* a_text) : m_text(a_text) {
    m_geometry = Rectangle{x,y,w,h};
    m_name = "frame";
  }
  void onUpdate(UpdateState& state, float elapsed) override {
    m_text.update(state.m_variables);
    UiControl::onUpdate(state, elapsed);
  }
  void onDraw(DrawItemsInterface& items) override {
    int x = m_geometry.x, y = m_geometry.y, w = m_geometry.w, h = m_geometry.h;
    items.add9Slice({ x-3, y-35, 120,   50}, {  1, 114, 30, 30}, 10, 0xff553333);
    items.add9Slice({ x+0,  y+0, w+0, h+ 0}, { 90, 114, 30, 30}, 20, 0xff000000);
    items.addQuad(  { x+3,  y+3, w-6, h- 6}, {150, 114, 30, 30},     0xffffffff);
    items.addContrastString( x+15, y-25, m_text, 1);
    UiControl::onDraw(items);
  }
private:
  Text m_text;
};


class Panel : public UiControl
{
public:
  Panel(int w, int h, Color col) : m_w(w), m_h(h), m_col(col) {
    m_geometry = Rectangle{0,0,w,h};
    m_name = "panel";
  }
  void onUpdate(UpdateState& state, float elapsed) override {
    UiControl::onUpdate(state, elapsed);
  }
  void onDraw(DrawItemsInterface& items) override {
    items.addQuad(Rectangle{  0,   0, m_w,     m_h},     Rectangle{150, 114, 30, 30},   0x33333333);
    items.add9Slice(Rectangle{ 50,  50, m_w-100, m_h-200}, Rectangle{90, 114, 30, 30}, 50, 0xdd223322);//0x77CF77);
    UiControl::onDraw(items);
  }
private:
  int m_w, m_h;
  Color m_col;
};


class RectangleShape : public UiControl
{
public:
  RectangleShape(int x, int y, int w, int h, Color col) : m_col(col) {
    m_geometry = Rectangle{x,y,w,h};
  }
  void onUpdate(UpdateState& state, float elapsed) override {
    m_t += elapsed;
    for (size_t i = 0; i < m_animationList.size(); i++) {
      AnimationParameters params = m_animationList[i];
      double tmp, ratio = m_t * 100.0 / params.m_timeScale;
      if (ratio >= 1.0) {
        if (params.m_loop)
          ratio = modf(ratio, &tmp);
        else
          ratio = 1.0;
      }
      int value = Math::lerp(params.m_t0, params.m_t1, params.m_curve(ratio));
      Log(LL_Trace, "applying anim param %s = %i", params.m_parameter.c_str(), value);
      if (params.m_parameter == "x")
        m_geometry.x = value;
      else if (params.m_parameter == "y")
        m_geometry.y = value;
      else if (params.m_parameter == "w")
        m_geometry.w = value;
      else if (params.m_parameter == "h")
        m_geometry.h = value;
      else if (params.m_parameter == "r")
        m_col.r = value;
      else if (params.m_parameter == "g")
        m_col.g = value;
      else if (params.m_parameter == "b")
        m_col.b = value;
      else if (params.m_parameter == "a")
        m_col.a = value;
    }
    UiControl::onUpdate(state, elapsed);
  }
  void onDraw(DrawItemsInterface& items) override {
    items.addQuad(m_geometry, Rectangle{180+1, 114+1, 30-2, 30-2}, m_col);
    UiControl::onDraw(items);
  }
private:
  float m_t;
  Color m_col;
};


class ImageShape : public UiControl
{
public:
  ImageShape(int x, int y, const char* assetName) {
    int w = 255, h = 255;
    m_asset = assetName;
    Log(LL_Trace, "image loading with asset: %s", assetName);
    // load asset and set m_texIdx
    m_geometry = Rectangle{x,y,w,h};
  }
  void onUpdate(UpdateState& state, float elapsed) override {
    m_texIdx = state.getTextureIdForAsset(m_asset.c_str());
    UiControl::onUpdate(state, elapsed);
  }
  void onDraw(DrawItemsInterface& items) override {
    items.addQuad(m_geometry, Rectangle{0,0,m_geometry.w,m_geometry.h}, 0xff223344, m_texIdx);
    UiControl::onDraw(items);
  }
private:
  std::string m_asset;
  int m_texIdx = 1;
};


class DynamicImageShape : public UiControl
{
public:
  DynamicImageShape(int x, int y, int texId) {
    int w = 512, h = 512;
    m_texIdx = texId;
    m_geometry = Rectangle{x,y,w,h};
  }
  void onUpdate(UpdateState& state, float elapsed) override {
    UiControl::onUpdate(state, elapsed);
  }
  void onDraw(DrawItemsInterface& items) override {
    items.addQuad(m_geometry, Rectangle{0,0,m_geometry.w,m_geometry.h}, 0xff223344, m_texIdx);
    UiControl::onDraw(items);
  }
private:
  int m_texIdx = 1;
};


class View
{
public:
  View(int x, int y, int w, int h, const char* view) : m_geometry{x,y,w,h}, m_view(view) {}
  Rectangle m_geometry;
  std::string m_view;
};


class Dialog
{
public:
  Dialog() {}
  Dialog(int w, int h) {
    m_controls.push_back(new Panel(w,h,0xaa770000));
  }
  ~Dialog() {
    for (UiControl* ctrl : m_controls) {
      delete ctrl;
    }
  }
  bool handleMouse(MouseState ms, bool& consumed) {
    m_needsUpdate = false;
    // for (UiControl* ctrl : m_controls) {
    for (long i = m_controls.size()-1; i >= 0; i--) {
      UiControl* ctrl = m_controls[i];
      consumed = ctrl->handleMouse(ms);
      if (ctrl->m_needsUpdate)
        Log(LL_Trace, "need update for ctrl %s", ctrl->m_name.c_str());
      m_needsUpdate |= ctrl->m_needsUpdate;
      if (consumed)
        break; // Only let one control consume the mouse event (perhaps should do this in reverse ctrl order)
    }
    return m_needsUpdate;
  }

  void update(UpdateState& state, float elapsed) {
    for (UiControl* ctrl : m_background) {
      Log(LL_Trace, "update for ctrl %s", ctrl->m_name.c_str());
      ctrl->onUpdate(state, elapsed);
      ctrl->m_needsUpdate = false;
    }
    for (UiControl* ctrl : m_controls) {
      Log(LL_Trace, "update for ctrl %s", ctrl->m_name.c_str());
      ctrl->onUpdate(state, elapsed);
      ctrl->m_needsUpdate = false;
    }
    m_needsUpdate = false;
  }

  void drawBackgroundItems(DrawItemsInterface& items) {
    for (UiControl* ctrl : m_background)
      ctrl->onDraw(items);
  }

  void drawForegroundItems(DrawItemsInterface& items) {
    for (UiControl* ctrl : m_controls)
      ctrl->onDraw(items);
  }

 // void close()                    { m_onClose(); }
 // std::function<void()>           m_onClose;
  std::vector<UiControl*>         m_background;
  std::vector<View*>              m_views;
  std::vector<UiControl*>         m_controls;
  bool                            m_needsUpdate = false;
  ActionMap                       m_actionMap;
  std::string                     m_name;
  std::string                     m_stage; // expected c++ level
};


class Ui
{
public:
  ~Ui() {
    // Probably need to remove then from the DialogMap, not the stack
    //for (Dialog* dialog : m_stack)
    //  delete dialog;
  }

  void loadDialogsFromAsset(const char* assetName);
  void dialogRedirect(std::string redirect);

  bool handleMouse(MouseState ms) { bool consumed = false;
    if (ms.down != m_lastMouse.down) {
      float dx = m_lastMouse.pos.x - ms.pos.x;
      if (ms.down)
        dialogDispatch("onTap");
      else if (dx > 5)
        dialogDispatch("onSwipeRight");
      else if (dx < -5)
        dialogDispatch("onSwipeLeft");
      m_lastMouse = ms;
    }
    if (m_stack.size())
      m_needsUpdate = current().handleMouse(ms, consumed);
    return consumed;
  }

  void update(float a_elapsed)                         { if (m_stack.size()) { current().update(m_state, a_elapsed); } m_needsUpdate = false; }
  void drawBackgroundItems(DrawItemsInterface& items)  { if (m_stack.size()) { current().drawBackgroundItems(items); } }
  void drawForegroundItems(DrawItemsInterface& items)  { if (m_stack.size()) { current().drawForegroundItems(items); } }
  
  std::vector<View*>* getCurrentViews()               { if (m_stack.size()) { return &current().m_views; } return nullptr; }
  //void getCurrentViews(std::vector<View*>& views)      { if (m_stack.size()) { views = current().m_views; } }

  void pushDialog(Dialog* dialog) { m_stack.push_back(dialog); /* dialog->m_onClose = [this](){ pop(); }; */ m_needsUpdate = true; }
  void pop()                      { m_stack.pop_back(); m_needsUpdate = true; }
  void closeCurrent()             { if (m_stack.size()) pop(); } //current().close(); }

  void exitCurrent()              { dialogDispatch("onExit"); }

  std::string currentStage()      { if (m_stack.size()) return current().m_stage; return "blank"; }

  void resetState()               { m_state.m_loadTexturesQueue.clear(); }
  UpdateState& state()            { return m_state; }
  bool needsUpdate()              { return m_needsUpdate; }

  void onLaunch()                 { genericDispatch("onLaunch");   }
  void onGameOver()               { genericDispatch("onGameOver"); }

  void bindFunctionToLua(const char* name, lua_CFunction fn) {
    m_luaVM.bindCFunction(name, fn);
  }

private:
  MouseState m_lastMouse = {{0,0},false};
  ActionMap m_gameFlowSteps;
  void genericDispatch(const char* event);   // events that are mapped to actions that are ui.json wide / game flow steps
  void dialogDispatch(const char* event) {   // events that are mapped to actions for particular dialogs
    if (m_stack.size())  {
      if (current().m_actionMap.count(event))
        for (auto action : current().m_actionMap[event])
          dialogRedirect(action);
    }
  }

  Dialog& current()               { return *m_stack.back(); }
  std::vector<Dialog*> m_stack;
  bool m_needsUpdate = true;
  typedef std::map<std::string, Dialog*> DialogMap;
  DialogMap m_dialogMap;
  float m_t = 0.0;
  LuaVM m_luaVM;
  UpdateState m_state;
};


}


#endif // DIALOG_H