diff --git a/src/renderer/a2ui-renderer.h b/src/renderer/a2ui-renderer.h index f1467f5..1a5373a 100644 --- a/src/renderer/a2ui-renderer.h +++ b/src/renderer/a2ui-renderer.h @@ -93,6 +93,14 @@ class A2uiRenderer : public Dali::ConnectionTracker Dali::Ui::View RenderTextField(const ComponentModel& comp, DataContext& ctx); Dali::Ui::View RenderCheckBox(const ComponentModel& comp, DataContext& ctx); Dali::Ui::View RenderChoicePicker(const ComponentModel& comp, DataContext& ctx); + Dali::Ui::View RenderChoicePickerChips(const ComponentModel& comp, DataContext& ctx, + const std::string& boundPath, + const std::string& currentValue, + const Dali::Ui::TreeNode* optionsNode); + Dali::Ui::View RenderChoicePickerRadio(const ComponentModel& comp, DataContext& ctx, + const std::string& boundPath, + const std::string& currentValue, + const Dali::Ui::TreeNode* optionsNode); Dali::Ui::View RenderSlider(const ComponentModel& comp, DataContext& ctx); Dali::Ui::View RenderDateTimeInput(const ComponentModel& comp, DataContext& ctx); Dali::Ui::View RenderProgressBar(const ComponentModel& comp, DataContext& ctx); diff --git a/src/renderer/components/choice-picker.cpp b/src/renderer/components/choice-picker.cpp index 563c315..cd918ae 100644 --- a/src/renderer/components/choice-picker.cpp +++ b/src/renderer/components/choice-picker.cpp @@ -3,10 +3,127 @@ namespace A2ui { +// Helper structure for choice picker options +struct ChoiceOptionData { + std::string label; + std::string value; +}; + +// Extract option label/value from TreeNode +ChoiceOptionData ExtractOptionData(const TreeNode& optNode) { + const TreeNode* optLabelNode = optNode.Find("label"); + const TreeNode* optValueNode = optNode.Find("value"); + + std::string optLabel = (optLabelNode && optLabelNode->GetType() == TreeNode::STRING) + ? optLabelNode->GetString() : ""; + std::string optValue = (optValueNode && optValueNode->GetType() == TreeNode::STRING) + ? optValueNode->GetString() : ""; + + return {optLabel, optValue}; +} + View A2uiRenderer::RenderChoicePicker(const ComponentModel& comp, DataContext& ctx) { if(!comp.rawNode) return View::New(); + std::string displayStyle = GetNodeString(*comp.rawNode, "displayStyle", ""); + + // Common: Get bound path and current value + const TreeNode* valueNode = comp.rawNode->Find("value"); + std::string boundPath = valueNode ? GetBoundPath(valueNode, ctx) : ""; + const Dali::Ui::TreeNode* arrayNode = !boundPath.empty() ? ctx.GetDataModel().ResolvePath(boundPath) : nullptr; + std::string currentValue = ""; + if(arrayNode && arrayNode->GetType() == TreeNode::ARRAY && arrayNode->CBegin() != arrayNode->CEnd()) { + // Check if array is not empty and get the first element's value + currentValue = (*arrayNode->CBegin()).second.GetString(); + } else if (arrayNode) { + currentValue = ctx.GetDataModel().GetString(boundPath); + } + + // Common: Validate options + const TreeNode* optionsNode = comp.rawNode->Find("options"); + if(!optionsNode || optionsNode->GetType() != TreeNode::ARRAY) { + return View::New(); + } + + if(displayStyle == "chips") { + return RenderChoicePickerChips(comp, ctx, boundPath, currentValue, optionsNode); + } else { + return RenderChoicePickerRadio(comp, ctx, boundPath, currentValue, optionsNode); + } +} + +// Chips style rendering +View A2uiRenderer::RenderChoicePickerChips(const ComponentModel& comp, DataContext& ctx, + const std::string& boundPath, + const std::string& currentValue, + const TreeNode* optionsNode) +{ + FlexLayout container = FlexLayout::New(); + container.SetDirection(FlexDirection::ROW); + container.SetAlignItems(FlexAlign::CENTER); + container.SetRequestedWidth(WRAP_CONTENT); + container.SetRequestedHeight(WRAP_CONTENT); + container.SetMargin(Extents(4, 4, 0, 0)); + + // Collect option views for reactive updates + std::vector> optionViews; + + for(auto it = optionsNode->CBegin(); it != optionsNode->CEnd(); ++it) { + auto optData = ExtractOptionData((*it).second); + const std::string& optLabel = optData.label; + const std::string& optValue = optData.value; + + // Option label + Label optLbl = Label::New(optLabel.c_str()); + optLbl.SetFontSize(15.0f); + optLbl.SetRequestedWidth(WRAP_CONTENT); + optLbl.SetRequestedHeight(WRAP_CONTENT); + optLbl.SetMargin(Extents(8, 0, 0, 0)); + optLbl.SetPadding(Extents(10, 10, 8, 8)); + optLbl.SetCornerRadius(10); + + bool selected = (optValue == currentValue); + optLbl.SetTextColor(selected ? COLOR_CHECK_OFF : COLOR_CHECK_ON); + optLbl.SetBackgroundColor(selected ? COLOR_CHECK_ON : COLOR_CHECK_OFF); + + optionViews.push_back({optLbl, optValue}); + + // Click handler + if(!boundPath.empty()) { + optLbl.AsInteractive([this, optValue, boundPath, ctx](InteractiveTrait& trait) mutable { + trait.ClickedSignal().Connect(this, + [optValue, boundPath, ctx](View, const InputEvent&) mutable -> bool { + ctx.GetDataModel().SetValue(boundPath, optValue); + return true; + }); + }); + } + + container.Add(optLbl); + } + + // Watch for value changes → update all option views + if(!boundPath.empty()) { + ctx.GetDataModel().Watch(boundPath, + [optionViews](const std::string&, const std::string& val) mutable { + for(auto& pair : optionViews) { + bool selected = (pair.second == val); + pair.first.SetBackgroundColor(selected ? COLOR_CHECK_ON : COLOR_CHECK_OFF); + pair.first.SetTextColor(selected ? COLOR_CHECK_OFF : COLOR_CHECK_ON); + } + }); + } + + return container; +} + +// Radio style rendering +View A2uiRenderer::RenderChoicePickerRadio(const ComponentModel& comp, DataContext& ctx, + const std::string& boundPath, + const std::string& currentValue, + const TreeNode* optionsNode) +{ FlexLayout container = FlexLayout::New(); container.SetDirection(FlexDirection::COLUMN); container.SetRequestedWidth(MATCH_PARENT); @@ -26,29 +143,14 @@ View A2uiRenderer::RenderChoicePicker(const ComponentModel& comp, DataContext& c container.Add(label); } - const TreeNode* valueNode = comp.rawNode->Find("value"); - std::string boundPath = valueNode ? GetBoundPath(valueNode, ctx) : ""; - std::string currentValue = !boundPath.empty() ? ctx.GetDataModel().GetString(boundPath) : ""; - - const TreeNode* optionsNode = comp.rawNode->Find("options"); - if(!optionsNode || optionsNode->GetType() != TreeNode::ARRAY) - { - return container; - } - // Collect option views for reactive updates std::vector> optionIcons; for(auto it = optionsNode->CBegin(); it != optionsNode->CEnd(); ++it) { - const TreeNode& optNode = (*it).second; - const TreeNode* optLabelNode = optNode.Find("label"); - const TreeNode* optValueNode = optNode.Find("value"); - - std::string optLabel = (optLabelNode && optLabelNode->GetType() == TreeNode::STRING) - ? optLabelNode->GetString() : ""; - std::string optValue = (optValueNode && optValueNode->GetType() == TreeNode::STRING) - ? optValueNode->GetString() : ""; + auto optData = ExtractOptionData((*it).second); + const std::string& optLabel = optData.label; + const std::string& optValue = optData.value; FlexLayout optRow = FlexLayout::New(); optRow.SetDirection(FlexDirection::ROW);