11 if (!o)
return nullptr;
13 return i == o->end() ? nullptr : &i->second;
20 {
"control",
v.control}, {
"scale",
v.scale}, {
"deadzone",
v.deadzone},
21 {
"invert",
v.invert}};
27 x.displayNameKey =
"editor.input." +
p;
30 x.defaultValue = std::move(
d);
31 x.flags = PropertyFlag::Runtime;
32 x.numeric.minimum = lo;
33 x.numeric.maximum = hi;
36bool errors(
const std::vector<EditorDiagnostic>&
d) {
37 return std::any_of(
d.begin(),
d.end(),
38 [](
const auto&
x) { return x.severity() == DiagnosticSeverity::Error; });
43 return {
TargetId(id_),
"input-map", revision_,
false, {
CapabilityId(
"eve.editor.target.input-map-properties")}};
50 if (
s.items.empty())
return false;
51 const auto type =
s.items[0].type;
52 for (
const auto& i :
s.items) {
53 if (i.target !=
TargetId(id_) || i.type !=
type)
return false;
54 bool found =
type ==
"input.action" ? std::any_of(actions_.begin(), actions_.end(),
55 [&](
const auto&
v) { return v.id.value() == i.item.value(); })
56 :
std::any_of(bindings_.
begin(), bindings_.
end(), [&](const auto&
v) {
57 return v.id.value() == i.item.value();
59 if (!
found)
return false;
61 return type ==
"input.action" ||
type ==
"input.binding";
71 if (!
s.items.empty() &&
s.items[0].type ==
"input.binding") {
73 x.properties = {prop(
"binding.action", PropertyType::ObjectRef,
"",
"binding"),
74 prop(
"binding.device", PropertyType::Enum,
"keyboard",
"binding"),
75 prop(
"binding.control", PropertyType::String,
"",
"binding"),
76 prop(
"binding.scale", PropertyType::Float, 1.0,
"response", -100, 100),
77 prop(
"binding.deadzone", PropertyType::Float, 0.0,
"response", 0, .99),
78 prop(
"binding.invert", PropertyType::Bool,
false,
"response")};
79 x.properties[1].enumItems = {
"keyboard",
"mouse",
"gamepad",
"joystick",
"touch"};
81 x.typeId =
"input.action";
82 x.properties = {prop(
"action.name", PropertyType::String,
"",
"action"),
83 prop(
"action.kind", PropertyType::Enum,
"button",
"action")};
84 x.properties[1].enumItems = {
"button",
"axis1d"};
89 if (!matches(
s) || !schema(
s).find(
p))
return {};
90 std::optional<EditorValue> common;
91 for (
const auto& i :
s.items) {
93 if (i.type ==
"input.action") {
94 const auto&
a = *std::find_if(actions_.begin(), actions_.end(),
95 [&](
const auto&
x) { return x.id.value() == i.item.value(); });
98 const auto&
b = *std::find_if(bindings_.begin(), bindings_.end(),
99 [&](
const auto&
x) { return x.id.value() == i.item.value(); });
101 v =
b.action.value();
115 else if (*common !=
v)
116 return {PropertyReadState::Mixed, {}, {}};
118 return {PropertyReadState::Value, *common, {}};
122 for (
const auto&
v : actions_)
a.push_back(
action(
v));
123 for (
const auto&
v : bindings_)
b.push_back(
binding(
v));
128 op.
type =
"input-map.replace.v1";
135 op.
mergeKey =
"input-map:" + id_ +
":" + (
p.empty() ?
"structure" :
p);
136 return eve::editing::applied<DomainOperation>(std::move(op));
140 if (mode == PropertySetMode::Reset)
return makeReset(
s,
p);
141 auto d = schema(
s).find(
p);
143 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected,
RuleId(
"editor.input.set"),
144 "Input property edit is invalid");
146 for (
const auto& i :
s.items)
147 if (i.type ==
"input.action") {
148 auto&
a = *std::find_if(
c.actions_.begin(),
c.actions_.end(),
149 [&](
const auto&
x) { return x.id.value() == i.item.value(); });
151 a.name = *
v.getIf<std::string>();
153 a.kind = *
v.getIf<std::string>();
155 auto&
b = *std::find_if(
c.bindings_.begin(),
c.bindings_.end(),
156 [&](
const auto&
x) { return x.id.value() == i.item.value(); });
160 b.device = *
v.getIf<std::string>();
162 b.control = *
v.getIf<std::string>();
164 b.scale = *
v.getIf<
double>();
166 b.deadzone = *
v.getIf<
double>();
168 b.invert = *
v.getIf<
bool>();
170 if (errors(
c.validate()))
171 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected,
RuleId(
"editor.input.invalid"),
172 "Input edit produces invalid bindings");
173 return replacement(
c.contentValue(),
p.value());
176 auto d = schema(
s).find(
p);
178 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported,
RuleId(
"editor.input.property"),
179 "Unknown input property");
180 return makeSet(
s,
p,
d->defaultValue, PropertySetMode::Absolute);
184 c.actions_.push_back(std::move(
v));
185 if (errors(
c.validate()))
186 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected,
RuleId(
"editor.input.action"),
187 "Input action is invalid");
188 return replacement(
c.contentValue());
191 if (std::any_of(bindings_.begin(), bindings_.end(), [&](
const auto&
b) { return b.action == id; }))
192 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected,
RuleId(
"editor.input.action-referenced"),
193 "Delete bindings before deleting their action");
195 const auto before =
c.actions_.size();
196 std::erase_if(
c.actions_, [&](
const auto&
a) { return a.id == id; });
197 if (before ==
c.actions_.size())
198 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound,
RuleId(
"editor.input.action"),
199 "Input action does not exist");
200 return replacement(
c.contentValue());
204 c.bindings_.push_back(std::move(
v));
205 if (errors(
c.validate()))
206 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected,
RuleId(
"editor.input.binding"),
207 "Input binding is invalid");
208 return replacement(
c.contentValue());
212 const auto before =
c.bindings_.size();
213 std::erase_if(
c.bindings_, [&](
const auto&
b) { return b.id == id; });
214 if (before ==
c.bindings_.size())
215 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound,
RuleId(
"editor.input.binding"),
216 "Input binding does not exist");
217 return replacement(
c.contentValue());
219std::vector<EditorDiagnostic> InputMapTarget::validate()
const {
220 std::vector<EditorDiagnostic>
d;
221 std::set<std::string>
ids, names, controls;
222 for (
const auto&
a : actions_) {
223 if (
a.id.empty() ||
a.name.empty() || !
ids.insert(
a.id.value()).second || !names.insert(
a.name).second ||
224 (
a.kind !=
"button" &&
a.kind !=
"axis1d"))
227 DiagnosticSeverity::Error,
"Action IDs/names must be unique and kinds supported"));
229 for (
const auto&
b : bindings_) {
231 std::any_of(actions_.begin(), actions_.end(), [&](
const auto&
a) { return a.id == b.action; });
232 if (
b.id.empty() || !
ids.insert(
b.id.value()).second || !
target ||
b.control.empty() ||
233 !std::isfinite(
b.scale) ||
b.scale == 0 || std::abs(
b.scale) > 100 || !std::isfinite(
b.deadzone) ||
234 b.deadzone < 0 ||
b.deadzone >= 1)
237 DiagnosticSeverity::Error,
"Binding identity, reference or response is invalid"));
238 const std::string
key =
b.device +
":" +
b.control;
239 if (!controls.insert(
key).second)
242 DiagnosticSeverity::Warning,
"Control is bound more than once: " +
key));
244 if (actions_.size() > 1024 || bindings_.size() > 8192)
247 DiagnosticSeverity::Error,
"Input map exceeds action or binding budget"));
252 !op.
payload.isWithinLimits(5, 50000, 4 * 1024 * 1024))
253 return eve::editing::failed<void>(EditorStatus::Rejected,
RuleId(
"editor.input.operation"),
254 "Input map operation is invalid");
255 auto *av = field(op.
payload,
"actions"), *bv = field(op.
payload,
"bindings");
258 if (!
aa || !ba ||
aa->size() > 1024 || ba->size() > 8192)
259 return eve::editing::failed<void>(EditorStatus::Rejected,
RuleId(
"editor.input.payload"),
260 "Input map payload exceeds limits");
262 for (
const auto&
v : *
aa) {
263 auto *i = field(
v,
"id"), *
n = field(
v,
"name"), *k = field(
v,
"kind");
264 auto* is = i ? i->getIf<std::string>() :
nullptr;
265 auto* ns =
n ?
n->getIf<std::string>() :
nullptr;
266 auto* ks = k ? k->getIf<std::string>() :
nullptr;
267 if (!is || !ns || !ks)
268 return eve::editing::failed<void>(EditorStatus::Rejected,
RuleId(
"editor.input.action"),
269 "Input action payload is invalid");
270 c.actions_.push_back({
ObjectId(*is), *ns, *ks});
272 for (
const auto&
v : *ba) {
273 auto *i = field(
v,
"id"), *
a = field(
v,
"action"), *dev = field(
v,
"device"), *ctl = field(
v,
"control"),
274 *
scale = field(
v,
"scale"), *dead = field(
v,
"deadzone"), *inv = field(
v,
"invert");
275 auto* is = i ? i->getIf<std::string>() :
nullptr;
276 auto* as =
a ?
a->getIf<std::string>() :
nullptr;
277 auto* ds = dev ? dev->getIf<std::string>() :
nullptr;
278 auto* cs = ctl ? ctl->getIf<std::string>() :
nullptr;
279 auto* sn =
scale ?
scale->getIf<
double>() :
nullptr;
280 auto* dn = dead ? dead->getIf<
double>() :
nullptr;
281 auto* ib = inv ? inv->getIf<
bool>() :
nullptr;
282 if (!is || !as || !ds || !cs || !sn || !dn || !ib)
283 return eve::editing::failed<void>(EditorStatus::Rejected,
RuleId(
"editor.input.binding"),
284 "Input binding payload is invalid");
287 if (errors(
c.validate()))
288 return eve::editing::failed<void>(EditorStatus::Rejected,
RuleId(
"editor.input.invalid"),
289 "Input map validation failed");
290 actions_ = std::move(
c.actions_);
291 bindings_ = std::move(
c.bindings_);
293 dirty_.include(0, 0);
294 return eve::editing::applied<void>();
296std::unique_ptr<IDomainOperationTarget> InputMapTarget::cloneDomainState()
const {
297 return std::make_unique<InputMapTarget>(*
this);
299Result<void> InputMapTarget::commitDomainState(std::unique_ptr<IDomainOperationTarget>
c) {
301 if (!
t ||
t->id_ != id_)
302 return eve::editing::failed<void>(EditorStatus::Conflict,
RuleId(
"editor.input.candidate"),
303 "Input candidate mismatch");
305 return eve::editing::applied<void>();
311 auto *
v = field(
s,
"schemaVersion"), *
c = field(
s,
"content");
312 auto*
n =
v ?
v->getIf<int64_t>() :
nullptr;
313 if (!
n || *
n != 1 || !
c)
314 return eve::editing::failed<void>(EditorStatus::Unsupported,
RuleId(
"editor.input.snapshot"),
315 "Unsupported input snapshot");
318 op.
type =
"input-map.replace.v1";
320 auto result = applyDomainOperation(op);
321 if (result.ok()) dirty_.clear();
325 const InputMapTarget&
t,
const std::vector<InputControlSample>& samples)
const {
326 if (errors(
t.validate()))
327 return eve::editing::failed<std::map<std::string, double>>(
328 EditorStatus::Rejected,
RuleId(
"editor.input.invalid"),
"Cannot evaluate invalid input map");
329 std::map<std::string, double> out;
330 for (
const auto&
a :
t.actions()) out[
a.name] = 0;
331 for (
const auto&
b :
t.bindings()) {
332 auto s = std::find_if(samples.begin(), samples.end(),
333 [&](
const auto&
x) { return x.device == b.device && x.control == b.control; });
334 if (
s == samples.end())
continue;
335 double value = std::clamp(
s->value, -1.0, 1.0);
336 if (std::abs(
value) <=
b.deadzone)
342 auto a = std::find_if(
t.actions().begin(),
t.actions().end(), [&](
const auto&
x) { return x.id == b.action; });
343 if (
a->kind ==
"button")
344 out[
a->name] = std::max(out[
a->name], std::abs(
value) >= .5 ? 1.0 : 0.0);
346 out[
a->name] = std::clamp(out[
a->name] +
value, -1.0, 1.0);
348 return eve::editing::applied<std::map<std::string, double>>(std::move(out));
350void InputBindingCapture::begin(std::string
device) {
352 device_ = std::move(
device);
354void InputBindingCapture::cancel() {
359 if (!active_ || (!device_.empty() &&
s.device != device_) ||
s.control.empty() || !std::isfinite(
s.value) ||
360 std::abs(
s.value) < .5)
std::array< float, 3 > scale
eve::action::ActionVfxBinding binding
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
UUID-backed identifier adapter for legacy textual boundaries.
UI-independent property access for an immutable selection snapshot.
Dotted property path (layout.size) compared and persisted as text.
Deterministic owning value tree shared by authoring hosts.
std::vector< Value > Array
std::map< std::string, Value > Object
const T * getIf() const
Return an immediately borrowed typed value.
StrongId< TargetIdTag > TargetId
PropertyType
PropertyType public API.
PropertySetMode
PropertySetMode public API.
StrongId< CapabilityIdTag > CapabilityId
Result< void > validatePropertyValue(const PropertyDescriptor &descriptor, const Value &value)
Validate property value.
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
DomainOperation public API.
std::vector< std::string > affectedProperties
PropertyDescriptor public API.
PropertyReadResult public API.
EVENGINE_API_PLATFORM public API.
SelectionSnapshot public API.
Stable discovery metadata exposed by every editable target.