diff --git a/docs/used/NewEditor_VectorFieldFamilyRootDedupPlan_完成归档_2026-04-22.md b/docs/used/NewEditor_VectorFieldFamilyRootDedupPlan_完成归档_2026-04-22.md new file mode 100644 index 00000000..97da3112 --- /dev/null +++ b/docs/used/NewEditor_VectorFieldFamilyRootDedupPlan_完成归档_2026-04-22.md @@ -0,0 +1,213 @@ +# NewEditor Vector Field Family Root Dedup Plan + +Date: 2026-04-22 +Owner: Codex +Scope: `new_editor` vector field family only + +## Background + +`new_editor` currently carries three parallel vector field families: + +- `UIEditorVector2Field*` +- `UIEditorVector3Field*` +- `UIEditorVector4Field*` + +The duplication is not limited to one call site. It exists in four stacked layers: + +1. Field widget types and widget implementation +2. Field interaction state machine implementation +3. PropertyGrid vector adapters +4. PropertyGrid style adapters + +This means the real root problem is not one repeated helper function. The root problem is that one vector-field capability was modeled as three separate families, so every upper layer had to copy itself three times. + +## Objective + +Fix the vector field family from the root without damaging behavior, architecture, or existing call sites. + +The target state is: + +- one shared vector field widget core +- one shared vector field interaction core +- thin `Vector2/3/4` compatibility wrappers at the public API boundary +- reduced duplicate adapter logic in PropertyGrid/style layers + +## Non-Goals + +This plan will not: + +- change panel refresh architecture +- change PropertyGrid field kind semantics +- merge `Vector2/3/4` external command/event meanings +- redesign unrelated field families +- rename public editor commands + +## Constraints + +1. External behavior must stay stable. +2. Existing `Vector2/3/4` entry points must remain callable. +3. No fake abstraction that increases total code. +4. Each phase must compile `XCUIEditorApp`. +5. Each phase must run a startup-level smoke test. + +## Strategy + +Use a bottom-up unification strategy. + +Do not start from `PropertyGrid`. + +Start from the vector field family core, because that is where the duplication originates. Once the core is unified, upper-layer duplication can be removed safely instead of being papered over with more traits and wrappers. + +## Phase A: Shared Widget Core + +### Goal + +Collapse `UIEditorVector2Field.cpp`, `UIEditorVector3Field.cpp`, and `UIEditorVector4Field.cpp` onto one shared implementation core while preserving the public `Vector2/3/4` function names. + +### Planned Work + +1. Introduce one shared internal vector-field widget core for: + - metrics resolution + - palette resolution + - component number-spec projection + - row/component color resolution + - point-inside test + - normalize/parse/format plumbing + - layout construction + - hit testing + - background/foreground drawing +2. Keep `UIEditorVector2Field.*`, `UIEditorVector3Field.*`, `UIEditorVector4Field.*` as compatibility shells over the shared core. +3. Preserve all existing public names: + - `BuildUIEditorVector2FieldLayout` + - `BuildUIEditorVector3FieldLayout` + - `BuildUIEditorVector4FieldLayout` + - `AppendUIEditorVector2Field*` + - `AppendUIEditorVector3Field*` + - `AppendUIEditorVector4Field*` + +### Guardrails + +1. Axis colors must remain dimension-correct: + - `X`, `Y`, `Z`, `W` +2. Existing palette member layout and public signatures must remain intact. +3. No PropertyGrid logic changes in this phase. + +### Validation Gate + +- `cmake --build build --config Debug --target XCUIEditorApp` +- smoke launch `build/new_editor/Debug/XCUIEditor.exe` + +## Phase B: Shared Interaction Core + +### Goal + +Collapse `UIEditorVector2FieldInteraction.cpp`, `UIEditorVector3FieldInteraction.cpp`, and `UIEditorVector4FieldInteraction.cpp` onto one shared interaction core while preserving public `UpdateUIEditorVector[2/3/4]FieldInteraction` entry points. + +### Planned Work + +1. Introduce one shared internal interaction core for: + - selection movement + - begin/commit/cancel edit + - step application + - drag application + - focus handling + - pointer hit processing + - keyboard handling + - frame result accumulation +2. Keep per-dimension public wrappers thin. +3. Preserve current behavior for: + - active component tracking + - focused/editing transitions + - commit rejection + - drag sensitivity + - hit target propagation + +### Guardrails + +1. No semantic merging with unrelated editable-field families. +2. No behavior rewrite of keyboard/pointer policy. +3. No change to public result payloads. + +### Validation Gate + +- `cmake --build build --config Debug --target XCUIEditorApp` +- smoke launch `build/new_editor/Debug/XCUIEditor.exe` + +## Phase C: PropertyGrid And Style Adapter Closure + +### Goal + +Remove the adapter duplication that only exists because the vector family used to be triplicated. + +### Planned Work + +1. Deduplicate PropertyGrid vector spec builders where safe. +2. Deduplicate PropertyGrid vector interaction traits where safe. +3. Deduplicate vector style adapter builders for metrics/palette hosting where safe. +4. Keep `UIEditorPropertyGridFieldKind::{Vector2, Vector3, Vector4}` unchanged. + +### Guardrails + +1. Do not invent a fake shared rename/state-machine layer. +2. Do not collapse `PropertyGrid` storage semantics that are still legitimately field-kind specific. +3. Only deduplicate logic that becomes structurally identical after Phase A and B. + +### Validation Gate + +- `cmake --build build --config Debug --target XCUIEditorApp` +- smoke launch `build/new_editor/Debug/XCUIEditor.exe` + +## Phase D: Closure Review And Final Reduction + +### Goal + +Do one final review pass on the new shared vector core and the remaining PropertyGrid vector adapters, and only keep changes that still reduce code after the root unification is already in place. + +### Planned Work + +1. Recheck the new shared widget/interaction headers for mechanical repetition that can be reduced without adding another abstraction layer. +2. Recheck remaining `PropertyGrid` vector helpers and keep only reductions that are obviously net-negative in code size. +3. Stop immediately if the remaining duplication is only API-boundary compatibility code. + +### Guardrails + +1. Do not reopen panel architecture, refresh policy, or tree code. +2. Do not invent a second-round trait layer just to compress wrappers. +3. If the remaining code is already the thinnest safe compatibility shell, declare closure instead of forcing more edits. + +### Validation Gate + +- `cmake --build build --config Debug --target XCUIEditorApp` +- smoke launch `build/new_editor/Debug/XCUIEditor.exe` + +## Expected Outcome + +After completion: + +- vector field widget logic exists once instead of three times +- vector field interaction logic exists once instead of three times +- upper-layer vector adapters become thinner +- code volume drops materially instead of shifting sideways + +## Stop Conditions + +Stop and reassess if any of the following happens: + +1. public API compatibility starts requiring broader engine/editor rewiring +2. behavior drift appears in focus/edit/drag handling +3. total code starts increasing instead of decreasing + +## Deliverables + +1. shared vector field core implementation +2. shared vector field interaction implementation +3. reduced PropertyGrid/style duplication +4. final closure review of remaining vector-family duplication +5. per-phase build and smoke validation + +## Execution Status + +- Phase A: completed +- Phase B: completed +- Phase C: completed +- Phase D: completed diff --git a/new_editor/src/Fields/UIEditorFieldStyle.cpp b/new_editor/src/Fields/UIEditorFieldStyle.cpp index 11f56a6a..85c6b40c 100644 --- a/new_editor/src/Fields/UIEditorFieldStyle.cpp +++ b/new_editor/src/Fields/UIEditorFieldStyle.cpp @@ -201,10 +201,11 @@ Widgets::UIEditorTextFieldPalette BuildUIEditorPropertyGridTextFieldPalette( return hosted; } -Widgets::UIEditorVector2FieldMetrics BuildUIEditorPropertyGridVector2FieldMetrics( +template +Metrics BuildUIEditorPropertyGridVectorFieldMetrics( const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, - const Widgets::UIEditorVector2FieldMetrics& fallback) { - Widgets::UIEditorVector2FieldMetrics hosted = fallback; + const Metrics& fallback) { + Metrics hosted = fallback; hosted.rowHeight = propertyGridMetrics.fieldRowHeight; hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; hosted.labelControlGap = propertyGridMetrics.labelControlGap; @@ -224,119 +225,76 @@ Widgets::UIEditorVector2FieldMetrics BuildUIEditorPropertyGridVector2FieldMetric return hosted; } +template +Palette BuildUIEditorPropertyGridVectorFieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Palette& fallback) { + Palette hosted = fallback; + hosted.surfaceColor = kTransparent; + hosted.borderColor = kTransparent; + hosted.focusedBorderColor = kTransparent; + hosted.rowHoverColor = kTransparent; + hosted.rowActiveColor = kTransparent; + hosted.componentColor = propertyGridPalette.valueBoxColor; + hosted.componentHoverColor = propertyGridPalette.valueBoxHoverColor; + hosted.componentEditingColor = propertyGridPalette.valueBoxEditingColor; + hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; + hosted.componentBorderColor = propertyGridPalette.valueBoxBorderColor; + hosted.componentFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; + hosted.prefixColor = propertyGridPalette.valueBoxHoverColor; + hosted.prefixBorderColor = propertyGridPalette.valueBoxBorderColor; + hosted.labelColor = propertyGridPalette.labelTextColor; + hosted.valueColor = propertyGridPalette.valueTextColor; + hosted.readOnlyValueColor = propertyGridPalette.readOnlyValueTextColor; + return hosted; +} + +Widgets::UIEditorVector2FieldMetrics BuildUIEditorPropertyGridVector2FieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorVector2FieldMetrics& fallback) { + return BuildUIEditorPropertyGridVectorFieldMetrics( + propertyGridMetrics, + fallback); +} + Widgets::UIEditorVector2FieldPalette BuildUIEditorPropertyGridVector2FieldPalette( const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, const Widgets::UIEditorVector2FieldPalette& fallback) { - Widgets::UIEditorVector2FieldPalette hosted = fallback; - hosted.surfaceColor = kTransparent; - hosted.borderColor = kTransparent; - hosted.focusedBorderColor = kTransparent; - hosted.rowHoverColor = kTransparent; - hosted.rowActiveColor = kTransparent; - hosted.componentColor = propertyGridPalette.valueBoxColor; - hosted.componentHoverColor = propertyGridPalette.valueBoxHoverColor; - hosted.componentEditingColor = propertyGridPalette.valueBoxEditingColor; - hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; - hosted.componentBorderColor = propertyGridPalette.valueBoxBorderColor; - hosted.componentFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; - hosted.prefixColor = propertyGridPalette.valueBoxHoverColor; - hosted.prefixBorderColor = propertyGridPalette.valueBoxBorderColor; - hosted.labelColor = propertyGridPalette.labelTextColor; - hosted.valueColor = propertyGridPalette.valueTextColor; - hosted.readOnlyValueColor = propertyGridPalette.readOnlyValueTextColor; - return hosted; + return BuildUIEditorPropertyGridVectorFieldPalette( + propertyGridPalette, + fallback); } Widgets::UIEditorVector3FieldMetrics BuildUIEditorPropertyGridVector3FieldMetrics( const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, const Widgets::UIEditorVector3FieldMetrics& fallback) { - Widgets::UIEditorVector3FieldMetrics hosted = fallback; - hosted.rowHeight = propertyGridMetrics.fieldRowHeight; - hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; - hosted.labelControlGap = propertyGridMetrics.labelControlGap; - hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.sharedControlColumnMinWidth = propertyGridMetrics.sharedControlColumnMinWidth; - hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; - hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; - hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; - hosted.labelFontSize = propertyGridMetrics.labelFontSize; - hosted.valueTextInsetX = propertyGridMetrics.valueBoxInsetX; - hosted.valueTextInsetY = propertyGridMetrics.valueTextInsetY; - hosted.valueFontSize = propertyGridMetrics.valueFontSize; - hosted.cornerRounding = propertyGridMetrics.cornerRounding; - hosted.componentRounding = propertyGridMetrics.valueBoxRounding; - hosted.borderThickness = propertyGridMetrics.borderThickness; - hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; - return hosted; + return BuildUIEditorPropertyGridVectorFieldMetrics( + propertyGridMetrics, + fallback); } Widgets::UIEditorVector3FieldPalette BuildUIEditorPropertyGridVector3FieldPalette( const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, const Widgets::UIEditorVector3FieldPalette& fallback) { - Widgets::UIEditorVector3FieldPalette hosted = fallback; - hosted.surfaceColor = kTransparent; - hosted.borderColor = kTransparent; - hosted.focusedBorderColor = kTransparent; - hosted.rowHoverColor = kTransparent; - hosted.rowActiveColor = kTransparent; - hosted.componentColor = propertyGridPalette.valueBoxColor; - hosted.componentHoverColor = propertyGridPalette.valueBoxHoverColor; - hosted.componentEditingColor = propertyGridPalette.valueBoxEditingColor; - hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; - hosted.componentBorderColor = propertyGridPalette.valueBoxBorderColor; - hosted.componentFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; - hosted.prefixColor = propertyGridPalette.valueBoxHoverColor; - hosted.prefixBorderColor = propertyGridPalette.valueBoxBorderColor; - hosted.labelColor = propertyGridPalette.labelTextColor; - hosted.valueColor = propertyGridPalette.valueTextColor; - hosted.readOnlyValueColor = propertyGridPalette.readOnlyValueTextColor; - return hosted; + return BuildUIEditorPropertyGridVectorFieldPalette( + propertyGridPalette, + fallback); } Widgets::UIEditorVector4FieldMetrics BuildUIEditorPropertyGridVector4FieldMetrics( const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, const Widgets::UIEditorVector4FieldMetrics& fallback) { - Widgets::UIEditorVector4FieldMetrics hosted = fallback; - hosted.rowHeight = propertyGridMetrics.fieldRowHeight; - hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; - hosted.labelControlGap = propertyGridMetrics.labelControlGap; - hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.sharedControlColumnMinWidth = propertyGridMetrics.sharedControlColumnMinWidth; - hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; - hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; - hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; - hosted.labelFontSize = propertyGridMetrics.labelFontSize; - hosted.valueTextInsetX = propertyGridMetrics.valueBoxInsetX; - hosted.valueTextInsetY = propertyGridMetrics.valueTextInsetY; - hosted.valueFontSize = propertyGridMetrics.valueFontSize; - hosted.cornerRounding = propertyGridMetrics.cornerRounding; - hosted.componentRounding = propertyGridMetrics.valueBoxRounding; - hosted.borderThickness = propertyGridMetrics.borderThickness; - hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; - return hosted; + return BuildUIEditorPropertyGridVectorFieldMetrics( + propertyGridMetrics, + fallback); } Widgets::UIEditorVector4FieldPalette BuildUIEditorPropertyGridVector4FieldPalette( const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, const Widgets::UIEditorVector4FieldPalette& fallback) { - Widgets::UIEditorVector4FieldPalette hosted = fallback; - hosted.surfaceColor = kTransparent; - hosted.borderColor = kTransparent; - hosted.focusedBorderColor = kTransparent; - hosted.rowHoverColor = kTransparent; - hosted.rowActiveColor = kTransparent; - hosted.componentColor = propertyGridPalette.valueBoxColor; - hosted.componentHoverColor = propertyGridPalette.valueBoxHoverColor; - hosted.componentEditingColor = propertyGridPalette.valueBoxEditingColor; - hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; - hosted.componentBorderColor = propertyGridPalette.valueBoxBorderColor; - hosted.componentFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; - hosted.prefixColor = propertyGridPalette.valueBoxHoverColor; - hosted.prefixBorderColor = propertyGridPalette.valueBoxBorderColor; - hosted.labelColor = propertyGridPalette.labelTextColor; - hosted.valueColor = propertyGridPalette.valueTextColor; - hosted.readOnlyValueColor = propertyGridPalette.readOnlyValueTextColor; - return hosted; + return BuildUIEditorPropertyGridVectorFieldPalette( + propertyGridPalette, + fallback); } Widgets::UIEditorEnumFieldMetrics BuildUIEditorPropertyGridEnumFieldMetrics( diff --git a/new_editor/src/Fields/UIEditorPropertyGrid.cpp b/new_editor/src/Fields/UIEditorPropertyGrid.cpp index 8b80a179..b8161a03 100644 --- a/new_editor/src/Fields/UIEditorPropertyGrid.cpp +++ b/new_editor/src/Fields/UIEditorPropertyGrid.cpp @@ -381,46 +381,33 @@ UIEditorTextFieldSpec BuildTextFieldSpec(const UIEditorPropertyGridField& field) return spec; } -UIEditorVector2FieldSpec BuildVector2FieldSpec(const UIEditorPropertyGridField& field) { - UIEditorVector2FieldSpec spec = {}; +template +Spec BuildVectorFieldSpec( + const UIEditorPropertyGridField& field, + const Value& value) { + Spec spec = {}; spec.fieldId = field.fieldId; spec.label = field.label; - spec.values = field.vector2Value.values; - spec.componentLabels = field.vector2Value.componentLabels; - spec.step = field.vector2Value.step; - spec.minValue = field.vector2Value.minValue; - spec.maxValue = field.vector2Value.maxValue; - spec.integerMode = field.vector2Value.integerMode; + spec.values = value.values; + spec.componentLabels = value.componentLabels; + spec.step = value.step; + spec.minValue = value.minValue; + spec.maxValue = value.maxValue; + spec.integerMode = value.integerMode; spec.readOnly = field.readOnly; return spec; } +UIEditorVector2FieldSpec BuildVector2FieldSpec(const UIEditorPropertyGridField& field) { + return BuildVectorFieldSpec(field, field.vector2Value); +} + UIEditorVector3FieldSpec BuildVector3FieldSpec(const UIEditorPropertyGridField& field) { - UIEditorVector3FieldSpec spec = {}; - spec.fieldId = field.fieldId; - spec.label = field.label; - spec.values = field.vector3Value.values; - spec.componentLabels = field.vector3Value.componentLabels; - spec.step = field.vector3Value.step; - spec.minValue = field.vector3Value.minValue; - spec.maxValue = field.vector3Value.maxValue; - spec.integerMode = field.vector3Value.integerMode; - spec.readOnly = field.readOnly; - return spec; + return BuildVectorFieldSpec(field, field.vector3Value); } UIEditorVector4FieldSpec BuildVector4FieldSpec(const UIEditorPropertyGridField& field) { - UIEditorVector4FieldSpec spec = {}; - spec.fieldId = field.fieldId; - spec.label = field.label; - spec.values = field.vector4Value.values; - spec.componentLabels = field.vector4Value.componentLabels; - spec.step = field.vector4Value.step; - spec.minValue = field.vector4Value.minValue; - spec.maxValue = field.vector4Value.maxValue; - spec.integerMode = field.vector4Value.integerMode; - spec.readOnly = field.readOnly; - return spec; + return BuildVectorFieldSpec(field, field.vector4Value); } UIEditorPropertyGridFieldRects ResolveFieldRects( @@ -587,34 +574,39 @@ UIEditorTextFieldHitTargetKind ResolveTextHoveredTarget( : UIEditorTextFieldHitTargetKind::Row; } -UIEditorVector2FieldHitTargetKind ResolveVector2HoveredTarget( +template +HitTargetKind ResolveVectorHoveredTarget( const UIEditorPropertyGridState& state, const UIEditorPropertyGridField& field) { if (state.hoveredFieldId != field.fieldId) { - return UIEditorVector2FieldHitTargetKind::None; + return HitTargetKind::None; } - return UIEditorVector2FieldHitTargetKind::Row; + return HitTargetKind::Row; +} + +UIEditorVector2FieldHitTargetKind ResolveVector2HoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field) { + return ResolveVectorHoveredTarget( + state, + field); } UIEditorVector3FieldHitTargetKind ResolveVector3HoveredTarget( const UIEditorPropertyGridState& state, const UIEditorPropertyGridField& field) { - if (state.hoveredFieldId != field.fieldId) { - return UIEditorVector3FieldHitTargetKind::None; - } - - return UIEditorVector3FieldHitTargetKind::Row; + return ResolveVectorHoveredTarget( + state, + field); } UIEditorVector4FieldHitTargetKind ResolveVector4HoveredTarget( const UIEditorPropertyGridState& state, const UIEditorPropertyGridField& field) { - if (state.hoveredFieldId != field.fieldId) { - return UIEditorVector4FieldHitTargetKind::None; - } - - return UIEditorVector4FieldHitTargetKind::Row; + return ResolveVectorHoveredTarget( + state, + field); } std::vector BuildEnumPopupItems( @@ -743,29 +735,45 @@ bool BuildEnumPopupRuntime( return true; } -std::string FormatVector2ValueText(const UIEditorPropertyGridField& field) { - const UIEditorVector2FieldSpec spec = BuildVector2FieldSpec(field); - return FormatUIEditorVector2FieldComponentValue(spec, 0u) + ", " + - FormatUIEditorVector2FieldComponentValue(spec, 1u); -} - std::string FormatColorValueText(const UIEditorPropertyGridField& field) { return FormatUIEditorColorFieldHexText(BuildColorFieldSpec(field)); } +template +std::string FormatVectorValueText( + const UIEditorPropertyGridField& field, + BuildSpecFn&& buildSpec, + FormatComponentFn&& formatComponent) { + const Spec spec = buildSpec(field); + std::string text = {}; + for (std::size_t componentIndex = 0u; componentIndex < spec.values.size(); ++componentIndex) { + if (!text.empty()) { + text += ", "; + } + text += formatComponent(spec, componentIndex); + } + return text; +} + +std::string FormatVector2ValueText(const UIEditorPropertyGridField& field) { + return FormatVectorValueText( + field, + BuildVector2FieldSpec, + FormatUIEditorVector2FieldComponentValue); +} + std::string FormatVector3ValueText(const UIEditorPropertyGridField& field) { - const UIEditorVector3FieldSpec spec = BuildVector3FieldSpec(field); - return FormatUIEditorVector3FieldComponentValue(spec, 0u) + ", " + - FormatUIEditorVector3FieldComponentValue(spec, 1u) + ", " + - FormatUIEditorVector3FieldComponentValue(spec, 2u); + return FormatVectorValueText( + field, + BuildVector3FieldSpec, + FormatUIEditorVector3FieldComponentValue); } std::string FormatVector4ValueText(const UIEditorPropertyGridField& field) { - const UIEditorVector4FieldSpec spec = BuildVector4FieldSpec(field); - return FormatUIEditorVector4FieldComponentValue(spec, 0u) + ", " + - FormatUIEditorVector4FieldComponentValue(spec, 1u) + ", " + - FormatUIEditorVector4FieldComponentValue(spec, 2u) + ", " + - FormatUIEditorVector4FieldComponentValue(spec, 3u); + return FormatVectorValueText( + field, + BuildVector4FieldSpec, + FormatUIEditorVector4FieldComponentValue); } } // namespace XCEngine::UI::Editor::Widgets::Internal diff --git a/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp b/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp index a63fabcf..25bfb823 100644 --- a/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp +++ b/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp @@ -920,6 +920,39 @@ bool BeginFieldEdit( return true; } +template +bool CommitVectorFieldEdit( + UIEditorPropertyGridInteractionState& state, + ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, + UIEditorPropertyGridField& field, + Values& values, + BuildSpecFn&& buildSpec, + TryParseFn&& tryParseValue, + NormalizeFn&& normalizeValue, + UIEditorPropertyGridInteractionResult& result) { + const std::size_t componentIndex = + state.editableFieldSession.activeComponentIndex; + Spec spec = buildSpec(field); + if (componentIndex >= spec.values.size()) { + result.editCommitRejected = true; + result.consumed = true; + return false; + } + + double parsedValue = spec.values[componentIndex]; + if (!tryParseValue(spec, propertyEditModel.GetStagedValue(), parsedValue)) { + result.editCommitRejected = true; + result.consumed = true; + return false; + } + + values[componentIndex] = normalizeValue(spec, parsedValue); + result.committedFieldId = field.fieldId; + result.committedValue = ResolveUIEditorPropertyGridFieldValueText(field); + SetChangedValueResult(field, result); + return true; +} + bool CommitActiveEdit( UIEditorPropertyGridInteractionState& state, ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, @@ -969,89 +1002,80 @@ bool CommitActiveEdit( result.committedValue = field->valueText; SetChangedValueResult(*field, result); } else if (field->kind == UIEditorPropertyGridFieldKind::Vector2) { - const std::size_t componentIndex = - state.editableFieldSession.activeComponentIndex; - Widgets::UIEditorVector2FieldSpec spec = - Widgets::Internal::BuildVector2FieldSpec(*field); - if (componentIndex >= spec.values.size()) { - result.editCommitRejected = true; - result.consumed = true; + if (!CommitVectorFieldEdit( + state, + propertyEditModel, + *field, + field->vector2Value.values, + [](const UIEditorPropertyGridField& source) { + return Widgets::Internal::BuildVector2FieldSpec(source); + }, + [](const Widgets::UIEditorVector2FieldSpec& spec, + std::string_view text, + double& outValue) { + return Widgets::TryParseUIEditorVector2FieldComponentValue( + spec, + text, + outValue); + }, + [](const Widgets::UIEditorVector2FieldSpec& spec, double value) { + return Widgets::NormalizeUIEditorVector2FieldComponentValue( + spec, + value); + }, + result)) { return false; } - - double parsedValue = spec.values[componentIndex]; - if (!Widgets::TryParseUIEditorVector2FieldComponentValue( - spec, - propertyEditModel.GetStagedValue(), - parsedValue)) { - result.editCommitRejected = true; - result.consumed = true; - return false; - } - - field->vector2Value.values[componentIndex] = - Widgets::NormalizeUIEditorVector2FieldComponentValue( - spec, - parsedValue); - result.committedFieldId = field->fieldId; - result.committedValue = ResolveUIEditorPropertyGridFieldValueText(*field); - SetChangedValueResult(*field, result); } else if (field->kind == UIEditorPropertyGridFieldKind::Vector3) { - const std::size_t componentIndex = - state.editableFieldSession.activeComponentIndex; - Widgets::UIEditorVector3FieldSpec spec = - Widgets::Internal::BuildVector3FieldSpec(*field); - if (componentIndex >= spec.values.size()) { - result.editCommitRejected = true; - result.consumed = true; + if (!CommitVectorFieldEdit( + state, + propertyEditModel, + *field, + field->vector3Value.values, + [](const UIEditorPropertyGridField& source) { + return Widgets::Internal::BuildVector3FieldSpec(source); + }, + [](const Widgets::UIEditorVector3FieldSpec& spec, + std::string_view text, + double& outValue) { + return Widgets::TryParseUIEditorVector3FieldComponentValue( + spec, + text, + outValue); + }, + [](const Widgets::UIEditorVector3FieldSpec& spec, double value) { + return Widgets::NormalizeUIEditorVector3FieldComponentValue( + spec, + value); + }, + result)) { return false; } - - double parsedValue = spec.values[componentIndex]; - if (!Widgets::TryParseUIEditorVector3FieldComponentValue( - spec, - propertyEditModel.GetStagedValue(), - parsedValue)) { - result.editCommitRejected = true; - result.consumed = true; - return false; - } - - field->vector3Value.values[componentIndex] = - Widgets::NormalizeUIEditorVector3FieldComponentValue( - spec, - parsedValue); - result.committedFieldId = field->fieldId; - result.committedValue = ResolveUIEditorPropertyGridFieldValueText(*field); - SetChangedValueResult(*field, result); } else if (field->kind == UIEditorPropertyGridFieldKind::Vector4) { - const std::size_t componentIndex = - state.editableFieldSession.activeComponentIndex; - Widgets::UIEditorVector4FieldSpec spec = - Widgets::Internal::BuildVector4FieldSpec(*field); - if (componentIndex >= spec.values.size()) { - result.editCommitRejected = true; - result.consumed = true; + if (!CommitVectorFieldEdit( + state, + propertyEditModel, + *field, + field->vector4Value.values, + [](const UIEditorPropertyGridField& source) { + return Widgets::Internal::BuildVector4FieldSpec(source); + }, + [](const Widgets::UIEditorVector4FieldSpec& spec, + std::string_view text, + double& outValue) { + return Widgets::TryParseUIEditorVector4FieldComponentValue( + spec, + text, + outValue); + }, + [](const Widgets::UIEditorVector4FieldSpec& spec, double value) { + return Widgets::NormalizeUIEditorVector4FieldComponentValue( + spec, + value); + }, + result)) { return false; } - - double parsedValue = spec.values[componentIndex]; - if (!Widgets::TryParseUIEditorVector4FieldComponentValue( - spec, - propertyEditModel.GetStagedValue(), - parsedValue)) { - result.editCommitRejected = true; - result.consumed = true; - return false; - } - - field->vector4Value.values[componentIndex] = - Widgets::NormalizeUIEditorVector4FieldComponentValue( - spec, - parsedValue); - result.committedFieldId = field->fieldId; - result.committedValue = ResolveUIEditorPropertyGridFieldValueText(*field); - SetChangedValueResult(*field, result); } propertyEditModel.CommitEdit(); @@ -1198,6 +1222,14 @@ void PruneStateEntries( visualStates.end()); } +template +bool HasVectorHoverStateChanged( + const FieldStateType& before, + const FieldStateType& after) { + return before.hoveredTarget != after.hoveredTarget || + before.hoveredComponentIndex != after.hoveredComponentIndex; +} + template bool HasMeaningfulResult( const typename Traits::InteractionResult& result, @@ -2317,11 +2349,11 @@ bool ProcessColorFieldEvent( namespace XCEngine::UI::Editor::Widgets::Internal { -const UIEditorPropertyGridVector2FieldVisualState* FindVector2FieldVisualState( - const UIEditorPropertyGridState& state, +template +const Entry* FindVectorFieldVisualState( + const std::vector& visualStates, std::string_view fieldId) { - for (const UIEditorPropertyGridVector2FieldVisualState& entry : - state.vector2FieldStates) { + for (const Entry& entry : visualStates) { if (entry.fieldId == fieldId) { return &entry; } @@ -2330,30 +2362,22 @@ const UIEditorPropertyGridVector2FieldVisualState* FindVector2FieldVisualState( return nullptr; } +const UIEditorPropertyGridVector2FieldVisualState* FindVector2FieldVisualState( + const UIEditorPropertyGridState& state, + std::string_view fieldId) { + return FindVectorFieldVisualState(state.vector2FieldStates, fieldId); +} + const UIEditorPropertyGridVector3FieldVisualState* FindVector3FieldVisualState( const UIEditorPropertyGridState& state, std::string_view fieldId) { - for (const UIEditorPropertyGridVector3FieldVisualState& entry : - state.vector3FieldStates) { - if (entry.fieldId == fieldId) { - return &entry; - } - } - - return nullptr; + return FindVectorFieldVisualState(state.vector3FieldStates, fieldId); } const UIEditorPropertyGridVector4FieldVisualState* FindVector4FieldVisualState( const UIEditorPropertyGridState& state, std::string_view fieldId) { - for (const UIEditorPropertyGridVector4FieldVisualState& entry : - state.vector4FieldStates) { - if (entry.fieldId == fieldId) { - return &entry; - } - } - - return nullptr; + return FindVectorFieldVisualState(state.vector4FieldStates, fieldId); } } // namespace XCEngine::UI::Editor::Widgets::Internal @@ -2479,6 +2503,14 @@ void PruneStateEntries( visualStates.end()); } +template +bool HasVectorHoverStateChanged( + const FieldStateType& before, + const FieldStateType& after) { + return before.hoveredTarget != after.hoveredTarget || + before.hoveredComponentIndex != after.hoveredComponentIndex; +} + template bool HasMeaningfulResult( const typename Traits::InteractionResult& result, @@ -2495,7 +2527,7 @@ bool HasMeaningfulResult( result.editCommitted || result.editCommitRejected || result.editCanceled || - Traits::HasHoverStateChanged( + HasVectorHoverStateChanged( previousFieldState, Traits::FieldState(interactionState)) || result.hitTarget.kind != Traits::kNoneHitTargetKind || @@ -2727,13 +2759,6 @@ struct Vector2Traits { inputEvents, metrics); } - - static bool HasHoverStateChanged( - const FieldStateType& before, - const FieldStateType& after) { - return before.hoveredTarget != after.hoveredTarget || - before.hoveredComponentIndex != after.hoveredComponentIndex; - } }; struct Vector3Traits { @@ -2791,13 +2816,6 @@ struct Vector3Traits { inputEvents, metrics); } - - static bool HasHoverStateChanged( - const FieldStateType& before, - const FieldStateType& after) { - return before.hoveredTarget != after.hoveredTarget || - before.hoveredComponentIndex != after.hoveredComponentIndex; - } }; struct Vector4Traits { @@ -2855,13 +2873,6 @@ struct Vector4Traits { inputEvents, metrics); } - - static bool HasHoverStateChanged( - const FieldStateType& before, - const FieldStateType& after) { - return before.hoveredTarget != after.hoveredTarget || - before.hoveredComponentIndex != after.hoveredComponentIndex; - } }; template <> diff --git a/new_editor/src/Fields/UIEditorVector2Field.cpp b/new_editor/src/Fields/UIEditorVector2Field.cpp index f97accba..a3822abb 100644 --- a/new_editor/src/Fields/UIEditorVector2Field.cpp +++ b/new_editor/src/Fields/UIEditorVector2Field.cpp @@ -1,251 +1,91 @@ #include -#include -#include -#include - -#include +#include "UIEditorVectorFieldShared.h" namespace XCEngine::UI::Editor::Widgets { namespace { -using ::XCEngine::UI::UIDrawList; -using ::XCEngine::UI::UIPoint; -using ::XCEngine::UI::UIRect; +struct Vector2FieldTraits { + using Spec = UIEditorVector2FieldSpec; + using State = UIEditorVector2FieldState; + using Metrics = UIEditorVector2FieldMetrics; + using Palette = UIEditorVector2FieldPalette; + using Layout = UIEditorVector2FieldLayout; + using HitTarget = UIEditorVector2FieldHitTarget; + using HitTargetKind = UIEditorVector2FieldHitTargetKind; -float ClampNonNegative(float value) { - return (std::max)(0.0f, value); -} + static constexpr std::size_t kComponentCount = 2u; + static constexpr std::size_t kInvalidComponentIndex = + UIEditorVector2FieldInvalidComponentIndex; + static constexpr HitTargetKind kNoneHitTargetKind = + UIEditorVector2FieldHitTargetKind::None; + static constexpr HitTargetKind kRowHitTargetKind = + UIEditorVector2FieldHitTargetKind::Row; + static constexpr HitTargetKind kComponentHitTargetKind = + UIEditorVector2FieldHitTargetKind::Component; -UIEditorVector2FieldMetrics ResolveMetrics(const UIEditorVector2FieldMetrics& metrics) { - const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); - - UIEditorVector2FieldMetrics resolved = metrics; - if (AreUIEditorFieldMetricsEqual(metrics.controlTrailingInset, 8.0f)) { - resolved.controlTrailingInset = tokens.controlTrailingInset; - } - if (AreUIEditorFieldMetricsEqual(metrics.componentMinWidth, 72.0f)) { - resolved.componentMinWidth = tokens.vectorComponentMinWidth; - } - if (AreUIEditorFieldMetricsEqual(metrics.componentPrefixWidth, 9.0f)) { - resolved.componentPrefixWidth = tokens.vectorPrefixWidth; - } - if (AreUIEditorFieldMetricsEqual(metrics.componentLabelGap, 4.0f)) { - resolved.componentLabelGap = tokens.vectorPrefixGap; + static const ::XCEngine::UI::UIColor& AxisColor( + const Palette& palette, + std::size_t componentIndex) { + return componentIndex == 0u ? palette.axisXColor : palette.axisYColor; } - return resolved; -} - -UIEditorVector2FieldPalette ResolvePalette(const UIEditorVector2FieldPalette& palette) { - const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); - - UIEditorVector2FieldPalette resolved = palette; - if (AreUIEditorFieldColorsEqual(palette.componentColor, ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f))) { - resolved.componentColor = tokens.controlColor; + static ::XCEngine::UI::UIColor& AxisColor( + Palette& palette, + std::size_t componentIndex) { + return componentIndex == 0u ? palette.axisXColor : palette.axisYColor; } - if (AreUIEditorFieldColorsEqual( - palette.componentHoverColor, - ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f))) { - resolved.componentHoverColor = tokens.controlHoverColor; - } - if (AreUIEditorFieldColorsEqual( - palette.componentEditingColor, - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f))) { - resolved.componentEditingColor = tokens.controlEditingColor; - } - if (AreUIEditorFieldColorsEqual(palette.readOnlyColor, ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f))) { - resolved.readOnlyColor = tokens.controlReadOnlyColor; - } - if (AreUIEditorFieldColorsEqual( - palette.componentBorderColor, - ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f))) { - resolved.componentBorderColor = tokens.controlBorderColor; - } - if (AreUIEditorFieldColorsEqual( - palette.componentFocusedBorderColor, - ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f))) { - resolved.componentFocusedBorderColor = tokens.controlFocusedBorderColor; - } - if (AreUIEditorFieldColorsEqual(palette.prefixColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { - resolved.prefixColor = tokens.prefixColor; - } - if (AreUIEditorFieldColorsEqual( - palette.prefixBorderColor, - ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { - resolved.prefixBorderColor = tokens.prefixBorderColor; - } - if (AreUIEditorFieldColorsEqual(palette.labelColor, ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f))) { - resolved.labelColor = tokens.labelColor; - } - if (AreUIEditorFieldColorsEqual(palette.valueColor, ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f))) { - resolved.valueColor = tokens.valueColor; - } - if (AreUIEditorFieldColorsEqual( - palette.readOnlyValueColor, - ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f))) { - resolved.readOnlyValueColor = tokens.readOnlyValueColor; - } - if (AreUIEditorFieldColorsEqual(palette.axisXColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { - resolved.axisXColor = tokens.axisXColor; - } - if (AreUIEditorFieldColorsEqual(palette.axisYColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { - resolved.axisYColor = tokens.axisYColor; - } - - return resolved; -} - -float ApproximateTextWidth(float fontSize, std::size_t characterCount) { - return fontSize * 0.55f * static_cast(characterCount); -} - -UIEditorNumberFieldSpec BuildComponentNumberSpec( - const UIEditorVector2FieldSpec& spec, - std::size_t componentIndex) { - UIEditorNumberFieldSpec numberSpec = {}; - numberSpec.fieldId = spec.fieldId + "." + std::to_string(componentIndex); - numberSpec.label.clear(); - numberSpec.value = componentIndex < spec.values.size() ? spec.values[componentIndex] : 0.0; - numberSpec.step = spec.step; - numberSpec.minValue = spec.minValue; - numberSpec.maxValue = spec.maxValue; - numberSpec.integerMode = spec.integerMode; - numberSpec.readOnly = spec.readOnly; - return numberSpec; -} - -::XCEngine::UI::UIColor ResolveRowFillColor( - const UIEditorVector2FieldState& state, - const UIEditorVector2FieldPalette& palette) { - if (state.activeTarget != UIEditorVector2FieldHitTargetKind::None) { - return palette.rowActiveColor; - } - if (state.hoveredTarget != UIEditorVector2FieldHitTargetKind::None) { - return palette.rowHoverColor; - } - return palette.surfaceColor; -} - -::XCEngine::UI::UIColor ResolveComponentFillColor( - const UIEditorVector2FieldSpec& spec, - const UIEditorVector2FieldState& state, - const UIEditorVector2FieldPalette& palette, - std::size_t componentIndex) { - if (spec.readOnly) { - return palette.readOnlyColor; - } - if (state.editing && state.selectedComponentIndex == componentIndex) { - return palette.componentEditingColor; - } - if (state.hoveredComponentIndex == componentIndex) { - return palette.componentHoverColor; - } - return palette.componentColor; -} - -::XCEngine::UI::UIColor ResolveComponentBorderColor( - const UIEditorVector2FieldState& state, - const UIEditorVector2FieldPalette& palette, - std::size_t componentIndex) { - if (state.editing && state.selectedComponentIndex == componentIndex) { - return palette.componentFocusedBorderColor; - } - return palette.componentBorderColor; -} +}; } // namespace -bool IsUIEditorVector2FieldPointInside( - const UIRect& rect, - const UIPoint& point) { - return point.x >= rect.x && - point.x <= rect.x + rect.width && - point.y >= rect.y && - point.y <= rect.y + rect.height; +bool IsUIEditorVector2FieldPointInside(const UIRect& rect, const UIPoint& point) { + return Internal::VectorFieldWidgetShared::IsPointInside(rect, point); } double NormalizeUIEditorVector2FieldComponentValue( const UIEditorVector2FieldSpec& spec, double value) { - return NormalizeUIEditorNumberFieldValue(BuildComponentNumberSpec(spec, 0u), value); + return Internal::VectorFieldWidgetShared::NormalizeComponentValue( + spec, + value); } bool TryParseUIEditorVector2FieldComponentValue( const UIEditorVector2FieldSpec& spec, std::string_view text, double& outValue) { - return TryParseUIEditorNumberFieldValue(BuildComponentNumberSpec(spec, 0u), text, outValue); + return Internal::VectorFieldWidgetShared::TryParseComponentValue( + spec, + text, + outValue); } std::string FormatUIEditorVector2FieldComponentValue( const UIEditorVector2FieldSpec& spec, std::size_t componentIndex) { - return FormatUIEditorNumberFieldValue(BuildComponentNumberSpec(spec, componentIndex)); + return Internal::VectorFieldWidgetShared::FormatComponentValue( + spec, + componentIndex); } UIEditorVector2FieldLayout BuildUIEditorVector2FieldLayout( const UIRect& bounds, - const UIEditorVector2FieldSpec&, + const UIEditorVector2FieldSpec& spec, const UIEditorVector2FieldMetrics& metrics) { - const UIEditorVector2FieldMetrics resolvedMetrics = ResolveMetrics(metrics); - const float requiredControlWidth = - resolvedMetrics.componentMinWidth * 2.0f + resolvedMetrics.componentGap; - const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout( + return Internal::VectorFieldWidgetShared::BuildLayout( bounds, - requiredControlWidth, - UIEditorFieldRowLayoutMetrics { - resolvedMetrics.rowHeight, - resolvedMetrics.horizontalPadding, - resolvedMetrics.labelControlGap, - resolvedMetrics.controlColumnStart, - resolvedMetrics.sharedControlColumnMinWidth, - resolvedMetrics.controlTrailingInset, - resolvedMetrics.controlInsetY, - }); - const float componentWidth = - ClampNonNegative((hostLayout.controlRect.width - resolvedMetrics.componentGap) / 2.0f); - - UIEditorVector2FieldLayout layout = {}; - layout.bounds = hostLayout.bounds; - layout.labelRect = hostLayout.labelRect; - layout.controlRect = hostLayout.controlRect; - - for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { - const float componentX = - layout.controlRect.x + - (componentWidth + resolvedMetrics.componentGap) * static_cast(componentIndex); - const UIRect componentRect(componentX, layout.controlRect.y, componentWidth, layout.controlRect.height); - const float prefixWidth = (std::min)(resolvedMetrics.componentPrefixWidth, componentRect.width); - const float labelGap = ClampNonNegative(resolvedMetrics.componentLabelGap); - const float valueX = componentRect.x + prefixWidth + labelGap; - layout.componentRects[componentIndex] = componentRect; - layout.componentPrefixRects[componentIndex] = - UIRect(componentRect.x, componentRect.y, prefixWidth, componentRect.height); - layout.componentValueRects[componentIndex] = - UIRect( - valueX, - componentRect.y, - ClampNonNegative(componentRect.width - prefixWidth - labelGap), - componentRect.height); - } - - return layout; + spec, + metrics); } UIEditorVector2FieldHitTarget HitTestUIEditorVector2Field( const UIEditorVector2FieldLayout& layout, const UIPoint& point) { - for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { - if (IsUIEditorVector2FieldPointInside(layout.componentRects[componentIndex], point)) { - return { UIEditorVector2FieldHitTargetKind::Component, componentIndex }; - } - } - if (IsUIEditorVector2FieldPointInside(layout.bounds, point)) { - return { UIEditorVector2FieldHitTargetKind::Row, UIEditorVector2FieldInvalidComponentIndex }; - } - return {}; + return Internal::VectorFieldWidgetShared::HitTest( + layout, + point); } void AppendUIEditorVector2FieldBackground( @@ -255,33 +95,13 @@ void AppendUIEditorVector2FieldBackground( const UIEditorVector2FieldState& state, const UIEditorVector2FieldPalette& palette, const UIEditorVector2FieldMetrics& metrics) { - const UIEditorVector2FieldPalette resolvedPalette = ResolvePalette(palette); - const UIEditorVector2FieldMetrics resolvedMetrics = ResolveMetrics(metrics); - if (ResolveRowFillColor(state, resolvedPalette).a > 0.0f) { - drawList.AddFilledRect( - layout.bounds, - ResolveRowFillColor(state, resolvedPalette), - resolvedMetrics.cornerRounding); - } - if ((state.focused ? resolvedPalette.focusedBorderColor.a : resolvedPalette.borderColor.a) > 0.0f) { - drawList.AddRectOutline( - layout.bounds, - state.focused ? resolvedPalette.focusedBorderColor : resolvedPalette.borderColor, - state.focused ? resolvedMetrics.focusedBorderThickness : resolvedMetrics.borderThickness, - resolvedMetrics.cornerRounding); - } - - for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { - drawList.AddFilledRect( - layout.componentValueRects[componentIndex], - ResolveComponentFillColor(spec, state, resolvedPalette, componentIndex), - resolvedMetrics.componentRounding); - drawList.AddRectOutline( - layout.componentValueRects[componentIndex], - ResolveComponentBorderColor(state, resolvedPalette, componentIndex), - resolvedMetrics.borderThickness, - resolvedMetrics.componentRounding); - } + Internal::VectorFieldWidgetShared::AppendBackground( + drawList, + layout, + spec, + state, + palette, + metrics); } void AppendUIEditorVector2FieldForeground( @@ -291,69 +111,13 @@ void AppendUIEditorVector2FieldForeground( const UIEditorVector2FieldState& state, const UIEditorVector2FieldPalette& palette, const UIEditorVector2FieldMetrics& metrics) { - const UIEditorVector2FieldPalette resolvedPalette = ResolvePalette(palette); - const UIEditorVector2FieldMetrics resolvedMetrics = ResolveMetrics(metrics); - drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, resolvedMetrics.labelFontSize)); - drawList.AddText( - UIPoint( - layout.labelRect.x, - ResolveUIEditorTextTop(layout.labelRect, resolvedMetrics.labelFontSize, resolvedMetrics.labelTextInsetY)), - spec.label, - resolvedPalette.labelColor, - resolvedMetrics.labelFontSize); - drawList.PopClipRect(); - - for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { - drawList.PushClipRect( - ResolveUIEditorTextClipRect(layout.componentPrefixRects[componentIndex], resolvedMetrics.prefixFontSize)); - const std::string& componentLabel = spec.componentLabels[componentIndex]; - const float prefixTextX = - layout.componentPrefixRects[componentIndex].x + - ClampNonNegative( - (layout.componentPrefixRects[componentIndex].width - - ApproximateTextWidth(resolvedMetrics.prefixFontSize, componentLabel.size())) * - 0.5f) + - resolvedMetrics.prefixTextInsetX; - drawList.AddText( - UIPoint( - prefixTextX, - ResolveUIEditorTextTop( - layout.componentPrefixRects[componentIndex], - resolvedMetrics.prefixFontSize, - resolvedMetrics.prefixTextInsetY)), - componentLabel, - resolvedPalette.labelColor, - resolvedMetrics.prefixFontSize); - drawList.PopClipRect(); - - drawList.PushClipRect( - ResolveUIEditorTextClipRect(layout.componentValueRects[componentIndex], resolvedMetrics.valueFontSize)); - drawList.AddText( - UIPoint( - layout.componentValueRects[componentIndex].x + resolvedMetrics.valueTextInsetX, - ResolveUIEditorTextTop( - layout.componentValueRects[componentIndex], - resolvedMetrics.valueFontSize, - resolvedMetrics.valueTextInsetY)), - state.editing && state.selectedComponentIndex == componentIndex - ? state.displayTexts[componentIndex] - : FormatUIEditorVector2FieldComponentValue(spec, componentIndex), - spec.readOnly ? resolvedPalette.readOnlyValueColor : resolvedPalette.valueColor, - resolvedMetrics.valueFontSize); - if (state.editing && state.selectedComponentIndex == componentIndex) { - AppendUIEditorTextCaret( - drawList, - layout.componentValueRects[componentIndex], - state.displayTexts[componentIndex], - state.caretOffset, - state.caretBlinkStartNanoseconds, - resolvedPalette.valueColor, - resolvedMetrics.valueFontSize, - resolvedMetrics.valueTextInsetX, - resolvedMetrics.valueTextInsetY); - } - drawList.PopClipRect(); - } + Internal::VectorFieldWidgetShared::AppendForeground( + drawList, + layout, + spec, + state, + palette, + metrics); } void AppendUIEditorVector2Field( @@ -363,11 +127,13 @@ void AppendUIEditorVector2Field( const UIEditorVector2FieldState& state, const UIEditorVector2FieldPalette& palette, const UIEditorVector2FieldMetrics& metrics) { - const UIEditorVector2FieldPalette resolvedPalette = ResolvePalette(palette); - const UIEditorVector2FieldMetrics resolvedMetrics = ResolveMetrics(metrics); - const UIEditorVector2FieldLayout layout = BuildUIEditorVector2FieldLayout(bounds, spec, resolvedMetrics); - AppendUIEditorVector2FieldBackground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); - AppendUIEditorVector2FieldForeground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); + Internal::VectorFieldWidgetShared::AppendField( + drawList, + bounds, + spec, + state, + palette, + metrics); } } // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/src/Fields/UIEditorVector2FieldInteraction.cpp b/new_editor/src/Fields/UIEditorVector2FieldInteraction.cpp index 232cc251..5421807b 100644 --- a/new_editor/src/Fields/UIEditorVector2FieldInteraction.cpp +++ b/new_editor/src/Fields/UIEditorVector2FieldInteraction.cpp @@ -1,626 +1,55 @@ #include -#include - -#include -#include +#include "UIEditorVectorFieldInteractionShared.h" namespace XCEngine::UI::Editor { namespace { -using ::XCEngine::Input::KeyCode; -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIPointerButton; -using ::XCEngine::UI::Editor::Widgets::BuildUIEditorVector2FieldLayout; -using ::XCEngine::UI::Editor::Widgets::FormatUIEditorVector2FieldComponentValue; -using ::XCEngine::UI::Editor::Widgets::HitTestUIEditorVector2Field; -using ::XCEngine::UI::Editor::Widgets::IsUIEditorVector2FieldPointInside; -using ::XCEngine::UI::Editor::Widgets::NormalizeUIEditorVector2FieldComponentValue; -using ::XCEngine::UI::Editor::Widgets::TryParseUIEditorVector2FieldComponentValue; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector2FieldHitTarget; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector2FieldHitTargetKind; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector2FieldInvalidComponentIndex; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector2FieldLayout; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector2FieldMetrics; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector2FieldSpec; +struct Vector2FieldInteractionTraits { + using Spec = Widgets::UIEditorVector2FieldSpec; + using Metrics = Widgets::UIEditorVector2FieldMetrics; + using Layout = Widgets::UIEditorVector2FieldLayout; + using HitTarget = Widgets::UIEditorVector2FieldHitTarget; + using HitTargetKind = Widgets::UIEditorVector2FieldHitTargetKind; + using InteractionState = UIEditorVector2FieldInteractionState; + using InteractionResult = UIEditorVector2FieldInteractionResult; + using InteractionFrame = UIEditorVector2FieldInteractionFrame; -constexpr std::size_t kComponentCount = 2u; -constexpr std::size_t kLastComponentIndex = 1u; + static constexpr std::size_t kComponentCount = 2u; + static constexpr std::size_t kInvalidComponentIndex = + Widgets::UIEditorVector2FieldInvalidComponentIndex; + static constexpr HitTargetKind kNoneHitTargetKind = + Widgets::UIEditorVector2FieldHitTargetKind::None; + static constexpr HitTargetKind kRowHitTargetKind = + Widgets::UIEditorVector2FieldHitTargetKind::Row; + static constexpr HitTargetKind kComponentHitTargetKind = + Widgets::UIEditorVector2FieldHitTargetKind::Component; -bool IsPermittedCharacter( - const UIEditorVector2FieldSpec& spec, - std::uint32_t character) { - if (character >= static_cast('0') && - character <= static_cast('9')) { - return true; - } - if (character == static_cast('-') || - character == static_cast('+')) { - return true; - } - return !spec.integerMode && character == static_cast('.'); -} - -std::size_t ResolveSelectedComponentIndex( - const UIEditorVector2FieldInteractionState& state) { - return state.vector2FieldState.selectedComponentIndex == - UIEditorVector2FieldInvalidComponentIndex - ? 0u - : state.vector2FieldState.selectedComponentIndex; -} - -double ResolveDragSensitivity(const UIEditorVector2FieldSpec& spec) { - if (spec.step != 0.0) { - return spec.step; + static auto& FieldState(InteractionState& state) { + return state.vector2FieldState; } - return spec.integerMode ? 1.0 : 0.1; -} - -void SyncDisplayState( - UIEditorVector2FieldInteractionState& state, - const UIEditorVector2FieldSpec& spec, - const UIEditorVector2FieldLayout& layout) { - auto& fieldState = state.vector2FieldState; - const auto& session = state.session; - fieldState.focused = session.focused; - fieldState.editing = session.editing; - fieldState.caretBlinkStartNanoseconds = session.caretBlinkStartNanoseconds; - for (std::size_t componentIndex = 0u; componentIndex < kComponentCount; - ++componentIndex) { - fieldState.displayTexts[componentIndex] = - session.editing && - fieldState.selectedComponentIndex == componentIndex - ? session.textInputState.value - : FormatUIEditorVector2FieldComponentValue(spec, componentIndex); + static const auto& FieldState(const InteractionState& state) { + return state.vector2FieldState; } - if (session.editing && - fieldState.selectedComponentIndex != - UIEditorVector2FieldInvalidComponentIndex) { - fieldState.caretOffset = session.textInputState.caret; - } else if (fieldState.selectedComponentIndex != - UIEditorVector2FieldInvalidComponentIndex) { - fieldState.caretOffset = - fieldState.displayTexts[fieldState.selectedComponentIndex].size(); - } else { - fieldState.caretOffset = 0u; - } - if (!session.hasPointerPosition) { - fieldState.hoveredTarget = UIEditorVector2FieldHitTargetKind::None; - fieldState.hoveredComponentIndex = - UIEditorVector2FieldInvalidComponentIndex; - return; - } - - const UIEditorVector2FieldHitTarget hitTarget = - HitTestUIEditorVector2Field(layout, session.pointerPosition); - fieldState.hoveredTarget = hitTarget.kind; - fieldState.hoveredComponentIndex = hitTarget.componentIndex; -} - -bool SelectComponent( - UIEditorVector2FieldInteractionState& state, - std::size_t componentIndex, - UIEditorVector2FieldInteractionResult& result) { - if (componentIndex >= kComponentCount) { - return false; - } - - const std::size_t before = ResolveSelectedComponentIndex(state); - state.vector2FieldState.selectedComponentIndex = componentIndex; - state.session.activeComponentIndex = componentIndex; - result.selectionChanged = before != componentIndex; - result.selectedComponentIndex = componentIndex; - return true; -} - -bool MoveSelection( - UIEditorVector2FieldInteractionState& state, - int direction, - UIEditorVector2FieldInteractionResult& result) { - const std::size_t before = ResolveSelectedComponentIndex(state); - const std::size_t after = direction < 0 - ? (before == 0u ? 0u : before - 1u) - : (before >= kLastComponentIndex ? kLastComponentIndex : before + 1u); - state.vector2FieldState.selectedComponentIndex = after; - state.session.activeComponentIndex = after; - result.selectionChanged = before != after; - result.selectedComponentIndex = after; - result.consumed = true; - return true; -} - -bool BeginEdit( - UIEditorVector2FieldInteractionState& state, - const UIEditorVector2FieldSpec& spec, - std::size_t componentIndex, - bool clearText) { - if (spec.readOnly || componentIndex >= spec.values.size()) { - return false; - } - - state.vector2FieldState.selectedComponentIndex = componentIndex; - return BeginUIEditorEditableFieldEdit( - state.session, - spec.fieldId, - componentIndex, - FormatUIEditorVector2FieldComponentValue(spec, componentIndex), - clearText); -} - -bool CommitEdit( - UIEditorVector2FieldInteractionState& state, - UIEditorVector2FieldSpec& spec, - UIEditorVector2FieldInteractionResult& result) { - const std::size_t componentIndex = - state.vector2FieldState.selectedComponentIndex; - if (!state.session.editing || componentIndex >= spec.values.size()) { - return false; - } - - double parsedValue = spec.values[componentIndex]; - if (!TryParseUIEditorVector2FieldComponentValue( - spec, - state.session.textInputState.value, - parsedValue)) { - result.consumed = true; - result.editCommitRejected = true; - return false; - } - - result.valuesBefore = spec.values; - spec.values[componentIndex] = - NormalizeUIEditorVector2FieldComponentValue(spec, parsedValue); - result.valuesAfter = spec.values; - result.valueChanged = result.valuesBefore != result.valuesAfter; - result.editCommitted = true; - result.consumed = true; - result.changedComponentIndex = componentIndex; - result.selectedComponentIndex = componentIndex; - result.committedText = - FormatUIEditorVector2FieldComponentValue(spec, componentIndex); - CommitUIEditorEditableFieldEdit(state.session); - return true; -} - -bool CancelEdit( - UIEditorVector2FieldInteractionState& state, - const UIEditorVector2FieldSpec& spec, - UIEditorVector2FieldInteractionResult& result) { - const std::size_t componentIndex = - state.vector2FieldState.selectedComponentIndex; - if (!state.session.editing || componentIndex >= spec.values.size()) { - return false; - } - - CancelUIEditorEditableFieldEdit(state.session); - result.consumed = true; - result.editCanceled = true; - result.valuesBefore = spec.values; - result.valuesAfter = spec.values; - result.selectedComponentIndex = componentIndex; - return true; -} - -bool ApplyStep( - UIEditorVector2FieldInteractionState& state, - UIEditorVector2FieldSpec& spec, - double direction, - bool snapToEdge, - UIEditorVector2FieldInteractionResult& result) { - if (spec.readOnly) { - return false; - } - - const std::size_t componentIndex = ResolveSelectedComponentIndex(state); - state.vector2FieldState.selectedComponentIndex = componentIndex; - state.session.activeComponentIndex = componentIndex; - if (state.session.editing && !CommitEdit(state, spec, result)) { - return result.editCommitRejected; - } - - result.valuesBefore = spec.values; - if (snapToEdge) { - spec.values[componentIndex] = direction < 0.0 - ? NormalizeUIEditorVector2FieldComponentValue(spec, spec.minValue) - : NormalizeUIEditorVector2FieldComponentValue(spec, spec.maxValue); - } else { - const double step = spec.step == 0.0 ? 1.0 : spec.step; - spec.values[componentIndex] = - NormalizeUIEditorVector2FieldComponentValue( - spec, - spec.values[componentIndex] + step * direction); - result.stepDelta = step * direction; - } - - result.valuesAfter = spec.values; - result.stepApplied = true; - result.valueChanged = result.valuesBefore != result.valuesAfter; - result.changedComponentIndex = componentIndex; - result.selectedComponentIndex = componentIndex; - result.consumed = true; - return true; -} - -bool ApplyDrag( - UIEditorVector2FieldInteractionState& state, - UIEditorVector2FieldSpec& spec, - std::size_t componentIndex, - UIEditorVector2FieldInteractionResult& result) { - if (spec.readOnly || state.session.editing || - componentIndex >= spec.values.size()) { - return false; - } - - result.valuesBefore = spec.values; - spec.values[componentIndex] = - NormalizeUIEditorVector2FieldComponentValue( - spec, - state.session.dragStartValue + - ResolveUIEditorEditableFieldDragDelta( - state.session, - ResolveDragSensitivity(spec))); - result.valuesAfter = spec.values; - result.valueChanged = result.valuesBefore != result.valuesAfter; - result.changedComponentIndex = componentIndex; - result.selectedComponentIndex = componentIndex; - result.consumed = true; - return true; -} +}; } // namespace UIEditorVector2FieldInteractionFrame UpdateUIEditorVector2FieldInteraction( UIEditorVector2FieldInteractionState& state, - UIEditorVector2FieldSpec& spec, + Widgets::UIEditorVector2FieldSpec& spec, const ::XCEngine::UI::UIRect& bounds, - const std::vector& inputEvents, - const UIEditorVector2FieldMetrics& metrics) { - UIEditorVector2FieldLayout layout = BuildUIEditorVector2FieldLayout( - bounds, - spec, - metrics); - SyncDisplayState(state, spec, layout); - - UIEditorVector2FieldInteractionResult interactionResult = {}; - interactionResult.selectedComponentIndex = - state.vector2FieldState.selectedComponentIndex; - for (const UIInputEvent& event : inputEvents) { - UpdateUIEditorEditableFieldPointerPosition(state.session, event); - - UIEditorVector2FieldInteractionResult eventResult = {}; - eventResult.selectedComponentIndex = - state.vector2FieldState.selectedComponentIndex; - switch (event.type) { - case UIInputEventType::FocusGained: - eventResult.focusChanged = !state.session.focused; - state.session.focused = true; - if (state.vector2FieldState.selectedComponentIndex == - UIEditorVector2FieldInvalidComponentIndex) { - state.vector2FieldState.selectedComponentIndex = 0u; - } - state.session.activeComponentIndex = - state.vector2FieldState.selectedComponentIndex; - eventResult.selectedComponentIndex = - state.vector2FieldState.selectedComponentIndex; - break; - - case UIInputEventType::FocusLost: - eventResult.focusChanged = state.session.focused; - state.session.focused = false; - state.vector2FieldState.activeTarget = - UIEditorVector2FieldHitTargetKind::None; - state.vector2FieldState.activeComponentIndex = - UIEditorVector2FieldInvalidComponentIndex; - state.session.hasPointerPosition = false; - EndUIEditorEditableFieldDrag(state.session); - if (state.session.editing) { - CommitEdit(state, spec, eventResult); - if (eventResult.editCommitRejected) { - CancelEdit(state, spec, eventResult); - } - } - break; - - case UIInputEventType::PointerMove: - TryActivateUIEditorEditableFieldDrag(state.session, event); - if (state.session.dragActive && - state.vector2FieldState.activeTarget == - UIEditorVector2FieldHitTargetKind::Component && - state.vector2FieldState.activeComponentIndex != - UIEditorVector2FieldInvalidComponentIndex) { - ApplyDrag( - state, - spec, - state.vector2FieldState.activeComponentIndex, - eventResult); - } - break; - - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerLeave: - break; - - case UIInputEventType::PointerButtonDown: { - const UIEditorVector2FieldHitTarget hitTarget = - state.session.hasPointerPosition - ? HitTestUIEditorVector2Field( - layout, - state.session.pointerPosition) - : UIEditorVector2FieldHitTarget {}; - eventResult.hitTarget = hitTarget; - - if (event.pointerButton != UIPointerButton::Left) { - break; - } - - const bool insideField = - state.session.hasPointerPosition && - IsUIEditorVector2FieldPointInside( - layout.bounds, - state.session.pointerPosition); - if (insideField) { - eventResult.focusChanged = !state.session.focused; - state.session.focused = true; - state.vector2FieldState.activeTarget = - hitTarget.kind == UIEditorVector2FieldHitTargetKind::None - ? UIEditorVector2FieldHitTargetKind::Row - : hitTarget.kind; - state.vector2FieldState.activeComponentIndex = - hitTarget.componentIndex; - if (hitTarget.kind == - UIEditorVector2FieldHitTargetKind::Component && - !state.session.editing) { - ArmUIEditorEditableFieldDrag( - state.session, - spec.fieldId, - hitTarget.componentIndex, - hitTarget.componentIndex < spec.values.size() - ? spec.values[hitTarget.componentIndex] - : 0.0); - SelectComponent(state, hitTarget.componentIndex, eventResult); - } else { - EndUIEditorEditableFieldDrag(state.session); - if (state.vector2FieldState.selectedComponentIndex == - UIEditorVector2FieldInvalidComponentIndex) { - state.vector2FieldState.selectedComponentIndex = 0u; - state.session.activeComponentIndex = 0u; - eventResult.selectedComponentIndex = 0u; - } - } - eventResult.consumed = true; - } else { - if (state.session.editing) { - CommitEdit(state, spec, eventResult); - if (!eventResult.editCommitRejected) { - eventResult.focusChanged = state.session.focused; - state.session.focused = false; - } - } else if (state.session.focused) { - eventResult.focusChanged = true; - state.session.focused = false; - } - state.vector2FieldState.activeTarget = - UIEditorVector2FieldHitTargetKind::None; - state.vector2FieldState.activeComponentIndex = - UIEditorVector2FieldInvalidComponentIndex; - EndUIEditorEditableFieldDrag(state.session); - } - break; - } - - case UIInputEventType::PointerButtonUp: { - const UIEditorVector2FieldHitTarget hitTarget = - state.session.hasPointerPosition - ? HitTestUIEditorVector2Field( - layout, - state.session.pointerPosition) - : UIEditorVector2FieldHitTarget {}; - eventResult.hitTarget = hitTarget; - - if (event.pointerButton == UIPointerButton::Left) { - const UIEditorVector2FieldHitTargetKind activeTarget = - state.vector2FieldState.activeTarget; - const std::size_t activeComponentIndex = - state.vector2FieldState.activeComponentIndex; - state.vector2FieldState.activeTarget = - UIEditorVector2FieldHitTargetKind::None; - state.vector2FieldState.activeComponentIndex = - UIEditorVector2FieldInvalidComponentIndex; - - if (activeTarget == UIEditorVector2FieldHitTargetKind::Component && - hitTarget.kind == UIEditorVector2FieldHitTargetKind::Component && - activeComponentIndex == hitTarget.componentIndex) { - SelectComponent(state, hitTarget.componentIndex, eventResult); - if (state.session.dragActive) { - eventResult.consumed = true; - } else if (!state.session.editing && - IsUIEditorEditableFieldDoubleClick( - state.session, - spec.fieldId, - event, - hitTarget.componentIndex)) { - eventResult.editStarted = - BeginEdit(state, spec, hitTarget.componentIndex, false); - eventResult.consumed = true; - } else { - RecordUIEditorEditableFieldClick( - state.session, - spec.fieldId, - event, - hitTarget.componentIndex); - eventResult.consumed = true; - } - } else if (hitTarget.kind == UIEditorVector2FieldHitTargetKind::Row) { - eventResult.consumed = true; - } - - EndUIEditorEditableFieldDrag(state.session); - } - break; - } - - case UIInputEventType::KeyDown: - if (!state.session.focused) { - break; - } - - if (state.session.editing) { - if (event.keyCode == static_cast(KeyCode::Escape)) { - CancelEdit(state, spec, eventResult); - break; - } - if (event.keyCode == static_cast(KeyCode::Tab)) { - if (CommitEdit(state, spec, eventResult)) { - MoveSelection( - state, - event.modifiers.shift ? -1 : 1, - eventResult); - } - eventResult.consumed = true; - break; - } - - const auto textResult = HandleUIEditorEditableFieldKeyDown( - state.session, - event.keyCode, - event.modifiers); - if (textResult.handled) { - eventResult.consumed = true; - eventResult.selectedComponentIndex = - state.vector2FieldState.selectedComponentIndex; - if (textResult.submitRequested) { - CommitEdit(state, spec, eventResult); - } - } - } else { - switch (static_cast(event.keyCode)) { - case KeyCode::Left: - MoveSelection(state, -1, eventResult); - break; - case KeyCode::Right: - case KeyCode::Tab: - MoveSelection( - state, - static_cast(event.keyCode) == KeyCode::Tab && - event.modifiers.shift - ? -1 - : 1, - eventResult); - break; - case KeyCode::Up: - ApplyStep(state, spec, 1.0, false, eventResult); - break; - case KeyCode::Down: - ApplyStep(state, spec, -1.0, false, eventResult); - break; - case KeyCode::Home: - ApplyStep(state, spec, -1.0, true, eventResult); - break; - case KeyCode::End: - ApplyStep(state, spec, 1.0, true, eventResult); - break; - case KeyCode::Enter: - eventResult.selectedComponentIndex = - ResolveSelectedComponentIndex(state); - eventResult.editStarted = BeginEdit( - state, - spec, - eventResult.selectedComponentIndex, - false); - eventResult.consumed = eventResult.editStarted; - break; - default: - break; - } - } - break; - - case UIInputEventType::Character: - if (!state.session.focused || - spec.readOnly || - event.modifiers.control || - event.modifiers.alt || - event.modifiers.super || - !IsPermittedCharacter(spec, event.character)) { - break; - } - - if (state.vector2FieldState.selectedComponentIndex == - UIEditorVector2FieldInvalidComponentIndex) { - state.vector2FieldState.selectedComponentIndex = 0u; - } - state.session.activeComponentIndex = - state.vector2FieldState.selectedComponentIndex; - eventResult.selectedComponentIndex = - state.vector2FieldState.selectedComponentIndex; - if (!state.session.editing) { - eventResult.editStarted = BeginEdit( - state, - spec, - state.vector2FieldState.selectedComponentIndex, - true); - } - if (InsertUIEditorEditableFieldCharacter( - state.session, - event.character)) { - eventResult.consumed = true; - } - break; - - default: - break; - } - - layout = BuildUIEditorVector2FieldLayout(bounds, spec, metrics); - SyncDisplayState(state, spec, layout); - if (eventResult.hitTarget.kind == UIEditorVector2FieldHitTargetKind::None && - state.session.hasPointerPosition) { - eventResult.hitTarget = HitTestUIEditorVector2Field( - layout, - state.session.pointerPosition); - } - if (eventResult.selectedComponentIndex == - UIEditorVector2FieldInvalidComponentIndex) { - eventResult.selectedComponentIndex = - state.vector2FieldState.selectedComponentIndex; - } - - if (eventResult.consumed || - eventResult.focusChanged || - eventResult.valueChanged || - eventResult.stepApplied || - eventResult.selectionChanged || - eventResult.editStarted || - eventResult.editCommitted || - eventResult.editCommitRejected || - eventResult.editCanceled || - eventResult.hitTarget.kind != - UIEditorVector2FieldHitTargetKind::None) { - interactionResult = std::move(eventResult); - } - } - - layout = BuildUIEditorVector2FieldLayout(bounds, spec, metrics); - SyncDisplayState(state, spec, layout); - if (interactionResult.hitTarget.kind == UIEditorVector2FieldHitTargetKind::None && - state.session.hasPointerPosition) { - interactionResult.hitTarget = HitTestUIEditorVector2Field( - layout, - state.session.pointerPosition); - } - if (interactionResult.selectedComponentIndex == - UIEditorVector2FieldInvalidComponentIndex) { - interactionResult.selectedComponentIndex = - state.vector2FieldState.selectedComponentIndex; - } - - return { - std::move(layout), - std::move(interactionResult) - }; + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + const Widgets::UIEditorVector2FieldMetrics& metrics) { + return Internal::VectorFieldInteractionShared::UpdateInteraction< + Vector2FieldInteractionTraits>( + state, + spec, + bounds, + inputEvents, + metrics); } } // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Fields/UIEditorVector3Field.cpp b/new_editor/src/Fields/UIEditorVector3Field.cpp index 7756786b..289756b3 100644 --- a/new_editor/src/Fields/UIEditorVector3Field.cpp +++ b/new_editor/src/Fields/UIEditorVector3Field.cpp @@ -1,254 +1,105 @@ #include -#include -#include -#include - -#include +#include "UIEditorVectorFieldShared.h" namespace XCEngine::UI::Editor::Widgets { namespace { -using ::XCEngine::UI::UIDrawList; -using ::XCEngine::UI::UIPoint; -using ::XCEngine::UI::UIRect; +struct Vector3FieldTraits { + using Spec = UIEditorVector3FieldSpec; + using State = UIEditorVector3FieldState; + using Metrics = UIEditorVector3FieldMetrics; + using Palette = UIEditorVector3FieldPalette; + using Layout = UIEditorVector3FieldLayout; + using HitTarget = UIEditorVector3FieldHitTarget; + using HitTargetKind = UIEditorVector3FieldHitTargetKind; -float ClampNonNegative(float value) { - return (std::max)(0.0f, value); -} + static constexpr std::size_t kComponentCount = 3u; + static constexpr std::size_t kInvalidComponentIndex = + UIEditorVector3FieldInvalidComponentIndex; + static constexpr HitTargetKind kNoneHitTargetKind = + UIEditorVector3FieldHitTargetKind::None; + static constexpr HitTargetKind kRowHitTargetKind = + UIEditorVector3FieldHitTargetKind::Row; + static constexpr HitTargetKind kComponentHitTargetKind = + UIEditorVector3FieldHitTargetKind::Component; -UIEditorVector3FieldMetrics ResolveMetrics(const UIEditorVector3FieldMetrics& metrics) { - const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); - - UIEditorVector3FieldMetrics resolved = metrics; - if (AreUIEditorFieldMetricsEqual(metrics.controlTrailingInset, 8.0f)) { - resolved.controlTrailingInset = tokens.controlTrailingInset; - } - if (AreUIEditorFieldMetricsEqual(metrics.componentMinWidth, 72.0f)) { - resolved.componentMinWidth = tokens.vectorComponentMinWidth; - } - if (AreUIEditorFieldMetricsEqual(metrics.componentPrefixWidth, 9.0f)) { - resolved.componentPrefixWidth = tokens.vectorPrefixWidth; - } - if (AreUIEditorFieldMetricsEqual(metrics.componentLabelGap, 4.0f)) { - resolved.componentLabelGap = tokens.vectorPrefixGap; + static const ::XCEngine::UI::UIColor& AxisColor( + const Palette& palette, + std::size_t componentIndex) { + switch (componentIndex) { + case 0u: + return palette.axisXColor; + case 1u: + return palette.axisYColor; + default: + return palette.axisZColor; + } } - return resolved; -} - -UIEditorVector3FieldPalette ResolvePalette(const UIEditorVector3FieldPalette& palette) { - const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); - - UIEditorVector3FieldPalette resolved = palette; - if (AreUIEditorFieldColorsEqual(palette.componentColor, ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f))) { - resolved.componentColor = tokens.controlColor; + static ::XCEngine::UI::UIColor& AxisColor( + Palette& palette, + std::size_t componentIndex) { + switch (componentIndex) { + case 0u: + return palette.axisXColor; + case 1u: + return palette.axisYColor; + default: + return palette.axisZColor; + } } - if (AreUIEditorFieldColorsEqual( - palette.componentHoverColor, - ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f))) { - resolved.componentHoverColor = tokens.controlHoverColor; - } - if (AreUIEditorFieldColorsEqual( - palette.componentEditingColor, - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f))) { - resolved.componentEditingColor = tokens.controlEditingColor; - } - if (AreUIEditorFieldColorsEqual(palette.readOnlyColor, ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f))) { - resolved.readOnlyColor = tokens.controlReadOnlyColor; - } - if (AreUIEditorFieldColorsEqual( - palette.componentBorderColor, - ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f))) { - resolved.componentBorderColor = tokens.controlBorderColor; - } - if (AreUIEditorFieldColorsEqual( - palette.componentFocusedBorderColor, - ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f))) { - resolved.componentFocusedBorderColor = tokens.controlFocusedBorderColor; - } - if (AreUIEditorFieldColorsEqual(palette.prefixColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { - resolved.prefixColor = tokens.prefixColor; - } - if (AreUIEditorFieldColorsEqual( - palette.prefixBorderColor, - ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { - resolved.prefixBorderColor = tokens.prefixBorderColor; - } - if (AreUIEditorFieldColorsEqual(palette.labelColor, ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f))) { - resolved.labelColor = tokens.labelColor; - } - if (AreUIEditorFieldColorsEqual(palette.valueColor, ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f))) { - resolved.valueColor = tokens.valueColor; - } - if (AreUIEditorFieldColorsEqual( - palette.readOnlyValueColor, - ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f))) { - resolved.readOnlyValueColor = tokens.readOnlyValueColor; - } - if (AreUIEditorFieldColorsEqual(palette.axisXColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { - resolved.axisXColor = tokens.axisXColor; - } - if (AreUIEditorFieldColorsEqual(palette.axisYColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { - resolved.axisYColor = tokens.axisYColor; - } - if (AreUIEditorFieldColorsEqual(palette.axisZColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { - resolved.axisZColor = tokens.axisZColor; - } - - return resolved; -} - -float ApproximateTextWidth(float fontSize, std::size_t characterCount) { - return fontSize * 0.55f * static_cast(characterCount); -} - -UIEditorNumberFieldSpec BuildComponentNumberSpec( - const UIEditorVector3FieldSpec& spec, - std::size_t componentIndex) { - UIEditorNumberFieldSpec numberSpec = {}; - numberSpec.fieldId = spec.fieldId + "." + std::to_string(componentIndex); - numberSpec.label.clear(); - numberSpec.value = componentIndex < spec.values.size() ? spec.values[componentIndex] : 0.0; - numberSpec.step = spec.step; - numberSpec.minValue = spec.minValue; - numberSpec.maxValue = spec.maxValue; - numberSpec.integerMode = spec.integerMode; - numberSpec.readOnly = spec.readOnly; - return numberSpec; -} - -::XCEngine::UI::UIColor ResolveRowFillColor( - const UIEditorVector3FieldState& state, - const UIEditorVector3FieldPalette& palette) { - if (state.activeTarget != UIEditorVector3FieldHitTargetKind::None) { - return palette.rowActiveColor; - } - if (state.hoveredTarget != UIEditorVector3FieldHitTargetKind::None) { - return palette.rowHoverColor; - } - return palette.surfaceColor; -} - -::XCEngine::UI::UIColor ResolveComponentFillColor( - const UIEditorVector3FieldSpec& spec, - const UIEditorVector3FieldState& state, - const UIEditorVector3FieldPalette& palette, - std::size_t componentIndex) { - if (spec.readOnly) { - return palette.readOnlyColor; - } - if (state.editing && state.selectedComponentIndex == componentIndex) { - return palette.componentEditingColor; - } - if (state.hoveredComponentIndex == componentIndex) { - return palette.componentHoverColor; - } - return palette.componentColor; -} - -::XCEngine::UI::UIColor ResolveComponentBorderColor( - const UIEditorVector3FieldState& state, - const UIEditorVector3FieldPalette& palette, - std::size_t componentIndex) { - if (state.editing && state.selectedComponentIndex == componentIndex) { - return palette.componentFocusedBorderColor; - } - return palette.componentBorderColor; -} +}; } // namespace -bool IsUIEditorVector3FieldPointInside( - const UIRect& rect, - const UIPoint& point) { - return point.x >= rect.x && - point.x <= rect.x + rect.width && - point.y >= rect.y && - point.y <= rect.y + rect.height; +bool IsUIEditorVector3FieldPointInside(const UIRect& rect, const UIPoint& point) { + return Internal::VectorFieldWidgetShared::IsPointInside(rect, point); } double NormalizeUIEditorVector3FieldComponentValue( const UIEditorVector3FieldSpec& spec, double value) { - return NormalizeUIEditorNumberFieldValue(BuildComponentNumberSpec(spec, 0u), value); + return Internal::VectorFieldWidgetShared::NormalizeComponentValue( + spec, + value); } bool TryParseUIEditorVector3FieldComponentValue( const UIEditorVector3FieldSpec& spec, std::string_view text, double& outValue) { - return TryParseUIEditorNumberFieldValue(BuildComponentNumberSpec(spec, 0u), text, outValue); + return Internal::VectorFieldWidgetShared::TryParseComponentValue( + spec, + text, + outValue); } std::string FormatUIEditorVector3FieldComponentValue( const UIEditorVector3FieldSpec& spec, std::size_t componentIndex) { - return FormatUIEditorNumberFieldValue(BuildComponentNumberSpec(spec, componentIndex)); + return Internal::VectorFieldWidgetShared::FormatComponentValue( + spec, + componentIndex); } UIEditorVector3FieldLayout BuildUIEditorVector3FieldLayout( const UIRect& bounds, - const UIEditorVector3FieldSpec&, + const UIEditorVector3FieldSpec& spec, const UIEditorVector3FieldMetrics& metrics) { - const UIEditorVector3FieldMetrics resolvedMetrics = ResolveMetrics(metrics); - const float requiredControlWidth = - resolvedMetrics.componentMinWidth * 3.0f + resolvedMetrics.componentGap * 2.0f; - const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout( + return Internal::VectorFieldWidgetShared::BuildLayout( bounds, - requiredControlWidth, - UIEditorFieldRowLayoutMetrics { - resolvedMetrics.rowHeight, - resolvedMetrics.horizontalPadding, - resolvedMetrics.labelControlGap, - resolvedMetrics.controlColumnStart, - resolvedMetrics.sharedControlColumnMinWidth, - resolvedMetrics.controlTrailingInset, - resolvedMetrics.controlInsetY, - }); - const float componentWidth = - ClampNonNegative((hostLayout.controlRect.width - resolvedMetrics.componentGap * 2.0f) / 3.0f); - - UIEditorVector3FieldLayout layout = {}; - layout.bounds = hostLayout.bounds; - layout.labelRect = hostLayout.labelRect; - layout.controlRect = hostLayout.controlRect; - - for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { - const float componentX = - layout.controlRect.x + - (componentWidth + resolvedMetrics.componentGap) * static_cast(componentIndex); - const UIRect componentRect(componentX, layout.controlRect.y, componentWidth, layout.controlRect.height); - const float prefixWidth = (std::min)(resolvedMetrics.componentPrefixWidth, componentRect.width); - const float labelGap = ClampNonNegative(resolvedMetrics.componentLabelGap); - const float valueX = componentRect.x + prefixWidth + labelGap; - layout.componentRects[componentIndex] = componentRect; - layout.componentPrefixRects[componentIndex] = - UIRect(componentRect.x, componentRect.y, prefixWidth, componentRect.height); - layout.componentValueRects[componentIndex] = - UIRect( - valueX, - componentRect.y, - ClampNonNegative(componentRect.width - prefixWidth - labelGap), - componentRect.height); - } - - return layout; + spec, + metrics); } UIEditorVector3FieldHitTarget HitTestUIEditorVector3Field( const UIEditorVector3FieldLayout& layout, const UIPoint& point) { - for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { - if (IsUIEditorVector3FieldPointInside(layout.componentRects[componentIndex], point)) { - return { UIEditorVector3FieldHitTargetKind::Component, componentIndex }; - } - } - if (IsUIEditorVector3FieldPointInside(layout.bounds, point)) { - return { UIEditorVector3FieldHitTargetKind::Row, UIEditorVector3FieldInvalidComponentIndex }; - } - return {}; + return Internal::VectorFieldWidgetShared::HitTest( + layout, + point); } void AppendUIEditorVector3FieldBackground( @@ -258,33 +109,13 @@ void AppendUIEditorVector3FieldBackground( const UIEditorVector3FieldState& state, const UIEditorVector3FieldPalette& palette, const UIEditorVector3FieldMetrics& metrics) { - const UIEditorVector3FieldPalette resolvedPalette = ResolvePalette(palette); - const UIEditorVector3FieldMetrics resolvedMetrics = ResolveMetrics(metrics); - if (ResolveRowFillColor(state, resolvedPalette).a > 0.0f) { - drawList.AddFilledRect( - layout.bounds, - ResolveRowFillColor(state, resolvedPalette), - resolvedMetrics.cornerRounding); - } - if ((state.focused ? resolvedPalette.focusedBorderColor.a : resolvedPalette.borderColor.a) > 0.0f) { - drawList.AddRectOutline( - layout.bounds, - state.focused ? resolvedPalette.focusedBorderColor : resolvedPalette.borderColor, - state.focused ? resolvedMetrics.focusedBorderThickness : resolvedMetrics.borderThickness, - resolvedMetrics.cornerRounding); - } - - for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { - drawList.AddFilledRect( - layout.componentValueRects[componentIndex], - ResolveComponentFillColor(spec, state, resolvedPalette, componentIndex), - resolvedMetrics.componentRounding); - drawList.AddRectOutline( - layout.componentValueRects[componentIndex], - ResolveComponentBorderColor(state, resolvedPalette, componentIndex), - resolvedMetrics.borderThickness, - resolvedMetrics.componentRounding); - } + Internal::VectorFieldWidgetShared::AppendBackground( + drawList, + layout, + spec, + state, + palette, + metrics); } void AppendUIEditorVector3FieldForeground( @@ -294,69 +125,13 @@ void AppendUIEditorVector3FieldForeground( const UIEditorVector3FieldState& state, const UIEditorVector3FieldPalette& palette, const UIEditorVector3FieldMetrics& metrics) { - const UIEditorVector3FieldPalette resolvedPalette = ResolvePalette(palette); - const UIEditorVector3FieldMetrics resolvedMetrics = ResolveMetrics(metrics); - drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, resolvedMetrics.labelFontSize)); - drawList.AddText( - UIPoint( - layout.labelRect.x, - ResolveUIEditorTextTop(layout.labelRect, resolvedMetrics.labelFontSize, resolvedMetrics.labelTextInsetY)), - spec.label, - resolvedPalette.labelColor, - resolvedMetrics.labelFontSize); - drawList.PopClipRect(); - - for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { - drawList.PushClipRect( - ResolveUIEditorTextClipRect(layout.componentPrefixRects[componentIndex], resolvedMetrics.prefixFontSize)); - const std::string& componentLabel = spec.componentLabels[componentIndex]; - const float prefixTextX = - layout.componentPrefixRects[componentIndex].x + - ClampNonNegative( - (layout.componentPrefixRects[componentIndex].width - - ApproximateTextWidth(resolvedMetrics.prefixFontSize, componentLabel.size())) * - 0.5f) + - resolvedMetrics.prefixTextInsetX; - drawList.AddText( - UIPoint( - prefixTextX, - ResolveUIEditorTextTop( - layout.componentPrefixRects[componentIndex], - resolvedMetrics.prefixFontSize, - resolvedMetrics.prefixTextInsetY)), - componentLabel, - resolvedPalette.labelColor, - resolvedMetrics.prefixFontSize); - drawList.PopClipRect(); - - drawList.PushClipRect( - ResolveUIEditorTextClipRect(layout.componentValueRects[componentIndex], resolvedMetrics.valueFontSize)); - drawList.AddText( - UIPoint( - layout.componentValueRects[componentIndex].x + resolvedMetrics.valueTextInsetX, - ResolveUIEditorTextTop( - layout.componentValueRects[componentIndex], - resolvedMetrics.valueFontSize, - resolvedMetrics.valueTextInsetY)), - state.editing && state.selectedComponentIndex == componentIndex - ? state.displayTexts[componentIndex] - : FormatUIEditorVector3FieldComponentValue(spec, componentIndex), - spec.readOnly ? resolvedPalette.readOnlyValueColor : resolvedPalette.valueColor, - resolvedMetrics.valueFontSize); - if (state.editing && state.selectedComponentIndex == componentIndex) { - AppendUIEditorTextCaret( - drawList, - layout.componentValueRects[componentIndex], - state.displayTexts[componentIndex], - state.caretOffset, - state.caretBlinkStartNanoseconds, - resolvedPalette.valueColor, - resolvedMetrics.valueFontSize, - resolvedMetrics.valueTextInsetX, - resolvedMetrics.valueTextInsetY); - } - drawList.PopClipRect(); - } + Internal::VectorFieldWidgetShared::AppendForeground( + drawList, + layout, + spec, + state, + palette, + metrics); } void AppendUIEditorVector3Field( @@ -366,11 +141,13 @@ void AppendUIEditorVector3Field( const UIEditorVector3FieldState& state, const UIEditorVector3FieldPalette& palette, const UIEditorVector3FieldMetrics& metrics) { - const UIEditorVector3FieldPalette resolvedPalette = ResolvePalette(palette); - const UIEditorVector3FieldMetrics resolvedMetrics = ResolveMetrics(metrics); - const UIEditorVector3FieldLayout layout = BuildUIEditorVector3FieldLayout(bounds, spec, resolvedMetrics); - AppendUIEditorVector3FieldBackground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); - AppendUIEditorVector3FieldForeground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); + Internal::VectorFieldWidgetShared::AppendField( + drawList, + bounds, + spec, + state, + palette, + metrics); } } // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/src/Fields/UIEditorVector3FieldInteraction.cpp b/new_editor/src/Fields/UIEditorVector3FieldInteraction.cpp index 4fc8b819..1640d308 100644 --- a/new_editor/src/Fields/UIEditorVector3FieldInteraction.cpp +++ b/new_editor/src/Fields/UIEditorVector3FieldInteraction.cpp @@ -1,626 +1,55 @@ #include -#include - -#include -#include +#include "UIEditorVectorFieldInteractionShared.h" namespace XCEngine::UI::Editor { namespace { -using ::XCEngine::Input::KeyCode; -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIPointerButton; -using ::XCEngine::UI::Editor::Widgets::BuildUIEditorVector3FieldLayout; -using ::XCEngine::UI::Editor::Widgets::FormatUIEditorVector3FieldComponentValue; -using ::XCEngine::UI::Editor::Widgets::HitTestUIEditorVector3Field; -using ::XCEngine::UI::Editor::Widgets::IsUIEditorVector3FieldPointInside; -using ::XCEngine::UI::Editor::Widgets::NormalizeUIEditorVector3FieldComponentValue; -using ::XCEngine::UI::Editor::Widgets::TryParseUIEditorVector3FieldComponentValue; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector3FieldHitTarget; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector3FieldHitTargetKind; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector3FieldInvalidComponentIndex; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector3FieldLayout; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector3FieldMetrics; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector3FieldSpec; +struct Vector3FieldInteractionTraits { + using Spec = Widgets::UIEditorVector3FieldSpec; + using Metrics = Widgets::UIEditorVector3FieldMetrics; + using Layout = Widgets::UIEditorVector3FieldLayout; + using HitTarget = Widgets::UIEditorVector3FieldHitTarget; + using HitTargetKind = Widgets::UIEditorVector3FieldHitTargetKind; + using InteractionState = UIEditorVector3FieldInteractionState; + using InteractionResult = UIEditorVector3FieldInteractionResult; + using InteractionFrame = UIEditorVector3FieldInteractionFrame; -constexpr std::size_t kComponentCount = 3u; -constexpr std::size_t kLastComponentIndex = 2u; + static constexpr std::size_t kComponentCount = 3u; + static constexpr std::size_t kInvalidComponentIndex = + Widgets::UIEditorVector3FieldInvalidComponentIndex; + static constexpr HitTargetKind kNoneHitTargetKind = + Widgets::UIEditorVector3FieldHitTargetKind::None; + static constexpr HitTargetKind kRowHitTargetKind = + Widgets::UIEditorVector3FieldHitTargetKind::Row; + static constexpr HitTargetKind kComponentHitTargetKind = + Widgets::UIEditorVector3FieldHitTargetKind::Component; -bool IsPermittedCharacter( - const UIEditorVector3FieldSpec& spec, - std::uint32_t character) { - if (character >= static_cast('0') && - character <= static_cast('9')) { - return true; - } - if (character == static_cast('-') || - character == static_cast('+')) { - return true; - } - return !spec.integerMode && character == static_cast('.'); -} - -std::size_t ResolveSelectedComponentIndex( - const UIEditorVector3FieldInteractionState& state) { - return state.vector3FieldState.selectedComponentIndex == - UIEditorVector3FieldInvalidComponentIndex - ? 0u - : state.vector3FieldState.selectedComponentIndex; -} - -double ResolveDragSensitivity(const UIEditorVector3FieldSpec& spec) { - if (spec.step != 0.0) { - return spec.step; + static auto& FieldState(InteractionState& state) { + return state.vector3FieldState; } - return spec.integerMode ? 1.0 : 0.1; -} - -void SyncDisplayState( - UIEditorVector3FieldInteractionState& state, - const UIEditorVector3FieldSpec& spec, - const UIEditorVector3FieldLayout& layout) { - auto& fieldState = state.vector3FieldState; - const auto& session = state.session; - fieldState.focused = session.focused; - fieldState.editing = session.editing; - fieldState.caretBlinkStartNanoseconds = session.caretBlinkStartNanoseconds; - for (std::size_t componentIndex = 0u; componentIndex < kComponentCount; - ++componentIndex) { - fieldState.displayTexts[componentIndex] = - session.editing && - fieldState.selectedComponentIndex == componentIndex - ? session.textInputState.value - : FormatUIEditorVector3FieldComponentValue(spec, componentIndex); + static const auto& FieldState(const InteractionState& state) { + return state.vector3FieldState; } - if (session.editing && - fieldState.selectedComponentIndex != - UIEditorVector3FieldInvalidComponentIndex) { - fieldState.caretOffset = session.textInputState.caret; - } else if (fieldState.selectedComponentIndex != - UIEditorVector3FieldInvalidComponentIndex) { - fieldState.caretOffset = - fieldState.displayTexts[fieldState.selectedComponentIndex].size(); - } else { - fieldState.caretOffset = 0u; - } - if (!session.hasPointerPosition) { - fieldState.hoveredTarget = UIEditorVector3FieldHitTargetKind::None; - fieldState.hoveredComponentIndex = - UIEditorVector3FieldInvalidComponentIndex; - return; - } - - const UIEditorVector3FieldHitTarget hitTarget = - HitTestUIEditorVector3Field(layout, session.pointerPosition); - fieldState.hoveredTarget = hitTarget.kind; - fieldState.hoveredComponentIndex = hitTarget.componentIndex; -} - -bool SelectComponent( - UIEditorVector3FieldInteractionState& state, - std::size_t componentIndex, - UIEditorVector3FieldInteractionResult& result) { - if (componentIndex >= kComponentCount) { - return false; - } - - const std::size_t before = ResolveSelectedComponentIndex(state); - state.vector3FieldState.selectedComponentIndex = componentIndex; - state.session.activeComponentIndex = componentIndex; - result.selectionChanged = before != componentIndex; - result.selectedComponentIndex = componentIndex; - return true; -} - -bool MoveSelection( - UIEditorVector3FieldInteractionState& state, - int direction, - UIEditorVector3FieldInteractionResult& result) { - const std::size_t before = ResolveSelectedComponentIndex(state); - const std::size_t after = direction < 0 - ? (before == 0u ? 0u : before - 1u) - : (before >= kLastComponentIndex ? kLastComponentIndex : before + 1u); - state.vector3FieldState.selectedComponentIndex = after; - state.session.activeComponentIndex = after; - result.selectionChanged = before != after; - result.selectedComponentIndex = after; - result.consumed = true; - return true; -} - -bool BeginEdit( - UIEditorVector3FieldInteractionState& state, - const UIEditorVector3FieldSpec& spec, - std::size_t componentIndex, - bool clearText) { - if (spec.readOnly || componentIndex >= spec.values.size()) { - return false; - } - - state.vector3FieldState.selectedComponentIndex = componentIndex; - return BeginUIEditorEditableFieldEdit( - state.session, - spec.fieldId, - componentIndex, - FormatUIEditorVector3FieldComponentValue(spec, componentIndex), - clearText); -} - -bool CommitEdit( - UIEditorVector3FieldInteractionState& state, - UIEditorVector3FieldSpec& spec, - UIEditorVector3FieldInteractionResult& result) { - const std::size_t componentIndex = - state.vector3FieldState.selectedComponentIndex; - if (!state.session.editing || componentIndex >= spec.values.size()) { - return false; - } - - double parsedValue = spec.values[componentIndex]; - if (!TryParseUIEditorVector3FieldComponentValue( - spec, - state.session.textInputState.value, - parsedValue)) { - result.consumed = true; - result.editCommitRejected = true; - return false; - } - - result.valuesBefore = spec.values; - spec.values[componentIndex] = - NormalizeUIEditorVector3FieldComponentValue(spec, parsedValue); - result.valuesAfter = spec.values; - result.valueChanged = result.valuesBefore != result.valuesAfter; - result.editCommitted = true; - result.consumed = true; - result.changedComponentIndex = componentIndex; - result.selectedComponentIndex = componentIndex; - result.committedText = - FormatUIEditorVector3FieldComponentValue(spec, componentIndex); - CommitUIEditorEditableFieldEdit(state.session); - return true; -} - -bool CancelEdit( - UIEditorVector3FieldInteractionState& state, - const UIEditorVector3FieldSpec& spec, - UIEditorVector3FieldInteractionResult& result) { - const std::size_t componentIndex = - state.vector3FieldState.selectedComponentIndex; - if (!state.session.editing || componentIndex >= spec.values.size()) { - return false; - } - - CancelUIEditorEditableFieldEdit(state.session); - result.consumed = true; - result.editCanceled = true; - result.valuesBefore = spec.values; - result.valuesAfter = spec.values; - result.selectedComponentIndex = componentIndex; - return true; -} - -bool ApplyStep( - UIEditorVector3FieldInteractionState& state, - UIEditorVector3FieldSpec& spec, - double direction, - bool snapToEdge, - UIEditorVector3FieldInteractionResult& result) { - if (spec.readOnly) { - return false; - } - - const std::size_t componentIndex = ResolveSelectedComponentIndex(state); - state.vector3FieldState.selectedComponentIndex = componentIndex; - state.session.activeComponentIndex = componentIndex; - if (state.session.editing && !CommitEdit(state, spec, result)) { - return result.editCommitRejected; - } - - result.valuesBefore = spec.values; - if (snapToEdge) { - spec.values[componentIndex] = direction < 0.0 - ? NormalizeUIEditorVector3FieldComponentValue(spec, spec.minValue) - : NormalizeUIEditorVector3FieldComponentValue(spec, spec.maxValue); - } else { - const double step = spec.step == 0.0 ? 1.0 : spec.step; - spec.values[componentIndex] = - NormalizeUIEditorVector3FieldComponentValue( - spec, - spec.values[componentIndex] + step * direction); - result.stepDelta = step * direction; - } - - result.valuesAfter = spec.values; - result.stepApplied = true; - result.valueChanged = result.valuesBefore != result.valuesAfter; - result.changedComponentIndex = componentIndex; - result.selectedComponentIndex = componentIndex; - result.consumed = true; - return true; -} - -bool ApplyDrag( - UIEditorVector3FieldInteractionState& state, - UIEditorVector3FieldSpec& spec, - std::size_t componentIndex, - UIEditorVector3FieldInteractionResult& result) { - if (spec.readOnly || state.session.editing || - componentIndex >= spec.values.size()) { - return false; - } - - result.valuesBefore = spec.values; - spec.values[componentIndex] = - NormalizeUIEditorVector3FieldComponentValue( - spec, - state.session.dragStartValue + - ResolveUIEditorEditableFieldDragDelta( - state.session, - ResolveDragSensitivity(spec))); - result.valuesAfter = spec.values; - result.valueChanged = result.valuesBefore != result.valuesAfter; - result.changedComponentIndex = componentIndex; - result.selectedComponentIndex = componentIndex; - result.consumed = true; - return true; -} +}; } // namespace UIEditorVector3FieldInteractionFrame UpdateUIEditorVector3FieldInteraction( UIEditorVector3FieldInteractionState& state, - UIEditorVector3FieldSpec& spec, + Widgets::UIEditorVector3FieldSpec& spec, const ::XCEngine::UI::UIRect& bounds, - const std::vector& inputEvents, - const UIEditorVector3FieldMetrics& metrics) { - UIEditorVector3FieldLayout layout = BuildUIEditorVector3FieldLayout( - bounds, - spec, - metrics); - SyncDisplayState(state, spec, layout); - - UIEditorVector3FieldInteractionResult interactionResult = {}; - interactionResult.selectedComponentIndex = - state.vector3FieldState.selectedComponentIndex; - for (const UIInputEvent& event : inputEvents) { - UpdateUIEditorEditableFieldPointerPosition(state.session, event); - - UIEditorVector3FieldInteractionResult eventResult = {}; - eventResult.selectedComponentIndex = - state.vector3FieldState.selectedComponentIndex; - switch (event.type) { - case UIInputEventType::FocusGained: - eventResult.focusChanged = !state.session.focused; - state.session.focused = true; - if (state.vector3FieldState.selectedComponentIndex == - UIEditorVector3FieldInvalidComponentIndex) { - state.vector3FieldState.selectedComponentIndex = 0u; - } - state.session.activeComponentIndex = - state.vector3FieldState.selectedComponentIndex; - eventResult.selectedComponentIndex = - state.vector3FieldState.selectedComponentIndex; - break; - - case UIInputEventType::FocusLost: - eventResult.focusChanged = state.session.focused; - state.session.focused = false; - state.vector3FieldState.activeTarget = - UIEditorVector3FieldHitTargetKind::None; - state.vector3FieldState.activeComponentIndex = - UIEditorVector3FieldInvalidComponentIndex; - state.session.hasPointerPosition = false; - EndUIEditorEditableFieldDrag(state.session); - if (state.session.editing) { - CommitEdit(state, spec, eventResult); - if (eventResult.editCommitRejected) { - CancelEdit(state, spec, eventResult); - } - } - break; - - case UIInputEventType::PointerMove: - TryActivateUIEditorEditableFieldDrag(state.session, event); - if (state.session.dragActive && - state.vector3FieldState.activeTarget == - UIEditorVector3FieldHitTargetKind::Component && - state.vector3FieldState.activeComponentIndex != - UIEditorVector3FieldInvalidComponentIndex) { - ApplyDrag( - state, - spec, - state.vector3FieldState.activeComponentIndex, - eventResult); - } - break; - - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerLeave: - break; - - case UIInputEventType::PointerButtonDown: { - const UIEditorVector3FieldHitTarget hitTarget = - state.session.hasPointerPosition - ? HitTestUIEditorVector3Field( - layout, - state.session.pointerPosition) - : UIEditorVector3FieldHitTarget {}; - eventResult.hitTarget = hitTarget; - - if (event.pointerButton != UIPointerButton::Left) { - break; - } - - const bool insideField = - state.session.hasPointerPosition && - IsUIEditorVector3FieldPointInside( - layout.bounds, - state.session.pointerPosition); - if (insideField) { - eventResult.focusChanged = !state.session.focused; - state.session.focused = true; - state.vector3FieldState.activeTarget = - hitTarget.kind == UIEditorVector3FieldHitTargetKind::None - ? UIEditorVector3FieldHitTargetKind::Row - : hitTarget.kind; - state.vector3FieldState.activeComponentIndex = - hitTarget.componentIndex; - if (hitTarget.kind == - UIEditorVector3FieldHitTargetKind::Component && - !state.session.editing) { - ArmUIEditorEditableFieldDrag( - state.session, - spec.fieldId, - hitTarget.componentIndex, - hitTarget.componentIndex < spec.values.size() - ? spec.values[hitTarget.componentIndex] - : 0.0); - SelectComponent(state, hitTarget.componentIndex, eventResult); - } else { - EndUIEditorEditableFieldDrag(state.session); - if (state.vector3FieldState.selectedComponentIndex == - UIEditorVector3FieldInvalidComponentIndex) { - state.vector3FieldState.selectedComponentIndex = 0u; - state.session.activeComponentIndex = 0u; - eventResult.selectedComponentIndex = 0u; - } - } - eventResult.consumed = true; - } else { - if (state.session.editing) { - CommitEdit(state, spec, eventResult); - if (!eventResult.editCommitRejected) { - eventResult.focusChanged = state.session.focused; - state.session.focused = false; - } - } else if (state.session.focused) { - eventResult.focusChanged = true; - state.session.focused = false; - } - state.vector3FieldState.activeTarget = - UIEditorVector3FieldHitTargetKind::None; - state.vector3FieldState.activeComponentIndex = - UIEditorVector3FieldInvalidComponentIndex; - EndUIEditorEditableFieldDrag(state.session); - } - break; - } - - case UIInputEventType::PointerButtonUp: { - const UIEditorVector3FieldHitTarget hitTarget = - state.session.hasPointerPosition - ? HitTestUIEditorVector3Field( - layout, - state.session.pointerPosition) - : UIEditorVector3FieldHitTarget {}; - eventResult.hitTarget = hitTarget; - - if (event.pointerButton == UIPointerButton::Left) { - const UIEditorVector3FieldHitTargetKind activeTarget = - state.vector3FieldState.activeTarget; - const std::size_t activeComponentIndex = - state.vector3FieldState.activeComponentIndex; - state.vector3FieldState.activeTarget = - UIEditorVector3FieldHitTargetKind::None; - state.vector3FieldState.activeComponentIndex = - UIEditorVector3FieldInvalidComponentIndex; - - if (activeTarget == UIEditorVector3FieldHitTargetKind::Component && - hitTarget.kind == UIEditorVector3FieldHitTargetKind::Component && - activeComponentIndex == hitTarget.componentIndex) { - SelectComponent(state, hitTarget.componentIndex, eventResult); - if (state.session.dragActive) { - eventResult.consumed = true; - } else if (!state.session.editing && - IsUIEditorEditableFieldDoubleClick( - state.session, - spec.fieldId, - event, - hitTarget.componentIndex)) { - eventResult.editStarted = - BeginEdit(state, spec, hitTarget.componentIndex, false); - eventResult.consumed = true; - } else { - RecordUIEditorEditableFieldClick( - state.session, - spec.fieldId, - event, - hitTarget.componentIndex); - eventResult.consumed = true; - } - } else if (hitTarget.kind == UIEditorVector3FieldHitTargetKind::Row) { - eventResult.consumed = true; - } - - EndUIEditorEditableFieldDrag(state.session); - } - break; - } - - case UIInputEventType::KeyDown: - if (!state.session.focused) { - break; - } - - if (state.session.editing) { - if (event.keyCode == static_cast(KeyCode::Escape)) { - CancelEdit(state, spec, eventResult); - break; - } - if (event.keyCode == static_cast(KeyCode::Tab)) { - if (CommitEdit(state, spec, eventResult)) { - MoveSelection( - state, - event.modifiers.shift ? -1 : 1, - eventResult); - } - eventResult.consumed = true; - break; - } - - const auto textResult = HandleUIEditorEditableFieldKeyDown( - state.session, - event.keyCode, - event.modifiers); - if (textResult.handled) { - eventResult.consumed = true; - eventResult.selectedComponentIndex = - state.vector3FieldState.selectedComponentIndex; - if (textResult.submitRequested) { - CommitEdit(state, spec, eventResult); - } - } - } else { - switch (static_cast(event.keyCode)) { - case KeyCode::Left: - MoveSelection(state, -1, eventResult); - break; - case KeyCode::Right: - case KeyCode::Tab: - MoveSelection( - state, - static_cast(event.keyCode) == KeyCode::Tab && - event.modifiers.shift - ? -1 - : 1, - eventResult); - break; - case KeyCode::Up: - ApplyStep(state, spec, 1.0, false, eventResult); - break; - case KeyCode::Down: - ApplyStep(state, spec, -1.0, false, eventResult); - break; - case KeyCode::Home: - ApplyStep(state, spec, -1.0, true, eventResult); - break; - case KeyCode::End: - ApplyStep(state, spec, 1.0, true, eventResult); - break; - case KeyCode::Enter: - eventResult.selectedComponentIndex = - ResolveSelectedComponentIndex(state); - eventResult.editStarted = BeginEdit( - state, - spec, - eventResult.selectedComponentIndex, - false); - eventResult.consumed = eventResult.editStarted; - break; - default: - break; - } - } - break; - - case UIInputEventType::Character: - if (!state.session.focused || - spec.readOnly || - event.modifiers.control || - event.modifiers.alt || - event.modifiers.super || - !IsPermittedCharacter(spec, event.character)) { - break; - } - - if (state.vector3FieldState.selectedComponentIndex == - UIEditorVector3FieldInvalidComponentIndex) { - state.vector3FieldState.selectedComponentIndex = 0u; - } - state.session.activeComponentIndex = - state.vector3FieldState.selectedComponentIndex; - eventResult.selectedComponentIndex = - state.vector3FieldState.selectedComponentIndex; - if (!state.session.editing) { - eventResult.editStarted = BeginEdit( - state, - spec, - state.vector3FieldState.selectedComponentIndex, - true); - } - if (InsertUIEditorEditableFieldCharacter( - state.session, - event.character)) { - eventResult.consumed = true; - } - break; - - default: - break; - } - - layout = BuildUIEditorVector3FieldLayout(bounds, spec, metrics); - SyncDisplayState(state, spec, layout); - if (eventResult.hitTarget.kind == UIEditorVector3FieldHitTargetKind::None && - state.session.hasPointerPosition) { - eventResult.hitTarget = HitTestUIEditorVector3Field( - layout, - state.session.pointerPosition); - } - if (eventResult.selectedComponentIndex == - UIEditorVector3FieldInvalidComponentIndex) { - eventResult.selectedComponentIndex = - state.vector3FieldState.selectedComponentIndex; - } - - if (eventResult.consumed || - eventResult.focusChanged || - eventResult.valueChanged || - eventResult.stepApplied || - eventResult.selectionChanged || - eventResult.editStarted || - eventResult.editCommitted || - eventResult.editCommitRejected || - eventResult.editCanceled || - eventResult.hitTarget.kind != - UIEditorVector3FieldHitTargetKind::None) { - interactionResult = std::move(eventResult); - } - } - - layout = BuildUIEditorVector3FieldLayout(bounds, spec, metrics); - SyncDisplayState(state, spec, layout); - if (interactionResult.hitTarget.kind == UIEditorVector3FieldHitTargetKind::None && - state.session.hasPointerPosition) { - interactionResult.hitTarget = HitTestUIEditorVector3Field( - layout, - state.session.pointerPosition); - } - if (interactionResult.selectedComponentIndex == - UIEditorVector3FieldInvalidComponentIndex) { - interactionResult.selectedComponentIndex = - state.vector3FieldState.selectedComponentIndex; - } - - return { - std::move(layout), - std::move(interactionResult) - }; + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + const Widgets::UIEditorVector3FieldMetrics& metrics) { + return Internal::VectorFieldInteractionShared::UpdateInteraction< + Vector3FieldInteractionTraits>( + state, + spec, + bounds, + inputEvents, + metrics); } } // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Fields/UIEditorVector4Field.cpp b/new_editor/src/Fields/UIEditorVector4Field.cpp index 9c4f8eea..30257419 100644 --- a/new_editor/src/Fields/UIEditorVector4Field.cpp +++ b/new_editor/src/Fields/UIEditorVector4Field.cpp @@ -1,257 +1,109 @@ #include -#include -#include -#include - -#include +#include "UIEditorVectorFieldShared.h" namespace XCEngine::UI::Editor::Widgets { namespace { -using ::XCEngine::UI::UIDrawList; -using ::XCEngine::UI::UIPoint; -using ::XCEngine::UI::UIRect; +struct Vector4FieldTraits { + using Spec = UIEditorVector4FieldSpec; + using State = UIEditorVector4FieldState; + using Metrics = UIEditorVector4FieldMetrics; + using Palette = UIEditorVector4FieldPalette; + using Layout = UIEditorVector4FieldLayout; + using HitTarget = UIEditorVector4FieldHitTarget; + using HitTargetKind = UIEditorVector4FieldHitTargetKind; -float ClampNonNegative(float value) { - return (std::max)(0.0f, value); -} + static constexpr std::size_t kComponentCount = 4u; + static constexpr std::size_t kInvalidComponentIndex = + UIEditorVector4FieldInvalidComponentIndex; + static constexpr HitTargetKind kNoneHitTargetKind = + UIEditorVector4FieldHitTargetKind::None; + static constexpr HitTargetKind kRowHitTargetKind = + UIEditorVector4FieldHitTargetKind::Row; + static constexpr HitTargetKind kComponentHitTargetKind = + UIEditorVector4FieldHitTargetKind::Component; -UIEditorVector4FieldMetrics ResolveMetrics(const UIEditorVector4FieldMetrics& metrics) { - const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); - - UIEditorVector4FieldMetrics resolved = metrics; - if (AreUIEditorFieldMetricsEqual(metrics.controlTrailingInset, 8.0f)) { - resolved.controlTrailingInset = tokens.controlTrailingInset; - } - if (AreUIEditorFieldMetricsEqual(metrics.componentMinWidth, 72.0f)) { - resolved.componentMinWidth = tokens.vectorComponentMinWidth; - } - if (AreUIEditorFieldMetricsEqual(metrics.componentPrefixWidth, 9.0f)) { - resolved.componentPrefixWidth = tokens.vectorPrefixWidth; - } - if (AreUIEditorFieldMetricsEqual(metrics.componentLabelGap, 4.0f)) { - resolved.componentLabelGap = tokens.vectorPrefixGap; + static const ::XCEngine::UI::UIColor& AxisColor( + const Palette& palette, + std::size_t componentIndex) { + switch (componentIndex) { + case 0u: + return palette.axisXColor; + case 1u: + return palette.axisYColor; + case 2u: + return palette.axisZColor; + default: + return palette.axisWColor; + } } - return resolved; -} - -UIEditorVector4FieldPalette ResolvePalette(const UIEditorVector4FieldPalette& palette) { - const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); - - UIEditorVector4FieldPalette resolved = palette; - if (AreUIEditorFieldColorsEqual(palette.componentColor, ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f))) { - resolved.componentColor = tokens.controlColor; + static ::XCEngine::UI::UIColor& AxisColor( + Palette& palette, + std::size_t componentIndex) { + switch (componentIndex) { + case 0u: + return palette.axisXColor; + case 1u: + return palette.axisYColor; + case 2u: + return palette.axisZColor; + default: + return palette.axisWColor; + } } - if (AreUIEditorFieldColorsEqual( - palette.componentHoverColor, - ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f))) { - resolved.componentHoverColor = tokens.controlHoverColor; - } - if (AreUIEditorFieldColorsEqual( - palette.componentEditingColor, - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f))) { - resolved.componentEditingColor = tokens.controlEditingColor; - } - if (AreUIEditorFieldColorsEqual(palette.readOnlyColor, ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f))) { - resolved.readOnlyColor = tokens.controlReadOnlyColor; - } - if (AreUIEditorFieldColorsEqual( - palette.componentBorderColor, - ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f))) { - resolved.componentBorderColor = tokens.controlBorderColor; - } - if (AreUIEditorFieldColorsEqual( - palette.componentFocusedBorderColor, - ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f))) { - resolved.componentFocusedBorderColor = tokens.controlFocusedBorderColor; - } - if (AreUIEditorFieldColorsEqual(palette.prefixColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { - resolved.prefixColor = tokens.prefixColor; - } - if (AreUIEditorFieldColorsEqual( - palette.prefixBorderColor, - ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { - resolved.prefixBorderColor = tokens.prefixBorderColor; - } - if (AreUIEditorFieldColorsEqual(palette.labelColor, ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f))) { - resolved.labelColor = tokens.labelColor; - } - if (AreUIEditorFieldColorsEqual(palette.valueColor, ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f))) { - resolved.valueColor = tokens.valueColor; - } - if (AreUIEditorFieldColorsEqual( - palette.readOnlyValueColor, - ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f))) { - resolved.readOnlyValueColor = tokens.readOnlyValueColor; - } - if (AreUIEditorFieldColorsEqual(palette.axisXColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { - resolved.axisXColor = tokens.axisXColor; - } - if (AreUIEditorFieldColorsEqual(palette.axisYColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { - resolved.axisYColor = tokens.axisYColor; - } - if (AreUIEditorFieldColorsEqual(palette.axisZColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { - resolved.axisZColor = tokens.axisZColor; - } - if (AreUIEditorFieldColorsEqual(palette.axisWColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { - resolved.axisWColor = tokens.axisWColor; - } - - return resolved; -} - -float ApproximateTextWidth(float fontSize, std::size_t characterCount) { - return fontSize * 0.55f * static_cast(characterCount); -} - -UIEditorNumberFieldSpec BuildComponentNumberSpec( - const UIEditorVector4FieldSpec& spec, - std::size_t componentIndex) { - UIEditorNumberFieldSpec numberSpec = {}; - numberSpec.fieldId = spec.fieldId + "." + std::to_string(componentIndex); - numberSpec.label.clear(); - numberSpec.value = componentIndex < spec.values.size() ? spec.values[componentIndex] : 0.0; - numberSpec.step = spec.step; - numberSpec.minValue = spec.minValue; - numberSpec.maxValue = spec.maxValue; - numberSpec.integerMode = spec.integerMode; - numberSpec.readOnly = spec.readOnly; - return numberSpec; -} - -::XCEngine::UI::UIColor ResolveRowFillColor( - const UIEditorVector4FieldState& state, - const UIEditorVector4FieldPalette& palette) { - if (state.activeTarget != UIEditorVector4FieldHitTargetKind::None) { - return palette.rowActiveColor; - } - if (state.hoveredTarget != UIEditorVector4FieldHitTargetKind::None) { - return palette.rowHoverColor; - } - return palette.surfaceColor; -} - -::XCEngine::UI::UIColor ResolveComponentFillColor( - const UIEditorVector4FieldSpec& spec, - const UIEditorVector4FieldState& state, - const UIEditorVector4FieldPalette& palette, - std::size_t componentIndex) { - if (spec.readOnly) { - return palette.readOnlyColor; - } - if (state.editing && state.selectedComponentIndex == componentIndex) { - return palette.componentEditingColor; - } - if (state.hoveredComponentIndex == componentIndex) { - return palette.componentHoverColor; - } - return palette.componentColor; -} - -::XCEngine::UI::UIColor ResolveComponentBorderColor( - const UIEditorVector4FieldState& state, - const UIEditorVector4FieldPalette& palette, - std::size_t componentIndex) { - if (state.editing && state.selectedComponentIndex == componentIndex) { - return palette.componentFocusedBorderColor; - } - return palette.componentBorderColor; -} +}; } // namespace -bool IsUIEditorVector4FieldPointInside( - const UIRect& rect, - const UIPoint& point) { - return point.x >= rect.x && - point.x <= rect.x + rect.width && - point.y >= rect.y && - point.y <= rect.y + rect.height; +bool IsUIEditorVector4FieldPointInside(const UIRect& rect, const UIPoint& point) { + return Internal::VectorFieldWidgetShared::IsPointInside(rect, point); } double NormalizeUIEditorVector4FieldComponentValue( const UIEditorVector4FieldSpec& spec, double value) { - return NormalizeUIEditorNumberFieldValue(BuildComponentNumberSpec(spec, 0u), value); + return Internal::VectorFieldWidgetShared::NormalizeComponentValue( + spec, + value); } bool TryParseUIEditorVector4FieldComponentValue( const UIEditorVector4FieldSpec& spec, std::string_view text, double& outValue) { - return TryParseUIEditorNumberFieldValue(BuildComponentNumberSpec(spec, 0u), text, outValue); + return Internal::VectorFieldWidgetShared::TryParseComponentValue( + spec, + text, + outValue); } std::string FormatUIEditorVector4FieldComponentValue( const UIEditorVector4FieldSpec& spec, std::size_t componentIndex) { - return FormatUIEditorNumberFieldValue(BuildComponentNumberSpec(spec, componentIndex)); + return Internal::VectorFieldWidgetShared::FormatComponentValue( + spec, + componentIndex); } UIEditorVector4FieldLayout BuildUIEditorVector4FieldLayout( const UIRect& bounds, - const UIEditorVector4FieldSpec&, + const UIEditorVector4FieldSpec& spec, const UIEditorVector4FieldMetrics& metrics) { - const UIEditorVector4FieldMetrics resolvedMetrics = ResolveMetrics(metrics); - const float requiredControlWidth = - resolvedMetrics.componentMinWidth * 4.0f + resolvedMetrics.componentGap * 3.0f; - const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout( + return Internal::VectorFieldWidgetShared::BuildLayout( bounds, - requiredControlWidth, - UIEditorFieldRowLayoutMetrics { - resolvedMetrics.rowHeight, - resolvedMetrics.horizontalPadding, - resolvedMetrics.labelControlGap, - resolvedMetrics.controlColumnStart, - resolvedMetrics.sharedControlColumnMinWidth, - resolvedMetrics.controlTrailingInset, - resolvedMetrics.controlInsetY, - }); - const float componentWidth = - ClampNonNegative((hostLayout.controlRect.width - resolvedMetrics.componentGap * 3.0f) / 4.0f); - - UIEditorVector4FieldLayout layout = {}; - layout.bounds = hostLayout.bounds; - layout.labelRect = hostLayout.labelRect; - layout.controlRect = hostLayout.controlRect; - - for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { - const float componentX = - layout.controlRect.x + - (componentWidth + resolvedMetrics.componentGap) * static_cast(componentIndex); - const UIRect componentRect(componentX, layout.controlRect.y, componentWidth, layout.controlRect.height); - const float prefixWidth = (std::min)(resolvedMetrics.componentPrefixWidth, componentRect.width); - const float labelGap = ClampNonNegative(resolvedMetrics.componentLabelGap); - const float valueX = componentRect.x + prefixWidth + labelGap; - layout.componentRects[componentIndex] = componentRect; - layout.componentPrefixRects[componentIndex] = - UIRect(componentRect.x, componentRect.y, prefixWidth, componentRect.height); - layout.componentValueRects[componentIndex] = - UIRect( - valueX, - componentRect.y, - ClampNonNegative(componentRect.width - prefixWidth - labelGap), - componentRect.height); - } - - return layout; + spec, + metrics); } UIEditorVector4FieldHitTarget HitTestUIEditorVector4Field( const UIEditorVector4FieldLayout& layout, const UIPoint& point) { - for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { - if (IsUIEditorVector4FieldPointInside(layout.componentRects[componentIndex], point)) { - return { UIEditorVector4FieldHitTargetKind::Component, componentIndex }; - } - } - if (IsUIEditorVector4FieldPointInside(layout.bounds, point)) { - return { UIEditorVector4FieldHitTargetKind::Row, UIEditorVector4FieldInvalidComponentIndex }; - } - return {}; + return Internal::VectorFieldWidgetShared::HitTest( + layout, + point); } void AppendUIEditorVector4FieldBackground( @@ -261,33 +113,13 @@ void AppendUIEditorVector4FieldBackground( const UIEditorVector4FieldState& state, const UIEditorVector4FieldPalette& palette, const UIEditorVector4FieldMetrics& metrics) { - const UIEditorVector4FieldPalette resolvedPalette = ResolvePalette(palette); - const UIEditorVector4FieldMetrics resolvedMetrics = ResolveMetrics(metrics); - if (ResolveRowFillColor(state, resolvedPalette).a > 0.0f) { - drawList.AddFilledRect( - layout.bounds, - ResolveRowFillColor(state, resolvedPalette), - resolvedMetrics.cornerRounding); - } - if ((state.focused ? resolvedPalette.focusedBorderColor.a : resolvedPalette.borderColor.a) > 0.0f) { - drawList.AddRectOutline( - layout.bounds, - state.focused ? resolvedPalette.focusedBorderColor : resolvedPalette.borderColor, - state.focused ? resolvedMetrics.focusedBorderThickness : resolvedMetrics.borderThickness, - resolvedMetrics.cornerRounding); - } - - for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { - drawList.AddFilledRect( - layout.componentValueRects[componentIndex], - ResolveComponentFillColor(spec, state, resolvedPalette, componentIndex), - resolvedMetrics.componentRounding); - drawList.AddRectOutline( - layout.componentValueRects[componentIndex], - ResolveComponentBorderColor(state, resolvedPalette, componentIndex), - resolvedMetrics.borderThickness, - resolvedMetrics.componentRounding); - } + Internal::VectorFieldWidgetShared::AppendBackground( + drawList, + layout, + spec, + state, + palette, + metrics); } void AppendUIEditorVector4FieldForeground( @@ -297,69 +129,13 @@ void AppendUIEditorVector4FieldForeground( const UIEditorVector4FieldState& state, const UIEditorVector4FieldPalette& palette, const UIEditorVector4FieldMetrics& metrics) { - const UIEditorVector4FieldPalette resolvedPalette = ResolvePalette(palette); - const UIEditorVector4FieldMetrics resolvedMetrics = ResolveMetrics(metrics); - drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, resolvedMetrics.labelFontSize)); - drawList.AddText( - UIPoint( - layout.labelRect.x, - ResolveUIEditorTextTop(layout.labelRect, resolvedMetrics.labelFontSize, resolvedMetrics.labelTextInsetY)), - spec.label, - resolvedPalette.labelColor, - resolvedMetrics.labelFontSize); - drawList.PopClipRect(); - - for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { - drawList.PushClipRect( - ResolveUIEditorTextClipRect(layout.componentPrefixRects[componentIndex], resolvedMetrics.prefixFontSize)); - const std::string& componentLabel = spec.componentLabels[componentIndex]; - const float prefixTextX = - layout.componentPrefixRects[componentIndex].x + - ClampNonNegative( - (layout.componentPrefixRects[componentIndex].width - - ApproximateTextWidth(resolvedMetrics.prefixFontSize, componentLabel.size())) * - 0.5f) + - resolvedMetrics.prefixTextInsetX; - drawList.AddText( - UIPoint( - prefixTextX, - ResolveUIEditorTextTop( - layout.componentPrefixRects[componentIndex], - resolvedMetrics.prefixFontSize, - resolvedMetrics.prefixTextInsetY)), - componentLabel, - resolvedPalette.labelColor, - resolvedMetrics.prefixFontSize); - drawList.PopClipRect(); - - drawList.PushClipRect( - ResolveUIEditorTextClipRect(layout.componentValueRects[componentIndex], resolvedMetrics.valueFontSize)); - drawList.AddText( - UIPoint( - layout.componentValueRects[componentIndex].x + resolvedMetrics.valueTextInsetX, - ResolveUIEditorTextTop( - layout.componentValueRects[componentIndex], - resolvedMetrics.valueFontSize, - resolvedMetrics.valueTextInsetY)), - state.editing && state.selectedComponentIndex == componentIndex - ? state.displayTexts[componentIndex] - : FormatUIEditorVector4FieldComponentValue(spec, componentIndex), - spec.readOnly ? resolvedPalette.readOnlyValueColor : resolvedPalette.valueColor, - resolvedMetrics.valueFontSize); - if (state.editing && state.selectedComponentIndex == componentIndex) { - AppendUIEditorTextCaret( - drawList, - layout.componentValueRects[componentIndex], - state.displayTexts[componentIndex], - state.caretOffset, - state.caretBlinkStartNanoseconds, - resolvedPalette.valueColor, - resolvedMetrics.valueFontSize, - resolvedMetrics.valueTextInsetX, - resolvedMetrics.valueTextInsetY); - } - drawList.PopClipRect(); - } + Internal::VectorFieldWidgetShared::AppendForeground( + drawList, + layout, + spec, + state, + palette, + metrics); } void AppendUIEditorVector4Field( @@ -369,11 +145,13 @@ void AppendUIEditorVector4Field( const UIEditorVector4FieldState& state, const UIEditorVector4FieldPalette& palette, const UIEditorVector4FieldMetrics& metrics) { - const UIEditorVector4FieldPalette resolvedPalette = ResolvePalette(palette); - const UIEditorVector4FieldMetrics resolvedMetrics = ResolveMetrics(metrics); - const UIEditorVector4FieldLayout layout = BuildUIEditorVector4FieldLayout(bounds, spec, resolvedMetrics); - AppendUIEditorVector4FieldBackground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); - AppendUIEditorVector4FieldForeground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); + Internal::VectorFieldWidgetShared::AppendField( + drawList, + bounds, + spec, + state, + palette, + metrics); } } // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/src/Fields/UIEditorVector4FieldInteraction.cpp b/new_editor/src/Fields/UIEditorVector4FieldInteraction.cpp index f6910892..6a303035 100644 --- a/new_editor/src/Fields/UIEditorVector4FieldInteraction.cpp +++ b/new_editor/src/Fields/UIEditorVector4FieldInteraction.cpp @@ -1,626 +1,55 @@ #include -#include - -#include -#include +#include "UIEditorVectorFieldInteractionShared.h" namespace XCEngine::UI::Editor { namespace { -using ::XCEngine::Input::KeyCode; -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIPointerButton; -using ::XCEngine::UI::Editor::Widgets::BuildUIEditorVector4FieldLayout; -using ::XCEngine::UI::Editor::Widgets::FormatUIEditorVector4FieldComponentValue; -using ::XCEngine::UI::Editor::Widgets::HitTestUIEditorVector4Field; -using ::XCEngine::UI::Editor::Widgets::IsUIEditorVector4FieldPointInside; -using ::XCEngine::UI::Editor::Widgets::NormalizeUIEditorVector4FieldComponentValue; -using ::XCEngine::UI::Editor::Widgets::TryParseUIEditorVector4FieldComponentValue; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector4FieldHitTarget; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector4FieldHitTargetKind; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector4FieldInvalidComponentIndex; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector4FieldLayout; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector4FieldMetrics; -using ::XCEngine::UI::Editor::Widgets::UIEditorVector4FieldSpec; +struct Vector4FieldInteractionTraits { + using Spec = Widgets::UIEditorVector4FieldSpec; + using Metrics = Widgets::UIEditorVector4FieldMetrics; + using Layout = Widgets::UIEditorVector4FieldLayout; + using HitTarget = Widgets::UIEditorVector4FieldHitTarget; + using HitTargetKind = Widgets::UIEditorVector4FieldHitTargetKind; + using InteractionState = UIEditorVector4FieldInteractionState; + using InteractionResult = UIEditorVector4FieldInteractionResult; + using InteractionFrame = UIEditorVector4FieldInteractionFrame; -constexpr std::size_t kComponentCount = 4u; -constexpr std::size_t kLastComponentIndex = 3u; + static constexpr std::size_t kComponentCount = 4u; + static constexpr std::size_t kInvalidComponentIndex = + Widgets::UIEditorVector4FieldInvalidComponentIndex; + static constexpr HitTargetKind kNoneHitTargetKind = + Widgets::UIEditorVector4FieldHitTargetKind::None; + static constexpr HitTargetKind kRowHitTargetKind = + Widgets::UIEditorVector4FieldHitTargetKind::Row; + static constexpr HitTargetKind kComponentHitTargetKind = + Widgets::UIEditorVector4FieldHitTargetKind::Component; -bool IsPermittedCharacter( - const UIEditorVector4FieldSpec& spec, - std::uint32_t character) { - if (character >= static_cast('0') && - character <= static_cast('9')) { - return true; - } - if (character == static_cast('-') || - character == static_cast('+')) { - return true; - } - return !spec.integerMode && character == static_cast('.'); -} - -std::size_t ResolveSelectedComponentIndex( - const UIEditorVector4FieldInteractionState& state) { - return state.vector4FieldState.selectedComponentIndex == - UIEditorVector4FieldInvalidComponentIndex - ? 0u - : state.vector4FieldState.selectedComponentIndex; -} - -double ResolveDragSensitivity(const UIEditorVector4FieldSpec& spec) { - if (spec.step != 0.0) { - return spec.step; + static auto& FieldState(InteractionState& state) { + return state.vector4FieldState; } - return spec.integerMode ? 1.0 : 0.1; -} - -void SyncDisplayState( - UIEditorVector4FieldInteractionState& state, - const UIEditorVector4FieldSpec& spec, - const UIEditorVector4FieldLayout& layout) { - auto& fieldState = state.vector4FieldState; - const auto& session = state.session; - fieldState.focused = session.focused; - fieldState.editing = session.editing; - fieldState.caretBlinkStartNanoseconds = session.caretBlinkStartNanoseconds; - for (std::size_t componentIndex = 0u; componentIndex < kComponentCount; - ++componentIndex) { - fieldState.displayTexts[componentIndex] = - session.editing && - fieldState.selectedComponentIndex == componentIndex - ? session.textInputState.value - : FormatUIEditorVector4FieldComponentValue(spec, componentIndex); + static const auto& FieldState(const InteractionState& state) { + return state.vector4FieldState; } - if (session.editing && - fieldState.selectedComponentIndex != - UIEditorVector4FieldInvalidComponentIndex) { - fieldState.caretOffset = session.textInputState.caret; - } else if (fieldState.selectedComponentIndex != - UIEditorVector4FieldInvalidComponentIndex) { - fieldState.caretOffset = - fieldState.displayTexts[fieldState.selectedComponentIndex].size(); - } else { - fieldState.caretOffset = 0u; - } - if (!session.hasPointerPosition) { - fieldState.hoveredTarget = UIEditorVector4FieldHitTargetKind::None; - fieldState.hoveredComponentIndex = - UIEditorVector4FieldInvalidComponentIndex; - return; - } - - const UIEditorVector4FieldHitTarget hitTarget = - HitTestUIEditorVector4Field(layout, session.pointerPosition); - fieldState.hoveredTarget = hitTarget.kind; - fieldState.hoveredComponentIndex = hitTarget.componentIndex; -} - -bool SelectComponent( - UIEditorVector4FieldInteractionState& state, - std::size_t componentIndex, - UIEditorVector4FieldInteractionResult& result) { - if (componentIndex >= kComponentCount) { - return false; - } - - const std::size_t before = ResolveSelectedComponentIndex(state); - state.vector4FieldState.selectedComponentIndex = componentIndex; - state.session.activeComponentIndex = componentIndex; - result.selectionChanged = before != componentIndex; - result.selectedComponentIndex = componentIndex; - return true; -} - -bool MoveSelection( - UIEditorVector4FieldInteractionState& state, - int direction, - UIEditorVector4FieldInteractionResult& result) { - const std::size_t before = ResolveSelectedComponentIndex(state); - const std::size_t after = direction < 0 - ? (before == 0u ? 0u : before - 1u) - : (before >= kLastComponentIndex ? kLastComponentIndex : before + 1u); - state.vector4FieldState.selectedComponentIndex = after; - state.session.activeComponentIndex = after; - result.selectionChanged = before != after; - result.selectedComponentIndex = after; - result.consumed = true; - return true; -} - -bool BeginEdit( - UIEditorVector4FieldInteractionState& state, - const UIEditorVector4FieldSpec& spec, - std::size_t componentIndex, - bool clearText) { - if (spec.readOnly || componentIndex >= spec.values.size()) { - return false; - } - - state.vector4FieldState.selectedComponentIndex = componentIndex; - return BeginUIEditorEditableFieldEdit( - state.session, - spec.fieldId, - componentIndex, - FormatUIEditorVector4FieldComponentValue(spec, componentIndex), - clearText); -} - -bool CommitEdit( - UIEditorVector4FieldInteractionState& state, - UIEditorVector4FieldSpec& spec, - UIEditorVector4FieldInteractionResult& result) { - const std::size_t componentIndex = - state.vector4FieldState.selectedComponentIndex; - if (!state.session.editing || componentIndex >= spec.values.size()) { - return false; - } - - double parsedValue = spec.values[componentIndex]; - if (!TryParseUIEditorVector4FieldComponentValue( - spec, - state.session.textInputState.value, - parsedValue)) { - result.consumed = true; - result.editCommitRejected = true; - return false; - } - - result.valuesBefore = spec.values; - spec.values[componentIndex] = - NormalizeUIEditorVector4FieldComponentValue(spec, parsedValue); - result.valuesAfter = spec.values; - result.valueChanged = result.valuesBefore != result.valuesAfter; - result.editCommitted = true; - result.consumed = true; - result.changedComponentIndex = componentIndex; - result.selectedComponentIndex = componentIndex; - result.committedText = - FormatUIEditorVector4FieldComponentValue(spec, componentIndex); - CommitUIEditorEditableFieldEdit(state.session); - return true; -} - -bool CancelEdit( - UIEditorVector4FieldInteractionState& state, - const UIEditorVector4FieldSpec& spec, - UIEditorVector4FieldInteractionResult& result) { - const std::size_t componentIndex = - state.vector4FieldState.selectedComponentIndex; - if (!state.session.editing || componentIndex >= spec.values.size()) { - return false; - } - - CancelUIEditorEditableFieldEdit(state.session); - result.consumed = true; - result.editCanceled = true; - result.valuesBefore = spec.values; - result.valuesAfter = spec.values; - result.selectedComponentIndex = componentIndex; - return true; -} - -bool ApplyStep( - UIEditorVector4FieldInteractionState& state, - UIEditorVector4FieldSpec& spec, - double direction, - bool snapToEdge, - UIEditorVector4FieldInteractionResult& result) { - if (spec.readOnly) { - return false; - } - - const std::size_t componentIndex = ResolveSelectedComponentIndex(state); - state.vector4FieldState.selectedComponentIndex = componentIndex; - state.session.activeComponentIndex = componentIndex; - if (state.session.editing && !CommitEdit(state, spec, result)) { - return result.editCommitRejected; - } - - result.valuesBefore = spec.values; - if (snapToEdge) { - spec.values[componentIndex] = direction < 0.0 - ? NormalizeUIEditorVector4FieldComponentValue(spec, spec.minValue) - : NormalizeUIEditorVector4FieldComponentValue(spec, spec.maxValue); - } else { - const double step = spec.step == 0.0 ? 1.0 : spec.step; - spec.values[componentIndex] = - NormalizeUIEditorVector4FieldComponentValue( - spec, - spec.values[componentIndex] + step * direction); - result.stepDelta = step * direction; - } - - result.valuesAfter = spec.values; - result.stepApplied = true; - result.valueChanged = result.valuesBefore != result.valuesAfter; - result.changedComponentIndex = componentIndex; - result.selectedComponentIndex = componentIndex; - result.consumed = true; - return true; -} - -bool ApplyDrag( - UIEditorVector4FieldInteractionState& state, - UIEditorVector4FieldSpec& spec, - std::size_t componentIndex, - UIEditorVector4FieldInteractionResult& result) { - if (spec.readOnly || state.session.editing || - componentIndex >= spec.values.size()) { - return false; - } - - result.valuesBefore = spec.values; - spec.values[componentIndex] = - NormalizeUIEditorVector4FieldComponentValue( - spec, - state.session.dragStartValue + - ResolveUIEditorEditableFieldDragDelta( - state.session, - ResolveDragSensitivity(spec))); - result.valuesAfter = spec.values; - result.valueChanged = result.valuesBefore != result.valuesAfter; - result.changedComponentIndex = componentIndex; - result.selectedComponentIndex = componentIndex; - result.consumed = true; - return true; -} +}; } // namespace UIEditorVector4FieldInteractionFrame UpdateUIEditorVector4FieldInteraction( UIEditorVector4FieldInteractionState& state, - UIEditorVector4FieldSpec& spec, + Widgets::UIEditorVector4FieldSpec& spec, const ::XCEngine::UI::UIRect& bounds, - const std::vector& inputEvents, - const UIEditorVector4FieldMetrics& metrics) { - UIEditorVector4FieldLayout layout = BuildUIEditorVector4FieldLayout( - bounds, - spec, - metrics); - SyncDisplayState(state, spec, layout); - - UIEditorVector4FieldInteractionResult interactionResult = {}; - interactionResult.selectedComponentIndex = - state.vector4FieldState.selectedComponentIndex; - for (const UIInputEvent& event : inputEvents) { - UpdateUIEditorEditableFieldPointerPosition(state.session, event); - - UIEditorVector4FieldInteractionResult eventResult = {}; - eventResult.selectedComponentIndex = - state.vector4FieldState.selectedComponentIndex; - switch (event.type) { - case UIInputEventType::FocusGained: - eventResult.focusChanged = !state.session.focused; - state.session.focused = true; - if (state.vector4FieldState.selectedComponentIndex == - UIEditorVector4FieldInvalidComponentIndex) { - state.vector4FieldState.selectedComponentIndex = 0u; - } - state.session.activeComponentIndex = - state.vector4FieldState.selectedComponentIndex; - eventResult.selectedComponentIndex = - state.vector4FieldState.selectedComponentIndex; - break; - - case UIInputEventType::FocusLost: - eventResult.focusChanged = state.session.focused; - state.session.focused = false; - state.vector4FieldState.activeTarget = - UIEditorVector4FieldHitTargetKind::None; - state.vector4FieldState.activeComponentIndex = - UIEditorVector4FieldInvalidComponentIndex; - state.session.hasPointerPosition = false; - EndUIEditorEditableFieldDrag(state.session); - if (state.session.editing) { - CommitEdit(state, spec, eventResult); - if (eventResult.editCommitRejected) { - CancelEdit(state, spec, eventResult); - } - } - break; - - case UIInputEventType::PointerMove: - TryActivateUIEditorEditableFieldDrag(state.session, event); - if (state.session.dragActive && - state.vector4FieldState.activeTarget == - UIEditorVector4FieldHitTargetKind::Component && - state.vector4FieldState.activeComponentIndex != - UIEditorVector4FieldInvalidComponentIndex) { - ApplyDrag( - state, - spec, - state.vector4FieldState.activeComponentIndex, - eventResult); - } - break; - - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerLeave: - break; - - case UIInputEventType::PointerButtonDown: { - const UIEditorVector4FieldHitTarget hitTarget = - state.session.hasPointerPosition - ? HitTestUIEditorVector4Field( - layout, - state.session.pointerPosition) - : UIEditorVector4FieldHitTarget {}; - eventResult.hitTarget = hitTarget; - - if (event.pointerButton != UIPointerButton::Left) { - break; - } - - const bool insideField = - state.session.hasPointerPosition && - IsUIEditorVector4FieldPointInside( - layout.bounds, - state.session.pointerPosition); - if (insideField) { - eventResult.focusChanged = !state.session.focused; - state.session.focused = true; - state.vector4FieldState.activeTarget = - hitTarget.kind == UIEditorVector4FieldHitTargetKind::None - ? UIEditorVector4FieldHitTargetKind::Row - : hitTarget.kind; - state.vector4FieldState.activeComponentIndex = - hitTarget.componentIndex; - if (hitTarget.kind == - UIEditorVector4FieldHitTargetKind::Component && - !state.session.editing) { - ArmUIEditorEditableFieldDrag( - state.session, - spec.fieldId, - hitTarget.componentIndex, - hitTarget.componentIndex < spec.values.size() - ? spec.values[hitTarget.componentIndex] - : 0.0); - SelectComponent(state, hitTarget.componentIndex, eventResult); - } else { - EndUIEditorEditableFieldDrag(state.session); - if (state.vector4FieldState.selectedComponentIndex == - UIEditorVector4FieldInvalidComponentIndex) { - state.vector4FieldState.selectedComponentIndex = 0u; - state.session.activeComponentIndex = 0u; - eventResult.selectedComponentIndex = 0u; - } - } - eventResult.consumed = true; - } else { - if (state.session.editing) { - CommitEdit(state, spec, eventResult); - if (!eventResult.editCommitRejected) { - eventResult.focusChanged = state.session.focused; - state.session.focused = false; - } - } else if (state.session.focused) { - eventResult.focusChanged = true; - state.session.focused = false; - } - state.vector4FieldState.activeTarget = - UIEditorVector4FieldHitTargetKind::None; - state.vector4FieldState.activeComponentIndex = - UIEditorVector4FieldInvalidComponentIndex; - EndUIEditorEditableFieldDrag(state.session); - } - break; - } - - case UIInputEventType::PointerButtonUp: { - const UIEditorVector4FieldHitTarget hitTarget = - state.session.hasPointerPosition - ? HitTestUIEditorVector4Field( - layout, - state.session.pointerPosition) - : UIEditorVector4FieldHitTarget {}; - eventResult.hitTarget = hitTarget; - - if (event.pointerButton == UIPointerButton::Left) { - const UIEditorVector4FieldHitTargetKind activeTarget = - state.vector4FieldState.activeTarget; - const std::size_t activeComponentIndex = - state.vector4FieldState.activeComponentIndex; - state.vector4FieldState.activeTarget = - UIEditorVector4FieldHitTargetKind::None; - state.vector4FieldState.activeComponentIndex = - UIEditorVector4FieldInvalidComponentIndex; - - if (activeTarget == UIEditorVector4FieldHitTargetKind::Component && - hitTarget.kind == UIEditorVector4FieldHitTargetKind::Component && - activeComponentIndex == hitTarget.componentIndex) { - SelectComponent(state, hitTarget.componentIndex, eventResult); - if (state.session.dragActive) { - eventResult.consumed = true; - } else if (!state.session.editing && - IsUIEditorEditableFieldDoubleClick( - state.session, - spec.fieldId, - event, - hitTarget.componentIndex)) { - eventResult.editStarted = - BeginEdit(state, spec, hitTarget.componentIndex, false); - eventResult.consumed = true; - } else { - RecordUIEditorEditableFieldClick( - state.session, - spec.fieldId, - event, - hitTarget.componentIndex); - eventResult.consumed = true; - } - } else if (hitTarget.kind == UIEditorVector4FieldHitTargetKind::Row) { - eventResult.consumed = true; - } - - EndUIEditorEditableFieldDrag(state.session); - } - break; - } - - case UIInputEventType::KeyDown: - if (!state.session.focused) { - break; - } - - if (state.session.editing) { - if (event.keyCode == static_cast(KeyCode::Escape)) { - CancelEdit(state, spec, eventResult); - break; - } - if (event.keyCode == static_cast(KeyCode::Tab)) { - if (CommitEdit(state, spec, eventResult)) { - MoveSelection( - state, - event.modifiers.shift ? -1 : 1, - eventResult); - } - eventResult.consumed = true; - break; - } - - const auto textResult = HandleUIEditorEditableFieldKeyDown( - state.session, - event.keyCode, - event.modifiers); - if (textResult.handled) { - eventResult.consumed = true; - eventResult.selectedComponentIndex = - state.vector4FieldState.selectedComponentIndex; - if (textResult.submitRequested) { - CommitEdit(state, spec, eventResult); - } - } - } else { - switch (static_cast(event.keyCode)) { - case KeyCode::Left: - MoveSelection(state, -1, eventResult); - break; - case KeyCode::Right: - case KeyCode::Tab: - MoveSelection( - state, - static_cast(event.keyCode) == KeyCode::Tab && - event.modifiers.shift - ? -1 - : 1, - eventResult); - break; - case KeyCode::Up: - ApplyStep(state, spec, 1.0, false, eventResult); - break; - case KeyCode::Down: - ApplyStep(state, spec, -1.0, false, eventResult); - break; - case KeyCode::Home: - ApplyStep(state, spec, -1.0, true, eventResult); - break; - case KeyCode::End: - ApplyStep(state, spec, 1.0, true, eventResult); - break; - case KeyCode::Enter: - eventResult.selectedComponentIndex = - ResolveSelectedComponentIndex(state); - eventResult.editStarted = BeginEdit( - state, - spec, - eventResult.selectedComponentIndex, - false); - eventResult.consumed = eventResult.editStarted; - break; - default: - break; - } - } - break; - - case UIInputEventType::Character: - if (!state.session.focused || - spec.readOnly || - event.modifiers.control || - event.modifiers.alt || - event.modifiers.super || - !IsPermittedCharacter(spec, event.character)) { - break; - } - - if (state.vector4FieldState.selectedComponentIndex == - UIEditorVector4FieldInvalidComponentIndex) { - state.vector4FieldState.selectedComponentIndex = 0u; - } - state.session.activeComponentIndex = - state.vector4FieldState.selectedComponentIndex; - eventResult.selectedComponentIndex = - state.vector4FieldState.selectedComponentIndex; - if (!state.session.editing) { - eventResult.editStarted = BeginEdit( - state, - spec, - state.vector4FieldState.selectedComponentIndex, - true); - } - if (InsertUIEditorEditableFieldCharacter( - state.session, - event.character)) { - eventResult.consumed = true; - } - break; - - default: - break; - } - - layout = BuildUIEditorVector4FieldLayout(bounds, spec, metrics); - SyncDisplayState(state, spec, layout); - if (eventResult.hitTarget.kind == UIEditorVector4FieldHitTargetKind::None && - state.session.hasPointerPosition) { - eventResult.hitTarget = HitTestUIEditorVector4Field( - layout, - state.session.pointerPosition); - } - if (eventResult.selectedComponentIndex == - UIEditorVector4FieldInvalidComponentIndex) { - eventResult.selectedComponentIndex = - state.vector4FieldState.selectedComponentIndex; - } - - if (eventResult.consumed || - eventResult.focusChanged || - eventResult.valueChanged || - eventResult.stepApplied || - eventResult.selectionChanged || - eventResult.editStarted || - eventResult.editCommitted || - eventResult.editCommitRejected || - eventResult.editCanceled || - eventResult.hitTarget.kind != - UIEditorVector4FieldHitTargetKind::None) { - interactionResult = std::move(eventResult); - } - } - - layout = BuildUIEditorVector4FieldLayout(bounds, spec, metrics); - SyncDisplayState(state, spec, layout); - if (interactionResult.hitTarget.kind == UIEditorVector4FieldHitTargetKind::None && - state.session.hasPointerPosition) { - interactionResult.hitTarget = HitTestUIEditorVector4Field( - layout, - state.session.pointerPosition); - } - if (interactionResult.selectedComponentIndex == - UIEditorVector4FieldInvalidComponentIndex) { - interactionResult.selectedComponentIndex = - state.vector4FieldState.selectedComponentIndex; - } - - return { - std::move(layout), - std::move(interactionResult) - }; + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + const Widgets::UIEditorVector4FieldMetrics& metrics) { + return Internal::VectorFieldInteractionShared::UpdateInteraction< + Vector4FieldInteractionTraits>( + state, + spec, + bounds, + inputEvents, + metrics); } } // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Fields/UIEditorVectorFieldInteractionShared.h b/new_editor/src/Fields/UIEditorVectorFieldInteractionShared.h new file mode 100644 index 00000000..85a53d19 --- /dev/null +++ b/new_editor/src/Fields/UIEditorVectorFieldInteractionShared.h @@ -0,0 +1,632 @@ +#pragma once + +#include "UIEditorVectorFieldShared.h" + +#include + +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor::Internal::VectorFieldInteractionShared { + +using ::XCEngine::Input::KeyCode; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPointerButton; + +template +bool IsPermittedCharacter( + const typename Traits::Spec& spec, + std::uint32_t character) { + if (character >= static_cast('0') && + character <= static_cast('9')) { + return true; + } + if (character == static_cast('-') || + character == static_cast('+')) { + return true; + } + return !spec.integerMode && character == static_cast('.'); +} + +template +std::size_t ResolveSelectedComponentIndex( + const typename Traits::InteractionState& state) { + return Traits::FieldState(state).selectedComponentIndex == + Traits::kInvalidComponentIndex + ? 0u + : Traits::FieldState(state).selectedComponentIndex; +} + +template +double ResolveDragSensitivity(const typename Traits::Spec& spec) { + if (spec.step != 0.0) { + return spec.step; + } + + return spec.integerMode ? 1.0 : 0.1; +} + +template +void SyncDisplayState( + typename Traits::InteractionState& state, + const typename Traits::Spec& spec, + const typename Traits::Layout& layout) { + auto& fieldState = Traits::FieldState(state); + const auto& session = state.session; + fieldState.focused = session.focused; + fieldState.editing = session.editing; + fieldState.caretBlinkStartNanoseconds = session.caretBlinkStartNanoseconds; + for (std::size_t componentIndex = 0u; + componentIndex < Traits::kComponentCount; + ++componentIndex) { + fieldState.displayTexts[componentIndex] = + session.editing && fieldState.selectedComponentIndex == componentIndex + ? session.textInputState.value + : VectorFieldWidgetShared::FormatComponentValue( + spec, + componentIndex); + } + if (session.editing && + fieldState.selectedComponentIndex != Traits::kInvalidComponentIndex) { + fieldState.caretOffset = session.textInputState.caret; + } else if (fieldState.selectedComponentIndex != Traits::kInvalidComponentIndex) { + fieldState.caretOffset = + fieldState.displayTexts[fieldState.selectedComponentIndex].size(); + } else { + fieldState.caretOffset = 0u; + } + + if (!session.hasPointerPosition) { + fieldState.hoveredTarget = Traits::kNoneHitTargetKind; + fieldState.hoveredComponentIndex = Traits::kInvalidComponentIndex; + return; + } + + const typename Traits::HitTarget hitTarget = + VectorFieldWidgetShared::HitTest(layout, session.pointerPosition); + fieldState.hoveredTarget = hitTarget.kind; + fieldState.hoveredComponentIndex = hitTarget.componentIndex; +} + +template +bool SelectComponent( + typename Traits::InteractionState& state, + std::size_t componentIndex, + typename Traits::InteractionResult& result) { + if (componentIndex >= Traits::kComponentCount) { + return false; + } + + auto& fieldState = Traits::FieldState(state); + const std::size_t before = ResolveSelectedComponentIndex(state); + fieldState.selectedComponentIndex = componentIndex; + state.session.activeComponentIndex = componentIndex; + result.selectionChanged = before != componentIndex; + result.selectedComponentIndex = componentIndex; + return true; +} + +template +bool MoveSelection( + typename Traits::InteractionState& state, + int direction, + typename Traits::InteractionResult& result) { + auto& fieldState = Traits::FieldState(state); + const std::size_t before = ResolveSelectedComponentIndex(state); + const std::size_t lastComponentIndex = Traits::kComponentCount - 1u; + const std::size_t after = direction < 0 + ? (before == 0u ? 0u : before - 1u) + : (before >= lastComponentIndex ? lastComponentIndex : before + 1u); + fieldState.selectedComponentIndex = after; + state.session.activeComponentIndex = after; + result.selectionChanged = before != after; + result.selectedComponentIndex = after; + result.consumed = true; + return true; +} + +template +bool BeginEdit( + typename Traits::InteractionState& state, + const typename Traits::Spec& spec, + std::size_t componentIndex, + bool clearText) { + if (spec.readOnly || componentIndex >= spec.values.size()) { + return false; + } + + Traits::FieldState(state).selectedComponentIndex = componentIndex; + return BeginUIEditorEditableFieldEdit( + state.session, + spec.fieldId, + componentIndex, + VectorFieldWidgetShared::FormatComponentValue(spec, componentIndex), + clearText); +} + +template +bool CommitEdit( + typename Traits::InteractionState& state, + typename Traits::Spec& spec, + typename Traits::InteractionResult& result) { + const std::size_t componentIndex = + Traits::FieldState(state).selectedComponentIndex; + if (!state.session.editing || componentIndex >= spec.values.size()) { + return false; + } + + double parsedValue = spec.values[componentIndex]; + if (!VectorFieldWidgetShared::TryParseComponentValue( + spec, + state.session.textInputState.value, + parsedValue)) { + result.consumed = true; + result.editCommitRejected = true; + return false; + } + + result.valuesBefore = spec.values; + spec.values[componentIndex] = + VectorFieldWidgetShared::NormalizeComponentValue(spec, parsedValue); + result.valuesAfter = spec.values; + result.valueChanged = result.valuesBefore != result.valuesAfter; + result.editCommitted = true; + result.consumed = true; + result.changedComponentIndex = componentIndex; + result.selectedComponentIndex = componentIndex; + result.committedText = + VectorFieldWidgetShared::FormatComponentValue(spec, componentIndex); + CommitUIEditorEditableFieldEdit(state.session); + return true; +} + +template +bool CancelEdit( + typename Traits::InteractionState& state, + const typename Traits::Spec& spec, + typename Traits::InteractionResult& result) { + const std::size_t componentIndex = + Traits::FieldState(state).selectedComponentIndex; + if (!state.session.editing || componentIndex >= spec.values.size()) { + return false; + } + + CancelUIEditorEditableFieldEdit(state.session); + result.consumed = true; + result.editCanceled = true; + result.valuesBefore = spec.values; + result.valuesAfter = spec.values; + result.selectedComponentIndex = componentIndex; + return true; +} + +template +bool ApplyStep( + typename Traits::InteractionState& state, + typename Traits::Spec& spec, + double direction, + bool snapToEdge, + typename Traits::InteractionResult& result) { + if (spec.readOnly) { + return false; + } + + const std::size_t componentIndex = ResolveSelectedComponentIndex(state); + Traits::FieldState(state).selectedComponentIndex = componentIndex; + state.session.activeComponentIndex = componentIndex; + if (state.session.editing && !CommitEdit(state, spec, result)) { + return result.editCommitRejected; + } + + result.valuesBefore = spec.values; + if (snapToEdge) { + spec.values[componentIndex] = direction < 0.0 + ? VectorFieldWidgetShared::NormalizeComponentValue( + spec, + spec.minValue) + : VectorFieldWidgetShared::NormalizeComponentValue( + spec, + spec.maxValue); + } else { + const double step = spec.step == 0.0 ? 1.0 : spec.step; + spec.values[componentIndex] = + VectorFieldWidgetShared::NormalizeComponentValue( + spec, + spec.values[componentIndex] + step * direction); + result.stepDelta = step * direction; + } + + result.valuesAfter = spec.values; + result.stepApplied = true; + result.valueChanged = result.valuesBefore != result.valuesAfter; + result.changedComponentIndex = componentIndex; + result.selectedComponentIndex = componentIndex; + result.consumed = true; + return true; +} + +template +bool ApplyDrag( + typename Traits::InteractionState& state, + typename Traits::Spec& spec, + std::size_t componentIndex, + typename Traits::InteractionResult& result) { + if (spec.readOnly || state.session.editing || componentIndex >= spec.values.size()) { + return false; + } + + result.valuesBefore = spec.values; + spec.values[componentIndex] = + VectorFieldWidgetShared::NormalizeComponentValue( + spec, + state.session.dragStartValue + + ResolveUIEditorEditableFieldDragDelta( + state.session, + ResolveDragSensitivity(spec))); + result.valuesAfter = spec.values; + result.valueChanged = result.valuesBefore != result.valuesAfter; + result.changedComponentIndex = componentIndex; + result.selectedComponentIndex = componentIndex; + result.consumed = true; + return true; +} + +template +typename Traits::InteractionFrame UpdateInteraction( + typename Traits::InteractionState& state, + typename Traits::Spec& spec, + const ::XCEngine::UI::UIRect& bounds, + const std::vector& inputEvents, + const typename Traits::Metrics& metrics) { + typename Traits::Layout layout = + VectorFieldWidgetShared::BuildLayout(bounds, spec, metrics); + SyncDisplayState(state, spec, layout); + + typename Traits::InteractionResult interactionResult = {}; + interactionResult.selectedComponentIndex = + Traits::FieldState(state).selectedComponentIndex; + for (const UIInputEvent& event : inputEvents) { + UpdateUIEditorEditableFieldPointerPosition(state.session, event); + + typename Traits::InteractionResult eventResult = {}; + eventResult.selectedComponentIndex = + Traits::FieldState(state).selectedComponentIndex; + switch (event.type) { + case UIInputEventType::FocusGained: + eventResult.focusChanged = !state.session.focused; + state.session.focused = true; + if (Traits::FieldState(state).selectedComponentIndex == + Traits::kInvalidComponentIndex) { + Traits::FieldState(state).selectedComponentIndex = 0u; + } + state.session.activeComponentIndex = + Traits::FieldState(state).selectedComponentIndex; + eventResult.selectedComponentIndex = + Traits::FieldState(state).selectedComponentIndex; + break; + + case UIInputEventType::FocusLost: + eventResult.focusChanged = state.session.focused; + state.session.focused = false; + Traits::FieldState(state).activeTarget = Traits::kNoneHitTargetKind; + Traits::FieldState(state).activeComponentIndex = + Traits::kInvalidComponentIndex; + state.session.hasPointerPosition = false; + EndUIEditorEditableFieldDrag(state.session); + if (state.session.editing) { + CommitEdit(state, spec, eventResult); + if (eventResult.editCommitRejected) { + CancelEdit(state, spec, eventResult); + } + } + break; + + case UIInputEventType::PointerMove: + TryActivateUIEditorEditableFieldDrag(state.session, event); + if (state.session.dragActive && + Traits::FieldState(state).activeTarget == + Traits::kComponentHitTargetKind && + Traits::FieldState(state).activeComponentIndex != + Traits::kInvalidComponentIndex) { + ApplyDrag( + state, + spec, + Traits::FieldState(state).activeComponentIndex, + eventResult); + } + break; + + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerLeave: + break; + + case UIInputEventType::PointerButtonDown: { + const typename Traits::HitTarget hitTarget = + state.session.hasPointerPosition + ? VectorFieldWidgetShared::HitTest( + layout, + state.session.pointerPosition) + : typename Traits::HitTarget {}; + eventResult.hitTarget = hitTarget; + + if (event.pointerButton != UIPointerButton::Left) { + break; + } + + const bool insideField = + state.session.hasPointerPosition && + VectorFieldWidgetShared::IsPointInside( + layout.bounds, + state.session.pointerPosition); + if (insideField) { + eventResult.focusChanged = !state.session.focused; + state.session.focused = true; + Traits::FieldState(state).activeTarget = + hitTarget.kind == Traits::kNoneHitTargetKind + ? Traits::kRowHitTargetKind + : hitTarget.kind; + Traits::FieldState(state).activeComponentIndex = + hitTarget.componentIndex; + if (hitTarget.kind == Traits::kComponentHitTargetKind && + !state.session.editing) { + ArmUIEditorEditableFieldDrag( + state.session, + spec.fieldId, + hitTarget.componentIndex, + hitTarget.componentIndex < spec.values.size() + ? spec.values[hitTarget.componentIndex] + : 0.0); + SelectComponent( + state, + hitTarget.componentIndex, + eventResult); + } else { + EndUIEditorEditableFieldDrag(state.session); + if (Traits::FieldState(state).selectedComponentIndex == + Traits::kInvalidComponentIndex) { + Traits::FieldState(state).selectedComponentIndex = 0u; + state.session.activeComponentIndex = 0u; + eventResult.selectedComponentIndex = 0u; + } + } + eventResult.consumed = true; + } else { + if (state.session.editing) { + CommitEdit(state, spec, eventResult); + if (!eventResult.editCommitRejected) { + eventResult.focusChanged = state.session.focused; + state.session.focused = false; + } + } else if (state.session.focused) { + eventResult.focusChanged = true; + state.session.focused = false; + } + Traits::FieldState(state).activeTarget = Traits::kNoneHitTargetKind; + Traits::FieldState(state).activeComponentIndex = + Traits::kInvalidComponentIndex; + EndUIEditorEditableFieldDrag(state.session); + } + break; + } + + case UIInputEventType::PointerButtonUp: { + const typename Traits::HitTarget hitTarget = + state.session.hasPointerPosition + ? VectorFieldWidgetShared::HitTest( + layout, + state.session.pointerPosition) + : typename Traits::HitTarget {}; + eventResult.hitTarget = hitTarget; + + if (event.pointerButton == UIPointerButton::Left) { + const typename Traits::HitTargetKind activeTarget = + Traits::FieldState(state).activeTarget; + const std::size_t activeComponentIndex = + Traits::FieldState(state).activeComponentIndex; + Traits::FieldState(state).activeTarget = Traits::kNoneHitTargetKind; + Traits::FieldState(state).activeComponentIndex = + Traits::kInvalidComponentIndex; + + if (activeTarget == Traits::kComponentHitTargetKind && + hitTarget.kind == Traits::kComponentHitTargetKind && + activeComponentIndex == hitTarget.componentIndex) { + SelectComponent( + state, + hitTarget.componentIndex, + eventResult); + if (state.session.dragActive) { + eventResult.consumed = true; + } else if (!state.session.editing && + IsUIEditorEditableFieldDoubleClick( + state.session, + spec.fieldId, + event, + hitTarget.componentIndex)) { + eventResult.editStarted = + BeginEdit( + state, + spec, + hitTarget.componentIndex, + false); + eventResult.consumed = true; + } else { + RecordUIEditorEditableFieldClick( + state.session, + spec.fieldId, + event, + hitTarget.componentIndex); + eventResult.consumed = true; + } + } else if (hitTarget.kind == Traits::kRowHitTargetKind) { + eventResult.consumed = true; + } + + EndUIEditorEditableFieldDrag(state.session); + } + break; + } + + case UIInputEventType::KeyDown: + if (!state.session.focused) { + break; + } + + if (state.session.editing) { + if (event.keyCode == static_cast(KeyCode::Escape)) { + CancelEdit(state, spec, eventResult); + break; + } + if (event.keyCode == static_cast(KeyCode::Tab)) { + if (CommitEdit(state, spec, eventResult)) { + MoveSelection( + state, + event.modifiers.shift ? -1 : 1, + eventResult); + } + eventResult.consumed = true; + break; + } + + const auto textResult = HandleUIEditorEditableFieldKeyDown( + state.session, + event.keyCode, + event.modifiers); + if (textResult.handled) { + eventResult.consumed = true; + eventResult.selectedComponentIndex = + Traits::FieldState(state).selectedComponentIndex; + if (textResult.submitRequested) { + CommitEdit(state, spec, eventResult); + } + } + } else { + switch (static_cast(event.keyCode)) { + case KeyCode::Left: + MoveSelection(state, -1, eventResult); + break; + case KeyCode::Right: + case KeyCode::Tab: + MoveSelection( + state, + static_cast(event.keyCode) == KeyCode::Tab && + event.modifiers.shift + ? -1 + : 1, + eventResult); + break; + case KeyCode::Up: + ApplyStep(state, spec, 1.0, false, eventResult); + break; + case KeyCode::Down: + ApplyStep(state, spec, -1.0, false, eventResult); + break; + case KeyCode::Home: + ApplyStep(state, spec, -1.0, true, eventResult); + break; + case KeyCode::End: + ApplyStep(state, spec, 1.0, true, eventResult); + break; + case KeyCode::Enter: + eventResult.selectedComponentIndex = + ResolveSelectedComponentIndex(state); + eventResult.editStarted = + BeginEdit( + state, + spec, + eventResult.selectedComponentIndex, + false); + eventResult.consumed = eventResult.editStarted; + break; + default: + break; + } + } + break; + + case UIInputEventType::Character: + if (!state.session.focused || + spec.readOnly || + event.modifiers.control || + event.modifiers.alt || + event.modifiers.super || + !IsPermittedCharacter(spec, event.character)) { + break; + } + + if (Traits::FieldState(state).selectedComponentIndex == + Traits::kInvalidComponentIndex) { + Traits::FieldState(state).selectedComponentIndex = 0u; + } + state.session.activeComponentIndex = + Traits::FieldState(state).selectedComponentIndex; + eventResult.selectedComponentIndex = + Traits::FieldState(state).selectedComponentIndex; + if (!state.session.editing) { + eventResult.editStarted = + BeginEdit( + state, + spec, + Traits::FieldState(state).selectedComponentIndex, + true); + } + if (InsertUIEditorEditableFieldCharacter( + state.session, + event.character)) { + eventResult.consumed = true; + } + break; + + default: + break; + } + + layout = VectorFieldWidgetShared::BuildLayout(bounds, spec, metrics); + SyncDisplayState(state, spec, layout); + if (eventResult.hitTarget.kind == Traits::kNoneHitTargetKind && + state.session.hasPointerPosition) { + eventResult.hitTarget = + VectorFieldWidgetShared::HitTest( + layout, + state.session.pointerPosition); + } + if (eventResult.selectedComponentIndex == Traits::kInvalidComponentIndex) { + eventResult.selectedComponentIndex = + Traits::FieldState(state).selectedComponentIndex; + } + + if (eventResult.consumed || + eventResult.focusChanged || + eventResult.valueChanged || + eventResult.stepApplied || + eventResult.selectionChanged || + eventResult.editStarted || + eventResult.editCommitted || + eventResult.editCommitRejected || + eventResult.editCanceled || + eventResult.hitTarget.kind != Traits::kNoneHitTargetKind) { + interactionResult = std::move(eventResult); + } + } + + layout = VectorFieldWidgetShared::BuildLayout(bounds, spec, metrics); + SyncDisplayState(state, spec, layout); + if (interactionResult.hitTarget.kind == Traits::kNoneHitTargetKind && + state.session.hasPointerPosition) { + interactionResult.hitTarget = + VectorFieldWidgetShared::HitTest( + layout, + state.session.pointerPosition); + } + if (interactionResult.selectedComponentIndex == Traits::kInvalidComponentIndex) { + interactionResult.selectedComponentIndex = + Traits::FieldState(state).selectedComponentIndex; + } + + return { std::move(layout), std::move(interactionResult) }; +} + +} // namespace XCEngine::UI::Editor::Internal::VectorFieldInteractionShared diff --git a/new_editor/src/Fields/UIEditorVectorFieldShared.h b/new_editor/src/Fields/UIEditorVectorFieldShared.h new file mode 100644 index 00000000..f6952cc4 --- /dev/null +++ b/new_editor/src/Fields/UIEditorVectorFieldShared.h @@ -0,0 +1,502 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::Internal::VectorFieldWidgetShared { + +using ::XCEngine::UI::UIDrawList; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; + +inline float ClampNonNegative(float value) { + return (std::max)(0.0f, value); +} + +inline ::XCEngine::UI::UIColor DefaultAxisPlaceholderColor() { + return ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f); +} + +inline void OverrideMetricIfDefault( + float& value, + float placeholder, + float replacement) { + if (Widgets::AreUIEditorFieldMetricsEqual(value, placeholder)) { + value = replacement; + } +} + +inline void OverrideColorIfDefault( + ::XCEngine::UI::UIColor& value, + const ::XCEngine::UI::UIColor& placeholder, + const ::XCEngine::UI::UIColor& replacement) { + if (Widgets::AreUIEditorFieldColorsEqual(value, placeholder)) { + value = replacement; + } +} + +inline const ::XCEngine::UI::UIColor& ResolveAxisTokenColor( + const Widgets::UIEditorInspectorFieldStyleTokens& tokens, + std::size_t componentIndex) { + switch (componentIndex) { + case 0u: + return tokens.axisXColor; + case 1u: + return tokens.axisYColor; + case 2u: + return tokens.axisZColor; + default: + return tokens.axisWColor; + } +} + +template +typename Traits::Metrics ResolveMetrics(const typename Traits::Metrics& metrics) { + const auto& tokens = Widgets::GetUIEditorInspectorFieldStyleTokens(); + + typename Traits::Metrics resolved = metrics; + OverrideMetricIfDefault( + resolved.controlTrailingInset, + 8.0f, + tokens.controlTrailingInset); + OverrideMetricIfDefault( + resolved.componentMinWidth, + 72.0f, + tokens.vectorComponentMinWidth); + OverrideMetricIfDefault( + resolved.componentPrefixWidth, + 9.0f, + tokens.vectorPrefixWidth); + OverrideMetricIfDefault( + resolved.componentLabelGap, + 4.0f, + tokens.vectorPrefixGap); + + return resolved; +} + +template +typename Traits::Palette ResolvePalette(const typename Traits::Palette& palette) { + const auto& tokens = Widgets::GetUIEditorInspectorFieldStyleTokens(); + + typename Traits::Palette resolved = palette; + OverrideColorIfDefault( + resolved.componentColor, + ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f), + tokens.controlColor); + OverrideColorIfDefault( + resolved.componentHoverColor, + ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f), + tokens.controlHoverColor); + OverrideColorIfDefault( + resolved.componentEditingColor, + ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f), + tokens.controlEditingColor); + OverrideColorIfDefault( + resolved.readOnlyColor, + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f), + tokens.controlReadOnlyColor); + OverrideColorIfDefault( + resolved.componentBorderColor, + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f), + tokens.controlBorderColor); + OverrideColorIfDefault( + resolved.componentFocusedBorderColor, + ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f), + tokens.controlFocusedBorderColor); + OverrideColorIfDefault( + resolved.prefixColor, + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f), + tokens.prefixColor); + OverrideColorIfDefault( + resolved.prefixBorderColor, + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f), + tokens.prefixBorderColor); + OverrideColorIfDefault( + resolved.labelColor, + ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f), + tokens.labelColor); + OverrideColorIfDefault( + resolved.valueColor, + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f), + tokens.valueColor); + OverrideColorIfDefault( + resolved.readOnlyValueColor, + ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f), + tokens.readOnlyValueColor); + + for (std::size_t componentIndex = 0u; + componentIndex < Traits::kComponentCount; + ++componentIndex) { + if (Widgets::AreUIEditorFieldColorsEqual( + Traits::AxisColor(palette, componentIndex), + DefaultAxisPlaceholderColor())) { + Traits::AxisColor(resolved, componentIndex) = + ResolveAxisTokenColor(tokens, componentIndex); + } + } + + return resolved; +} + +inline float ApproximateTextWidth(float fontSize, std::size_t characterCount) { + return fontSize * 0.55f * static_cast(characterCount); +} + +template +Widgets::UIEditorNumberFieldSpec BuildComponentNumberSpec( + const typename Traits::Spec& spec, + std::size_t componentIndex) { + Widgets::UIEditorNumberFieldSpec numberSpec = {}; + numberSpec.fieldId = spec.fieldId + "." + std::to_string(componentIndex); + numberSpec.label.clear(); + numberSpec.value = componentIndex < spec.values.size() + ? spec.values[componentIndex] + : 0.0; + numberSpec.step = spec.step; + numberSpec.minValue = spec.minValue; + numberSpec.maxValue = spec.maxValue; + numberSpec.integerMode = spec.integerMode; + numberSpec.readOnly = spec.readOnly; + return numberSpec; +} + +template +::XCEngine::UI::UIColor ResolveRowFillColor( + const typename Traits::State& state, + const typename Traits::Palette& palette) { + if (state.activeTarget != Traits::kNoneHitTargetKind) { + return palette.rowActiveColor; + } + if (state.hoveredTarget != Traits::kNoneHitTargetKind) { + return palette.rowHoverColor; + } + return palette.surfaceColor; +} + +template +::XCEngine::UI::UIColor ResolveComponentFillColor( + const typename Traits::Spec& spec, + const typename Traits::State& state, + const typename Traits::Palette& palette, + std::size_t componentIndex) { + if (spec.readOnly) { + return palette.readOnlyColor; + } + if (state.editing && state.selectedComponentIndex == componentIndex) { + return palette.componentEditingColor; + } + if (state.hoveredComponentIndex == componentIndex) { + return palette.componentHoverColor; + } + return palette.componentColor; +} + +template +::XCEngine::UI::UIColor ResolveComponentBorderColor( + const typename Traits::State& state, + const typename Traits::Palette& palette, + std::size_t componentIndex) { + if (state.editing && state.selectedComponentIndex == componentIndex) { + return palette.componentFocusedBorderColor; + } + return palette.componentBorderColor; +} + +inline bool IsPointInside(const UIRect& rect, const UIPoint& point) { + return point.x >= rect.x && + point.x <= rect.x + rect.width && + point.y >= rect.y && + point.y <= rect.y + rect.height; +} + +template +double NormalizeComponentValue( + const typename Traits::Spec& spec, + double value) { + return Widgets::NormalizeUIEditorNumberFieldValue( + BuildComponentNumberSpec(spec, 0u), + value); +} + +template +bool TryParseComponentValue( + const typename Traits::Spec& spec, + std::string_view text, + double& outValue) { + return Widgets::TryParseUIEditorNumberFieldValue( + BuildComponentNumberSpec(spec, 0u), + text, + outValue); +} + +template +std::string FormatComponentValue( + const typename Traits::Spec& spec, + std::size_t componentIndex) { + return Widgets::FormatUIEditorNumberFieldValue( + BuildComponentNumberSpec(spec, componentIndex)); +} + +template +typename Traits::Layout BuildLayout( + const UIRect& bounds, + const typename Traits::Spec&, + const typename Traits::Metrics& metrics) { + static_assert(Traits::kComponentCount > 0u); + + const typename Traits::Metrics resolvedMetrics = ResolveMetrics(metrics); + const float gapCount = static_cast(Traits::kComponentCount - 1u); + const float requiredControlWidth = + resolvedMetrics.componentMinWidth * static_cast(Traits::kComponentCount) + + resolvedMetrics.componentGap * gapCount; + const Widgets::UIEditorFieldRowLayout hostLayout = + Widgets::BuildUIEditorFieldRowLayout( + bounds, + requiredControlWidth, + Widgets::UIEditorFieldRowLayoutMetrics{ + resolvedMetrics.rowHeight, + resolvedMetrics.horizontalPadding, + resolvedMetrics.labelControlGap, + resolvedMetrics.controlColumnStart, + resolvedMetrics.sharedControlColumnMinWidth, + resolvedMetrics.controlTrailingInset, + resolvedMetrics.controlInsetY, + }); + const float componentWidth = ClampNonNegative( + (hostLayout.controlRect.width - resolvedMetrics.componentGap * gapCount) / + static_cast(Traits::kComponentCount)); + + typename Traits::Layout layout = {}; + layout.bounds = hostLayout.bounds; + layout.labelRect = hostLayout.labelRect; + layout.controlRect = hostLayout.controlRect; + + for (std::size_t componentIndex = 0u; + componentIndex < layout.componentRects.size(); + ++componentIndex) { + const float componentX = + layout.controlRect.x + + (componentWidth + resolvedMetrics.componentGap) * + static_cast(componentIndex); + const UIRect componentRect( + componentX, + layout.controlRect.y, + componentWidth, + layout.controlRect.height); + const float prefixWidth = + (std::min)(resolvedMetrics.componentPrefixWidth, componentRect.width); + const float labelGap = ClampNonNegative(resolvedMetrics.componentLabelGap); + const float valueX = componentRect.x + prefixWidth + labelGap; + layout.componentRects[componentIndex] = componentRect; + layout.componentPrefixRects[componentIndex] = + UIRect(componentRect.x, componentRect.y, prefixWidth, componentRect.height); + layout.componentValueRects[componentIndex] = + UIRect( + valueX, + componentRect.y, + ClampNonNegative(componentRect.width - prefixWidth - labelGap), + componentRect.height); + } + + return layout; +} + +template +typename Traits::HitTarget HitTest( + const typename Traits::Layout& layout, + const UIPoint& point) { + for (std::size_t componentIndex = 0u; + componentIndex < layout.componentRects.size(); + ++componentIndex) { + if (IsPointInside(layout.componentRects[componentIndex], point)) { + return { Traits::kComponentHitTargetKind, componentIndex }; + } + } + + if (IsPointInside(layout.bounds, point)) { + return { Traits::kRowHitTargetKind, Traits::kInvalidComponentIndex }; + } + + return {}; +} + +template +void AppendBackground( + UIDrawList& drawList, + const typename Traits::Layout& layout, + const typename Traits::Spec& spec, + const typename Traits::State& state, + const typename Traits::Palette& palette, + const typename Traits::Metrics& metrics) { + const typename Traits::Palette resolvedPalette = ResolvePalette(palette); + const typename Traits::Metrics resolvedMetrics = ResolveMetrics(metrics); + + if (ResolveRowFillColor(state, resolvedPalette).a > 0.0f) { + drawList.AddFilledRect( + layout.bounds, + ResolveRowFillColor(state, resolvedPalette), + resolvedMetrics.cornerRounding); + } + if ((state.focused + ? resolvedPalette.focusedBorderColor.a + : resolvedPalette.borderColor.a) > 0.0f) { + drawList.AddRectOutline( + layout.bounds, + state.focused + ? resolvedPalette.focusedBorderColor + : resolvedPalette.borderColor, + state.focused + ? resolvedMetrics.focusedBorderThickness + : resolvedMetrics.borderThickness, + resolvedMetrics.cornerRounding); + } + + for (std::size_t componentIndex = 0u; + componentIndex < layout.componentRects.size(); + ++componentIndex) { + drawList.AddFilledRect( + layout.componentValueRects[componentIndex], + ResolveComponentFillColor( + spec, + state, + resolvedPalette, + componentIndex), + resolvedMetrics.componentRounding); + drawList.AddRectOutline( + layout.componentValueRects[componentIndex], + ResolveComponentBorderColor( + state, + resolvedPalette, + componentIndex), + resolvedMetrics.borderThickness, + resolvedMetrics.componentRounding); + } +} + +template +void AppendForeground( + UIDrawList& drawList, + const typename Traits::Layout& layout, + const typename Traits::Spec& spec, + const typename Traits::State& state, + const typename Traits::Palette& palette, + const typename Traits::Metrics& metrics) { + const typename Traits::Palette resolvedPalette = ResolvePalette(palette); + const typename Traits::Metrics resolvedMetrics = ResolveMetrics(metrics); + + drawList.PushClipRect( + Widgets::ResolveUIEditorTextClipRect( + layout.labelRect, + resolvedMetrics.labelFontSize)); + drawList.AddText( + UIPoint( + layout.labelRect.x, + Widgets::ResolveUIEditorTextTop( + layout.labelRect, + resolvedMetrics.labelFontSize, + resolvedMetrics.labelTextInsetY)), + spec.label, + resolvedPalette.labelColor, + resolvedMetrics.labelFontSize); + drawList.PopClipRect(); + + for (std::size_t componentIndex = 0u; + componentIndex < layout.componentRects.size(); + ++componentIndex) { + drawList.PushClipRect( + Widgets::ResolveUIEditorTextClipRect( + layout.componentPrefixRects[componentIndex], + resolvedMetrics.prefixFontSize)); + const std::string& componentLabel = spec.componentLabels[componentIndex]; + const float prefixTextX = + layout.componentPrefixRects[componentIndex].x + + ClampNonNegative( + (layout.componentPrefixRects[componentIndex].width - + ApproximateTextWidth( + resolvedMetrics.prefixFontSize, + componentLabel.size())) * + 0.5f) + + resolvedMetrics.prefixTextInsetX; + drawList.AddText( + UIPoint( + prefixTextX, + Widgets::ResolveUIEditorTextTop( + layout.componentPrefixRects[componentIndex], + resolvedMetrics.prefixFontSize, + resolvedMetrics.prefixTextInsetY)), + componentLabel, + resolvedPalette.labelColor, + resolvedMetrics.prefixFontSize); + drawList.PopClipRect(); + + drawList.PushClipRect( + Widgets::ResolveUIEditorTextClipRect( + layout.componentValueRects[componentIndex], + resolvedMetrics.valueFontSize)); + drawList.AddText( + UIPoint( + layout.componentValueRects[componentIndex].x + + resolvedMetrics.valueTextInsetX, + Widgets::ResolveUIEditorTextTop( + layout.componentValueRects[componentIndex], + resolvedMetrics.valueFontSize, + resolvedMetrics.valueTextInsetY)), + state.editing && state.selectedComponentIndex == componentIndex + ? state.displayTexts[componentIndex] + : FormatComponentValue(spec, componentIndex), + spec.readOnly + ? resolvedPalette.readOnlyValueColor + : resolvedPalette.valueColor, + resolvedMetrics.valueFontSize); + if (state.editing && state.selectedComponentIndex == componentIndex) { + Widgets::AppendUIEditorTextCaret( + drawList, + layout.componentValueRects[componentIndex], + state.displayTexts[componentIndex], + state.caretOffset, + state.caretBlinkStartNanoseconds, + resolvedPalette.valueColor, + resolvedMetrics.valueFontSize, + resolvedMetrics.valueTextInsetX, + resolvedMetrics.valueTextInsetY); + } + drawList.PopClipRect(); + } +} + +template +void AppendField( + UIDrawList& drawList, + const UIRect& bounds, + const typename Traits::Spec& spec, + const typename Traits::State& state, + const typename Traits::Palette& palette, + const typename Traits::Metrics& metrics) { + const typename Traits::Palette resolvedPalette = ResolvePalette(palette); + const typename Traits::Metrics resolvedMetrics = ResolveMetrics(metrics); + const typename Traits::Layout layout = + BuildLayout(bounds, spec, resolvedMetrics); + AppendBackground( + drawList, + layout, + spec, + state, + resolvedPalette, + resolvedMetrics); + AppendForeground( + drawList, + layout, + spec, + state, + resolvedPalette, + resolvedMetrics); +} + +} // namespace XCEngine::UI::Editor::Internal::VectorFieldWidgetShared