aboutsummaryrefslogtreecommitdiff
path: root/src/ui/routed_event_dispatch.hpp
blob: 0e60e4922f61f4cc2d3bd68989f025edf2e0d337 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#pragma once
#include "cru/ui/control.hpp"

#include "cru/common/logger.hpp"

#include <list>

namespace cru::ui {
// Dispatch the event.
//
// This will raise routed event of the control and its parent and parent's
// parent ... (until "last_receiver" if it's not nullptr) with appropriate args.
//
// First tunnel from top to bottom possibly stopped by "handled" flag in
// EventArgs. Second bubble from bottom to top possibly stopped by "handled"
// flag in EventArgs. Last direct to each control.
//
// Args is of type "EventArgs". The first init argument is "sender", which is
// automatically bound to each receiving control. The second init argument is
// "original_sender", which is unchanged. And "args" will be perfectly forwarded
// as the rest arguments.
template <typename EventArgs, typename... Args>
void DispatchEvent(const char* event_name,
                   Control* const original_sender,
                   event::RoutedEvent<EventArgs>* (Control::*event_ptr)(),
                   Control* const last_receiver, Args&&... args) {
#ifndef CRU_DEBUG
  CRU_UNUSED(event_name)
#endif

#ifdef CRU_DEBUG
  bool do_log = true;
  if (event_name == "MouseMove") do_log = false;
#endif

  if (original_sender == last_receiver) {
    /*
    #ifdef CRU_DEBUG
        if (do_log)
          log::Debug(
              "Routed event {} no need to dispatch (original_sender == "
              "last_receiver). Original sender is {}.",
              event_name, original_sender->GetControlType());
    #endif
    */
    return;
  }

  std::list<Control*> receive_list;

  auto parent = original_sender;
  while (parent != last_receiver) {
    receive_list.push_back(parent);
    parent = parent->GetParent();
  }

#ifdef CRU_DEBUG
  if (do_log) {
    std::string log = "Dispatch routed event ";
    log += event_name;
    log += ". Path (parent first): ";
    auto i = receive_list.crbegin();
    const auto end = --receive_list.crend();
    for (; i != end; ++i) {
      log += (*i)->GetControlType();
      log += " -> ";
    }
    log += (*i)->GetControlType();
    log::Debug(log);
  }
#endif

  auto handled = false;

#ifdef CRU_DEBUG
  int count = 0;
#endif

  // tunnel
  for (auto i = receive_list.crbegin(); i != receive_list.crend(); ++i) {
#ifdef CRU_DEBUG
    count++;
#endif
    EventArgs event_args(*i, original_sender, std::forward<Args>(args)...);
    static_cast<Event<EventArgs&>*>(((*i)->*event_ptr)()->Tunnel())
        ->Raise(event_args);
    if (event_args.IsHandled()) {
      handled = true;
#ifdef CRU_DEBUG
      if (do_log)
        log::Debug(
            "Routed event is short-circuit in TUNNEL at {}-st control (count "
            "from parent).",
            count);
#endif
      break;
    }
  }

  // bubble
  if (!handled) {
    for (auto i : receive_list) {
#ifdef CRU_DEBUG
      count--;
#endif
      EventArgs event_args(i, original_sender, std::forward<Args>(args)...);
      static_cast<Event<EventArgs&>*>((i->*event_ptr)()->Bubble())
          ->Raise(event_args);
      if (event_args.IsHandled()) {
#ifdef CRU_DEBUG
        if (do_log)
          log::Debug(
              "Routed event is short-circuit in BUBBLE at {}-st control "
              "(count "
              "from parent).",
              count);
#endif
        break;
      }
    }
  }

  // direct
  for (auto i : receive_list) {
    EventArgs event_args(i, original_sender, std::forward<Args>(args)...);
    static_cast<Event<EventArgs&>*>((i->*event_ptr)()->Direct())
        ->Raise(event_args);
  }

#ifdef CRU_DEBUG
  if (do_log) log::Debug("Routed event dispatch finished.");
#endif
}
}  // namespace cru::ui