Add editor typed field widgets and validation scenarios
This commit is contained in:
@@ -15,10 +15,13 @@ add_library(XCUIEditorLib STATIC
|
||||
src/Core/EditorShellAsset.cpp
|
||||
src/Core/UIEditorCommandDispatcher.cpp
|
||||
src/Core/UIEditorCommandRegistry.cpp
|
||||
src/Core/UIEditorBoolFieldInteraction.cpp
|
||||
src/Core/UIEditorDockHostInteraction.cpp
|
||||
src/Core/UIEditorEnumFieldInteraction.cpp
|
||||
src/Core/UIEditorListViewInteraction.cpp
|
||||
src/Core/UIEditorMenuModel.cpp
|
||||
src/Core/UIEditorMenuSession.cpp
|
||||
src/Core/UIEditorNumberFieldInteraction.cpp
|
||||
src/Core/UIEditorPanelContentHost.cpp
|
||||
src/Core/UIEditorPanelHostLifecycle.cpp
|
||||
src/Core/UIEditorPanelRegistry.cpp
|
||||
@@ -37,10 +40,13 @@ add_library(XCUIEditorLib STATIC
|
||||
src/Core/UIEditorWorkspaceModel.cpp
|
||||
src/Core/UIEditorWorkspaceSession.cpp
|
||||
src/Widgets/UIEditorCollectionPrimitives.cpp
|
||||
src/Widgets/UIEditorBoolField.cpp
|
||||
src/Widgets/UIEditorDockHost.cpp
|
||||
src/Widgets/UIEditorEnumField.cpp
|
||||
src/Widgets/UIEditorListView.cpp
|
||||
src/Widgets/UIEditorMenuBar.cpp
|
||||
src/Widgets/UIEditorMenuPopup.cpp
|
||||
src/Widgets/UIEditorNumberField.cpp
|
||||
src/Widgets/UIEditorPanelFrame.cpp
|
||||
src/Widgets/UIEditorPropertyGrid.cpp
|
||||
src/Widgets/UIEditorScrollView.cpp
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEditor/Widgets/UIEditorBoolField.h>
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine::UI::Editor {
|
||||
|
||||
struct UIEditorBoolFieldInteractionState {
|
||||
Widgets::UIEditorBoolFieldState fieldState = {};
|
||||
::XCEngine::UI::UIPoint pointerPosition = {};
|
||||
bool hasPointerPosition = false;
|
||||
};
|
||||
|
||||
struct UIEditorBoolFieldInteractionResult {
|
||||
bool consumed = false;
|
||||
bool valueChanged = false;
|
||||
bool focusedChanged = false;
|
||||
bool newValue = false;
|
||||
Widgets::UIEditorBoolFieldHitTarget hitTarget = {};
|
||||
};
|
||||
|
||||
struct UIEditorBoolFieldInteractionFrame {
|
||||
Widgets::UIEditorBoolFieldLayout layout = {};
|
||||
UIEditorBoolFieldInteractionResult result = {};
|
||||
};
|
||||
|
||||
UIEditorBoolFieldInteractionFrame UpdateUIEditorBoolFieldInteraction(
|
||||
UIEditorBoolFieldInteractionState& state,
|
||||
bool& value,
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const Widgets::UIEditorBoolFieldSpec& spec,
|
||||
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
|
||||
const Widgets::UIEditorBoolFieldMetrics& metrics = {});
|
||||
|
||||
} // namespace XCEngine::UI::Editor
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEditor/Widgets/UIEditorEnumField.h>
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine::UI::Editor {
|
||||
|
||||
struct UIEditorEnumFieldInteractionState {
|
||||
Widgets::UIEditorEnumFieldState fieldState = {};
|
||||
::XCEngine::UI::UIPoint pointerPosition = {};
|
||||
bool hasPointerPosition = false;
|
||||
};
|
||||
|
||||
struct UIEditorEnumFieldInteractionResult {
|
||||
bool consumed = false;
|
||||
bool selectionChanged = false;
|
||||
bool focusedChanged = false;
|
||||
std::size_t selectedIndex = 0u;
|
||||
Widgets::UIEditorEnumFieldHitTarget hitTarget = {};
|
||||
};
|
||||
|
||||
struct UIEditorEnumFieldInteractionFrame {
|
||||
Widgets::UIEditorEnumFieldLayout layout = {};
|
||||
UIEditorEnumFieldInteractionResult result = {};
|
||||
};
|
||||
|
||||
UIEditorEnumFieldInteractionFrame UpdateUIEditorEnumFieldInteraction(
|
||||
UIEditorEnumFieldInteractionState& state,
|
||||
std::size_t& selectedIndex,
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const Widgets::UIEditorEnumFieldSpec& spec,
|
||||
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
|
||||
const Widgets::UIEditorEnumFieldMetrics& metrics = {});
|
||||
|
||||
} // namespace XCEngine::UI::Editor
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEditor/Widgets/UIEditorNumberField.h>
|
||||
|
||||
#include <XCEngine/UI/Text/UITextInputController.h>
|
||||
#include <XCEngine/UI/Types.h>
|
||||
#include <XCEngine/UI/Widgets/UIPropertyEditModel.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine::UI::Editor {
|
||||
|
||||
struct UIEditorNumberFieldInteractionState {
|
||||
Widgets::UIEditorNumberFieldState numberFieldState = {};
|
||||
::XCEngine::UI::Text::UITextInputState textInputState = {};
|
||||
::XCEngine::UI::Widgets::UIPropertyEditModel editModel = {};
|
||||
::XCEngine::UI::UIPoint pointerPosition = {};
|
||||
bool hasPointerPosition = false;
|
||||
};
|
||||
|
||||
struct UIEditorNumberFieldInteractionResult {
|
||||
bool consumed = false;
|
||||
bool focusChanged = false;
|
||||
bool valueChanged = false;
|
||||
bool stepApplied = false;
|
||||
bool editStarted = false;
|
||||
bool editCommitted = false;
|
||||
bool editCommitRejected = false;
|
||||
bool editCanceled = false;
|
||||
Widgets::UIEditorNumberFieldHitTarget hitTarget = {};
|
||||
double valueBefore = 0.0;
|
||||
double valueAfter = 0.0;
|
||||
double stepDelta = 0.0;
|
||||
std::string committedText = {};
|
||||
};
|
||||
|
||||
struct UIEditorNumberFieldInteractionFrame {
|
||||
Widgets::UIEditorNumberFieldLayout layout = {};
|
||||
UIEditorNumberFieldInteractionResult result = {};
|
||||
};
|
||||
|
||||
UIEditorNumberFieldInteractionFrame UpdateUIEditorNumberFieldInteraction(
|
||||
UIEditorNumberFieldInteractionState& state,
|
||||
Widgets::UIEditorNumberFieldSpec& spec,
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
|
||||
const Widgets::UIEditorNumberFieldMetrics& metrics = {});
|
||||
|
||||
} // namespace XCEngine::UI::Editor
|
||||
113
new_editor/include/XCEditor/Widgets/UIEditorBoolField.h
Normal file
113
new_editor/include/XCEditor/Widgets/UIEditorBoolField.h
Normal file
@@ -0,0 +1,113 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine::UI::Editor::Widgets {
|
||||
|
||||
enum class UIEditorBoolFieldHitTargetKind : std::uint8_t {
|
||||
None = 0,
|
||||
Row,
|
||||
Toggle
|
||||
};
|
||||
|
||||
struct UIEditorBoolFieldSpec {
|
||||
std::string fieldId = {};
|
||||
std::string label = {};
|
||||
bool value = false;
|
||||
bool readOnly = false;
|
||||
};
|
||||
|
||||
struct UIEditorBoolFieldState {
|
||||
UIEditorBoolFieldHitTargetKind hoveredTarget = UIEditorBoolFieldHitTargetKind::None;
|
||||
bool focused = false;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
struct UIEditorBoolFieldMetrics {
|
||||
float rowHeight = 32.0f;
|
||||
float horizontalPadding = 12.0f;
|
||||
float labelControlGap = 20.0f;
|
||||
float controlColumnStart = 236.0f;
|
||||
float toggleWidth = 42.0f;
|
||||
float toggleHeight = 20.0f;
|
||||
float toggleKnobInset = 3.0f;
|
||||
float textInsetY = 8.0f;
|
||||
float cornerRounding = 6.0f;
|
||||
float borderThickness = 1.0f;
|
||||
float focusedBorderThickness = 2.0f;
|
||||
};
|
||||
|
||||
struct UIEditorBoolFieldPalette {
|
||||
::XCEngine::UI::UIColor surfaceColor =
|
||||
::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f);
|
||||
::XCEngine::UI::UIColor borderColor =
|
||||
::XCEngine::UI::UIColor(0.29f, 0.29f, 0.29f, 1.0f);
|
||||
::XCEngine::UI::UIColor focusedBorderColor =
|
||||
::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f);
|
||||
::XCEngine::UI::UIColor rowHoverColor =
|
||||
::XCEngine::UI::UIColor(0.22f, 0.22f, 0.22f, 1.0f);
|
||||
::XCEngine::UI::UIColor rowActiveColor =
|
||||
::XCEngine::UI::UIColor(0.28f, 0.28f, 0.28f, 1.0f);
|
||||
::XCEngine::UI::UIColor toggleOffColor =
|
||||
::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f);
|
||||
::XCEngine::UI::UIColor toggleOnColor =
|
||||
::XCEngine::UI::UIColor(0.78f, 0.78f, 0.78f, 1.0f);
|
||||
::XCEngine::UI::UIColor toggleReadOnlyColor =
|
||||
::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f);
|
||||
::XCEngine::UI::UIColor toggleBorderColor =
|
||||
::XCEngine::UI::UIColor(0.36f, 0.36f, 0.36f, 1.0f);
|
||||
::XCEngine::UI::UIColor knobColor =
|
||||
::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f);
|
||||
::XCEngine::UI::UIColor labelColor =
|
||||
::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f);
|
||||
::XCEngine::UI::UIColor valueColor =
|
||||
::XCEngine::UI::UIColor(0.70f, 0.70f, 0.70f, 1.0f);
|
||||
};
|
||||
|
||||
struct UIEditorBoolFieldLayout {
|
||||
::XCEngine::UI::UIRect bounds = {};
|
||||
::XCEngine::UI::UIRect labelRect = {};
|
||||
::XCEngine::UI::UIRect toggleRect = {};
|
||||
::XCEngine::UI::UIRect knobRect = {};
|
||||
};
|
||||
|
||||
struct UIEditorBoolFieldHitTarget {
|
||||
UIEditorBoolFieldHitTargetKind kind = UIEditorBoolFieldHitTargetKind::None;
|
||||
};
|
||||
|
||||
UIEditorBoolFieldLayout BuildUIEditorBoolFieldLayout(
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const UIEditorBoolFieldSpec& spec,
|
||||
const UIEditorBoolFieldMetrics& metrics = {});
|
||||
|
||||
UIEditorBoolFieldHitTarget HitTestUIEditorBoolField(
|
||||
const UIEditorBoolFieldLayout& layout,
|
||||
const ::XCEngine::UI::UIPoint& point);
|
||||
|
||||
void AppendUIEditorBoolFieldBackground(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const UIEditorBoolFieldLayout& layout,
|
||||
const UIEditorBoolFieldSpec& spec,
|
||||
const UIEditorBoolFieldState& state,
|
||||
const UIEditorBoolFieldPalette& palette = {},
|
||||
const UIEditorBoolFieldMetrics& metrics = {});
|
||||
|
||||
void AppendUIEditorBoolFieldForeground(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const UIEditorBoolFieldLayout& layout,
|
||||
const UIEditorBoolFieldSpec& spec,
|
||||
const UIEditorBoolFieldPalette& palette = {},
|
||||
const UIEditorBoolFieldMetrics& metrics = {});
|
||||
|
||||
void AppendUIEditorBoolField(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const UIEditorBoolFieldSpec& spec,
|
||||
const UIEditorBoolFieldState& state,
|
||||
const UIEditorBoolFieldPalette& palette = {},
|
||||
const UIEditorBoolFieldMetrics& metrics = {});
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Widgets
|
||||
127
new_editor/include/XCEditor/Widgets/UIEditorEnumField.h
Normal file
127
new_editor/include/XCEditor/Widgets/UIEditorEnumField.h
Normal file
@@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine::UI::Editor::Widgets {
|
||||
|
||||
enum class UIEditorEnumFieldHitTargetKind : std::uint8_t {
|
||||
None = 0,
|
||||
Row,
|
||||
PreviousButton,
|
||||
NextButton,
|
||||
ValueBox
|
||||
};
|
||||
|
||||
struct UIEditorEnumFieldSpec {
|
||||
std::string fieldId = {};
|
||||
std::string label = {};
|
||||
std::vector<std::string> options = {};
|
||||
std::size_t selectedIndex = 0u;
|
||||
bool readOnly = false;
|
||||
};
|
||||
|
||||
struct UIEditorEnumFieldState {
|
||||
UIEditorEnumFieldHitTargetKind hoveredTarget = UIEditorEnumFieldHitTargetKind::None;
|
||||
bool focused = false;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
struct UIEditorEnumFieldMetrics {
|
||||
float rowHeight = 32.0f;
|
||||
float horizontalPadding = 12.0f;
|
||||
float labelControlGap = 20.0f;
|
||||
float controlColumnStart = 236.0f;
|
||||
float buttonWidth = 24.0f;
|
||||
float controlGap = 4.0f;
|
||||
float valueBoxMinWidth = 96.0f;
|
||||
float valueBoxHeight = 24.0f;
|
||||
float labelTextInsetY = 8.0f;
|
||||
float valueTextInsetX = 8.0f;
|
||||
float valueTextInsetY = 6.0f;
|
||||
float buttonTextInsetX = 8.0f;
|
||||
float buttonTextInsetY = 6.0f;
|
||||
float cornerRounding = 6.0f;
|
||||
float borderThickness = 1.0f;
|
||||
float focusedBorderThickness = 2.0f;
|
||||
};
|
||||
|
||||
struct UIEditorEnumFieldPalette {
|
||||
::XCEngine::UI::UIColor surfaceColor =
|
||||
::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f);
|
||||
::XCEngine::UI::UIColor borderColor =
|
||||
::XCEngine::UI::UIColor(0.29f, 0.29f, 0.29f, 1.0f);
|
||||
::XCEngine::UI::UIColor focusedBorderColor =
|
||||
::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f);
|
||||
::XCEngine::UI::UIColor rowHoverColor =
|
||||
::XCEngine::UI::UIColor(0.22f, 0.22f, 0.22f, 1.0f);
|
||||
::XCEngine::UI::UIColor rowActiveColor =
|
||||
::XCEngine::UI::UIColor(0.28f, 0.28f, 0.28f, 1.0f);
|
||||
::XCEngine::UI::UIColor valueBoxColor =
|
||||
::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f);
|
||||
::XCEngine::UI::UIColor buttonColor =
|
||||
::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f);
|
||||
::XCEngine::UI::UIColor buttonHoverColor =
|
||||
::XCEngine::UI::UIColor(0.30f, 0.30f, 0.30f, 1.0f);
|
||||
::XCEngine::UI::UIColor readOnlyColor =
|
||||
::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f);
|
||||
::XCEngine::UI::UIColor controlBorderColor =
|
||||
::XCEngine::UI::UIColor(0.36f, 0.36f, 0.36f, 1.0f);
|
||||
::XCEngine::UI::UIColor labelColor =
|
||||
::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f);
|
||||
::XCEngine::UI::UIColor valueColor =
|
||||
::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f);
|
||||
};
|
||||
|
||||
struct UIEditorEnumFieldLayout {
|
||||
::XCEngine::UI::UIRect bounds = {};
|
||||
::XCEngine::UI::UIRect labelRect = {};
|
||||
::XCEngine::UI::UIRect controlRect = {};
|
||||
::XCEngine::UI::UIRect valueRect = {};
|
||||
::XCEngine::UI::UIRect previousRect = {};
|
||||
::XCEngine::UI::UIRect nextRect = {};
|
||||
};
|
||||
|
||||
struct UIEditorEnumFieldHitTarget {
|
||||
UIEditorEnumFieldHitTargetKind kind = UIEditorEnumFieldHitTargetKind::None;
|
||||
};
|
||||
|
||||
std::string ResolveUIEditorEnumFieldValueText(const UIEditorEnumFieldSpec& spec);
|
||||
|
||||
UIEditorEnumFieldLayout BuildUIEditorEnumFieldLayout(
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const UIEditorEnumFieldSpec& spec,
|
||||
const UIEditorEnumFieldMetrics& metrics = {});
|
||||
|
||||
UIEditorEnumFieldHitTarget HitTestUIEditorEnumField(
|
||||
const UIEditorEnumFieldLayout& layout,
|
||||
const ::XCEngine::UI::UIPoint& point);
|
||||
|
||||
void AppendUIEditorEnumFieldBackground(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const UIEditorEnumFieldLayout& layout,
|
||||
const UIEditorEnumFieldSpec& spec,
|
||||
const UIEditorEnumFieldState& state,
|
||||
const UIEditorEnumFieldPalette& palette = {},
|
||||
const UIEditorEnumFieldMetrics& metrics = {});
|
||||
|
||||
void AppendUIEditorEnumFieldForeground(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const UIEditorEnumFieldLayout& layout,
|
||||
const UIEditorEnumFieldSpec& spec,
|
||||
const UIEditorEnumFieldPalette& palette = {},
|
||||
const UIEditorEnumFieldMetrics& metrics = {});
|
||||
|
||||
void AppendUIEditorEnumField(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const UIEditorEnumFieldSpec& spec,
|
||||
const UIEditorEnumFieldState& state,
|
||||
const UIEditorEnumFieldPalette& palette = {},
|
||||
const UIEditorEnumFieldMetrics& metrics = {});
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Widgets
|
||||
146
new_editor/include/XCEditor/Widgets/UIEditorNumberField.h
Normal file
146
new_editor/include/XCEditor/Widgets/UIEditorNumberField.h
Normal file
@@ -0,0 +1,146 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine::UI::Editor::Widgets {
|
||||
|
||||
enum class UIEditorNumberFieldHitTargetKind : std::uint8_t {
|
||||
None = 0,
|
||||
Row,
|
||||
DecrementButton,
|
||||
IncrementButton,
|
||||
ValueBox
|
||||
};
|
||||
|
||||
struct UIEditorNumberFieldSpec {
|
||||
std::string fieldId = {};
|
||||
std::string label = {};
|
||||
double value = 0.0;
|
||||
double step = 1.0;
|
||||
double minValue = 0.0;
|
||||
double maxValue = 100.0;
|
||||
bool integerMode = true;
|
||||
bool readOnly = false;
|
||||
};
|
||||
|
||||
struct UIEditorNumberFieldState {
|
||||
UIEditorNumberFieldHitTargetKind hoveredTarget = UIEditorNumberFieldHitTargetKind::None;
|
||||
UIEditorNumberFieldHitTargetKind activeTarget = UIEditorNumberFieldHitTargetKind::None;
|
||||
bool focused = false;
|
||||
bool editing = false;
|
||||
std::string displayText = {};
|
||||
};
|
||||
|
||||
struct UIEditorNumberFieldMetrics {
|
||||
float rowHeight = 32.0f;
|
||||
float horizontalPadding = 12.0f;
|
||||
float labelControlGap = 20.0f;
|
||||
float controlColumnStart = 236.0f;
|
||||
float buttonWidth = 22.0f;
|
||||
float buttonGap = 4.0f;
|
||||
float valueBoxMinWidth = 72.0f;
|
||||
float controlInsetY = 4.0f;
|
||||
float labelTextInsetY = 8.0f;
|
||||
float valueTextInsetX = 8.0f;
|
||||
float valueTextInsetY = 8.0f;
|
||||
float buttonTextInsetY = 8.0f;
|
||||
float cornerRounding = 6.0f;
|
||||
float valueBoxRounding = 5.0f;
|
||||
float buttonRounding = 4.0f;
|
||||
float borderThickness = 1.0f;
|
||||
float focusedBorderThickness = 2.0f;
|
||||
};
|
||||
|
||||
struct UIEditorNumberFieldPalette {
|
||||
::XCEngine::UI::UIColor surfaceColor =
|
||||
::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f);
|
||||
::XCEngine::UI::UIColor borderColor =
|
||||
::XCEngine::UI::UIColor(0.29f, 0.29f, 0.29f, 1.0f);
|
||||
::XCEngine::UI::UIColor focusedBorderColor =
|
||||
::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f);
|
||||
::XCEngine::UI::UIColor rowHoverColor =
|
||||
::XCEngine::UI::UIColor(0.22f, 0.22f, 0.22f, 1.0f);
|
||||
::XCEngine::UI::UIColor rowActiveColor =
|
||||
::XCEngine::UI::UIColor(0.28f, 0.28f, 0.28f, 1.0f);
|
||||
::XCEngine::UI::UIColor valueBoxColor =
|
||||
::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f);
|
||||
::XCEngine::UI::UIColor valueBoxHoverColor =
|
||||
::XCEngine::UI::UIColor(0.22f, 0.22f, 0.22f, 1.0f);
|
||||
::XCEngine::UI::UIColor valueBoxEditingColor =
|
||||
::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f);
|
||||
::XCEngine::UI::UIColor buttonColor =
|
||||
::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f);
|
||||
::XCEngine::UI::UIColor buttonHoverColor =
|
||||
::XCEngine::UI::UIColor(0.30f, 0.30f, 0.30f, 1.0f);
|
||||
::XCEngine::UI::UIColor buttonActiveColor =
|
||||
::XCEngine::UI::UIColor(0.36f, 0.36f, 0.36f, 1.0f);
|
||||
::XCEngine::UI::UIColor readOnlyColor =
|
||||
::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f);
|
||||
::XCEngine::UI::UIColor controlBorderColor =
|
||||
::XCEngine::UI::UIColor(0.36f, 0.36f, 0.36f, 1.0f);
|
||||
::XCEngine::UI::UIColor labelColor =
|
||||
::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f);
|
||||
::XCEngine::UI::UIColor valueColor =
|
||||
::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f);
|
||||
::XCEngine::UI::UIColor readOnlyValueColor =
|
||||
::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f);
|
||||
::XCEngine::UI::UIColor stepTextColor =
|
||||
::XCEngine::UI::UIColor(0.90f, 0.90f, 0.90f, 1.0f);
|
||||
};
|
||||
|
||||
struct UIEditorNumberFieldLayout {
|
||||
::XCEngine::UI::UIRect bounds = {};
|
||||
::XCEngine::UI::UIRect labelRect = {};
|
||||
::XCEngine::UI::UIRect controlRect = {};
|
||||
::XCEngine::UI::UIRect valueRect = {};
|
||||
::XCEngine::UI::UIRect decrementRect = {};
|
||||
::XCEngine::UI::UIRect incrementRect = {};
|
||||
};
|
||||
|
||||
struct UIEditorNumberFieldHitTarget {
|
||||
UIEditorNumberFieldHitTargetKind kind = UIEditorNumberFieldHitTargetKind::None;
|
||||
};
|
||||
|
||||
bool IsUIEditorNumberFieldPointInside(
|
||||
const ::XCEngine::UI::UIRect& rect,
|
||||
const ::XCEngine::UI::UIPoint& point);
|
||||
|
||||
std::string FormatUIEditorNumberFieldValue(const UIEditorNumberFieldSpec& spec);
|
||||
|
||||
UIEditorNumberFieldLayout BuildUIEditorNumberFieldLayout(
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const UIEditorNumberFieldSpec& spec,
|
||||
const UIEditorNumberFieldMetrics& metrics = {});
|
||||
|
||||
UIEditorNumberFieldHitTarget HitTestUIEditorNumberField(
|
||||
const UIEditorNumberFieldLayout& layout,
|
||||
const ::XCEngine::UI::UIPoint& point);
|
||||
|
||||
void AppendUIEditorNumberFieldBackground(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const UIEditorNumberFieldLayout& layout,
|
||||
const UIEditorNumberFieldSpec& spec,
|
||||
const UIEditorNumberFieldState& state,
|
||||
const UIEditorNumberFieldPalette& palette = {},
|
||||
const UIEditorNumberFieldMetrics& metrics = {});
|
||||
|
||||
void AppendUIEditorNumberFieldForeground(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const UIEditorNumberFieldLayout& layout,
|
||||
const UIEditorNumberFieldSpec& spec,
|
||||
const UIEditorNumberFieldPalette& palette = {},
|
||||
const UIEditorNumberFieldMetrics& metrics = {});
|
||||
|
||||
void AppendUIEditorNumberField(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const UIEditorNumberFieldSpec& spec,
|
||||
const UIEditorNumberFieldState& state,
|
||||
const UIEditorNumberFieldPalette& palette = {},
|
||||
const UIEditorNumberFieldMetrics& metrics = {});
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Widgets
|
||||
143
new_editor/src/Core/UIEditorBoolFieldInteraction.cpp
Normal file
143
new_editor/src/Core/UIEditorBoolFieldInteraction.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
#include <XCEditor/Core/UIEditorBoolFieldInteraction.h>
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
|
||||
namespace XCEngine::UI::Editor {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::XCEngine::Input::KeyCode;
|
||||
using ::XCEngine::UI::UIInputEvent;
|
||||
using ::XCEngine::UI::UIInputEventType;
|
||||
using ::XCEngine::UI::UIPointerButton;
|
||||
using Widgets::BuildUIEditorBoolFieldLayout;
|
||||
using Widgets::HitTestUIEditorBoolField;
|
||||
using Widgets::UIEditorBoolFieldHitTargetKind;
|
||||
|
||||
bool ShouldUsePointerPosition(const UIInputEvent& event) {
|
||||
switch (event.type) {
|
||||
case UIInputEventType::PointerMove:
|
||||
case UIInputEventType::PointerEnter:
|
||||
case UIInputEventType::PointerButtonDown:
|
||||
case UIInputEventType::PointerButtonUp:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UIEditorBoolFieldInteractionFrame UpdateUIEditorBoolFieldInteraction(
|
||||
UIEditorBoolFieldInteractionState& state,
|
||||
bool& value,
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const Widgets::UIEditorBoolFieldSpec& spec,
|
||||
const std::vector<UIInputEvent>& inputEvents,
|
||||
const Widgets::UIEditorBoolFieldMetrics& metrics) {
|
||||
Widgets::UIEditorBoolFieldSpec resolvedSpec = spec;
|
||||
resolvedSpec.value = value;
|
||||
|
||||
Widgets::UIEditorBoolFieldLayout layout =
|
||||
BuildUIEditorBoolFieldLayout(bounds, resolvedSpec, metrics);
|
||||
if (state.hasPointerPosition) {
|
||||
state.fieldState.hoveredTarget = HitTestUIEditorBoolField(layout, state.pointerPosition).kind;
|
||||
} else {
|
||||
state.fieldState.hoveredTarget = UIEditorBoolFieldHitTargetKind::None;
|
||||
}
|
||||
|
||||
UIEditorBoolFieldInteractionResult interactionResult = {};
|
||||
for (const UIInputEvent& event : inputEvents) {
|
||||
if (ShouldUsePointerPosition(event)) {
|
||||
state.pointerPosition = event.position;
|
||||
state.hasPointerPosition = true;
|
||||
} else if (event.type == UIInputEventType::PointerLeave) {
|
||||
state.hasPointerPosition = false;
|
||||
}
|
||||
|
||||
UIEditorBoolFieldInteractionResult eventResult = {};
|
||||
switch (event.type) {
|
||||
case UIInputEventType::FocusGained:
|
||||
state.fieldState.focused = true;
|
||||
eventResult.focusedChanged = true;
|
||||
break;
|
||||
|
||||
case UIInputEventType::FocusLost:
|
||||
state.fieldState.focused = false;
|
||||
state.fieldState.active = false;
|
||||
state.hasPointerPosition = false;
|
||||
state.fieldState.hoveredTarget = UIEditorBoolFieldHitTargetKind::None;
|
||||
eventResult.focusedChanged = true;
|
||||
break;
|
||||
|
||||
case UIInputEventType::PointerMove:
|
||||
case UIInputEventType::PointerEnter:
|
||||
case UIInputEventType::PointerLeave:
|
||||
break;
|
||||
|
||||
case UIInputEventType::PointerButtonDown:
|
||||
eventResult.hitTarget = state.hasPointerPosition
|
||||
? HitTestUIEditorBoolField(layout, state.pointerPosition)
|
||||
: Widgets::UIEditorBoolFieldHitTarget {};
|
||||
if (event.pointerButton == UIPointerButton::Left &&
|
||||
eventResult.hitTarget.kind != UIEditorBoolFieldHitTargetKind::None) {
|
||||
state.fieldState.focused = true;
|
||||
state.fieldState.active = true;
|
||||
eventResult.consumed = true;
|
||||
} else if (event.pointerButton == UIPointerButton::Left) {
|
||||
state.fieldState.focused = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case UIInputEventType::PointerButtonUp:
|
||||
eventResult.hitTarget = state.hasPointerPosition
|
||||
? HitTestUIEditorBoolField(layout, state.pointerPosition)
|
||||
: Widgets::UIEditorBoolFieldHitTarget {};
|
||||
if (event.pointerButton == UIPointerButton::Left &&
|
||||
state.fieldState.active &&
|
||||
eventResult.hitTarget.kind != UIEditorBoolFieldHitTargetKind::None &&
|
||||
!resolvedSpec.readOnly) {
|
||||
value = !value;
|
||||
eventResult.valueChanged = true;
|
||||
eventResult.newValue = value;
|
||||
eventResult.consumed = true;
|
||||
}
|
||||
state.fieldState.active = false;
|
||||
break;
|
||||
|
||||
case UIInputEventType::KeyDown:
|
||||
if (state.fieldState.focused &&
|
||||
!resolvedSpec.readOnly &&
|
||||
(static_cast<KeyCode>(event.keyCode) == KeyCode::Space ||
|
||||
static_cast<KeyCode>(event.keyCode) == KeyCode::Enter)) {
|
||||
value = !value;
|
||||
eventResult.valueChanged = true;
|
||||
eventResult.newValue = value;
|
||||
eventResult.consumed = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
resolvedSpec.value = value;
|
||||
layout = BuildUIEditorBoolFieldLayout(bounds, resolvedSpec, metrics);
|
||||
if (state.hasPointerPosition) {
|
||||
state.fieldState.hoveredTarget = HitTestUIEditorBoolField(layout, state.pointerPosition).kind;
|
||||
} else {
|
||||
state.fieldState.hoveredTarget = UIEditorBoolFieldHitTargetKind::None;
|
||||
}
|
||||
|
||||
if (eventResult.consumed ||
|
||||
eventResult.valueChanged ||
|
||||
eventResult.focusedChanged ||
|
||||
eventResult.hitTarget.kind != UIEditorBoolFieldHitTargetKind::None) {
|
||||
interactionResult = eventResult;
|
||||
}
|
||||
}
|
||||
|
||||
return { layout, interactionResult };
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor
|
||||
190
new_editor/src/Core/UIEditorEnumFieldInteraction.cpp
Normal file
190
new_editor/src/Core/UIEditorEnumFieldInteraction.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
#include <XCEditor/Core/UIEditorEnumFieldInteraction.h>
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
|
||||
namespace XCEngine::UI::Editor {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::XCEngine::Input::KeyCode;
|
||||
using ::XCEngine::UI::UIInputEvent;
|
||||
using ::XCEngine::UI::UIInputEventType;
|
||||
using ::XCEngine::UI::UIPointerButton;
|
||||
using Widgets::BuildUIEditorEnumFieldLayout;
|
||||
using Widgets::HitTestUIEditorEnumField;
|
||||
using Widgets::UIEditorEnumFieldHitTargetKind;
|
||||
|
||||
bool ShouldUsePointerPosition(const UIInputEvent& event) {
|
||||
switch (event.type) {
|
||||
case UIInputEventType::PointerMove:
|
||||
case UIInputEventType::PointerEnter:
|
||||
case UIInputEventType::PointerButtonDown:
|
||||
case UIInputEventType::PointerButtonUp:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool MoveSelection(std::size_t& selectedIndex, const Widgets::UIEditorEnumFieldSpec& spec, int direction) {
|
||||
if (spec.options.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t nextIndex = selectedIndex;
|
||||
if (direction < 0) {
|
||||
nextIndex = selectedIndex == 0u ? 0u : selectedIndex - 1u;
|
||||
} else {
|
||||
nextIndex = (selectedIndex + 1u >= spec.options.size()) ? spec.options.size() - 1u : selectedIndex + 1u;
|
||||
}
|
||||
|
||||
if (nextIndex == selectedIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
selectedIndex = nextIndex;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UIEditorEnumFieldInteractionFrame UpdateUIEditorEnumFieldInteraction(
|
||||
UIEditorEnumFieldInteractionState& state,
|
||||
std::size_t& selectedIndex,
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const Widgets::UIEditorEnumFieldSpec& spec,
|
||||
const std::vector<UIInputEvent>& inputEvents,
|
||||
const Widgets::UIEditorEnumFieldMetrics& metrics) {
|
||||
Widgets::UIEditorEnumFieldSpec resolvedSpec = spec;
|
||||
if (!resolvedSpec.options.empty() && selectedIndex >= resolvedSpec.options.size()) {
|
||||
selectedIndex = resolvedSpec.options.size() - 1u;
|
||||
}
|
||||
resolvedSpec.selectedIndex = selectedIndex;
|
||||
|
||||
Widgets::UIEditorEnumFieldLayout layout =
|
||||
BuildUIEditorEnumFieldLayout(bounds, resolvedSpec, metrics);
|
||||
if (state.hasPointerPosition) {
|
||||
state.fieldState.hoveredTarget = HitTestUIEditorEnumField(layout, state.pointerPosition).kind;
|
||||
} else {
|
||||
state.fieldState.hoveredTarget = UIEditorEnumFieldHitTargetKind::None;
|
||||
}
|
||||
|
||||
UIEditorEnumFieldInteractionResult interactionResult = {};
|
||||
for (const UIInputEvent& event : inputEvents) {
|
||||
if (ShouldUsePointerPosition(event)) {
|
||||
state.pointerPosition = event.position;
|
||||
state.hasPointerPosition = true;
|
||||
} else if (event.type == UIInputEventType::PointerLeave) {
|
||||
state.hasPointerPosition = false;
|
||||
}
|
||||
|
||||
UIEditorEnumFieldInteractionResult eventResult = {};
|
||||
switch (event.type) {
|
||||
case UIInputEventType::FocusGained:
|
||||
state.fieldState.focused = true;
|
||||
break;
|
||||
|
||||
case UIInputEventType::FocusLost:
|
||||
state.fieldState.focused = false;
|
||||
state.fieldState.active = false;
|
||||
state.hasPointerPosition = false;
|
||||
state.fieldState.hoveredTarget = UIEditorEnumFieldHitTargetKind::None;
|
||||
break;
|
||||
|
||||
case UIInputEventType::PointerMove:
|
||||
case UIInputEventType::PointerEnter:
|
||||
case UIInputEventType::PointerLeave:
|
||||
break;
|
||||
|
||||
case UIInputEventType::PointerButtonDown:
|
||||
eventResult.hitTarget = state.hasPointerPosition
|
||||
? HitTestUIEditorEnumField(layout, state.pointerPosition)
|
||||
: Widgets::UIEditorEnumFieldHitTarget {};
|
||||
if (event.pointerButton == UIPointerButton::Left &&
|
||||
eventResult.hitTarget.kind != UIEditorEnumFieldHitTargetKind::None) {
|
||||
state.fieldState.focused = true;
|
||||
state.fieldState.active = true;
|
||||
eventResult.consumed = true;
|
||||
} else if (event.pointerButton == UIPointerButton::Left) {
|
||||
state.fieldState.focused = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case UIInputEventType::PointerButtonUp:
|
||||
eventResult.hitTarget = state.hasPointerPosition
|
||||
? HitTestUIEditorEnumField(layout, state.pointerPosition)
|
||||
: Widgets::UIEditorEnumFieldHitTarget {};
|
||||
if (event.pointerButton == UIPointerButton::Left &&
|
||||
state.fieldState.active &&
|
||||
!resolvedSpec.readOnly) {
|
||||
bool changed = false;
|
||||
if (eventResult.hitTarget.kind == UIEditorEnumFieldHitTargetKind::PreviousButton) {
|
||||
changed = MoveSelection(selectedIndex, resolvedSpec, -1);
|
||||
} else if (eventResult.hitTarget.kind == UIEditorEnumFieldHitTargetKind::NextButton) {
|
||||
changed = MoveSelection(selectedIndex, resolvedSpec, 1);
|
||||
}
|
||||
if (changed) {
|
||||
eventResult.selectionChanged = true;
|
||||
eventResult.selectedIndex = selectedIndex;
|
||||
eventResult.consumed = true;
|
||||
}
|
||||
}
|
||||
state.fieldState.active = false;
|
||||
break;
|
||||
|
||||
case UIInputEventType::KeyDown:
|
||||
if (state.fieldState.focused && !resolvedSpec.readOnly) {
|
||||
bool changed = false;
|
||||
switch (static_cast<KeyCode>(event.keyCode)) {
|
||||
case KeyCode::Left:
|
||||
changed = MoveSelection(selectedIndex, resolvedSpec, -1);
|
||||
break;
|
||||
case KeyCode::Right:
|
||||
case KeyCode::Enter:
|
||||
changed = MoveSelection(selectedIndex, resolvedSpec, 1);
|
||||
break;
|
||||
case KeyCode::Home:
|
||||
changed = !resolvedSpec.options.empty() && selectedIndex != 0u;
|
||||
selectedIndex = 0u;
|
||||
break;
|
||||
case KeyCode::End:
|
||||
if (!resolvedSpec.options.empty()) {
|
||||
const std::size_t lastIndex = resolvedSpec.options.size() - 1u;
|
||||
changed = selectedIndex != lastIndex;
|
||||
selectedIndex = lastIndex;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (changed) {
|
||||
eventResult.selectionChanged = true;
|
||||
eventResult.selectedIndex = selectedIndex;
|
||||
eventResult.consumed = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
resolvedSpec.selectedIndex = selectedIndex;
|
||||
layout = BuildUIEditorEnumFieldLayout(bounds, resolvedSpec, metrics);
|
||||
if (state.hasPointerPosition) {
|
||||
state.fieldState.hoveredTarget = HitTestUIEditorEnumField(layout, state.pointerPosition).kind;
|
||||
} else {
|
||||
state.fieldState.hoveredTarget = UIEditorEnumFieldHitTargetKind::None;
|
||||
}
|
||||
|
||||
if (eventResult.consumed ||
|
||||
eventResult.selectionChanged ||
|
||||
eventResult.hitTarget.kind != UIEditorEnumFieldHitTargetKind::None) {
|
||||
interactionResult = eventResult;
|
||||
}
|
||||
}
|
||||
|
||||
return { layout, interactionResult };
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor
|
||||
477
new_editor/src/Core/UIEditorNumberFieldInteraction.cpp
Normal file
477
new_editor/src/Core/UIEditorNumberFieldInteraction.cpp
Normal file
@@ -0,0 +1,477 @@
|
||||
#include <XCEditor/Core/UIEditorNumberFieldInteraction.h>
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine::UI::Editor {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::XCEngine::Input::KeyCode;
|
||||
using ::XCEngine::UI::UIInputEvent;
|
||||
using ::XCEngine::UI::UIInputEventType;
|
||||
using ::XCEngine::UI::UIPointerButton;
|
||||
using ::XCEngine::UI::Text::HandleKeyDown;
|
||||
using ::XCEngine::UI::Text::InsertCharacter;
|
||||
using ::XCEngine::UI::Editor::Widgets::FormatUIEditorNumberFieldValue;
|
||||
using ::XCEngine::UI::Editor::Widgets::BuildUIEditorNumberFieldLayout;
|
||||
using ::XCEngine::UI::Editor::Widgets::HitTestUIEditorNumberField;
|
||||
using ::XCEngine::UI::Editor::Widgets::IsUIEditorNumberFieldPointInside;
|
||||
using ::XCEngine::UI::Editor::Widgets::UIEditorNumberFieldHitTarget;
|
||||
using ::XCEngine::UI::Editor::Widgets::UIEditorNumberFieldHitTargetKind;
|
||||
using ::XCEngine::UI::Editor::Widgets::UIEditorNumberFieldLayout;
|
||||
using ::XCEngine::UI::Editor::Widgets::UIEditorNumberFieldMetrics;
|
||||
using ::XCEngine::UI::Editor::Widgets::UIEditorNumberFieldSpec;
|
||||
|
||||
bool ShouldUsePointerPosition(const UIInputEvent& event) {
|
||||
switch (event.type) {
|
||||
case UIInputEventType::PointerMove:
|
||||
case UIInputEventType::PointerEnter:
|
||||
case UIInputEventType::PointerButtonDown:
|
||||
case UIInputEventType::PointerButtonUp:
|
||||
case UIInputEventType::PointerWheel:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsPermittedCharacter(const UIEditorNumberFieldSpec& spec, std::uint32_t character) {
|
||||
if (character >= static_cast<std::uint32_t>('0') &&
|
||||
character <= static_cast<std::uint32_t>('9')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (character == static_cast<std::uint32_t>('-') ||
|
||||
character == static_cast<std::uint32_t>('+')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !spec.integerMode && character == static_cast<std::uint32_t>('.');
|
||||
}
|
||||
|
||||
double NormalizeValue(const UIEditorNumberFieldSpec& spec, double value) {
|
||||
const double minValue = (std::min)(spec.minValue, spec.maxValue);
|
||||
const double maxValue = (std::max)(spec.minValue, spec.maxValue);
|
||||
value = (std::clamp)(value, minValue, maxValue);
|
||||
if (spec.integerMode) {
|
||||
value = static_cast<double>(std::llround(value));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
bool TryParseValue(
|
||||
const UIEditorNumberFieldSpec& spec,
|
||||
const std::string& text,
|
||||
double& outValue) {
|
||||
if (text.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
std::size_t consumed = 0u;
|
||||
const double parsed = std::stod(text, &consumed);
|
||||
if (consumed != text.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue = NormalizeValue(spec, parsed);
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void SyncDisplayText(
|
||||
UIEditorNumberFieldInteractionState& state,
|
||||
const UIEditorNumberFieldSpec& spec) {
|
||||
if (!state.numberFieldState.editing) {
|
||||
state.numberFieldState.displayText = FormatUIEditorNumberFieldValue(spec);
|
||||
}
|
||||
}
|
||||
|
||||
void SyncHoverTarget(
|
||||
UIEditorNumberFieldInteractionState& state,
|
||||
const UIEditorNumberFieldLayout& layout) {
|
||||
if (!state.hasPointerPosition) {
|
||||
state.numberFieldState.hoveredTarget = UIEditorNumberFieldHitTargetKind::None;
|
||||
return;
|
||||
}
|
||||
|
||||
state.numberFieldState.hoveredTarget =
|
||||
HitTestUIEditorNumberField(layout, state.pointerPosition).kind;
|
||||
}
|
||||
|
||||
bool BeginEdit(
|
||||
UIEditorNumberFieldInteractionState& state,
|
||||
const UIEditorNumberFieldSpec& spec,
|
||||
bool clearText) {
|
||||
if (spec.readOnly) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string baseline = FormatUIEditorNumberFieldValue(spec);
|
||||
const bool changed = state.editModel.BeginEdit(spec.fieldId, baseline);
|
||||
if (!changed &&
|
||||
state.editModel.HasActiveEdit() &&
|
||||
state.editModel.GetActiveFieldId() != spec.fieldId) {
|
||||
return false;
|
||||
}
|
||||
if (!changed && state.numberFieldState.editing) {
|
||||
return false;
|
||||
}
|
||||
|
||||
state.numberFieldState.editing = true;
|
||||
state.textInputState.value = clearText ? std::string() : baseline;
|
||||
state.textInputState.caret = state.textInputState.value.size();
|
||||
state.editModel.UpdateStagedValue(state.textInputState.value);
|
||||
state.numberFieldState.displayText = state.textInputState.value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CommitEdit(
|
||||
UIEditorNumberFieldInteractionState& state,
|
||||
UIEditorNumberFieldSpec& spec,
|
||||
UIEditorNumberFieldInteractionResult& result) {
|
||||
if (!state.numberFieldState.editing || !state.editModel.HasActiveEdit()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double parsedValue = spec.value;
|
||||
if (!TryParseValue(spec, state.textInputState.value, parsedValue)) {
|
||||
result.consumed = true;
|
||||
result.editCommitRejected = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
result.valueBefore = spec.value;
|
||||
spec.value = parsedValue;
|
||||
result.valueAfter = spec.value;
|
||||
result.valueChanged = result.valueBefore != result.valueAfter;
|
||||
result.editCommitted = true;
|
||||
result.consumed = true;
|
||||
result.committedText = FormatUIEditorNumberFieldValue(spec);
|
||||
|
||||
state.editModel.CommitEdit();
|
||||
state.textInputState = {};
|
||||
state.numberFieldState.editing = false;
|
||||
state.numberFieldState.displayText = result.committedText;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CancelEdit(
|
||||
UIEditorNumberFieldInteractionState& state,
|
||||
const UIEditorNumberFieldSpec& spec,
|
||||
UIEditorNumberFieldInteractionResult& result) {
|
||||
if (!state.numberFieldState.editing || !state.editModel.HasActiveEdit()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
state.editModel.CancelEdit();
|
||||
state.textInputState = {};
|
||||
state.numberFieldState.editing = false;
|
||||
state.numberFieldState.displayText = FormatUIEditorNumberFieldValue(spec);
|
||||
result.consumed = true;
|
||||
result.editCanceled = true;
|
||||
result.valueBefore = spec.value;
|
||||
result.valueAfter = spec.value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ApplyStep(
|
||||
UIEditorNumberFieldInteractionState& state,
|
||||
UIEditorNumberFieldSpec& spec,
|
||||
double direction,
|
||||
UIEditorNumberFieldInteractionResult& result) {
|
||||
if (spec.readOnly) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (state.numberFieldState.editing && !CommitEdit(state, spec, result)) {
|
||||
return result.editCommitRejected;
|
||||
}
|
||||
|
||||
const double step = spec.step == 0.0 ? 1.0 : spec.step;
|
||||
const double before = spec.value;
|
||||
spec.value = NormalizeValue(spec, spec.value + step * direction);
|
||||
|
||||
result.consumed = true;
|
||||
result.stepApplied = true;
|
||||
result.valueBefore = before;
|
||||
result.valueAfter = spec.value;
|
||||
result.stepDelta = step * direction;
|
||||
result.valueChanged = before != spec.value || result.valueChanged;
|
||||
state.numberFieldState.displayText = FormatUIEditorNumberFieldValue(spec);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ApplyKeyboardStep(
|
||||
UIEditorNumberFieldInteractionState& state,
|
||||
UIEditorNumberFieldSpec& spec,
|
||||
KeyCode keyCode,
|
||||
UIEditorNumberFieldInteractionResult& result) {
|
||||
switch (keyCode) {
|
||||
case KeyCode::Left:
|
||||
case KeyCode::Down:
|
||||
return ApplyStep(state, spec, -1.0, result);
|
||||
|
||||
case KeyCode::Right:
|
||||
case KeyCode::Up:
|
||||
return ApplyStep(state, spec, 1.0, result);
|
||||
|
||||
case KeyCode::Home: {
|
||||
if (spec.readOnly) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const double before = spec.value;
|
||||
spec.value = NormalizeValue(spec, (std::min)(spec.minValue, spec.maxValue));
|
||||
result.consumed = true;
|
||||
result.stepApplied = true;
|
||||
result.valueBefore = before;
|
||||
result.valueAfter = spec.value;
|
||||
result.valueChanged = before != spec.value;
|
||||
state.numberFieldState.displayText = FormatUIEditorNumberFieldValue(spec);
|
||||
return true;
|
||||
}
|
||||
|
||||
case KeyCode::End: {
|
||||
if (spec.readOnly) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const double before = spec.value;
|
||||
spec.value = NormalizeValue(spec, (std::max)(spec.minValue, spec.maxValue));
|
||||
result.consumed = true;
|
||||
result.stepApplied = true;
|
||||
result.valueBefore = before;
|
||||
result.valueAfter = spec.value;
|
||||
result.valueChanged = before != spec.value;
|
||||
state.numberFieldState.displayText = FormatUIEditorNumberFieldValue(spec);
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UIEditorNumberFieldInteractionFrame UpdateUIEditorNumberFieldInteraction(
|
||||
UIEditorNumberFieldInteractionState& state,
|
||||
UIEditorNumberFieldSpec& spec,
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const std::vector<UIInputEvent>& inputEvents,
|
||||
const UIEditorNumberFieldMetrics& metrics) {
|
||||
UIEditorNumberFieldLayout layout = BuildUIEditorNumberFieldLayout(bounds, spec, metrics);
|
||||
SyncDisplayText(state, spec);
|
||||
SyncHoverTarget(state, layout);
|
||||
|
||||
UIEditorNumberFieldInteractionResult interactionResult = {};
|
||||
for (const UIInputEvent& event : inputEvents) {
|
||||
if (ShouldUsePointerPosition(event)) {
|
||||
state.pointerPosition = event.position;
|
||||
state.hasPointerPosition = true;
|
||||
} else if (event.type == UIInputEventType::PointerLeave) {
|
||||
state.hasPointerPosition = false;
|
||||
}
|
||||
|
||||
UIEditorNumberFieldInteractionResult eventResult = {};
|
||||
switch (event.type) {
|
||||
case UIInputEventType::FocusGained:
|
||||
eventResult.focusChanged = !state.numberFieldState.focused;
|
||||
state.numberFieldState.focused = true;
|
||||
break;
|
||||
|
||||
case UIInputEventType::FocusLost:
|
||||
eventResult.focusChanged = state.numberFieldState.focused;
|
||||
state.numberFieldState.focused = false;
|
||||
state.numberFieldState.activeTarget = UIEditorNumberFieldHitTargetKind::None;
|
||||
state.hasPointerPosition = false;
|
||||
if (state.numberFieldState.editing) {
|
||||
CommitEdit(state, spec, eventResult);
|
||||
if (eventResult.editCommitRejected) {
|
||||
CancelEdit(state, spec, eventResult);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case UIInputEventType::PointerMove:
|
||||
case UIInputEventType::PointerEnter:
|
||||
case UIInputEventType::PointerLeave:
|
||||
break;
|
||||
|
||||
case UIInputEventType::PointerButtonDown: {
|
||||
const UIEditorNumberFieldHitTarget hitTarget =
|
||||
state.hasPointerPosition
|
||||
? HitTestUIEditorNumberField(layout, state.pointerPosition)
|
||||
: UIEditorNumberFieldHitTarget {};
|
||||
eventResult.hitTarget = hitTarget;
|
||||
|
||||
if (event.pointerButton != UIPointerButton::Left) {
|
||||
break;
|
||||
}
|
||||
|
||||
const bool insideField =
|
||||
state.hasPointerPosition &&
|
||||
IsUIEditorNumberFieldPointInside(layout.bounds, state.pointerPosition);
|
||||
if (insideField) {
|
||||
eventResult.focusChanged = !state.numberFieldState.focused;
|
||||
state.numberFieldState.focused = true;
|
||||
state.numberFieldState.activeTarget =
|
||||
hitTarget.kind == UIEditorNumberFieldHitTargetKind::None
|
||||
? UIEditorNumberFieldHitTargetKind::Row
|
||||
: hitTarget.kind;
|
||||
eventResult.consumed = true;
|
||||
} else {
|
||||
if (state.numberFieldState.editing) {
|
||||
CommitEdit(state, spec, eventResult);
|
||||
if (!eventResult.editCommitRejected) {
|
||||
eventResult.focusChanged = state.numberFieldState.focused;
|
||||
state.numberFieldState.focused = false;
|
||||
}
|
||||
} else if (state.numberFieldState.focused) {
|
||||
eventResult.focusChanged = true;
|
||||
state.numberFieldState.focused = false;
|
||||
}
|
||||
state.numberFieldState.activeTarget = UIEditorNumberFieldHitTargetKind::None;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case UIInputEventType::PointerButtonUp: {
|
||||
const UIEditorNumberFieldHitTarget hitTarget =
|
||||
state.hasPointerPosition
|
||||
? HitTestUIEditorNumberField(layout, state.pointerPosition)
|
||||
: UIEditorNumberFieldHitTarget {};
|
||||
eventResult.hitTarget = hitTarget;
|
||||
|
||||
if (event.pointerButton == UIPointerButton::Left) {
|
||||
const UIEditorNumberFieldHitTargetKind activeTarget = state.numberFieldState.activeTarget;
|
||||
state.numberFieldState.activeTarget = UIEditorNumberFieldHitTargetKind::None;
|
||||
|
||||
if (activeTarget == UIEditorNumberFieldHitTargetKind::DecrementButton &&
|
||||
hitTarget.kind == UIEditorNumberFieldHitTargetKind::DecrementButton) {
|
||||
ApplyStep(state, spec, -1.0, eventResult);
|
||||
} else if (
|
||||
activeTarget == UIEditorNumberFieldHitTargetKind::IncrementButton &&
|
||||
hitTarget.kind == UIEditorNumberFieldHitTargetKind::IncrementButton) {
|
||||
ApplyStep(state, spec, 1.0, eventResult);
|
||||
} else if (
|
||||
activeTarget == UIEditorNumberFieldHitTargetKind::ValueBox &&
|
||||
hitTarget.kind == UIEditorNumberFieldHitTargetKind::ValueBox) {
|
||||
if (!state.numberFieldState.editing) {
|
||||
eventResult.editStarted = BeginEdit(state, spec, false);
|
||||
}
|
||||
eventResult.consumed = true;
|
||||
} else if (hitTarget.kind == UIEditorNumberFieldHitTargetKind::Row) {
|
||||
eventResult.consumed = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case UIInputEventType::KeyDown:
|
||||
if (!state.numberFieldState.focused) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (state.numberFieldState.editing) {
|
||||
if (event.keyCode == static_cast<std::int32_t>(KeyCode::Escape)) {
|
||||
CancelEdit(state, spec, eventResult);
|
||||
break;
|
||||
}
|
||||
|
||||
const auto textResult =
|
||||
HandleKeyDown(state.textInputState, event.keyCode, event.modifiers);
|
||||
if (textResult.handled) {
|
||||
state.editModel.UpdateStagedValue(state.textInputState.value);
|
||||
state.numberFieldState.displayText = state.textInputState.value;
|
||||
eventResult.consumed = true;
|
||||
if (textResult.valueChanged) {
|
||||
eventResult.valueBefore = spec.value;
|
||||
eventResult.valueAfter = spec.value;
|
||||
}
|
||||
if (textResult.submitRequested) {
|
||||
CommitEdit(state, spec, eventResult);
|
||||
}
|
||||
}
|
||||
} else if (event.keyCode == static_cast<std::int32_t>(KeyCode::Enter)) {
|
||||
eventResult.editStarted = BeginEdit(state, spec, false);
|
||||
eventResult.consumed = eventResult.editStarted;
|
||||
} else {
|
||||
ApplyKeyboardStep(
|
||||
state,
|
||||
spec,
|
||||
static_cast<KeyCode>(event.keyCode),
|
||||
eventResult);
|
||||
}
|
||||
break;
|
||||
|
||||
case UIInputEventType::Character:
|
||||
if (!state.numberFieldState.focused ||
|
||||
spec.readOnly ||
|
||||
event.modifiers.control ||
|
||||
event.modifiers.alt ||
|
||||
event.modifiers.super ||
|
||||
!IsPermittedCharacter(spec, event.character)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!state.numberFieldState.editing) {
|
||||
eventResult.editStarted = BeginEdit(state, spec, true);
|
||||
}
|
||||
|
||||
if (InsertCharacter(state.textInputState, event.character)) {
|
||||
state.editModel.UpdateStagedValue(state.textInputState.value);
|
||||
state.numberFieldState.displayText = state.textInputState.value;
|
||||
eventResult.consumed = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
layout = BuildUIEditorNumberFieldLayout(bounds, spec, metrics);
|
||||
SyncDisplayText(state, spec);
|
||||
SyncHoverTarget(state, layout);
|
||||
if (eventResult.hitTarget.kind == UIEditorNumberFieldHitTargetKind::None &&
|
||||
state.hasPointerPosition) {
|
||||
eventResult.hitTarget = HitTestUIEditorNumberField(layout, state.pointerPosition);
|
||||
}
|
||||
|
||||
if (eventResult.consumed ||
|
||||
eventResult.focusChanged ||
|
||||
eventResult.valueChanged ||
|
||||
eventResult.stepApplied ||
|
||||
eventResult.editStarted ||
|
||||
eventResult.editCommitted ||
|
||||
eventResult.editCommitRejected ||
|
||||
eventResult.editCanceled ||
|
||||
eventResult.hitTarget.kind != UIEditorNumberFieldHitTargetKind::None) {
|
||||
interactionResult = std::move(eventResult);
|
||||
}
|
||||
}
|
||||
|
||||
layout = BuildUIEditorNumberFieldLayout(bounds, spec, metrics);
|
||||
SyncDisplayText(state, spec);
|
||||
SyncHoverTarget(state, layout);
|
||||
if (interactionResult.hitTarget.kind == UIEditorNumberFieldHitTargetKind::None &&
|
||||
state.hasPointerPosition) {
|
||||
interactionResult.hitTarget = HitTestUIEditorNumberField(layout, state.pointerPosition);
|
||||
}
|
||||
|
||||
return {
|
||||
std::move(layout),
|
||||
std::move(interactionResult)
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor
|
||||
133
new_editor/src/Widgets/UIEditorBoolField.cpp
Normal file
133
new_editor/src/Widgets/UIEditorBoolField.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
#include <XCEditor/Widgets/UIEditorBoolField.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace XCEngine::UI::Editor::Widgets {
|
||||
|
||||
namespace {
|
||||
|
||||
bool ContainsPoint(const ::XCEngine::UI::UIRect& rect, const ::XCEngine::UI::UIPoint& point) {
|
||||
return point.x >= rect.x &&
|
||||
point.x <= rect.x + rect.width &&
|
||||
point.y >= rect.y &&
|
||||
point.y <= rect.y + rect.height;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UIEditorBoolFieldLayout BuildUIEditorBoolFieldLayout(
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const UIEditorBoolFieldSpec& spec,
|
||||
const UIEditorBoolFieldMetrics& metrics) {
|
||||
UIEditorBoolFieldLayout layout = {};
|
||||
layout.bounds = bounds;
|
||||
|
||||
const float controlX = (std::min)(
|
||||
bounds.x + bounds.width - metrics.horizontalPadding - metrics.toggleWidth,
|
||||
bounds.x + metrics.controlColumnStart);
|
||||
const float labelWidth = (std::max)(
|
||||
0.0f,
|
||||
controlX - bounds.x - metrics.horizontalPadding - metrics.labelControlGap);
|
||||
layout.labelRect = ::XCEngine::UI::UIRect(
|
||||
bounds.x + metrics.horizontalPadding,
|
||||
bounds.y,
|
||||
labelWidth,
|
||||
bounds.height);
|
||||
layout.toggleRect = ::XCEngine::UI::UIRect(
|
||||
controlX,
|
||||
bounds.y + (std::max)(0.0f, (bounds.height - metrics.toggleHeight) * 0.5f),
|
||||
metrics.toggleWidth,
|
||||
metrics.toggleHeight);
|
||||
|
||||
const float knobSize = (std::max)(0.0f, layout.toggleRect.height - metrics.toggleKnobInset * 2.0f);
|
||||
const float knobX = spec.value
|
||||
? layout.toggleRect.x + layout.toggleRect.width - metrics.toggleKnobInset - knobSize
|
||||
: layout.toggleRect.x + metrics.toggleKnobInset;
|
||||
layout.knobRect = ::XCEngine::UI::UIRect(
|
||||
knobX,
|
||||
layout.toggleRect.y + metrics.toggleKnobInset,
|
||||
knobSize,
|
||||
knobSize);
|
||||
return layout;
|
||||
}
|
||||
|
||||
UIEditorBoolFieldHitTarget HitTestUIEditorBoolField(
|
||||
const UIEditorBoolFieldLayout& layout,
|
||||
const ::XCEngine::UI::UIPoint& point) {
|
||||
if (!ContainsPoint(layout.bounds, point)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (ContainsPoint(layout.toggleRect, point)) {
|
||||
return { UIEditorBoolFieldHitTargetKind::Toggle };
|
||||
}
|
||||
|
||||
return { UIEditorBoolFieldHitTargetKind::Row };
|
||||
}
|
||||
|
||||
void AppendUIEditorBoolFieldBackground(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const UIEditorBoolFieldLayout& layout,
|
||||
const UIEditorBoolFieldSpec& spec,
|
||||
const UIEditorBoolFieldState& state,
|
||||
const UIEditorBoolFieldPalette& palette,
|
||||
const UIEditorBoolFieldMetrics& metrics) {
|
||||
const bool hovered = state.hoveredTarget != UIEditorBoolFieldHitTargetKind::None;
|
||||
const ::XCEngine::UI::UIColor rowColor =
|
||||
state.active ? palette.rowActiveColor :
|
||||
(hovered ? palette.rowHoverColor : palette.surfaceColor);
|
||||
drawList.AddFilledRect(layout.bounds, rowColor, metrics.cornerRounding);
|
||||
drawList.AddRectOutline(
|
||||
layout.bounds,
|
||||
state.focused ? palette.focusedBorderColor : palette.borderColor,
|
||||
state.focused ? metrics.focusedBorderThickness : metrics.borderThickness,
|
||||
metrics.cornerRounding);
|
||||
|
||||
const ::XCEngine::UI::UIColor toggleColor =
|
||||
spec.readOnly ? palette.toggleReadOnlyColor :
|
||||
(spec.value ? palette.toggleOnColor : palette.toggleOffColor);
|
||||
drawList.AddFilledRect(layout.toggleRect, toggleColor, layout.toggleRect.height * 0.5f);
|
||||
drawList.AddRectOutline(
|
||||
layout.toggleRect,
|
||||
palette.toggleBorderColor,
|
||||
metrics.borderThickness,
|
||||
layout.toggleRect.height * 0.5f);
|
||||
drawList.AddFilledRect(layout.knobRect, palette.knobColor, layout.knobRect.height * 0.5f);
|
||||
}
|
||||
|
||||
void AppendUIEditorBoolFieldForeground(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const UIEditorBoolFieldLayout& layout,
|
||||
const UIEditorBoolFieldSpec& spec,
|
||||
const UIEditorBoolFieldPalette& palette,
|
||||
const UIEditorBoolFieldMetrics& metrics) {
|
||||
drawList.PushClipRect(layout.labelRect);
|
||||
drawList.AddText(
|
||||
::XCEngine::UI::UIPoint(layout.labelRect.x, layout.labelRect.y + metrics.textInsetY),
|
||||
spec.label,
|
||||
palette.labelColor,
|
||||
12.0f);
|
||||
drawList.PopClipRect();
|
||||
|
||||
drawList.AddText(
|
||||
::XCEngine::UI::UIPoint(
|
||||
layout.toggleRect.x - 42.0f,
|
||||
layout.bounds.y + metrics.textInsetY),
|
||||
spec.value ? "On" : "Off",
|
||||
palette.valueColor,
|
||||
12.0f);
|
||||
}
|
||||
|
||||
void AppendUIEditorBoolField(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const UIEditorBoolFieldSpec& spec,
|
||||
const UIEditorBoolFieldState& state,
|
||||
const UIEditorBoolFieldPalette& palette,
|
||||
const UIEditorBoolFieldMetrics& metrics) {
|
||||
const UIEditorBoolFieldLayout layout = BuildUIEditorBoolFieldLayout(bounds, spec, metrics);
|
||||
AppendUIEditorBoolFieldBackground(drawList, layout, spec, state, palette, metrics);
|
||||
AppendUIEditorBoolFieldForeground(drawList, layout, spec, palette, metrics);
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Widgets
|
||||
184
new_editor/src/Widgets/UIEditorEnumField.cpp
Normal file
184
new_editor/src/Widgets/UIEditorEnumField.cpp
Normal file
@@ -0,0 +1,184 @@
|
||||
#include <XCEditor/Widgets/UIEditorEnumField.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace XCEngine::UI::Editor::Widgets {
|
||||
|
||||
namespace {
|
||||
|
||||
bool ContainsPoint(const ::XCEngine::UI::UIRect& rect, const ::XCEngine::UI::UIPoint& point) {
|
||||
return point.x >= rect.x &&
|
||||
point.x <= rect.x + rect.width &&
|
||||
point.y >= rect.y &&
|
||||
point.y <= rect.y + rect.height;
|
||||
}
|
||||
|
||||
std::size_t ClampSelectedIndex(const UIEditorEnumFieldSpec& spec) {
|
||||
if (spec.options.empty()) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
return (std::min)(spec.selectedIndex, spec.options.size() - 1u);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string ResolveUIEditorEnumFieldValueText(const UIEditorEnumFieldSpec& spec) {
|
||||
if (spec.options.empty()) {
|
||||
return "(none)";
|
||||
}
|
||||
|
||||
return spec.options[ClampSelectedIndex(spec)];
|
||||
}
|
||||
|
||||
UIEditorEnumFieldLayout BuildUIEditorEnumFieldLayout(
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const UIEditorEnumFieldSpec& spec,
|
||||
const UIEditorEnumFieldMetrics& metrics) {
|
||||
UIEditorEnumFieldLayout layout = {};
|
||||
layout.bounds = bounds;
|
||||
layout.bounds.height = bounds.height > 0.0f ? bounds.height : metrics.rowHeight;
|
||||
|
||||
const float controlWidth =
|
||||
metrics.buttonWidth * 2.0f + metrics.controlGap * 2.0f + metrics.valueBoxMinWidth;
|
||||
const float controlX = (std::min)(
|
||||
layout.bounds.x + layout.bounds.width - metrics.horizontalPadding - controlWidth,
|
||||
bounds.x + metrics.controlColumnStart);
|
||||
const float labelWidth = (std::max)(
|
||||
0.0f,
|
||||
controlX - layout.bounds.x - metrics.horizontalPadding - metrics.labelControlGap);
|
||||
layout.labelRect = ::XCEngine::UI::UIRect(
|
||||
layout.bounds.x + metrics.horizontalPadding,
|
||||
layout.bounds.y,
|
||||
labelWidth,
|
||||
layout.bounds.height);
|
||||
|
||||
const float controlY =
|
||||
layout.bounds.y + (std::max)(0.0f, (layout.bounds.height - metrics.valueBoxHeight) * 0.5f);
|
||||
layout.controlRect = ::XCEngine::UI::UIRect(
|
||||
controlX,
|
||||
controlY,
|
||||
controlWidth,
|
||||
metrics.valueBoxHeight);
|
||||
layout.previousRect = ::XCEngine::UI::UIRect(controlX, controlY, metrics.buttonWidth, metrics.valueBoxHeight);
|
||||
const float valueX = layout.previousRect.x + layout.previousRect.width + metrics.controlGap;
|
||||
layout.nextRect = ::XCEngine::UI::UIRect(
|
||||
controlX + controlWidth - metrics.buttonWidth,
|
||||
controlY,
|
||||
metrics.buttonWidth,
|
||||
metrics.valueBoxHeight);
|
||||
layout.valueRect = ::XCEngine::UI::UIRect(
|
||||
valueX,
|
||||
controlY,
|
||||
(std::max)(0.0f, layout.nextRect.x - valueX - metrics.controlGap),
|
||||
metrics.valueBoxHeight);
|
||||
return layout;
|
||||
}
|
||||
|
||||
UIEditorEnumFieldHitTarget HitTestUIEditorEnumField(
|
||||
const UIEditorEnumFieldLayout& layout,
|
||||
const ::XCEngine::UI::UIPoint& point) {
|
||||
if (!ContainsPoint(layout.bounds, point)) {
|
||||
return {};
|
||||
}
|
||||
if (ContainsPoint(layout.previousRect, point)) {
|
||||
return { UIEditorEnumFieldHitTargetKind::PreviousButton };
|
||||
}
|
||||
if (ContainsPoint(layout.nextRect, point)) {
|
||||
return { UIEditorEnumFieldHitTargetKind::NextButton };
|
||||
}
|
||||
if (ContainsPoint(layout.valueRect, point)) {
|
||||
return { UIEditorEnumFieldHitTargetKind::ValueBox };
|
||||
}
|
||||
return { UIEditorEnumFieldHitTargetKind::Row };
|
||||
}
|
||||
|
||||
void AppendUIEditorEnumFieldBackground(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const UIEditorEnumFieldLayout& layout,
|
||||
const UIEditorEnumFieldSpec& spec,
|
||||
const UIEditorEnumFieldState& state,
|
||||
const UIEditorEnumFieldPalette& palette,
|
||||
const UIEditorEnumFieldMetrics& metrics) {
|
||||
const bool hovered = state.hoveredTarget != UIEditorEnumFieldHitTargetKind::None;
|
||||
drawList.AddFilledRect(
|
||||
layout.bounds,
|
||||
state.active ? palette.rowActiveColor : (hovered ? palette.rowHoverColor : palette.surfaceColor),
|
||||
metrics.cornerRounding);
|
||||
drawList.AddRectOutline(
|
||||
layout.bounds,
|
||||
state.focused ? palette.focusedBorderColor : palette.borderColor,
|
||||
state.focused ? metrics.focusedBorderThickness : metrics.borderThickness,
|
||||
metrics.cornerRounding);
|
||||
|
||||
const ::XCEngine::UI::UIColor controlColor = spec.readOnly ? palette.readOnlyColor : palette.valueBoxColor;
|
||||
drawList.AddFilledRect(layout.valueRect, controlColor, metrics.cornerRounding);
|
||||
drawList.AddRectOutline(layout.valueRect, palette.controlBorderColor, metrics.borderThickness, metrics.cornerRounding);
|
||||
|
||||
const ::XCEngine::UI::UIColor prevColor =
|
||||
state.hoveredTarget == UIEditorEnumFieldHitTargetKind::PreviousButton
|
||||
? palette.buttonHoverColor
|
||||
: palette.buttonColor;
|
||||
const ::XCEngine::UI::UIColor nextColor =
|
||||
state.hoveredTarget == UIEditorEnumFieldHitTargetKind::NextButton
|
||||
? palette.buttonHoverColor
|
||||
: palette.buttonColor;
|
||||
drawList.AddFilledRect(layout.previousRect, spec.readOnly ? palette.readOnlyColor : prevColor, metrics.cornerRounding);
|
||||
drawList.AddFilledRect(layout.nextRect, spec.readOnly ? palette.readOnlyColor : nextColor, metrics.cornerRounding);
|
||||
drawList.AddRectOutline(layout.previousRect, palette.controlBorderColor, metrics.borderThickness, metrics.cornerRounding);
|
||||
drawList.AddRectOutline(layout.nextRect, palette.controlBorderColor, metrics.borderThickness, metrics.cornerRounding);
|
||||
}
|
||||
|
||||
void AppendUIEditorEnumFieldForeground(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const UIEditorEnumFieldLayout& layout,
|
||||
const UIEditorEnumFieldSpec& spec,
|
||||
const UIEditorEnumFieldPalette& palette,
|
||||
const UIEditorEnumFieldMetrics& metrics) {
|
||||
drawList.PushClipRect(layout.labelRect);
|
||||
drawList.AddText(
|
||||
::XCEngine::UI::UIPoint(layout.labelRect.x, layout.labelRect.y + metrics.labelTextInsetY),
|
||||
spec.label,
|
||||
palette.labelColor,
|
||||
12.0f);
|
||||
drawList.PopClipRect();
|
||||
|
||||
drawList.PushClipRect(layout.valueRect);
|
||||
drawList.AddText(
|
||||
::XCEngine::UI::UIPoint(
|
||||
layout.valueRect.x + metrics.valueTextInsetX,
|
||||
layout.valueRect.y + metrics.valueTextInsetY),
|
||||
ResolveUIEditorEnumFieldValueText(spec),
|
||||
palette.valueColor,
|
||||
12.0f);
|
||||
drawList.PopClipRect();
|
||||
|
||||
drawList.AddText(
|
||||
::XCEngine::UI::UIPoint(
|
||||
layout.previousRect.x + metrics.buttonTextInsetX,
|
||||
layout.previousRect.y + metrics.buttonTextInsetY),
|
||||
"<",
|
||||
palette.valueColor,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
::XCEngine::UI::UIPoint(
|
||||
layout.nextRect.x + metrics.buttonTextInsetX,
|
||||
layout.nextRect.y + metrics.buttonTextInsetY),
|
||||
">",
|
||||
palette.valueColor,
|
||||
12.0f);
|
||||
}
|
||||
|
||||
void AppendUIEditorEnumField(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const UIEditorEnumFieldSpec& spec,
|
||||
const UIEditorEnumFieldState& state,
|
||||
const UIEditorEnumFieldPalette& palette,
|
||||
const UIEditorEnumFieldMetrics& metrics) {
|
||||
const UIEditorEnumFieldLayout layout = BuildUIEditorEnumFieldLayout(bounds, spec, metrics);
|
||||
AppendUIEditorEnumFieldBackground(drawList, layout, spec, state, palette, metrics);
|
||||
AppendUIEditorEnumFieldForeground(drawList, layout, spec, palette, metrics);
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Widgets
|
||||
284
new_editor/src/Widgets/UIEditorNumberField.cpp
Normal file
284
new_editor/src/Widgets/UIEditorNumberField.cpp
Normal file
@@ -0,0 +1,284 @@
|
||||
#include <XCEditor/Widgets/UIEditorNumberField.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
namespace XCEngine::UI::Editor::Widgets {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::XCEngine::UI::UIDrawList;
|
||||
using ::XCEngine::UI::UIPoint;
|
||||
using ::XCEngine::UI::UIRect;
|
||||
|
||||
float ClampNonNegative(float value) {
|
||||
return (std::max)(0.0f, value);
|
||||
}
|
||||
|
||||
double NormalizeRangeValue(const UIEditorNumberFieldSpec& spec, double value) {
|
||||
const double minValue = (std::min)(spec.minValue, spec.maxValue);
|
||||
const double maxValue = (std::max)(spec.minValue, spec.maxValue);
|
||||
value = (std::clamp)(value, minValue, maxValue);
|
||||
if (spec.integerMode) {
|
||||
value = static_cast<double>(std::llround(value));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
std::string FormatNumberValue(double value, bool integerMode) {
|
||||
std::ostringstream stream = {};
|
||||
if (integerMode) {
|
||||
stream << static_cast<long long>(std::llround(value));
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
stream << std::fixed << std::setprecision(3) << value;
|
||||
std::string text = stream.str();
|
||||
while (!text.empty() && text.back() == '0') {
|
||||
text.pop_back();
|
||||
}
|
||||
if (!text.empty() && text.back() == '.') {
|
||||
text.pop_back();
|
||||
}
|
||||
return text.empty() ? "0" : text;
|
||||
}
|
||||
|
||||
::XCEngine::UI::UIColor ResolveRowFillColor(
|
||||
const UIEditorNumberFieldState& state,
|
||||
const UIEditorNumberFieldPalette& palette) {
|
||||
if (state.activeTarget != UIEditorNumberFieldHitTargetKind::None) {
|
||||
return palette.rowActiveColor;
|
||||
}
|
||||
if (state.hoveredTarget != UIEditorNumberFieldHitTargetKind::None) {
|
||||
return palette.rowHoverColor;
|
||||
}
|
||||
return palette.surfaceColor;
|
||||
}
|
||||
|
||||
::XCEngine::UI::UIColor ResolveValueFillColor(
|
||||
const UIEditorNumberFieldSpec& spec,
|
||||
const UIEditorNumberFieldState& state,
|
||||
const UIEditorNumberFieldPalette& palette) {
|
||||
if (spec.readOnly) {
|
||||
return palette.readOnlyColor;
|
||||
}
|
||||
if (state.editing) {
|
||||
return palette.valueBoxEditingColor;
|
||||
}
|
||||
if (state.hoveredTarget == UIEditorNumberFieldHitTargetKind::ValueBox) {
|
||||
return palette.valueBoxHoverColor;
|
||||
}
|
||||
return palette.valueBoxColor;
|
||||
}
|
||||
|
||||
::XCEngine::UI::UIColor ResolveButtonFillColor(
|
||||
const UIEditorNumberFieldSpec& spec,
|
||||
const UIEditorNumberFieldState& state,
|
||||
UIEditorNumberFieldHitTargetKind targetKind,
|
||||
const UIEditorNumberFieldPalette& palette) {
|
||||
if (spec.readOnly) {
|
||||
return palette.readOnlyColor;
|
||||
}
|
||||
if (state.activeTarget == targetKind) {
|
||||
return palette.buttonActiveColor;
|
||||
}
|
||||
if (state.hoveredTarget == targetKind) {
|
||||
return palette.buttonHoverColor;
|
||||
}
|
||||
return palette.buttonColor;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool IsUIEditorNumberFieldPointInside(
|
||||
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;
|
||||
}
|
||||
|
||||
std::string FormatUIEditorNumberFieldValue(const UIEditorNumberFieldSpec& spec) {
|
||||
return FormatNumberValue(NormalizeRangeValue(spec, spec.value), spec.integerMode);
|
||||
}
|
||||
|
||||
UIEditorNumberFieldLayout BuildUIEditorNumberFieldLayout(
|
||||
const UIRect& bounds,
|
||||
const UIEditorNumberFieldSpec&,
|
||||
const UIEditorNumberFieldMetrics& metrics) {
|
||||
const float rowHeight = bounds.height > 0.0f ? bounds.height : metrics.rowHeight;
|
||||
const UIRect rowBounds(bounds.x, bounds.y, bounds.width, rowHeight);
|
||||
|
||||
const float controlStartX =
|
||||
rowBounds.x + (std::min)(metrics.controlColumnStart, rowBounds.width * 0.6f);
|
||||
const float controlInsetY = (std::min)(metrics.controlInsetY, rowBounds.height * 0.25f);
|
||||
const float controlHeight = ClampNonNegative(rowBounds.height - controlInsetY * 2.0f);
|
||||
const float controlWidth = ClampNonNegative(
|
||||
rowBounds.width - (controlStartX - rowBounds.x) - metrics.horizontalPadding);
|
||||
const float maxButtonWidth = (std::max)(0.0f, (controlWidth - metrics.buttonGap * 2.0f) * 0.2f);
|
||||
const float buttonWidth = ClampNonNegative((std::min)(metrics.buttonWidth, maxButtonWidth));
|
||||
const float remainingValueWidth =
|
||||
ClampNonNegative(controlWidth - buttonWidth * 2.0f - metrics.buttonGap * 2.0f);
|
||||
const float valueWidth = (std::min)(
|
||||
(std::max)(metrics.valueBoxMinWidth, remainingValueWidth),
|
||||
remainingValueWidth);
|
||||
const float controlActualWidth =
|
||||
buttonWidth * 2.0f + metrics.buttonGap * 2.0f + valueWidth;
|
||||
const float controlX =
|
||||
rowBounds.x + rowBounds.width - metrics.horizontalPadding - controlActualWidth;
|
||||
|
||||
UIEditorNumberFieldLayout layout = {};
|
||||
layout.bounds = rowBounds;
|
||||
layout.labelRect = UIRect(
|
||||
rowBounds.x + metrics.horizontalPadding,
|
||||
rowBounds.y,
|
||||
ClampNonNegative(controlX - metrics.labelControlGap - rowBounds.x - metrics.horizontalPadding),
|
||||
rowBounds.height);
|
||||
layout.controlRect = UIRect(controlX, rowBounds.y + controlInsetY, controlActualWidth, controlHeight);
|
||||
layout.decrementRect = UIRect(
|
||||
layout.controlRect.x,
|
||||
layout.controlRect.y,
|
||||
buttonWidth,
|
||||
layout.controlRect.height);
|
||||
layout.valueRect = UIRect(
|
||||
layout.decrementRect.x + layout.decrementRect.width + metrics.buttonGap,
|
||||
layout.controlRect.y,
|
||||
valueWidth,
|
||||
layout.controlRect.height);
|
||||
layout.incrementRect = UIRect(
|
||||
layout.valueRect.x + layout.valueRect.width + metrics.buttonGap,
|
||||
layout.controlRect.y,
|
||||
buttonWidth,
|
||||
layout.controlRect.height);
|
||||
return layout;
|
||||
}
|
||||
|
||||
UIEditorNumberFieldHitTarget HitTestUIEditorNumberField(
|
||||
const UIEditorNumberFieldLayout& layout,
|
||||
const UIPoint& point) {
|
||||
if (IsUIEditorNumberFieldPointInside(layout.decrementRect, point)) {
|
||||
return { UIEditorNumberFieldHitTargetKind::DecrementButton };
|
||||
}
|
||||
if (IsUIEditorNumberFieldPointInside(layout.incrementRect, point)) {
|
||||
return { UIEditorNumberFieldHitTargetKind::IncrementButton };
|
||||
}
|
||||
if (IsUIEditorNumberFieldPointInside(layout.valueRect, point)) {
|
||||
return { UIEditorNumberFieldHitTargetKind::ValueBox };
|
||||
}
|
||||
if (IsUIEditorNumberFieldPointInside(layout.bounds, point)) {
|
||||
return { UIEditorNumberFieldHitTargetKind::Row };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void AppendUIEditorNumberFieldBackground(
|
||||
UIDrawList& drawList,
|
||||
const UIEditorNumberFieldLayout& layout,
|
||||
const UIEditorNumberFieldSpec& spec,
|
||||
const UIEditorNumberFieldState& state,
|
||||
const UIEditorNumberFieldPalette& palette,
|
||||
const UIEditorNumberFieldMetrics& metrics) {
|
||||
drawList.AddFilledRect(layout.bounds, ResolveRowFillColor(state, palette), metrics.cornerRounding);
|
||||
drawList.AddRectOutline(
|
||||
layout.bounds,
|
||||
state.focused ? palette.focusedBorderColor : palette.borderColor,
|
||||
state.focused ? metrics.focusedBorderThickness : metrics.borderThickness,
|
||||
metrics.cornerRounding);
|
||||
|
||||
drawList.AddFilledRect(
|
||||
layout.valueRect,
|
||||
ResolveValueFillColor(spec, state, palette),
|
||||
metrics.valueBoxRounding);
|
||||
drawList.AddRectOutline(
|
||||
layout.valueRect,
|
||||
palette.controlBorderColor,
|
||||
metrics.borderThickness,
|
||||
metrics.valueBoxRounding);
|
||||
|
||||
drawList.AddFilledRect(
|
||||
layout.decrementRect,
|
||||
ResolveButtonFillColor(spec, state, UIEditorNumberFieldHitTargetKind::DecrementButton, palette),
|
||||
metrics.buttonRounding);
|
||||
drawList.AddRectOutline(
|
||||
layout.decrementRect,
|
||||
palette.controlBorderColor,
|
||||
metrics.borderThickness,
|
||||
metrics.buttonRounding);
|
||||
|
||||
drawList.AddFilledRect(
|
||||
layout.incrementRect,
|
||||
ResolveButtonFillColor(spec, state, UIEditorNumberFieldHitTargetKind::IncrementButton, palette),
|
||||
metrics.buttonRounding);
|
||||
drawList.AddRectOutline(
|
||||
layout.incrementRect,
|
||||
palette.controlBorderColor,
|
||||
metrics.borderThickness,
|
||||
metrics.buttonRounding);
|
||||
}
|
||||
|
||||
void AppendUIEditorNumberFieldForeground(
|
||||
UIDrawList& drawList,
|
||||
const UIEditorNumberFieldLayout& layout,
|
||||
const UIEditorNumberFieldSpec& spec,
|
||||
const UIEditorNumberFieldPalette& palette,
|
||||
const UIEditorNumberFieldMetrics& metrics) {
|
||||
drawList.PushClipRect(layout.labelRect);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.labelRect.x, layout.labelRect.y + metrics.labelTextInsetY),
|
||||
spec.label,
|
||||
palette.labelColor,
|
||||
13.0f);
|
||||
drawList.PopClipRect();
|
||||
|
||||
drawList.AddText(
|
||||
UIPoint(layout.decrementRect.x + 8.0f, layout.decrementRect.y + metrics.buttonTextInsetY),
|
||||
"-",
|
||||
palette.stepTextColor,
|
||||
13.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.incrementRect.x + 7.0f, layout.incrementRect.y + metrics.buttonTextInsetY),
|
||||
"+",
|
||||
palette.stepTextColor,
|
||||
13.0f);
|
||||
|
||||
drawList.PushClipRect(layout.valueRect);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.valueRect.x + metrics.valueTextInsetX, layout.valueRect.y + metrics.valueTextInsetY),
|
||||
FormatUIEditorNumberFieldValue(spec),
|
||||
spec.readOnly ? palette.readOnlyValueColor : palette.valueColor,
|
||||
13.0f);
|
||||
drawList.PopClipRect();
|
||||
}
|
||||
|
||||
void AppendUIEditorNumberField(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& bounds,
|
||||
const UIEditorNumberFieldSpec& spec,
|
||||
const UIEditorNumberFieldState& state,
|
||||
const UIEditorNumberFieldPalette& palette,
|
||||
const UIEditorNumberFieldMetrics& metrics) {
|
||||
const UIEditorNumberFieldLayout layout = BuildUIEditorNumberFieldLayout(bounds, spec, metrics);
|
||||
AppendUIEditorNumberFieldBackground(drawList, layout, spec, state, palette, metrics);
|
||||
AppendUIEditorNumberFieldForeground(drawList, layout, spec, palette, metrics);
|
||||
|
||||
if (state.editing) {
|
||||
drawList.AddFilledRect(layout.valueRect, palette.valueBoxEditingColor, metrics.valueBoxRounding);
|
||||
drawList.AddRectOutline(
|
||||
layout.valueRect,
|
||||
palette.controlBorderColor,
|
||||
metrics.borderThickness,
|
||||
metrics.valueBoxRounding);
|
||||
drawList.PushClipRect(layout.valueRect);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.valueRect.x + metrics.valueTextInsetX, layout.valueRect.y + metrics.valueTextInsetY),
|
||||
state.displayText,
|
||||
palette.valueColor,
|
||||
13.0f);
|
||||
drawList.PopClipRect();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Widgets
|
||||
Reference in New Issue
Block a user