19#include <toml++/toml.hpp>
26class TOAST_API ITrigger {
56 virtual auto name() const -> std::string_view = 0;
64 static auto
fromToml(const toml::table& table,
ValueType vt) -> std::shared_ptr<ITrigger>;
69inline auto levelOf(
const InputSample& sample) ->
float {
70 return sample.is_vector ? glm::length(sample.vector) : std::abs(sample.scalar);
73inline auto toNDC(glm::vec2 screen,
const EvalContext& ctx) -> glm::vec2 {
74 const glm::vec2 size = glm::max(ctx.
viewport_size, glm::vec2 {1.0f});
76 return glm::vec2 {((local.x / size.x) * 2.0f) - 1.0f, 1.0f - ((local.y / size.y) * 2.0f)};
79inline auto deltaToNDC(glm::vec2 screen_delta,
const EvalContext& ctx) -> glm::vec2 {
80 const glm::vec2 size = glm::max(ctx.
viewport_size, glm::vec2 {1.0f});
81 return glm::vec2 {(screen_delta.x / size.x) * 2.0f, -(screen_delta.y / size.y) * 2.0f};
84inline auto applyAxisThreshold(
float value, glm::vec2 range,
bool normalize) ->
float {
85 const float mag = std::abs(value);
92 const float span = std::max(range.y - range.x, 0.0001f);
93 const float t = glm::clamp((mag - range.x) / span, 0.0f, 1.0f);
94 return std::copysign(t, value);
102class StartTrigger :
public ITrigger {
104 StartTrigger(
const toml::table& t,
ValueType vt)
105 : m_threshold(parse::number(t[
"threshold"], 0.5f)),
106 m_value(parse::value(t[
"value"], vt)),
107 m_countdown(parse::number(t[
"countdown"], 0.0f)) { }
111 const bool on = detail::levelOf(sample) >= m_threshold;
113 if (on && !m_was_on) {
115 if (m_countdown <= 0.0f) {
124 }
else if (on && !m_started) {
125 m_elapsed += ctx.delta;
126 if (m_elapsed >= m_countdown) {
134 }
else if (!on && m_was_on) {
146 auto name() const -> std::string_view
override {
151 float m_threshold = 0.5f;
152 Value m_value {
true};
153 float m_countdown = 0.0f;
154 bool m_was_on =
false;
155 bool m_started =
false;
156 float m_elapsed = 0.0f;
162class ReleaseTrigger :
public ITrigger {
164 ReleaseTrigger(
const toml::table& t,
ValueType vt)
165 : m_threshold(parse::number(t[
"threshold"], 0.5f)),
166 m_value(parse::value(t[
"value"], vt)) { }
170 const bool on = detail::levelOf(sample) >= m_threshold;
171 if (!on && m_was_on) {
181 auto name() const -> std::string_view
override {
186 float m_threshold = 0.5f;
187 Value m_value {
false};
188 bool m_was_on =
false;
194class HoldTrigger :
public ITrigger {
196 HoldTrigger(
const toml::table& t,
ValueType vt)
197 : m_threshold_start(parse::number(t[
"threshold_start"], 0.5f)),
198 m_threshold_release(parse::number(t[
"threshold_release"], 0.5f)),
199 m_value(parse::value(t[
"value"], vt)),
200 m_send_start(parse::boolean(t[
"send_start"],
false)),
201 m_send_release(parse::boolean(t[
"send_release"],
false)),
202 m_countdown(parse::number(t[
"countdown"], 0.0f)) { }
206 const float level = detail::levelOf(sample);
208 const bool was_holding = m_holding;
209 if (!m_holding && level >= m_threshold_start) {
213 }
else if (m_holding && level < m_threshold_release) {
219 if (!was_holding && m_countdown <= 0.0f) {
222 m_elapsed += ctx.delta;
223 if (m_elapsed >= m_countdown) {
242 }
else if (was_holding) {
245 }
else if (m_send_release) {
255 auto name() const -> std::string_view
override {
260 float m_threshold_start = 0.5f;
261 float m_threshold_release = 0.5f;
262 Value m_value {
true};
263 bool m_send_start =
false;
264 bool m_send_release =
false;
265 float m_countdown = 0.0f;
266 bool m_holding =
false;
267 bool m_started =
false;
268 float m_elapsed = 0.0f;
274class PulseTrigger :
public ITrigger {
276 PulseTrigger(
const toml::table& t,
ValueType vt)
277 : m_threshold_start(parse::number(t[
"threshold_start"], 0.5f)),
278 m_threshold_release(parse::number(t[
"threshold_release"], 0.5f)),
279 m_value(parse::value(t[
"value"], vt)),
280 m_rate(std::max(parse::number(t[
"rate"], 1.0f), 0.0001f)),
281 m_send_start(parse::boolean(t[
"send_start"],
false)),
282 m_send_release(parse::boolean(t[
"send_release"],
false)),
283 m_countdown(parse::number(t[
"countdown"], 0.0f)) { }
287 const float level = detail::levelOf(sample);
289 const bool was_holding = m_holding;
290 if (!m_holding && level >= m_threshold_start) {
295 }
else if (m_holding && level < m_threshold_release) {
301 if (!was_holding && m_countdown <= 0.0f) {
304 m_elapsed += ctx.delta;
305 if (m_elapsed >= m_countdown) {
322 m_pulse_acc += ctx.delta;
323 if (m_pulse_acc >= m_rate) {
324 m_pulse_acc -= m_rate;
330 }
else if (was_holding) {
333 }
else if (m_send_release) {
343 auto name() const -> std::string_view
override {
348 float m_threshold_start = 0.5f;
349 float m_threshold_release = 0.5f;
350 Value m_value {
true};
352 bool m_send_start =
false;
353 bool m_send_release =
false;
354 float m_countdown = 0.0f;
355 bool m_holding =
false;
356 bool m_started =
false;
357 float m_elapsed = 0.0f;
358 float m_pulse_acc = 0.0f;
364class Axis1DTrigger :
public ITrigger {
366 Axis1DTrigger(
const toml::table& t,
ValueType vt)
368 m_threshold(parse::vec2(t[
"threshold"], glm::vec2 {0.2f, 1.0f})),
369 m_normalize(parse::boolean(t[
"normalize"],
false)),
370 m_axis(
static_cast<int>(parse::number(t[
"axis"], 0.0f))),
371 m_send_start(parse::boolean(t[
"send_start"],
false)),
372 m_send_release(parse::boolean(t[
"send_release"],
false)) { }
376 const float out = detail::applyAxisThreshold(sample.scalar, m_threshold, m_normalize);
377 const bool active = std::abs(sample.scalar) >= m_threshold.x;
382 v[m_axis == 1 ? 1 : 0] = out;
388 if (!m_was_active && m_send_start) {
392 }
else if (m_was_active) {
393 if (m_send_release) {
400 m_was_active = active;
405 auto name() const -> std::string_view
override {
410 ValueType m_value_type = ValueType::axis_1d;
411 glm::vec2 m_threshold {0.2f, 1.0f};
412 bool m_normalize =
false;
414 bool m_send_start =
false;
415 bool m_send_release =
false;
416 bool m_was_active =
false;
422class Axis2DTrigger :
public ITrigger {
424 Axis2DTrigger(
const toml::table& t,
ValueType )
425 : m_threshold_x(parse::vec2(t[
"threshold_x"], glm::vec2 {0.2f, 1.0f})),
426 m_threshold_y(parse::vec2(t[
"threshold_y"], glm::vec2 {0.2f, 1.0f})),
427 m_normalize(parse::boolean(t[
"normalize"],
false)),
428 m_send_start(parse::boolean(t[
"send_start"],
false)),
429 m_send_release(parse::boolean(t[
"send_release"],
false)) { }
433 const glm::vec2 out {
434 detail::applyAxisThreshold(sample.vector.x, m_threshold_x, m_normalize),
435 detail::applyAxisThreshold(sample.vector.y, m_threshold_y, m_normalize),
437 const bool active = out.x != 0.0f || out.y != 0.0f;
442 if (!m_was_active && m_send_start) {
446 }
else if (m_was_active) {
447 if (m_send_release) {
454 m_was_active = active;
459 auto name() const -> std::string_view
override {
464 glm::vec2 m_threshold_x {0.2f, 1.0f};
465 glm::vec2 m_threshold_y {0.2f, 1.0f};
466 bool m_normalize =
false;
467 bool m_send_start =
false;
468 bool m_send_release =
false;
469 bool m_was_active =
false;
475class CursorTrigger :
public ITrigger {
479 CursorTrigger(
const toml::table& t,
ValueType )
480 : m_space(
static_cast<int>(parse::number(t[
"space"], 1.0f))),
481 m_delta(parse::boolean(t[
"delta"],
false)) { }
485 const glm::vec2 screen = sample.vector;
487 glm::vec2 measured = screen;
489 measured = m_has_last ? (screen - m_last) : glm::vec2 {0.0f};
499 out = m_delta ? detail::deltaToNDC(measured, ctx) : detail::toNDC(measured, ctx);
503 if (!m_has_reported || out != m_reported) {
505 m_has_reported =
true;
514 auto name() const -> std::string_view
override {
520 bool m_delta =
false;
521 glm::vec2 m_last {0.0f};
522 bool m_has_last =
false;
523 glm::vec2 m_reported {0.0f};
524 bool m_has_reported =
false;
Small helpers shared by the TOML parsing paths of triggers, modifiers and binds.
Per-frame context passed down to triggers and modifiers during evaluation.
Definition value.hpp:140
glm::vec2 viewport_size
size of the active viewport in pixels, for cursor NDC conversion
Definition value.hpp:143
glm::vec2 viewport_pos
top-left of the active viewport in pixels
Definition value.hpp:144
Value type shared by triggers, modifiers and actionsg.
ValueType
Value domain of an action or an input sample.
Definition value.hpp:22
@ axis_2d
vector
Definition value.hpp:25
ActionEvent
Discrete event an action can emit, carried alongside the action in an InputEvent.
Definition value.hpp:76
@ release
the input went from on to off
Definition value.hpp:79
@ tries
the input was pressed but a countdown must elapse before it starts
Definition value.hpp:80
@ cancelled
the input was released before the countdown finished
Definition value.hpp:82
@ countdown
equivalent of hold while the start countdown is running
Definition value.hpp:81
@ hold
the input is held; sent continuously or on a pulse
Definition value.hpp:78
@ start
the input went from off to on
Definition value.hpp:77